`timescale 1ns/1ps

module TB;

    // ====================================================
    // Parameters
    // ====================================================
    parameter FIFO_DEPTH       = 64;
    parameter SCRAMBLER_SEED   = 32'hFFFFFFFF;
    parameter CLK_PERIOD       = 10;          // 10ns
    parameter PATN_COUNT       = 20'd10;      // Training match count
    parameter TAP_STEP         = 3'd3;        // Delay adjustment step

    // ====================================================
    // Constants (moved to top for visibility)
    // ====================================================
    localparam [31:0] TRAINING_PATN = 32'h68666E6C;  // "hfnl"
    localparam [31:0] TRAINING_EXIT = 32'h65786974;  // "exit"
    localparam [31:0] FRAME_HEADER  = 32'hBCBCBCBC;

initial begin
  $fsdbAutoSwitchDumpfile(500, "./verdplus.fsdb", 1000000);
  $fsdbDumpvars();
  $fsdbDumpMDA();
end 

    // ====================================================
    // Signals
    // ====================================================
    logic clk;
    logic rst_n;
    logic [3:0] serial_in;
    logic [19:0] patn_count;
    logic [2:0] tap_step;
    logic link_down;
    logic [2:0] delay_tap;
    logic [12:0] wr_addr;
    logic [511:0] wr_data;
    logic wr_en;
    logic [63:0] byte_mask;
    logic crc_error;
    logic [2  :0] tap_adj_mask = 3'b111;      // Delay adjustment mask
    logic [0  :0] tap_force = 1'b0   ;        // Delay force to tap_step
    logic         tap_adj_req  ;
    logic         frame_done   ;
    logic [31 :0] train_status ;
    logic [31 :0] frame_status ;
    logic         always_on = 1'b0  ; 
    logic         prefilling   ;
    logic         prefill_start;
    
    // Added for scrambler test
    logic         descram_en;
    logic [31:0]  lfsr_lane[0:3];   // 4 independent LFSRs for scrambling
    logic         train_ready;
    logic         force_train;        // Force Training
    // ====================================================
    // DUT Instance
    // ====================================================
    ulink_rx #(
      .FIFO_DEPTH     ( FIFO_DEPTH     )
     ,.SCRAMBLER_SEED ( SCRAMBLER_SEED )
    ) dut (
       .clk           ( clk            )
      ,.rst_n         ( rst_n          )
      ,.serial_in     ( serial_in      )
      ,.patn_count    ( patn_count     )
      ,.tap_step      ( tap_step       )
      ,.descram_en    ( descram_en     )   // now variable
      ,.link_down     ( link_down      )
      ,.delay_tap     ( delay_tap      )
      ,.wr_addr       ( wr_addr        )
      ,.wr_data       ( wr_data        )
      ,.wr_en         ( wr_en          )
      ,.byte_mask     ( byte_mask      )
      ,.crc_error     ( crc_error      )
      ,.tap_adj_mask  ( tap_adj_mask   )
      ,.tap_force     ( tap_force      )
      ,.tap_adj_req   ( tap_adj_req    )
      ,.frame_done    ( frame_done     )
      ,.train_status  ( train_status   )
      ,.frame_status  ( frame_status   )
      ,.always_on     ( always_on      )
      ,.prefilling    ( prefilling     )
      ,.prefill_start ( prefill_start  )
	  ,.train_ready   ( train_ready    )
	  ,.force_train   ( force_train    )
    );

    // ====================================================
    // Clock Generation
    // ====================================================
    initial begin
        clk = 0;
        forever #(CLK_PERIOD/2) clk = ~clk;
    end

    // ====================================================
    // Tasks for Stimulus Generation
    // ====================================================

    // Reset task
    task reset;
        begin
            rst_n = 0;
            repeat(5) @(posedge clk);
            rst_n = 1;
            repeat(2) @(posedge clk);
        end
    endtask

    // Send one 128-bit block over 32 cycles
    task send_block;
        input [127:0] block;
        begin
            integer i;
            for (i = 31; i >= 0; i = i - 1) begin
                serial_in[0] = block[i];
                serial_in[1] = block[32 + i];
                serial_in[2] = block[64 + i];
                serial_in[3] = block[96 + i];
                @(posedge clk);
                #0.1;
            end
        end
    endtask

    // Send multiple identical blocks
    task send_blocks;
        input [127:0] block;
        input integer count;
        begin
            repeat(count) send_block(block);
        end
    endtask

    // Send a sequence of 32-bit words (packed into 128-bit blocks)
    task send_words;
        input integer num_words;
        input [31:0] words[0:255];
        begin
            integer word_idx;
            integer j;
            reg [127:0] block;
            word_idx = 0;
            while (word_idx < num_words) begin
                block = 128'h0;
                for (j = 0; j < 4; j = j + 1) begin
                    if (word_idx < num_words) begin
                        block[32*j +: 32] = words[word_idx];
                        word_idx = word_idx + 1;
                    end
                end
                send_block(block);
            end
        end
    endtask

   // ====================================================
// Scrambler tasks (used when descram_en=1)
// ====================================================

// Original scrambler (all lanes scrambled)
task scramble_block;
    input [127:0] original;
    output [127:0] scrambled;
    integer i;
    reg [31:0] lane_word;
    begin
        scrambled = 128'h0;
        for (i = 0; i < 4; i = i + 1) begin
            lane_word = original[32*i +: 32];
            scrambled[32*i +: 32] = lane_word ^ lfsr_lane[i];
            // Update LFSR: feedback = lfsr[31]^lfsr[30]^lfsr[29]^lfsr[28]
            lfsr_lane[i] = {lfsr_lane[i][30:0], 
                             lfsr_lane[i][31] ^ lfsr_lane[i][30] ^ 
                             lfsr_lane[i][29] ^ lfsr_lane[i][28]};
        end
    end
endtask

// New scrambler with per-lane mask (1=scramble that lane)
task scramble_block_masked;
    input [127:0] original;
    input [3:0]   mask;        // 1=scramble this lane
    output [127:0] scrambled;
    integer i;
    reg [31:0] lane_word;
    begin
        scrambled = 128'h0;
        for (i = 0; i < 4; i = i + 1) begin
            lane_word = original[32*i +: 32];
            if (mask[i]) begin
                scrambled[32*i +: 32] = lane_word ^ lfsr_lane[i];
                // Update LFSR only for scrambled lanes
                lfsr_lane[i] = {lfsr_lane[i][30:0], 
                                 lfsr_lane[i][31] ^ lfsr_lane[i][30] ^ 
                                 lfsr_lane[i][29] ^ lfsr_lane[i][28]};
            end else begin
                scrambled[32*i +: 32] = lane_word;
                // No LFSR update for unscrambled lanes
            end
        end
    end
endtask

    // ====================================================
    // CRC32 Computation Functions
    // ====================================================
    function automatic [31:0] crc32_next;
        input [31:0] crc;
        input [31:0] data;
        reg [31:0] new_crc;
        reg b;
        integer i;
        begin
            new_crc = crc;
            for (i = 0; i < 32; i = i + 1) begin
                b = new_crc[31] ^ data[31-i];
                new_crc = {new_crc[30:0], 1'b0};
                if (b) new_crc = new_crc ^ 32'h04C11DB7;
            end
            crc32_next = new_crc;
        end
    endfunction

    function automatic [31:0] crc32_compute;
        input [31:0] words[0:255];
        input integer num;
        reg [31:0] crc;
        integer i;
        begin
            crc = 32'hFFFFFFFF;
            for (i = 0; i < num; i = i + 1) begin
                crc = crc32_next(crc, words[i]);
            end
            crc32_compute = crc;
        end
    endfunction

    // ====================================================
    // Test Variables (module level)
    // ====================================================
    integer data_words;
    reg [31:0] frame_words[0:255];
    integer frame_len;
    integer i;
    // ====================================================
    // Test Sequence
    // ====================================================
    initial begin
        // Initialize
        serial_in = 4'h0;
        patn_count = PATN_COUNT;
        tap_step = TAP_STEP;
        descram_en = 0;   // default disabled
        force_train = 0;

        $display("========================================");
        $display("Testbench started at %0t", $time);
        $display("========================================");

        reset();
        wait(prefill_start == 1);
        repeat(10) @(posedge clk);
        prefilling = 1;
        repeat(100) @(posedge clk);
        prefilling = 0;
        // -------------------------------------------------
        // Phase 1: Link Training (successful)
        // -------------------------------------------------
        $display("Phase 1: Training with correct patterns...");

        send_blocks({4{TRAINING_PATN}}, PATN_COUNT - 0);
        send_blocks({4{TRAINING_EXIT}}, 1);

        wait(dut.u_train.train_ready == 1);
        $display("Link ready at %0t", $time);

        // -------------------------------------------------
        // Phase 2: Send a correct frame
        // -------------------------------------------------
        $display("Phase 2: Sending a correct frame...");

        data_words = 20;
        frame_len = 2 + data_words + 1;

        frame_words[0] = FRAME_HEADER;
        frame_words[1] = (32'h1235 << 16) | data_words;

        for (i = 0; i < data_words; i = i + 1) begin
            frame_words[2+i] = 32'hA0000000 + i;
        end

        // Compute CRC over addr_len and data (1 + data_words words)
        begin
            reg [31:0] crc_tmp[0:255];
            integer k;
            for (k = 0; k < 1 + data_words; k = k + 1) begin
                crc_tmp[k] = frame_words[1 + k];
            end
            frame_words[2 + data_words] = crc32_compute(crc_tmp, 1 + data_words);
        end

        send_words(frame_len, frame_words);

        fork
            begin
                @(posedge wr_en);
                $display("Write detected: addr=%0d data=%h mask=%h", wr_addr, wr_data, byte_mask);
                if (wr_addr !== 13'h123) $error("Unexpected write address: %0d", wr_addr);
                if (byte_mask[31:0] !== 32'hFFFF_0000) $error("Byte mask mismatch: %h", byte_mask);
                if (byte_mask[63:32] !== 32'hFFFF_FFFF) $error("Byte mask high part not zero: %h", byte_mask[63:32]);
                $display("Correct frame write verified.");
            end
        join_none

        repeat(100) @(posedge clk);

        // -------------------------------------------------
        // Phase 3: Send a frame with bad CRC
        // -------------------------------------------------
        $display("Phase 3: Sending a frame with bad CRC...");

        frame_words[2+data_words] = ~frame_words[2+data_words];
        send_words(frame_len, frame_words);

        @(posedge crc_error);
        $display("CRC error detected at %0t", $time);
        repeat(10) @(posedge clk);
        if (link_down) $display("Link down as expected.");
        else $error("Link not down after CRC error.");

        // -------------------------------------------------
        // Phase 4: Re-train and send another correct frame
        // -------------------------------------------------
        $display("Phase 4: Re-training...");

        send_blocks({4{TRAINING_PATN}}, PATN_COUNT - 0);
        send_blocks({4{TRAINING_EXIT}}, 1);

        wait(dut.u_train.train_ready == 1);
        $display("Link ready again.");

        // Compute CRC over addr_len and data (1 + data_words words)
        begin
            reg [31:0] crc_tmp[0:255];
            integer k;
            for (k = 0; k < 1 + data_words; k = k + 1) begin
                crc_tmp[k] = frame_words[1 + k];
            end
            frame_words[2 + data_words] = crc32_compute(crc_tmp, 1 + data_words);
        end
        send_words(frame_len, frame_words);

        repeat(100) @(posedge clk);

        // -------------------------------------------------
        // Phase 5: Test delay_tap adjustment on match failure
        // -------------------------------------------------
        $display("Phase 5: Testing delay_tap adjustment...");

        force_train = 1;
		repeat(1) @(posedge clk);
		force_train = 0;
        send_blocks(128'h0, 5);
        repeat(200) @(posedge clk);
        $display("Final delay_tap = %0d", delay_tap);

        // -------------------------------------------------
        // Phase 6: Test with scrambler enabled (header not scrambled)
        // -------------------------------------------------
        $display("Phase 6: Testing with descrambler enabled (header not scrambled)...");

        // Reset and train with descram_en=0 (training patterns are not scrambled)
        descram_en = 0;
        force_train = 1;
		repeat(1) @(posedge clk);
		force_train = 0;
        send_blocks({4{TRAINING_PATN}}, PATN_COUNT - 0);
        send_blocks({4{TRAINING_EXIT}}, 1);

        wait(dut.u_train.train_ready == 1);
        $display("Link ready for scrambled data.");

        // Enable descrambler in DUT and initialize LFSRs for transmission
        descram_en = 1;
        for (i = 0; i < 4; i = i + 1) lfsr_lane[i] = SCRAMBLER_SEED;

        // Construct a simple frame (small data length)
        data_words = 10;
        frame_len = 2 + data_words + 1;

        frame_words[0] = FRAME_HEADER;
        frame_words[1] = (32'h1234 << 16) | data_words;  // base_addr=0x1234, offset=0, len=10

        for (i = 0; i < data_words; i = i + 1) begin
            frame_words[2+i] = 32'hB0000000 + i;  // different pattern from Phase 2
        end

        // Compute CRC over addr_len and data (1 + data_words words)
        begin
            reg [31:0] crc_tmp[0:255];
            integer k;
            for (k = 0; k < 1 + data_words; k = k + 1) begin
                crc_tmp[k] = frame_words[1 + k];
            end
            frame_words[2 + data_words] = crc32_compute(crc_tmp, 1 + data_words);
        end

        // Now scramble each 128-bit block and send
        begin
            integer word_idx;
            integer j;
            integer block_num;
            reg [127:0] orig_block, scrambled_block;
            reg [3:0]   block_mask;
            word_idx = 0;
            block_num = 0;
            while (word_idx < frame_len) begin
                orig_block = 128'h0;
                // Fill block with up to 4 words, pad with 0 if needed
                for (j = 0; j < 4; j = j + 1) begin
                    if (word_idx < frame_len) begin
                        orig_block[32*j +: 32] = frame_words[word_idx];
                        word_idx = word_idx + 1;
                    end else begin
                        orig_block[32*j +: 32] = 32'h0;  // pad with 0
                    end
                end
                // Determine mask: first block leaves lane0 unscrambled (header), others all scrambled
                block_mask = (block_num == 0) ? 4'b1110 : 4'b1111;
                scramble_block_masked(orig_block, block_mask, scrambled_block);
                send_block(scrambled_block);
                block_num = block_num + 1;
            end
        end

        // Monitor writes
        fork
            begin
                @(posedge wr_en);
                $display("Scrambled test write: addr=%0d data=%h mask=%h", wr_addr, wr_data, byte_mask);
            end
        join_none

        repeat(200) @(posedge clk);

        // Check that no CRC error happened
        if (crc_error) $error("CRC error in scrambled test");
        else $display("Scrambled test passed (no CRC error).");

        // Disable scrambler for rest of test
        descram_en = 0;

        // -------------------------------------------------
        // End of test
        // -------------------------------------------------
        repeat(50) @(posedge clk);
        $display("========================================");
        $display("Testbench finished at %0t", $time);
        $display("========================================");
        $finish;
    end

    // ====================================================
    // Monitor and Assertions
    // ====================================================
    always @(posedge clk) begin
        if (wr_en) $display("WRITE: addr=%0d data=%h mask=%h", wr_addr, wr_data, byte_mask);
        if (crc_error) $display("CRC_ERROR pulse at %0t", $time);
    end

    always @(posedge clk) begin
        if (dut.u_train.train_ready && link_down)
            $error("Inconsistent state: train_ready and link_down both high");
    end

endmodule
