gain block example not working on X410
mbr0wn opened this issue · 0 comments
mbr0wn commented
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