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 | |
| 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')
| -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 | 
