//
// Copyright 2019 Ettus Research, A National Instruments Company
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: PkgRfnocItemUtils
//
// Description: This package contains classes to handle and process CHDR items
// (e.g., samples) and to convert between CHDR words and items. It includes the
// following classes:
//
//   - ItemDataBuff: A class that holds a collection of items of arbitrary
//                   width and functions to convert to and from a CHDR vector.
//
//   - ItemDataBuffQueue: A queue of ItemDataBuff buffers
//

package PkgRfnocItemUtils;

  import rfnoc_chdr_utils_pkg::*;

  //---------------------------------------------------------------------------
  // Item Data Buffer
  //---------------------------------------------------------------------------
  //
  // This is a class of "items" of an arbitrary type
  //
  //---------------------------------------------------------------------------

  class ItemDataBuff #(type item_t = logic [31:0], int CHDR_W = 64);

    localparam ITEM_W = $bits(item_t);

    // Redefine these types from rfnoc_chdr_utils_pkg to workaround Vivado 2019.1 bug
    typedef logic [CHDR_W-1:0] chdr_word_t;
    typedef chdr_word_t        chdr_word_queue_t[$];

    // Store data in the user-specified format
    local item_t buff[$];

    // Create a new empty buffer
    function new();
      buff.delete();
    endfunction : new

    // Get the bitwidth of a item in this buffer
    function int item_w();
      return ITEM_W;
    endfunction : item_w

    // Delete the contents of this buffer
    function void delete();
      buff.delete();
    endfunction : delete

    // Get the size (in number of words) of this buffer
    function int size();
      return buff.size();
    endfunction : size

    // Get the size (in number of bytes) of this buffer
    function int get_bytes();
      return size() * (item_w() / 8);
    endfunction : get_bytes

    // Get the i'th element in this buffer
    function item_t get(int i);
      return buff[i];
    endfunction : get

    // Put and element in this buffer. If i is negative
    // then put it at the end
    function void put(item_t d, int i = -1);
      if (i < 0)
        buff.push_back(d);
      else
        buff.insert(i, d);
    endfunction : put

    // Convert the contents of this buffer to a CHDR payload A CHDR payload can
    // be transmitted using the block controller BFM.
    function chdr_word_queue_t to_chdr_payload();
      int samps_per_word = CHDR_W / ITEM_W;
      int num_chdr_lines = ((buff.size() + samps_per_word - 1) / samps_per_word);
      chdr_word_t chdr_w_vtr[$];
      for (int i = 0; i < num_chdr_lines; i++) begin
        chdr_word_t tmp = 'x;
        for (int j = 0; j < samps_per_word; j++) begin
          tmp[j*ITEM_W +: ITEM_W] = buff[i*samps_per_word + j];
        end
        chdr_w_vtr.push_back(tmp);
      end
      return chdr_w_vtr;
    endfunction : to_chdr_payload

    // Populate this buffer using a CHDR payload A CHDR payload can be received
    // using the block controller BFM.
    function void from_chdr_payload(chdr_word_queue_t chdr_w_vtr, int bytes);
      int samps_per_word = CHDR_W / ITEM_W;
      int bytes_left = bytes;
      buff.delete();
      foreach (chdr_w_vtr[i]) begin
        for (int j = 0; j < samps_per_word; j++) begin
          if (bytes_left > 0) begin
            buff.push_back(chdr_w_vtr[i][j*ITEM_W +: ITEM_W]);
            bytes_left -= (ITEM_W/8);
          end
        end
      end
    endfunction : from_chdr_payload

    // Output a string representation of the contents of the buffer
    function string sprint(string format = "%X", bit as_chdr = 0);
      if (as_chdr) begin
        string str = $sformatf("ItemDataBuff (64-bit CHDR, %0d bytes)\n", get_bytes());
        chdr_word_queue_t chdr_q = this.to_chdr_payload();
        foreach (chdr_q[i]) begin
          str = { str, $sformatf({"%5d> ", format, "\n"}, i, chdr_q[i]) };
        end
        return str;
      end else begin
        string str = $sformatf("ItemDataBuff (%0d-bit)\n", ITEM_W);
        foreach (buff[i]) begin
          str = { str, $sformatf({"%5d> ", format, "\n"}, i, buff[i]) };
        end
        return str;
      end
    endfunction : sprint

    // Print a string representation of the contents of the buffer
    function void print(string format = "%X", bit as_chdr = 0);
      $display(sprint(format, as_chdr));
    endfunction : print

    // Check if the contents of two buffers is equal
    function bit equal(
      ItemDataBuff #(item_t,CHDR_W) rhs
    );
      if (this.size() != rhs.size()) return 0;
      for (int i = 0; i < this.size(); i++) begin
        if (this.get(i) != rhs.get(i)) return 0;
      end
      return 1;
    endfunction : equal

  endclass


  //---------------------------------------------------------------------------
  // Item Data Buffer Queue
  //---------------------------------------------------------------------------
  //
  // This is a queue of item buffers
  //
  //---------------------------------------------------------------------------

  class ItemDataBuffQueue #(type item_t = logic [31:0], int CHDR_W = 64);
    local ItemDataBuff #(item_t, CHDR_W) queue[$];

    // Create a new empty queue
    function new();
      queue.delete();
    endfunction : new

    // Delete the contents of this queue
    function void delete();
      queue.delete();
    endfunction : delete

    // Get the size (in number of buffers) of this queue
    function int size();
      return queue.size();
    endfunction : size

    // Get the i'th element in this buffer
    function ItemDataBuff #(item_t) get(int i);
      return queue[i];
    endfunction : get

    // Put an element in this buffer. If i is negative
    // then put it at the end
    function void put(ItemDataBuff #(item_t) buff, int i = -1);
      if (i < 0)
        queue.push_back(buff);
      else
        queue.insert(i, buff);
    endfunction : put

    // Create a queue of buffers and fill them using the contents of the specified file.
    function void from_hex_file(
      int max_buff_size,
      string filename
    );
      string line;
      int word_i = 0;
      int handle = $fopen(filename, "r");
      queue.delete();
      while ($fgets(line, handle) > 0) begin
        item_t word = 'x;
        int buff_i = word_i++ / max_buff_size;
        if (queue.size() < buff_i + 1) begin
          ItemDataBuff #(item_t) buff = new;
          queue.push_back(buff);
        end
        if ($sscanf(line, "%x", word) > 0) begin
          queue[buff_i].put(word);
        end else begin
          $error("File parse error");
        end
      end
      $fclose(handle);
      handle = 0;
    endfunction : from_hex_file

    // Write the contents of the queue of buffers to the specified file.
    function void to_hex_file(
      string filename
    );
      int handle = $fopen(filename, "w");
      foreach (queue[i]) begin
        for (int j = 0; j < queue[i].size(); j++) begin
          $fdisplay(handle, $sformatf("%x", queue[i].get(j)));
        end
      end
      $fclose(handle);
      handle = 0;
    endfunction : to_hex_file

  endclass
endpackage : PkgRfnocItemUtils
