///////////////////////////////////////////////////////////////////////////////
// LVDS_DRIVER.sv
// Enhanced LVDS Driver (4 lanes, 8 clock cycles per word)
// Reads a 32-bit data word from a text file and sends it, providing LVDS interface driving
// New features: automatic frame header detection, dynamic scrambler mask, CRC32 calculation, TXT frame integrity check
// Added training sequence support (configurable pattern count)
// Added option to ignore CRC errors (ignore_crc_error)
///////////////////////////////////////////////////////////////////////////////

`ifndef LVDS_DRIVER_SV
`define LVDS_DRIVER_SV

//-------------------------------------------------------------------------
// LVDS interface definition
//-------------------------------------------------------------------------
interface lvds_if (input bit clk);
  logic        valid;   // Data valid indication
  logic [3:0]  data;    // Data on 4 lanes
endinterface

//-------------------------------------------------------------------------
// DataReader class: Reads 32-bit hexadecimal data from a file and stores it in MSB-first queue
//-------------------------------------------------------------------------
class DataReader;
  
  bit spi_data_queue[$];   // Bit-level queue (MSB first)

  function void read_txt_file(input string filename);
    int file_id;
    string line;
    bit [31:0] value;
    int i;

    file_id = $fopen(filename, "r");
    if (file_id == 0) begin
      $display("Error: Failed to open file %s", filename);
      return;
    end

    while (!$feof(file_id)) begin
      $fscanf(file_id, "%h\n", value);
      // Store the 32-bit word into queue MSB first
      for (i = 31; i >= 0; i--) begin
        spi_data_queue.push_back(value[i]);
      end
    end

    $fclose(file_id);
  endfunction

  function void get_data_queue(ref bit data_queue[$]);
    data_queue = spi_data_queue;
    spi_data_queue.delete();
  endfunction

endclass

//-------------------------------------------------------------------------
// lvds_item class: Encapsulates a set of 32-bit data words
//-------------------------------------------------------------------------
class lvds_item;

  // Data word queue (each element 32 bits)
  bit [31:0] data[$];

  function new(string name = "lvds_item");
  endfunction

  // Unpack from bit stream (MSB first)
  function void unpack(ref bit stream[$]);
    bit [31:0] word;
    data.delete();
    while (stream.size() >= 32) begin
      for (int i = 0; i < 32; i++) begin
        word[31-i] = stream.pop_front();
      end
      data.push_back(word);
    end
  endfunction

  // Pack to bit stream (MSB first)
  function void pack(ref bit stream[$]);
    foreach (data[i]) begin
      for (int j = 31; j >= 0; j--) begin
        stream.push_back(data[i][j]);
      end
    end
  endfunction

  // Print item content (debug)
  function void print();
    $display("LVDS Item: %0d words", data.size());
    foreach (data[i]) begin
      $display("  data[%0d] = %h", i, data[i]);
    end
  endfunction

endclass

