EttusResearch / uhd

The USRP™ Hardware Driver Repository
http://uhd.ettus.com
Other
942 stars 644 forks source link

gain block example not working on X410 #762

Open mbr0wn opened 1 week ago

mbr0wn commented 1 week ago

Thanks to @ptrkrysik for pointing this out on the mailing list.

I'm pasting this from the mailing list:

Ok. I know what was stopping the RFNoC flowgraph after correction of the clock. 
I was setting SPP (samples-per-packet) to a value that was somehow wrong for 
this case. After removing that command-line parameter, the loopback through 
‘Gain’ block started to work.

I’m posting my changed ‘Gain’ block, that is clocked with ‘ce’ clock, in case 
anyone is interested.

Best Regards,\
Piotr Krysik

//
// Copyright 2024 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: rfnoc_block_gain_tb
//
// Description: Testbench for the gain RFNoC block.
//

`default_nettype none

module rfnoc_block_gain_tb #(
  string IP_OPTION = "HDL_IP"
);

  `include "test_exec.svh"

  import PkgTestExec::*;
  import PkgChdrUtils::*;
  import PkgRfnocBlockCtrlBfm::*;
  import PkgRfnocItemUtils::*;

  //---------------------------------------------------------------------------
  // Testbench Configuration
  //---------------------------------------------------------------------------

  localparam [31:0] NOC_ID          = 32'h00000B16;
  localparam [ 9:0] THIS_PORTID     = 10'h123;
  localparam int    CHDR_W          = 64;    // CHDR size in bits
  localparam int    MTU             = 13;    // Log2 of max transmission unit 
in CHDR words
  localparam int    NUM_PORTS_I     = 1;
  localparam int    NUM_PORTS_O     = 1;
  localparam int    ITEM_W          = 32;    // Sample size in bits
  localparam int    SPP             = 64;    // Samples per packet
  localparam int    PKT_SIZE_BYTES  = SPP * (ITEM_W/8);
  localparam int    STALL_PROB      = 25;    // Default BFM stall probability
  localparam real   CHDR_CLK_PER    = 5.0;   // 200 MHz
  localparam real   CTRL_CLK_PER    = 8.0;   // 125 MHz
  localparam real   CE_CLK_PER      = 4.0;   // 250 MHz

  //---------------------------------------------------------------------------
  // Clocks and Resets
  //---------------------------------------------------------------------------

  bit rfnoc_chdr_clk;
  bit rfnoc_ctrl_clk;
  bit ce_clk;

  sim_clock_gen #(CHDR_CLK_PER) rfnoc_chdr_clk_gen (.clk(rfnoc_chdr_clk), 
.rst());
  sim_clock_gen #(CTRL_CLK_PER) rfnoc_ctrl_clk_gen (.clk(rfnoc_ctrl_clk), 
