diff options
author | Wade Fife <wade.fife@ettus.com> | 2021-02-16 19:38:48 -0600 |
---|---|---|
committer | Wade Fife <wade.fife@ettus.com> | 2021-11-04 11:04:54 -0500 |
commit | a94ea11f00bba2c4227c9ab173eb4b6ef1049bad (patch) | |
tree | b0fcc3dcc26c42a2427b22a74cd6c0b903acb1b9 /fpga/usrp3/lib | |
parent | f6ec1496486f14ce16042062bdddcec38fa316a5 (diff) | |
download | uhd-a94ea11f00bba2c4227c9ab173eb4b6ef1049bad.tar.gz uhd-a94ea11f00bba2c4227c9ab173eb4b6ef1049bad.tar.bz2 uhd-a94ea11f00bba2c4227c9ab173eb4b6ef1049bad.zip |
fpga: rfnoc: Add RFNoC CHDR resize module
Diffstat (limited to 'fpga/usrp3/lib')
-rw-r--r-- | fpga/usrp3/lib/rfnoc/sim/chdr_resize_tb/Makefile | 39 | ||||
-rw-r--r-- | fpga/usrp3/lib/rfnoc/sim/chdr_resize_tb/chdr_resize_all_tb.sv | 46 | ||||
-rw-r--r-- | fpga/usrp3/lib/rfnoc/sim/chdr_resize_tb/chdr_resize_tb.sv | 666 | ||||
-rw-r--r-- | fpga/usrp3/lib/rfnoc/utils/Makefile.srcs | 3 | ||||
-rw-r--r-- | fpga/usrp3/lib/rfnoc/utils/chdr_convert_down.v | 451 | ||||
-rw-r--r-- | fpga/usrp3/lib/rfnoc/utils/chdr_convert_up.v | 448 | ||||
-rw-r--r-- | fpga/usrp3/lib/rfnoc/utils/chdr_resize.v | 378 |
7 files changed, 2031 insertions, 0 deletions
diff --git a/fpga/usrp3/lib/rfnoc/sim/chdr_resize_tb/Makefile b/fpga/usrp3/lib/rfnoc/sim/chdr_resize_tb/Makefile new file mode 100644 index 000000000..813ae2b13 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/sim/chdr_resize_tb/Makefile @@ -0,0 +1,39 @@ +# +# Copyright 2021 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# + +#------------------------------------------------- +# Top-of-Makefile +#------------------------------------------------- +# Define BASE_DIR to point to the "top" dir. +BASE_DIR = $(abspath ../../../../top) +# Include viv_sim_preample after defining BASE_DIR +include $(BASE_DIR)/../tools/make/viv_sim_preamble.mak + +#------------------------------------------------- +# Design Specific +#------------------------------------------------- + +DESIGN_SRCS += $(abspath \ +$(abspath ../../utils/chdr_convert_up.v) \ +$(abspath ../../utils/chdr_convert_down.v) \ +$(abspath ../../utils/chdr_resize.v) \ +) + +#------------------------------------------------- +# Testbench Specific +#------------------------------------------------- +SIM_TOP = chdr_resize_all_tb +SIM_SRCS = \ +$(abspath chdr_resize_tb.sv) \ +$(abspath chdr_resize_all_tb.sv) \ + +#------------------------------------------------- +# Bottom-of-Makefile +#------------------------------------------------- +# Include all simulator specific makefiles here +# Each should define a unique target to simulate +# e.g. xsim, vsim, etc and a common "clean" target +include $(BASE_DIR)/../tools/make/viv_simulator.mak diff --git a/fpga/usrp3/lib/rfnoc/sim/chdr_resize_tb/chdr_resize_all_tb.sv b/fpga/usrp3/lib/rfnoc/sim/chdr_resize_tb/chdr_resize_all_tb.sv new file mode 100644 index 000000000..997a6fa09 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/sim/chdr_resize_tb/chdr_resize_all_tb.sv @@ -0,0 +1,46 @@ +// +// Copyright 2021 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: chdr_resize_all_tb +// +// Description: +// +// Top-level testbench for chdr_resize, testing different configurations of +// the module. +// + +module chdr_resize_all_tb; + // Up-size CHDR bus + chdr_resize_tb #(.I_CHDR_W( 64), .O_CHDR_W(128), .PIPELINE("NONE" )) chdr_resize_tb_00(); + chdr_resize_tb #(.I_CHDR_W( 64), .O_CHDR_W(256), .PIPELINE("IN" )) chdr_resize_tb_01(); + chdr_resize_tb #(.I_CHDR_W(128), .O_CHDR_W(256), .PIPELINE("OUT" )) chdr_resize_tb_02(); + chdr_resize_tb #(.I_CHDR_W(128), .O_CHDR_W(512), .PIPELINE("INOUT")) chdr_resize_tb_03(); + chdr_resize_tb #(.I_CHDR_W(256), .O_CHDR_W(512), .PIPELINE("NONE" )) chdr_resize_tb_04(); + // Down-size CHDR bus + chdr_resize_tb #(.I_CHDR_W(128), .O_CHDR_W( 64), .PIPELINE("NONE" )) chdr_resize_tb_10(); + chdr_resize_tb #(.I_CHDR_W(256), .O_CHDR_W( 64), .PIPELINE("IN" )) chdr_resize_tb_11(); + chdr_resize_tb #(.I_CHDR_W(256), .O_CHDR_W(128), .PIPELINE("OUT" )) chdr_resize_tb_12(); + chdr_resize_tb #(.I_CHDR_W(512), .O_CHDR_W(128), .PIPELINE("INOUT")) chdr_resize_tb_13(); + chdr_resize_tb #(.I_CHDR_W(512), .O_CHDR_W(256), .PIPELINE("NONE" )) chdr_resize_tb_14(); + + // Up-size CHDR encoding (keep bus width) + chdr_resize_tb #(.I_CHDR_W( 64), .O_CHDR_W(128), .I_DATA_W( 64), .O_DATA_W( 64), .PIPELINE("NONE" )) chdr_resize_tb_20(); + chdr_resize_tb #(.I_CHDR_W( 64), .O_CHDR_W(512), .I_DATA_W( 64), .O_DATA_W( 64), .PIPELINE("IN" )) chdr_resize_tb_21(); + chdr_resize_tb #(.I_CHDR_W(128), .O_CHDR_W(256), .I_DATA_W(128), .O_DATA_W(128), .PIPELINE("OUT" )) chdr_resize_tb_22(); + chdr_resize_tb #(.I_CHDR_W(128), .O_CHDR_W(512), .I_DATA_W(128), .O_DATA_W(128), .PIPELINE("INOUT")) chdr_resize_tb_23(); + chdr_resize_tb #(.I_CHDR_W(256), .O_CHDR_W(512), .I_DATA_W(256), .O_DATA_W(256), .PIPELINE("NONE" )) chdr_resize_tb_24(); + // Down-size CHDR encoding (keep bus width) + chdr_resize_tb #(.I_CHDR_W(128), .O_CHDR_W( 64), .I_DATA_W( 64), .O_DATA_W( 64), .PIPELINE("NONE" )) chdr_resize_tb_30(); + chdr_resize_tb #(.I_CHDR_W(512), .O_CHDR_W( 64), .I_DATA_W( 64), .O_DATA_W( 64), .PIPELINE("IN" )) chdr_resize_tb_31(); + chdr_resize_tb #(.I_CHDR_W(256), .O_CHDR_W(128), .I_DATA_W(128), .O_DATA_W(128), .PIPELINE("OUT" )) chdr_resize_tb_32(); + chdr_resize_tb #(.I_CHDR_W(512), .O_CHDR_W(128), .I_DATA_W(128), .O_DATA_W(128), .PIPELINE("INOUT")) chdr_resize_tb_33(); + chdr_resize_tb #(.I_CHDR_W(512), .O_CHDR_W(256), .I_DATA_W(256), .O_DATA_W(256), .PIPELINE("NONE" )) chdr_resize_tb_34(); + + // No resize (pass through) + chdr_resize_tb #(.I_CHDR_W(512), .O_CHDR_W(512), .PIPELINE("NONE" )) chdr_resize_tb_40(); + chdr_resize_tb #(.I_CHDR_W(256), .O_CHDR_W(256), .PIPELINE("IN" )) chdr_resize_tb_41(); + chdr_resize_tb #(.I_CHDR_W(128), .O_CHDR_W(128), .PIPELINE("OUT" )) chdr_resize_tb_42(); + chdr_resize_tb #(.I_CHDR_W( 64), .O_CHDR_W( 64), .PIPELINE("INOUT")) chdr_resize_tb_43(); +endmodule : chdr_resize_all_tb diff --git a/fpga/usrp3/lib/rfnoc/sim/chdr_resize_tb/chdr_resize_tb.sv b/fpga/usrp3/lib/rfnoc/sim/chdr_resize_tb/chdr_resize_tb.sv new file mode 100644 index 000000000..63b314abd --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/sim/chdr_resize_tb/chdr_resize_tb.sv @@ -0,0 +1,666 @@ +// +// Copyright 2021 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: chdr_resize_tb +// +// Description: +// +// Testbench for chdr_resize. +// + +`default_nettype none + + +module chdr_resize_tb #( + parameter I_CHDR_W = 64, + parameter O_CHDR_W = 128, + parameter I_DATA_W = I_CHDR_W, + parameter O_DATA_W = O_CHDR_W, + parameter USER_W = 4, + parameter PIPELINE = "NONE" +); + + // Include macros and time declarations for use with PkgTestExec + `include "test_exec.svh" + + `define MIN(A, B) ((A)<(B)?(A):(B)) + `define DIV_CEIL(N, D) (((N)+(D)-1)/(D)) // ceiling(N/D) + + import PkgTestExec::*; + import PkgChdrUtils::*; + import PkgChdrBfm::*; + import PkgRandom::*; + + // Clock periods + localparam real CLK_PERIOD = 10.0; + + // Widths for BFMs to use + localparam ITEM_W = 8; + + // Test parameters + localparam NUM_PACKETS = 1000; // Number of packets to test + localparam MAX_MDATA_WORDS = 31; // Maximum number of metadata words (31 + // is the max supported by CHDR). + localparam MAX_PYLD_WORDS = 64; // Maximum number of payload words. + localparam USE_RANDOM = 1; // Use random vs. sequential data. + localparam DEBUG = 0; // Display extra debug info + + + //--------------------------------------------------------------------------- + // Clocks and Resets + //--------------------------------------------------------------------------- + + bit clk; + bit rst; + + sim_clock_gen #(.PERIOD(CLK_PERIOD), .AUTOSTART(0)) + clk_gen (.clk(clk), .rst(rst)); + + + //--------------------------------------------------------------------------- + // Bus Functional Models + //--------------------------------------------------------------------------- + + // Connections to DUT as interfaces: + AxiStreamIf #(I_CHDR_W) m_chdr (clk, 1'b0); + AxiStreamIf #(O_CHDR_W) s_chdr (clk, 1'b0); + + // CHDR BFMs. Because the input and output have different CHDR widths, we + // need two BFMs, one for each CHDR width. + ChdrBfm #(I_CHDR_W) m_bfm = new(m_chdr, null); + ChdrBfm #(O_CHDR_W) s_bfm = new(null, s_chdr); + + // CHDR data types + typedef ChdrPacket #(I_CHDR_W)::ChdrPacket_t IChdrPacket_t; + typedef ChdrPacket #(O_CHDR_W)::ChdrPacket_t OChdrPacket_t; + typedef ChdrData #(I_CHDR_W, ITEM_W)::chdr_word_t i_chdr_word_t; + typedef ChdrData #(O_CHDR_W, ITEM_W)::chdr_word_t o_chdr_word_t; + + + //--------------------------------------------------------------------------- + // Device Under Test (DUT) + //--------------------------------------------------------------------------- + // + // To simplify the testbench we randomly generate CHDR packets, resize them, + // then change their size back. This tests up-sizing and down-sizing + // simultaneously and simplifies output checking. It's possible, however, + // that we make a mistake up-sizing, then undo that mistake when downsizing, + // so there is some risk to this simplification. + // + //--------------------------------------------------------------------------- + + wire [I_DATA_W-1:0] i_data_tdata; + wire [ USER_W-1:0] i_data_tuser; + wire i_data_tlast; + wire i_data_tvalid; + wire i_data_tready; + + wire [O_DATA_W-1:0] o_data_tdata; + wire [ USER_W-1:0] o_data_tuser; + wire o_data_tlast; + wire o_data_tvalid; + wire o_data_tready; + + chdr_resize #( + .I_CHDR_W (I_CHDR_W), + .O_CHDR_W (O_CHDR_W), + .I_DATA_W (I_DATA_W), + .O_DATA_W (O_DATA_W), + .USER_W (USER_W), + .PIPELINE (PIPELINE) + ) chdr_resize_i ( + .clk (clk), + .rst (rst), + .i_chdr_tdata (i_data_tdata), + .i_chdr_tuser (i_data_tuser), + .i_chdr_tlast (i_data_tlast), + .i_chdr_tvalid (i_data_tvalid), + .i_chdr_tready (i_data_tready), + .o_chdr_tdata (o_data_tdata), + .o_chdr_tuser (o_data_tuser), + .o_chdr_tlast (o_data_tlast), + .o_chdr_tvalid (o_data_tvalid), + .o_chdr_tready (o_data_tready) + ); + + + //--------------------------------------------------------------------------- + // Bus Resizer for DUT + //--------------------------------------------------------------------------- + // + // Resize the DUT inputs/outputs to match match the widths used by our BFMs. + // + //--------------------------------------------------------------------------- + + localparam IN_WORD_W = `MIN(I_CHDR_W, I_DATA_W); + localparam OUT_WORD_W = `MIN(O_CHDR_W, O_DATA_W); + localparam OUT_KEEP_W = O_CHDR_W/OUT_WORD_W; + + wire [I_CHDR_W-1:0] i_chdr_tdata; + wire i_chdr_tlast; + wire i_chdr_tvalid; + wire i_chdr_tready; + + wire [ O_CHDR_W-1:0] o_chdr_tdata_unmasked; + wire [ O_CHDR_W-1:0] o_chdr_tdata; + wire [OUT_KEEP_W-1:0] o_chdr_tkeep; + wire o_chdr_tlast; + wire o_chdr_tvalid; + wire o_chdr_tready; + + axis_width_conv #( + .WORD_W (IN_WORD_W), + .IN_WORDS (I_CHDR_W/IN_WORD_W), + .OUT_WORDS (I_DATA_W/IN_WORD_W), + .SYNC_CLKS (1), + .PIPELINE ("NONE") + ) axis_width_conv_in ( + .s_axis_aclk (clk), + .s_axis_rst (rst), + .s_axis_tdata (i_chdr_tdata), + .s_axis_tkeep ('1), + .s_axis_tlast (i_chdr_tlast), + .s_axis_tvalid (i_chdr_tvalid), + .s_axis_tready (i_chdr_tready), + .m_axis_aclk (clk), + .m_axis_rst (rst), + .m_axis_tdata (i_data_tdata), + .m_axis_tkeep (), + .m_axis_tlast (i_data_tlast), + .m_axis_tvalid (i_data_tvalid), + .m_axis_tready (i_data_tready) + ); + + axis_width_conv #( + .WORD_W (OUT_WORD_W), + .IN_WORDS (O_DATA_W/OUT_WORD_W), + .OUT_WORDS (O_CHDR_W/OUT_WORD_W), + .SYNC_CLKS (1), + .PIPELINE ("NONE") + ) axis_width_conv_out ( + .s_axis_aclk (clk), + .s_axis_rst (rst), + .s_axis_tdata (o_data_tdata), + .s_axis_tkeep ('1), + .s_axis_tlast (o_data_tlast), + .s_axis_tvalid (o_data_tvalid), + .s_axis_tready (o_data_tready), + .m_axis_aclk (clk), + .m_axis_rst (rst), + .m_axis_tdata (o_chdr_tdata_unmasked), + .m_axis_tkeep (o_chdr_tkeep), + .m_axis_tlast (o_chdr_tlast), + .m_axis_tvalid (o_chdr_tvalid), + .m_axis_tready (o_chdr_tready) + ); + + + // Invalidate the bits we shouldn't be keeping by changing them to X. This + // ensures we aren't checking bits that aren't there and thinking they're OK + // because they happen to be 0. + reg [O_CHDR_W-1:0] o_chdr_keep_mask; + + always_comb begin + for (int w = 0; w < OUT_KEEP_W; w++) begin + if (o_chdr_tkeep[w] == 1'b1) begin + o_chdr_keep_mask[w*OUT_WORD_W+:OUT_WORD_W] = {OUT_WORD_W{1'b1}}; + end else begin + o_chdr_keep_mask[w*OUT_WORD_W+:OUT_WORD_W] = {OUT_WORD_W{1'bX}}; + end + end + end + + // This mask operation leaves the bits we're keeping unmodified but changes + // the ones we aren't keeping to X. + assign o_chdr_tdata = o_chdr_tdata_unmasked & o_chdr_keep_mask; + + + //--------------------------------------------------------------------------- + // BFM Connections + //--------------------------------------------------------------------------- + + // Input + assign i_chdr_tdata = m_chdr.tdata; + assign i_chdr_tlast = m_chdr.tlast; + assign i_chdr_tvalid = m_chdr.tvalid; + assign m_chdr.tready = i_chdr_tready; + + // Output + assign s_chdr.tdata = o_chdr_tdata; + assign s_chdr.tlast = o_chdr_tlast; + assign s_chdr.tvalid = o_chdr_tvalid; + assign o_chdr_tready = s_chdr.tready; + + + //--------------------------------------------------------------------------- + // Debug Monitors + //--------------------------------------------------------------------------- + // + // Display packet info as packets go in/out of the DUT to make packets easier + // to find in the simulator. + // + //--------------------------------------------------------------------------- + + if (DEBUG) begin + bit i_chdr_sop = 1; + bit mid_chdr_sop = 1; + bit o_chdr_sop = 1; + + always @(posedge clk) begin + chdr_header_t header; + + if (i_chdr_tvalid && i_chdr_tready) begin + if (i_chdr_sop) begin + header = i_chdr_tdata; + $display("In packet, @%0t: 0x%16X %p", $realtime, header, header); + i_chdr_sop <= 0; + end + i_chdr_sop <= i_chdr_tlast; + end + + if (o_chdr_tvalid && o_chdr_tready) begin + if (o_chdr_sop) begin + header = o_chdr_tdata; + $display("Out packet, @%0t: 0x%16X %p", $realtime, header, header); + o_chdr_sop <= 0; + end + o_chdr_sop <= o_chdr_tlast; + end + end + end + + + //--------------------------------------------------------------------------- + // TUSER Generation + //--------------------------------------------------------------------------- + // + // Create a simple counter on TUSER than increments for each packet. + // + //--------------------------------------------------------------------------- + + reg [USER_W-1:0] i_user_count = 0; + + always @(posedge clk) begin + if (rst) begin + i_user_count <= 0; + end else if (i_data_tvalid && i_data_tready) begin + if (i_data_tlast) begin + i_user_count <= i_user_count + 1; + end + end + end + + // Ensure that i_data_tuser is only valid during the packet to guarantee the + // DUT doesn't sample it outside that. + assign i_data_tuser = + (i_data_tvalid && i_data_tready) ? i_user_count : 'X; + + + //--------------------------------------------------------------------------- + // TUSER Checking + //--------------------------------------------------------------------------- + // + // Verify that each output packet has the expected count on TUSER. + // + //--------------------------------------------------------------------------- + + reg [USER_W-1:0] o_user_count = 0; + + always @(posedge clk) begin + if (rst) begin + o_user_count <= 0; + end else if (o_data_tvalid && o_data_tready) begin + `ASSERT_ERROR( + o_data_tuser == o_user_count, + "TUSER output doesn't match expected count." + ); + if (o_data_tlast) begin + o_user_count <= o_user_count+1; + end + end + end + + + //--------------------------------------------------------------------------- + // Helper Logic + //--------------------------------------------------------------------------- + + // Convert the input packet to the output packet, based on the configured + // I_CHDR_W and O_CHDR_W. Returns the expected output packet. This function + // does to the input ChdrPacket what the DUT is supposed to do to the CHDR + // AXI-Stream packet. + function automatic OChdrPacket_t convert_packet(IChdrPacket_t i_packet); + OChdrPacket_t o_packet = new; + int num_mdata; + int max_num_mdata = 2**$bits(o_packet.header.num_mdata)-1; + + // Check if we're doing any conversion or resizing + if (I_CHDR_W == O_CHDR_W && I_CHDR_W == I_DATA_W && O_CHDR_W == O_DATA_W) begin + // We don't modify the packet at all in this case. We should just pass it + // through. + o_packet = OChdrPacket_t'(i_packet.copy()); + return o_packet; + end + + //--------------------------------- + // Update the header + + o_packet.header = i_packet.header; + + // NumMData + if (I_CHDR_W > O_CHDR_W) begin + // Make sure there isn't too much metadata for a smaller CHDR_W packet + num_mdata = i_packet.header.num_mdata * I_CHDR_W / O_CHDR_W; + if (num_mdata > max_num_mdata) num_mdata = max_num_mdata; + end else begin + // Round up to the nearest whole O_CHDR_W word + num_mdata = `DIV_CEIL(i_packet.header.num_mdata * I_CHDR_W, O_CHDR_W); + end + o_packet.header.num_mdata = num_mdata; + + // Length + o_packet.header.length = + // Header + (O_CHDR_W/8) + + // Timestamp (goes in same word as header, unless O_CHDR_W is 64-bit) + ((o_packet.header.pkt_type == CHDR_DATA_WITH_TS && O_CHDR_W == 64) ? (O_CHDR_W/8) : 0) + + // Metadata + (O_CHDR_W/8)*o_packet.header.num_mdata + + // Payload data. We expect the length of the output packet to be a + // multiple of CHDR_W for management packets. + ((o_packet.header.pkt_type == CHDR_MANAGEMENT) ? + `DIV_CEIL(i_packet.data_bytes(), I_CHDR_W/8) * (O_CHDR_W/8) : i_packet.data_bytes()); + + // Timestamp + o_packet.timestamp = i_packet.timestamp; + + //--------------------------------- + // Copy the data and metadata + + if (I_CHDR_W > O_CHDR_W) begin + // Drop any metadata beyond what CHDR allows + o_packet.metadata = ChdrData#(I_CHDR_W, O_CHDR_W)::chdr_to_item(i_packet.metadata, + num_mdata * O_CHDR_W/8); + // Drop any O_CHDR_W words that aren't part of the payload + o_packet.data = ChdrData#(I_CHDR_W, O_CHDR_W)::chdr_to_item(i_packet.data, + `DIV_CEIL(i_packet.data_bytes(), O_CHDR_W/8) * O_CHDR_W/8); + end else begin + o_chdr_word_t last_mdata; + o_packet.metadata = ChdrData#(O_CHDR_W, I_CHDR_W)::item_to_chdr(i_packet.metadata); + o_packet.data = ChdrData#(O_CHDR_W, I_CHDR_W)::item_to_chdr(i_packet.data); + + // When I_CHDR_W < O_CHDR_W, the number of metadata bytes might not be a + // multiple of O_CHDR_W. The DUT should zero these extra bytes. + if (o_packet.metadata.size() > 0) begin + last_mdata = o_packet.metadata[$]; + for (int i = 0; i < o_packet.mdata_bytes()-i_packet.mdata_bytes(); i++) begin + last_mdata[$bits(o_chdr_word_t)-i*8-1 -: 8] = 8'd0; + end + o_packet.metadata[$] = last_mdata; + end + end + + //--------------------------------- + // Copy management packet data + + if (o_packet.header.pkt_type == CHDR_MANAGEMENT) begin + // Management packets don't get serialized, so we just copy each word, + // ignoring the upper bits. + o_packet.data = {}; + for (int i = 0; i < i_packet.data.size(); i++) begin + if (i == 0) begin + // Update the CHDR width in the management header word + o_chdr_word_t word; + word = i_packet.data[i]; + word[47:45] = translate_chdr_w(O_CHDR_W); + o_packet.data.push_back(word); + end else begin + o_packet.data.push_back(i_packet.data[i]); + end + end + end + + return o_packet; + endfunction : convert_packet + + + // Compare the output packet to what we expect the output packet to be. + function automatic string compare_packets( + input OChdrPacket_t packet, + input OChdrPacket_t exp_packet + ); + string msg = ""; + + // Check that the headers match (including num_mdata and length) + if(packet.header != exp_packet.header) begin + msg = { msg, "Headers do not match\n" }; + end + + // Check that the timestamps match + if (exp_packet.header.pkt_type == CHDR_DATA_WITH_TS) begin + if(packet.timestamp != exp_packet.timestamp) begin + msg = { msg, "Timestamps do not match\n" }; + end + end + + // Check that the metadata matches + if(!exp_packet.chdr_word_queues_equal( + packet.metadata, exp_packet.metadata, packet.mdata_bytes()) + ) begin + msg = { msg, "Metadata does not match\n" }; + end + + // Check that the payloads match + if(!exp_packet.chdr_word_queues_equal( + packet.data, exp_packet.data, exp_packet.data_bytes()) + ) begin + msg = { msg, "Payloads do not match\n" }; + end + + return msg; + endfunction : compare_packets + + + //--------------------------------------------------------------------------- + // Tests + //--------------------------------------------------------------------------- + + // Perform a randomized test with the given stall probabilities in the input + // and output ports. + task automatic test_random(int in_stall_prob, int out_stall_prob); + longint word_count = 0; + bit enable_input = 0; + string msg; + mailbox #(IChdrPacket_t) packets = new; + + msg = $sformatf("Test Random Packets (%0d%%, %0d%%)", + in_stall_prob, out_stall_prob); + test.start_test(msg, 10ms); + + m_bfm.set_master_stall_prob(in_stall_prob); + s_bfm.set_slave_stall_prob(out_stall_prob); + + fork + //------------------------------- + // Input Process + //------------------------------- + + begin : input_process + IChdrPacket_t chdr_packet = new; + i_chdr_word_t data[$]; + i_chdr_word_t mdata[$]; + chdr_header_t header; + chdr_timestamp_t timestamp; + int data_byte_length; + int count; + + repeat (NUM_PACKETS) begin + //------------------------------- + // Generate a random packet + //------------------------------- + + // Start with a random header. Make sure the packet type is legal. + do begin + header = Rand#($bits(chdr_header_t))::rand_logic(); + end while (header.pkt_type == CHDR_RESERVED_0 || header.pkt_type == CHDR_RESERVED_1); + + // Generate timestamp + if (header.pkt_type == CHDR_DATA_WITH_TS) begin + if (USE_RANDOM) timestamp = Rand#($bits(chdr_header_t))::rand_logic(); + else timestamp = 64'h0123456789ABCDEF; + end else begin + timestamp = 0; + end + + // Generate random metadata (50% chance of no metadata) + mdata = {}; + if ($urandom_range(0, 1)) begin + count = 0; + repeat ($urandom_range(1, MAX_MDATA_WORDS)) begin + if (USE_RANDOM) mdata.push_back(Rand#(I_CHDR_W)::rand_logic()); + else mdata.push_back(64'hA1000000 + count++); + end + end + + // Generate random data (always at least one word) + data = {}; + count = 0; + repeat ($urandom_range(1, MAX_PYLD_WORDS)) begin + if (USE_RANDOM) data.push_back(Rand#(I_CHDR_W)::rand_logic()); + else data.push_back(64'hB2000000 + count++); + end + + // Calculate the size of data minus one word, in bytes + data_byte_length = (data.size()-1) * (I_CHDR_W/8); + // Add from 1 byte to a full word of bytes to test partially filling + // the last word. + if (header.pkt_type == CHDR_MANAGEMENT) begin + // For management packets, the spec is not explicit about whether + // the length must include the padding of the last word. We assume + // the worst case, that either case is possible and we expect the + // DUT to handle both correctly. + data_byte_length += $urandom_range(1, I_CHDR_W/64) * 8; + end else begin + data_byte_length += $urandom_range(1, I_CHDR_W/8); + end + + // Build packet + chdr_packet.write_raw( + header, + data, + mdata, + timestamp, + data_byte_length + ); + + // Queue the packet + m_bfm.put_chdr(chdr_packet); + + // Queue up what we sent for the output process to check + packets.put(chdr_packet.copy()); + end + $display("Done inputting packets."); + end : input_process + + //------------------------------- + // Output Process + //------------------------------- + + begin : output_process + IChdrPacket_t i_packet; + OChdrPacket_t o_packet; + OChdrPacket_t exp_packet; + int packet_count; + string msg; + + repeat (NUM_PACKETS) begin + s_bfm.get_chdr(o_packet); + packets.get(i_packet); + exp_packet = convert_packet(i_packet); + msg = compare_packets(o_packet, exp_packet); + if(msg != "") begin + $display("Sent packet:"); + i_packet.print(0); + $display("Received packet:"); + o_packet.print(0); + $display("Expected packet:"); + exp_packet.print(0); + `ASSERT_ERROR(0, $sformatf( + "Output packet is incorrect for the following reasons:\n%s", msg) + ); + end + end + $display("Done processing packets."); + end : output_process + join + + test.end_test(); + + endtask : test_random + + + //--------------------------------------------------------------------------- + // Main Test Process + //--------------------------------------------------------------------------- + + initial begin : tb_main + string msg; + string tb_name; + + tb_name = $sformatf( { + "chdr_resize_tb\n", + "I_CHDR_W = %03d\n", + "O_CHDR_W = %03d\n", + "I_DATA_W = %03d\n", + "O_DATA_W = %03d\n", + "PIPLINE = %s" }, + I_CHDR_W, O_CHDR_W, I_DATA_W, O_DATA_W, PIPELINE + ); + test.start_tb(tb_name, 100ms); + + // Don't start the clocks until after start_tb() returns. This ensures that + // the clocks aren't toggling while other instances of this testbench are + // running, which speeds up simulation time. + clk_gen.start(); + + // Start the BFM + m_bfm.run(); + s_bfm.run(); + + //-------------------------------- + // Reset + //-------------------------------- + + test.start_test("Reset", 10us); + clk_gen.reset(); + if (rst) @rst; + test.end_test(); + + //-------------------------------- + // Test Sequences + //-------------------------------- + + test_random(50, 50); // Test 50% push-back + test_random( 0, 0); // Test no push-back + test_random(50, 0); // Test for underflow + test_random( 0, 50); // Test for overflow + + //-------------------------------- + // Finish Up + //-------------------------------- + + // 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(0); + + // Kill the clocks to end this instance of the testbench + clk_gen.kill(); + + end : tb_main + +endmodule : chdr_resize_tb + + +`default_nettype wire diff --git a/fpga/usrp3/lib/rfnoc/utils/Makefile.srcs b/fpga/usrp3/lib/rfnoc/utils/Makefile.srcs index c8fb9648f..fe4135f17 100644 --- a/fpga/usrp3/lib/rfnoc/utils/Makefile.srcs +++ b/fpga/usrp3/lib/rfnoc/utils/Makefile.srcs @@ -10,6 +10,9 @@ RFNOC_UTIL_SRCS = $(abspath $(addprefix $(BASE_DIR)/../lib/rfnoc/utils/, \ chdr_trim_payload.v \ chdr_pad_packet.v \ +chdr_resize.v \ +chdr_convert_up.v \ +chdr_convert_down.v \ context_handler_sync.v \ context_builder.v \ context_parser.v \ diff --git a/fpga/usrp3/lib/rfnoc/utils/chdr_convert_down.v b/fpga/usrp3/lib/rfnoc/utils/chdr_convert_down.v new file mode 100644 index 000000000..e3322bdda --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/utils/chdr_convert_down.v @@ -0,0 +1,451 @@ +// +// Copyright 2021 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: chdr_convert_down +// +// Description: +// +// Takes a CHDR packet data stream that was generated using a CHDR width +// (I_CHDR_W) that is wider than the current bus width (DATA_W) and reformats +// the packet stream to use the CHDR_W equal to that of the current bus width +// (DATA_W). It does not resize the bus, but rather only changes the CHDR_W +// of the encoded packets. +// +// Packets with different CHDR width have a different maximum number of +// metadata bytes. This module repacks the the metadata into the new word +// size, little-endian ordered. If there is too much metadata for the smaller +// DATA_W packet, then the excess metadata will be discarded. +// +// Parameters: +// +// I_CHDR_W : CHDR_W for the input data stream on i_chdr. Must be larger than +// DATA_W. +// DATA_W : Width of the data bus, and the new CHDR_W for the output data +// stream on o_chdr. +// PIPELINE : Indicates whether to add pipeline stages to the input and/or +// output. This can be: "NONE", "IN", "OUT", or "INOUT". + +`default_nettype none + + +module chdr_convert_down #( + parameter I_CHDR_W = 512, + parameter DATA_W = 64, + parameter PIPELINE = "NONE" +) ( + input wire clk, + input wire rst, + + // Input + input wire [DATA_W-1:0] i_chdr_tdata, + input wire i_chdr_tlast, + input wire i_chdr_tvalid, + output wire i_chdr_tready, + + // Output + output wire [DATA_W-1:0] o_chdr_tdata, + output wire o_chdr_tlast, + output wire o_chdr_tvalid, + input wire o_chdr_tready +); + + `include "../core/rfnoc_chdr_utils.vh" + `include "../core/rfnoc_chdr_internal_utils.vh" + + // Calculate ceiling(N/D) + `define DIV_CEIL(N,D) (((N)+(D)-1)/(D)) + + + //--------------------------------------------------------------------------- + // Check Parameters + //--------------------------------------------------------------------------- + + generate + if (!( + // Must be reducing the CHDR width + (I_CHDR_W > DATA_W) && + // CHDR widths must be valid (at least 64 and powers of 2) + (I_CHDR_W >= 64) && + (DATA_W >= 64) && + (2**$clog2(I_CHDR_W) == I_CHDR_W) && + (2**$clog2(DATA_W) == DATA_W) && + // I_CHDR_W must be a multiple of DATA_W + (I_CHDR_W % DATA_W == 0) + )) begin : gen_error + ERROR__Invalid_CHDR_or_data_width_parameters(); + end + endgenerate + + + //--------------------------------------------------------------------------- + // Input Register + //--------------------------------------------------------------------------- + + wire [DATA_W-1:0] i_pipe_tdata; + wire i_pipe_tlast; + wire i_pipe_tvalid; + reg i_pipe_tready; + + if (PIPELINE == "IN" || PIPELINE == "INOUT") begin : gen_in_pipeline + // Add a pipeline stage + axi_fifo_flop2 #( + .WIDTH (1 + DATA_W) + ) axi_fifo_flop2_i ( + .clk (clk), + .reset (rst), + .clear (1'b0), + .i_tdata ({i_chdr_tlast, i_chdr_tdata}), + .i_tvalid (i_chdr_tvalid), + .i_tready (i_chdr_tready), + .o_tdata ({i_pipe_tlast, i_pipe_tdata}), + .o_tvalid (i_pipe_tvalid), + .o_tready (i_pipe_tready), + .space (), + .occupied () + ); + end else begin : gen_no_in_pipeline + assign i_pipe_tdata = i_chdr_tdata; + assign i_pipe_tlast = i_chdr_tlast; + assign i_pipe_tvalid = i_chdr_tvalid; + assign i_chdr_tready = i_pipe_tready; + end + + + //--------------------------------------------------------------------------- + // Downsize State Machine + //--------------------------------------------------------------------------- + // + // This state machine does the translation from the larger CHDR_W to the + // smaller CHDR_W by updating the header and dropping empty words. + // + //--------------------------------------------------------------------------- + + // States + localparam [2:0] ST_HDR = 3'd0; // CHDR header + localparam [2:0] ST_TS = 3'd1; // CHDR timestamp + localparam [2:0] ST_HDR_DROP = 3'd2; // CHDR header, drop unused words + localparam [2:0] ST_MDATA = 3'd3; // CHDR metadata words + localparam [2:0] ST_MDATA_DROP = 3'd4; // CHDR metadata, drop unused words + localparam [2:0] ST_PYLD = 3'd5; // CHDR payload words + localparam [2:0] ST_PYLD_DROP = 3'd6; // CHDR payload, drop unused words + localparam [2:0] ST_MGMT_PYLD = 3'd7; // CHDR management payload words + + reg [2:0] state = ST_HDR; + + // Determine the number of bits needed to represent the new number of + // metadata words, which might be bigger than the allowed value of 31. + localparam NUM_MDATA_W = $clog2(31*I_CHDR_W/DATA_W + 1); + + // Number of output words per input word + localparam NUM_WORDS = I_CHDR_W/DATA_W; + + // Determine the number of bits needed to represent a counter to track which + // CHDR words are valid and which are unused and need to be dropped. + localparam COUNT_W = $clog2(NUM_WORDS); + + // Determine the maximum number DATA_W-sized payload words. The maximum + // packet size is 2**16-1 bytes, then subtract one word for the smallest + // possible header and convert that to a number of whole CHDR words. + localparam NUM_PYLD_WORDS = `DIV_CEIL((2**16-1) - (DATA_W/8), DATA_W/8); + + // Determine the number of bits needed to represent a counter to track which + // O_DATA_W payload word we are processing. + localparam PYLD_COUNT_W = $clog2(NUM_PYLD_WORDS + 1); + + // Header info we need to save + reg [ NUM_MDATA_W-1:0] i_num_mdata_reg; // Input packet NumMData in terms of DATA_W words + reg [ 4:0] o_num_mdata_reg; // Output packet NumMData to keep + reg [ 2:0] pkt_type_reg; // Packet type + reg [PYLD_COUNT_W-1:0] pyld_len_reg; // Packet payload length in DATA_W words + reg [PYLD_COUNT_W-1:0] mgmt_pyld_len_reg; // Management payload length in DATA_W words + + // Counters (number of DATA_W sized words processed on the input) + reg [ NUM_MDATA_W-1:0] mdata_count; + reg [PYLD_COUNT_W-1:0] pyld_count; + reg [ COUNT_W-1:0] word_count; // Zero based (starts at 0) + + // Shortcuts for CHDR header info + wire [ 2:0] pkt_type = chdr_get_pkt_type(i_pipe_tdata[63:0]); + wire [15:0] pyld_len_bytes = chdr_calc_payload_length(I_CHDR_W, i_pipe_tdata[63:0]); + + // Calculate the payload length in DATA_W words + wire [PYLD_COUNT_W-1:0] pyld_len = `DIV_CEIL(pyld_len_bytes, DATA_W/8); + + // Calculate the payload length of a management packet in words (management + // packets have the same number of payload words, regardless of CHDR width). + wire [PYLD_COUNT_W-1:0] mgmt_pyld_len = + `DIV_CEIL(chdr_calc_payload_length(I_CHDR_W, i_pipe_tdata), I_CHDR_W/8); + + // Calculate NumMData from input packet in terms of DATA_W words + wire [NUM_MDATA_W-1:0] i_num_mdata = + chdr_get_num_mdata(i_pipe_tdata[63:0]) * (I_CHDR_W/DATA_W); + // Calculate NumMData for output packet (limit to max of 31) + wire [4:0] o_num_mdata = (i_num_mdata <= 31) ? i_num_mdata : 31; + + // Generate packet headers with updated NumMData and Length fields + reg [DATA_W-1:0] new_header; + always @(*) begin + new_header = i_pipe_tdata; + // Update NumMData + new_header[63:0] = chdr_set_num_mdata(new_header, o_num_mdata); + // Update packet length + new_header[63:0] = chdr_update_length(DATA_W, new_header, + (pkt_type == CHDR_PKT_TYPE_MGMT) ? mgmt_pyld_len * (DATA_W/8) : pyld_len_bytes); + end + + reg [DATA_W-1:0] new_mgmt_header; + always @(*) begin + // Update the CHDRWidth field in the management header. + new_mgmt_header = i_pipe_tdata; + new_mgmt_header[63:0] = + chdr_mgmt_set_chdr_w(i_pipe_tdata[63:0], chdr_w_to_enum(DATA_W)); + end + + + always @(posedge clk) begin + if (rst) begin + state <= ST_HDR; + mdata_count <= 'bX; + pyld_count <= 'bX; + word_count <= 'bX; + pkt_type_reg <= 'bX; + pyld_len_reg <= 'bX; + mgmt_pyld_len_reg <= 'bX; + i_num_mdata_reg <= 'bX; + o_num_mdata_reg <= 'bX; + end else if (i_pipe_tvalid & i_pipe_tready) begin + // Default assignment + word_count <= word_count + 1; + + case (state) + + // ST_HDR: CHDR Header + ST_HDR: begin + mdata_count <= 1; // The first metadata word will be word 1 + pyld_count <= 1; // The first payload word will be word 1 + word_count <= 1; // Word 0 is the current word (header) + pkt_type_reg <= pkt_type; + pyld_len_reg <= pyld_len; + mgmt_pyld_len_reg <= mgmt_pyld_len; + // Save number of DATA_W words of mdata we expect + i_num_mdata_reg <= i_num_mdata; + // Save the number of DATA_W words of mdata we can keep + o_num_mdata_reg <= o_num_mdata; + if (DATA_W == 64) begin + if (pkt_type == CHDR_PKT_TYPE_DATA_TS) begin + // Next word must be the timestamp + state <= ST_TS; + end else begin + // Next word(s) must be empty, so drop it + state <= ST_HDR_DROP; + end + end else begin + // DATA_W >= 128. We should have received the header word and + // timestamp (if present) this clock cycle. Since I_CHDR_W > + // DATA_W, there must be extra words with the header that we need + // to drop. + state <= ST_HDR_DROP; + end + end + + // ST_TS: Timestamp (DATA_W == 64 only) + ST_TS: begin + if (I_CHDR_W > 128) begin + state <= ST_HDR_DROP; + end else if (o_num_mdata_reg != 0) begin + state <= ST_MDATA; + end else begin + state <= ST_PYLD; + end + end + + // ST_HDR_DROP: CHDR header, drop unused words + ST_HDR_DROP: begin + if (word_count == NUM_WORDS-1) begin + if (o_num_mdata_reg != 0) begin + state <= ST_MDATA; + end else if(pkt_type_reg == CHDR_PKT_TYPE_MGMT) begin + state <= ST_MGMT_PYLD; + end else begin + state <= ST_PYLD; + end + end + end + + // ST_MDATA: Metadata words + ST_MDATA: begin + mdata_count <= mdata_count + 1; + if (mdata_count == o_num_mdata_reg) begin + if (mdata_count < i_num_mdata_reg) begin + // There are more MDATA words to deal with than we can fit, so we + // need to drop the rest. + state <= ST_MDATA_DROP; + end else if (pkt_type_reg == CHDR_PKT_TYPE_MGMT) begin + state <= ST_MGMT_PYLD; + end else begin + state <= ST_PYLD; + end + end + end + + // ST_MDATA_DROP: Drop excess metadata words + ST_MDATA_DROP: begin + mdata_count <= mdata_count + 1; + if (mdata_count == i_num_mdata_reg) begin + if (pkt_type_reg == CHDR_PKT_TYPE_MGMT) begin + state <= ST_MGMT_PYLD; + end else begin + state <= ST_PYLD; + end + end + end + + // ST_PYLD: Payload words + ST_PYLD: begin + pyld_count <= pyld_count + 1; + if (i_pipe_tlast) begin + state <= ST_HDR; + end else if (pyld_count == pyld_len_reg) begin + state <= ST_PYLD_DROP; + end + end + + // ST_PYLD_DROP: Payload, drop unused words + ST_PYLD_DROP: begin + // The input packet may have had empty words at the end if the + // payload didn't fill the last CHDR word. We remove those here. + if (i_pipe_tlast) begin + state <= ST_HDR; + end + end + + // ST_MGMT_PYLD: Management words + ST_MGMT_PYLD: begin + // Management packets are different from other packet types in that + // the payload is not serialized. In the new DATA_W, we'll have empty + // words we need to discard. When word_count is zero, that's when we + // have a valid word. For all other counts, we want to discard words. + if (word_count == 0) begin + pyld_count <= pyld_count + 1; + end + if (i_pipe_tlast) begin + state <= ST_HDR; + end + end + + endcase + end + end + + + //----------------------------- + // State machine output logic + //----------------------------- + + reg [DATA_W-1:0] o_pipe_tdata; + reg o_pipe_tlast; + reg o_pipe_tvalid; + wire o_pipe_tready; + + always @(*) begin + case (state) + ST_HDR : begin + o_pipe_tdata = new_header; + o_pipe_tlast = i_pipe_tlast; + o_pipe_tvalid = i_pipe_tvalid; + i_pipe_tready = o_pipe_tready; + end + ST_TS : begin + o_pipe_tdata = i_pipe_tdata; + o_pipe_tlast = i_pipe_tlast; + o_pipe_tvalid = i_pipe_tvalid; + i_pipe_tready = o_pipe_tready; + end + ST_HDR_DROP : begin + o_pipe_tdata = { DATA_W {1'bX} }; + o_pipe_tlast = 1'bX; + o_pipe_tvalid = 1'b0; + i_pipe_tready = 1'b1; + end + ST_MDATA : begin + o_pipe_tdata = i_pipe_tdata; + o_pipe_tlast = i_pipe_tlast; + o_pipe_tvalid = i_pipe_tvalid; + i_pipe_tready = o_pipe_tready; + end + ST_MDATA_DROP : begin + o_pipe_tdata = { DATA_W {1'bX} }; + o_pipe_tlast = 1'bX; + o_pipe_tvalid = 1'b0; + i_pipe_tready = 1'b1; + end + ST_PYLD : begin + o_pipe_tdata = i_pipe_tdata; + o_pipe_tlast = (pyld_count == pyld_len_reg); + o_pipe_tvalid = i_pipe_tvalid; + i_pipe_tready = o_pipe_tready; + end + ST_PYLD_DROP : begin + o_pipe_tdata = { DATA_W {1'bX} }; + o_pipe_tlast = 1'bX; + o_pipe_tvalid = 1'b0; + i_pipe_tready = 1'b1; + end + ST_MGMT_PYLD : begin + if (word_count == 0) begin + o_pipe_tdata = (pyld_count == 1) ? new_mgmt_header : i_pipe_tdata; + o_pipe_tlast = (pyld_count == mgmt_pyld_len_reg); + o_pipe_tvalid = i_pipe_tvalid; + i_pipe_tready = o_pipe_tready; + end else begin + // Drop unused management payload words + o_pipe_tdata = { DATA_W {1'bX} }; + o_pipe_tlast = 1'bX; + o_pipe_tvalid = 1'b0; + i_pipe_tready = 1'b1; + end + end + default : begin + o_pipe_tdata = { DATA_W {1'bX} }; + o_pipe_tlast = 1'bX; + o_pipe_tvalid = 1'bX; + i_pipe_tready = 1'bX; + end + endcase + end + + + //--------------------------------------------------------------------------- + // Output Register + //--------------------------------------------------------------------------- + + if (PIPELINE == "OUT" || PIPELINE == "INOUT") begin : gen_out_pipeline + // Add a pipeline stage + axi_fifo_flop2 #( + .WIDTH (1 + DATA_W) + ) axi_fifo_flop2_i ( + .clk (clk), + .reset (rst), + .clear (1'b0), + .i_tdata ({ o_pipe_tlast, o_pipe_tdata }), + .i_tvalid (o_pipe_tvalid), + .i_tready (o_pipe_tready), + .o_tdata ({ o_chdr_tlast, o_chdr_tdata }), + .o_tvalid (o_chdr_tvalid), + .o_tready (o_chdr_tready), + .space (), + .occupied () + ); + end else begin : gen_no_out_pipeline + assign o_chdr_tdata = o_pipe_tdata; + assign o_chdr_tlast = o_pipe_tlast; + assign o_chdr_tvalid = o_pipe_tvalid; + assign o_pipe_tready = o_chdr_tready; + end + +endmodule + + +`default_nettype wire diff --git a/fpga/usrp3/lib/rfnoc/utils/chdr_convert_up.v b/fpga/usrp3/lib/rfnoc/utils/chdr_convert_up.v new file mode 100644 index 000000000..7d7b79a96 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/utils/chdr_convert_up.v @@ -0,0 +1,448 @@ +// +// Copyright 2021 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: chdr_convert_up +// +// Description: +// +// Takes a CHDR packet data stream that was generated using a CHDR width +// equal to the current bust width (DATA_W) and reformats the packet stream +// to use a wider width (O_CHDR_W). It does not resize the bus, but rather +// only changes the CHDR_W of the encoded packets. +// +// The metadata might not be a nice multiple of O_CHDR_W sized words. This +// module repacks the metadata into the new word size, little-endian ordered, +// and pads the last metadata word with zeros if necessary. +// +// Parameters: +// +// DATA_W : The width of the data bus and the input CHDR width for the +// input data stream on i_chdr. +// O_CHDR_W : CHDR_W for the output data stream on o_chdr. Must be larger +// than DATA_W. +// PIPELINE : Indicates whether to add pipeline stages to the input and/or +// output. This can be: "NONE", "IN", "OUT", or "INOUT". +// + +`default_nettype none + + +module chdr_convert_up #( + parameter DATA_W = 64, + parameter O_CHDR_W = 512, + parameter PIPELINE = "NONE" +) ( + input wire clk, + input wire rst, + + // Input + input wire [DATA_W-1:0] i_chdr_tdata, + input wire i_chdr_tlast, + input wire i_chdr_tvalid, + output wire i_chdr_tready, + + // Output + output wire [DATA_W-1:0] o_chdr_tdata, + output wire o_chdr_tlast, + output wire o_chdr_tvalid, + input wire o_chdr_tready +); + + `include "../core/rfnoc_chdr_utils.vh" + `include "../core/rfnoc_chdr_internal_utils.vh" + + // Calculate ceiling(N/D) + `define DIV_CEIL(N,D) (((N)+(D)-1)/(D)) + + + //--------------------------------------------------------------------------- + // Check Parameters + //--------------------------------------------------------------------------- + + generate + if (!( + // Must be up-sizing + (DATA_W < O_CHDR_W) && + // CHDR widths must be valid (at least 64 and powers of 2) + (DATA_W >= 64) && + (O_CHDR_W >= 64) && + (2**$clog2(DATA_W) == DATA_W) && + (2**$clog2(O_CHDR_W) == O_CHDR_W) && + // O_CHDR_W must be a multiple of DATA_W + (O_CHDR_W % DATA_W == 0) + )) begin : gen_error + ERROR__Invalid_CHDR_W_parameters(); + end + endgenerate + + + //--------------------------------------------------------------------------- + // Input Register + //--------------------------------------------------------------------------- + + wire [DATA_W-1:0] i_pipe_tdata; + wire i_pipe_tlast; + wire i_pipe_tvalid; + reg i_pipe_tready; + + if (PIPELINE == "IN" || PIPELINE == "INOUT") begin : gen_in_pipeline + // Add a pipeline stage + axi_fifo_flop2 #( + .WIDTH (1 + DATA_W) + ) axi_fifo_flop2_i ( + .clk (clk), + .reset (rst), + .clear (1'b0), + .i_tdata ({i_chdr_tlast, i_chdr_tdata}), + .i_tvalid (i_chdr_tvalid), + .i_tready (i_chdr_tready), + .o_tdata ({i_pipe_tlast, i_pipe_tdata}), + .o_tvalid (i_pipe_tvalid), + .o_tready (i_pipe_tready), + .space (), + .occupied () + ); + end else begin : gen_no_in_pipeline + assign i_pipe_tdata = i_chdr_tdata; + assign i_pipe_tlast = i_chdr_tlast; + assign i_pipe_tvalid = i_chdr_tvalid; + assign i_chdr_tready = i_pipe_tready; + end + + + //--------------------------------------------------------------------------- + // Up-size State Machine + //--------------------------------------------------------------------------- + // + // This state machine does the translation from the smaller CHDR_W to the + // larger CHDR_W by updating the header and padding words as needed. + // + //--------------------------------------------------------------------------- + + // States + localparam [3:0] ST_HDR = 4'd0; // CHDR header + localparam [3:0] ST_TS = 4'd1; // CHDR timestamp + localparam [3:0] ST_HDR_PAD = 4'd2; // CHDR header padding + localparam [3:0] ST_MDATA = 4'd3; // CHDR metadata words + localparam [3:0] ST_MDATA_PAD = 4'd4; // CHDR metadata padding + localparam [3:0] ST_PYLD = 4'd5; // CHDR payload words + localparam [3:0] ST_MGMT_HDR = 4'd6; // CHDR management header word + localparam [3:0] ST_MGMT_PYLD = 4'd7; // CHDR management payload words + localparam [3:0] ST_MGMT_PAD = 4'd8; // CHDR management word padding + localparam [3:0] ST_LAST_PAD = 4'd9; // Pad the last CHDR word + + reg [3:0] state = ST_HDR; + + // Number of input words per output word + localparam NUM_WORDS = O_CHDR_W/DATA_W; + + // Determine the number of bits needed to represent a counter to track + // which CHDR words are valid and which are padding. + localparam COUNT_W = $clog2(NUM_WORDS); + + // Determine the maximum number DATA_W-sized payload words. The maximum + // packet size is 2**16-1 bytes, then subtract one word for the smallest + // possible header and convert that to a number of whole CHDR words. + localparam NUM_PYLD_WORDS = `DIV_CEIL((2**16-1) - (DATA_W/8), DATA_W/8); + + // Determine the number of bits needed to represent a counter to track which + // I_DATA_W payload word we are processing. + localparam PYLD_COUNT_W = $clog2(NUM_PYLD_WORDS + 1); + + // Header info we need to save + reg [4:0] num_mdata_reg; + reg [2:0] pkt_type_reg; + + // Counters (number of DATA_W sized words processed on the input) + reg [ 4:0] mdata_count; + reg [COUNT_W-1:0] word_count; // Zero based (starts at 0) + + // Shortcuts for CHDR header info + wire [2:0] pkt_type = chdr_get_pkt_type(i_pipe_tdata[63:0]); + wire [4:0] num_mdata = chdr_get_num_mdata(i_pipe_tdata[63:0]); + + // Calculate payload length in bytes + wire [15:0] pyld_len_bytes = chdr_calc_payload_length(DATA_W, i_pipe_tdata[63:0]); + + // Calculate the payload length of a management packet in words (management + // packets have the same number of payload words, regardless of CHDR width). + wire [PYLD_COUNT_W-1:0] mgmt_pyld_len = `DIV_CEIL(pyld_len_bytes, DATA_W/8); + + // Determine the number of metadata words for the output packet + wire [4:0] o_num_mdata = `DIV_CEIL(num_mdata, O_CHDR_W/DATA_W); + + // Generate packet headers with updated NumMData and Length fields + reg [DATA_W-1:0] new_header; + always @(*) begin + // Pass through upper bits unchanged (e.g., timestamp) + new_header = i_pipe_tdata; + // Update NumMData + new_header[63:0] = chdr_set_num_mdata(new_header, o_num_mdata); + // Update packet length + new_header[63:0] = chdr_update_length(O_CHDR_W, new_header, + (pkt_type == CHDR_PKT_TYPE_MGMT) ? mgmt_pyld_len * (O_CHDR_W/8) : pyld_len_bytes); + end + + reg [DATA_W-1:0] new_mgmt_header; + always @(*) begin + // Update the CHDRWidth field in the management header. + new_mgmt_header = i_pipe_tdata; + new_mgmt_header[63:0] = + chdr_mgmt_set_chdr_w(i_pipe_tdata[63:0], chdr_w_to_enum(O_CHDR_W)); + end + + reg [DATA_W-1:0] o_pipe_tdata; + reg o_pipe_tlast; + reg o_pipe_tvalid; + wire o_pipe_tready; + + always @(posedge clk) begin + if (rst) begin + state <= ST_HDR; + mdata_count <= 'bX; + word_count <= 'bX; + num_mdata_reg <= 'bX; + pkt_type_reg <= 'bX; + end else if (o_pipe_tvalid & o_pipe_tready) begin + // Default assignment + word_count <= word_count + 1; + + case (state) + + // ST_HDR: CHDR Header + ST_HDR: begin + mdata_count <= 1; // The first metadata word will be word 1 + word_count <= 1; // Word 0 is the current word (header) + pkt_type_reg <= pkt_type; + // Save the number of DATA_W sized metadata words + num_mdata_reg <= num_mdata; + if (DATA_W == 64) begin + // When CHDR_W == 64, the timestamp comes after the header. + if (pkt_type == CHDR_PKT_TYPE_DATA_TS) begin + state <= ST_TS; + end else begin + // O_CHDR_W must be at least 128, so there must be at least one + // word of header padding. + state <= ST_HDR_PAD; + end + end else begin + // If DATA_W > 64 then O_CHDR_W must be at least 256, so we know + // there must be some header padding needed. + state <= ST_HDR_PAD; + end + end + + // ST_TS: Timestamp (DATA_W == 64 only) + ST_TS: begin + if (O_CHDR_W > 128) begin + state <= ST_HDR_PAD; + end else begin + if (num_mdata_reg != 0) begin + state <= ST_MDATA; + end else begin + state <= ST_PYLD; + end + end + end + + // ST_HDR_PAD: CHDR header padding to fill out the last O_CHDR_W + ST_HDR_PAD: begin + if (word_count == NUM_WORDS-1) begin + if (num_mdata_reg != 0) begin + state <= ST_MDATA; + end else if (pkt_type_reg == CHDR_PKT_TYPE_MGMT) begin + state <= ST_MGMT_HDR; + end else begin + state <= ST_PYLD; + end + end + end + + // ST_MDATA: Metadata words + ST_MDATA: begin + mdata_count <= mdata_count + 1; + if (mdata_count == num_mdata_reg) begin + // If we've input a multiple of O_CHDR_W, then we're done with + // metadata. Otherwise, we need to add some padding words. + if (word_count == NUM_WORDS-1) begin + if (pkt_type_reg == CHDR_PKT_TYPE_MGMT) begin + state <= ST_MGMT_HDR; + end else begin + state <= ST_PYLD; + end + end else begin + state <= ST_MDATA_PAD; + end + end + end + + // ST_MDATA_PAD: Add metadata padding to fill out the last O_CHDR_W + ST_MDATA_PAD: begin + if (word_count == NUM_WORDS-1) begin + if (pkt_type_reg == CHDR_PKT_TYPE_MGMT) begin + state <= ST_MGMT_HDR; + end else begin + state <= ST_PYLD; + end + end + end + + // ST_PYLD: Payload words + ST_PYLD: begin + if (i_pipe_tlast) begin + // We don't pad data words because unused bytes are not sent or + // expected on the transport. + state <= ST_HDR; + end + end + + // ST_MGMT_HDR: Management header + ST_MGMT_HDR: begin + // Management packets are different from other packet types in that + // the payload is not serialized. So we need to pad each word to make + // it a full O_CHDR_W size. + if (i_pipe_tlast) begin + state <= ST_LAST_PAD; + end else begin + state <= ST_MGMT_PAD; + end + end + + // ST_MGMT_PYLD: Management operation words + ST_MGMT_PYLD: begin + if (i_pipe_tlast) begin + state <= ST_LAST_PAD; + end else begin + state <= ST_MGMT_PAD; + end + end + + // ST_MGMT_PAD: Management word padding + ST_MGMT_PAD: begin + if (word_count == NUM_WORDS-1) begin + state <= ST_MGMT_PYLD; + end + end + + // ST_LAST_PAD: Pad the last word so output is a multiple of O_CHDR_W + ST_LAST_PAD : begin + if (word_count == NUM_WORDS-1) begin + state <= ST_HDR; + end + end + + endcase + end + end + + + //----------------------------- + // State machine output logic + //----------------------------- + + always @(*) begin + case (state) + ST_HDR : begin + o_pipe_tdata = new_header; + o_pipe_tvalid = i_pipe_tvalid; + o_pipe_tlast = i_pipe_tlast; + i_pipe_tready = o_pipe_tready; + end + ST_TS : begin + o_pipe_tdata = i_pipe_tdata; + o_pipe_tvalid = i_pipe_tvalid; + o_pipe_tlast = i_pipe_tlast; + i_pipe_tready = o_pipe_tready; + end + ST_HDR_PAD : begin + o_pipe_tdata = { DATA_W {1'b0} }; + o_pipe_tvalid = 1'b1; + o_pipe_tlast = 1'b0; + i_pipe_tready = 1'b0; + end + ST_MDATA : begin + o_pipe_tdata = i_pipe_tdata; + o_pipe_tvalid = i_pipe_tvalid; + o_pipe_tlast = 1'b0; + i_pipe_tready = o_pipe_tready; + end + ST_MDATA_PAD : begin + o_pipe_tdata = { DATA_W {1'b0} }; + o_pipe_tvalid = 1'b1; + o_pipe_tlast = 1'b0; + i_pipe_tready = 1'b0; + end + ST_PYLD : begin + o_pipe_tdata = i_pipe_tdata; + o_pipe_tvalid = i_pipe_tvalid; + o_pipe_tlast = i_pipe_tlast; + i_pipe_tready = o_pipe_tready; + end + ST_MGMT_HDR : begin + o_pipe_tdata = new_mgmt_header; + o_pipe_tvalid = i_pipe_tvalid; + o_pipe_tlast = 1'b0; + i_pipe_tready = o_pipe_tready; + end + ST_MGMT_PYLD : begin + o_pipe_tdata = i_pipe_tdata; + o_pipe_tvalid = i_pipe_tvalid; + o_pipe_tlast = 1'b0; + i_pipe_tready = o_pipe_tready; + end + ST_MGMT_PAD : begin + o_pipe_tdata = { DATA_W {1'b0} }; + o_pipe_tvalid = 1'b1; + o_pipe_tlast = 1'b0; + i_pipe_tready = 1'b0; + end + ST_LAST_PAD : begin + o_pipe_tdata = { DATA_W {1'b0} }; + o_pipe_tvalid = 1'b1; + o_pipe_tlast = (word_count == NUM_WORDS-1); + i_pipe_tready = 1'b0; + end + default : begin + o_pipe_tdata = 'bX; + o_pipe_tvalid = 1'bX; + o_pipe_tlast = 1'bX; + i_pipe_tready = 1'bX; + end + endcase + end + + + //--------------------------------------------------------------------------- + // Output Register + //--------------------------------------------------------------------------- + + if (PIPELINE == "OUT" || PIPELINE == "INOUT") begin : gen_out_pipeline + // Add a pipeline stage + axi_fifo_flop2 #( + .WIDTH (1 + DATA_W) + ) axi_fifo_flop2_i ( + .clk (clk), + .reset (rst), + .clear (1'b0), + .i_tdata ({ o_pipe_tlast, o_pipe_tdata }), + .i_tvalid (o_pipe_tvalid), + .i_tready (o_pipe_tready), + .o_tdata ({ o_chdr_tlast, o_chdr_tdata }), + .o_tvalid (o_chdr_tvalid), + .o_tready (o_chdr_tready), + .space (), + .occupied () + ); + end else begin : gen_no_out_pipeline + assign o_chdr_tdata = o_pipe_tdata; + assign o_chdr_tlast = o_pipe_tlast; + assign o_chdr_tvalid = o_pipe_tvalid; + assign o_pipe_tready = o_chdr_tready; + end + +endmodule + + +`default_nettype wire diff --git a/fpga/usrp3/lib/rfnoc/utils/chdr_resize.v b/fpga/usrp3/lib/rfnoc/utils/chdr_resize.v new file mode 100644 index 000000000..888d196f6 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/utils/chdr_resize.v @@ -0,0 +1,378 @@ +// +// Copyright 2021 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: chdr_resize +// +// Description: +// +// Takes a CHDR packet data stream and converts it from one CHDR width to a +// different CHDR width. It can also do CHDR width conversion without +// changing the bus width, if the bus width is the same size as the smaller +// CHDR width. +// +// For example, to convert from a 64-bit CHDR_W to a 256-bit CHDR_W, you +// would set I_CHDR_W to 64 and O_CHDR_W to 256 (by default, I_DATA_W will be +// set to 64 and O_DATA_W will be set to 256). +// +// But you could also convert from 64-bit CHDR to 256-bit CHDR while keeping +// the bus width at 64 bits. In this case you would set I_CHDR_W to 64 and +// O_CHDR_W to 256, but set both I_DATA_W and O_DATA_W to 64. +// +// There are some restrictions, including the requirement that I_CHDR_W == +// I_DATA_W or O_CHDR_W == O_DATA_W, and that MIN(I_DATA_W, O_DATA_W) == +// MIN(I_CHDR_W, O_CHDR_W). Basically, it can't do CHDR width conversion +// where the smaller CHDR width is smaller than the bus width(s). For +// example, you could not do conversion from 64-bit to 256-bit CHDR with +// input and output bus widths of 256. +// +// TUSER is supported, but is not resized. TUSER is sampled along with the +// first word of the input packet and is assumed to be the same for the +// duration of the packet. +// +// Also, note that packets with different CHDR_W have a different maximum +// number of metadata bytes. This module repacks the metadata in +// little-endian order in the new word size. If there is too much metadata +// for a smaller CHDR_W packet, the extra data will be discarded. +// +// Parameters: +// +// I_CHDR_W : CHDR_W for the input data stream on i_chdr. +// O_CHDR_W : CHDR_W for the output data stream on o_chdr. +// I_DATA_W : Bus width for i_chdr_tdata. +// O_DATA_W : Bus width for o_chdr_tdata. +// USER_W : Width for i_chdr_tuser and o_chdr_tuser. +// PIPELINE : Indicates whether to add pipeline stages to the input and/or +// output. This can be: "NONE", "IN", "OUT", or "INOUT". +// + +`default_nettype none + + +module chdr_resize #( + parameter I_CHDR_W = 64, + parameter O_CHDR_W = 512, + parameter I_DATA_W = I_CHDR_W, + parameter O_DATA_W = O_CHDR_W, + parameter USER_W = 1, + parameter PIPELINE = "NONE" +) ( + input wire clk, + input wire rst, + + // Input + input wire [I_DATA_W-1:0] i_chdr_tdata, + input wire [ USER_W-1:0] i_chdr_tuser, + input wire i_chdr_tlast, + input wire i_chdr_tvalid, + output wire i_chdr_tready, + + // Input + output wire [O_DATA_W-1:0] o_chdr_tdata, + output wire [ USER_W-1:0] o_chdr_tuser, + output wire o_chdr_tlast, + output wire o_chdr_tvalid, + input wire o_chdr_tready +); + + `define MIN(X, Y) ((X) < (Y) ? (X) : (Y)) + + // Determine the bus width of the CHDR converter, which is always the smaller + // bus width of the input and output. + localparam CONVERT_W = `MIN(I_DATA_W, O_DATA_W); + // Determine if we need the bus down-sizer + localparam DO_DOWNSIZE = (I_DATA_W > O_DATA_W); + // Determine if we need the CHDR width converter + localparam DO_CONVERT = (I_CHDR_W != O_CHDR_W); + // Determine if we need the bus up-sizer + localparam DO_UPSIZE = (I_DATA_W < O_DATA_W); + + // Determine the pipeline settings. We want pipeline stages on the input + // and/or output, depending on the PIPELINE parameter, as well as between the + // up-sizer and converter, and between the converter and down-sizer, any of + // which may or may not be present. We don't, however, want back-to-back + // pipeline stages (e.g., on the output of the down-sizer and the input to + // the converter). If both an up/down-sizer and converter are used, the + // settings below will turn on the adjacent pipeline stage in the converter + // and turn off the corresponding pipeline stage in the up/down-sizer. + localparam DOWNSIZE_PIPELINE = + (PIPELINE == "IN" && DO_CONVERT) ? "IN" : + (PIPELINE == "IN" && !DO_CONVERT) ? "IN" : + (PIPELINE == "INOUT" && DO_CONVERT) ? "IN" : + (PIPELINE == "INOUT" && !DO_CONVERT) ? "INOUT" : + (PIPELINE == "OUT" && DO_CONVERT) ? "NONE" : + (PIPELINE == "OUT" && !DO_CONVERT) ? "OUT" : + "NONE" ; + localparam CONVERT_PIPELINE = + (PIPELINE == "IN" && DO_DOWNSIZE) ? "IN" : + (PIPELINE == "IN" && DO_UPSIZE ) ? "INOUT" : + (PIPELINE == "IN" /* neither */) ? "IN" : + (PIPELINE == "INOUT" && DO_DOWNSIZE) ? "INOUT" : + (PIPELINE == "INOUT" && DO_UPSIZE ) ? "INOUT" : + (PIPELINE == "INOUT" /* neither */) ? "INOUT" : + (PIPELINE == "OUT" && DO_DOWNSIZE) ? "INOUT" : + (PIPELINE == "OUT" && DO_UPSIZE ) ? "OUT" : + (PIPELINE == "OUT" /* neither */) ? "OUT" : + "NONE" ; + localparam UPSIZE_PIPELINE = + (PIPELINE == "IN" && DO_CONVERT) ? "NONE" : + (PIPELINE == "IN" && !DO_CONVERT) ? "IN" : + (PIPELINE == "INOUT" && DO_CONVERT) ? "OUT" : + (PIPELINE == "INOUT" && !DO_CONVERT) ? "INOUT" : + (PIPELINE == "OUT" && DO_CONVERT) ? "OUT" : + (PIPELINE == "OUT" && !DO_CONVERT) ? "OUT" : + "NONE" ; + + + generate + + //------------------------------------------------------------------------- + // Check Parameters + //------------------------------------------------------------------------- + + if (!( + // All widths must be valid CHDR widths (at least 64 and powers of 2) + (2**$clog2(I_CHDR_W) == I_CHDR_W) && + (2**$clog2(O_CHDR_W) == O_CHDR_W) && + (2**$clog2(I_DATA_W) == I_DATA_W) && + (2**$clog2(O_DATA_W) == O_DATA_W) && + (I_CHDR_W >= 64) && + (O_CHDR_W >= 64) && + (I_DATA_W >= 64) && + (O_DATA_W >= 64) && + // The converter width must match the smaller bus width. It doesn't work + // on buses wider than the CHDR width. + (CONVERT_W == `MIN(I_CHDR_W, O_CHDR_W)) + )) begin : gen_error + ERROR__Invalid_CHDR_or_data_width_parameters(); + end + + + //------------------------------------------------------------------------- + // TUSER Data Path + //------------------------------------------------------------------------- + // + // Sample TUSER at the beginning of the input packet and output it for the + // duration of the output packet. + // + //------------------------------------------------------------------------- + + if (DO_DOWNSIZE || DO_UPSIZE || DO_CONVERT || PIPELINE == "INOUT") begin : gen_tuser_buffer + if (!DO_DOWNSIZE && !DO_UPSIZE && DO_CONVERT && PIPELINE == "NONE") begin : gen_tuser_reg + // In this case, there's a combinatorial path from i_chdr to o_chdr, so + // we can't use a FIFO to buffer TUSER. + + // Track start of packet on o_chdr + reg o_chdr_sop = 1; + always @(posedge clk) begin + if (rst) begin + o_chdr_sop <= 1; + end else if (o_chdr_tvalid && o_chdr_tready) begin + o_chdr_sop <= o_chdr_tlast; + end + end + + reg [USER_W-1:0] o_tuser_reg; + always @(posedge clk) begin + if (rst) begin + o_tuser_reg <= {USER_W{1'bX}}; + end else if (o_chdr_tvalid && o_chdr_tready && o_chdr_sop) begin + o_tuser_reg <= i_chdr_tuser; + end + end + + // Pass through TUSER for first word in the packet, then use a holding + // register for the rest of the packet. + assign o_chdr_tuser = (o_chdr_sop) ? i_chdr_tuser : o_tuser_reg; + + end else begin : gen_tuser_fifo + // In this case we use a FIFO to buffer TUSER. + + // Track start of packet on i_chdr + reg i_chdr_sop = 1; + always @(posedge clk) begin + if (rst) begin + i_chdr_sop <= 1; + end else if (i_chdr_tvalid && i_chdr_tready) begin + i_chdr_sop <= i_chdr_tlast; + end + end + + axi_fifo_short #( + .WIDTH (USER_W) + ) axi_fifo_short_i ( + .clk (clk), + .reset (rst), + .clear (1'b0), + .i_tdata (i_chdr_tuser), + .i_tvalid (i_chdr_tvalid && i_chdr_tready && i_chdr_sop), + .i_tready (), + .o_tdata (o_chdr_tuser), + .o_tvalid (), + .o_tready (o_chdr_tready && o_chdr_tvalid && o_chdr_tlast), + .space (), + .occupied () + ); + end + end else begin : gen_tuser_pass_through + // In this case there's no logic on the data path, so we can pass TUSER + // through directly. + assign o_chdr_tuser = i_chdr_tuser; + end + + + //------------------------------------------------------------------------- + // Down-Size Input Bus Width + //------------------------------------------------------------------------- + + wire [CONVERT_W-1:0] resized_tdata; + wire resized_tlast; + wire resized_tvalid; + wire resized_tready; + + if (DO_DOWNSIZE) begin : gen_bus_downsize + axis_width_conv #( + .WORD_W (CONVERT_W), + .IN_WORDS (I_DATA_W / CONVERT_W), + .OUT_WORDS (1), + .SYNC_CLKS (1), + .PIPELINE (DOWNSIZE_PIPELINE) + ) axis_width_conv_i ( + .s_axis_aclk (clk), + .s_axis_rst (rst), + .s_axis_tdata (i_chdr_tdata), + .s_axis_tkeep ({(I_DATA_W / CONVERT_W){1'b1}}), + .s_axis_tlast (i_chdr_tlast), + .s_axis_tvalid (i_chdr_tvalid), + .s_axis_tready (i_chdr_tready), + .m_axis_aclk (clk), + .m_axis_rst (rst), + .m_axis_tdata (resized_tdata), + .m_axis_tkeep (), + .m_axis_tlast (resized_tlast), + .m_axis_tvalid (resized_tvalid), + .m_axis_tready (resized_tready) + ); + end else begin : gen_no_bus_downsize + assign resized_tdata = i_chdr_tdata; + assign resized_tlast = i_chdr_tlast; + assign resized_tvalid = i_chdr_tvalid; + assign i_chdr_tready = resized_tready; + end + + + //------------------------------------------------------------------------- + // CHDR Width Protocol Conversion + //------------------------------------------------------------------------- + + wire [CONVERT_W-1:0] converted_tdata; + wire converted_tlast; + wire converted_tvalid; + wire converted_tready; + + if (DO_CONVERT) begin : gen_convert + if (I_CHDR_W > O_CHDR_W) begin : gen_chdr_convert_down + chdr_convert_down #( + .I_CHDR_W (I_CHDR_W), + .DATA_W (CONVERT_W), + .PIPELINE (CONVERT_PIPELINE) + ) chdr_convert_down_i ( + .clk (clk), + .rst (rst), + .i_chdr_tdata (resized_tdata), + .i_chdr_tlast (resized_tlast), + .i_chdr_tvalid (resized_tvalid), + .i_chdr_tready (resized_tready), + .o_chdr_tdata (o_chdr_tdata), + .o_chdr_tlast (o_chdr_tlast), + .o_chdr_tvalid (o_chdr_tvalid), + .o_chdr_tready (o_chdr_tready) + ); + end else if (I_CHDR_W < O_CHDR_W) begin : gen_chdr_convert_up + chdr_convert_up #( + .DATA_W (CONVERT_W), + .O_CHDR_W (O_CHDR_W), + .PIPELINE (PIPELINE) + ) chdr_convert_up_i ( + .clk (clk), + .rst (rst), + .i_chdr_tdata (resized_tdata), + .i_chdr_tlast (resized_tlast), + .i_chdr_tvalid (resized_tvalid), + .i_chdr_tready (resized_tready), + .o_chdr_tdata (converted_tdata), + .o_chdr_tlast (converted_tlast), + .o_chdr_tvalid (converted_tvalid), + .o_chdr_tready (converted_tready) + ); + end + end else begin : gen_no_convert + if (PIPELINE == "INOUT" && !DO_DOWNSIZE && !DO_UPSIZE) begin : gen_pipeline + // In this case there's no conversion or up-size/down-size, so we're + // just passing the data through unchanged. However, if PIPELINE is set + // to INOUT then we should have a pipeline stage, so we add that here. + axi_fifo_flop2 #( + .WIDTH (1 + CONVERT_W) + ) axi_fifo_flop2_i ( + .clk (clk), + .reset (rst), + .clear (1'b0), + .i_tdata ({ resized_tlast, resized_tdata }), + .i_tvalid (resized_tvalid), + .i_tready (resized_tready), + .o_tdata ({ converted_tlast, converted_tdata }), + .o_tvalid (converted_tvalid), + .o_tready (converted_tready), + .space (), + .occupied () + ); + end else begin : gen_convert_bypass + assign converted_tdata = resized_tdata; + assign converted_tlast = resized_tlast; + assign converted_tvalid = resized_tvalid; + assign resized_tready = converted_tready; + end + end + + + //------------------------------------------------------------------------- + // Up-Size Output Bus Width + //------------------------------------------------------------------------- + + if (DO_UPSIZE) begin : gen_bus_upsize + axis_width_conv #( + .WORD_W (CONVERT_W), + .IN_WORDS (1), + .OUT_WORDS (O_DATA_W / CONVERT_W), + .SYNC_CLKS (1), + .PIPELINE (UPSIZE_PIPELINE) + ) axis_width_conv_i ( + .s_axis_aclk (clk), + .s_axis_rst (rst), + .s_axis_tdata (converted_tdata), + .s_axis_tkeep (1'b1), + .s_axis_tlast (converted_tlast), + .s_axis_tvalid (converted_tvalid), + .s_axis_tready (converted_tready), + .m_axis_aclk (clk), + .m_axis_rst (rst), + .m_axis_tdata (o_chdr_tdata), + .m_axis_tkeep (), + .m_axis_tlast (o_chdr_tlast), + .m_axis_tvalid (o_chdr_tvalid), + .m_axis_tready (o_chdr_tready) + ); + end else begin : gen_no_bus_upsize + assign o_chdr_tdata = converted_tdata; + assign o_chdr_tlast = converted_tlast; + assign o_chdr_tvalid = converted_tvalid; + assign converted_tready = o_chdr_tready; + end + + endgenerate + +endmodule + + +`default_nettype wire |