//-------------------------------------------------------------------------
// lvds_driver class: LVDS driver, supports file-driven, TB-compatible direct sending, and scrambling
//-------------------------------------------------------------------------
class lvds_driver;

  // Original members
  string file_path;                // Data file path
  virtual lvds_if drv_if;           // Virtual interface
  int interval = 0;                 // Inter-word gap (clock cycles), 0 means continuous transmission
  int clk_period = 1600;            // Clock period (ps), used only for debug

  // Scrambler related members
  bit [31:0] lfsr_lane[0:3];        // Independent LFSR states for 4 lanes
  bit        scrambler_en = 0;      // Global scrambler enable (if set, scrambling is automatically applied during transmission)
  bit [3:0]  scrambler_mask = 4'b1111; // Default scrambler mask, 1 indicates that lane is scrambled

  // Frame header auto-detection related
  bit [31:0] frame_header = 32'hBCBCBCBC; // Default frame header
  bit        auto_detect_header = 1;      // Whether to auto-detect header and adjust mask

  // Frame integrity check enable
  bit        enable_frame_check = 1;      // Whether to perform frame validation on file content before transmission
  bit        ignore_crc_error = 1;        // If set, CRC errors will be warnings (not fatal)

  // Training related constants and variables
  const bit [31:0] TRAINING_PATN = 32'h68666E6C;  // "hfnl"
  const bit [31:0] TRAINING_EXIT = 32'h65786974;  // "exit"
  int train_count = 10;                           // Number of training pattern blocks to send (can be configured)

  function new(string name = "lvds_driver");
    // Initialize LFSR (default uses SCRAMBLER_SEED = 32'hFFFFFFFF)
    for (int i = 0; i < 4; i++) lfsr_lane[i] = 32'hFFFFFFFF;
  endfunction

  //-------------------------------------------------------------------------
  // Send a 32-bit word (fully compatible with TB approach)
  // Place the word on lane0, other lanes constant 0, send MSB first over 32 clocks
  //-------------------------------------------------------------------------
  local task send_word(bit [31:0] word);
    for (int i = 31; i >= 0; i--) begin
      @(posedge drv_if.clk);
      drv_if.valid <= 1'b1;
      drv_if.data[0] <= word[i];
      drv_if.data[3:1] <= 3'b0;
    end
  endtask

  //-------------------------------------------------------------------------
  // Send an lvds_item (internally calls send_word)
  // Note: This method sends only one word on lane0 each time; for parallel transmission use send_words family tasks
  //-------------------------------------------------------------------------
  task send(lvds_item item);
    foreach (item.data[i]) begin
      send_word(item.data[i]);
      repeat (interval) @(posedge drv_if.clk);
    end
    @(posedge drv_if.clk);
    drv_if.valid <= 1'b0;
    drv_if.data  <= 4'b0;
  endtask

  //-------------------------------------------------------------------------
  // Send a 128-bit block, fully compatible with TB's send_block (MSB first)
  //-------------------------------------------------------------------------
  task send_block(input [127:0] block);
    for (int i = 31; i >= 0; i--) begin
      @(posedge drv_if.clk);
      drv_if.valid <= 1'b1;
      drv_if.data[0] <= block[i];
      drv_if.data[1] <= block[32 + i];
      drv_if.data[2] <= block[64 + i];
      drv_if.data[3] <= block[96 + i];
    end
  endtask

  //-------------------------------------------------------------------------
  // Send multiple identical 128-bit blocks consecutively
  //-------------------------------------------------------------------------
  task send_blocks(input [127:0] block, input int count);
    repeat (count) send_block(block);
  endtask

  //-------------------------------------------------------------------------
  // Send a sequence of 32-bit words (automatically pack into 128-bit blocks, zero-pad if needed)
  // Parameter words is an array, num_words is the actual number of words (up to 256)
  //-------------------------------------------------------------------------
  task send_words(input int num_words, input [31:0] words[0:255]);
    int word_idx;
    int j;
    reg [127:0] block;

    word_idx = 0;
    while (word_idx < num_words) begin
      block = 128'h0;
      for (j = 0; j < 4; j++) begin
        if (word_idx < num_words) begin
          block[32*j +: 32] = words[word_idx];
          word_idx++;
        end
      end
      send_block(block);
    end
  endtask

  //-------------------------------------------------------------------------
  // Initialize LFSR (all lanes use the same seed)
  //-------------------------------------------------------------------------
  function void init_scrambler(bit [31:0] seed);
    for (int i = 0; i < 4; i++) lfsr_lane[i] = seed;
  endfunction

  //-------------------------------------------------------------------------
  // Update a single LFSR (polynomial: x^32 + x^30 + x^29 + x^28 + 1)
  //-------------------------------------------------------------------------
  function bit [31:0] lfsr_update(bit [31:0] lfsr);
    bit feedback;
    feedback = lfsr[31] ^ lfsr[30] ^ lfsr[29] ^ lfsr[28];
    lfsr_update = {lfsr[30:0], feedback};
  endfunction

  //-------------------------------------------------------------------------
  // Scramble a single 128-bit block with lane mask support
  // Input original: original block; mask: whether each lane is scrambled (1=scramble)
  // Output scrambled: scrambled block
  // Note: This method updates the internal LFSR state (only for lanes that are scrambled)
  //-------------------------------------------------------------------------
  function void scramble_block_masked(input [127:0] original, input [3:0] mask, output [127:0] scrambled);
    bit [31:0] lane_word;
    scrambled = 128'h0;
    for (int i = 0; i < 4; i++) begin
      lane_word = original[32*i +: 32];
      if (mask[i]) begin
        scrambled[32*i +: 32] = lane_word ^ lfsr_lane[i];
        lfsr_lane[i] = lfsr_update(lfsr_lane[i]);  // Update LFSR only for scrambled lanes
      end else begin
        scrambled[32*i +: 32] = lane_word;
      end
    end
  endfunction

  //-------------------------------------------------------------------------
  // Send a scrambled 128-bit block (using current scrambler_en and scrambler_mask)
  // If scrambler_en is 0, directly send the original block (same as send_block)
  //-------------------------------------------------------------------------
  task send_block_scrambled(input [127:0] block);
    if (scrambler_en) begin
      logic [127:0] scrambled;
      scramble_block_masked(block, scrambler_mask, scrambled);
      send_block(scrambled);
    end else begin
      send_block(block);
    end
  endtask

  //-------------------------------------------------------------------------
  // Send a sequence of scrambled 32-bit words (using fixed scrambler_mask)
  //-------------------------------------------------------------------------
  task send_words_scrambled(input int num_words, input [31:0] words[0:255]);
    int word_idx;
    int j;
    reg [127:0] block;

    word_idx = 0;
    while (word_idx < num_words) begin
      block = 128'h0;
      for (j = 0; j < 4; j++) begin
        if (word_idx < num_words) begin
          block[32*j +: 32] = words[word_idx];
          word_idx++;
        end
      end
      send_block_scrambled(block);
    end
  endtask

  //-------------------------------------------------------------------------
  // Frame check function, verifies if words form a valid frame (header, length, CRC)
  // Returns 1 for pass, 0 for fail, err_msg outputs error information
  //-------------------------------------------------------------------------
  static function bit check_frame(bit [31:0] words[$], ref string err_msg);
    int num_words = words.size();
    bit [31:0] header, addr_len, crc_expected, crc_computed;
    int data_len;
    bit [31:0] data_words[0:255]; // For CRC calculation
    int i;

    // Basic length check: at least 3 words (header, addr/len, CRC)
    if (num_words < 3) begin
      err_msg = "Frame word count less than 3";
      return 0;
    end

    header = words[0];
    if (header !== 32'hBCBCBCBC) begin
      err_msg = $sformatf("Frame header error, expected 32'hBCBCBCBC, got 32'h%h", header);
      return 0;
    end

    addr_len = words[1];
    data_len = addr_len[15:0]; // Lower 16 bits are data length
    // Verify total word count: 1(header) + 1(addr/len) + data_len + 1(CRC) = 3 + data_len
    if (num_words != 3 + data_len) begin
      err_msg = $sformatf("Frame length mismatch: expected %0d words from length field, got %0d words", 3+data_len, num_words);
      return 0;
    end

    // Collect data part (addr_len word + data words) for CRC calculation
    // Note: CRC range is from words[1] to words[1+data_len-1] (total 1+data_len words)
    for (i = 0; i < 1+data_len; i++) begin
      data_words[i] = words[1 + i];
    end
    crc_computed = crc32_compute(data_words, 1+data_len);
	crc_expected = words[2+data_len]; // CRC word

    if (crc_computed !== crc_expected) begin
      err_msg = $sformatf("CRC error: computed 32'h%h, expected 32'h%h", crc_computed, crc_expected);
      return 0;
    end

    err_msg = "Frame check passed";
    return 1;
  endfunction

  //-------------------------------------------------------------------------
  // Read complete frame data from file, auto-detect frame header and dynamically adjust scrambler mask
  // The lane containing the frame header is not scrambled, other lanes are scrambled; all subsequent blocks are fully scrambled
  // Perform frame integrity check before transmission (if enable_frame_check is 1)
  //-------------------------------------------------------------------------
  task send_frame_from_file(string filename);
    DataReader reader = new();
    bit bit_stream[$];
    lvds_item item = new();
    string err_msg;
    reader.read_txt_file(filename);
    reader.get_data_queue(bit_stream);
    item.unpack(bit_stream);

    // Frame check
    if (enable_frame_check) begin
      if (!check_frame(item.data, err_msg)) begin
        if (ignore_crc_error) begin
          $warning("Frame check warning: %s, file %s (ignored)", err_msg, filename);
        end else begin
          $error("Frame check failed: %s, file %s", err_msg, filename);
          return; // Stop transmission
        end
      end else begin
        $display("Frame check passed: %s", err_msg);
      end
    end

    send_words_scrambled_auto(item.data);
  endtask

  //-------------------------------------------------------------------------
  // Send word queue, auto-detect frame header and dynamically adjust scrambler mask
  // If auto_detect_header is 0 or scrambler_en is 0, use fixed mask (scrambler_mask)
  // Otherwise, detect per block; when header is encountered, set that lane mask to 0, subsequent blocks all 1
  //-------------------------------------------------------------------------
  task send_words_scrambled_auto(bit [31:0] words[$]);
    int word_idx;
    int block_idx;
    bit header_found;
    reg [127:0] block;
    bit [3:0] mask;
    int num;
    bit [31:0] words_arr[0:255];

    if (!scrambler_en || !auto_detect_header) begin
      // Fallback to fixed mask transmission
      num = words.size();
      foreach (words[i]) words_arr[i] = words[i];
      send_words_scrambled(num, words_arr);
      return;
    end

    // Auto-detection mode
    word_idx = 0;
    block_idx = 0;
    header_found = 0;

    while (word_idx < words.size()) begin
      // Pack current block (up to 4 words)
      block = 128'h0;
      for (int j = 0; j < 4; j++) begin
        if (word_idx < words.size()) begin
          block[32*j +: 32] = words[word_idx];
          word_idx++;
        end
      end

      // Determine scrambler mask for current block
      if (!header_found) begin
        mask = 4'b1111;  // Default all scrambled
        // Check if current block contains the frame header
        for (int j = 0; j < 4; j++) begin
          if (word_idx - 4 + j < words.size()) begin // Ensure index is valid
            if (block[32*j +: 32] == frame_header) begin
              mask[j] = 1'b0;  // Lane containing header is not scrambled
              header_found = 1;
              // Note: Even if multiple headers appear in one block (theoretically impossible), we handle only the first
            end
          end
        end
      end else begin
        mask = 4'b1111;  // Header already found, subsequent blocks all scrambled
      end

      // Send current block (scrambled according to mask)
      if (scrambler_en) begin
        logic [127:0] scrambled;
        scramble_block_masked(block, mask, scrambled);
        send_block(scrambled);
      end else begin
        send_block(block);
      end
      block_idx++;
    end
  endtask

  //-------------------------------------------------------------------------
  // Main driving task: Read data from file and send (using full parallel 128-bit scrambled transmission)
  // Automatically detect frame header and apply scrambler mask (if scrambler enabled)
  //-------------------------------------------------------------------------
  task do_drive();
    send_frame_from_file(file_path);
  endtask

  //-------------------------------------------------------------------------
  // Send training sequence: train_count blocks of TRAINING_PATN, then one block of TRAINING_EXIT
  // Optionally disable scrambling during training
  //-------------------------------------------------------------------------
  task send_training();
    bit [127:0] train_block = {4{TRAINING_PATN}};
    bit [127:0] exit_block = {4{TRAINING_EXIT}};
    bit prev_scrambler_en = scrambler_en;
    scrambler_en = 0;  // Training patterns are not scrambled
    send_blocks(train_block, train_count);
    send_block(exit_block);
    scrambler_en = prev_scrambler_en;
  endtask

  //-------------------------------------------------------------------------
  // Optional: Combined task to send training then data frame
  //-------------------------------------------------------------------------
  task send_training_and_data(string filename);
    send_training();
    send_frame_from_file(filename);
  endtask

  //-------------------------------------------------------------------------
  // CRC32 calculation functions (polynomial 0x04C11DB7, initial value 0xFFFFFFFF)
  //-------------------------------------------------------------------------
  static function automatic bit [31:0] crc32_next(input bit [31:0] crc, input bit [31:0] data);
    bit [31:0] new_crc;
    bit b;
    new_crc = crc;
    for (int i = 0; i < 32; i++) 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
    return new_crc;
  endfunction

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

endclass

`endif // LVDS_DRIVER_SV