.rst());
  sim_clock_gen #(CE_CLK_PER) ce_clk_gen (.clk(ce_clk), .rst());

  //---------------------------------------------------------------------------
  // Bus Functional Models
  //---------------------------------------------------------------------------

  // Backend Interface
  RfnocBackendIf backend (rfnoc_chdr_clk, rfnoc_ctrl_clk);

  // AXIS-Ctrl Interface
  AxiStreamIf #(32) m_ctrl (rfnoc_ctrl_clk, 1'b0);
  AxiStreamIf #(32) s_ctrl (rfnoc_ctrl_clk, 1'b0);

  // AXIS-CHDR Interfaces
  AxiStreamIf #(CHDR_W) m_chdr [NUM_PORTS_I] (rfnoc_chdr_clk, 1'b0);
  AxiStreamIf #(CHDR_W) s_chdr [NUM_PORTS_O] (rfnoc_chdr_clk, 1'b0);

  // Block Controller BFM
  RfnocBlockCtrlBfm #(CHDR_W, ITEM_W) blk_ctrl = new(backend, m_ctrl, s_ctrl);

  // CHDR word and item/sample data types
  typedef ChdrData #(CHDR_W, ITEM_W)::chdr_word_t chdr_word_t;
  typedef ChdrData #(CHDR_W, ITEM_W)::item_t      item_t;

  // Connect block controller to BFMs
  for (genvar i = 0; i < NUM_PORTS_I; i++) begin : gen_bfm_input_connections
    initial begin
      blk_ctrl.connect_master_data_port(i, m_chdr[i], PKT_SIZE_BYTES);
      blk_ctrl.set_master_stall_prob(i, STALL_PROB);
    end
  end
  for (genvar i = 0; i < NUM_PORTS_O; i++) begin : gen_bfm_output_connections
    initial begin
      blk_ctrl.connect_slave_data_port(i, s_chdr[i]);
      blk_ctrl.set_slave_stall_prob(i, STALL_PROB);
    end
  end

  //---------------------------------------------------------------------------
  // Device Under Test (DUT)
  //---------------------------------------------------------------------------

  // DUT Slave (Input) Port Signals
  logic [CHDR_W*NUM_PORTS_I-1:0] s_rfnoc_chdr_tdata;
  logic [       NUM_PORTS_I-1:0] s_rfnoc_chdr_tlast;
  logic [       NUM_PORTS_I-1:0] s_rfnoc_chdr_tvalid;
  logic [       NUM_PORTS_I-1:0] s_rfnoc_chdr_tready;

  // DUT Master (Output) Port Signals
  logic [CHDR_W*NUM_PORTS_O-1:0] m_rfnoc_chdr_tdata;
  logic [       NUM_PORTS_O-1:0] m_rfnoc_chdr_tlast;
  logic [       NUM_PORTS_O-1:0] m_rfnoc_chdr_tvalid;
  logic [       NUM_PORTS_O-1:0] m_rfnoc_chdr_tready;

  // Map the array of BFMs to a flat vector for the DUT connections
  for (genvar i = 0; i < NUM_PORTS_I; i++) begin : gen_dut_input_connections
    // Connect BFM master to DUT slave port
    assign s_rfnoc_chdr_tdata[CHDR_W*i+:CHDR_W] = m_chdr[i].tdata;
    assign s_rfnoc_chdr_tlast[i]                = m_chdr[i].tlast;
    assign s_rfnoc_chdr_tvalid[i]               = m_chdr[i].tvalid;
    assign m_chdr[i].tready                     = s_rfnoc_chdr_tready[i];
  end
  for (genvar i = 0; i < NUM_PORTS_O; i++) begin : gen_dut_output_connections
    // Connect BFM slave to DUT master port
    assign s_chdr[i].tdata        = m_rfnoc_chdr_tdata[CHDR_W*i+:CHDR_W];
    assign s_chdr[i].tlast        = m_rfnoc_chdr_tlast[i];
    assign s_chdr[i].tvalid       = m_rfnoc_chdr_tvalid[i];
    assign m_rfnoc_chdr_tready[i] = s_chdr[i].tready;
  end

  rfnoc_block_gain #(
    .THIS_PORTID         (THIS_PORTID),
    .CHDR_W              (CHDR_W),
    .MTU                 (MTU),
    .IP_OPTION           (IP_OPTION)
  ) dut (
    .rfnoc_chdr_clk      (rfnoc_chdr_clk),
    .rfnoc_ctrl_clk      (rfnoc_ctrl_clk),
    .ce_clk              (ce_clk),
    .rfnoc_core_config   (backend.cfg),
    .rfnoc_core_status   (backend.sts),
    .s_rfnoc_chdr_tdata  (s_rfnoc_chdr_tdata),
    .s_rfnoc_chdr_tlast  (s_rfnoc_chdr_tlast),
    .s_rfnoc_chdr_tvalid (s_rfnoc_chdr_tvalid),
    .s_rfnoc_chdr_tready (s_rfnoc_chdr_tready),
    .m_rfnoc_chdr_tdata  (m_rfnoc_chdr_tdata),
    .m_rfnoc_chdr_tlast  (m_rfnoc_chdr_tlast),
    .m_rfnoc_chdr_tvalid (m_rfnoc_chdr_tvalid),
    .m_rfnoc_chdr_tready (m_rfnoc_chdr_tready),
    .s_rfnoc_ctrl_tdata  (m_ctrl.tdata),
    .s_rfnoc_ctrl_tlast  (m_ctrl.tlast),
    .s_rfnoc_ctrl_tvalid (m_ctrl.tvalid),
    .s_rfnoc_ctrl_tready (m_ctrl.tready),
    .m_rfnoc_ctrl_tdata  (s_ctrl.tdata),
    .m_rfnoc_ctrl_tlast  (s_ctrl.tlast),
    .m_rfnoc_ctrl_tvalid (s_ctrl.tvalid),
    .m_rfnoc_ctrl_tready (s_ctrl.tready)
  );

  //---------------------------------------------------------------------------
  // Main Test Process
  //---------------------------------------------------------------------------

  // Multiply two signed 16-bit numbers and clip the result
  function shortint mult_and_clip(shortint a, shortint b);
    int      product;
    shortint result;
    product = int'(a) * int'(b);
    result = product[15:0];
    if (product > 16'sh7FFF) result = 16'sh7FFF;
    if (product < 16'sh8000) result = 16'sh8000;
    return result;
  endfunction : mult_and_clip

  // Generate a random signed 16-bit integer in the range [a, b]
  function shortint rand_shortint(int a, int b);
    return signed'($urandom_range(b - a)) + a;
  endfunction : rand_shortint

  localparam int REG_GAIN_ADDR = dut.REG_GAIN_ADDR;

  initial begin : tb_main
    string tb_name;
    tb_name = $sformatf("rfnoc_block_gain_tb: IP_OPTION = %s", IP_OPTION);

    // Initialize the test exec object for this testbench
    test.start_tb(tb_name);

    // Start the BFMs running
    blk_ctrl.run();

    //--------------------------------
    // Reset
    //--------------------------------

    test.start_test("Flush block then reset it", 10us);
    blk_ctrl.flush_and_reset();
    test.end_test();

    //--------------------------------
    // Verify Block Info
    //--------------------------------

    test.start_test("Verify Block Info", 2us);
    `ASSERT_ERROR(blk_ctrl.get_noc_id() == NOC_ID, "Incorrect NOC_ID Value");
    `ASSERT_ERROR(blk_ctrl.get_num_data_i() == NUM_PORTS_I, "Incorrect 
NUM_DATA_I Value");
    `ASSERT_ERROR(blk_ctrl.get_num_data_o() == NUM_PORTS_O, "Incorrect 
NUM_DATA_O Value");
    `ASSERT_ERROR(blk_ctrl.get_mtu() == MTU, "Incorrect MTU Value");
    test.end_test();

    //--------------------------------
    // Test Sequences
    //--------------------------------

    begin
      // Read and write the gain register to make sure it updates correctly.
      logic [31:0] val32;
      test.start_test("Verify gain register", 5us);

      blk_ctrl.reg_read(REG_GAIN_ADDR, val32);
      `ASSERT_ERROR(
        val32 == 1, "Initial value for REG_GAIN_ADDR is not 1");

      // Write a value wider than the register to verify the width
      blk_ctrl.reg_write(REG_GAIN_ADDR, 32'h12348765);
      blk_ctrl.reg_read(REG_GAIN_ADDR, val32);
      `ASSERT_ERROR(
        val32 == 32'h8765, "Initial value for REG_GAIN_ADDR is not correct");

      test.end_test();
    end

    begin
      // Iterate through a series of gain values to test.
      localparam shortint MAX_TEST_VAL =  255;
      localparam shortint MIN_TEST_VAL = -255;
      static logic [15:0] test_gains[$] = {
        1,     // Make sure unity gain leaves data unmodified
        -1,    // Make sure -1 negates data
        0,     // Make sure 0 gain results in 0
        37,    // Make sure a normal gain computes correctly
        -22,   // Make sure a normal gain computes correctly
        256    // Make sure a large gain causes clipping
      };

      foreach (test_gains[gain_index]) begin
        shortint     gain;
        int          num_bytes;
        item_t send_samples[$];    // Sample words
        item_t recv_samples[$];    // Sample words

        gain = test_gains[gain_index];

        test.start_test($sformatf("Test gain of %0d", int'(gain)), 10us);

        blk_ctrl.reg_write(REG_GAIN_ADDR, gain);

        // Generate a payload of random samples in the range [-255, 255], two
        // samples per CHDR word.
        send_samples = {};
        for (int i = 0; i < SPP; i++) begin
          send_samples.push_back({
            rand_shortint(MIN_TEST_VAL, MAX_TEST_VAL),  // I
            rand_shortint(MIN_TEST_VAL, MAX_TEST_VAL)   // Q
          });
        end

        // Queue a packet for transfer
        blk_ctrl.send_items(0, send_samples);

        // Receive the output packet
        blk_ctrl.recv_items(0, recv_samples);

        // Check the resulting payload size
        `ASSERT_ERROR(recv_samples.size() == SPP,
          "Received payload didn't match size of payload sent");

        // Check the resulting samples
        for (int i = 0; i < SPP; i++) begin
          item_t sample_in;
          item_t expected;
          item_t sample_out;

          sample_in  = send_samples[i];
          sample_out = recv_samples[i];

          expected[31:16] = mult_and_clip(gain, sample_in[31:16]);  // I
          expected[15: 0] = mult_and_clip(gain, sample_in[15: 0]);  // Q

          `ASSERT_ERROR(
            sample_out == expected,
            $sformatf("For sample %0d, gain %0d, input 0x%X, received 0x%X, 
expected 0x%X",
                      i, gain, sample_in, sample_out, expected));
        end

        test.end_test();
      end
    end

    //--------------------------------
    // Finish Up
    //--------------------------------

    // Display final statistics and results. End the TB, but don't $finish,
    // since we don't want to kill other instances of this testbench that may
    // be running.
    test.end_tb(.finish(0));
  end : tb_main

endmodule : rfnoc_block_gain_tb

`default_nettype wire

schema: rfnoc_modtool_args
module_name: gain
version: "1.0"
rfnoc_version: "1.0"
chdr_width: 64
noc_id: 0xB16

clocks:
  - name: rfnoc_chdr
    freq: "[]"
  - name: rfnoc_ctrl
    freq: "[]"
  - name: ce
    freq: "[]"

control:
  fpga_iface: ctrlport
  interface_direction: slave
  fifo_depth: 32
  clk_domain: ce
  ctrlport:
    byte_mode: False
    timed: False
    has_status: False

data:
  fpga_iface: axis_pyld_ctxt
  clk_domain: ce
  inputs:
    in:
      index: 0
      item_width: 32
      nipc: 1
      context_fifo_depth: 2
      payload_fifo_depth: 2
      format: sc16
      mdata_sig: ~
  outputs:
    out:
      index: 0
      item_width: 32
      nipc: 1
      context_fifo_depth: 2
      payload_fifo_depth: 2
      format: sc16
      mdata_sig: ~

//
// Copyright 2021 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: rfnoc_block_gain
//
// Description:
//
//   This is an example RFNoC block. It applies a numeric gain to incoming
//   samples then outputs the result. A single register is used to control the
//   gain setting.
//
// Parameters:
//
//   THIS_PORTID : Control crossbar port to which this block is connected
//   CHDR_W      : AXIS-CHDR data bus width
//   MTU         : Maximum transmission unit (i.e., maximum packet size in
//                 CHDR words is 2**MTU).
//   IP_OPTION   : Select which IP to use for the complex multiply. Use one of
//                 the following options:
//                 HDL_IP         = In-tree RFNoC HDL, with a DSP48E1 primitive
//                 IN_TREE_IP     = In-tree "complex_multiplier" (Xilinx IP)
//                 OUT_OF_TREE_IP = Out-of-tree "cmplx_mul" (Xilinx IP)
//

`default_nettype none

module rfnoc_block_gain #(
  parameter [9:0] THIS_PORTID     = 10'd0,
  parameter       CHDR_W          = 64,
  parameter [5:0] MTU             = 10,
  parameter       IP_OPTION       = "IN_TREE_IP"
) (
  // RFNoC Framework Clocks and Resets
  input  wire                   rfnoc_chdr_clk,
  input  wire                   rfnoc_ctrl_clk,
  input  wire                   ce_clk,
  // RFNoC Backend Interface
  input  wire [511:0]           rfnoc_core_config,
  output wire [511:0]           rfnoc_core_status,
  // AXIS-CHDR Input Ports (from framework)
  input  wire [(1)*CHDR_W-1:0] s_rfnoc_chdr_tdata,
  input  wire [(1)-1:0]        s_rfnoc_chdr_tlast,
  input  wire [(1)-1:0]        s_rfnoc_chdr_tvalid,
  output wire [(1)-1:0]        s_rfnoc_chdr_tready,
  // AXIS-CHDR Output Ports (to framework)
  output wire [(1)*CHDR_W-1:0] m_rfnoc_chdr_tdata,
  output wire [(1)-1:0]        m_rfnoc_chdr_tlast,
  output wire [(1)-1:0]        m_rfnoc_chdr_tvalid,
  input  wire [(1)-1:0]        m_rfnoc_chdr_tready,
  // AXIS-Ctrl Input Port (from framework)
  input  wire [31:0]            s_rfnoc_ctrl_tdata,
  input  wire                   s_rfnoc_ctrl_tlast,
  input  wire                   s_rfnoc_ctrl_tvalid,
  output wire                   s_rfnoc_ctrl_tready,
  // AXIS-Ctrl Output Port (to framework)
  output wire [31:0]            m_rfnoc_ctrl_tdata,
  output wire                   m_rfnoc_ctrl_tlast,
  output wire                   m_rfnoc_ctrl_tvalid,
  input  wire                   m_rfnoc_ctrl_tready
);

  // These are examples of how to include an in-tree header file. UHD_FPGA_DIR
  // is defined automatically and can be referenced as needed. Tools vary
  // somewhat in how they support using macros in `include statements.
  //
  // This works in Vivado:
  //
  //   `include `"`UHD_FPGA_DIR/usrp3/lib/rfnoc/core/rfnoc_chdr_utils.vh`"
  //
  // Some tools allow this:
  //
  //   `define INCLUDE_UHD_FILE(REL_PATH) `"`UHD_FPGA_DIR/REL_PATH`"
  //   `include `INCLUDE_UHD_FILE(usrp3/lib/rfnoc/core/rfnoc_chdr_utils.vh)
  //
  // This should work in most tools:
  `define RFNOC_CHDR_UTILS_PATH 
`"`UHD_FPGA_DIR/usrp3/lib/rfnoc/core/rfnoc_chdr_utils.vh`"
  `include `RFNOC_CHDR_UTILS_PATH

  //---------------------------------------------------------------------------
  // Signal Declarations
  //---------------------------------------------------------------------------

  // Clocks and Resets
  wire               ctrlport_clk;
  wire               ctrlport_rst;
  wire               axis_data_clk;
  wire               axis_data_rst;
  // CtrlPort Master
  wire               m_ctrlport_req_wr;
  wire               m_ctrlport_req_rd;
  wire [19:0]        m_ctrlport_req_addr;
  wire [31:0]        m_ctrlport_req_data;
  reg                m_ctrlport_resp_ack;
  reg  [31:0]        m_ctrlport_resp_data;
  // Payload Stream to User Logic: in
  wire [32*1-1:0]    m_in_payload_tdata;
  wire [1-1:0]       m_in_payload_tkeep;
  wire               m_in_payload_tlast;
  wire               m_in_payload_tvalid;
  wire               m_in_payload_tready;
  // Context Stream to User Logic: in
  wire [CHDR_W-1:0]  m_in_context_tdata;
  wire [3:0]         m_in_context_tuser;
  wire               m_in_context_tlast;
  wire               m_in_context_tvalid;
  wire               m_in_context_tready;
  // Payload Stream from User Logic: out
  wire [32*1-1:0]    s_out_payload_tdata;
  wire [0:0]         s_out_payload_tkeep;
  wire               s_out_payload_tlast;
  wire               s_out_payload_tvalid;
  wire               s_out_payload_tready;
  // Context Stream from User Logic: out
  wire [CHDR_W-1:0]  s_out_context_tdata;
  wire [3:0]         s_out_context_tuser;
  wire               s_out_context_tlast;
  wire               s_out_context_tvalid;
  wire               s_out_context_tready;

  //---------------------------------------------------------------------------
  // NoC Shell
  //---------------------------------------------------------------------------

  noc_shell_gain #(
    .CHDR_W              (CHDR_W),
    .THIS_PORTID         (THIS_PORTID),
    .MTU                 (MTU)
  ) noc_shell_gain_i (
    //---------------------
    // Framework Interface
    //---------------------

    // Clock Inputs
    .rfnoc_chdr_clk      (rfnoc_chdr_clk),
    .rfnoc_ctrl_clk      (rfnoc_ctrl_clk),
    .ce_clk              (ce_clk),
    // Reset Outputs
    .rfnoc_chdr_rst      (),
    .rfnoc_ctrl_rst      (),
    .ce_rst              (),
    // RFNoC Backend Interface
    .rfnoc_core_config   (rfnoc_core_config),
    .rfnoc_core_status   (rfnoc_core_status),
    // CHDR Input Ports  (from framework)
    .s_rfnoc_chdr_tdata  (s_rfnoc_chdr_tdata),
    .s_rfnoc_chdr_tlast  (s_rfnoc_chdr_tlast),
    .s_rfnoc_chdr_tvalid (s_rfnoc_chdr_tvalid),
    .s_rfnoc_chdr_tready (s_rfnoc_chdr_tready),
    // CHDR Output Ports (to framework)
    .m_rfnoc_chdr_tdata  (m_rfnoc_chdr_tdata),
    .m_rfnoc_chdr_tlast  (m_rfnoc_chdr_tlast),
    .m_rfnoc_chdr_tvalid (m_rfnoc_chdr_tvalid),
    .m_rfnoc_chdr_tready (m_rfnoc_chdr_tready),
    // AXIS-Ctrl Input Port (from framework)
    .s_rfnoc_ctrl_tdata  (s_rfnoc_ctrl_tdata),
    .s_rfnoc_ctrl_tlast  (s_rfnoc_ctrl_tlast),
    .s_rfnoc_ctrl_tvalid (s_rfnoc_ctrl_tvalid),
    .s_rfnoc_ctrl_tready (s_rfnoc_ctrl_tready),
    // AXIS-Ctrl Output Port (to framework)
    .m_rfnoc_ctrl_tdata  (m_rfnoc_ctrl_tdata),
    .m_rfnoc_ctrl_tlast  (m_rfnoc_ctrl_tlast),
    .m_rfnoc_ctrl_tvalid (m_rfnoc_ctrl_tvalid),
    .m_rfnoc_ctrl_tready (m_rfnoc_ctrl_tready),

    //---------------------
    // Client Interface
    //---------------------

    // CtrlPort Clock and Reset
    .ctrlport_clk              (ctrlport_clk),
    .ctrlport_rst              (ctrlport_rst),
    // CtrlPort Master
    .m_ctrlport_req_wr         (m_ctrlport_req_wr),
    .m_ctrlport_req_rd         (m_ctrlport_req_rd),
    .m_ctrlport_req_addr       (m_ctrlport_req_addr),
    .m_ctrlport_req_data       (m_ctrlport_req_data),
    .m_ctrlport_resp_ack       (m_ctrlport_resp_ack),
    .m_ctrlport_resp_data      (m_ctrlport_resp_data),

    // AXI-Stream Payload Context Clock and Reset
    .axis_data_clk (axis_data_clk),
    .axis_data_rst (axis_data_rst),
    // Payload Stream to User Logic: in
    .m_in_payload_tdata  (m_in_payload_tdata),
    .m_in_payload_tkeep  (m_in_payload_tkeep),
    .m_in_payload_tlast  (m_in_payload_tlast),
    .m_in_payload_tvalid (m_in_payload_tvalid),
    .m_in_payload_tready (m_in_payload_tready),
    // Context Stream to User Logic: in
    .m_in_context_tdata  (m_in_context_tdata),
    .m_in_context_tuser  (m_in_context_tuser),
    .m_in_context_tlast  (m_in_context_tlast),
    .m_in_context_tvalid (m_in_context_tvalid),
    .m_in_context_tready (m_in_context_tready),
    // Payload Stream from User Logic: out
    .s_out_payload_tdata  (s_out_payload_tdata),
    .s_out_payload_tkeep  (s_out_payload_tkeep),
    .s_out_payload_tlast  (s_out_payload_tlast),
    .s_out_payload_tvalid (s_out_payload_tvalid),
    .s_out_payload_tready (s_out_payload_tready),
    // Context Stream from User Logic: out
    .s_out_context_tdata  (s_out_context_tdata),
    .s_out_context_tuser  (s_out_context_tuser),
    .s_out_context_tlast  (s_out_context_tlast),
    .s_out_context_tvalid (s_out_context_tvalid),
    .s_out_context_tready (s_out_context_tready)
  );

  //---------------------------------------------------------------------------
  // User Logic
  //---------------------------------------------------------------------------
  //
  // The code above this point is essentially unmodified from what was
  // generated by the tool. The code below implements the gain example.
  //
  // All registers are in the ctrlport_clk domain and the signal processing is
  // in the axis_data_clk domain. However, we specified in the block YAML
  // configuration file that we want both the control and data interfaces on
  // the rfnoc_chdr clock. So we don't need to worry about crossing the
  // register data from ctrlport_clk and axis_data_clk.
  //
  //---------------------------------------------------------------------------

  //---------------------------------------------------------------------------
  // Registers
  //---------------------------------------------------------------------------
  //
  // There's only one register now, but we'll structure the register code to
  // make it easier to add more registers later.
  //
  //---------------------------------------------------------------------------

  localparam REG_GAIN_ADDR    = 0;    // Address for gain value
  localparam REG_GAIN_DEFAULT = 1;    // Default gain value

  reg [15:0] reg_gain = REG_GAIN_DEFAULT;

  always @(posedge ctrlport_clk) begin
    if (ctrlport_rst) begin
      reg_gain = REG_GAIN_DEFAULT;
    end else begin
      // Default assignment
      m_ctrlport_resp_ack <= 0;

      // Handle read requests
      if (m_ctrlport_req_rd) begin
        case (m_ctrlport_req_addr)
          REG_GAIN_ADDR: begin
            m_ctrlport_resp_ack  <= 1;
            m_ctrlport_resp_data <= { 16'b0, reg_gain };
          end
        endcase
      end

      // Handle write requests
      if (m_ctrlport_req_wr) begin
        case (m_ctrlport_req_addr)
          REG_GAIN_ADDR: begin
            m_ctrlport_resp_ack <= 1;
            reg_gain            <= m_ctrlport_req_data[15:0];
          end
        endcase
      end
    end
  end

  //---------------------------------------------------------------------------
  // Signal Processing
  //---------------------------------------------------------------------------
  //
  // Multiply each complex sample by a real-valued gain. The RFNoC signals
  // m_in_payload_* and m_out_payload_* expect the data with the real/I
  // component on the upper bits [31:16] and the imaginary/Q component on the
  // lower bits [15:0].
  //
  // We only input the real-valued gain (reg_gain) when we have payload data to
  // go in (m_in_payload_*). That way the current gain value always applies to
  // the current sample. This assumes that the tready of both inputs have
  // identical behavior.
  //
  //---------------------------------------------------------------------------

  // Multiply result. I/real in [63:32], Q/imaginary in [31:0] (sc32).
  wire [63:0] mult_tdata;
  wire        mult_tlast;
  wire        mult_tvalid;
  wire        mult_tready;

  generate
    // Use a generate statement to choose which IP to use for the multiply.
    // These all do the same thing and we only have multiple options to show
    // how you can use IP from different locations.

    if (IP_OPTION == "HDL_IP") begin : gen_rfnoc_ip
      // Use the RFNoC mult_rc Verilog module, which uses a DSP48E1 primitive
      mult_rc #(
        .WIDTH_REAL (16),
        .WIDTH_CPLX (16),
        .WIDTH_P    (32),
        .DROP_TOP_P (5),     // Must be 5 for a normal multiply in DSP48E1
        .LATENCY    (4)      // Turn on all pipeline registers in the DSP48E1
      ) mult_rc_i (
        .clk         (axis_data_clk),
        .reset       (axis_data_rst),
        .real_tdata  (reg_gain),
        .real_tlast  (m_in_payload_tlast),
        .real_tvalid (m_in_payload_tvalid),
        .real_tready (),
        .cplx_tdata  (m_in_payload_tdata),
        .cplx_tlast  (m_in_payload_tlast),
        .cplx_tvalid (m_in_payload_tvalid),
        .cplx_tready (m_in_payload_tready),
        .p_tdata     (mult_tdata),
        .p_tlast     (mult_tlast),
        .p_tvalid    (mult_tvalid),
        .p_tready    (mult_tready)
      );

    end else if (IP_OPTION == "IN_TREE_IP") begin : gen_in_tree_ip
      // Use the "cmul" module, which uses the in-tree "complex_multiplier" IP.
      // This is a Xilinx Complex Multiplier LogiCORE IP located in the UHD
      // repository in fpga/usrp3/lib/ip/.

      // The LSB of the output is clipped in this IP, so double the gain to
      // compensate. This limits the maximum gain in this version.
      wire [15:0] gain = 2*reg_gain;

      cmul cmul_i (
        .clk      (axis_data_clk),
        .reset    (axis_data_rst),
        .a_tdata  ({gain, 16'b0}),
        .a_tlast  (m_in_payload_tlast ),
        .a_tvalid (m_in_payload_tvalid),
        .a_tready (),
        .b_tdata  (m_in_payload_tdata ),
        .b_tlast  (m_in_payload_tlast ),
        .b_tvalid (m_in_payload_tvalid),
        .b_tready (m_in_payload_tready),
        .o_tdata  (mult_tdata),
        .o_tlast  (mult_tlast),
        .o_tvalid (mult_tvalid),
        .o_tready (mult_tready)
      );

    end else if (IP_OPTION == "OUT_OF_TREE_IP") begin : gen_oot_ip
      // Use the out-of-tree "cmplx_mul" IP, which is a Xilinx Complex
      // Multiplier LogiCORE IP located in the IP directory of this example.

      // This IP expects real/I in the lower bits and imaginary/Q in the upper
      // bits, so we swap I and Q on the input and output to match RFNoC.
      //
      // This IP has a 33-bit output, but because it's AXI-Stream, each
      // component is placed in a 5-byte word. Since our gain is real only,
      // we'll never need all 33-bits.
      wire [79:0] m_axis_dout_tdata;

      cmplx_mul cmplx_mul_i (
        .aclk               (axis_data_clk),
        .aresetn            (~axis_data_rst),
        .s_axis_a_tdata     ({16'd0, reg_gain}),            // Real gain
        .s_axis_a_tlast     (m_in_payload_tlast),
        .s_axis_a_tvalid    (m_in_payload_tvalid),
        .s_axis_a_tready    (),
        .s_axis_b_tdata     ({m_in_payload_tdata[15:0],     // Imaginary
                              m_in_payload_tdata[31:16]}),  // Real
        .s_axis_b_tlast     (m_in_payload_tlast),
        .s_axis_b_tvalid    (m_in_payload_tvalid),
        .s_axis_b_tready    (m_in_payload_tready),
        .m_axis_dout_tdata  (m_axis_dout_tdata),
        .m_axis_dout_tlast  (mult_tlast),
        .m_axis_dout_tvalid (mult_tvalid),
        .m_axis_dout_tready (mult_tready)
      );

      assign mult_tdata[63:32] = m_axis_dout_tdata[31: 0]; // Real
      assign mult_tdata[31: 0] = m_axis_dout_tdata[71:40]; // Imaginary

    end
  endgenerate

  // Clip the results
  axi_clip_complex #(
    .WIDTH_IN  (32),
    .WIDTH_OUT (16)
  ) axi_clip_complex_i (
    .clk      (axis_data_clk),
    .reset    (axis_data_rst),
    .i_tdata  (mult_tdata),
    .i_tlast  (mult_tlast),
    .i_tvalid (mult_tvalid),
    .i_tready (mult_tready),
    .o_tdata  (s_out_payload_tdata),
    .o_tlast  (s_out_payload_tlast),
    .o_tvalid (s_out_payload_tvalid),
    .o_tready (s_out_payload_tready)
  );

  // Only 1-sample per clock, so tkeep should always be asserted
  assign s_out_payload_tkeep = 1'b1;

  // We're not doing anything fancy with the context (the CHDR header info) so
  // we can simply pass the input context through unchanged.
  assign s_out_context_tdata  = m_in_context_tdata;
  assign s_out_context_tuser  = m_in_context_tuser;
  assign s_out_context_tlast  = m_in_context_tlast;
  assign s_out_context_tvalid = m_in_context_tvalid;
  assign m_in_context_tready  = s_out_context_tready;

endmodule // rfnoc_block_gain

`default_nettype wire

//
// Copyright 2024 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: noc_shell_gain
//
// Description:
//
//   This is a tool-generated NoC-shell for the gain block.
//   See the RFNoC specification for more information about NoC shells.
//
// Parameters:
//
//   THIS_PORTID  : Control crossbar port to which this block is connected
//   CHDR_W       : AXIS-CHDR data bus width
//   CTRL_CLK_IDX : The index of the control clock for this block. This is used
//                  to populate the backend interface, from where UHD can query
//                  the clock index and thus auto-deduct which clock is used.
//   TB_CLK_IDX   : The index of the timebase clock for this block. This is used
//                  to populate the backend interface, from where UHD can query
//                  the clock index and thus auto-deduct which clock is used.
//   MTU          : Maximum transmission unit (i.e., maximum packet size in
//                  CHDR words is 2**MTU).
//

`default_nettype none

module noc_shell_gain #(
  parameter [9:0] THIS_PORTID     = 10'd0,
  parameter       CHDR_W          = 64,
  parameter [5:0] CTRL_CLK_IDX    = 6'h3F,
  parameter [5:0] TB_CLK_IDX      = 6'h3F,
  parameter [5:0] MTU             = 10
) (
  //---------------------
  // Framework Interface
  //---------------------

  // RFNoC Framework Clocks
  input  wire rfnoc_chdr_clk,
  input  wire rfnoc_ctrl_clk,
  input  wire ce_clk,

  // NoC Shell Generated Resets
  output wire rfnoc_chdr_rst,
  output wire rfnoc_ctrl_rst,
  output wire ce_rst,

  // RFNoC Backend Interface
  input  wire [511:0]          rfnoc_core_config,
  output wire [511:0]          rfnoc_core_status,

  // AXIS-CHDR Input Ports (from framework)
  input  wire [(1)*CHDR_W-1:0] s_rfnoc_chdr_tdata,
  input  wire [(1)-1:0]        s_rfnoc_chdr_tlast,
  input  wire [(1)-1:0]        s_rfnoc_chdr_tvalid,
  output wire [(1)-1:0]        s_rfnoc_chdr_tready,
  // AXIS-CHDR Output Ports (to framework)
  output wire [(1)*CHDR_W-1:0] m_rfnoc_chdr_tdata,
  output wire [(1)-1:0]        m_rfnoc_chdr_tlast,
  output wire [(1)-1:0]        m_rfnoc_chdr_tvalid,
  input  wire [(1)-1:0]        m_rfnoc_chdr_tready,

  // AXIS-Ctrl Control Input Port (from framework)
  input  wire [31:0]           s_rfnoc_ctrl_tdata,
  input  wire                  s_rfnoc_ctrl_tlast,
  input  wire                  s_rfnoc_ctrl_tvalid,
  output wire                  s_rfnoc_ctrl_tready,
  // AXIS-Ctrl Control Output Port (to framework)
  output wire [31:0]           m_rfnoc_ctrl_tdata,
  output wire                  m_rfnoc_ctrl_tlast,
  output wire                  m_rfnoc_ctrl_tvalid,
  input  wire                  m_rfnoc_ctrl_tready,

  //---------------------
  // Client Interface
  //---------------------

  // CtrlPort Clock and Reset
  output wire               ctrlport_clk,
  output wire               ctrlport_rst,
  // CtrlPort Master
  output wire               m_ctrlport_req_wr,
  output wire               m_ctrlport_req_rd,
  output wire [19:0]        m_ctrlport_req_addr,
  output wire [31:0]        m_ctrlport_req_data,
  input  wire               m_ctrlport_resp_ack,
  input  wire [31:0]        m_ctrlport_resp_data,

  // AXI-Stream Payload Context Clock and Reset
  output wire               axis_data_clk,
  output wire               axis_data_rst,
  // Payload Stream to User Logic: in
  output wire [32*1-1:0]    m_in_payload_tdata,
  output wire [1-1:0]       m_in_payload_tkeep,
  output wire               m_in_payload_tlast,
  output wire               m_in_payload_tvalid,
  input  wire               m_in_payload_tready,
  // Context Stream to User Logic: in
  output wire [CHDR_W-1:0]  m_in_context_tdata,
  output wire [3:0]         m_in_context_tuser,
  output wire               m_in_context_tlast,
  output wire               m_in_context_tvalid,
  input  wire               m_in_context_tready,
  // Payload Stream from User Logic: out
  input  wire [32*1-1:0]    s_out_payload_tdata,
  input  wire [0:0]         s_out_payload_tkeep,
  input  wire               s_out_payload_tlast,
  input  wire               s_out_payload_tvalid,
  output wire               s_out_payload_tready,
  // Context Stream from User Logic: out
  input  wire [CHDR_W-1:0]  s_out_context_tdata,
  input  wire [3:0]         s_out_context_tuser,
  input  wire               s_out_context_tlast,
  input  wire               s_out_context_tvalid,
  output wire               s_out_context_tready
);

  //---------------------------------------------------------------------------
  //  Backend Interface
  //---------------------------------------------------------------------------

  wire         data_i_flush_en;
  wire [31:0]  data_i_flush_timeout;
  wire [63:0]  data_i_flush_active;
  wire [63:0]  data_i_flush_done;
  wire         data_o_flush_en;
  wire [31:0]  data_o_flush_timeout;
  wire [63:0]  data_o_flush_active;
  wire [63:0]  data_o_flush_done;

  backend_iface #(
    .NOC_ID        (32'h00000B16),
    .NUM_DATA_I    (1),
    .NUM_DATA_O    (1),
    .CTRL_FIFOSIZE ($clog2(32)),
    .MTU           (MTU)
  ) backend_iface_i (
    .rfnoc_chdr_clk       (rfnoc_chdr_clk),
    .rfnoc_chdr_rst       (rfnoc_chdr_rst),
    .rfnoc_ctrl_clk       (rfnoc_ctrl_clk),
    .rfnoc_ctrl_rst       (rfnoc_ctrl_rst),
    .rfnoc_core_config    (rfnoc_core_config),
    .rfnoc_core_status    (rfnoc_core_status),
    .data_i_flush_en      (data_i_flush_en),
    .data_i_flush_timeout (data_i_flush_timeout),
    .data_i_flush_active  (data_i_flush_active),
    .data_i_flush_done    (data_i_flush_done),
    .data_o_flush_en      (data_o_flush_en),
    .data_o_flush_timeout (data_o_flush_timeout),
    .data_o_flush_active  (data_o_flush_active),
    .data_o_flush_done    (data_o_flush_done)
  );

  //---------------------------------------------------------------------------
  //  Reset Generation
  //---------------------------------------------------------------------------

  wire ce_rst_pulse;

  pulse_synchronizer #(.MODE ("POSEDGE")) pulse_synchronizer_ce (
    .clk_a(rfnoc_chdr_clk), .rst_a(1'b0), .pulse_a (rfnoc_chdr_rst), .busy_a (),
    .clk_b(ce_clk), .pulse_b (ce_rst_pulse)
  );

  pulse_stretch_min #(.LENGTH(32)) pulse_stretch_min_ce (
    .clk(ce_clk), .rst(1'b0),
    .pulse_in(ce_rst_pulse), .pulse_out(ce_rst)
  );

  //---------------------------------------------------------------------------
  //  Control Path
  //---------------------------------------------------------------------------

  assign ctrlport_clk = ce_clk;
  assign ctrlport_rst = ce_rst;

  ctrlport_endpoint #(
    .THIS_PORTID      (THIS_PORTID),
    .SYNC_CLKS        (0),
    .AXIS_CTRL_MST_EN (0),
    .AXIS_CTRL_SLV_EN (1),
    .SLAVE_FIFO_SIZE  ($clog2(32))
  ) ctrlport_endpoint_i (
    .rfnoc_ctrl_clk            (rfnoc_ctrl_clk),
    .rfnoc_ctrl_rst            (rfnoc_ctrl_rst),
    .ctrlport_clk              (ctrlport_clk),
    .ctrlport_rst              (ctrlport_rst),
    .s_rfnoc_ctrl_tdata        (s_rfnoc_ctrl_tdata),
    .s_rfnoc_ctrl_tlast        (s_rfnoc_ctrl_tlast),
    .s_rfnoc_ctrl_tvalid       (s_rfnoc_ctrl_tvalid),
    .s_rfnoc_ctrl_tready       (s_rfnoc_ctrl_tready),
    .m_rfnoc_ctrl_tdata        (m_rfnoc_ctrl_tdata),
    .m_rfnoc_ctrl_tlast        (m_rfnoc_ctrl_tlast),
    .m_rfnoc_ctrl_tvalid       (m_rfnoc_ctrl_tvalid),
    .m_rfnoc_ctrl_tready       (m_rfnoc_ctrl_tready),
    .m_ctrlport_req_wr         (m_ctrlport_req_wr),
    .m_ctrlport_req_rd         (m_ctrlport_req_rd),
    .m_ctrlport_req_addr       (m_ctrlport_req_addr),
    .m_ctrlport_req_data       (m_ctrlport_req_data),
    .m_ctrlport_req_byte_en    (),
    .m_ctrlport_req_has_time   (),
    .m_ctrlport_req_time       (),
    .m_ctrlport_resp_ack       (m_ctrlport_resp_ack),
    .m_ctrlport_resp_status    (2'b0),
    .m_ctrlport_resp_data      (m_ctrlport_resp_data),
    .s_ctrlport_req_wr         (1'b0),
    .s_ctrlport_req_rd         (1'b0),
    .s_ctrlport_req_addr       (20'b0),
    .s_ctrlport_req_portid     (10'b0),
    .s_ctrlport_req_rem_epid   (16'b0),
    .s_ctrlport_req_rem_portid (10'b0),
    .s_ctrlport_req_data       (32'b0),
    .s_ctrlport_req_byte_en    (4'hF),
    .s_ctrlport_req_has_time   (1'b0),
    .s_ctrlport_req_time       (64'b0),
    .s_ctrlport_resp_ack       (),
    .s_ctrlport_resp_status    (),
    .s_ctrlport_resp_data      ()
  );

  //---------------------------------------------------------------------------
  //  Data Path
  //---------------------------------------------------------------------------

  genvar i;

  assign axis_data_clk = ce_clk;
  assign axis_data_rst = ce_rst;

  //---------------------
  // Input Data Paths
  //---------------------

  chdr_to_axis_pyld_ctxt #(
    .CHDR_W              (CHDR_W),
    .ITEM_W              (32),
    .NIPC                (1),
    .SYNC_CLKS           (0),
    .CONTEXT_FIFO_SIZE   ($clog2(2)),
    .PAYLOAD_FIFO_SIZE   ($clog2(2)),
    .CONTEXT_PREFETCH_EN (1)
  ) chdr_to_axis_pyld_ctxt_in_in (
    .axis_chdr_clk         (rfnoc_chdr_clk),
    .axis_chdr_rst         (rfnoc_chdr_rst),
    .axis_data_clk         (axis_data_clk),
    .axis_data_rst         (axis_data_rst),
    .s_axis_chdr_tdata     (s_rfnoc_chdr_tdata[(0)*CHDR_W+:CHDR_W]),
    .s_axis_chdr_tlast     (s_rfnoc_chdr_tlast[0]),
    .s_axis_chdr_tvalid    (s_rfnoc_chdr_tvalid[0]),
    .s_axis_chdr_tready    (s_rfnoc_chdr_tready[0]),
    .m_axis_payload_tdata  (m_in_payload_tdata),
    .m_axis_payload_tkeep  (m_in_payload_tkeep),
    .m_axis_payload_tlast  (m_in_payload_tlast),
    .m_axis_payload_tvalid (m_in_payload_tvalid),
    .m_axis_payload_tready (m_in_payload_tready),
    .m_axis_context_tdata  (m_in_context_tdata),
    .m_axis_context_tuser  (m_in_context_tuser),
    .m_axis_context_tlast  (m_in_context_tlast),
    .m_axis_context_tvalid (m_in_context_tvalid),
    .m_axis_context_tready (m_in_context_tready),
    .flush_en              (data_i_flush_en),
    .flush_timeout         (data_i_flush_timeout),
    .flush_active          (data_i_flush_active[0]),
    .flush_done            (data_i_flush_done[0])
  );

  //---------------------
  // Output Data Paths
  //---------------------

  axis_pyld_ctxt_to_chdr #(
    .CHDR_W              (CHDR_W),
    .ITEM_W              (32),
    .NIPC                (1),
    .SYNC_CLKS           (0),
    .CONTEXT_FIFO_SIZE   ($clog2(2)),
    .PAYLOAD_FIFO_SIZE   ($clog2(2)),
    .MTU                 (MTU),
    .CONTEXT_PREFETCH_EN (1)
  ) axis_pyld_ctxt_to_chdr_out_out (
    .axis_chdr_clk         (rfnoc_chdr_clk),
    .axis_chdr_rst         (rfnoc_chdr_rst),
    .axis_data_clk         (axis_data_clk),
    .axis_data_rst         (axis_data_rst),
    .m_axis_chdr_tdata     (m_rfnoc_chdr_tdata[(0)*CHDR_W+:CHDR_W]),
    .m_axis_chdr_tlast     (m_rfnoc_chdr_tlast[0]),
    .m_axis_chdr_tvalid    (m_rfnoc_chdr_tvalid[0]),
    .m_axis_chdr_tready    (m_rfnoc_chdr_tready[0]),
    .s_axis_payload_tdata  (s_out_payload_tdata),
    .s_axis_payload_tkeep  (s_out_payload_tkeep),
    .s_axis_payload_tlast  (s_out_payload_tlast),
    .s_axis_payload_tvalid (s_out_payload_tvalid),
    .s_axis_payload_tready (s_out_payload_tready),
    .s_axis_context_tdata  (s_out_context_tdata),
    .s_axis_context_tuser  (s_out_context_tuser),
    .s_axis_context_tlast  (s_out_context_tlast),
    .s_axis_context_tvalid (s_out_context_tvalid),
    .s_axis_context_tready (s_out_context_tready),
    .framer_errors         (),
    .flush_en              (data_o_flush_en),
    .flush_timeout         (data_o_flush_timeout),
    .flush_active          (data_o_flush_active[0]),
    .flush_done            (data_o_flush_done[0])
  );

endmodule // noc_shell_gain

`default_nettype wire