`timescale 1ns / 1ps

module tb_fix_msg_decoder;
    reg clk;
    reg rst;
    reg [7:0] data_in;

    wire valid;
    wire [7:0] msg_type;
    wire [63:0] sender_id;
    wire [31:0] msg_seq_num;

    localparam NUM_TESTS = 9;
    localparam MAX_MSG_LEN = 62;

    reg [7:0] test_data [0:NUM_TESTS-1][0:MAX_MSG_LEN-1];
    integer test_msg_len [0:NUM_TESTS-1];
    reg [7:0] exp_msg_type [0:NUM_TESTS-1];
    reg [63:0] exp_sender_id [0:NUM_TESTS-1];
    reg [31:0] exp_msg_seq_num [0:NUM_TESTS-1];

    integer errors = 0;
    integer tests_run = 0;
    integer i;
    integer j;

    fix_msg_decoder uut (
        .clk(clk),
        .rst(rst),
        .data_in(data_in),
        .valid(valid),
        .msg_type(msg_type),
        .sender_id(sender_id),
        .msg_seq_num(msg_seq_num)
    );

    initial begin
        clk = 1'b0;
        forever #5 clk = ~clk;
    end

    task check_outputs;
        input [255:0] tag;
        input expect_valid;
        input integer test_idx;
        begin
            tests_run = tests_run + 1;

            if (valid !== expect_valid) begin
                $display("ERROR [%s]: valid mismatch. Expected %b, got %b", tag, expect_valid, valid);
                errors = errors + 1;
            end

            if (expect_valid) begin
                if (msg_type !== exp_msg_type[test_idx]) begin
                    $display("ERROR [%s]: msg_type mismatch. Expected %h, got %h",
                             tag, exp_msg_type[test_idx], msg_type);
                    errors = errors + 1;
                end
                if (sender_id !== exp_sender_id[test_idx]) begin
                    $display("ERROR [%s]: sender_id mismatch. Expected %h, got %h",
                             tag, exp_sender_id[test_idx], sender_id);
                    errors = errors + 1;
                end
                if (msg_seq_num !== exp_msg_seq_num[test_idx]) begin
                    $display("ERROR [%s]: msg_seq_num mismatch. Expected %0d, got %0d",
                             tag, exp_msg_seq_num[test_idx], msg_seq_num);
                    errors = errors + 1;
                end
            end
        end
    endtask

    task drive_reset_cycle;
        input [255:0] tag;
        input [7:0] reset_byte;
        begin
            rst = 1'b1;
            data_in = reset_byte;
            @(posedge clk);
            #1;
            check_outputs(tag, 1'b0, 0);
        end
    endtask

    task send_message;
        input integer test_idx;
        input [255:0] tag;
        begin
            for (j = 0; j < test_msg_len[test_idx]; j = j + 1) begin
                rst = 1'b0;
                data_in = test_data[test_idx][j];
                @(posedge clk);
                #1;
                check_outputs(tag, (j == test_msg_len[test_idx] - 1), test_idx);
            end
        end
    endtask

    task send_partial_then_reset;
        input integer test_idx;
        input integer byte_count;
        input [255:0] tag;
        begin
            for (j = 0; j < byte_count; j = j + 1) begin
                rst = 1'b0;
                data_in = test_data[test_idx][j];
                @(posedge clk);
                #1;
                check_outputs(tag, 1'b0, test_idx);
            end

            drive_reset_cycle("midstream_reset", 8'h00);
            rst = 1'b0;
        end
    endtask

    initial begin
        rst = 1'b0;
        data_in = 8'h00;

        // 0: Basic ordered
        test_msg_len[0] = 39;
        exp_msg_type[0] = 8'h44;
        exp_sender_id[0] = 64'h53454E4445523031;
        exp_msg_seq_num[0] = 32'd1;
        test_data[0][0] = 8'h38;  test_data[0][1] = 8'h3D;  test_data[0][2] = 8'h46;  test_data[0][3] = 8'h49;  test_data[0][4] = 8'h58;  test_data[0][5] = 8'h2E;  test_data[0][6] = 8'h34;  test_data[0][7] = 8'h2E;
        test_data[0][8] = 8'h32;  test_data[0][9] = 8'h01;  test_data[0][10] = 8'h33;  test_data[0][11] = 8'h35;  test_data[0][12] = 8'h3D;  test_data[0][13] = 8'h44;  test_data[0][14] = 8'h01;  test_data[0][15] = 8'h34;
        test_data[0][16] = 8'h39;  test_data[0][17] = 8'h3D;  test_data[0][18] = 8'h53;  test_data[0][19] = 8'h45;  test_data[0][20] = 8'h4E;  test_data[0][21] = 8'h44;  test_data[0][22] = 8'h45;  test_data[0][23] = 8'h52;
        test_data[0][24] = 8'h30;  test_data[0][25] = 8'h31;  test_data[0][26] = 8'h01;  test_data[0][27] = 8'h33;  test_data[0][28] = 8'h34;  test_data[0][29] = 8'h3D;  test_data[0][30] = 8'h31;  test_data[0][31] = 8'h01;
        test_data[0][32] = 8'h31;  test_data[0][33] = 8'h30;  test_data[0][34] = 8'h3D;  test_data[0][35] = 8'h31;  test_data[0][36] = 8'h37;  test_data[0][37] = 8'h32;  test_data[0][38] = 8'h01;

        // 1: Short sender
        test_msg_len[1] = 34;
        exp_msg_type[1] = 8'h38;
        exp_sender_id[1] = 64'h4142000000000000;
        exp_msg_seq_num[1] = 32'd42;
        test_data[1][0] = 8'h38;  test_data[1][1] = 8'h3D;  test_data[1][2] = 8'h46;  test_data[1][3] = 8'h49;  test_data[1][4] = 8'h58;  test_data[1][5] = 8'h2E;  test_data[1][6] = 8'h34;  test_data[1][7] = 8'h2E;
        test_data[1][8] = 8'h32;  test_data[1][9] = 8'h01;  test_data[1][10] = 8'h33;  test_data[1][11] = 8'h35;  test_data[1][12] = 8'h3D;  test_data[1][13] = 8'h38;  test_data[1][14] = 8'h01;  test_data[1][15] = 8'h34;
        test_data[1][16] = 8'h39;  test_data[1][17] = 8'h3D;  test_data[1][18] = 8'h41;  test_data[1][19] = 8'h42;  test_data[1][20] = 8'h01;  test_data[1][21] = 8'h33;  test_data[1][22] = 8'h34;  test_data[1][23] = 8'h3D;
        test_data[1][24] = 8'h34;  test_data[1][25] = 8'h32;  test_data[1][26] = 8'h01;  test_data[1][27] = 8'h31;  test_data[1][28] = 8'h30;  test_data[1][29] = 8'h3D;  test_data[1][30] = 8'h30;  test_data[1][31] = 8'h30;
        test_data[1][32] = 8'h35;  test_data[1][33] = 8'h01;

        // 2: Unusual order
        test_msg_len[2] = 39;
        exp_msg_type[2] = 8'h44;
        exp_sender_id[2] = 64'h52454F5244455200;
        exp_msg_seq_num[2] = 32'd77;
        test_data[2][0] = 8'h38;  test_data[2][1] = 8'h3D;  test_data[2][2] = 8'h46;  test_data[2][3] = 8'h49;  test_data[2][4] = 8'h58;  test_data[2][5] = 8'h2E;  test_data[2][6] = 8'h34;  test_data[2][7] = 8'h2E;
        test_data[2][8] = 8'h32;  test_data[2][9] = 8'h01;  test_data[2][10] = 8'h34;  test_data[2][11] = 8'h39;  test_data[2][12] = 8'h3D;  test_data[2][13] = 8'h52;  test_data[2][14] = 8'h45;  test_data[2][15] = 8'h4F;
        test_data[2][16] = 8'h52;  test_data[2][17] = 8'h44;  test_data[2][18] = 8'h45;  test_data[2][19] = 8'h52;  test_data[2][20] = 8'h01;  test_data[2][21] = 8'h33;  test_data[2][22] = 8'h34;  test_data[2][23] = 8'h3D;
        test_data[2][24] = 8'h37;  test_data[2][25] = 8'h37;  test_data[2][26] = 8'h01;  test_data[2][27] = 8'h33;  test_data[2][28] = 8'h35;  test_data[2][29] = 8'h3D;  test_data[2][30] = 8'h44;  test_data[2][31] = 8'h01;
        test_data[2][32] = 8'h31;  test_data[2][33] = 8'h30;  test_data[2][34] = 8'h3D;  test_data[2][35] = 8'h32;  test_data[2][36] = 8'h31;  test_data[2][37] = 8'h38;  test_data[2][38] = 8'h01;

        // 3: Extra ignored tags
        test_msg_len[3] = 62;
        exp_msg_type[3] = 8'h44;
        exp_sender_id[3] = 64'h414C504841000000;
        exp_msg_seq_num[3] = 32'd500;
        test_data[3][0] = 8'h38;  test_data[3][1] = 8'h3D;  test_data[3][2] = 8'h46;  test_data[3][3] = 8'h49;  test_data[3][4] = 8'h58;  test_data[3][5] = 8'h2E;  test_data[3][6] = 8'h34;  test_data[3][7] = 8'h2E;
        test_data[3][8] = 8'h32;  test_data[3][9] = 8'h01;  test_data[3][10] = 8'h39;  test_data[3][11] = 8'h3D;  test_data[3][12] = 8'h31;  test_data[3][13] = 8'h32;  test_data[3][14] = 8'h30;  test_data[3][15] = 8'h01;
        test_data[3][16] = 8'h33;  test_data[3][17] = 8'h35;  test_data[3][18] = 8'h3D;  test_data[3][19] = 8'h44;  test_data[3][20] = 8'h01;  test_data[3][21] = 8'h34;  test_data[3][22] = 8'h39;  test_data[3][23] = 8'h3D;
        test_data[3][24] = 8'h41;  test_data[3][25] = 8'h4C;  test_data[3][26] = 8'h50;  test_data[3][27] = 8'h48;  test_data[3][28] = 8'h41;  test_data[3][29] = 8'h01;  test_data[3][30] = 8'h33;  test_data[3][31] = 8'h34;
        test_data[3][32] = 8'h3D;  test_data[3][33] = 8'h35;  test_data[3][34] = 8'h30;  test_data[3][35] = 8'h30;  test_data[3][36] = 8'h01;  test_data[3][37] = 8'h31;  test_data[3][38] = 8'h31;  test_data[3][39] = 8'h3D;
        test_data[3][40] = 8'h4F;  test_data[3][41] = 8'h52;  test_data[3][42] = 8'h44;  test_data[3][43] = 8'h31;  test_data[3][44] = 8'h32;  test_data[3][45] = 8'h33;  test_data[3][46] = 8'h01;  test_data[3][47] = 8'h35;
        test_data[3][48] = 8'h35;  test_data[3][49] = 8'h3D;  test_data[3][50] = 8'h41;  test_data[3][51] = 8'h41;  test_data[3][52] = 8'h50;  test_data[3][53] = 8'h4C;  test_data[3][54] = 8'h01;  test_data[3][55] = 8'h31;
        test_data[3][56] = 8'h30;  test_data[3][57] = 8'h3D;  test_data[3][58] = 8'h31;  test_data[3][59] = 8'h35;  test_data[3][60] = 8'h32;  test_data[3][61] = 8'h01;

        // 4: Large seq
        test_msg_len[4] = 44;
        exp_msg_type[4] = 8'h44;
        exp_sender_id[4] = 64'h4249475345510000;
        exp_msg_seq_num[4] = 32'd16777300;
        test_data[4][0] = 8'h38;  test_data[4][1] = 8'h3D;  test_data[4][2] = 8'h46;  test_data[4][3] = 8'h49;  test_data[4][4] = 8'h58;  test_data[4][5] = 8'h2E;  test_data[4][6] = 8'h34;  test_data[4][7] = 8'h2E;
        test_data[4][8] = 8'h32;  test_data[4][9] = 8'h01;  test_data[4][10] = 8'h33;  test_data[4][11] = 8'h35;  test_data[4][12] = 8'h3D;  test_data[4][13] = 8'h44;  test_data[4][14] = 8'h01;  test_data[4][15] = 8'h34;
        test_data[4][16] = 8'h39;  test_data[4][17] = 8'h3D;  test_data[4][18] = 8'h42;  test_data[4][19] = 8'h49;  test_data[4][20] = 8'h47;  test_data[4][21] = 8'h53;  test_data[4][22] = 8'h45;  test_data[4][23] = 8'h51;
        test_data[4][24] = 8'h01;  test_data[4][25] = 8'h33;  test_data[4][26] = 8'h34;  test_data[4][27] = 8'h3D;  test_data[4][28] = 8'h31;  test_data[4][29] = 8'h36;  test_data[4][30] = 8'h37;  test_data[4][31] = 8'h37;
        test_data[4][32] = 8'h37;  test_data[4][33] = 8'h33;  test_data[4][34] = 8'h30;  test_data[4][35] = 8'h30;  test_data[4][36] = 8'h01;  test_data[4][37] = 8'h31;  test_data[4][38] = 8'h30;  test_data[4][39] = 8'h3D;
        test_data[4][40] = 8'h31;  test_data[4][41] = 8'h37;  test_data[4][42] = 8'h39;  test_data[4][43] = 8'h01;

        // 5: One-char ID
        test_msg_len[5] = 36;
        exp_msg_type[5] = 8'h30;
        exp_sender_id[5] = 64'h4100000000000000;
        exp_msg_seq_num[5] = 32'd12345;
        test_data[5][0] = 8'h38;  test_data[5][1] = 8'h3D;  test_data[5][2] = 8'h46;  test_data[5][3] = 8'h49;  test_data[5][4] = 8'h58;  test_data[5][5] = 8'h2E;  test_data[5][6] = 8'h34;  test_data[5][7] = 8'h2E;
        test_data[5][8] = 8'h32;  test_data[5][9] = 8'h01;  test_data[5][10] = 8'h33;  test_data[5][11] = 8'h35;  test_data[5][12] = 8'h3D;  test_data[5][13] = 8'h30;  test_data[5][14] = 8'h01;  test_data[5][15] = 8'h34;
        test_data[5][16] = 8'h39;  test_data[5][17] = 8'h3D;  test_data[5][18] = 8'h41;  test_data[5][19] = 8'h01;  test_data[5][20] = 8'h33;  test_data[5][21] = 8'h34;  test_data[5][22] = 8'h3D;  test_data[5][23] = 8'h31;
        test_data[5][24] = 8'h32;  test_data[5][25] = 8'h33;  test_data[5][26] = 8'h34;  test_data[5][27] = 8'h35;  test_data[5][28] = 8'h01;  test_data[5][29] = 8'h31;  test_data[5][30] = 8'h30;  test_data[5][31] = 8'h3D;
        test_data[5][32] = 8'h33;  test_data[5][33] = 8'h33;  test_data[5][34] = 8'h33;  test_data[5][35] = 8'h01;

        // 6: Max-length ID
        test_msg_len[6] = 41;
        exp_msg_type[6] = 8'h46;
        exp_sender_id[6] = 64'h4142434445464748;
        exp_msg_seq_num[6] = 32'd256;
        test_data[6][0] = 8'h38;  test_data[6][1] = 8'h3D;  test_data[6][2] = 8'h46;  test_data[6][3] = 8'h49;  test_data[6][4] = 8'h58;  test_data[6][5] = 8'h2E;  test_data[6][6] = 8'h34;  test_data[6][7] = 8'h2E;
        test_data[6][8] = 8'h32;  test_data[6][9] = 8'h01;  test_data[6][10] = 8'h33;  test_data[6][11] = 8'h35;  test_data[6][12] = 8'h3D;  test_data[6][13] = 8'h46;  test_data[6][14] = 8'h01;  test_data[6][15] = 8'h34;
        test_data[6][16] = 8'h39;  test_data[6][17] = 8'h3D;  test_data[6][18] = 8'h41;  test_data[6][19] = 8'h42;  test_data[6][20] = 8'h43;  test_data[6][21] = 8'h44;  test_data[6][22] = 8'h45;  test_data[6][23] = 8'h46;
        test_data[6][24] = 8'h47;  test_data[6][25] = 8'h48;  test_data[6][26] = 8'h01;  test_data[6][27] = 8'h33;  test_data[6][28] = 8'h34;  test_data[6][29] = 8'h3D;  test_data[6][30] = 8'h32;  test_data[6][31] = 8'h35;
        test_data[6][32] = 8'h36;  test_data[6][33] = 8'h01;  test_data[6][34] = 8'h31;  test_data[6][35] = 8'h30;  test_data[6][36] = 8'h3D;  test_data[6][37] = 8'h31;  test_data[6][38] = 8'h35;  test_data[6][39] = 8'h31;
        test_data[6][40] = 8'h01;

        // 7: FIX44 extras
        test_msg_len[7] = 47;
        exp_msg_type[7] = 8'h41;
        exp_sender_id[7] = 64'h494E495400000000;
        exp_msg_seq_num[7] = 32'd1;
        test_data[7][0] = 8'h38;  test_data[7][1] = 8'h3D;  test_data[7][2] = 8'h46;  test_data[7][3] = 8'h49;  test_data[7][4] = 8'h58;  test_data[7][5] = 8'h2E;  test_data[7][6] = 8'h34;  test_data[7][7] = 8'h2E;
        test_data[7][8] = 8'h34;  test_data[7][9] = 8'h01;  test_data[7][10] = 8'h33;  test_data[7][11] = 8'h35;  test_data[7][12] = 8'h3D;  test_data[7][13] = 8'h41;  test_data[7][14] = 8'h01;  test_data[7][15] = 8'h34;
        test_data[7][16] = 8'h39;  test_data[7][17] = 8'h3D;  test_data[7][18] = 8'h49;  test_data[7][19] = 8'h4E;  test_data[7][20] = 8'h49;  test_data[7][21] = 8'h54;  test_data[7][22] = 8'h01;  test_data[7][23] = 8'h33;
        test_data[7][24] = 8'h34;  test_data[7][25] = 8'h3D;  test_data[7][26] = 8'h31;  test_data[7][27] = 8'h01;  test_data[7][28] = 8'h39;  test_data[7][29] = 8'h38;  test_data[7][30] = 8'h3D;  test_data[7][31] = 8'h30;
        test_data[7][32] = 8'h01;  test_data[7][33] = 8'h31;  test_data[7][34] = 8'h30;  test_data[7][35] = 8'h38;  test_data[7][36] = 8'h3D;  test_data[7][37] = 8'h33;  test_data[7][38] = 8'h30;  test_data[7][39] = 8'h01;
        test_data[7][40] = 8'h31;  test_data[7][41] = 8'h30;  test_data[7][42] = 8'h3D;  test_data[7][43] = 8'h32;  test_data[7][44] = 8'h31;  test_data[7][45] = 8'h34;  test_data[7][46] = 8'h01;

        // 8: Tag10 ignored
        test_msg_len[8] = 39;
        exp_msg_type[8] = 8'h35;
        exp_sender_id[8] = 64'h4C4F474F55545F53;
        exp_msg_seq_num[8] = 32'd0;
        test_data[8][0] = 8'h38;  test_data[8][1] = 8'h3D;  test_data[8][2] = 8'h46;  test_data[8][3] = 8'h49;  test_data[8][4] = 8'h58;  test_data[8][5] = 8'h2E;  test_data[8][6] = 8'h34;  test_data[8][7] = 8'h2E;
        test_data[8][8] = 8'h32;  test_data[8][9] = 8'h01;  test_data[8][10] = 8'h33;  test_data[8][11] = 8'h35;  test_data[8][12] = 8'h3D;  test_data[8][13] = 8'h35;  test_data[8][14] = 8'h01;  test_data[8][15] = 8'h34;
        test_data[8][16] = 8'h39;  test_data[8][17] = 8'h3D;  test_data[8][18] = 8'h4C;  test_data[8][19] = 8'h4F;  test_data[8][20] = 8'h47;  test_data[8][21] = 8'h4F;  test_data[8][22] = 8'h55;  test_data[8][23] = 8'h54;
        test_data[8][24] = 8'h5F;  test_data[8][25] = 8'h53;  test_data[8][26] = 8'h01;  test_data[8][27] = 8'h33;  test_data[8][28] = 8'h34;  test_data[8][29] = 8'h3D;  test_data[8][30] = 8'h30;  test_data[8][31] = 8'h01;
        test_data[8][32] = 8'h31;  test_data[8][33] = 8'h30;  test_data[8][34] = 8'h3D;  test_data[8][35] = 8'h39;  test_data[8][36] = 8'h39;  test_data[8][37] = 8'h39;  test_data[8][38] = 8'h01;

        $display("===========================================");
        $display("  Financial Market Message Parser Testbench");
        $display("===========================================");

        drive_reset_cycle("reset_0", 8'h00);
        drive_reset_cycle("reset_1", 8'h41);
        rst = 1'b0;

        for (i = 0; i < NUM_TESTS; i = i + 1)
            send_message(i, "stream");

        send_partial_then_reset(3, 18, "partial_before_reset");
        send_message(1, "after_midstream_reset");

        $display("");
        $display("===========================================");
        $display("  Tests Run: %0d", tests_run);
        $display("===========================================");

        if (errors == 0) begin
            $display("TEST_RESULT: PASS");
        end else begin
            $display("TEST_RESULT: FAIL (%0d errors)", errors);
        end

        $finish;
    end
endmodule
