diff options
Diffstat (limited to 'fpga/usrp3/lib/rfnoc/blocks')
53 files changed, 14730 insertions, 0 deletions
diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/Makefile b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/Makefile new file mode 100644 index 000000000..acee50882 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/Makefile @@ -0,0 +1,45 @@ +# +# Copyright 2019 Ettus Research, A National Instruments Company +# +# 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_preamble after defining BASE_DIR +include $(BASE_DIR)/../tools/make/viv_sim_preamble.mak + +#------------------------------------------------- +# Design Specific +#------------------------------------------------- +# Include makefiles and sources for the DUT and its dependencies +include $(BASE_DIR)/../lib/rfnoc/core/Makefile.srcs +include $(BASE_DIR)/../lib/rfnoc/utils/Makefile.srcs +include Makefile.srcs + +DESIGN_SRCS += $(abspath \ +$(RFNOC_CORE_SRCS) \ +$(RFNOC_UTIL_SRCS) \ +$(RFNOC_BLOCK_AXI_RAM_FIFO_SRCS) \ +) + +#------------------------------------------------- +# Testbench Specific +#------------------------------------------------- +SIM_TOP = rfnoc_block_axi_ram_fifo_all_tb + +SIM_SRCS = \ +$(abspath sim_axi_ram.sv) \ +$(abspath rfnoc_block_axi_ram_fifo_tb.sv) \ +$(abspath rfnoc_block_axi_ram_fifo_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/blocks/rfnoc_block_axi_ram_fifo/Makefile.srcs b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/Makefile.srcs new file mode 100644 index 000000000..9faa27321 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/Makefile.srcs @@ -0,0 +1,18 @@ +# +# Copyright 2019 Ettus Research, A National Instruments Company +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# + +################################################## +# RFNoC Utility Sources +################################################## +RFNOC_BLOCK_AXI_RAM_FIFO_SRCS = $(abspath $(addprefix $(BASE_DIR)/../lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/, \ +noc_shell_axi_ram_fifo.v \ +axi_ram_fifo_regs.vh \ +axi_ram_fifo_regs.v \ +axi_ram_fifo_bist.v \ +axi_ram_fifo_bist_regs.v \ +axi_ram_fifo.v \ +rfnoc_block_axi_ram_fifo.v \ +)) diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/axi_ram_fifo.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/axi_ram_fifo.v new file mode 100644 index 000000000..5dd5f5ec4 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/axi_ram_fifo.v @@ -0,0 +1,1228 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module:  axi_ram_fifo +// +// Description: +// +//   Implements a FIFO using a memory-mapped AXI interface as storage. This can  +//   be connected to any memory-mapped AXI4 bus interface, such as DRAM, SRAM,  +//   or AXI interconnect IP. The input and output interfaces to the FIFO are  +//   AXI-Stream. +// +//   The logic is designed to buffer up multiple words so that writes and reads  +//   can be implemented as efficient burst transactions on the AXI4 bus. This  +//   core never crosses 4 KiB boundaries, per AXI4 rules (a burst must not  +//   cross a 4 KiB boundary). +// +//   The FIFO must be at least 4 KiB in size so that the 4 KiB page boundary  +//   protection also handles/prevents the FIFO wrap corner case. +// +// Parameters: +// +//   MEM_ADDR_W     : The width of the byte address to use for the AXI4 memory  +//                    mapped interface. +// +//   MEM_DATA_W     : The width of the data port to use for the AXI4 memory  +//                    mapped interface. +// +//   KEEP_W         : Width of tkeep on the AXI-Stream interface. Set to 1 if  +//                    tkeep is not used. +// +//   FIFO_ADDR_BASE : Default base address to use for this FIFO. +// +//   FIFO_ADDR_MASK : Default byte address mask, which defines which memory  +//                    address bits can be used for the FIFO. For example, an 64  +//                    KiB memory region, or 2^16 bytes, would require the mask  +//                    0xFFFF (16 ones). In other words, the mask should be the  +//                    size of the memory region minus 1. +// +//   BURST_TIMEOUT  : Default number of memory clock cycles to wait for new  +//                    data before performing a short, sub-optimal burst. One  +//                    value per FIFO. +// +//   BIST           : If true, BIST logic will be included in the build. +// +//   CLK_RATE       : Frequency of clk in Hz +// +//   IN_FIFO_SIZE   : The input FIFO size will be 2**IN_FIFO_SIZE in depth. +// +//   OUT_FIFO_SIZE  : The output FIFO size will be 2**OUT_FIFO_SIZE in depth.  +//                    This must be at least 9 so that there is enough space to  +//                    accept a full AXI4 burst and then accept additional  +//                    bursts while the FIFO is reading out. +// + +module axi_ram_fifo #( +  parameter                  MEM_ADDR_W     = 32, +  parameter                  MEM_DATA_W     = 64, +  parameter                  KEEP_W         = 1, +  parameter [MEM_ADDR_W-1:0] FIFO_ADDR_BASE = 'h0, +  parameter [MEM_ADDR_W-1:0] FIFO_ADDR_MASK = 'h00FFFFFF, +  parameter                  BURST_TIMEOUT  = 256, +  parameter                  BIST           = 1, +  parameter                  CLK_RATE       = 200e6, +  parameter                  IN_FIFO_SIZE   = 11, +  parameter                  OUT_FIFO_SIZE  = 10 +) ( +   +  input clk, +  input rst, + +  //-------------------------------------------------------------------------- +  // CTRL Port +  //-------------------------------------------------------------------------- + +  input  wire        s_ctrlport_req_wr, +  input  wire        s_ctrlport_req_rd, +  input  wire [19:0] s_ctrlport_req_addr, +  input  wire [31:0] s_ctrlport_req_data, +  output wire        s_ctrlport_resp_ack, +  output wire [31:0] s_ctrlport_resp_data, + +  //-------------------------------------------------------------------------- +  // AXI-Stream Interface +  //-------------------------------------------------------------------------- + +  // FIFO Input +  input  wire [MEM_DATA_W-1:0] s_tdata, +  input  wire [    KEEP_W-1:0] s_tkeep, +  input  wire                  s_tlast, +  input  wire                  s_tvalid, +  output wire                  s_tready, + +  // FIFO Output +  output wire [MEM_DATA_W-1:0] m_tdata, +  output wire [    KEEP_W-1:0] m_tkeep, +  output wire                  m_tlast, +  output wire                  m_tvalid, +  input  wire                  m_tready, + +  //-------------------------------------------------------------------------- +  // AXI4 Memory Interface +  //-------------------------------------------------------------------------- + +  // AXI Write Address Channel +  output wire [             0:0] m_axi_awid,     // Write address ID. This signal is the identification tag for the write address signals. +  output wire [  MEM_ADDR_W-1:0] m_axi_awaddr,   // Write address. The write address gives the address of the first transfer in a write burst. +  output wire [             7:0] m_axi_awlen,    // Burst length. The burst length gives the exact number of transfers in a burst. +  output wire [             2:0] m_axi_awsize,   // Burst size. This signal indicates the size of each transfer in the burst. +  output wire [             1:0] m_axi_awburst,  // Burst type. The burst type and the size information, determine how the address is calculated. +  output wire [             0:0] m_axi_awlock,   // Lock type. Provides additional information about the atomic characteristics of the transfer. +  output wire [             3:0] m_axi_awcache,  // Memory type. This signal indicates how transactions are required to progress. +  output wire [             2:0] m_axi_awprot,   // Protection type. This signal indicates the privilege and security level of the transaction. +  output wire [             3:0] m_axi_awqos,    // Quality of Service, QoS. The QoS identifier sent for each write transaction. +  output wire [             3:0] m_axi_awregion, // Region identifier. Permits a single physical interface on a slave to be re-used. +  output wire [             0:0] m_axi_awuser,   // User signal. Optional User-defined signal in the write address channel. +  output wire                    m_axi_awvalid,  // Write address valid. This signal indicates that the channel is signaling valid write addr. +  input  wire                    m_axi_awready,  // Write address ready. This signal indicates that the slave is ready to accept an address. +  // +  // AXI Write Data Channel +  output wire [  MEM_DATA_W-1:0] m_axi_wdata,    // Write data +  output wire [MEM_DATA_W/8-1:0] m_axi_wstrb,    // Write strobes. This signal indicates which byte lanes hold valid data. +  output wire                    m_axi_wlast,    // Write last. This signal indicates the last transfer in a write burst. +  output wire [             0:0] m_axi_wuser,    // User signal. Optional User-defined signal in the write data channel. +  output wire                    m_axi_wvalid,   // Write valid. This signal indicates that valid write data and strobes are available. +  input  wire                    m_axi_wready,   // Write ready. This signal indicates that the slave can accept the write data. +  // +  // AXI Write Response Channel +  input  wire [             0:0] m_axi_bid,      // Response ID tag. This signal is the ID tag of the write response. +  input  wire [             1:0] m_axi_bresp,    // Write response. This signal indicates the status of the write transaction. +  input  wire [             0:0] m_axi_buser,    // User signal. Optional User-defined signal in the write response channel. +  input  wire                    m_axi_bvalid,   // Write response valid. This signal indicates that the channel is signaling a valid response. +  output wire                    m_axi_bready,   // Response ready. This signal indicates that the master can accept a write response. +  // +  // AXI Read Address Channel +  output wire [             0:0] m_axi_arid,     // Read address ID. This signal is the identification tag for the read address group of signals. +  output wire [  MEM_ADDR_W-1:0] m_axi_araddr,   // Read address. The read address gives the address of the first transfer in a read burst. +  output wire [             7:0] m_axi_arlen,    // Burst length. This signal indicates the exact number of transfers in a burst. +  output wire [             2:0] m_axi_arsize,   // Burst size. This signal indicates the size of each transfer in the burst. +  output wire [             1:0] m_axi_arburst,  // Burst type. The burst type and the size information determine how the address for each transfer. +  output wire [             0:0] m_axi_arlock,   // Lock type. This signal provides additional information about the atomic characteristics. +  output wire [             3:0] m_axi_arcache,  // Memory type. This signal indicates how transactions are required to progress. +  output wire [             2:0] m_axi_arprot,   // Protection type. This signal indicates the privilege and security level of the transaction. +  output wire [             3:0] m_axi_arqos,    // Quality of Service, QoS. QoS identifier sent for each read transaction. +  output wire [             3:0] m_axi_arregion, // Region identifier. Permits a single physical interface on a slave to be re-used. +  output wire [             0:0] m_axi_aruser,   // User signal. Optional User-defined signal in the read address channel. +  output wire                    m_axi_arvalid,  // Read address valid. This signal indicates that the channel is signaling valid read addr. +  input  wire                    m_axi_arready,  // Read address ready. This signal indicates that the slave is ready to accept an address. +  // +  // AXI Read Data Channel +  input  wire [             0:0] m_axi_rid,      // Read ID tag. This signal is the identification tag for the read data group of signals. +  input  wire [  MEM_DATA_W-1:0] m_axi_rdata,    // Read data. +  input  wire [             1:0] m_axi_rresp,    // Read response. This signal indicates the status of the read transfer. +  input  wire                    m_axi_rlast,    // Read last. This signal indicates the last transfer in a read burst. +  input  wire [             0:0] m_axi_ruser,    // User signal. Optional User-defined signal in the read data channel. +  input  wire                    m_axi_rvalid,   // Read valid. This signal indicates that the channel is signaling the required read data. +  output wire                    m_axi_rready    // Read ready. This signal indicates that the master can accept the read data and response. +); + +  `include "axi_ram_fifo_regs.vh" + + +  //--------------------------------------------------------------------------- +  // Parameter Checking +  //--------------------------------------------------------------------------- + +  // The input FIFO size must be at least 9 so that there is enough space to  +  // hold an entire burst and be able to accept new data while that burst is  +  // waiting to be ready out. +  if (IN_FIFO_SIZE < 9) begin +    IN_FIFO_SIZE_must_be_at_least_9(); +  end + +  // The output FIFO size must be at least 9 so that there is enough space to  +  // accept a full AXI4 burst (255 words) and then accept additional bursts  +  // while the FIFO is waiting to be read out. +  if (OUT_FIFO_SIZE < 9) begin +    OUT_FIFO_SIZE_must_be_at_least_9(); +  end + +  // The memory must be at least as big as the default FIFO mask +  if (2.0**MEM_ADDR_W < FIFO_ADDR_MASK+1) begin +    MEM_ADDR_W_must_be_larger_than_size_indicated_by_FIFO_ADDR_MASK(); +  end + +  // The FIFO memory must be large enough for a full AXI4 burst + 64 words  +  // that's allocated to allow for read/write reordering. +  // TODO: Is the 64-word extra needed? Why 64? +  // +  // Min size allowed for memory region in bytes +  localparam FIFO_MIN_RAM_SIZE  = (256+64) * (MEM_DATA_W/8);  +  // +  // Equivalent mask +  localparam FIFO_ADDR_MASK_MIN = 2**($clog2(FIFO_MIN_RAM_SIZE))-1; +  // +  // Check the parameter +  if (FIFO_ADDR_MASK < FIFO_ADDR_MASK_MIN) begin +    FIFO_ADDR_MASK_must_be_at_least_256_plus_64_words(); +  end + +  // The 4 KiB page-crossing detection logic assumes that the memory is more  +  // than 4 kiB in size. This could be fixed in the code, but 8 KiB is already  +  // pretty small for an external memory. +  if (2.0**MEM_ADDR_W < 8192) begin +    MEM_ADDR_W_must_be_at_least_8_KiB(); +  end + +  // Make sure the default burst timeout is not too big for the register +  if ($clog2(BURST_TIMEOUT+1) > REG_TIMEOUT_W) begin +    BURST_TIMEOUT_must_not_exceed_the_range_of_REG_TIMEOUT_W(); +  end + + +  //--------------------------------------------------------------------------- +  // Local Parameters +  //--------------------------------------------------------------------------- + +  // Width of the timeout counter +  localparam TIMEOUT_W = REG_TIMEOUT_W; + +  // Address widths. Each memory byte address can be broken up into the word  +  // address portion (the upper bits) and the byte address portion (lower  +  // bits). Although the memory is byte addressable, we only read/write whole  +  // words. +  localparam BYTE_ADDR_W = $clog2(MEM_DATA_W/8); +  localparam WORD_ADDR_W = MEM_ADDR_W - BYTE_ADDR_W; + + +  //--------------------------------------------------------------------------- +  // Functions +  //--------------------------------------------------------------------------- + +  function automatic integer min(input integer a, b); +    min = a < b ? a : b; +  endfunction + + +  //--------------------------------------------------------------------------- +  // Signal Declarations +  //--------------------------------------------------------------------------- + +  // Track RAM FIFO state, in number of words +  reg [WORD_ADDR_W:0] space; +  reg [WORD_ADDR_W:0] occupied; +  reg [WORD_ADDR_W:0] occupied_minus_one; // Maintain a -1 version to break critical timing paths + +  reg [31:0] out_pkt_count = 0; + +  // +  // Input Side +  // +  reg  [MEM_DATA_W-1:0] s_tdata_fifo; +  reg                   s_tvalid_fifo; +  wire                  s_tready_fifo; + +  wire [MEM_DATA_W-1:0] m_tdata_fifo; +  wire                  m_tvalid_fifo; +  reg                   m_tready_fifo; + +  wire [MEM_DATA_W-1:0] s_tdata_i1; +  wire [    KEEP_W-1:0] s_tkeep_i1; +  wire                  s_tvalid_i1, s_tready_i1, s_tlast_i1; + +  wire [MEM_DATA_W-1:0] s_tdata_i2; +  wire                  s_tvalid_i2, s_tready_i2; + +  wire [MEM_DATA_W-1:0] s_tdata_i3; +  wire                  s_tvalid_i3; +  reg                   s_tready_i3; + +  wire [MEM_DATA_W-1:0] s_tdata_input; +  wire                  s_tvalid_input, s_tready_input; + +  wire [15:0] space_input, occupied_input; +  reg  [15:0] space_input_reg; +  reg         suppress_reads; + +  // +  // Output Side +  // +  wire [MEM_DATA_W-1:0] m_tdata_output; +  wire                  m_tvalid_output, m_tready_output; + +  reg [MEM_DATA_W-1:0] m_tdata_i0; +  reg                  m_tvalid_i0; +  wire                 m_tready_i0; +   +  wire [MEM_DATA_W-1:0] m_tdata_i1; +  wire                  m_tvalid_i1, m_tready_i1; +   +  wire [MEM_DATA_W-1:0] m_tdata_i2; +  wire                  m_tvalid_i2, m_tready_i2; +   +  wire [MEM_DATA_W-1:0] m_tdata_i3; +  wire [    KEEP_W-1:0] m_tkeep_i3; +  wire                  m_tvalid_i3, m_tready_i3, m_tlast_i3; + +  wire [15:0] space_output; + + +  //--------------------------------------------------------------------------- +  // Registers +  //--------------------------------------------------------------------------- + +  wire [          15:0] set_suppress_threshold; +  wire [ TIMEOUT_W-1:0] set_timeout; +  wire                  set_clear = 1'b0;       // Clear no longer needed in RFNoC +  wire [MEM_ADDR_W-1:0] set_fifo_addr_base; +  wire [MEM_ADDR_W-1:0] set_fifo_addr_mask; + +  wire        s_ctrlport_resp_ack_regs; +  wire [31:0] s_ctrlport_resp_data_regs; + +  axi_ram_fifo_regs #( +    .MEM_ADDR_W         (MEM_ADDR_W), +    .MEM_DATA_W         (MEM_DATA_W), +    .FIFO_ADDR_BASE     (FIFO_ADDR_BASE), +    .FIFO_ADDR_MASK     (FIFO_ADDR_MASK), +    .FIFO_ADDR_MASK_MIN (FIFO_ADDR_MASK_MIN), +    .BIST               (BIST), +    .IN_FIFO_SIZE       (IN_FIFO_SIZE), +    .WORD_ADDR_W        (WORD_ADDR_W), +    .BURST_TIMEOUT      (BURST_TIMEOUT), +    .TIMEOUT_W          (TIMEOUT_W) +  ) axi_ram_fifo_regs_i ( +    .clk                    (clk), +    .rst                    (rst), +    .s_ctrlport_req_wr      (s_ctrlport_req_wr), +    .s_ctrlport_req_rd      (s_ctrlport_req_rd), +    .s_ctrlport_req_addr    (s_ctrlport_req_addr), +    .s_ctrlport_req_data    (s_ctrlport_req_data), +    .s_ctrlport_resp_ack    (s_ctrlport_resp_ack_regs), +    .s_ctrlport_resp_data   (s_ctrlport_resp_data_regs), +    .rb_out_pkt_count       (out_pkt_count), +    .rb_occupied            (occupied), +    .set_suppress_threshold (set_suppress_threshold), +    .set_timeout            (set_timeout), +    .set_fifo_addr_base     (set_fifo_addr_base), +    .set_fifo_addr_mask     (set_fifo_addr_mask) +  ); + +  //synthesis translate_off +  // Check the address mask at run-time +  always @(set_fifo_addr_mask) begin +    if (set_fifo_addr_mask < FIFO_ADDR_MASK_MIN) begin +      $display("ERROR: set_fifo_addr_mask was set too small!"); +    end +    if (2**$clog2(set_fifo_addr_mask)-1 != set_fifo_addr_mask) begin +      $display("ERROR: set_fifo_addr_mask must be a power of 2, minus 1!"); +    end +  end +  //synthesis translate_on + + +  //--------------------------------------------------------------------------- +  // BIST for production testing +  //--------------------------------------------------------------------------- + +  if (BIST) begin : gen_bist +    wire                  s_ctrlport_resp_ack_bist; +    wire [          31:0] s_ctrlport_resp_data_bist; +    wire [MEM_DATA_W-1:0] m_tdata_bist; +    wire                  m_tvalid_bist; +    reg                   m_tready_bist; +    reg  [MEM_DATA_W-1:0] s_tdata_bist; +    reg                   s_tvalid_bist; +    wire                  s_tready_bist; + +    wire bist_running; + +    axi_ram_fifo_bist #( +      .DATA_W   (MEM_DATA_W), +      .COUNT_W  (48), +      .CLK_RATE (CLK_RATE), +      .RAND     (1) +    ) axi_ram_fifo_bist_i ( +      .clk                  (clk), +      .rst                  (rst), +      .s_ctrlport_req_wr    (s_ctrlport_req_wr), +      .s_ctrlport_req_rd    (s_ctrlport_req_rd), +      .s_ctrlport_req_addr  (s_ctrlport_req_addr), +      .s_ctrlport_req_data  (s_ctrlport_req_data), +      .s_ctrlport_resp_ack  (s_ctrlport_resp_ack_bist), +      .s_ctrlport_resp_data (s_ctrlport_resp_data_bist), +      .m_tdata              (m_tdata_bist), +      .m_tvalid             (m_tvalid_bist), +      .m_tready             (m_tready_bist), +      .s_tdata              (s_tdata_bist), +      .s_tvalid             (s_tvalid_bist), +      .s_tready             (s_tready_bist), +      .running              (bist_running) +    ); + +    // Use a multiplexer to decide where the data flows, using the BIST when  +    // ever the BIST is running. +    always @(*) begin +      if (bist_running) begin +        // Insert the BIST logic +        s_tdata_fifo  = m_tdata_bist; +        s_tvalid_fifo = m_tvalid_bist; +        m_tready_bist = s_tready_fifo; +        // +        s_tdata_bist  = m_tdata_fifo; +        s_tvalid_bist = m_tvalid_fifo; +        m_tready_fifo = s_tready_bist; + +        // Disable output-logic +        s_tready_i3 = 0; +        m_tdata_i0  = m_tdata_fifo; +        m_tvalid_i0 = 0; +      end else begin +        // Disable BIST +        m_tready_bist = 0; +        s_tdata_bist  = m_tdata_fifo; +        s_tvalid_bist = 0; +         +        // Bypass BIST +        s_tdata_fifo  = s_tdata_i3; +        s_tvalid_fifo = s_tvalid_i3; +        s_tready_i3   = s_tready_fifo; +        // +        m_tdata_i0    = m_tdata_fifo; +        m_tvalid_i0   = m_tvalid_fifo; +        m_tready_fifo = m_tready_i0; +      end +    end + +    // Combine register responses +    ctrlport_resp_combine #( +      .NUM_SLAVES (2) +    ) ctrlport_resp_combine_i ( +      .ctrlport_clk           (clk), +      .ctrlport_rst           (rst), +      .m_ctrlport_resp_ack    ({s_ctrlport_resp_ack_bist, s_ctrlport_resp_ack_regs}), +      .m_ctrlport_resp_status ({2{2'b00}}), +      .m_ctrlport_resp_data   ({s_ctrlport_resp_data_bist, s_ctrlport_resp_data_regs}), +      .s_ctrlport_resp_ack    (s_ctrlport_resp_ack), +      .s_ctrlport_resp_status (), +      .s_ctrlport_resp_data   (s_ctrlport_resp_data) +    ); + +  end else begin : gen_no_bist +    assign s_ctrlport_resp_ack  = s_ctrlport_resp_ack_regs; +    assign s_ctrlport_resp_data = s_ctrlport_resp_data_regs; +    always @(*) begin +      // Bypass the BIST logic +      s_tdata_fifo  = s_tdata_i3; +      s_tvalid_fifo = s_tvalid_i3; +      s_tready_i3   = s_tready_fifo; +      // +      m_tdata_i0    = m_tdata_fifo; +      m_tvalid_i0   = m_tvalid_fifo; +      m_tready_fifo = m_tready_i0; +      // +    end +  end + + +  //--------------------------------------------------------------------------- +  // Input Handling and Buffer +  //--------------------------------------------------------------------------- +  // +  // This block embeds TLAST into the data stream using an escape code and  +  // buffers up input data. +  // +  //--------------------------------------------------------------------------- + +  // Insert flops to improve timing +  axi_fifo_flop2 #( +    .WIDTH (MEM_DATA_W+1+KEEP_W) +  ) input_pipe_i0 ( +    .clk      (clk), +    .reset    (rst), +    .clear    (set_clear), +    // +    .i_tdata  ({s_tkeep, s_tlast, s_tdata}), +    .i_tvalid (s_tvalid), +    .i_tready (s_tready), +    // +    .o_tdata  ({s_tkeep_i1, s_tlast_i1, s_tdata_i1}), +    .o_tvalid (s_tvalid_i1), +    .o_tready (s_tready_i1), +    // +    .space    (), +    .occupied () +  ); + +  axi_embed_tlast_tkeep #( +    .DATA_W (MEM_DATA_W), +    .KEEP_W (KEEP_W) +  ) axi_embed_tlast_tkeep_i ( +    .clk      (clk), +    .rst      (rst | set_clear), +    // +    .i_tdata  (s_tdata_i1), +    .i_tkeep  (s_tkeep_i1), +    .i_tlast  (s_tlast_i1), +    .i_tvalid (s_tvalid_i1), +    .i_tready (s_tready_i1), +    // +    .o_tdata  (s_tdata_i2), +    .o_tvalid (s_tvalid_i2), +    .o_tready (s_tready_i2) +  ); + +  // Insert flops to improve timing +  axi_fifo_flop2 #( +    .WIDTH (MEM_DATA_W) +  ) input_pipe_i1 ( +    .clk      (clk), +    .reset    (rst), +    .clear    (set_clear), +    // +    .i_tdata  (s_tdata_i2), +    .i_tvalid (s_tvalid_i2), +    .i_tready (s_tready_i2), +    // +    .o_tdata  (s_tdata_i3), +    .o_tvalid (s_tvalid_i3), +    .o_tready (s_tready_i3), +    // +    .space    (), +    .occupied () +  ); + +  axi_fifo #( +    .WIDTH (MEM_DATA_W), +    .SIZE  (IN_FIFO_SIZE) +  ) input_fifo ( +    .clk      (clk), +    .reset    (rst), +    .clear    (set_clear), +    // +    .i_tdata  (s_tdata_fifo), +    .i_tvalid (s_tvalid_fifo), +    .i_tready (s_tready_fifo), +    // +    .o_tdata  (s_tdata_input), +    .o_tvalid (s_tvalid_input), +    .o_tready (s_tready_input), +    // +    .space    (space_input), +    .occupied (occupied_input) +  ); + + +  //--------------------------------------------------------------------------- +  // Input (Memory Write) Logic +  //--------------------------------------------------------------------------- +  // +  // The input state machine waits for enough entries in input FIFO to trigger  +  // RAM write burst. A timeout can also trigger a burst so that smaller chunks +  // of data are not left to rot in the input FIFO. Also, if enough data is  +  // present in the input FIFO to complete a burst up to the edge of a 4 KiB  +  // page then we do a burst up to the 4 KiB boundary. +  // +  //--------------------------------------------------------------------------- + +  // +  // Input side declarations +  // +  localparam [2:0] INPUT_IDLE = 0; +  localparam [2:0] INPUT1 = 1; +  localparam [2:0] INPUT2 = 2; +  localparam [2:0] INPUT3 = 3;   +  localparam [2:0] INPUT4 = 4; +  localparam [2:0] INPUT5 = 5; +  localparam [2:0] INPUT6 = 6; + +  wire write_ctrl_ready; + +  reg [           2:0] input_state; +  reg                  input_timeout_triggered; +  reg                  input_timeout_reset; +  reg [ TIMEOUT_W-1:0] input_timeout_count; +  reg [MEM_ADDR_W-1:0] write_addr; +  reg                  write_ctrl_valid; +  reg [           7:0] write_count             = 0; +  reg [           8:0] write_count_plus_one    = 1; // Maintain a +1 version to break critical timing paths +  reg                  update_write; + +  reg [WORD_ADDR_W-1:0] input_page_boundary; + +  // +  // Input timeout counter. Timeout count only increments when there is some  +  // data waiting to be written to the RAM. +  // +  always @(posedge clk) begin +    if (rst | set_clear) begin +      input_timeout_count     <= 0; +      input_timeout_triggered <= 0; +    end else if (input_timeout_reset) begin +      input_timeout_count     <= 0; +      input_timeout_triggered <= 0; +    end else if (input_timeout_count == set_timeout) begin +      input_timeout_triggered <= 1; +    end else if (input_state == INPUT_IDLE) begin +      input_timeout_count <= input_timeout_count + ((occupied_input != 0) ? 1 : 0); +    end +  end + +  // +  // Input State Machine +  // +  always @(posedge clk) +    if (rst | set_clear) begin +      input_state          <= INPUT_IDLE; +      write_addr           <= set_fifo_addr_base & ~set_fifo_addr_mask; +      input_timeout_reset  <= 1'b0; +      write_ctrl_valid     <= 1'b0; +      write_count          <= 8'd0; +      write_count_plus_one <= 9'd1; +      update_write         <= 1'b0; +    end else begin +      case (input_state) +      // +      // INPUT_IDLE. +      // To start an input transfer to DRAM need: +      // 1) Space in the RAM +      // and either +      // 2) 256 entries in the input FIFO +      // or +      // 3) Timeout occurred while waiting for more data, which can only happen  +      //    if there's at least one word in the input FIFO). +      // +      INPUT_IDLE: begin +        write_ctrl_valid    <= 1'b0; +        update_write        <= 1'b0; +        input_timeout_reset <= 1'b0; +        if (space[WORD_ADDR_W:8] != 'd0) begin    // (space > 255): 256 or more slots in the RAM +          if (occupied_input[15:8] != 'd0) begin  // (occupied_input > 255): 256 or more words in input FIFO +            input_state         <= INPUT1; +            input_timeout_reset <= 1'b1; + +            // Calculate the number of entries remaining until next 4 KiB page  +            // boundary is crossed, minus 1. The units of calculation are  +            // words. The address is always word aligned. +            input_page_boundary <= { write_addr[MEM_ADDR_W-1:12], {12-BYTE_ADDR_W{1'b1}} } -  +                                   write_addr[MEM_ADDR_W-1 : BYTE_ADDR_W]; +          end else if (input_timeout_triggered) begin  // input FIFO timeout waiting for new data. +            input_state         <= INPUT2; +            input_timeout_reset <= 1'b1; +            // Calculate the number of entries remaining until next 4 KiB page  +            // boundary is crossed, minus 1. The units of calculation are  +            // words. The address is always word-aligned. +            input_page_boundary <= { write_addr[MEM_ADDR_W-1:12], {12-BYTE_ADDR_W{1'b1}} } -  +                                   write_addr[MEM_ADDR_W-1 : BYTE_ADDR_W];   +          end +        end +      end +      // +      // INPUT1. +      // Caused by input FIFO reaching 256 entries. +      // Request write burst of lesser of: +      // 1) Entries until page boundary crossed +      // 2) 256 +      // +      INPUT1: begin +        // Replicated write logic to break a read timing critical path for  +        // write_count. +        write_count          <= input_page_boundary[min(12, WORD_ADDR_W)-1:8] == 0 ?  +                                input_page_boundary[7:0] : +                                255; +        write_count_plus_one <= input_page_boundary[min(12, WORD_ADDR_W)-1:8] == 0 ?  +                                input_page_boundary[7:0] + 1 : +                                256; +        write_ctrl_valid     <= 1'b1; +        if (write_ctrl_ready) +          input_state <= INPUT4; // Preemptive ACK +        else +          input_state <= INPUT3; // Wait for ACK +      end +      // +      // INPUT2. +      // Caused by timeout of input FIFO (occupied_input must now be 256 or  +      // less since it was 255 or less in the INPUT_IDLE state; otherwise we  +      // would have gone to INPUT1). Request write burst of lesser of: +      // 1) Entries until page boundary crossed +      // 2) Entries in input FIFO +      // +      INPUT2: begin +        // Replicated write logic to break a read timing critical path for  +        // write_count. +        write_count          <= input_page_boundary < occupied_input[8:0] - 1 ? +                                input_page_boundary[7:0] :  +                                occupied_input[8:0] - 1;  // Max result of 255 +        write_count_plus_one <= input_page_boundary < occupied_input[8:0] - 1 ?  +                                input_page_boundary[7:0] + 1 : +                                occupied_input[8:0]; +        write_ctrl_valid     <= 1'b1; +        if (write_ctrl_ready) +          input_state <= INPUT4; // Preemptive ACK +        else +          input_state <= INPUT3; // Wait for ACK +      end +      // +      // INPUT3. +      // Wait in this state for AXI4 DMA engine to accept transaction. +      // +      INPUT3: begin +        if (write_ctrl_ready) begin +          write_ctrl_valid <= 1'b0; +          input_state      <= INPUT4; // ACK +        end else begin +          write_ctrl_valid <= 1'b1; +          input_state      <= INPUT3; // Wait for ACK +        end +      end +      // +      // INPUT4. +      // Wait here until write_ctrl_ready_deasserts. This is important as the  +      // next time it asserts we know that a write response was received. +      INPUT4: begin +        write_ctrl_valid <= 1'b0; +        if (!write_ctrl_ready) +          input_state <= INPUT5; // Move on +        else +          input_state <= INPUT4; // Wait for deassert +      end   +      // +      // INPUT5. +      // Transaction has been accepted by AXI4 DMA engine. Now we wait for the  +      // re-assertion of write_ctrl_ready which signals that the AXI4 DMA  +      // engine has received a response for the whole write transaction and we  +      // assume that this means it is committed to DRAM. We are now free to  +      // update write_addr pointer and go back to idle state. +      //  +      INPUT5: begin +        write_ctrl_valid <= 1'b0; +        if (write_ctrl_ready) begin +          write_addr <= ((write_addr + (write_count_plus_one << $clog2(MEM_DATA_W/8))) & set_fifo_addr_mask) | (write_addr & ~set_fifo_addr_mask); +          input_state <= INPUT6; +          update_write <= 1'b1; +        end else begin +          input_state <= INPUT5; +        end +      end +      // +      // INPUT6: +      // Need to let space update before looking if there's more to do. +      // +      INPUT6: begin +        input_state  <= INPUT_IDLE; +        update_write <= 1'b0; +      end + +      default:  +        input_state <= INPUT_IDLE; +    endcase // case(input_state) +  end + + +  //--------------------------------------------------------------------------- +  // Read Suppression Logic +  //--------------------------------------------------------------------------- +  // +  // Monitor occupied_input to deduce when DRAM FIFO is running short of  +  // bandwidth and there is a danger of back-pressure passing upstream of the  +  // DRAM FIFO. In this situation, we suppress read requests to the DRAM FIFO  +  // so that more bandwidth is available to writes. +  // +  // However, not reading can actually cause the FIFO to fill up and stall, so  +  // if the input is stalled, allow switching back to reads. This allows the  +  // memory to fill up without causing deadlock. +  // +  //--------------------------------------------------------------------------- + +  reg input_idle, input_idle_d1, input_stalled; + +  always @(posedge clk) begin +    // We consider the input to be stalled when the input state machine is idle  +    // for 2 or more clock cycles. +    input_idle    <= (input_state == INPUT_IDLE); +    input_idle_d1 <= input_idle; +    input_stalled <= input_idle && input_idle_d1; + +    space_input_reg <= space_input; +    if (space_input_reg < set_suppress_threshold && !input_stalled) +      suppress_reads <= 1'b1; +    else  +      suppress_reads <= 1'b0; +  end + + +  //--------------------------------------------------------------------------- +  // Output Handling and Buffer +  //--------------------------------------------------------------------------- +  // +  // This block buffers output data and extracts the TLAS signal that was  +  // embedded into the data stream.  +  // +  //--------------------------------------------------------------------------- + +  // Large FIFO to buffer data read from DRAM. This FIFO must be large enough  +  // to accept a full burst read. +  axi_fifo #( +    .WIDTH (MEM_DATA_W), +    .SIZE  (OUT_FIFO_SIZE) +  ) output_fifo ( +    .clk      (clk), +    .reset    (rst), +    .clear    (set_clear), +    // +    .i_tdata  (m_tdata_output), +    .i_tvalid (m_tvalid_output), +    .i_tready (m_tready_output), +    // +    .o_tdata  (m_tdata_fifo), +    .o_tvalid (m_tvalid_fifo), +    .o_tready (m_tready_fifo), +    // +    .space    (space_output), +    .occupied () +  ); + +  // Place flops right after FIFO to improve timing +  axi_fifo_flop2 #( +    .WIDTH (MEM_DATA_W) +  ) output_pipe_i0 ( +    .clk      (clk), +    .reset    (rst), +    .clear    (set_clear), +    // +    .i_tdata  (m_tdata_i0), +    .i_tvalid (m_tvalid_i0), +    .i_tready (m_tready_i0), +    // +    .o_tdata  (m_tdata_i1), +    .o_tvalid (m_tvalid_i1), +    .o_tready (m_tready_i1), +    // +    .space    (), +    .occupied () +  ); + +  // Pipeline flop before TLAST extraction logic +  axi_fifo_flop2 #( +    .WIDTH (MEM_DATA_W) +  ) output_pipe_i1 ( +    .clk      (clk), +    .reset    (rst), +    .clear    (set_clear), +    // +    .i_tdata  (m_tdata_i1), +    .i_tvalid (m_tvalid_i1), +    .i_tready (m_tready_i1), +    // +    .o_tdata  (m_tdata_i2), +    .o_tvalid (m_tvalid_i2), +    .o_tready (m_tready_i2), +    // +    .space    (), +    .occupied () +  ); + +  axi_extract_tlast_tkeep #( +    .DATA_W (MEM_DATA_W), +    .KEEP_W (KEEP_W) +  ) axi_extract_tlast_tkeep_i ( +    .clk      (clk), +    .rst      (rst | set_clear), +    // +    .i_tdata  (m_tdata_i2), +    .i_tvalid (m_tvalid_i2), +    .i_tready (m_tready_i2), +    // +    .o_tdata  (m_tdata_i3), +    .o_tkeep  (m_tkeep_i3), +    .o_tlast  (m_tlast_i3), +    .o_tvalid (m_tvalid_i3), +    .o_tready (m_tready_i3) +  ); + +  // Pipeline flop after TLAST extraction logic +  axi_fifo_flop2 #( +    .WIDTH (MEM_DATA_W+1+KEEP_W) +  ) output_pipe_i3 ( +    .clk      (clk), +    .reset    (rst), +    .clear    (set_clear), +    // +    .i_tdata  ({m_tkeep_i3, m_tlast_i3, m_tdata_i3}), +    .i_tvalid (m_tvalid_i3), +    .i_tready (m_tready_i3), +    // +    .o_tdata  ({m_tkeep, m_tlast, m_tdata}), +    .o_tvalid (m_tvalid), +    .o_tready (m_tready), +    // +    .space    (), +    .occupied () +  ); + + +  //------------------------------------------------------------------------- +  // Output (Memory Read) Logic +  //------------------------------------------------------------------------- +  // +  // The output state machine Wait for enough entries in RAM to trigger read  +  // burst. A timeout can also trigger a burst so that smaller chunks of data  +  // are not left to rot in the RAM. Also, if enough data is present in the RAM  +  // to complete a burst up to the edge of a 4 KiB page boundary then we do a  +  // burst up to the 4 KiB boundary. +  // +  //--------------------------------------------------------------------------- +   +  // +  // Output side declarations +  // +  localparam [2:0] OUTPUT_IDLE = 0; +  localparam [2:0] OUTPUT1 = 1; +  localparam [2:0] OUTPUT2 = 2; +  localparam [2:0] OUTPUT3 = 3;   +  localparam [2:0] OUTPUT4 = 4; +  localparam [2:0] OUTPUT5 = 5; +  localparam [2:0] OUTPUT6 = 6; + +  reg  [           2:0] output_state; +  reg                   output_timeout_triggered; +  reg                   output_timeout_reset; +  reg  [ TIMEOUT_W-1:0] output_timeout_count; +  reg  [MEM_ADDR_W-1:0] read_addr; +  reg                   read_ctrl_valid; +  wire                  read_ctrl_ready; +  reg  [           7:0] read_count          = 0; +  reg  [           8:0] read_count_plus_one = 1; // Maintain a +1 version to break critical timing paths +  reg                   update_read; + +  reg [WORD_ADDR_W-1:0] output_page_boundary; // Cache in a register to break critical timing paths + +  // +  // Output Packet Counter +  // +  always @(posedge clk) begin +    if (rst) begin +      out_pkt_count <= 0; +    end else if (m_tlast & m_tvalid & m_tready) begin +      out_pkt_count <= out_pkt_count + 1; +    end +  end + +  // +  // Output timeout counter. Timeout count only increments when there is some  +  // data waiting to be read from the RAM. +  // +  always @(posedge clk) begin +    if (rst | set_clear) begin +      output_timeout_count     <= 0; +      output_timeout_triggered <= 0; +    end else if (output_timeout_reset) begin +      output_timeout_count     <= 0; +      output_timeout_triggered <= 0; +    end else if (output_timeout_count == set_timeout) begin +      output_timeout_triggered <= 1; +    end else if (output_state == OUTPUT_IDLE) begin +      output_timeout_count <= output_timeout_count + ((occupied != 0) ? 1 : 0); +    end +  end + +  // +  // Output State Machine +  // +  always @(posedge clk) +    if (rst | set_clear) begin +      output_state         <= OUTPUT_IDLE; +      read_addr            <= set_fifo_addr_base & ~set_fifo_addr_mask; +      output_timeout_reset <= 1'b0; +      read_ctrl_valid      <= 1'b0; +      read_count           <= 8'd0; +      read_count_plus_one  <= 9'd1; +      update_read          <= 1'b0; +    end else begin +      case (output_state) +      // +      // OUTPUT_IDLE. +      // To start an output transfer from DRAM +      // 1) Space in the output FIFO  +      // and either +      // 2) 256 entries in the RAM +      // or +      // 3) Timeout occurred while waiting for more data, which can only happen  +      //    if there's at least one word in the RAM. +      // +      OUTPUT_IDLE: begin +        read_ctrl_valid      <= 1'b0; +        update_read          <= 1'b0; +        output_timeout_reset <= 1'b0; +        if (space_output[15:8] != 'd0 && !suppress_reads) begin // (space_output > 255): 256 or more words in the output FIFO +          if (occupied[WORD_ADDR_W:8] != 'd0) begin             // (occupied > 255): 256 or more words in RAM +            output_state         <= OUTPUT1; +            output_timeout_reset <= 1'b1; + +            // Calculate the number of entries remaining until next 4 KiB page  +            // boundary is crossed, minus 1. The units of calculation are  +            // words. The address is always word-aligned. +            output_page_boundary <= { read_addr[MEM_ADDR_W-1:12], {12-BYTE_ADDR_W{1'b1}} } -  +                                    read_addr[MEM_ADDR_W-1 : BYTE_ADDR_W]; +          end else if (output_timeout_triggered) begin   // output FIFO timeout waiting for new data. +            output_state         <= OUTPUT2; +            output_timeout_reset <= 1'b1; +            // Calculate the number of entries remaining until next 4 KiB page  +            // boundary is crossed, minus 1. The units of calculation are  +            // words. The address is always word-aligned. +            output_page_boundary <= { read_addr[MEM_ADDR_W-1:12], {12-BYTE_ADDR_W{1'b1}} } -  +                                    read_addr[MEM_ADDR_W-1 : BYTE_ADDR_W]; +          end +        end +      end +      // +      // OUTPUT1. +      // Caused by RAM FIFO reaching 256 entries. +      // Request read burst of lesser of lesser of: +      // 1) Entries until page boundary crossed +      // 2) 256 +      // +      OUTPUT1: begin +        // Replicated write logic to break a read timing critical path for read_count +        read_count          <= output_page_boundary[min(12, WORD_ADDR_W)-1:8] == 0 ?  +                               output_page_boundary[7:0] :  +                               255; +        read_count_plus_one <= output_page_boundary[min(12, WORD_ADDR_W)-1:8] == 0 ? +                               output_page_boundary[7:0] + 1 : +                               256; +        read_ctrl_valid     <= 1'b1; +        if (read_ctrl_ready) +          output_state <= OUTPUT4; // Preemptive ACK +        else +          output_state <= OUTPUT3; // Wait for ACK +      end +      // +      // OUTPUT2. +      // Caused by timeout of main FIFO +      // Request read burst of lesser of: +      // 1) Entries until page boundary crossed +      // 2) Entries in main FIFO +      // +      OUTPUT2: begin +        // Replicated write logic to break a read timing critical path for read_count +        read_count          <= output_page_boundary < occupied_minus_one ?  +                               output_page_boundary[7:0] : +                               occupied_minus_one[7:0]; +        read_count_plus_one <= output_page_boundary < occupied_minus_one ? +                               output_page_boundary[7:0] + 1 : +                               occupied[7:0]; +        read_ctrl_valid     <= 1'b1; +        if (read_ctrl_ready) +          output_state <= OUTPUT4; // Preemptive ACK +        else +          output_state <= OUTPUT3; // Wait for ACK +      end +      // +      // OUTPUT3. +      // Wait in this state for AXI4 DMA engine to accept transaction. +      // +      OUTPUT3: begin +        if (read_ctrl_ready) begin +          read_ctrl_valid <= 1'b0; +          output_state    <= OUTPUT4; // ACK +        end else begin +          read_ctrl_valid <= 1'b1; +          output_state    <= OUTPUT3; // Wait for ACK +        end +      end +      // +      // OUTPUT4. +      // Wait here until read_ctrl_ready_deasserts. This is important as the  +      // next time it asserts we know that a read response was received. +      OUTPUT4: begin +        read_ctrl_valid <= 1'b0; +        if (!read_ctrl_ready) +          output_state <= OUTPUT5; // Move on +        else +          output_state <= OUTPUT4; // Wait for deassert +      end   +      // +      // OUTPUT5. +      // Transaction has been accepted by AXI4 DMA engine. Now we wait for the  +      // re-assertion of read_ctrl_ready which signals that the AXI4 DMA engine  +      // has received a last signal and good response for the whole read  +      // transaction. We are now free to update read_addr pointer and go back  +      // to idle state. +      //  +      OUTPUT5: begin +        read_ctrl_valid <= 1'b0; +        if (read_ctrl_ready) begin +          read_addr <= ((read_addr + (read_count_plus_one << $clog2(MEM_DATA_W/8))) & set_fifo_addr_mask) | (read_addr & ~set_fifo_addr_mask); +          output_state <= OUTPUT6; +          update_read <= 1'b1; +        end else begin +          output_state <= OUTPUT5; +        end +      end +      // +      // OUTPUT6. +      // Need to get occupied value updated before checking if there's more to do. +      // +      OUTPUT6: begin +        update_read  <= 1'b0; +        output_state <= OUTPUT_IDLE; +      end + +      default:  +        output_state <= OUTPUT_IDLE; +    endcase // case(output_state) +  end + + +  //--------------------------------------------------------------------------- +  // Shared Read/Write Logic +  //--------------------------------------------------------------------------- + +  // +  // Count number of words stored in the RAM FIFO. +  // +  always @(posedge clk) begin +    if (rst | set_clear) begin +      occupied           <=  0; +      occupied_minus_one <= -1; +    end else begin +      occupied           <= occupied           + (update_write ? write_count_plus_one : 0) - (update_read ? read_count_plus_one : 0); +      occupied_minus_one <= occupied_minus_one + (update_write ? write_count_plus_one : 0) - (update_read ? read_count_plus_one : 0); +    end +  end + +  // +  // Count amount of space in the RAM FIFO, in words.  +  // +  always @(posedge clk) begin +    if (rst | set_clear) begin +      // Set to the FIFO size minus 64 words to make allowance for read/write  +      // reordering in DRAM controller. +      // TODO: Is the 64-word extra needed? Why 64? +      space <= set_fifo_addr_mask[MEM_ADDR_W-1 -: WORD_ADDR_W] & ~('d63); +    end else begin +      space <= space - (update_write ? write_count_plus_one : 0) + (update_read ? read_count_plus_one : 0); +    end +  end +   + +  //--------------------------------------------------------------------------- +  // AXI4 DMA Master +  //--------------------------------------------------------------------------- + +  axi_dma_master #( +     .AWIDTH (MEM_ADDR_W), +     .DWIDTH (MEM_DATA_W) +  ) axi_dma_master_i ( +     .aclk             (clk), +     .areset           (rst | set_clear), +     // Write Address +     .m_axi_awid       (m_axi_awid), +     .m_axi_awaddr     (m_axi_awaddr), +     .m_axi_awlen      (m_axi_awlen), +     .m_axi_awsize     (m_axi_awsize), +     .m_axi_awburst    (m_axi_awburst), +     .m_axi_awvalid    (m_axi_awvalid), +     .m_axi_awready    (m_axi_awready), +     .m_axi_awlock     (m_axi_awlock), +     .m_axi_awcache    (m_axi_awcache), +     .m_axi_awprot     (m_axi_awprot), +     .m_axi_awqos      (m_axi_awqos), +     .m_axi_awregion   (m_axi_awregion), +     .m_axi_awuser     (m_axi_awuser), +     // Write Data +     .m_axi_wdata      (m_axi_wdata), +     .m_axi_wstrb      (m_axi_wstrb), +     .m_axi_wlast      (m_axi_wlast), +     .m_axi_wvalid     (m_axi_wvalid), +     .m_axi_wready     (m_axi_wready), +     .m_axi_wuser      (m_axi_wuser), +     // Write Response +     .m_axi_bid        (m_axi_bid), +     .m_axi_bresp      (m_axi_bresp), +     .m_axi_bvalid     (m_axi_bvalid), +     .m_axi_bready     (m_axi_bready), +     .m_axi_buser      (m_axi_buser), +     // Read Address +     .m_axi_arid       (m_axi_arid), +     .m_axi_araddr     (m_axi_araddr), +     .m_axi_arlen      (m_axi_arlen), +     .m_axi_arsize     (m_axi_arsize), +     .m_axi_arburst    (m_axi_arburst), +     .m_axi_arvalid    (m_axi_arvalid), +     .m_axi_arready    (m_axi_arready), +     .m_axi_arlock     (m_axi_arlock), +     .m_axi_arcache    (m_axi_arcache), +     .m_axi_arprot     (m_axi_arprot), +     .m_axi_arqos      (m_axi_arqos), +     .m_axi_arregion   (m_axi_arregion), +     .m_axi_aruser     (m_axi_aruser), +     // Read Data +     .m_axi_rid        (m_axi_rid), +     .m_axi_rdata      (m_axi_rdata), +     .m_axi_rresp      (m_axi_rresp), +     .m_axi_rlast      (m_axi_rlast), +     .m_axi_rvalid     (m_axi_rvalid), +     .m_axi_rready     (m_axi_rready), +     .m_axi_ruser      (m_axi_ruser), +     // +     // DMA interface for Write transactions +     // +     .write_addr       (write_addr),       // Byte address for start of write transaction (should be 64-bit aligned) +     .write_count      (write_count),      // Count of 64-bit words to write. +     .write_ctrl_valid (write_ctrl_valid), +     .write_ctrl_ready (write_ctrl_ready), +     .write_data       (s_tdata_input), +     .write_data_valid (s_tvalid_input), +     .write_data_ready (s_tready_input), +     // +     // DMA interface for Read transactions +     // +     .read_addr        (read_addr),        // Byte address for start of read transaction (should be 64-bit aligned) +     .read_count       (read_count),       // Count of 64-bit words to read. +     .read_ctrl_valid  (read_ctrl_valid), +     .read_ctrl_ready  (read_ctrl_ready), +     .read_data        (m_tdata_output), +     .read_data_valid  (m_tvalid_output), +     .read_data_ready  (m_tready_output), +     // +     // Debug +     // +     .debug            () +  ); + +endmodule + diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/axi_ram_fifo_bist.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/axi_ram_fifo_bist.v new file mode 100644 index 000000000..2dd3f99d3 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/axi_ram_fifo_bist.v @@ -0,0 +1,294 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: axi_ram_fifo_bist +// +// Description: +// +//   Implements a built-in self test for the RAM FIFO. It can generate random +//   or sequential data that it outputs as quickly as possible. The output of +//   the RAM is verified to make sure that it matches what was input to the RAM. +// +// Parameters: +// +//   DATA_W   : The width of the data port to use for the AXI4-Stream interface +// +//   COUNT_W  : Width of internal counters. This must be wide enough so that +//              word, cycle, and and error counters don't overflow during a +//              test. +// +//   CLK_RATE : The frequency of clk in Hz +// +//   RAND     : Set to 1 for random data, 0 for sequential data. +// + +module axi_ram_fifo_bist #( +  parameter DATA_W   = 64, +  parameter COUNT_W  = 48, +  parameter CLK_RATE = 200e6, +  parameter RAND     = 1 +) ( +  input clk, +  input rst, + +  //-------------------------------------------------------------------------- +  // CTRL Port +  //-------------------------------------------------------------------------- + +  input  wire        s_ctrlport_req_wr, +  input  wire        s_ctrlport_req_rd, +  input  wire [19:0] s_ctrlport_req_addr, +  input  wire [31:0] s_ctrlport_req_data, +  output wire        s_ctrlport_resp_ack, +  output wire [31:0] s_ctrlport_resp_data, + +  //-------------------------------------------------------------------------- +  // AXI-Stream Interface +  //-------------------------------------------------------------------------- + +  // Output to RAM FIFO +  output wire [DATA_W-1:0] m_tdata, +  output reg               m_tvalid, +  input  wire              m_tready, + +  // Input from RAM FIFO +  input  wire [DATA_W-1:0] s_tdata, +  input  wire              s_tvalid, +  output wire              s_tready, + +  //--------------------------------------------------------------------------- +  // Status +  //--------------------------------------------------------------------------- + +  output reg running + +); + +  //--------------------------------------------------------------------------- +  // Local Parameters +  //--------------------------------------------------------------------------- + +  // Internal word size to use for data generation. The output word will be a +  // multiple of this size. +  localparam WORD_W = 32; + +  // Random number seed (must not be 0) +  localparam [WORD_W-1:0] SEED = 'h012345678; + +  // Test data reset value +  localparam [WORD_W-1:0] INIT = RAND ? SEED : 0; + + +  //--------------------------------------------------------------------------- +  // Assertions +  //--------------------------------------------------------------------------- + +  if (DATA_W % WORD_W != 0) begin +    DATA_W_must_be_a_multiple_of_WORD_W(); +  end + +  // LFSR only supports 8, 16, and 32 bits +  if (WORD_W != 32 && WORD_W != 16 && WORD_W != 8) begin +    WORD_W_not_supported(); +  end + +  //--------------------------------------------------------------------------- +  // Functions +  //--------------------------------------------------------------------------- + +  // Linear-feedback Shift Register for random number generation. +  function [WORD_W-1:0] lfsr(input [WORD_W-1:0] din); +    reg new_bit; +    begin +      case (WORD_W) +        8  : new_bit = din[7]  ^ din[5]  ^ din[4]  ^ din[3]; +        16 : new_bit = din[15] ^ din[14] ^ din[12] ^ din[3]; +        32 : new_bit = din[31] ^ din[21] ^ din[1]  ^ din[0]; +      endcase +      lfsr = { din[WORD_W-2:0], new_bit }; +    end +  endfunction + +  function [WORD_W-1:0] next(input [WORD_W-1:0] din); +    next = RAND ? lfsr(din) : din + 1; +  endfunction + + +  //--------------------------------------------------------------------------- +  // Signal Declarations +  //--------------------------------------------------------------------------- + +  reg [COUNT_W-1:0] tx_count;    // Number of words transmitted to FIFO +  reg [COUNT_W-1:0] rx_count;    // Number of words received back from FIFO +  reg [COUNT_W-1:0] error_count; // Number of words that show errors + +  reg [WORD_W-1:0]  tx_data = next(INIT); // Transmitted data word +  reg [DATA_W-1:0]  rx_data = INIT;       // Received data words +  reg [WORD_W-1:0]  exp_data;             // Expected data word +  reg               rx_valid;             // Received word is value (strobe) + +  wire [COUNT_W-1:0] num_words;   // Number of words to test +  reg  [COUNT_W-1:0] cycle_count; // Number of clock cycles test has been running for +  wire               start;       // Start test +  wire               stop;        // Stop test +  wire               clear;       // Clear the counters +  wire               continuous;  // Continuous test mode + + +  //--------------------------------------------------------------------------- +  // Registers +  //--------------------------------------------------------------------------- + +  axi_ram_fifo_bist_regs #( +    .DATA_W   (DATA_W), +    .COUNT_W  (COUNT_W), +    .CLK_RATE (CLK_RATE) +  ) axi_ram_fifo_bist_regs_i ( +    .clk                  (clk), +    .rst                  (rst), +    .s_ctrlport_req_wr    (s_ctrlport_req_wr), +    .s_ctrlport_req_rd    (s_ctrlport_req_rd), +    .s_ctrlport_req_addr  (s_ctrlport_req_addr), +    .s_ctrlport_req_data  (s_ctrlport_req_data), +    .s_ctrlport_resp_ack  (s_ctrlport_resp_ack), +    .s_ctrlport_resp_data (s_ctrlport_resp_data), +    .tx_count             (tx_count), +    .rx_count             (rx_count), +    .error_count          (error_count), +    .cycle_count          (cycle_count), +    .num_words            (num_words), +    .start                (start), +    .stop                 (stop), +    .clear                (clear), +    .continuous           (continuous), +    .running              (running) +  ); + + +  //--------------------------------------------------------------------------- +  // State Machine +  //--------------------------------------------------------------------------- + +  localparam ST_IDLE      = 0; +  localparam ST_ACTIVE    = 1; +  localparam ST_WAIT_DONE = 2; + +  reg [        1:0] state; +  reg [COUNT_W-1:0] num_words_m1; + +  always @(posedge clk) begin +    if (rst) begin +      state    <= ST_IDLE; +      m_tvalid <= 0; +      running  <= 0; +    end else begin +      m_tvalid <= 0; + +      case (state) +        ST_IDLE : begin +          num_words_m1 <= num_words-1; +          if (start) begin +            running <= 1; +            state   <= ST_ACTIVE; +          end +        end + +        ST_ACTIVE : begin +          if (stop || (tx_count == num_words_m1 && m_tvalid && m_tready && !continuous)) begin +            m_tvalid <= 0; +            state    <= ST_WAIT_DONE; +          end else begin +            m_tvalid <= 1; +            running  <= 1; +          end +        end + +        ST_WAIT_DONE : begin +          if (rx_count >= tx_count) begin +            running <= 0; +            state   <= ST_IDLE; +          end +        end +      endcase +    end +  end + + +  //--------------------------------------------------------------------------- +  // Data Generator +  //--------------------------------------------------------------------------- + +  reg count_en; + +  // Output data is the concatenation of our generated test word. +  assign m_tdata = {(DATA_W/WORD_W){ tx_data }}; + +  // We were born ready +  assign s_tready = 1; + +  always @(posedge clk) begin +    if (rst) begin +      tx_data     <= next(INIT); +      exp_data    <= INIT; +      rx_valid    <= 0; +      tx_count    <= 0; +      rx_count    <= 0; +      error_count <= 0; +      cycle_count <= 0; +      count_en    <= 0; +    end else begin +      // +      // Output Data generation +      // +      if (m_tvalid && m_tready) begin +        tx_data  <= next(tx_data); +        tx_count <= tx_count + 1; +      end + +      // +      // Expected Data Generation +      // +      if (s_tvalid & s_tready) begin +        rx_valid <= 1; +        exp_data <= next(exp_data); +        rx_count <= rx_count + 1; +        rx_data  <= s_tdata; +      end else begin +        rx_valid <= 0; +      end + +      // +      // Data checker +      // +      if (rx_valid) begin +        if (rx_data !== {(DATA_W/WORD_W){exp_data}}) begin +          error_count <= error_count + 1; +        end +      end + +      // +      // Cycle Counter +      // +      // Start counting after get the first word back so that we measure +      // throughput and not latency. +      if (state == ST_IDLE) count_en <= 0; +      else if (s_tvalid) count_en <= 1; + +      if (count_en) cycle_count <= cycle_count + 1;  + +      // +      // Clear counters upon request +      // +      if (clear) begin +        tx_count    <= 0; +        rx_count    <= 0; +        error_count <= 0; +        cycle_count <= 0; +      end +    end +  end + +endmodule + diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/axi_ram_fifo_bist_regs.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/axi_ram_fifo_bist_regs.v new file mode 100644 index 000000000..c161c10f5 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/axi_ram_fifo_bist_regs.v @@ -0,0 +1,206 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: axi_ram_fifo_bist_regs +// +// Description: +// +//   Implements the registers for the RAM FIFO BIST logic. +// +// Parameters: +// +//   DATA_W   : The width of the data port to use for the AXI4-Stream  +//              interface. +// +//   COUNT_W  : Width of internal counters. This must be wide enough so that  +//              word, cycle, and and error counters don't overflow during a  +//              test. +// +//   CLK_RATE : The frequency of clk in Hz +// + +module axi_ram_fifo_bist_regs #( +  parameter DATA_W    = 64, +  parameter COUNT_W   = 48, +  parameter CLK_RATE  = 200e6 +) ( +  input clk, +  input rst, + +  //-------------------------------------------------------------------------- +  // CTRL Port +  //-------------------------------------------------------------------------- + +  input  wire        s_ctrlport_req_wr, +  input  wire        s_ctrlport_req_rd, +  input  wire [19:0] s_ctrlport_req_addr, +  input  wire [31:0] s_ctrlport_req_data, +  output reg         s_ctrlport_resp_ack, +  output reg  [31:0] s_ctrlport_resp_data, + +  //-------------------------------------------------------------------------- +  // Control and Status +  //-------------------------------------------------------------------------- + +  input wire [COUNT_W-1:0] tx_count, +  input wire [COUNT_W-1:0] rx_count, +  input wire [COUNT_W-1:0] error_count, +  input wire [COUNT_W-1:0] cycle_count, + +  output wire [COUNT_W-1:0] num_words, +   +  output reg                start, +  output reg                stop, +  output reg                clear, +  output reg                continuous, +  input wire                running +); + +  `include "axi_ram_fifo_regs.vh" + +  localparam BYTES_PER_WORD = DATA_W/8; +  localparam WORD_SHIFT     = $clog2(BYTES_PER_WORD); + +  // Make sure DATA_W is a power of 2, or else the word/byte count conversion  +  // logic won't be correct. +  if (2**$clog2(DATA_W) != DATA_W) begin +    DATA_W_must_be_a_power_of_2(); +  end + +  // The register logic currently assumes that COUNT_W is at least 33 bits. +  if (COUNT_W <= 32) begin +    COUNT_W_must_be_larger_than_32(); +  end + +  wire [19:0] word_addr; +  wire [63:0] tx_byte_count; +  wire [63:0] rx_byte_count; +  reg  [63:0] num_bytes = 0; + +  reg [31:0] tx_byte_count_hi = 0; +  reg [31:0] rx_byte_count_hi = 0; +  reg [31:0] error_count_hi   = 0; +  reg [31:0] cycle_count_hi   = 0; + +  // Only use the word address to simplify address decoding logic +  assign word_addr = {s_ctrlport_req_addr[19:2], 2'b00 }; + +  // Convert between words and bytes +  assign tx_byte_count = tx_count  << WORD_SHIFT; +  assign rx_byte_count = rx_count  << WORD_SHIFT; +  assign num_words     = num_bytes >> WORD_SHIFT; + + +  always @(posedge clk) begin +    if (rst) begin +      s_ctrlport_resp_ack <= 0; +      start               <= 0; +      stop                <= 0; +      continuous          <= 0; +      clear               <= 0; +      num_bytes           <= 0; +    end else begin +      // Default values +      s_ctrlport_resp_ack <= 0; +      start <= 0; +      stop  <= 0; +      clear <= 0; + +      //----------------------------------------------------------------------- +      // Read Logic +      //----------------------------------------------------------------------- + +      if (s_ctrlport_req_rd) begin +        case (word_addr) +          REG_BIST_CTRL : begin +            s_ctrlport_resp_data                       <= 0; +            s_ctrlport_resp_data[REG_BIST_RUNNING_POS] <= running; +            s_ctrlport_resp_data[REG_BIST_CONT_POS]    <= continuous; +            s_ctrlport_resp_ack                        <= 1; +          end +          REG_BIST_CLK_RATE : begin +            s_ctrlport_resp_data <= CLK_RATE; +            s_ctrlport_resp_ack  <= 1; +          end +          REG_BIST_NUM_BYTES_LO : begin +            s_ctrlport_resp_data <= num_bytes[31:0]; +            s_ctrlport_resp_ack  <= 1; +          end +          REG_BIST_NUM_BYTES_HI : begin +            s_ctrlport_resp_data <= num_bytes[63:32]; +            s_ctrlport_resp_ack  <= 1; +          end +          REG_BIST_TX_BYTE_COUNT_LO : begin +            s_ctrlport_resp_data <= tx_byte_count[31:0]; +            tx_byte_count_hi     <= tx_byte_count[63:32]; +            s_ctrlport_resp_ack  <= 1; +          end +          REG_BIST_TX_BYTE_COUNT_HI : begin +            s_ctrlport_resp_data <= tx_byte_count_hi; +            s_ctrlport_resp_ack  <= 1;             +          end +          REG_BIST_RX_BYTE_COUNT_LO : begin +            s_ctrlport_resp_data           <= rx_byte_count[31:0]; +            rx_byte_count_hi[COUNT_W-33:0] <= rx_byte_count[COUNT_W-1:32]; +            s_ctrlport_resp_ack            <= 1; +          end +          REG_BIST_RX_BYTE_COUNT_HI : begin +            s_ctrlport_resp_data <= rx_byte_count_hi; +            s_ctrlport_resp_ack  <= 1;   +          end +          REG_BIST_ERROR_COUNT_LO : begin +            s_ctrlport_resp_data         <= error_count[31:0]; +            error_count_hi[COUNT_W-33:0] <= error_count[COUNT_W-1:32]; +            s_ctrlport_resp_ack          <= 1;   +          end +          REG_BIST_ERROR_COUNT_HI : begin +            s_ctrlport_resp_data <= 0; +            s_ctrlport_resp_data <= error_count_hi; +            s_ctrlport_resp_ack  <= 1;   +          end +          REG_BIST_CYCLE_COUNT_LO : begin +            s_ctrlport_resp_data         <= cycle_count[31:0]; +            cycle_count_hi[COUNT_W-33:0] <= cycle_count[COUNT_W-1:32]; +            s_ctrlport_resp_ack          <= 1;   +          end +          REG_BIST_CYCLE_COUNT_HI : begin +            s_ctrlport_resp_data <= 0; +            s_ctrlport_resp_data <= cycle_count_hi; +            s_ctrlport_resp_ack  <= 1;   +          end +        endcase +      end + + +      //----------------------------------------------------------------------- +      // Write Logic +      //----------------------------------------------------------------------- + +      if (s_ctrlport_req_wr) begin +        case (word_addr) +          REG_BIST_CTRL : begin +            start      <= s_ctrlport_req_data[REG_BIST_START_POS]; +            stop       <= s_ctrlport_req_data[REG_BIST_STOP_POS]; +            clear      <= s_ctrlport_req_data[REG_BIST_CLEAR_POS]; +            continuous <= s_ctrlport_req_data[REG_BIST_CONT_POS]; +            s_ctrlport_resp_ack <= 1; +          end +          REG_BIST_NUM_BYTES_LO : begin +            // Update only the word-count portion +            num_bytes[31:WORD_SHIFT] <= s_ctrlport_req_data[31:WORD_SHIFT]; +            s_ctrlport_resp_ack <= 1; +          end +          REG_BIST_NUM_BYTES_HI : begin +            num_bytes[COUNT_W-1:32] <= s_ctrlport_req_data[COUNT_W-33:0]; +            s_ctrlport_resp_ack <= 1; +          end +        endcase +      end + +    end +  end + +endmodule + diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/axi_ram_fifo_regs.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/axi_ram_fifo_regs.v new file mode 100644 index 000000000..4496d172d --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/axi_ram_fifo_regs.v @@ -0,0 +1,207 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module:  axi_ram_fifo_regs +// +// Description:   +// +//   Implements the software-accessible registers for the axi_ram_fifo block. +// + + +module axi_ram_fifo_regs #( +  parameter                  MEM_ADDR_W         = 32, +  parameter                  MEM_DATA_W         = 64, +  parameter [MEM_ADDR_W-1:0] FIFO_ADDR_BASE     = 'h0, +  parameter [MEM_ADDR_W-1:0] FIFO_ADDR_MASK     = 'h0000FFFF, +  parameter [MEM_ADDR_W-1:0] FIFO_ADDR_MASK_MIN = 'h00000FFF, +  parameter                  BIST               = 1, +  parameter                  IN_FIFO_SIZE       = 10, +  parameter                  WORD_ADDR_W        = 29, +  parameter                  BURST_TIMEOUT      = 128, +  parameter                  TIMEOUT_W          = 12 +) ( + +  input wire clk, +  input wire rst, + +  //-------------------------------------------------------------------------- +  // CTRL Port +  //-------------------------------------------------------------------------- + +  input  wire        s_ctrlport_req_wr, +  input  wire        s_ctrlport_req_rd, +  input  wire [19:0] s_ctrlport_req_addr, +  input  wire [31:0] s_ctrlport_req_data, +  output reg         s_ctrlport_resp_ack, +  output reg  [31:0] s_ctrlport_resp_data, + +  //--------------------------------------------------------------------------- +  // Register Inputs and Outputs +  //--------------------------------------------------------------------------- + +  // Read-back Registers +  input wire [         31:0] rb_out_pkt_count, +  input wire [WORD_ADDR_W:0] rb_occupied, + +  // Settings Registers +  output reg [          15:0] set_suppress_threshold, +  output reg [ TIMEOUT_W-1:0] set_timeout, +  output reg [MEM_ADDR_W-1:0] set_fifo_addr_base = FIFO_ADDR_BASE, +  output reg [MEM_ADDR_W-1:0] set_fifo_addr_mask = FIFO_ADDR_MASK +); + +  `include "axi_ram_fifo_regs.vh" + +  function automatic integer min(input integer a, b); +    min = a < b ? a : b; +  endfunction + +  function automatic integer max(input integer a, b); +    max = a > b ? a : b; +  endfunction + +  wire [19:0] word_addr; +  wire [63:0] reg_fifo_fullness; +  reg  [31:0] reg_fifo_fullness_hi; + +  // Only use the word address to simplify address decoding logic +  assign word_addr = {s_ctrlport_req_addr[19:2], 2'b00 }; + +  // Convert the "occupied" word count to a 64-bit byte value +  assign reg_fifo_fullness = { +    {64-MEM_ADDR_W{1'b0}},            // Set unused upper bits to 0 +    rb_occupied, +    {(MEM_ADDR_W-WORD_ADDR_W){1'b0}}  // Set byte offset bits to 0 +  }; + +  always @(posedge clk) begin +    if (rst) begin +      s_ctrlport_resp_ack    <= 0; +      set_suppress_threshold <= 0; +      set_timeout            <= BURST_TIMEOUT; +      set_fifo_addr_base     <= FIFO_ADDR_BASE; +      set_fifo_addr_mask     <= FIFO_ADDR_MASK; +    end else begin +      s_ctrlport_resp_ack <= 0; + +      //----------------------------------------------------------------------- +      // Write Logic +      //----------------------------------------------------------------------- + +      if (s_ctrlport_req_wr) begin +        case (word_addr) +          REG_FIFO_READ_SUPPRESS : begin +            set_suppress_threshold <= s_ctrlport_req_data[REG_FIFO_SUPPRESS_THRESH_POS +: REG_FIFO_SUPPRESS_THRESH_W]; +            s_ctrlport_resp_ack    <= 1; +          end +          REG_FIFO_TIMEOUT : begin +            set_timeout[REG_TIMEOUT_W-1:0] <= s_ctrlport_req_data[REG_TIMEOUT_W-1:0]; +            s_ctrlport_resp_ack <= 1; +          end +          REG_FIFO_ADDR_BASE_LO : begin +            set_fifo_addr_base[min(32, MEM_ADDR_W)-1:0] <= s_ctrlport_req_data[min(32, MEM_ADDR_W)-1:0]; +            s_ctrlport_resp_ack  <= 1; +          end +          REG_FIFO_ADDR_BASE_HI : begin +            if (MEM_ADDR_W > 32) begin +              set_fifo_addr_base[max(32, MEM_ADDR_W-1):32] <= s_ctrlport_req_data[max(0, MEM_ADDR_W-33):0]; +            end +            s_ctrlport_resp_ack  <= 1; +          end +          REG_FIFO_ADDR_MASK_LO : begin +            // Coerce the lower bits so we are guaranteed to meet the minimum mask size requirement. +            set_fifo_addr_mask[min(32, MEM_ADDR_W)-1:0] <=  +              s_ctrlport_req_data[min(32, MEM_ADDR_W)-1:0] | FIFO_ADDR_MASK_MIN; +            s_ctrlport_resp_ack  <= 1; +          end +          REG_FIFO_ADDR_MASK_HI : begin +            if (MEM_ADDR_W > 32) begin +              set_fifo_addr_mask[max(32, MEM_ADDR_W-1):32] <= s_ctrlport_req_data[max(0, MEM_ADDR_W-33):0]; +            end +            s_ctrlport_resp_ack  <= 1; +          end +        endcase +      end +       + +      //----------------------------------------------------------------------- +      // Read Logic +      //----------------------------------------------------------------------- + +      if (s_ctrlport_req_rd) begin +        case (word_addr) +          REG_FIFO_INFO : begin +            s_ctrlport_resp_data <= 0; +            s_ctrlport_resp_data[REG_FIFO_MAGIC_POS +: REG_FIFO_MAGIC_W] <= 16'hF1F0; +            s_ctrlport_resp_data[REG_FIFO_BIST_PRSNT_POS] <= (BIST != 0); +            s_ctrlport_resp_ack  <= 1; +          end +          REG_FIFO_READ_SUPPRESS : begin +            s_ctrlport_resp_data <= 0; +            s_ctrlport_resp_data[REG_FIFO_IN_FIFO_SIZE_POS +: REG_FIFO_IN_FIFO_SIZE_W]  +              <= IN_FIFO_SIZE; +            s_ctrlport_resp_data[REG_FIFO_SUPPRESS_THRESH_POS +: REG_FIFO_SUPPRESS_THRESH_W] +              <= set_suppress_threshold; +            s_ctrlport_resp_ack <= 1; +          end +          REG_FIFO_MEM_SIZE : begin +            s_ctrlport_resp_data <= 0; +            s_ctrlport_resp_data[REG_FIFO_DATA_SIZE_POS +: REG_FIFO_DATA_SIZE_W] +              <= MEM_DATA_W; +            s_ctrlport_resp_data[REG_FIFO_ADDR_SIZE_POS +: REG_FIFO_ADDR_SIZE_W] +              <= MEM_ADDR_W; +            s_ctrlport_resp_ack  <= 1; +          end +          REG_FIFO_TIMEOUT : begin +            s_ctrlport_resp_data <= 0; +            s_ctrlport_resp_data[REG_TIMEOUT_W-1:0] <= set_timeout[REG_TIMEOUT_W-1:0]; +            s_ctrlport_resp_ack  <= 1; +          end +          REG_FIFO_FULLNESS_LO : begin +            s_ctrlport_resp_data <= reg_fifo_fullness[31:0]; +            reg_fifo_fullness_hi <= reg_fifo_fullness[63:32]; +            s_ctrlport_resp_ack  <= 1; +          end +          REG_FIFO_FULLNESS_HI : begin +            s_ctrlport_resp_data <= reg_fifo_fullness_hi; +            s_ctrlport_resp_ack  <= 1; +          end +          REG_FIFO_ADDR_BASE_LO : begin +            s_ctrlport_resp_data <= 0; +            s_ctrlport_resp_data[min(32, MEM_ADDR_W)-1:0] <= set_fifo_addr_base[min(32, MEM_ADDR_W)-1:0]; +            s_ctrlport_resp_ack  <= 1; +          end +          REG_FIFO_ADDR_BASE_HI : begin +            s_ctrlport_resp_data <= 0; +            if (MEM_ADDR_W > 32) begin +              s_ctrlport_resp_data[max(0,MEM_ADDR_W-33):0] <= set_fifo_addr_base[max(32, MEM_ADDR_W-1):32]; +            end +            s_ctrlport_resp_ack  <= 1; +          end +          REG_FIFO_ADDR_MASK_LO : begin +            s_ctrlport_resp_data <= 0; +            s_ctrlport_resp_data[min(32, MEM_ADDR_W)-1:0] <= set_fifo_addr_mask[min(32, MEM_ADDR_W)-1:0]; +            s_ctrlport_resp_ack  <= 1; +          end +          REG_FIFO_ADDR_MASK_HI : begin +            s_ctrlport_resp_data <= 0; +            if (MEM_ADDR_W > 32) begin +              s_ctrlport_resp_data[max(0, MEM_ADDR_W-33):0] <= set_fifo_addr_mask[max(32, MEM_ADDR_W-1):32]; +            end +            s_ctrlport_resp_ack  <= 1; +          end +          REG_FIFO_PACKET_CNT : begin +            s_ctrlport_resp_data <= 0; +            s_ctrlport_resp_data <= rb_out_pkt_count; +            s_ctrlport_resp_ack  <= 1; +          end +        endcase +      end + +    end +  end +   +endmodule
\ No newline at end of file diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/axi_ram_fifo_regs.vh b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/axi_ram_fifo_regs.vh new file mode 100644 index 000000000..ccb942552 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/axi_ram_fifo_regs.vh @@ -0,0 +1,228 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module:  axi_ram_fifo_regs (Header) +// +// Description:  Header file for axi_ram_fifo_regs. All registers are 32-bit  +//               words from software's perspective. +// + +// Address space size, per FIFO. That is, each FIFO is separated in the CTRL  +// Port address space by 2^FIFO_ADDR_W bytes. +localparam RAM_FIFO_ADDR_W = 7; + + +// REG_FIFO_INFO (R|W) +// +// Contains info/control bits for the FIFO. +// +// [31:16] : Returns the magic number 0xF1F0 (read-only) +//     [0] : Indicates if BIST logic is present (read-only) +// +localparam REG_FIFO_INFO = 'h0; +// +localparam REG_FIFO_MAGIC_POS      = 16; +localparam REG_FIFO_BIST_PRSNT_POS = 0; +// +localparam REG_FIFO_MAGIC_W = 16; + + +// REG_FIFO_READ_SUPPRESS (R|W) +// +// Controls the read suppression threshold. RAM reads will be disabled whenever +// the amount of free space in the input buffer (in units of RAM words) falls +// below this threshold. This is intended to prevent input buffer overflows +// caused by the RAM being too busy with reads. To disable the read suppression +// feature, set the threshold to 0. In general, the threshold should be set to +// a small value relative to the input FIFO buffer size (the IN_FIFO_SIZE +// field) so that it is only enabled when the input FIFO buffer is close to +// overflowing. +// + +// [31:16] : Address width of input buffer. In other words, the input buffer is  +//           2**REG_FIFO_IN_FIFO_SIZE RAM words deep. (read-only) +// [15: 0] : Read suppression threshold, in RAM words (read/write) +// +localparam REG_FIFO_READ_SUPPRESS = 'h4; +// +localparam REG_FIFO_IN_FIFO_SIZE_POS    = 16; +localparam REG_FIFO_SUPPRESS_THRESH_POS = 0; +// +localparam REG_FIFO_IN_FIFO_SIZE_W    = 16; +localparam REG_FIFO_SUPPRESS_THRESH_W = 16; + + +// REG_FIFO_MEM_SIZE (R) +// +// Returns information about the size of the attached memory. The address size  +// allows software to determine what mask and base address values are valid. +// +// [31:16] : Returns the bit width of the RAM word size. +// [15: 0] : Returns the bit width of the RAM byte address size. That is, the  +//           addressable portion of the attached memory is  +//           2**REG_FIFO_ADDR_SIZE bytes. +// +localparam REG_FIFO_MEM_SIZE = 'h8; +// +localparam REG_FIFO_DATA_SIZE_POS = 16; +localparam REG_FIFO_ADDR_SIZE_POS = 0; +// +localparam REG_FIFO_DATA_SIZE_W = 16; +localparam REG_FIFO_ADDR_SIZE_W = 16; + + +// REG_FIFO_TIMEOUT (R/W) +// +// Programs the FIFO timeout, in memory interface clock cycles. For efficiency, +// we want the memory to read and write full bursts. But we also don't want +// smaller amounts of data to be stuck in the FIFO. This timeout determines how +// long we wait for new data before we go ahead and perform a smaller +// read/write. A longer timeout will make more efficient use of the memory, but +// will increase latency. The default value is set by a module parameter. +// +// [31:12] : <Reserved> +// [11: 0] : Timeout +// +localparam REG_FIFO_TIMEOUT = 'hC; +// +localparam REG_TIMEOUT_POS = 0; +localparam REG_TIMEOUT_W   = 12; + + +// REG_FIFO_FULLNESS (R) +// +// Returns the fullness of the FIFO in bytes. This is is a 64-bit register in  +// which the least-significant 32-bit word must be read first. +// +localparam REG_FIFO_FULLNESS_LO = 'h10; +localparam REG_FIFO_FULLNESS_HI = 'h14; + + +// REG_FIFO_ADDR_BASE (R|W) +// +// Sets the base byte address to use for this FIFO. This should only be updated  +// when the FIFO is idle. This should be set to a multiple of  +// REG_FIFO_ADDR_MASK+1. Depending on the size of the memory connected, upper  +// bits might be ignored.  +// +localparam REG_FIFO_ADDR_BASE_LO = 'h18; +localparam REG_FIFO_ADDR_BASE_HI = 'h1C; + + +// REG_FIFO_ADDR_MASK (R|W) +// +// The byte address mask that controls the portion of the memory address that  +// is allocated to this FIFO. For example, set to 0xFFFF for a 64 KiB memory.  +// +// This should only be updated when the FIFO is idle. It must be equal to a  +// power-of-2 minus 1. It should be no smaller than FIFO_ADDR_MASK_MIN, defined  +// in axi_ram_fifo.v, otherwise it will be coerced up to that size.  +// +// This is is a 64-bit register in which the least-significant 32-bit word must  +// be read/written first. Depending on the size of the memory connected, the  +// upper bits might be ignored. +// +localparam REG_FIFO_ADDR_MASK_LO = 'h20; +localparam REG_FIFO_ADDR_MASK_HI = 'h24; + + +// REG_FIFO_PACKET_CNT (R) +// +// Returns the number of packets transferred out of the FIFO block. +// +localparam REG_FIFO_PACKET_CNT = 'h28; + + +//----------------------------------------------------------------------------- +// BIST Registers +//----------------------------------------------------------------------------- +// +// Only read these registers if the BIST component is included. +// +//----------------------------------------------------------------------------- + +// REG_BIST_CTRL (R|W) +// +// Control register for the BIST component.  +// +// [4] : BIST is running. Changes to 1 after a test is started, then returns to  +//       0 when BIST is complete. +// +// [3] : Continuous mode (run until stopped). When set to 1, test will continue  +//       to run until Stop bit is set. +// +// [2] : Clear the BIST counters (i.e., the TX, RX, cycle, and error counters) +// +// [1] : Stop BIST  (strobe). Write a 1 to this bit to stop the test that is  +//       currently running  +// +// [0] : Start BIST (strobe). Write a 1 to this bit to start a test using the  +//       configured NUM_BYTES and continuous mode setting. +// +localparam REG_BIST_CTRL = 'h30; +// +localparam REG_BIST_RUNNING_POS = 4; +localparam REG_BIST_CONT_POS    = 3; +localparam REG_BIST_CLEAR_POS   = 2;  // Strobe +localparam REG_BIST_STOP_POS    = 1;  // Strobe +localparam REG_BIST_START_POS   = 0;  // Strobe + + +// REG_BIST_CLOCK_RATE (R) +// +// Reports the clock rate of the BIST component in Hz. This can be used with  +// REG_BIST_CYCLE_COUNT to calculate throughput. +// +localparam REG_BIST_CLK_RATE = 'h34; + + +// REG_BIST_NUM_BYTES (R|W) +// +// Number of bytes to generate for the next BIST run. THis is not used if the  +// REG_BIST_CONT_POS bit is set. This register should not be updated while the  +// BIST is running. +// +localparam REG_BIST_NUM_BYTES_LO = 'h38; +localparam REG_BIST_NUM_BYTES_HI = 'h3C; + + +// REG_BIST_TX_BYTE_COUNT (R) +// +// Reports the number of bytes transmitted by the BIST component. This should  +// always be read least-significant word first to ensure coherency. Once BIST  +// is complete, the TX count will equal the RX count. +// +localparam REG_BIST_TX_BYTE_COUNT_LO = 'h40; +localparam REG_BIST_TX_BYTE_COUNT_HI = 'h44; + + +// REG_BIST_RX_BYTE_COUNT (R) +// +// Reports the number of bytes received by the BIST component. This should  +// always be read least-significant word first to ensure coherency. Once BIST  +// is complete, the TX count will equal the RX count. +// +localparam REG_BIST_RX_BYTE_COUNT_LO = 'h48; +localparam REG_BIST_RX_BYTE_COUNT_HI = 'h4C; + + +// REG_BIST_ERROR_COUNT (R) +// +// Reports the number of words in which the BIST component detected errors. +// This should always be read least-significant word first to ensure coherency. +// +localparam REG_BIST_ERROR_COUNT_LO = 'h50; +localparam REG_BIST_ERROR_COUNT_HI = 'h54; + + +// REG_BIST_CYCLE_COUNT (R) +// +// Reports the number of clock cycles that have elapsed while the BIST was  +// running. This can be used to calculate throughput. This should always be  +// read least-significant word first to ensure coherency. +// +localparam REG_BIST_CYCLE_COUNT_LO = 'h58; +localparam REG_BIST_CYCLE_COUNT_HI = 'h5C; + diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/noc_shell_axi_ram_fifo.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/noc_shell_axi_ram_fifo.v new file mode 100644 index 000000000..fc353595d --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/noc_shell_axi_ram_fifo.v @@ -0,0 +1,319 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: noc_shell_axi_ram_fifo +// +// Description: A NoC Shell for the RFNoC AXI RAM FIFO. This NoC Shell  +//              implements the control port interface but does nothing to the  +//              data path other than moving it to the requested clock domain. +// + +`define MAX(X,Y) ((X) > (Y) ? (X) : (Y)) + + +module noc_shell_axi_ram_fifo #( +  parameter [31:0] NOC_ID           = 32'h0, +  parameter [ 9:0] THIS_PORTID      = 10'd0, +  parameter        CHDR_W           = 64, +  parameter        DATA_W           = 64, +  parameter [ 5:0] CTRL_FIFO_SIZE   = 0, +  parameter [ 0:0] CTRLPORT_MST_EN  = 1, +  parameter [ 0:0] CTRLPORT_SLV_EN  = 1, +  parameter [ 5:0] NUM_DATA_I       = 1, +  parameter [ 5:0] NUM_DATA_O       = 1, +  parameter [ 5:0] MTU              = 10, +  parameter        SYNC_DATA_CLOCKS = 0 +) ( +  //--------------------------------------------------------------------------- +  // Framework Interface +  //--------------------------------------------------------------------------- + +  // RFNoC Framework Clocks and Resets +  input  wire                           rfnoc_chdr_clk, +  output wire                           rfnoc_chdr_rst, +  input  wire                           rfnoc_ctrl_clk, +  output wire                           rfnoc_ctrl_rst, +  // RFNoC Backend Interface +  input  wire [                  511:0] rfnoc_core_config, +  output wire [                  511:0] rfnoc_core_status, +  // CHDR Input Ports (from framework) +  input  wire [(CHDR_W*NUM_DATA_I)-1:0] s_rfnoc_chdr_tdata, +  input  wire [         NUM_DATA_I-1:0] s_rfnoc_chdr_tlast, +  input  wire [         NUM_DATA_I-1:0] s_rfnoc_chdr_tvalid, +  output wire [         NUM_DATA_I-1:0] s_rfnoc_chdr_tready, +  // CHDR Output Ports (to framework) +  output wire [(CHDR_W*NUM_DATA_O)-1:0] m_rfnoc_chdr_tdata, +  output wire [         NUM_DATA_O-1:0] m_rfnoc_chdr_tlast, +  output wire [         NUM_DATA_O-1:0] m_rfnoc_chdr_tvalid, +  input  wire [         NUM_DATA_O-1:0] m_rfnoc_chdr_tready, +  // AXIS-Ctrl Input Port (from framework) +  input  wire [                   31:0] s_rfnoc_ctrl_tdata, +  input  wire                           s_rfnoc_ctrl_tlast, +  input  wire                           s_rfnoc_ctrl_tvalid, +  output wire                           s_rfnoc_ctrl_tready, +  // AXIS-Ctrl Output Port (to framework) +  output wire [                   31:0] m_rfnoc_ctrl_tdata, +  output wire                           m_rfnoc_ctrl_tlast, +  output wire                           m_rfnoc_ctrl_tvalid, +  input  wire                           m_rfnoc_ctrl_tready, + +  //--------------------------------------------------------------------------- +  // Client Control Port Interface +  //--------------------------------------------------------------------------- + +  // Clock +  input  wire        ctrlport_clk, +  input  wire        ctrlport_rst, +  // Master +  output wire        m_ctrlport_req_wr, +  output wire        m_ctrlport_req_rd, +  output wire [19:0] m_ctrlport_req_addr, +  output wire [31:0] m_ctrlport_req_data, +  output wire [ 3:0] m_ctrlport_req_byte_en, +  output wire        m_ctrlport_req_has_time, +  output wire [63:0] m_ctrlport_req_time, +  input  wire        m_ctrlport_resp_ack, +  input  wire [ 1:0] m_ctrlport_resp_status, +  input  wire [31:0] m_ctrlport_resp_data, +  // Slave +  input  wire        s_ctrlport_req_wr, +  input  wire        s_ctrlport_req_rd, +  input  wire [19:0] s_ctrlport_req_addr, +  input  wire [ 9:0] s_ctrlport_req_portid, +  input  wire [15:0] s_ctrlport_req_rem_epid, +  input  wire [ 9:0] s_ctrlport_req_rem_portid, +  input  wire [31:0] s_ctrlport_req_data, +  input  wire [ 3:0] s_ctrlport_req_byte_en, +  input  wire        s_ctrlport_req_has_time, +  input  wire [63:0] s_ctrlport_req_time, +  output wire        s_ctrlport_resp_ack, +  output wire [ 1:0] s_ctrlport_resp_status, +  output wire [31:0] s_ctrlport_resp_data, + +  //--------------------------------------------------------------------------- +  // Client Data Interface +  //--------------------------------------------------------------------------- + +  // Clock +  input wire axis_data_clk, +  input wire axis_data_rst, + +  // Output data stream (to user logic) +  output wire [                (NUM_DATA_I*DATA_W)-1:0] m_axis_tdata, +  output wire [(NUM_DATA_I*`MAX(DATA_W/CHDR_W, 1))-1:0] m_axis_tkeep, +  output wire [                         NUM_DATA_I-1:0] m_axis_tlast, +  output wire [                         NUM_DATA_I-1:0] m_axis_tvalid, +  input  wire [                         NUM_DATA_I-1:0] m_axis_tready, + +  // Input data stream (from user logic) +  input  wire [                (NUM_DATA_O*DATA_W)-1:0] s_axis_tdata, +  input  wire [(NUM_DATA_O*`MAX(DATA_W/CHDR_W, 1))-1:0] s_axis_tkeep, +  input  wire [                         NUM_DATA_O-1:0] s_axis_tlast, +  input  wire [                         NUM_DATA_O-1:0] s_axis_tvalid, +  output wire [                         NUM_DATA_O-1:0] s_axis_tready +); + +  //--------------------------------------------------------------------------- +  //  Backend Interface +  //--------------------------------------------------------------------------- +  wire         data_i_flush_en; +  wire [31:0]  data_i_flush_timeout; +  wire [63:0]  data_i_flush_active; +  wire [63:0]  data_i_flush_done; +  wire         data_o_flush_en; +  wire [31:0]  data_o_flush_timeout; +  wire [63:0]  data_o_flush_active; +  wire [63:0]  data_o_flush_done; + +  backend_iface #( +    .NOC_ID        (NOC_ID), +    .NUM_DATA_I    (NUM_DATA_I), +    .NUM_DATA_O    (NUM_DATA_O), +    .CTRL_FIFOSIZE (CTRL_FIFO_SIZE), +    .MTU           (MTU) +  ) backend_iface_i ( +    .rfnoc_chdr_clk       (rfnoc_chdr_clk), +    .rfnoc_ctrl_clk       (rfnoc_ctrl_clk), +    .rfnoc_core_config    (rfnoc_core_config), +    .rfnoc_core_status    (rfnoc_core_status), +    .rfnoc_chdr_rst       (rfnoc_chdr_rst), +    .rfnoc_ctrl_rst       (rfnoc_ctrl_rst), +    .data_i_flush_en      (data_i_flush_en), +    .data_i_flush_timeout (data_i_flush_timeout), +    .data_i_flush_active  (data_i_flush_active), +    .data_i_flush_done    (data_i_flush_done), +    .data_o_flush_en      (data_o_flush_en), +    .data_o_flush_timeout (data_o_flush_timeout), +    .data_o_flush_active  (data_o_flush_active), +    .data_o_flush_done    (data_o_flush_done) +  ); + +  //--------------------------------------------------------------------------- +  //  Control Path +  //--------------------------------------------------------------------------- + +  ctrlport_endpoint #( +    .THIS_PORTID              (THIS_PORTID    ), +    .SYNC_CLKS                (0              ), +    .AXIS_CTRL_MST_EN         (CTRLPORT_SLV_EN), +    .AXIS_CTRL_SLV_EN         (CTRLPORT_MST_EN), +    .SLAVE_FIFO_SIZE          (CTRL_FIFO_SIZE ) +  ) ctrlport_ep_i ( +    .rfnoc_ctrl_clk           (rfnoc_ctrl_clk           ), +    .rfnoc_ctrl_rst           (rfnoc_ctrl_rst           ), +    .ctrlport_clk             (ctrlport_clk             ), +    .ctrlport_rst             (ctrlport_rst             ), +    .s_rfnoc_ctrl_tdata       (s_rfnoc_ctrl_tdata       ), +    .s_rfnoc_ctrl_tlast       (s_rfnoc_ctrl_tlast       ), +    .s_rfnoc_ctrl_tvalid      (s_rfnoc_ctrl_tvalid      ), +    .s_rfnoc_ctrl_tready      (s_rfnoc_ctrl_tready      ), +    .m_rfnoc_ctrl_tdata       (m_rfnoc_ctrl_tdata       ), +    .m_rfnoc_ctrl_tlast       (m_rfnoc_ctrl_tlast       ), +    .m_rfnoc_ctrl_tvalid      (m_rfnoc_ctrl_tvalid      ), +    .m_rfnoc_ctrl_tready      (m_rfnoc_ctrl_tready      ), +    .m_ctrlport_req_wr        (m_ctrlport_req_wr        ), +    .m_ctrlport_req_rd        (m_ctrlport_req_rd        ), +    .m_ctrlport_req_addr      (m_ctrlport_req_addr      ), +    .m_ctrlport_req_data      (m_ctrlport_req_data      ), +    .m_ctrlport_req_byte_en   (m_ctrlport_req_byte_en   ), +    .m_ctrlport_req_has_time  (m_ctrlport_req_has_time  ), +    .m_ctrlport_req_time      (m_ctrlport_req_time      ), +    .m_ctrlport_resp_ack      (m_ctrlport_resp_ack      ), +    .m_ctrlport_resp_status   (m_ctrlport_resp_status   ), +    .m_ctrlport_resp_data     (m_ctrlport_resp_data     ), +    .s_ctrlport_req_wr        (s_ctrlport_req_wr        ), +    .s_ctrlport_req_rd        (s_ctrlport_req_rd        ), +    .s_ctrlport_req_addr      (s_ctrlport_req_addr      ), +    .s_ctrlport_req_portid    (s_ctrlport_req_portid    ), +    .s_ctrlport_req_rem_epid  (s_ctrlport_req_rem_epid  ), +    .s_ctrlport_req_rem_portid(s_ctrlport_req_rem_portid), +    .s_ctrlport_req_data      (s_ctrlport_req_data      ), +    .s_ctrlport_req_byte_en   (s_ctrlport_req_byte_en   ), +    .s_ctrlport_req_has_time  (s_ctrlport_req_has_time  ), +    .s_ctrlport_req_time      (s_ctrlport_req_time      ), +    .s_ctrlport_resp_ack      (s_ctrlport_resp_ack      ), +    .s_ctrlport_resp_status   (s_ctrlport_resp_status   ), +    .s_ctrlport_resp_data     (s_ctrlport_resp_data     ) +  ); + +  //--------------------------------------------------------------------------- +  // Data Path +  //--------------------------------------------------------------------------- + +  // Set WORD_W to the smaller of DATA_W and CHDR_W. This will be our common  +  // word size between the CHDR and user data ports. +  localparam WORD_W = DATA_W < CHDR_W ? DATA_W : CHDR_W; +  localparam KEEP_W = `MAX(DATA_W/CHDR_W, 1); + +  genvar i; + +  for (i = 0; i < NUM_DATA_I; i = i + 1) begin : gen_in +    wire [CHDR_W-1:0] temp_in_tdata; +    wire              temp_in_tlast; +    wire              temp_in_tvalid; +    wire              temp_in_tready; + +    axis_packet_flush #( +      .WIDTH              (CHDR_W), +      .FLUSH_PARTIAL_PKTS (0), +      .TIMEOUT_W          (32), +      .PIPELINE           ("IN") +    ) in_packet_flush_i ( +      .clk           (rfnoc_chdr_clk), +      .reset         (rfnoc_chdr_rst), +      .enable        (data_i_flush_en), +      .timeout       (data_i_flush_timeout), +      .flushing      (data_i_flush_active[i]), +      .done          (data_i_flush_done[i]), +      .s_axis_tdata  (s_rfnoc_chdr_tdata[i*CHDR_W +: CHDR_W]), +      .s_axis_tlast  (s_rfnoc_chdr_tlast[i]), +      .s_axis_tvalid (s_rfnoc_chdr_tvalid[i]), +      .s_axis_tready (s_rfnoc_chdr_tready[i]), +      .m_axis_tdata  (temp_in_tdata), +      .m_axis_tlast  (temp_in_tlast), +      .m_axis_tvalid (temp_in_tvalid), +      .m_axis_tready (temp_in_tready) +    ); + +    axis_width_conv #( +      .WORD_W    (WORD_W), +      .IN_WORDS  (CHDR_W/WORD_W), +      .OUT_WORDS (DATA_W/WORD_W), +      .SYNC_CLKS (SYNC_DATA_CLOCKS), +      .PIPELINE  ("NONE") +    ) in_width_conv_i ( +      .s_axis_aclk   (rfnoc_chdr_clk), +      .s_axis_rst    (rfnoc_chdr_rst), +      .s_axis_tdata  (temp_in_tdata), +      .s_axis_tkeep  ({CHDR_W/WORD_W{1'b1}}), +      .s_axis_tlast  (temp_in_tlast), +      .s_axis_tvalid (temp_in_tvalid), +      .s_axis_tready (temp_in_tready), +      .m_axis_aclk   (axis_data_clk), +      .m_axis_rst    (axis_data_rst), +      .m_axis_tdata  (m_axis_tdata[i*DATA_W +: DATA_W]), +      .m_axis_tkeep  (m_axis_tkeep[i*KEEP_W +: KEEP_W]), +      .m_axis_tlast  (m_axis_tlast[i]), +      .m_axis_tvalid (m_axis_tvalid[i]), +      .m_axis_tready (m_axis_tready[i]) +    ); +  end + + +  for (i = 0; i < NUM_DATA_O; i = i + 1) begin : gen_out +    wire [       CHDR_W-1:0] temp_out_tdata; +    wire [CHDR_W/WORD_W-1:0] temp_out_tkeep; +    wire                     temp_out_tlast; +    wire                     temp_out_tvalid; +    wire                     temp_out_tready; + +    axis_width_conv #( +      .WORD_W    (WORD_W), +      .IN_WORDS  (DATA_W/WORD_W), +      .OUT_WORDS (CHDR_W/WORD_W), +      .SYNC_CLKS (SYNC_DATA_CLOCKS), +      .PIPELINE  ("NONE") +    ) out_width_conv_i ( +      .s_axis_aclk   (axis_data_clk), +      .s_axis_rst    (axis_data_rst), +      .s_axis_tdata  (s_axis_tdata[i*DATA_W +: DATA_W]), +      .s_axis_tkeep  (s_axis_tkeep[i*KEEP_W +: KEEP_W]), +      .s_axis_tlast  (s_axis_tlast[i]), +      .s_axis_tvalid (s_axis_tvalid[i]), +      .s_axis_tready (s_axis_tready[i]), +      .m_axis_aclk   (rfnoc_chdr_clk), +      .m_axis_rst    (rfnoc_chdr_rst), +      .m_axis_tdata  (temp_out_tdata), +      .m_axis_tkeep  (), +      .m_axis_tlast  (temp_out_tlast), +      .m_axis_tvalid (temp_out_tvalid), +      .m_axis_tready (temp_out_tready) +    ); + +    axis_packet_flush #( +      .WIDTH              (CHDR_W), +      .FLUSH_PARTIAL_PKTS (0), +      .TIMEOUT_W          (32), +      .PIPELINE           ("OUT") +    ) out_packet_flush_i ( +      .clk           (rfnoc_chdr_clk), +      .reset         (rfnoc_chdr_rst), +      .enable        (data_o_flush_en), +      .timeout       (data_o_flush_timeout), +      .flushing      (data_o_flush_active[i]), +      .done          (data_o_flush_done[i]), +      .s_axis_tdata  (temp_out_tdata), +      .s_axis_tlast  (temp_out_tlast), +      .s_axis_tvalid (temp_out_tvalid), +      .s_axis_tready (temp_out_tready), +      .m_axis_tdata  (m_rfnoc_chdr_tdata[i*CHDR_W +: CHDR_W]), +      .m_axis_tlast  (m_rfnoc_chdr_tlast[i]), +      .m_axis_tvalid (m_rfnoc_chdr_tvalid[i]), +      .m_axis_tready (m_rfnoc_chdr_tready[i]) +    ); + +  end + +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/rfnoc_block_axi_ram_fifo.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/rfnoc_block_axi_ram_fifo.v new file mode 100644 index 000000000..04d942ce0 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/rfnoc_block_axi_ram_fifo.v @@ -0,0 +1,485 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_axi_ram_fifo +// +// Description:  +// +//   Implements a FIFO using an AXI memory-mapped interface to an external  +//   memory. +// +// Parameters: +// +//   THIS_PORTID    : Control crossbar port to which this block is connected +//    +//   CHDR_W         : CHDR AXI-Stream data bus width +// +//   NUM_PORTS      : Number of independent FIFOs to support, all sharing the  +//                    same memory. +//    +//   MTU            : Maximum transfer unit (maximum packet size) to support,  +//                    in CHDR_W-sized words. +// +//   MEM_DATA_W     : Width of the data bus to use for the AXI memory-mapped  +//                    interface. This must be no bigger than CHDR_W and it must  +//                    evenly divide CHDR_W. +// +//   MEM_ADDR_W     : Width of the byte address to use for RAM addressing. This +//                    effectively sets the maximum combined size of all FIFOs. +//                    This must be less than or equal to AWIDTH. +// +//   AWIDTH         : Width of the address bus for the AXI memory-mapped +//                    interface. This must be at least as big as MEM_DATA_W. +// +//   FIFO_ADDR_BASE : Default base byte address of each FIFO. When NUM_PORTS >  +//                    1, this should be the concatenation of all the FIFO base  +//                    addresses. These values can be reconfigured by software. +// +//   FIFO_ADDR_MASK : Default byte address mask used by each FIFO. It must be  +//                    all ones. The size of the FIFO in bytes will be this  +//                    minus one. These values can be reconfigured by software. +// +//   BURST_TIMEOUT  : Default number of memory clock cycles to wait for new  +//                    data before performing a short, sub-optimal burst. One  +//                    value per FIFO. +// +//   IN_FIFO_SIZE   : Size of the input buffer. This is used to mitigate the  +//                    effects of memory write latency, which can be significant  +//                    when the external memory is DRAM. +// +//   OUT_FIFO_SIZE  : Size of the output buffer. This is used to mitigate the  +//                    effects of memory read latency, which can be significant  +//                    when the external memory is DRAM. +// +//   BIST           : Includes BIST logic when true. +// +//   MEM_CLK_RATE   : Frequency of mem_clk in Hz. This is used by BIST for  +//                    throughput calculation. +// + +module rfnoc_block_axi_ram_fifo #( +  parameter                            THIS_PORTID    = 0, +  parameter                            CHDR_W         = 64, +  parameter                            NUM_PORTS      = 1, +  parameter                            MTU            = 10, +  parameter                            MEM_DATA_W     = CHDR_W, +  parameter                            MEM_ADDR_W     = 32, +  parameter                            AWIDTH         = 32, +  parameter [NUM_PORTS*MEM_ADDR_W-1:0] FIFO_ADDR_BASE = {NUM_PORTS{ {MEM_ADDR_W{1'b0}} }}, +  parameter [NUM_PORTS*MEM_ADDR_W-1:0] FIFO_ADDR_MASK = {NUM_PORTS{ {(MEM_ADDR_W-$clog2(NUM_PORTS)){1'b1}} }}, +  parameter [        NUM_PORTS*32-1:0] BURST_TIMEOUT  = {NUM_PORTS{ 32'd256 }}, +  parameter                            IN_FIFO_SIZE   = 11, +  parameter                            OUT_FIFO_SIZE  = 11, +  parameter                            BIST           = 1, +  parameter                            MEM_CLK_RATE   = 200e6 +) ( +  //--------------------------------------------------------------------------- +  // AXIS CHDR Port +  //--------------------------------------------------------------------------- + +  input wire rfnoc_chdr_clk, + +  // CHDR inputs from framework +  input  wire [NUM_PORTS*CHDR_W-1:0] s_rfnoc_chdr_tdata, +  input  wire [       NUM_PORTS-1:0] s_rfnoc_chdr_tlast, +  input  wire [       NUM_PORTS-1:0] s_rfnoc_chdr_tvalid, +  output wire [       NUM_PORTS-1:0] s_rfnoc_chdr_tready, + +  // CHDR outputs to framework +  output wire [NUM_PORTS*CHDR_W-1:0] m_rfnoc_chdr_tdata, +  output wire [       NUM_PORTS-1:0] m_rfnoc_chdr_tlast, +  output wire [       NUM_PORTS-1:0] m_rfnoc_chdr_tvalid, +  input  wire [       NUM_PORTS-1:0] m_rfnoc_chdr_tready, + +  // Backend interface +  input  wire [511:0] rfnoc_core_config, +  output wire [511:0] rfnoc_core_status, + + +  //--------------------------------------------------------------------------- +  // AXIS CTRL Port +  //--------------------------------------------------------------------------- + +  input wire rfnoc_ctrl_clk, + +  // CTRL port requests from framework +  input  wire [31:0] s_rfnoc_ctrl_tdata, +  input  wire        s_rfnoc_ctrl_tlast, +  input  wire        s_rfnoc_ctrl_tvalid, +  output wire        s_rfnoc_ctrl_tready, + +  // CTRL port requests to framework +  output wire [31:0] m_rfnoc_ctrl_tdata, +  output wire        m_rfnoc_ctrl_tlast, +  output wire        m_rfnoc_ctrl_tvalid, +  input  wire        m_rfnoc_ctrl_tready, + + +  //--------------------------------------------------------------------------- +  // AXI Memory Mapped Interface +  //--------------------------------------------------------------------------- + +  // AXI Interface Clock and Reset +  input wire mem_clk, +  input wire axi_rst, + +  // AXI Write Address Channel +  output wire [         NUM_PORTS*1-1:0] m_axi_awid,     // Write address ID. This signal is the identification tag for the write address signals +  output wire [    NUM_PORTS*AWIDTH-1:0] m_axi_awaddr,   // Write address. The write address gives the address of the first transfer in a write burst +  output wire [         NUM_PORTS*8-1:0] m_axi_awlen,    // Burst length. The burst length gives the exact number of transfers in a burst. +  output wire [         NUM_PORTS*3-1:0] m_axi_awsize,   // Burst size. This signal indicates the size of each transfer in the burst. +  output wire [         NUM_PORTS*2-1:0] m_axi_awburst,  // Burst type. The burst type and the size information, determine how the address is calculated +  output wire [         NUM_PORTS*1-1:0] m_axi_awlock,   // Lock type. Provides additional information about the atomic characteristics of the transfer. +  output wire [         NUM_PORTS*4-1:0] m_axi_awcache,  // Memory type. This signal indicates how transactions are required to progress +  output wire [         NUM_PORTS*3-1:0] m_axi_awprot,   // Protection type. This signal indicates the privilege and security level of the transaction +  output wire [         NUM_PORTS*4-1:0] m_axi_awqos,    // Quality of Service, QoS. The QoS identifier sent for each write transaction +  output wire [         NUM_PORTS*4-1:0] m_axi_awregion, // Region identifier. Permits a single physical interface on a slave to be re-used. +  output wire [         NUM_PORTS*1-1:0] m_axi_awuser,   // User signal. Optional User-defined signal in the write address channel. +  output wire [         NUM_PORTS*1-1:0] m_axi_awvalid,  // Write address valid. This signal indicates that the channel is signaling valid write addr +  input  wire [         NUM_PORTS*1-1:0] m_axi_awready,  // Write address ready. This signal indicates that the slave is ready to accept an address +   +  // AXI Write Data Channel +  output wire [  NUM_PORTS*MEM_DATA_W-1:0] m_axi_wdata,  // Write data +  output wire [NUM_PORTS*MEM_DATA_W/8-1:0] m_axi_wstrb,  // Write strobes. This signal indicates which byte lanes hold valid data. +  output wire [           NUM_PORTS*1-1:0] m_axi_wlast,  // Write last. This signal indicates the last transfer in a write burst +  output wire [           NUM_PORTS*1-1:0] m_axi_wuser,  // User signal. Optional User-defined signal in the write data channel. +  output wire [           NUM_PORTS*1-1:0] m_axi_wvalid, // Write valid. This signal indicates that valid write data and strobes are available. +  input  wire [           NUM_PORTS*1-1:0] m_axi_wready, // Write ready. This signal indicates that the slave can accept the write data. +   +  // AXI Write Response Channel +  input  wire [         NUM_PORTS*1-1:0] m_axi_bid,      // Response ID tag. This signal is the ID tag of the write response. +  input  wire [         NUM_PORTS*2-1:0] m_axi_bresp,    // Write response. This signal indicates the status of the write transaction. +  input  wire [         NUM_PORTS*1-1:0] m_axi_buser,    // User signal. Optional User-defined signal in the write response channel. +  input  wire [         NUM_PORTS*1-1:0] m_axi_bvalid,   // Write response valid. This signal indicates that the channel is signaling a valid response +  output wire [         NUM_PORTS*1-1:0] m_axi_bready,   // Response ready. This signal indicates that the master can accept a write response +   +  // AXI Read Address Channel +  output wire [         NUM_PORTS*1-1:0] m_axi_arid,     // Read address ID. This signal is the identification tag for the read address group of signals +  output wire [    NUM_PORTS*AWIDTH-1:0] m_axi_araddr,   // Read address. The read address gives the address of the first transfer in a read burst +  output wire [         NUM_PORTS*8-1:0] m_axi_arlen,    // Burst length. This signal indicates the exact number of transfers in a burst. +  output wire [         NUM_PORTS*3-1:0] m_axi_arsize,   // Burst size. This signal indicates the size of each transfer in the burst. +  output wire [         NUM_PORTS*2-1:0] m_axi_arburst,  // Burst type. The burst type and the size information determine how the address for each transfer +  output wire [         NUM_PORTS*1-1:0] m_axi_arlock,   // Lock type. This signal provides additional information about the atomic characteristics +  output wire [         NUM_PORTS*4-1:0] m_axi_arcache,  // Memory type. This signal indicates how transactions are required to progress +  output wire [         NUM_PORTS*3-1:0] m_axi_arprot,   // Protection type. This signal indicates the privilege and security level of the transaction +  output wire [         NUM_PORTS*4-1:0] m_axi_arqos,    // Quality of Service, QoS. QoS identifier sent for each read transaction. +  output wire [         NUM_PORTS*4-1:0] m_axi_arregion, // Region identifier. Permits a single physical interface on a slave to be re-used +  output wire [         NUM_PORTS*1-1:0] m_axi_aruser,   // User signal. Optional User-defined signal in the read address channel. +  output wire [         NUM_PORTS*1-1:0] m_axi_arvalid,  // Read address valid. This signal indicates that the channel is signaling valid read addr +  input  wire [         NUM_PORTS*1-1:0] m_axi_arready,  // Read address ready. This signal indicates that the slave is ready to accept an address +   +  // AXI Read Data Channel +  input  wire [         NUM_PORTS*1-1:0] m_axi_rid,      // Read ID tag. This signal is the identification tag for the read data group of signals +  input  wire [NUM_PORTS*MEM_DATA_W-1:0] m_axi_rdata,    // Read data. +  input  wire [         NUM_PORTS*2-1:0] m_axi_rresp,    // Read response. This signal indicates the status of the read transfer +  input  wire [         NUM_PORTS*1-1:0] m_axi_rlast,    // Read last. This signal indicates the last transfer in a read burst. +  input  wire [         NUM_PORTS*1-1:0] m_axi_ruser,    // User signal. Optional User-defined signal in the read data channel. +  input  wire [         NUM_PORTS*1-1:0] m_axi_rvalid,   // Read valid. This signal indicates that the channel is signaling the required read data. +  output wire [         NUM_PORTS*1-1:0] m_axi_rready    // Read ready. This signal indicates that the master can accept the read data and response +); + +  `include "axi_ram_fifo_regs.vh" + +  localparam NOC_ID = 'hF1F0_0000; + +  // If the memory width is larger than the CHDR width, then we need to use  +  // tkeep to track which CHDR words are valid as they go through the FIFO.  +  // Calculate the TKEEP width here. Set to 1 if it's not needed. +  localparam KEEP_W = (MEM_DATA_W/CHDR_W) > 1 ? (MEM_DATA_W/CHDR_W) : 1; + + +  //--------------------------------------------------------------------------- +  // Parameter Checks +  //--------------------------------------------------------------------------- +   +  if (CHDR_W % MEM_DATA_W != 0 && MEM_DATA_W % CHDR_W != 0) +    CHDR_W_must_be_a_multiple_of_MEM_DATA_W_or_vice_versa(); + +  if (MEM_ADDR_W > AWIDTH) +    MEM_ADDR_W_must_be_greater_than_AWIDTH(); + + +  //--------------------------------------------------------------------------- +  // NoC Shell +  //--------------------------------------------------------------------------- + +  wire        rfnoc_chdr_rst; + +  wire        ctrlport_req_wr; +  wire        ctrlport_req_rd; +  wire [19:0] ctrlport_req_addr; +  wire [31:0] ctrlport_req_data; +  wire        ctrlport_resp_ack; +  wire [31:0] ctrlport_resp_data; + +  wire [NUM_PORTS*MEM_DATA_W-1:0] m_axis_data_tdata; +  wire [    NUM_PORTS*KEEP_W-1:0] m_axis_data_tkeep; +  wire [           NUM_PORTS-1:0] m_axis_data_tlast; +  wire [           NUM_PORTS-1:0] m_axis_data_tvalid; +  wire [           NUM_PORTS-1:0] m_axis_data_tready; + +  wire [NUM_PORTS*MEM_DATA_W-1:0] s_axis_data_tdata; +  wire [    NUM_PORTS*KEEP_W-1:0] s_axis_data_tkeep; +  wire [           NUM_PORTS-1:0] s_axis_data_tlast; +  wire [           NUM_PORTS-1:0] s_axis_data_tvalid; +  wire [           NUM_PORTS-1:0] s_axis_data_tready; + +  noc_shell_axi_ram_fifo #( +    .NOC_ID           (NOC_ID), +    .THIS_PORTID      (THIS_PORTID), +    .CHDR_W           (CHDR_W), +    .DATA_W           (MEM_DATA_W), +    .CTRL_FIFO_SIZE   (5), +    .CTRLPORT_MST_EN  (1), +    .CTRLPORT_SLV_EN  (0), +    .NUM_DATA_I       (NUM_PORTS), +    .NUM_DATA_O       (NUM_PORTS), +    .MTU              (MTU), +    .SYNC_DATA_CLOCKS (0) +  ) noc_shell_axi_ram_fifo_i ( +    .rfnoc_chdr_clk            (rfnoc_chdr_clk), +    .rfnoc_chdr_rst            (rfnoc_chdr_rst), +    .rfnoc_ctrl_clk            (rfnoc_ctrl_clk), +    .rfnoc_ctrl_rst            (), +    .rfnoc_core_config         (rfnoc_core_config), +    .rfnoc_core_status         (rfnoc_core_status), +    .s_rfnoc_chdr_tdata        (s_rfnoc_chdr_tdata), +    .s_rfnoc_chdr_tlast        (s_rfnoc_chdr_tlast), +    .s_rfnoc_chdr_tvalid       (s_rfnoc_chdr_tvalid), +    .s_rfnoc_chdr_tready       (s_rfnoc_chdr_tready), +    .m_rfnoc_chdr_tdata        (m_rfnoc_chdr_tdata), +    .m_rfnoc_chdr_tlast        (m_rfnoc_chdr_tlast), +    .m_rfnoc_chdr_tvalid       (m_rfnoc_chdr_tvalid), +    .m_rfnoc_chdr_tready       (m_rfnoc_chdr_tready), +    .s_rfnoc_ctrl_tdata        (s_rfnoc_ctrl_tdata), +    .s_rfnoc_ctrl_tlast        (s_rfnoc_ctrl_tlast), +    .s_rfnoc_ctrl_tvalid       (s_rfnoc_ctrl_tvalid), +    .s_rfnoc_ctrl_tready       (s_rfnoc_ctrl_tready), +    .m_rfnoc_ctrl_tdata        (m_rfnoc_ctrl_tdata), +    .m_rfnoc_ctrl_tlast        (m_rfnoc_ctrl_tlast), +    .m_rfnoc_ctrl_tvalid       (m_rfnoc_ctrl_tvalid), +    .m_rfnoc_ctrl_tready       (m_rfnoc_ctrl_tready), +    .ctrlport_clk              (mem_clk), +    .ctrlport_rst              (axi_rst), +    .m_ctrlport_req_wr         (ctrlport_req_wr), +    .m_ctrlport_req_rd         (ctrlport_req_rd), +    .m_ctrlport_req_addr       (ctrlport_req_addr), +    .m_ctrlport_req_data       (ctrlport_req_data), +    .m_ctrlport_req_byte_en    (), +    .m_ctrlport_req_has_time   (), +    .m_ctrlport_req_time       (), +    .m_ctrlport_resp_ack       (ctrlport_resp_ack), +    .m_ctrlport_resp_status    (2'b0), +    .m_ctrlport_resp_data      (ctrlport_resp_data), +    .s_ctrlport_req_wr         (1'b0), +    .s_ctrlport_req_rd         (1'b0), +    .s_ctrlport_req_addr       (20'b0), +    .s_ctrlport_req_portid     (10'b0), +    .s_ctrlport_req_rem_epid   (16'b0), +    .s_ctrlport_req_rem_portid (10'b0), +    .s_ctrlport_req_data       (32'b0), +    .s_ctrlport_req_byte_en    (4'b0), +    .s_ctrlport_req_has_time   (1'b0), +    .s_ctrlport_req_time       (64'b0), +    .s_ctrlport_resp_ack       (), +    .s_ctrlport_resp_status    (), +    .s_ctrlport_resp_data      (), +    .axis_data_clk             (mem_clk), +    .axis_data_rst             (axi_rst), +    .m_axis_tdata              (m_axis_data_tdata), +    .m_axis_tkeep              (m_axis_data_tkeep), +    .m_axis_tlast              (m_axis_data_tlast), +    .m_axis_tvalid             (m_axis_data_tvalid), +    .m_axis_tready             (m_axis_data_tready), +    .s_axis_tdata              (s_axis_data_tdata), +    .s_axis_tkeep              (s_axis_data_tkeep), +    .s_axis_tlast              (s_axis_data_tlast), +    .s_axis_tvalid             (s_axis_data_tvalid), +    .s_axis_tready             (s_axis_data_tready) +  ); + +  wire rfnoc_chdr_rst_mem_clk; +  reg  mem_rst_block; + +  // Cross the CHDR reset to the mem_clk domain +  pulse_synchronizer #( +    .MODE ("POSEDGE") +  ) ctrl_rst_sync_i ( +    .clk_a   (rfnoc_chdr_clk), +    .rst_a   (1'b0), +    .pulse_a (rfnoc_chdr_rst), +    .busy_a  (), +    .clk_b   (mem_clk), +    .pulse_b (rfnoc_chdr_rst_mem_clk) +  ); + +  // Combine the resets in a glitch-free manner +  always @(posedge mem_clk) begin +    mem_rst_block <= axi_rst | rfnoc_chdr_rst_mem_clk; +  end + + +  //--------------------------------------------------------------------------- +  // CTRL Port Splitter +  //--------------------------------------------------------------------------- + +  wire [   NUM_PORTS-1:0] m_ctrlport_req_wr; +  wire [   NUM_PORTS-1:0] m_ctrlport_req_rd; +  wire [20*NUM_PORTS-1:0] m_ctrlport_req_addr; +  wire [32*NUM_PORTS-1:0] m_ctrlport_req_data; +  wire [   NUM_PORTS-1:0] m_ctrlport_resp_ack; +  wire [32*NUM_PORTS-1:0] m_ctrlport_resp_data; + +  ctrlport_decoder #( +    .NUM_SLAVES   (NUM_PORTS), +    .BASE_ADDR    (0), +    .SLAVE_ADDR_W (RAM_FIFO_ADDR_W) +  ) ctrlport_splitter_i ( +    .ctrlport_clk            (mem_clk), +    .ctrlport_rst            (mem_rst_block), +    .s_ctrlport_req_wr       (ctrlport_req_wr), +    .s_ctrlport_req_rd       (ctrlport_req_rd), +    .s_ctrlport_req_addr     (ctrlport_req_addr), +    .s_ctrlport_req_data     (ctrlport_req_data), +    .s_ctrlport_req_byte_en  (4'b1111), +    .s_ctrlport_req_has_time (1'b0), +    .s_ctrlport_req_time     (64'b0), +    .s_ctrlport_resp_ack     (ctrlport_resp_ack), +    .s_ctrlport_resp_status  (), +    .s_ctrlport_resp_data    (ctrlport_resp_data), +    .m_ctrlport_req_wr       (m_ctrlport_req_wr), +    .m_ctrlport_req_rd       (m_ctrlport_req_rd), +    .m_ctrlport_req_addr     (m_ctrlport_req_addr), +    .m_ctrlport_req_data     (m_ctrlport_req_data), +    .m_ctrlport_req_byte_en  (), +    .m_ctrlport_req_has_time (), +    .m_ctrlport_req_time     (), +    .m_ctrlport_resp_ack     (m_ctrlport_resp_ack), +    .m_ctrlport_resp_status  ({NUM_PORTS*2{1'b0}}), +    .m_ctrlport_resp_data    (m_ctrlport_resp_data) +  ); + + +  //--------------------------------------------------------------------------- +  // FIFO Instances +  //--------------------------------------------------------------------------- + +  genvar i; +  for (i = 0; i < NUM_PORTS; i = i + 1) begin : gen_ram_fifos + +    wire [MEM_ADDR_W-1:0] m_axi_awaddr_int; +    wire [MEM_ADDR_W-1:0] m_axi_araddr_int; + +    // Resize the addresses from MEM_ADDR_W to AWIDTH +    assign m_axi_awaddr[(AWIDTH*(i+1))-1:AWIDTH*i] = m_axi_awaddr_int; +    assign m_axi_araddr[(AWIDTH*(i+1))-1:AWIDTH*i] = m_axi_araddr_int; + +    axi_ram_fifo #( +      .MEM_ADDR_W     (MEM_ADDR_W), +      .MEM_DATA_W     (MEM_DATA_W), +      .KEEP_W         (KEEP_W), +      .FIFO_ADDR_BASE (FIFO_ADDR_BASE[MEM_ADDR_W*i +: MEM_ADDR_W]), +      .FIFO_ADDR_MASK (FIFO_ADDR_MASK[MEM_ADDR_W*i +: MEM_ADDR_W]), +      .BURST_TIMEOUT  (BURST_TIMEOUT[32*i +: 32]), +      .BIST           (BIST), +      .CLK_RATE       (MEM_CLK_RATE), +      .IN_FIFO_SIZE   (IN_FIFO_SIZE), +      .OUT_FIFO_SIZE  (OUT_FIFO_SIZE) +    ) axi_ram_fifo_i ( + +      .clk(mem_clk), +      .rst(mem_rst_block), + +      //----------------------------------------------------------------------- +      // Control Port +      //----------------------------------------------------------------------- + +      .s_ctrlport_req_wr      (m_ctrlport_req_wr[i]), +      .s_ctrlport_req_rd      (m_ctrlport_req_rd[i]), +      .s_ctrlport_req_addr    (m_ctrlport_req_addr[20*i +: 20]), +      .s_ctrlport_req_data    (m_ctrlport_req_data[32*i +: 32]), +      .s_ctrlport_resp_ack    (m_ctrlport_resp_ack[i]), +      .s_ctrlport_resp_data   (m_ctrlport_resp_data[32*i +: 32]), + +      //----------------------------------------------------------------------- +      // AXI-Stream FIFO Interface +      //----------------------------------------------------------------------- + +      // AXI-Stream Input +      .s_tdata        (m_axis_data_tdata[MEM_DATA_W*i +: MEM_DATA_W]), +      .s_tkeep        (m_axis_data_tkeep[KEEP_W*i +: KEEP_W]), +      .s_tlast        (m_axis_data_tlast[i]), +      .s_tvalid       (m_axis_data_tvalid[i]), +      .s_tready       (m_axis_data_tready[i]), +      // +      //  AXI-Stream Output +      .m_tdata        (s_axis_data_tdata[MEM_DATA_W*i +: MEM_DATA_W]), +      .m_tkeep        (s_axis_data_tkeep[KEEP_W*i +: KEEP_W]), +      .m_tlast        (s_axis_data_tlast[i]), +      .m_tvalid       (s_axis_data_tvalid[i]), +      .m_tready       (s_axis_data_tready[i]), + +      //----------------------------------------------------------------------- +      // AXI4 Memory Interface +      //----------------------------------------------------------------------- + +      // AXI Write address channel +      .m_axi_awid     (m_axi_awid[i]), +      .m_axi_awaddr   (m_axi_awaddr_int), +      .m_axi_awlen    (m_axi_awlen[(8*(i+1))-1:8*i]), +      .m_axi_awsize   (m_axi_awsize[(3*(i+1))-1:3*i]), +      .m_axi_awburst  (m_axi_awburst[(2*(i+1))-1:2*i]), +      .m_axi_awlock   (m_axi_awlock[i]), +      .m_axi_awcache  (m_axi_awcache[(4*(i+1))-1:4*i]), +      .m_axi_awprot   (m_axi_awprot[(3*(i+1))-1:3*i]), +      .m_axi_awqos    (m_axi_awqos[(4*(i+1))-1:4*i]), +      .m_axi_awregion (m_axi_awregion[(4*(i+1))-1:4*i]), +      .m_axi_awuser   (m_axi_awuser[i]), +      .m_axi_awvalid  (m_axi_awvalid[i]), +      .m_axi_awready  (m_axi_awready[i]), +      // +      // AXI Write data channel. +      .m_axi_wdata    (m_axi_wdata[(MEM_DATA_W*(i+1))-1:MEM_DATA_W*i]), +      .m_axi_wstrb    (m_axi_wstrb[((MEM_DATA_W/8)*(i+1))-1:(MEM_DATA_W/8)*i]), +      .m_axi_wlast    (m_axi_wlast[i]), +      .m_axi_wuser    (m_axi_wuser[i]), +      .m_axi_wvalid   (m_axi_wvalid[i]), +      .m_axi_wready   (m_axi_wready[i]), +      // +      // AXI Write response channel signals +      .m_axi_bid      (m_axi_bid[i]), +      .m_axi_bresp    (m_axi_bresp[(2*(i+1))-1:2*i]), +      .m_axi_buser    (m_axi_buser[i]), +      .m_axi_bvalid   (m_axi_bvalid[i]), +      .m_axi_bready   (m_axi_bready[i]), +      // +      // AXI Read address channel +      .m_axi_arid     (m_axi_arid[i]), +      .m_axi_araddr   (m_axi_araddr_int), +      .m_axi_arlen    (m_axi_arlen[(8*(i+1))-1:8*i]), +      .m_axi_arsize   (m_axi_arsize[(3*(i+1))-1:3*i]), +      .m_axi_arburst  (m_axi_arburst[(2*(i+1))-1:2*i]), +      .m_axi_arlock   (m_axi_arlock[i]), +      .m_axi_arcache  (m_axi_arcache[(4*(i+1))-1:4*i]), +      .m_axi_arprot   (m_axi_arprot[(3*(i+1))-1:3*i]), +      .m_axi_arqos    (m_axi_arqos[(4*(i+1))-1:4*i]), +      .m_axi_arregion (m_axi_arregion[(4*(i+1))-1:4*i]), +      .m_axi_aruser   (m_axi_aruser[i]), +      .m_axi_arvalid  (m_axi_arvalid[i]), +      .m_axi_arready  (m_axi_arready[i]), +      // +      // AXI Read data channel +      .m_axi_rid      (m_axi_rid[i]), +      .m_axi_rdata    (m_axi_rdata[(MEM_DATA_W*(i+1))-1:MEM_DATA_W*i]), +      .m_axi_rresp    (m_axi_rresp[(2*(i+1))-1:2*i]), +      .m_axi_rlast    (m_axi_rlast[i]), +      .m_axi_ruser    (m_axi_ruser[i]), +      .m_axi_rvalid   (m_axi_rvalid[i]), +      .m_axi_rready   (m_axi_rready[i]) +    ); + +  end + +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/rfnoc_block_axi_ram_fifo_all_tb.sv b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/rfnoc_block_axi_ram_fifo_all_tb.sv new file mode 100644 index 000000000..575c600f9 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/rfnoc_block_axi_ram_fifo_all_tb.sv @@ -0,0 +1,70 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_axi_ram_fifo_all_tb +// +// Description: +// +//   This is the testbench for rfnoc_block_axi_ram_fifo that instantiates  +//   several variations of rfnoc_block_axi_ram_fifo_tb to test different  +//   configurations. +// + + +module rfnoc_block_axi_ram_fifo_all_tb; + +  timeunit 1ns; +  timeprecision 1ps; + +  import PkgTestExec::*; + + +  //--------------------------------------------------------------------------- +  // Test Definitions +  //--------------------------------------------------------------------------- + +  typedef struct { +    int CHDR_W; +    int NUM_PORTS; +    int MEM_DATA_W; +    int MEM_ADDR_W; +    int FIFO_ADDR_W; +    int IN_FIFO_SIZE; +    int OUT_FIFO_SIZE; +    bit OVERFLOW; +    bit BIST; +  } test_config_t; + +  localparam NUM_TESTS = 4; + +  localparam test_config_t test[NUM_TESTS] = '{ +    '{CHDR_W:  64, NUM_PORTS: 2, MEM_DATA_W:  64, MEM_ADDR_W: 13, FIFO_ADDR_W: 12, IN_FIFO_SIZE:  9, OUT_FIFO_SIZE:  9, OVERFLOW: 1, BIST: 1 }, +    '{CHDR_W:  64, NUM_PORTS: 1, MEM_DATA_W: 128, MEM_ADDR_W: 14, FIFO_ADDR_W: 13, IN_FIFO_SIZE:  9, OUT_FIFO_SIZE:  9, OVERFLOW: 1, BIST: 1 }, +    '{CHDR_W: 128, NUM_PORTS: 1, MEM_DATA_W:  64, MEM_ADDR_W: 13, FIFO_ADDR_W: 12, IN_FIFO_SIZE:  9, OUT_FIFO_SIZE: 10, OVERFLOW: 0, BIST: 1 }, +    '{CHDR_W: 128, NUM_PORTS: 1, MEM_DATA_W: 128, MEM_ADDR_W: 16, FIFO_ADDR_W: 14, IN_FIFO_SIZE: 12, OUT_FIFO_SIZE: 12, OVERFLOW: 0, BIST: 0 } +  }; + + +  //--------------------------------------------------------------------------- +  // DUT Instances +  //--------------------------------------------------------------------------- + +  genvar i; +  for (i = 0; i < NUM_TESTS; i++) begin : gen_test_config +    rfnoc_block_axi_ram_fifo_tb #( +      .CHDR_W        (test[i].CHDR_W), +      .NUM_PORTS     (test[i].NUM_PORTS), +      .MEM_DATA_W    (test[i].MEM_DATA_W), +      .MEM_ADDR_W    (test[i].MEM_ADDR_W), +      .FIFO_ADDR_W   (test[i].FIFO_ADDR_W), +      .IN_FIFO_SIZE  (test[i].IN_FIFO_SIZE), +      .OUT_FIFO_SIZE (test[i].OUT_FIFO_SIZE), +      .OVERFLOW      (test[i].OVERFLOW), +      .BIST          (test[i].BIST) +    ) rfnoc_block_radio_tb_i (); +  end : gen_test_config + + +endmodule : rfnoc_block_axi_ram_fifo_all_tb
\ No newline at end of file diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/rfnoc_block_axi_ram_fifo_tb.sv b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/rfnoc_block_axi_ram_fifo_tb.sv new file mode 100644 index 000000000..49e184ce0 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/rfnoc_block_axi_ram_fifo_tb.sv @@ -0,0 +1,1114 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_axi_ram_fifo_tb +// +// Description:  Testbench for rfnoc_block_axi_ram_fifo +// + + +module rfnoc_block_axi_ram_fifo_tb #( +  parameter int CHDR_W        = 64, +  parameter int NUM_PORTS     = 2, +  parameter int MEM_DATA_W    = 64, +  parameter int MEM_ADDR_W    = 13, +  parameter int FIFO_ADDR_W   = 12, +  parameter int IN_FIFO_SIZE  = 9, +  parameter int OUT_FIFO_SIZE = 9, +  parameter bit OVERFLOW      = 1, +  parameter bit BIST          = 1 +); + +  // Include macros and time declarations for use with PkgTestExec +  `include "test_exec.svh" + +  import PkgTestExec::*; +  import PkgChdrUtils::*; +  import PkgRfnocBlockCtrlBfm::*; + +  `include "axi_ram_fifo_regs.vh" + + +  //--------------------------------------------------------------------------- +  // Local Parameters +  //--------------------------------------------------------------------------- + +  // Simulation parameters +  localparam int  USE_RANDOM     = 1;        // Use random simulation data (not sequential) +  localparam real CHDR_CLK_PER   = 5.333333; // CHDR clock rate +  localparam real CTRL_CLK_PER   = 10.0;     // CTRL clock rate +  localparam real MEM_CLK_PER    = 3.333333; // Memory clock rate +  localparam int  SPP            = 256;      // Samples per packet +  localparam int  PKT_SIZE_BYTES = SPP*4;    // Bytes per packet +  localparam int  STALL_PROB     = 25;       // BFM stall probability + +  // Block configuration +  localparam int THIS_PORTID   = 'h123; +  localparam int MTU           = 12; +  localparam int NUM_HB        = 3; +  localparam int CIC_MAX_DECIM = 255; +  localparam int BURST_TIMEOUT = 64; +  localparam int MEM_CLK_RATE  = int'(1.0e9/MEM_CLK_PER);  // Frequency in Hz +  localparam int AWIDTH        = MEM_ADDR_W+1; + +  // Put FIFO 0 at the bottom of the memory and FIFO 1 immediately above it. +  localparam bit [MEM_ADDR_W-1:0] FIFO_ADDR_BASE_0 = 0; +  localparam bit [MEM_ADDR_W-1:0] FIFO_ADDR_BASE_1 = 2**FIFO_ADDR_W; +  localparam bit [MEM_ADDR_W-1:0] FIFO_ADDR_MASK   = 2**FIFO_ADDR_W-1; + + +  //--------------------------------------------------------------------------- +  // Clocks +  //--------------------------------------------------------------------------- + +  bit rfnoc_chdr_clk; +  bit rfnoc_ctrl_clk; +  bit mem_clk, mem_rst; + +  // Don't start the clocks automatically (AUTOSTART=0), since we expect +  // multiple instances of this testbench to run in sequence. They will be +  // started before the first test. +  sim_clock_gen #(.PERIOD(CHDR_CLK_PER), .AUTOSTART(0)) +    rfnoc_chdr_clk_gen (.clk(rfnoc_chdr_clk), .rst()); +  sim_clock_gen #(.PERIOD(CTRL_CLK_PER), .AUTOSTART(0)) +    rfnoc_ctrl_clk_gen (.clk(rfnoc_ctrl_clk), .rst()); +  sim_clock_gen #(.PERIOD(MEM_CLK_PER), .AUOSTART(0)) +    mem_clk_gen        (.clk(mem_clk),        .rst(mem_rst)); + + +  //--------------------------------------------------------------------------- +  // Bus Functional Models +  //--------------------------------------------------------------------------- + +  RfnocBackendIf        backend            (rfnoc_chdr_clk, rfnoc_ctrl_clk); +  AxiStreamIf #(32)     m_ctrl             (rfnoc_ctrl_clk, 1'b0); +  AxiStreamIf #(32)     s_ctrl             (rfnoc_ctrl_clk, 1'b0); +  AxiStreamIf #(CHDR_W) m_chdr [NUM_PORTS] (rfnoc_chdr_clk, 1'b0); +  AxiStreamIf #(CHDR_W) s_chdr [NUM_PORTS] (rfnoc_chdr_clk, 1'b0); + +  // Bus functional model for a software block controller +  RfnocBlockCtrlBfm #(.CHDR_W(CHDR_W)) blk_ctrl =  +    new(backend, m_ctrl, s_ctrl); + +  // Connect block controller to BFMs +  for (genvar i = 0; i < NUM_PORTS; i++) begin : gen_bfm_connections +    initial begin +      blk_ctrl.connect_master_data_port(i, m_chdr[i], PKT_SIZE_BYTES); +      blk_ctrl.connect_slave_data_port(i, s_chdr[i]); +      blk_ctrl.set_master_stall_prob(i, STALL_PROB); +      blk_ctrl.set_slave_stall_prob(i, STALL_PROB); +    end +  end + + +  //--------------------------------------------------------------------------- +  // AXI Memory Model +  //--------------------------------------------------------------------------- + +  // AXI Write Address Channel +  wire [         NUM_PORTS*1-1:0] m_axi_awid; +  wire [    NUM_PORTS*AWIDTH-1:0] m_axi_awaddr; +  wire [         NUM_PORTS*8-1:0] m_axi_awlen; +  wire [         NUM_PORTS*3-1:0] m_axi_awsize; +  wire [         NUM_PORTS*2-1:0] m_axi_awburst; +  wire [         NUM_PORTS*1-1:0] m_axi_awlock;   // Unused master output +  wire [         NUM_PORTS*4-1:0] m_axi_awcache;  // Unused master output +  wire [         NUM_PORTS*3-1:0] m_axi_awprot;   // Unused master output +  wire [         NUM_PORTS*4-1:0] m_axi_awqos;    // Unused master output +  wire [         NUM_PORTS*4-1:0] m_axi_awregion; // Unused master output +  wire [         NUM_PORTS*1-1:0] m_axi_awuser;   // Unused master output +  wire [         NUM_PORTS*1-1:0] m_axi_awvalid; +  wire [         NUM_PORTS*1-1:0] m_axi_awready; +  // AXI Write Data Channel +  wire [  NUM_PORTS*MEM_DATA_W-1:0] m_axi_wdata; +  wire [NUM_PORTS*MEM_DATA_W/8-1:0] m_axi_wstrb; +  wire [           NUM_PORTS*1-1:0] m_axi_wlast; +  wire [           NUM_PORTS*1-1:0] m_axi_wuser;  // Unused master output +  wire [           NUM_PORTS*1-1:0] m_axi_wvalid; +  wire [           NUM_PORTS*1-1:0] m_axi_wready; +  // AXI Write Response Channel +  wire [         NUM_PORTS*1-1:0] m_axi_bid; +  wire [         NUM_PORTS*2-1:0] m_axi_bresp; +  wire [         NUM_PORTS*1-1:0] m_axi_buser;  // Unused master input +  wire [         NUM_PORTS*1-1:0] m_axi_bvalid; +  wire [         NUM_PORTS*1-1:0] m_axi_bready; +  // AXI Read Address Channel +  wire [         NUM_PORTS*1-1:0] m_axi_arid; +  wire [    NUM_PORTS*AWIDTH-1:0] m_axi_araddr; +  wire [         NUM_PORTS*8-1:0] m_axi_arlen; +  wire [         NUM_PORTS*3-1:0] m_axi_arsize; +  wire [         NUM_PORTS*2-1:0] m_axi_arburst; +  wire [         NUM_PORTS*1-1:0] m_axi_arlock;   // Unused master output +  wire [         NUM_PORTS*4-1:0] m_axi_arcache;  // Unused master output +  wire [         NUM_PORTS*3-1:0] m_axi_arprot;   // Unused master output +  wire [         NUM_PORTS*4-1:0] m_axi_arqos;    // Unused master output +  wire [         NUM_PORTS*4-1:0] m_axi_arregion; // Unused master output +  wire [         NUM_PORTS*1-1:0] m_axi_aruser;   // Unused master output +  wire [         NUM_PORTS*1-1:0] m_axi_arvalid; +  wire [         NUM_PORTS*1-1:0] m_axi_arready; +  // AXI Read Data Channel +  wire [         NUM_PORTS*1-1:0] m_axi_rid; +  wire [NUM_PORTS*MEM_DATA_W-1:0] m_axi_rdata; +  wire [         NUM_PORTS*2-1:0] m_axi_rresp; +  wire [         NUM_PORTS*1-1:0] m_axi_rlast; +  wire [         NUM_PORTS*1-1:0] m_axi_ruser;  // Unused master input +  wire [         NUM_PORTS*1-1:0] m_axi_rvalid; +  wire [         NUM_PORTS*1-1:0] m_axi_rready; + +  // Unused master input signals +  assign m_axi_buser = {NUM_PORTS{1'b0}}; +  assign m_axi_ruser = {NUM_PORTS{1'b0}}; + +  for (genvar i = 0; i < NUM_PORTS; i = i+1) begin : gen_sim_axi_ram +    sim_axi_ram #( +      .AWIDTH     (AWIDTH), +      .DWIDTH     (MEM_DATA_W), +      .IDWIDTH    (1), +      .BIG_ENDIAN (0), +      .STALL_PROB (STALL_PROB) +    ) sim_axi_ram_i ( +      .s_aclk        (mem_clk), +      .s_aresetn     (~mem_rst), +      .s_axi_awid    (m_axi_awid[i]), +      .s_axi_awaddr  (m_axi_awaddr[i*AWIDTH +: AWIDTH]), +      .s_axi_awlen   (m_axi_awlen[i*8 +: 8]), +      .s_axi_awsize  (m_axi_awsize[i*3 +: 3]), +      .s_axi_awburst (m_axi_awburst[i*2 +: 2]), +      .s_axi_awvalid (m_axi_awvalid[i]), +      .s_axi_awready (m_axi_awready[i]), +      .s_axi_wdata   (m_axi_wdata[i*MEM_DATA_W +: MEM_DATA_W]), +      .s_axi_wstrb   (m_axi_wstrb[i*(MEM_DATA_W/8) +: (MEM_DATA_W/8)]), +      .s_axi_wlast   (m_axi_wlast[i]), +      .s_axi_wvalid  (m_axi_wvalid[i]), +      .s_axi_wready  (m_axi_wready[i]), +      .s_axi_bid     (m_axi_bid[i]), +      .s_axi_bresp   (m_axi_bresp[i*2 +: 2]), +      .s_axi_bvalid  (m_axi_bvalid[i]), +      .s_axi_bready  (m_axi_bready[i]), +      .s_axi_arid    (m_axi_arid[i]), +      .s_axi_araddr  (m_axi_araddr[i*AWIDTH +: AWIDTH]), +      .s_axi_arlen   (m_axi_arlen[i*8 +: 8]), +      .s_axi_arsize  (m_axi_arsize[i*3 +: 3]), +      .s_axi_arburst (m_axi_arburst[i*2 +: 2]), +      .s_axi_arvalid (m_axi_arvalid[i]), +      .s_axi_arready (m_axi_arready[i]), +      .s_axi_rid     (m_axi_rid[i]), +      .s_axi_rdata   (m_axi_rdata[i*MEM_DATA_W +: MEM_DATA_W]), +      .s_axi_rresp   (m_axi_rresp[i*2 +: 2]), +      .s_axi_rlast   (m_axi_rlast[i]), +      .s_axi_rvalid  (m_axi_rvalid[i]), +      .s_axi_rready  (m_axi_rready[i]) +    ); +  end + + +  //--------------------------------------------------------------------------- +  // DUT +  //--------------------------------------------------------------------------- + +  logic [NUM_PORTS*CHDR_W-1:0] s_rfnoc_chdr_tdata; +  logic [       NUM_PORTS-1:0] s_rfnoc_chdr_tlast; +  logic [       NUM_PORTS-1:0] s_rfnoc_chdr_tvalid; +  logic [       NUM_PORTS-1:0] s_rfnoc_chdr_tready; + +  logic [NUM_PORTS*CHDR_W-1:0] m_rfnoc_chdr_tdata; +  logic [       NUM_PORTS-1:0] m_rfnoc_chdr_tlast; +  logic [       NUM_PORTS-1:0] m_rfnoc_chdr_tvalid; +  logic [       NUM_PORTS-1:0] m_rfnoc_chdr_tready; + +  // Map the array of BFMs to a flat vector for the DUT +  for (genvar i = 0; i < NUM_PORTS; i++) begin : gen_dut_connections +    // Connect BFM master to DUT slave port +    assign s_rfnoc_chdr_tdata[CHDR_W*i+:CHDR_W] = m_chdr[i].tdata; +    assign s_rfnoc_chdr_tlast[i]                = m_chdr[i].tlast; +    assign s_rfnoc_chdr_tvalid[i]               = m_chdr[i].tvalid; +    assign m_chdr[i].tready                     = s_rfnoc_chdr_tready[i]; + +    // Connect BFM slave to DUT master port +    assign s_chdr[i].tdata        = m_rfnoc_chdr_tdata[CHDR_W*i+:CHDR_W]; +    assign s_chdr[i].tlast        = m_rfnoc_chdr_tlast[i]; +    assign s_chdr[i].tvalid       = m_rfnoc_chdr_tvalid[i]; +    assign m_rfnoc_chdr_tready[i] = s_chdr[i].tready; +  end + +  rfnoc_block_axi_ram_fifo #( +    .THIS_PORTID    (THIS_PORTID), +    .CHDR_W         (CHDR_W), +    .NUM_PORTS      (NUM_PORTS), +    .MTU            (MTU), +    .MEM_DATA_W     (MEM_DATA_W), +    .MEM_ADDR_W     (MEM_ADDR_W), +    .AWIDTH         (AWIDTH), +    .FIFO_ADDR_BASE ({ FIFO_ADDR_BASE_1, FIFO_ADDR_BASE_0 }), +    .FIFO_ADDR_MASK ({NUM_PORTS{FIFO_ADDR_MASK}}), +    .BURST_TIMEOUT  ({NUM_PORTS{BURST_TIMEOUT}}), +    .IN_FIFO_SIZE   (IN_FIFO_SIZE), +    .OUT_FIFO_SIZE  (OUT_FIFO_SIZE), +    .BIST           (BIST), +    .MEM_CLK_RATE   (MEM_CLK_RATE) +  ) rfnoc_block_axi_ram_fifo_i ( +    .rfnoc_chdr_clk      (rfnoc_chdr_clk), +    .s_rfnoc_chdr_tdata  (s_rfnoc_chdr_tdata), +    .s_rfnoc_chdr_tlast  (s_rfnoc_chdr_tlast), +    .s_rfnoc_chdr_tvalid (s_rfnoc_chdr_tvalid), +    .s_rfnoc_chdr_tready (s_rfnoc_chdr_tready), +    .m_rfnoc_chdr_tdata  (m_rfnoc_chdr_tdata), +    .m_rfnoc_chdr_tlast  (m_rfnoc_chdr_tlast), +    .m_rfnoc_chdr_tvalid (m_rfnoc_chdr_tvalid), +    .m_rfnoc_chdr_tready (m_rfnoc_chdr_tready), +    .rfnoc_core_config   (backend.cfg), +    .rfnoc_core_status   (backend.sts), +    .rfnoc_ctrl_clk      (rfnoc_ctrl_clk), +    .s_rfnoc_ctrl_tdata  (m_ctrl.tdata), +    .s_rfnoc_ctrl_tlast  (m_ctrl.tlast), +    .s_rfnoc_ctrl_tvalid (m_ctrl.tvalid), +    .s_rfnoc_ctrl_tready (m_ctrl.tready), +    .m_rfnoc_ctrl_tdata  (s_ctrl.tdata), +    .m_rfnoc_ctrl_tlast  (s_ctrl.tlast), +    .m_rfnoc_ctrl_tvalid (s_ctrl.tvalid), +    .m_rfnoc_ctrl_tready (s_ctrl.tready), +    .mem_clk             (mem_clk), +    .axi_rst             (mem_rst), +    .m_axi_awid          (m_axi_awid), +    .m_axi_awaddr        (m_axi_awaddr), +    .m_axi_awlen         (m_axi_awlen), +    .m_axi_awsize        (m_axi_awsize), +    .m_axi_awburst       (m_axi_awburst), +    .m_axi_awlock        (m_axi_awlock), +    .m_axi_awcache       (m_axi_awcache), +    .m_axi_awprot        (m_axi_awprot), +    .m_axi_awqos         (m_axi_awqos), +    .m_axi_awregion      (m_axi_awregion), +    .m_axi_awuser        (m_axi_awuser), +    .m_axi_awvalid       (m_axi_awvalid), +    .m_axi_awready       (m_axi_awready), +    .m_axi_wdata         (m_axi_wdata), +    .m_axi_wstrb         (m_axi_wstrb), +    .m_axi_wlast         (m_axi_wlast), +    .m_axi_wuser         (m_axi_wuser), +    .m_axi_wvalid        (m_axi_wvalid), +    .m_axi_wready        (m_axi_wready), +    .m_axi_bid           (m_axi_bid), +    .m_axi_bresp         (m_axi_bresp), +    .m_axi_buser         (m_axi_buser), +    .m_axi_bvalid        (m_axi_bvalid), +    .m_axi_bready        (m_axi_bready), +    .m_axi_arid          (m_axi_arid), +    .m_axi_araddr        (m_axi_araddr), +    .m_axi_arlen         (m_axi_arlen), +    .m_axi_arsize        (m_axi_arsize), +    .m_axi_arburst       (m_axi_arburst), +    .m_axi_arlock        (m_axi_arlock), +    .m_axi_arcache       (m_axi_arcache), +    .m_axi_arprot        (m_axi_arprot), +    .m_axi_arqos         (m_axi_arqos), +    .m_axi_arregion      (m_axi_arregion), +    .m_axi_aruser        (m_axi_aruser), +    .m_axi_arvalid       (m_axi_arvalid), +    .m_axi_arready       (m_axi_arready), +    .m_axi_rid           (m_axi_rid), +    .m_axi_rdata         (m_axi_rdata), +    .m_axi_rresp         (m_axi_rresp), +    .m_axi_rlast         (m_axi_rlast), +    .m_axi_ruser         (m_axi_ruser), +    .m_axi_rvalid        (m_axi_rvalid), +    .m_axi_rready        (m_axi_rready) +  ); + +  +  //--------------------------------------------------------------------------- +  // Helper Tasks +  //--------------------------------------------------------------------------- + +  task automatic write_reg(int port, bit [19:0] addr, bit [31:0] value); +    blk_ctrl.reg_write((2**RAM_FIFO_ADDR_W)*port + addr, value); +  endtask : write_reg + +  task automatic write_reg_64(int port, bit [19:0] addr, bit [63:0] value); +    blk_ctrl.reg_write((2**RAM_FIFO_ADDR_W)*port + addr + 0, value[31: 0]); +    blk_ctrl.reg_write((2**RAM_FIFO_ADDR_W)*port + addr + 4, value[63:32]); +  endtask : write_reg_64 + +  task automatic read_reg(int port, bit [19:0] addr, output logic [63:0] value); +    blk_ctrl.reg_read((2**RAM_FIFO_ADDR_W)*port + addr, value[31: 0]); +  endtask : read_reg + +  task automatic read_reg_64(int port, bit [19:0] addr, output logic [63:0] value); +    blk_ctrl.reg_read((2**RAM_FIFO_ADDR_W)*port + addr + 0, value[31: 0]); +    blk_ctrl.reg_read((2**RAM_FIFO_ADDR_W)*port + addr + 4, value[63:32]); +  endtask : read_reg_64 + + +  // Generate a unique sequence of incrementing numbers +  task automatic gen_test_data(int num_bytes, output chdr_word_t queue[$]); +    int             num_chdr_words; +    chdr_word_t     val64; + +    // Calculate the number of chdr_word_t size words +    num_chdr_words = int'($ceil(real'(num_bytes) / ($bits(chdr_word_t) / 8))); + +    for (int i = 0; i < num_chdr_words; i++) begin +      if (USE_RANDOM) begin +        val64 = { $urandom(), $urandom() };   // Random data, for more rigorous testing +      end else begin +        val64 = i;                            // Sequential data, for easier debugging +      end +      queue.push_back(val64); +    end +  endtask : gen_test_data + + +  //--------------------------------------------------------------------------- +  // Reset +  //--------------------------------------------------------------------------- +   +  task test_reset(); +    test.start_test("Wait for Reset", 10us); +    mem_clk_gen.reset(); +    blk_ctrl.flush_and_reset(); +    wait(!mem_rst); +    test.end_test(); +  endtask : test_reset + + +  //--------------------------------------------------------------------------- +  // Check NoC ID and Block Info +  //--------------------------------------------------------------------------- +   +  task test_block_info(); +    test.start_test("Verify Block Info", 2us); +    `ASSERT_ERROR(blk_ctrl.get_noc_id() == rfnoc_block_axi_ram_fifo_i.NOC_ID, "Incorrect NOC_ID Value"); +    `ASSERT_ERROR(blk_ctrl.get_num_data_i() == NUM_PORTS, "Incorrect NUM_DATA_I Value"); +    `ASSERT_ERROR(blk_ctrl.get_num_data_o() == NUM_PORTS, "Incorrect NUM_DATA_O Value"); +    `ASSERT_ERROR(blk_ctrl.get_mtu() == MTU, "Incorrect MTU Value"); +    test.end_test(); +  endtask : test_block_info + + +  //--------------------------------------------------------------------------- +  // Check Unused Signals +  //--------------------------------------------------------------------------- + +  task test_unused(); +    test.start_test("Check unused/static signals"); +    for (int port = 0; port < NUM_PORTS; port++) begin +      `ASSERT_ERROR(m_axi_awlock   [port*1 +: 1] == 1'b0, "m_axi_awlock value unexpected"); +      `ASSERT_ERROR(m_axi_awcache  [port*4 +: 4] == 4'hF, "m_axi_awcache value unexpected"); +      `ASSERT_ERROR(m_axi_awprot   [port*3 +: 3] == 3'h2, "m_axi_awprot value unexpected"); +      `ASSERT_ERROR(m_axi_awqos    [port*4 +: 4] == 4'h0, "m_axi_awqos value unexpected"); +      `ASSERT_ERROR(m_axi_awregion [port*4 +: 4] == 4'h0, "m_axi_awregion value unexpected"); +      `ASSERT_ERROR(m_axi_awuser   [port*1 +: 1] == 1'b0, "m_axi_awuser value unexpected"); +      // +      `ASSERT_ERROR(m_axi_wuser    [port*1 +: 1] == 1'b0, "m_axi_wuser value unexpected"); +      // +      `ASSERT_ERROR(m_axi_arlock   [port*1 +: 1] == 1'b0, "m_axi_arlock value unexpected"); +      `ASSERT_ERROR(m_axi_arcache  [port*4 +: 4] == 4'hF, "m_axi_arcache value unexpected"); +      `ASSERT_ERROR(m_axi_arprot   [port*3 +: 3] == 3'h2, "m_axi_arprot value unexpected"); +      `ASSERT_ERROR(m_axi_arqos    [port*4 +: 4] == 4'h0, "m_axi_arqos value unexpected"); +      `ASSERT_ERROR(m_axi_arregion [port*4 +: 4] == 4'h0, "m_axi_arregion value unexpected"); +      `ASSERT_ERROR(m_axi_aruser   [port*1 +: 1] == 1'b0, "m_axi_aruser value unexpected"); +    end +    test.end_test(); +  endtask : test_unused + + +  //--------------------------------------------------------------------------- +  // Test Registers +  //--------------------------------------------------------------------------- + +  task test_registers(); +    logic [63:0] val64, expected64, temp64; +    logic [31:0] val32, expected32, temp32; + +    test.start_test("Test registers", 50us); + +    for (int port = 0; port < NUM_PORTS; port++) begin +      // +      // REG_FIFO_INFO +      // +      expected32 = 0; +      expected32[REG_FIFO_MAGIC_POS +: REG_FIFO_MAGIC_W] = 16'hF1F0; +      expected32[REG_FIFO_BIST_PRSNT_POS] = (BIST != 0); +      read_reg(port, REG_FIFO_INFO, val32); +      `ASSERT_ERROR(val32 == expected32, "Initial value for REG_FIFO_INFO is not correct"); + +      // +      // REG_FIFO_READ_SUPPRESS +      // +      expected32 = 0; +      expected32[REG_FIFO_IN_FIFO_SIZE_POS+:REG_FIFO_IN_FIFO_SIZE_W] = IN_FIFO_SIZE; +      expected32[REG_FIFO_SUPPRESS_THRESH_POS+:REG_FIFO_SUPPRESS_THRESH_W] = 0; +      read_reg(port, REG_FIFO_READ_SUPPRESS, val32); +      `ASSERT_ERROR(val32 == expected32, "Initial value for REG_FIFO_READ_SUPPRESS is not correct"); + +      temp32 = expected32; +      expected32[REG_FIFO_SUPPRESS_THRESH_POS+:REG_FIFO_SUPPRESS_THRESH_W] =  +        ~val32[REG_FIFO_SUPPRESS_THRESH_POS+:REG_FIFO_SUPPRESS_THRESH_W]; +      write_reg(port, REG_FIFO_READ_SUPPRESS, expected32); +      read_reg(port, REG_FIFO_READ_SUPPRESS, val32); +      `ASSERT_ERROR(val32 == expected32, "REG_FIFO_READ_SUPPRESS did not update"); + +      expected32 = temp32; +      write_reg(port, REG_FIFO_READ_SUPPRESS, expected32); +      read_reg(port, REG_FIFO_READ_SUPPRESS, val32); +      `ASSERT_ERROR(val32 == expected32, "REG_FIFO_READ_SUPPRESS did not reset"); + +      // +      // REG_FIFO_MEM_SIZE +      // +      expected32 = 0; +      expected32[REG_FIFO_DATA_SIZE_POS +: REG_FIFO_DATA_SIZE_W] = MEM_DATA_W; +      expected32[REG_FIFO_ADDR_SIZE_POS +: REG_FIFO_ADDR_SIZE_W] = MEM_ADDR_W; +      read_reg(port, REG_FIFO_MEM_SIZE, val32); +      `ASSERT_ERROR(val32 == expected32, "Incorrect REG_FIFO_MEM_SIZE value!"); + +      // +      // REG_FIFO_TIMEOUT +      // +      expected32 = BURST_TIMEOUT; +      read_reg(port, REG_FIFO_TIMEOUT, val32); +      `ASSERT_ERROR(val32 == expected32, "Initial value for REG_FIFO_TIMEOUT is not correct"); + +      write_reg(port, REG_FIFO_TIMEOUT, {REG_TIMEOUT_W{1'b1}}); +      read_reg(port, REG_FIFO_TIMEOUT, val32); +      `ASSERT_ERROR(val32 == {REG_TIMEOUT_W{1'b1}}, "REG_FIFO_TIMEOUT did not update"); + +      write_reg(port, REG_FIFO_TIMEOUT, expected32); +      read_reg(port, REG_FIFO_TIMEOUT, val32); +      `ASSERT_ERROR(val32 == expected32, "REG_FIFO_TIMEOUT did not reset"); + +      // +      // REG_FIFO_FULLNESS +      // +      read_reg_64(port, REG_FIFO_FULLNESS_LO, val64); +      `ASSERT_ERROR(val64 == 0, "Incorrect REG_FIFO_FULLNESS value!"); + +      // +      // REG_FIFO_ADDR_BASE +      // +      expected64 = port * 2**FIFO_ADDR_W; +      read_reg_64(port, REG_FIFO_ADDR_BASE_LO, val64); +      `ASSERT_ERROR(val64 == expected64, "Initial value for REG_FIFO_ADDR_BASE is not correct"); + +      write_reg_64(port, REG_FIFO_ADDR_BASE_LO, {MEM_ADDR_W{1'b1}}); +      read_reg_64(port, REG_FIFO_ADDR_BASE_LO, val64); +      `ASSERT_ERROR(val64 == {MEM_ADDR_W{1'b1}}, "REG_FIFO_ADDR_BASE did not update"); + +      write_reg_64(port, REG_FIFO_ADDR_BASE_LO, expected64); +      read_reg_64(port, REG_FIFO_ADDR_BASE_LO, val64); +      `ASSERT_ERROR(val64 == expected64, "REG_FIFO_ADDR_BASE did not reset"); + +      // +      // REG_FIFO_ADDR_MASK +      // +      expected64 = {FIFO_ADDR_W{1'b1}}; +      read_reg_64(port, REG_FIFO_ADDR_MASK_LO, val64); +      `ASSERT_ERROR(val64 == expected64, "Initial value for REG_FIFO_ADDR_MASK_LO is not correct"); + +      // Set to the max value +      write_reg_64(port, REG_FIFO_ADDR_MASK_LO, {MEM_ADDR_W{1'b1}}); +      read_reg_64(port, REG_FIFO_ADDR_MASK_LO, val64); +      `ASSERT_ERROR(val64 == {MEM_ADDR_W{1'b1}}, "REG_FIFO_ADDR_MASK_LO did not update"); + +      // Set to the min value +      write_reg_64(port, REG_FIFO_ADDR_MASK_LO, 0); +      read_reg_64(port, REG_FIFO_ADDR_MASK_LO, val64); +      // Coerce to the minimum allowed value +      temp64 = rfnoc_block_axi_ram_fifo_i.gen_ram_fifos[0].axi_ram_fifo_i.FIFO_ADDR_MASK_MIN; +      `ASSERT_ERROR(val64 == temp64, "REG_FIFO_ADDR_MASK_LO did not update"); + +      write_reg_64(port, REG_FIFO_ADDR_MASK_LO, expected64); +      read_reg_64(port, REG_FIFO_ADDR_MASK_LO, val64); +      `ASSERT_ERROR(val64 == expected64, "REG_FIFO_ADDR_MASK_LO did not reset"); + +      // +      // REG_FIFO_PACKET_CNT +      // +      read_reg(port, REG_FIFO_PACKET_CNT, val32); +      `ASSERT_ERROR(val32 == 0, "Incorrect REG_FIFO_PACKET_CNT value!"); + +      if (BIST) begin +        read_reg(port, REG_BIST_CTRL, val32); +        `ASSERT_ERROR(val32 == 0, "Initial value for REG_BIST_CTRL is not correct"); +        read_reg(port, REG_BIST_CLK_RATE, val32); +        `ASSERT_ERROR(val32 == MEM_CLK_RATE, "Initial value for REG_BIST_CLK_RATE is not correct"); +        read_reg_64(port, REG_BIST_NUM_BYTES_LO, val64); +        `ASSERT_ERROR(val64 == 0, "Initial value for REG_BIST_NUM_BYTES is not correct"); +        read_reg_64(port, REG_BIST_TX_BYTE_COUNT_LO, val64); +        `ASSERT_ERROR(val64 == 0, "Initial value for REG_BIST_TX_BYTE_COUNT is not correct"); +        read_reg_64(port, REG_BIST_RX_BYTE_COUNT_LO, val64); +        `ASSERT_ERROR(val64 == 0, "Initial value for REG_BIST_RX_BYTE_COUNT is not correct"); +        read_reg_64(port, REG_BIST_ERROR_COUNT_LO, val64); +        `ASSERT_ERROR(val64 == 0, "Initial value for REG_BIST_ERROR_COUNT is not correct"); +        read_reg_64(port, REG_BIST_CYCLE_COUNT_LO, val64); +        `ASSERT_ERROR(val64 == 0, "Initial value for REG_BIST_CYCLE_COUNT is not correct"); +      end +    end + +    test.end_test(); +  endtask : test_registers + + +  //--------------------------------------------------------------------------- +  // Basic Test +  //--------------------------------------------------------------------------- +  // +  // Push a few packets through each FIFO. +  // +  //--------------------------------------------------------------------------- + +  task test_basic(); +    logic [31:0] val32; + +    test.start_test("Basic test", NUM_PORTS*20us); + +    for (int port = 0; port < NUM_PORTS; port++) begin +      chdr_word_t test_data[$]; +      logic [63:0] val64; +      timeout_t timeout; + +      // Generate the test data to send +      gen_test_data(PKT_SIZE_BYTES*3, test_data); + +      // Queue up the packets to send +      blk_ctrl.send_packets(port, test_data); + +      // Make sure fullness increases +      test.start_timeout(timeout, 4us,  +        $sformatf("Waiting for fullness to increase on port %0d", port)); +      forever begin +        read_reg_64(port, REG_FIFO_FULLNESS_LO, val64); +        if (val64 != 0) break; +      end +      test.end_timeout(timeout); + +      // Verify the data, one packet at a time +      for (int count = 0; count < test_data.size(); ) begin +        chdr_word_t recv_data[$]; +        int data_bytes; +        blk_ctrl.recv(port, recv_data, data_bytes); + +        `ASSERT_ERROR( +          data_bytes == PKT_SIZE_BYTES,  +          "Length didn't match expected value" +        ); + +        for (int i = 0; i < recv_data.size(); i++, count++) begin +          if (recv_data[i] != test_data[count]) begin +            $display("Expected %X, received %X on port %0d", test_data[count], recv_data[i], port); +          end +          `ASSERT_ERROR( +            recv_data[i] == test_data[count],  +            "Received data doesn't match expected value" +          ); +        end +      end + +      // Make sure the packet count updated +      read_reg(port, REG_FIFO_PACKET_CNT, val32); +      `ASSERT_ERROR(val32 > 0, "REG_FIFO_PACKET_CNT didn't update"); +    end + +    test.end_test(); +  endtask : test_basic + + +  //--------------------------------------------------------------------------- +  // Single Byte Test +  //--------------------------------------------------------------------------- + +  task test_single_byte(); +    test.start_test("Single byte test", 20us); + +    for (int port = 0; port < NUM_PORTS; port++) begin +      chdr_word_t test_data[$]; +      chdr_word_t recv_data[$]; +      int data_bytes; + +      gen_test_data(1, test_data); + +      blk_ctrl.send(port, test_data, 1); +      blk_ctrl.recv(port, recv_data, data_bytes); + +      `ASSERT_ERROR( +        data_bytes == 1 && recv_data.size() == CHDR_W/$bits(chdr_word_t),  +        "Length didn't match expected value" +      ); +      `ASSERT_ERROR( +        recv_data[0][7:0] == test_data[0][7:0],  +        "Received data doesn't match expected value" +      ); +    end + +    test.end_test(); +  endtask : test_single_byte + + +  //--------------------------------------------------------------------------- +  // Test Overflow +  //--------------------------------------------------------------------------- +  //  +  // Fill the FIFO on both ports to make sure if fills correctly and flow  +  // control works correct at the limits. +  // +  //--------------------------------------------------------------------------- + +  task test_overflow(); +    chdr_word_t test_data[NUM_PORTS][$]; +    int num_bytes, num_words; +    bit [NUM_PORTS-1:0] full_bits; +    logic [63:0] val64; +    timeout_t timeout; +    realtime start_time; + +    if (!OVERFLOW) return; + +    num_bytes = (MEM_DATA_W/8) * (2**IN_FIFO_SIZE + 2**OUT_FIFO_SIZE) + 2**MEM_ADDR_W; +    num_bytes = num_bytes * 3 / 2; +    num_words = num_bytes / (CHDR_W/8); + +    test.start_test("Overflow test", 10 * num_words * CHDR_CLK_PER); + +    // Stall the output of each FIFO, allow unrestricted input +    for (int port = 0; port < NUM_PORTS; port++) begin +      blk_ctrl.set_master_stall_prob(port, 0); +      blk_ctrl.set_slave_stall_prob(port, 100); +    end + +    // Input more packets into each FIFO than they can fit +    for (int port = 0; port < NUM_PORTS; port++) begin +      gen_test_data(num_bytes, test_data[port]); +      blk_ctrl.send_packets(port, test_data[port]); +    end + +    // Wait for both inputs to stall +    test.start_timeout(timeout, (4 * num_words + 1000) * CHDR_CLK_PER,  +      $sformatf("Waiting for input to stall")); +    full_bits = 0; +    forever begin +      for (int port = 0; port < NUM_PORTS; port++) begin +        full_bits[port] = ~s_rfnoc_chdr_tready[port]; +        if (!full_bits[port]) start_time = $realtime; +      end + +      // Break as soon as all FIFOs have been stalled for 1000 clock cycles +      if (full_bits == {NUM_PORTS{1'b1}} && $realtime-start_time > 1000 * CHDR_CLK_PER) break; +      #(CHDR_CLK_PER*100); +    end +    test.end_timeout(timeout); + +    // Make sure all the FIFOs filled up +    for (int port = 0; port < NUM_PORTS; port++) begin +      read_reg_64(port, REG_FIFO_FULLNESS_LO, val64); +      // FIFO is full once it comes within 256 words of being full +      `ASSERT_ERROR(val64 >= (2**FIFO_ADDR_W / (MEM_DATA_W/8)) - 256, "FIFO not reading full"); +    end + +    // Restore the input/output rates +    for (int port = 0; port < NUM_PORTS; port++) begin +      blk_ctrl.set_master_stall_prob(port, STALL_PROB); +      blk_ctrl.set_slave_stall_prob(port, STALL_PROB); +    end + +    // Read out and verify the data +    for (int port = 0; port < NUM_PORTS; port++) begin +      for (int count = 0; count < test_data[port].size(); ) begin +        chdr_word_t recv_data[$]; +        int data_bytes; +        int expected_length; +        blk_ctrl.recv(port, recv_data, data_bytes); + +        if (count*($bits(chdr_word_t)/8) + PKT_SIZE_BYTES <= num_bytes) begin +          // Should be a full packet +          expected_length = PKT_SIZE_BYTES; +        end else begin +          // Should be a partial packet +          expected_length = num_bytes % PKT_SIZE_BYTES; +        end + +        // Check the length +        `ASSERT_ERROR( +          data_bytes == expected_length,  +          "Length didn't match expected value" +        ); + +        for (int i = 0; i < recv_data.size(); i++, count++) begin +          `ASSERT_ERROR( +            recv_data[i] == test_data[port][count],  +            "Received data doesn't match expected value" +          ); +        end +      end +    end + +    test.end_test(); +  endtask : test_overflow + + +  //--------------------------------------------------------------------------- +  // Test Read Suppression +  //--------------------------------------------------------------------------- + +  task test_read_suppression(); +    chdr_word_t test_data[$]; +    logic [31:0] val32, save32; +    int port; + +    test.start_test("Read suppression test", 100us); + +    port = 0; // Only test one port + +    // Turn on read suppression with the max threshold to cause it to  +    // suppress everything. +    read_reg(port, REG_FIFO_READ_SUPPRESS, save32); +    val32 = save32; +    val32[REG_FIFO_SUPPRESS_THRESH_POS +: REG_FIFO_SUPPRESS_THRESH_W] = {REG_FIFO_SUPPRESS_THRESH_W{1'b1}}; +    write_reg(port, REG_FIFO_READ_SUPPRESS, val32); + +    // Generate the test data to send (send 8 RAM bursts) +    gen_test_data(MEM_DATA_W/8 * 256 * 8, test_data); + +    // Start sending packets then wait for the input to stall, either because  +    // we've filled the FIFO or we've input everything. +    blk_ctrl.set_master_stall_prob(port, 0); +    blk_ctrl.send_packets(port, test_data); +    wait (s_rfnoc_chdr_tvalid && s_rfnoc_chdr_tready); +    wait (!s_rfnoc_chdr_tvalid || !s_rfnoc_chdr_tready); + +    // Make sure nothing made it through +    `ASSERT_ERROR(blk_ctrl.num_received(port) == 0, "Read suppression failed"); + +    // Turn down the threshold  +    val32[REG_FIFO_SUPPRESS_THRESH_POS +: REG_FIFO_SUPPRESS_THRESH_W] = {REG_FIFO_SUPPRESS_THRESH_W{1'b0}}; +    write_reg(port, REG_FIFO_READ_SUPPRESS, val32); + +    blk_ctrl.set_master_stall_prob(port, STALL_PROB); + +    // Verify the data, one packet at a time +    for (int count = 0; count < test_data.size(); ) begin +      chdr_word_t recv_data[$]; +      int data_bytes; +      blk_ctrl.recv(port, recv_data, data_bytes); + +      for (int i = 0; i < recv_data.size(); i++, count++) begin +        if (recv_data[i] != test_data[count]) begin +          $display("Expected %X, received %X on port %0d", test_data[count], recv_data[i], port); +        end +        `ASSERT_ERROR( +          recv_data[i] == test_data[count],  +          "Received data doesn't match expected value" +        ); +      end +    end + +    // Restore suppression settings +    write_reg(port, REG_FIFO_READ_SUPPRESS, save32); + +    test.end_test(); +  endtask : test_read_suppression + + +  //--------------------------------------------------------------------------- +  // Random Tests +  //--------------------------------------------------------------------------- +  // +  // Perform a series of random tests with different read/write probabilities  +  // test unexpected conditions. +  // +  //--------------------------------------------------------------------------- + +  class RandTrans; +    chdr_word_t data[$]; +    int         num_bytes; +  endclass; + +  task test_random(); +    localparam NUM_PACKETS = 256; + +    mailbox #(RandTrans) data_queue; +    int port; + +    test.start_test("Random test", NUM_PACKETS * 2us); + +    data_queue = new(); +    port       = 0;         // Just check one port for this test + +    // Queue up a bunch of random packets +    begin : data_gen +      RandTrans trans; +      $display("Generating %0d random packets...", NUM_PACKETS); + +      for (int packet_count = 0; packet_count < NUM_PACKETS; packet_count++) begin +        trans = new(); +        trans.num_bytes = $urandom_range(1, PKT_SIZE_BYTES); +        gen_test_data(trans.num_bytes, trans.data); +        blk_ctrl.send(port, trans.data, trans.num_bytes); +        data_queue.put(trans); +      end +    end + +    // Receive and check all the packets +    fork +      begin : stall_update +        // Split the packets into four groups and use different stall  +        // behavior for each.  +        //  +        //   1. Start filling up the FIFO +        //   2. Let it run for a while +        //   3. Start emptying the FIFO +        //   4. Let it run until all the data gets through +        // +        for (int i = 0; i < 4; i++) begin +          case (i) +            0 : begin +              $display("Test fast writer, slow reader"); +              blk_ctrl.set_master_stall_prob(port, 10); +              blk_ctrl.set_slave_stall_prob(port, 80); +            end +            1 : begin +              $display("Test matched reader/writer"); +              blk_ctrl.set_master_stall_prob(port, STALL_PROB); +              blk_ctrl.set_slave_stall_prob(port, STALL_PROB); +            end +            2 : begin +              $display("Test slow writer, fast reader"); +              blk_ctrl.set_master_stall_prob(port, 90); +              blk_ctrl.set_slave_stall_prob(port, 10); +            end +            3 : begin +              $display("Test matched reader/writer"); +              blk_ctrl.set_master_stall_prob(port, STALL_PROB); +              blk_ctrl.set_slave_stall_prob(port, STALL_PROB); +            end +          endcase + +          // Wait for a quarter of the packets to be accepted by the RAM FIFO +          blk_ctrl.wait_complete(port, NUM_PACKETS/4); +        end +      end +      begin : data_check +        RandTrans   trans; +        chdr_word_t recv_data[$]; +        int         num_bytes; +        int         num_words; + + +        for (int packet_count = 0; packet_count < NUM_PACKETS; packet_count++) begin +          //$display("Checking packet %0d/%0d...", packet_count, NUM_PACKETS); + +          blk_ctrl.recv(port, recv_data, num_bytes); +          data_queue.get(trans); + +          // Check the length +          `ASSERT_ERROR( +            num_bytes == trans.num_bytes,  +            "Length didn't match expected value" +          ); + +          // If the generated data was an odd number of chdr_word_t words, we  +          // will get back an extra 0 word at the end. Calculate the correct  +          // number of words so that we ignore any extra at the end. +          num_words = int'($ceil(real'(num_bytes)/($bits(chdr_word_t)/8))); +          for (int i = 0; i < num_words; i++) begin +            `ASSERT_ERROR( +              recv_data[i] == trans.data[i], +              "Received data doesn't match expected value" +            ); +          end +        end +      end +    join + +    test.end_test(); +  endtask : test_random + + +  //--------------------------------------------------------------------------- +  // Test Clearing FIFO Block +  //--------------------------------------------------------------------------- + +  task test_clear(); +   test.start_test("FIFO clear test", 100us); + +   // TODO: +   $warning("Need to write a test flushing and resetting the block!"); + +   test.end_test(); +  endtask : test_clear + + +  //--------------------------------------------------------------------------- +  // Test BIST +  //--------------------------------------------------------------------------- + +  task test_bist(); +    logic [31:0] val32; +    logic [63:0] val64; +    int port; +    int num_bytes; + +    if (!BIST) return; + +    test.start_test("BIST test", 100us); + +    port = 0;  // Test the first port +    num_bytes = 2048; + +    // Start a test +    write_reg(port, REG_BIST_CTRL, 1 << REG_BIST_CLEAR_POS); +    write_reg(port, REG_BIST_NUM_BYTES_LO, num_bytes); +    write_reg(port, REG_BIST_CTRL, 1 << REG_BIST_START_POS); + +    // Make sure running bit gets set +    read_reg(port, REG_BIST_CTRL, val32); +    `ASSERT_ERROR(val32[REG_BIST_RUNNING_POS] == 1'b1, "RUNNING bit not set"); + +    // Wait for the test to complete +    do begin +      read_reg(port, REG_BIST_CTRL, val32); +    end while(val32[REG_BIST_RUNNING_POS]); + +    // Check the results +    read_reg_64(port, REG_BIST_TX_BYTE_COUNT_LO, val64); +    `ASSERT_ERROR(val64 == num_bytes, "TX_BYTE_COUNT is not correct"); +    read_reg_64(port, REG_BIST_RX_BYTE_COUNT_LO, val64); +    `ASSERT_ERROR(val64 == num_bytes, "RX_BYTE_COUNT is not correct"); +    read_reg_64(port, REG_BIST_ERROR_COUNT_LO, val64); +    `ASSERT_ERROR(val64 == 0, "ERROR_COUNT is not zero"); +    read_reg_64(port, REG_BIST_CYCLE_COUNT_LO, val64); +    `ASSERT_ERROR(val64 > 0, "CYCLE_COUNT did not update"); + +    // TODO: +    $warning("BIST Continuous mode is NOT being tested"); +    $warning("BIST error insertion is NOT being tested (errors might be ignored)"); + +    test.end_test(); +  endtask : test_bist + + +  //--------------------------------------------------------------------------- +  // BIST Throughput Test +  //--------------------------------------------------------------------------- +  // +  // This test sanity-checks the values returned by the BIST. If run with the +  // other BIST test, it also tests clearing the BIST counters. +  // +  //--------------------------------------------------------------------------- + +  task test_bist_throughput(); +    localparam   port = 0;  // Test the first port +    logic [31:0] val32; +    logic [63:0] val64; +    int          num_bytes; +    longint      rx_byte_count; +    longint      cycle_count; +    real         throughput; +    real         efficiency; + +    if (!BIST) return; + +    test.start_test("BIST throughput test", 100us); + +    num_bytes = 64*1024; + +    // Reset the memory probability +    gen_sim_axi_ram[port].sim_axi_ram_i.set_stall_prob(0); + +    // Start a test +    write_reg(port, REG_BIST_CTRL, 1 << REG_BIST_CLEAR_POS); +    write_reg(port, REG_BIST_NUM_BYTES_LO, num_bytes); +    write_reg(port, REG_BIST_CTRL, 1 << REG_BIST_START_POS); + +    // Make sure running bit gets set +    read_reg(port, REG_BIST_CTRL, val32); +    `ASSERT_ERROR(val32[REG_BIST_RUNNING_POS] == 1'b1, "RUNNING bit not set"); + +    // Wait for the test to complete +    do begin +      read_reg(port, REG_BIST_CTRL, val32); +    end while(val32[REG_BIST_RUNNING_POS]); + +    // Check the results +    read_reg_64(port, REG_BIST_TX_BYTE_COUNT_LO, val64); +    `ASSERT_ERROR(val64 == num_bytes, "TX_BYTE_COUNT is not correct"); +    read_reg_64(port, REG_BIST_RX_BYTE_COUNT_LO, rx_byte_count); +    `ASSERT_ERROR(rx_byte_count == num_bytes, "RX_BYTE_COUNT is not correct"); +    read_reg_64(port, REG_BIST_ERROR_COUNT_LO, val64); +    `ASSERT_ERROR(val64 == 0, "ERROR_COUNT is not zero"); +    read_reg_64(port, REG_BIST_CYCLE_COUNT_LO, cycle_count); +    `ASSERT_ERROR(cycle_count > 0, "CYCLE_COUNT did not update"); + +    // Throughput = num_bytes / time = num_bytes / (num_cyles * period) +    throughput = real'(rx_byte_count) / (real'(cycle_count) / real'(MEM_CLK_RATE)); + +    // Efficiency is the actual throughput divided by the theoretical max. We +    // use 0.5x in the calculation because we assume that the memory is a +    // half-duplex read/write memory running at MEM_CLK_RATE, but we're +    // measuring the full-duplex throughput. +    efficiency = throughput / (0.5 * real'(MEM_CLK_RATE) * (MEM_DATA_W/8)); + +    $display("BIST Throughput: %0.1f MB/s", throughput / 1.0e6); +    $display("BIST Efficiency: %0.1f %%", efficiency * 100.0 ); + +    `ASSERT_ERROR(efficiency > 0.95, "BIST efficiency was lower than expected"); + +    // Restore the memory stall probability +    gen_sim_axi_ram[port].sim_axi_ram_i.set_stall_prob(STALL_PROB); + +    test.end_test(); +  endtask; + + +  //--------------------------------------------------------------------------- +  // Main Test Process +  //--------------------------------------------------------------------------- + +  initial begin : tb_main +    const int port = 0; +    string tb_name; + +    // Generate a string for the name of this instance of the testbench +    tb_name = $sformatf( +      "rfnoc_block_axi_ram_fifo_tb\nCHDR_W = %0D, NUM_PORTS = %0D, MEM_DATA_W = %0D, MEM_ADDR_W = %0D, FIFO_ADDR_W = %0D, IN_FIFO_SIZE = %0D, OUT_FIFO_SIZE = %0D, OVERFLOW = %0D, BIST = %0D", +      CHDR_W, NUM_PORTS, MEM_DATA_W, MEM_ADDR_W, FIFO_ADDR_W, IN_FIFO_SIZE, OUT_FIFO_SIZE, OVERFLOW, BIST +    ); +    test.start_tb(tb_name); + +    // 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. +    rfnoc_chdr_clk_gen.start(); +    rfnoc_ctrl_clk_gen.start(); +    mem_clk_gen.start(); + +    // Start the BFMs running +    blk_ctrl.run(); + +    // +    // Run test procedures +    // +    test_reset(); +    test_block_info(); +    test_unused(); +    test_registers(); +    test_basic(); +    test_single_byte(); +    test_overflow(); +    test_read_suppression(); +    test_random(); +    test_clear(); +    test_bist(); +    test_bist_throughput(); + +    // 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 +    rfnoc_chdr_clk_gen.kill(); +    rfnoc_ctrl_clk_gen.kill(); +    mem_clk_gen.kill(); +  end +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/sim_axi_ram.sv b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/sim_axi_ram.sv new file mode 100644 index 000000000..ee7ff5df8 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/sim_axi_ram.sv @@ -0,0 +1,637 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: sim_axi_ram +// +// Description:  +// +//   Simulation model for a basic AXI4 memory mapped memory. A few notes on its  +//   behavior: +// +//     - This model does not reorder requests (regardless of WID/RID). All  +//       requests are evaluated strictly in order. +//     - The only supported response is OKAY +//     - This model supports misaligned memory accesses, which cause a  +//       simulation warning. +//     - A reset does not clear the memory contents +//     - The memory itself is implemented using an associative array (sparse  +//       matrix) so that large memories can be supported. +//     - This model is half duplex, meaning read and write data transfers won't +//       happen at the same time. A new data transfer won't begin until the +//       previous one has completed. +// + +module sim_axi_ram #( +  parameter AWIDTH     = 32, +  parameter DWIDTH     = 64, +  parameter IDWIDTH    = 2, +  parameter BIG_ENDIAN = 0, +  parameter STALL_PROB = 25 +) ( +  input logic s_aclk, +  input logic s_aresetn, + +  // Write Address Channel +  input  logic [IDWIDTH-1:0] s_axi_awid, +  input  logic [ AWIDTH-1:0] s_axi_awaddr, +  input  logic [        7:0] s_axi_awlen, +  input  logic [        2:0] s_axi_awsize, +  input  logic [        1:0] s_axi_awburst, +  input  logic               s_axi_awvalid, +  output logic               s_axi_awready, + +  // Write Data Channel +  input  logic [  DWIDTH-1:0] s_axi_wdata, +  input  logic [DWIDTH/8-1:0] s_axi_wstrb, +  input  logic                s_axi_wlast, +  input  logic                s_axi_wvalid, +  output logic                s_axi_wready, + +  // Write Response Channel +  output logic [IDWIDTH-1:0] s_axi_bid, +  output logic [        1:0] s_axi_bresp, +  output logic               s_axi_bvalid, +  input  logic               s_axi_bready, + +  // Read Address Channel +  input  logic [IDWIDTH-1:0] s_axi_arid, +  input  logic [ AWIDTH-1:0] s_axi_araddr, +  input  logic [        7:0] s_axi_arlen, +  input  logic [        2:0] s_axi_arsize, +  input  logic [        1:0] s_axi_arburst, +  input  logic               s_axi_arvalid, +  output logic               s_axi_arready, + +  // Read Data Channel +  output logic [       0:0] s_axi_rid, +  output logic [DWIDTH-1:0] s_axi_rdata, +  output logic [       1:0] s_axi_rresp, +  output logic              s_axi_rlast, +  output logic              s_axi_rvalid, +  input  logic              s_axi_rready +); + +  localparam DEBUG = 0; + +  //--------------------------------------------------------------------------- +  // Data Types +  //--------------------------------------------------------------------------- +   +  typedef enum logic [1:0] { FIXED, INCR, WRAP }            burst_t; +  typedef enum logic [1:0] { OKAY, EXOKAY, SLVERR, DECERR } resp_t; + +  typedef struct packed { +    longint             count;  // Number of requests to wait for before executing +    logic [IDWIDTH-1:0] id; +    logic [AWIDTH-1:0]  addr; +    logic [8:0]         len;    // Add an extra bit, since actual true length is +1 +    logic [7:0]         size;   // Add extra bits to store size in bytes, instead of clog2(size) +    burst_t             burst; +  } req_t; + +  // Make the address type an extra bit wide so that we can detect  +  // out-of-bounds accesses easily. +  typedef bit [AWIDTH:0] addr_t; + +  // Data word type +  typedef logic [DWIDTH-1:0] data_t; + +  // Mask to indicate which bits should be written. +  typedef bit [DWIDTH/8-1:0] mask_t; + + +  //--------------------------------------------------------------------------- +  // Data Structures +  //--------------------------------------------------------------------------- + +  byte             memory [addr_t];     // Byte addressable memory +  mailbox #(req_t) read_req   = new();  // Read request queue +  mailbox #(req_t) write_req  = new();  // Write request queue +  mailbox #(req_t) write_resp = new();  // Write response queue + +  longint req_count;    // Number of requests received +  longint compl_count;  // Number of requests completed + + +  //--------------------------------------------------------------------------- +  // External Configuration Interface +  //--------------------------------------------------------------------------- + +  int waddr_stall_prob = STALL_PROB; +  int wdata_stall_prob = STALL_PROB; +  int wresp_stall_prob = STALL_PROB; +  int raddr_stall_prob = STALL_PROB; +  int rdata_stall_prob = STALL_PROB; + +  // Set ALL stall probabilities to the same value +  function void set_stall_prob(int probability); +    assert(probability >= 0 && probability <= 100) else begin +      $error("Probability must be from 0 to 100"); +    end +    waddr_stall_prob = probability; +    wdata_stall_prob = probability; +    wresp_stall_prob = probability; +    raddr_stall_prob = probability; +    rdata_stall_prob = probability; +  endfunction : set_stall_prob + +  // Set WRITE stall probabilities to the same value +  function void set_write_stall_prob(int probability); +    assert(probability >= 0 && probability <= 100) else begin +      $error("Probability must be from 0 to 100"); +    end +    waddr_stall_prob = probability; +    wdata_stall_prob = probability; +    wresp_stall_prob = probability; +  endfunction : set_write_stall_prob + +  // Set READ stall probabilities to the same value +  function void set_read_stall_prob(int probability); +    assert(probability >= 0 && probability <= 100) else begin +      $error("Probability must be from 0 to 100"); +    end +    raddr_stall_prob = probability; +    rdata_stall_prob = probability; +  endfunction : set_read_stall_prob + +  // Set Write Address Channel stall probability   +  function void set_waddr_stall_prob(int probability); +    assert(probability >= 0 && probability <= 100) else begin +      $error("Probability must be from 0 to 100"); +    end +    waddr_stall_prob = probability; +  endfunction : set_waddr_stall_prob + +  // Set Write Data Channel stall probability +  function void set_wdata_stall_prob(int probability); +    assert(probability >= 0 && probability <= 100) else begin +      $error("Probability must be from 0 to 100"); +    end +    wdata_stall_prob = probability; +  endfunction : set_wdata_stall_prob + +  // Set Write Response Channel stall probability +  function void set_wresp_stall_prob(int probability); +    assert(probability >= 0 && probability <= 100) else begin +      $error("Probability must be from 0 to 100"); +    end +    wresp_stall_prob = probability; +  endfunction : set_wresp_stall_prob + +  // Set Read Address Channel stall probability +  function void set_raddr_stall_prob(int probability); +    assert(probability >= 0 && probability <= 100) else begin +      $error("Probability must be from 0 to 100"); +    end +    raddr_stall_prob = probability; +  endfunction : set_raddr_stall_prob + +  // Set Read Data Channel stall probability +  function void set_rdata_stall_prob(int probability); +    assert(probability >= 0 && probability <= 100) else begin +      $error("Probability must be from 0 to 100"); +    end +    rdata_stall_prob = probability; +  endfunction : set_rdata_stall_prob + +  // Get Write Address Channel stall probability   +  function int get_waddr_stall_prob(); +    return waddr_stall_prob; +  endfunction : get_waddr_stall_prob + +  // Get Write Data Channel stall probability +  function int get_wdata_stall_prob(); +    return wdata_stall_prob; +  endfunction : get_wdata_stall_prob + +  // Get Write Response Channel stall probability +  function int get_wresp_stall_prob(); +    return wresp_stall_prob; +  endfunction : get_wresp_stall_prob + +  // Get Read Address Channel stall probability +  function int get_raddr_stall_prob(); +    return raddr_stall_prob; +  endfunction : get_raddr_stall_prob + +  // Get Read Data Channel stall probability +  function int get_rdata_stall_prob(); +    return rdata_stall_prob; +  endfunction : get_rdata_stall_prob + + + +  //--------------------------------------------------------------------------- +  // Helper Functions +  //--------------------------------------------------------------------------- + +  function data_t read_mem(addr_t byte_addr, int num_bytes); +    data_t data; +    addr_t incr; + +    if (BIG_ENDIAN) begin +      byte_addr = byte_addr + num_bytes-1; +      incr      = -1; +    end else begin +      incr      = 1; +    end + +    for (int i = 0; i < num_bytes; i++) begin +      if (byte_addr >= 2**AWIDTH) begin +        $fatal(1, "Read extends beyond memory range"); +      end +      if (memory.exists(byte_addr)) data[i*8 +: 8] = memory[byte_addr]; +      else data[i*8 +: 8] = 'X; +      byte_addr += incr; +    end + +    return data; +  endfunction : read_mem + + +  function void write_mem(addr_t byte_addr, int num_bytes, data_t data, mask_t mask); +    addr_t incr; + +    if (BIG_ENDIAN) begin +      byte_addr = byte_addr + num_bytes-1; +      incr      = -1; +    end else begin +      incr      = 1; +    end + +    for (int i = 0; i < num_bytes; i++) begin +      if (mask[i]) begin +        if (byte_addr >= 2**AWIDTH) begin +          $fatal(1, "Write extends beyond memory range"); +        end +        memory[byte_addr] = data[i*8 +: 8]; +      end +      byte_addr += incr; +    end +  endfunction : write_mem + + +  //--------------------------------------------------------------------------- +  // Write Requests +  //--------------------------------------------------------------------------- + +  initial begin : write_req_proc +    req_t   req; +    burst_t burst; + +    s_axi_awready <= 0; + +    forever begin +      @(posedge s_aclk); +      if (!s_aresetn) continue; + +      if (s_axi_awvalid) begin +        if (s_axi_awready) begin +          req.count = req_count; +          req.id    = s_axi_awid; +          req.addr  = s_axi_awaddr; +          req.len   = s_axi_awlen + 1;    // Per AXI4 spec, Burst_length = AxLEN[7:0] + 1 +          req.size  = 2**s_axi_awsize;    // Store as true size in bytes, not clog2(size) +          req.burst = burst_t'(s_axi_awburst); + +          // Check that the request is valid +          assert (!$isunknown(req)) else begin +            $fatal(1, "Write request signals are unknown"); +          end +          assert (s_axi_araddr % (DWIDTH/8) == 0) else begin +            $warning("Unaligned memory write"); +          end +          assert (2**s_axi_awsize <= DWIDTH/8) else begin +            $fatal(1, "AWSIZE must not be larger than DWIDTH"); +          end +          assert ($cast(burst, s_axi_awburst)) else begin +            $fatal(1, "Invalid AWBURST value"); +          end + +          if (DEBUG) begin +            $display("WRITE REQ: id=%X, addr=%X, len=%X, size=%X, burst=%s, %t, %m",  +              req.id, req.addr, req.len, req.size, req.burst.name, $realtime); +          end + +          req_count++; +          write_req.put(req); +        end + +        // Randomly deassert ready +        s_axi_awready <= $urandom_range(99) < waddr_stall_prob ? 0 : 1; +      end +    end +  end : write_req_proc + + +  //--------------------------------------------------------------------------- +  // Read Requests +  //--------------------------------------------------------------------------- + +  initial begin : read_req_proc +    req_t   req; +    burst_t burst; + +    s_axi_arready <= 0; + +    forever begin +      @(posedge s_aclk); +      if (!s_aresetn) continue; + +      if (s_axi_arvalid) begin +        if (s_axi_arready) begin +          req.count = req_count; +          req.id    = s_axi_arid; +          req.addr  = s_axi_araddr; +          req.len   = s_axi_arlen + 1;    // Per AXI4 spec, Burst_length = AxLEN[7:0] + 1 +          req.size  = 2**s_axi_arsize;    // Store as true size in bytes, not clog2(size) +          req.burst = burst_t'(s_axi_arburst); + +          // Check that the request is valid +          assert(!$isunknown(req)) else begin +            $fatal(1, "Read request signals are unknown"); +          end +          assert(s_axi_araddr % (DWIDTH/8) == 0) else begin +            $warning("Unaligned memory read"); +          end +          assert(2**s_axi_arsize <= DWIDTH/8) else begin +            $fatal(1, "ARSIZE must not be larger than DWIDTH"); +          end +          assert ($cast(burst, s_axi_awburst)) else begin +            $fatal(1, "Invalid ARBURST value"); +          end + +          if (DEBUG) begin +            $display("READ REQ:  id=%X, addr=%X, len=%X, size=%X, burst=%s, %t, %m",  +              req.id, req.addr, req.len, req.size, req.burst.name, $realtime); +          end + +          req_count++; +          read_req.put(req); +        end + +        // Randomly deassert ready to cause a stall +        s_axi_arready <= $urandom_range(99) < raddr_stall_prob ? 0 : 1; +      end +    end +  end : read_req_proc + + +  //--------------------------------------------------------------------------- +  // Write Data +  //--------------------------------------------------------------------------- + +  initial begin : write_data_proc +    req_t req; +    bit [AWIDTH-1:0] addr; + +    forever begin +      // Wait for the next write request +      s_axi_wready <= 0; +      write_req.get(req); + +      // Wait for previous requests to complete +      while (compl_count < req.count) begin +        @(posedge s_aclk); +        if (!s_aresetn) break; +      end + +      // If reset was asserted, clear the request queue and start over +      if (!s_aresetn) begin +        while(write_req.try_get(req)); +        continue; +      end + +      // Iterate over the number of words in the request +      for (int i = 0; i < req.len; ) begin +        @(posedge s_aclk); +        if (!s_aresetn) break; + +        // Check if we have a new data word +        if (s_axi_wvalid) begin +          if (s_axi_wready) begin +            // Check the inputs +            if ($isunknown(s_axi_wstrb)) begin +              $fatal(1, "WSTRB is unknown"); +            end +            if ($isunknown(s_axi_wdata)) begin +              $warning(1, "WDATA is unknown; data will be changed to zero"); +            end + +            case (req.burst) +              FIXED : begin +                addr = req.addr; +              end +              INCR : begin +                // If the address rolls over, we've reached the end of the  +                // memory and we should stop here. +                addr = req.addr + i*req.size; +                if (addr < req.addr) break; +              end +              WRAP : begin +                // Allow roll-over +                addr = req.addr + i*req.size; +              end +            endcase + +            write_mem(addr, req.size, s_axi_wdata, s_axi_wstrb); + +            if (DEBUG) begin +              $display("WRITE: count=%3X, ADDR=%X, DATA=%X, SIZE=%X, STRB=%X, %t, %m",  +                i, addr, s_axi_wdata, req.size, s_axi_wstrb, $realtime); +            end + +            i++; +          end + +          // Randomly deassert ready to cause a stall +          s_axi_wready <= $urandom_range(99) < wdata_stall_prob ? 0 : 1; +        end +      end // for + +      // If reset was asserted, clear the request queue and start over +      if (!s_aresetn) begin +        while(write_req.try_get(req)); +        continue; +      end + +      compl_count++; + +      // Enqueue write response +      write_resp.put(req); + +      // Make sure WLAST asserted for the last word. If not we report an error.  +      // Per the AXI4 standard, "a slave is not required to use the WLAST  +      // signal" because "a slave can calculate the last write data transfer  +      // from the burst length AWLEN". +      if (s_axi_wlast != 1'b1) begin +        $error("WLAST not asserted on last word of burst"); +      end + +    end // forever +  end : write_data_proc + + +  //--------------------------------------------------------------------------- +  // Write Response +  //--------------------------------------------------------------------------- + +  initial begin : write_resp_proc +    req_t resp; +    bit [AWIDTH-1:0] addr; + +    forever begin +      s_axi_bid    <= 'X; +      s_axi_bresp  <= 'X; +      s_axi_bvalid <= 0; + +      // Wait for the next write response +      write_resp.get(resp); +      @(posedge s_aclk); + +      // If there's a reset, clear the response queue and start over +      if (!s_aresetn) begin +        while(write_resp.try_get(resp)); +        continue; +      end + +      // Randomly keep bvalid deasserted for next word to cause a stall +      if ($urandom_range(99) < wresp_stall_prob) begin +        do begin +          @(posedge s_aclk); +          if (!s_aresetn) break; +        end while ($urandom_range(99) < wresp_stall_prob); + +        // If reset was asserted, clear the response queue and start over +        if (!s_aresetn) begin +          while(write_resp.try_get(resp)); +          continue; +        end +      end + +      // Output the next response +      s_axi_bid    <= resp.id; +      s_axi_bresp  <= OKAY; +      s_axi_bvalid <= 1; + +      if (DEBUG) begin +        $display("WRITE RESP: ID=%X, %t, %m", resp.id, $realtime); +      end + +      // Wait for the response to be accepted +      do begin +        @(posedge s_aclk); +        if (!s_aresetn) break; +      end while (!s_axi_bready); + +      // Output the next response +      s_axi_bid    <= 'X; +      s_axi_bresp  <= 'X; +      s_axi_bvalid <= 0; + +      // If reset was asserted, clear the response queue and start over +      if (!s_aresetn) begin +        while(write_resp.try_get(resp)); +        continue; +      end +    end // forever +  end : write_resp_proc + + +  //--------------------------------------------------------------------------- +  // Read Data +  //--------------------------------------------------------------------------- + +  initial begin : read_data_proc +    req_t req; +    bit   [AWIDTH-1:0] addr; +    logic [DWIDTH-1:0] data; + +    forever begin +      s_axi_rid    <= 'X; +      s_axi_rdata  <= 'X; +      s_axi_rresp  <= 'X; +      s_axi_rlast  <= 'X; +      s_axi_rvalid <= 0; + +      // Wait for the next read request +      read_req.get(req); + +      // Wait for previous requests to complete +      do begin +        @(posedge s_aclk); +        if (!s_aresetn) break; +      end while (compl_count < req.count); + +      // If reset was asserted, clear the request queue and start over +      if (!s_aresetn) begin +        while(read_req.try_get(req)); +        continue; +      end + +      for (int i = 0; i < req.len; i++) begin +        // Randomly keep rvalid deasserted for next word to cause a stall +        if ($urandom_range(99) < rdata_stall_prob) begin +          do begin +            @(posedge s_aclk); +            if (!s_aresetn) break; +          end while ($urandom_range(99) < rdata_stall_prob); +          if (!s_aresetn) break; +        end + +        case (req.burst) +          FIXED : begin +            addr = req.addr; +          end +          INCR : begin +            // If the address rolls over, we've reached the end of the memory  +            // and we should stop here. +            addr = req.addr + i*req.size; +            if (addr < req.addr) break; +          end +          WRAP : begin +            // Allow roll-over +            addr = req.addr + i*req.size; +          end +        endcase + +        // Read the memory +        data = read_mem(addr, req.size); + +        // Output the next word +        s_axi_rid    <= req.id; +        s_axi_rdata  <= data; +        s_axi_rresp  <= OKAY; +        s_axi_rlast  <= (i == req.len-1); +        s_axi_rvalid <= 1; + +        if (DEBUG) begin +          $display("READ:  count=%3X, ADDR=%X, DATA=%X, SIZE=%X, %t, %m", i, addr, data, req.size, $realtime); +        end + +        // Wait for the word to be captured +        do begin +          @(posedge s_aclk); +          if (!s_aresetn) break; +        end while (!s_axi_rready); + +        s_axi_rid    <= 'X; +        s_axi_rdata  <= 'X; +        s_axi_rresp  <= 'X; +        s_axi_rlast  <= 'X; +        s_axi_rvalid <= 0; +      end // for + +      // If reset was asserted, clear the request queue and start over +      if (!s_aresetn) begin +        while(read_req.try_get(req)); +      end + +      compl_count++; + +    end // forever +  end : read_data_proc + +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_ddc/Makefile b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_ddc/Makefile new file mode 100644 index 000000000..d574c9a01 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_ddc/Makefile @@ -0,0 +1,68 @@ +# +# Copyright 2019 Ettus Research, A National Instruments Company +# +# 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_preamble after defining BASE_DIR +include $(BASE_DIR)/../tools/make/viv_sim_preamble.mak + +#------------------------------------------------- +# IP Specific +#------------------------------------------------- +# If simulation contains IP, define the IP_DIR and point +# it to the base level IP directory +LIB_IP_DIR = $(BASE_DIR)/../lib/ip + +# Include makefiles and sources for all IP components +# *after* defining the LIB_IP_DIR +#include $(LIB_IP_DIR)/axi_fft/Makefile.inc +#include $(LIB_IP_DIR)/complex_to_magphase/Makefile.inc +include $(LIB_IP_DIR)/complex_multiplier_dds/Makefile.inc +include $(LIB_IP_DIR)/dds_sin_cos_lut_only/Makefile.inc +include $(BASE_DIR)/x300/coregen_dsp/Makefile.srcs + +DESIGN_SRCS += $(abspath \ +$(LIB_IP_COMPLEX_MULTIPLIER_DDS_SRCS) \ +$(LIB_IP_DDS_SIN_COS_LUT_ONLY_SRCS) \ +$(COREGEN_DSP_SRCS) \ +) + +#------------------------------------------------- +# Design Specific +#------------------------------------------------- +# Include makefiles and sources for the DUT and its dependencies +include $(BASE_DIR)/../lib/rfnoc/core/Makefile.srcs +include $(BASE_DIR)/../lib/rfnoc/utils/Makefile.srcs +include Makefile.srcs + +DESIGN_SRCS += $(abspath \ +$(RFNOC_CORE_SRCS) \ +$(RFNOC_UTIL_SRCS) \ +$(RFNOC_BLOCK_DDC_SRCS) \ +) + +#------------------------------------------------- +# Testbench Specific +#------------------------------------------------- +# Define only one toplevel module +SIM_TOP = rfnoc_block_ddc_tb + +# Add test bench, user design under test, and +# additional user created files +SIM_SRCS = \ +$(COREGEN_DSP_SRCS) \ +$(abspath rfnoc_block_ddc_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/blocks/rfnoc_block_ddc/Makefile.srcs b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_ddc/Makefile.srcs new file mode 100644 index 000000000..28663f03c --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_ddc/Makefile.srcs @@ -0,0 +1,11 @@ +# +# Copyright 2019 Ettus Research, A National Instruments Company +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# + +RFNOC_BLOCK_DDC_SRCS = $(abspath $(addprefix $(BASE_DIR)/../lib/rfnoc/blocks/rfnoc_block_ddc/, \ +noc_shell_ddc.v \ +rfnoc_block_ddc_regs.vh \ +rfnoc_block_ddc.v \ +)) diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_ddc/noc_shell_ddc.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_ddc/noc_shell_ddc.v new file mode 100644 index 000000000..56a13ee0a --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_ddc/noc_shell_ddc.v @@ -0,0 +1,291 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: noc_shell_ddc +// +// Description: A NoC Shell for RFNoC. This should eventually be replaced  +// by an auto-generated NoC Shell. +// + +module noc_shell_ddc #( +  parameter [31:0] NOC_ID          = 32'h0, +  parameter [ 9:0] THIS_PORTID     = 10'd0, +  parameter        CHDR_W          = 64, +  parameter [ 0:0] CTRLPORT_SLV_EN = 1, +  parameter [ 0:0] CTRLPORT_MST_EN = 1, +  parameter [ 5:0] CTRL_FIFO_SIZE  = 6, +  parameter [ 5:0] NUM_DATA_I      = 1, +  parameter [ 5:0] NUM_DATA_O      = 1, +  parameter        ITEM_W          = 32, +  parameter        NIPC            = 2, +  parameter        PYLD_FIFO_SIZE  = 10, +  parameter        MTU             = 10 +)( +  //--------------------------------------------------------------------------- +  // Framework Interface +  //--------------------------------------------------------------------------- + +  // RFNoC Framework Clocks and Resets +  input  wire                           rfnoc_chdr_clk, +  output wire                           rfnoc_chdr_rst, +  input  wire                           rfnoc_ctrl_clk, +  output wire                           rfnoc_ctrl_rst, +  // RFNoC Backend Interface +  input  wire [                  511:0] rfnoc_core_config, +  output wire [                  511:0] rfnoc_core_status, +  // CHDR Input Ports (from framework) +  input  wire [(CHDR_W*NUM_DATA_I)-1:0] s_rfnoc_chdr_tdata, +  input  wire [         NUM_DATA_I-1:0] s_rfnoc_chdr_tlast, +  input  wire [         NUM_DATA_I-1:0] s_rfnoc_chdr_tvalid, +  output wire [         NUM_DATA_I-1:0] s_rfnoc_chdr_tready, +  // CHDR Output Ports (to framework) +  output wire [(CHDR_W*NUM_DATA_O)-1:0] m_rfnoc_chdr_tdata, +  output wire [         NUM_DATA_O-1:0] m_rfnoc_chdr_tlast, +  output wire [         NUM_DATA_O-1:0] m_rfnoc_chdr_tvalid, +  input  wire [         NUM_DATA_O-1:0] m_rfnoc_chdr_tready, +  // AXIS-Ctrl Input Port (from framework) +  input  wire [                   31:0] s_rfnoc_ctrl_tdata, +  input  wire                           s_rfnoc_ctrl_tlast, +  input  wire                           s_rfnoc_ctrl_tvalid, +  output wire                           s_rfnoc_ctrl_tready, +  // AXIS-Ctrl Output Port (to framework) +  output wire [                   31:0] m_rfnoc_ctrl_tdata, +  output wire                           m_rfnoc_ctrl_tlast, +  output wire                           m_rfnoc_ctrl_tvalid, +  input  wire                           m_rfnoc_ctrl_tready, + +  //--------------------------------------------------------------------------- +  // Client Control Port Interface +  //--------------------------------------------------------------------------- + +  // Clock +  input  wire        ctrlport_clk, +  input  wire        ctrlport_rst, +  // Master +  output wire        m_ctrlport_req_wr, +  output wire        m_ctrlport_req_rd, +  output wire [19:0] m_ctrlport_req_addr, +  output wire [31:0] m_ctrlport_req_data, +  output wire [ 3:0] m_ctrlport_req_byte_en, +  output wire        m_ctrlport_req_has_time, +  output wire [63:0] m_ctrlport_req_time, +  input  wire        m_ctrlport_resp_ack, +  input  wire [ 1:0] m_ctrlport_resp_status, +  input  wire [31:0] m_ctrlport_resp_data, +  // Slave +  input  wire        s_ctrlport_req_wr, +  input  wire        s_ctrlport_req_rd, +  input  wire [19:0] s_ctrlport_req_addr, +  input  wire [ 9:0] s_ctrlport_req_portid, +  input  wire [15:0] s_ctrlport_req_rem_epid, +  input  wire [ 9:0] s_ctrlport_req_rem_portid, +  input  wire [31:0] s_ctrlport_req_data, +  input  wire [ 3:0] s_ctrlport_req_byte_en, +  input  wire        s_ctrlport_req_has_time, +  input  wire [63:0] s_ctrlport_req_time, +  output wire        s_ctrlport_resp_ack, +  output wire [ 1:0] s_ctrlport_resp_status, +  output wire [31:0] s_ctrlport_resp_data, + +  //--------------------------------------------------------------------------- +  // Client Data Interface +  //--------------------------------------------------------------------------- + +  // Clock +  input  wire                                axis_data_clk, +  input  wire                                axis_data_rst, + +  // Output data stream (to user logic) +  output wire [(NUM_DATA_I*ITEM_W*NIPC)-1:0] m_axis_tdata, +  output wire [       (NUM_DATA_I*NIPC)-1:0] m_axis_tkeep, +  output wire [              NUM_DATA_I-1:0] m_axis_tlast, +  output wire [              NUM_DATA_I-1:0] m_axis_tvalid, +  input  wire [              NUM_DATA_I-1:0] m_axis_tready, +  // Sideband information +  output wire [         (NUM_DATA_I*64)-1:0] m_axis_ttimestamp, +  output wire [              NUM_DATA_I-1:0] m_axis_thas_time, +  output wire [         (NUM_DATA_I*16)-1:0] m_axis_tlength, +  output wire [              NUM_DATA_I-1:0] m_axis_teov, +  output wire [              NUM_DATA_I-1:0] m_axis_teob, + +  // Input data stream (from user logic) +  input  wire [(NUM_DATA_O*ITEM_W*NIPC)-1:0] s_axis_tdata, +  input  wire [       (NUM_DATA_O*NIPC)-1:0] s_axis_tkeep, +  input  wire [              NUM_DATA_O-1:0] s_axis_tlast, +  input  wire [              NUM_DATA_O-1:0] s_axis_tvalid, +  output wire [              NUM_DATA_O-1:0] s_axis_tready, +  // Sideband info (sampled on the first cycle of the packet) +  input  wire [         (NUM_DATA_O*64)-1:0] s_axis_ttimestamp, +  input  wire [              NUM_DATA_O-1:0] s_axis_thas_time, +  input  wire [              NUM_DATA_O-1:0] s_axis_teov, +  input  wire [              NUM_DATA_O-1:0] s_axis_teob +); +   +  localparam SNK_INFO_FIFO_SIZE = 4; +  localparam SNK_PYLD_FIFO_SIZE = PYLD_FIFO_SIZE; +  localparam SRC_INFO_FIFO_SIZE = 4; +  localparam SRC_PYLD_FIFO_SIZE = (MTU > PYLD_FIFO_SIZE) ? MTU : PYLD_FIFO_SIZE; + +  //--------------------------------------------------------------------------- +  //  Backend Interface +  //--------------------------------------------------------------------------- + +  wire         data_i_flush_en; +  wire [31:0]  data_i_flush_timeout; +  wire [63:0]  data_i_flush_active; +  wire [63:0]  data_i_flush_done; +  wire         data_o_flush_en; +  wire [31:0]  data_o_flush_timeout; +  wire [63:0]  data_o_flush_active; +  wire [63:0]  data_o_flush_done; + +  backend_iface #( +    .NOC_ID        (NOC_ID), +    .NUM_DATA_I    (NUM_DATA_I), +    .NUM_DATA_O    (NUM_DATA_O), +    .CTRL_FIFOSIZE (CTRL_FIFO_SIZE), +    .MTU           (MTU) +  ) backend_iface_i ( +    .rfnoc_chdr_clk       (rfnoc_chdr_clk), +    .rfnoc_ctrl_clk       (rfnoc_ctrl_clk), +    .rfnoc_core_config    (rfnoc_core_config), +    .rfnoc_core_status    (rfnoc_core_status), +    .rfnoc_chdr_rst       (rfnoc_chdr_rst), +    .rfnoc_ctrl_rst       (rfnoc_ctrl_rst), +    .data_i_flush_en      (data_i_flush_en), +    .data_i_flush_timeout (data_i_flush_timeout), +    .data_i_flush_active  (data_i_flush_active), +    .data_i_flush_done    (data_i_flush_done), +    .data_o_flush_en      (data_o_flush_en), +    .data_o_flush_timeout (data_o_flush_timeout), +    .data_o_flush_active  (data_o_flush_active), +    .data_o_flush_done    (data_o_flush_done) +  ); + +  //--------------------------------------------------------------------------- +  //  Control Path +  //--------------------------------------------------------------------------- + +  ctrlport_endpoint #( +    .THIS_PORTID              (THIS_PORTID    ), +    .SYNC_CLKS                (0              ), +    .AXIS_CTRL_MST_EN         (CTRLPORT_SLV_EN), +    .AXIS_CTRL_SLV_EN         (CTRLPORT_MST_EN), +    .SLAVE_FIFO_SIZE          (CTRL_FIFO_SIZE ) +  ) ctrlport_ep_i ( +    .rfnoc_ctrl_clk           (rfnoc_ctrl_clk           ), +    .rfnoc_ctrl_rst           (rfnoc_ctrl_rst           ), +    .ctrlport_clk             (ctrlport_clk             ), +    .ctrlport_rst             (ctrlport_rst             ), +    .s_rfnoc_ctrl_tdata       (s_rfnoc_ctrl_tdata       ), +    .s_rfnoc_ctrl_tlast       (s_rfnoc_ctrl_tlast       ), +    .s_rfnoc_ctrl_tvalid      (s_rfnoc_ctrl_tvalid      ), +    .s_rfnoc_ctrl_tready      (s_rfnoc_ctrl_tready      ), +    .m_rfnoc_ctrl_tdata       (m_rfnoc_ctrl_tdata       ), +    .m_rfnoc_ctrl_tlast       (m_rfnoc_ctrl_tlast       ), +    .m_rfnoc_ctrl_tvalid      (m_rfnoc_ctrl_tvalid      ), +    .m_rfnoc_ctrl_tready      (m_rfnoc_ctrl_tready      ), +    .m_ctrlport_req_wr        (m_ctrlport_req_wr        ), +    .m_ctrlport_req_rd        (m_ctrlport_req_rd        ), +    .m_ctrlport_req_addr      (m_ctrlport_req_addr      ), +    .m_ctrlport_req_data      (m_ctrlport_req_data      ), +    .m_ctrlport_req_byte_en   (m_ctrlport_req_byte_en   ), +    .m_ctrlport_req_has_time  (m_ctrlport_req_has_time  ), +    .m_ctrlport_req_time      (m_ctrlport_req_time      ), +    .m_ctrlport_resp_ack      (m_ctrlport_resp_ack      ), +    .m_ctrlport_resp_status   (m_ctrlport_resp_status   ), +    .m_ctrlport_resp_data     (m_ctrlport_resp_data     ), +    .s_ctrlport_req_wr        (s_ctrlport_req_wr        ), +    .s_ctrlport_req_rd        (s_ctrlport_req_rd        ), +    .s_ctrlport_req_addr      (s_ctrlport_req_addr      ), +    .s_ctrlport_req_portid    (s_ctrlport_req_portid    ), +    .s_ctrlport_req_rem_epid  (s_ctrlport_req_rem_epid  ), +    .s_ctrlport_req_rem_portid(s_ctrlport_req_rem_portid), +    .s_ctrlport_req_data      (s_ctrlport_req_data      ), +    .s_ctrlport_req_byte_en   (s_ctrlport_req_byte_en   ), +    .s_ctrlport_req_has_time  (s_ctrlport_req_has_time  ), +    .s_ctrlport_req_time      (s_ctrlport_req_time      ), +    .s_ctrlport_resp_ack      (s_ctrlport_resp_ack      ), +    .s_ctrlport_resp_status   (s_ctrlport_resp_status   ), +    .s_ctrlport_resp_data     (s_ctrlport_resp_data     ) +  ); + +  //--------------------------------------------------------------------------- +  //  Data Path +  //--------------------------------------------------------------------------- + +  genvar i; +  generate + +    for (i = 0; i < NUM_DATA_I; i = i + 1) begin: chdr_to_data +      chdr_to_axis_data #( +        .CHDR_W         (CHDR_W), +        .ITEM_W         (ITEM_W), +        .NIPC           (NIPC), +        .SYNC_CLKS      (0), +        .INFO_FIFO_SIZE (SNK_INFO_FIFO_SIZE), +        .PYLD_FIFO_SIZE (SNK_PYLD_FIFO_SIZE) +      ) chdr_to_axis_data_i ( +        .axis_chdr_clk      (rfnoc_chdr_clk), +        .axis_chdr_rst      (rfnoc_chdr_rst), +        .axis_data_clk      (axis_data_clk), +        .axis_data_rst      (axis_data_rst), +        .s_axis_chdr_tdata  (s_rfnoc_chdr_tdata  [(i*CHDR_W)+:CHDR_W]), +        .s_axis_chdr_tlast  (s_rfnoc_chdr_tlast  [i]), +        .s_axis_chdr_tvalid (s_rfnoc_chdr_tvalid [i]), +        .s_axis_chdr_tready (s_rfnoc_chdr_tready [i]), +        .m_axis_tdata       (m_axis_tdata  [i*ITEM_W*NIPC +: ITEM_W*NIPC]), +        .m_axis_tkeep       (m_axis_tkeep  [i*NIPC +: NIPC]), +        .m_axis_tlast       (m_axis_tlast  [i]), +        .m_axis_tvalid      (m_axis_tvalid [i]), +        .m_axis_tready      (m_axis_tready [i]), +        .m_axis_ttimestamp  (m_axis_ttimestamp [i*64 +: 64]), +        .m_axis_thas_time   (m_axis_thas_time  [i]), +        .m_axis_tlength     (m_axis_tlength    [i*16 +: 16]), +        .m_axis_teov        (m_axis_teov       [i]), +        .m_axis_teob        (m_axis_teob       [i]), +        .flush_en           (data_i_flush_en), +        .flush_timeout      (data_i_flush_timeout), +        .flush_active       (data_i_flush_active [i]), +        .flush_done         (data_i_flush_done   [i]) +      ); +    end + +    for (i = 0; i < NUM_DATA_O; i = i + 1) begin: data_to_chdr +      axis_data_to_chdr #( +        .CHDR_W         (CHDR_W), +        .ITEM_W         (ITEM_W), +        .NIPC           (NIPC), +        .SYNC_CLKS      (0), +        .INFO_FIFO_SIZE (4), +        .PYLD_FIFO_SIZE (SRC_INFO_FIFO_SIZE), +        .MTU            (SRC_PYLD_FIFO_SIZE) +      ) axis_data_to_chdr_i ( +        .axis_chdr_clk       (rfnoc_chdr_clk), +        .axis_chdr_rst       (rfnoc_chdr_rst), +        .axis_data_clk       (axis_data_clk), +        .axis_data_rst       (axis_data_rst), +        .m_axis_chdr_tdata   (m_rfnoc_chdr_tdata  [i*CHDR_W +: CHDR_W]), +        .m_axis_chdr_tlast   (m_rfnoc_chdr_tlast  [i]), +        .m_axis_chdr_tvalid  (m_rfnoc_chdr_tvalid [i]), +        .m_axis_chdr_tready  (m_rfnoc_chdr_tready [i]), +        .s_axis_tdata        (s_axis_tdata  [i*ITEM_W*NIPC +: ITEM_W*NIPC]), +        .s_axis_tkeep        (s_axis_tkeep  [i*NIPC +: NIPC]), +        .s_axis_tlast        (s_axis_tlast  [i]), +        .s_axis_tvalid       (s_axis_tvalid [i]), +        .s_axis_tready       (s_axis_tready [i]), +        .s_axis_ttimestamp   (s_axis_ttimestamp [i*64 +: 64]), +        .s_axis_thas_time    (s_axis_thas_time  [i]), +        .s_axis_teov         (s_axis_teov       [i]), +        .s_axis_teob         (s_axis_teob       [i]), +        .flush_en            (data_o_flush_en), +        .flush_timeout       (data_o_flush_timeout), +        .flush_active        (data_o_flush_active [i]), +        .flush_done          (data_o_flush_done   [i]) +      ); +    end +  endgenerate + +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_ddc/rfnoc_block_ddc.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_ddc/rfnoc_block_ddc.v new file mode 100644 index 000000000..3162743b6 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_ddc/rfnoc_block_ddc.v @@ -0,0 +1,420 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_ddc +// +// Description:  An digital down-converter block for RFNoC. +// +// Parameters: +// +//   THIS_PORTID    : Control crossbar port to which this block is connected +//   CHDR_W         : AXIS CHDR interface data width +//   NUM_PORTS      : Number of DDCs to instantiate +//   MTU            : Maximum transmission unit (i.e., maximum packet size) in +//                    CHDR words is 2**MTU. +//   CTRL_FIFO_SIZE : Size of the Control Port slave FIFO. This affects the +//                    number of outstanding commands that can be pending. +//   NUM_HB         : Number of half-band decimation blocks to include (0-3) +//   CIC_MAX_DECIM  : Maximum decimation to support in the CIC filter +// + +module rfnoc_block_ddc #( +  parameter THIS_PORTID    = 0, +  parameter CHDR_W         = 64, +  parameter NUM_PORTS      = 2, +  parameter MTU            = 10, +  parameter CTRL_FIFO_SIZE = 6, +  parameter NUM_HB         = 3, +  parameter CIC_MAX_DECIM  = 255 +) ( +  //--------------------------------------------------------------------------- +  // AXIS CHDR Port +  //--------------------------------------------------------------------------- + +  input wire rfnoc_chdr_clk, +  input wire ce_clk, + +  // CHDR inputs from framework +  input  wire [NUM_PORTS*CHDR_W-1:0] s_rfnoc_chdr_tdata, +  input  wire [       NUM_PORTS-1:0] s_rfnoc_chdr_tlast, +  input  wire [       NUM_PORTS-1:0] s_rfnoc_chdr_tvalid, +  output wire [       NUM_PORTS-1:0] s_rfnoc_chdr_tready, + +  // CHDR outputs to framework +  output wire [NUM_PORTS*CHDR_W-1:0] m_rfnoc_chdr_tdata, +  output wire [       NUM_PORTS-1:0] m_rfnoc_chdr_tlast, +  output wire [       NUM_PORTS-1:0] m_rfnoc_chdr_tvalid, +  input  wire [       NUM_PORTS-1:0] m_rfnoc_chdr_tready, + +  // Backend interface +  input  wire [511:0] rfnoc_core_config, +  output wire [511:0] rfnoc_core_status, + +  //--------------------------------------------------------------------------- +  // AXIS CTRL Port +  //--------------------------------------------------------------------------- + +  input wire rfnoc_ctrl_clk, + +  // CTRL port requests from framework +  input  wire [31:0] s_rfnoc_ctrl_tdata, +  input  wire        s_rfnoc_ctrl_tlast, +  input  wire        s_rfnoc_ctrl_tvalid, +  output wire        s_rfnoc_ctrl_tready, + +  // CTRL port requests to framework +  output wire [31:0] m_rfnoc_ctrl_tdata, +  output wire        m_rfnoc_ctrl_tlast, +  output wire        m_rfnoc_ctrl_tvalid, +  input  wire        m_rfnoc_ctrl_tready +); + +  // These are the only supported values for now +  localparam ITEM_W = 32; +  localparam NIPC   = 1; + +  localparam NOC_ID = 'hDDC0_0000; + +  localparam COMPAT_MAJOR  = 16'h0; +  localparam COMPAT_MINOR  = 16'h0; + +  `include "rfnoc_block_ddc_regs.vh" +  `include "../../core/rfnoc_axis_ctrl_utils.vh" + + +  //--------------------------------------------------------------------------- +  // Signal Declarations +  //--------------------------------------------------------------------------- + +  wire rfnoc_chdr_rst; + +  wire        ctrlport_req_wr; +  wire        ctrlport_req_rd; +  wire [19:0] ctrlport_req_addr; +  wire [31:0] ctrlport_req_data; +  wire        ctrlport_req_has_time; +  wire [63:0] ctrlport_req_time; +  wire        ctrlport_resp_ack; +  wire [31:0] ctrlport_resp_data; + +  wire [NUM_PORTS*ITEM_W-1:0] m_axis_data_tdata; +  wire [       NUM_PORTS-1:0] m_axis_data_tlast; +  wire [       NUM_PORTS-1:0] m_axis_data_tvalid; +  wire [       NUM_PORTS-1:0] m_axis_data_tready; +  wire [    NUM_PORTS*64-1:0] m_axis_data_ttimestamp; +  wire [       NUM_PORTS-1:0] m_axis_data_thas_time; +  wire [    16*NUM_PORTS-1:0] m_axis_data_tlength; +  wire [       NUM_PORTS-1:0] m_axis_data_teob; +  wire [   NUM_PORTS*128-1:0] m_axis_data_tuser; + +  wire [NUM_PORTS*ITEM_W-1:0] s_axis_data_tdata; +  wire [       NUM_PORTS-1:0] s_axis_data_tlast; +  wire [       NUM_PORTS-1:0] s_axis_data_tvalid; +  wire [       NUM_PORTS-1:0] s_axis_data_tready; +  wire [   NUM_PORTS*128-1:0] s_axis_data_tuser; +  wire [       NUM_PORTS-1:0] s_axis_data_teob; +  wire [    NUM_PORTS*64-1:0] s_axis_data_ttimestamp; +  wire [       NUM_PORTS-1:0] s_axis_data_thas_time; + +  wire ddc_rst; + +  // Cross the CHDR reset to the ce_clk domain +  synchronizer ddc_rst_sync_i ( +    .clk (ce_clk), +    .rst (1'b0), +    .in  (rfnoc_chdr_rst), +    .out (ddc_rst) +  ); + + +  //--------------------------------------------------------------------------- +  // NoC Shell +  //--------------------------------------------------------------------------- + +  noc_shell_ddc #( +    .NOC_ID          (NOC_ID), +    .THIS_PORTID     (THIS_PORTID), +    .CHDR_W          (CHDR_W), +    .CTRLPORT_SLV_EN (0), +    .CTRLPORT_MST_EN (1), +    .CTRL_FIFO_SIZE  (CTRL_FIFO_SIZE), +    .NUM_DATA_I      (NUM_PORTS), +    .NUM_DATA_O      (NUM_PORTS), +    .ITEM_W          (ITEM_W), +    .NIPC            (NIPC), +    .PYLD_FIFO_SIZE  (MTU), +    .MTU             (MTU) +  ) noc_shell_ddc_i ( +    .rfnoc_chdr_clk            (rfnoc_chdr_clk), +    .rfnoc_chdr_rst            (rfnoc_chdr_rst), +    .rfnoc_ctrl_clk            (rfnoc_ctrl_clk), +    .rfnoc_ctrl_rst            (), +    .rfnoc_core_config         (rfnoc_core_config), +    .rfnoc_core_status         (rfnoc_core_status), +    .s_rfnoc_chdr_tdata        (s_rfnoc_chdr_tdata), +    .s_rfnoc_chdr_tlast        (s_rfnoc_chdr_tlast), +    .s_rfnoc_chdr_tvalid       (s_rfnoc_chdr_tvalid), +    .s_rfnoc_chdr_tready       (s_rfnoc_chdr_tready), +    .m_rfnoc_chdr_tdata        (m_rfnoc_chdr_tdata), +    .m_rfnoc_chdr_tlast        (m_rfnoc_chdr_tlast), +    .m_rfnoc_chdr_tvalid       (m_rfnoc_chdr_tvalid), +    .m_rfnoc_chdr_tready       (m_rfnoc_chdr_tready), +    .s_rfnoc_ctrl_tdata        (s_rfnoc_ctrl_tdata), +    .s_rfnoc_ctrl_tlast        (s_rfnoc_ctrl_tlast), +    .s_rfnoc_ctrl_tvalid       (s_rfnoc_ctrl_tvalid), +    .s_rfnoc_ctrl_tready       (s_rfnoc_ctrl_tready), +    .m_rfnoc_ctrl_tdata        (m_rfnoc_ctrl_tdata), +    .m_rfnoc_ctrl_tlast        (m_rfnoc_ctrl_tlast), +    .m_rfnoc_ctrl_tvalid       (m_rfnoc_ctrl_tvalid), +    .m_rfnoc_ctrl_tready       (m_rfnoc_ctrl_tready), +    .ctrlport_clk              (ce_clk), +    .ctrlport_rst              (ddc_rst), +    .m_ctrlport_req_wr         (ctrlport_req_wr), +    .m_ctrlport_req_rd         (ctrlport_req_rd), +    .m_ctrlport_req_addr       (ctrlport_req_addr), +    .m_ctrlport_req_data       (ctrlport_req_data), +    .m_ctrlport_req_byte_en    (), +    .m_ctrlport_req_has_time   (ctrlport_req_has_time), +    .m_ctrlport_req_time       (ctrlport_req_time), +    .m_ctrlport_resp_ack       (ctrlport_resp_ack), +    .m_ctrlport_resp_status    (AXIS_CTRL_STS_OKAY), +    .m_ctrlport_resp_data      (ctrlport_resp_data), +    .s_ctrlport_req_wr         (1'b0), +    .s_ctrlport_req_rd         (1'b0), +    .s_ctrlport_req_addr       (20'b0), +    .s_ctrlport_req_portid     (10'b0), +    .s_ctrlport_req_rem_epid   (16'b0), +    .s_ctrlport_req_rem_portid (10'b0), +    .s_ctrlport_req_data       (32'b0), +    .s_ctrlport_req_byte_en    (4'b0), +    .s_ctrlport_req_has_time   (1'b0), +    .s_ctrlport_req_time       (64'b0), +    .s_ctrlport_resp_ack       (), +    .s_ctrlport_resp_status    (), +    .s_ctrlport_resp_data      (), +    .axis_data_clk             (ce_clk), +    .axis_data_rst             (ddc_rst), +    .m_axis_tdata              (m_axis_data_tdata), +    .m_axis_tkeep              (), +    .m_axis_tlast              (m_axis_data_tlast), +    .m_axis_tvalid             (m_axis_data_tvalid), +    .m_axis_tready             (m_axis_data_tready), +    .m_axis_ttimestamp         (m_axis_data_ttimestamp), +    .m_axis_thas_time          (m_axis_data_thas_time), +    .m_axis_tlength            (m_axis_data_tlength), +    .m_axis_teov               (), +    .m_axis_teob               (m_axis_data_teob), +    .s_axis_tdata              (s_axis_data_tdata), +    .s_axis_tkeep              ({NUM_PORTS*NIPC{1'b1}}), +    .s_axis_tlast              (s_axis_data_tlast), +    .s_axis_tvalid             (s_axis_data_tvalid), +    .s_axis_tready             (s_axis_data_tready), +    .s_axis_ttimestamp         (s_axis_data_ttimestamp), +    .s_axis_thas_time          (s_axis_data_thas_time), +    .s_axis_teov               ({NUM_PORTS{1'b0}}), +    .s_axis_teob               (s_axis_data_teob) +  ); + + +  //--------------------------------------------------------------------------- +  // Register Translation +  //--------------------------------------------------------------------------- +  // +  // Each DDC block is allocated an address spaces. This block translates CTRL +  // port transactions in that space to settings bus. +  // +  //--------------------------------------------------------------------------- + +  wire [ 8*NUM_PORTS-1:0] set_addr; +  wire [32*NUM_PORTS-1:0] set_data; +  wire [   NUM_PORTS-1:0] set_has_time; +  wire [   NUM_PORTS-1:0] set_stb; +  wire [64*NUM_PORTS-1:0] set_time; +  wire [ 8*NUM_PORTS-1:0] rb_addr; +  reg  [64*NUM_PORTS-1:0] rb_data; +  wire [   NUM_PORTS-1:0] rb_stb; + +  ctrlport_to_settings_bus # ( +    .NUM_PORTS (NUM_PORTS) +  ) ctrlport_to_settings_bus_i ( +    .ctrlport_clk             (ce_clk), +    .ctrlport_rst             (ddc_rst), +    .s_ctrlport_req_wr        (ctrlport_req_wr), +    .s_ctrlport_req_rd        (ctrlport_req_rd), +    .s_ctrlport_req_addr      (ctrlport_req_addr), +    .s_ctrlport_req_data      (ctrlport_req_data), +    .s_ctrlport_req_has_time  (ctrlport_req_has_time), +    .s_ctrlport_req_time      (ctrlport_req_time), +    .s_ctrlport_resp_ack      (ctrlport_resp_ack), +    .s_ctrlport_resp_data     (ctrlport_resp_data), +    .set_data                 (set_data), +    .set_addr                 (set_addr), +    .set_stb                  (set_stb), +    .set_time                 (set_time), +    .set_has_time             (set_has_time), +    .rb_stb                   (rb_stb), +    .rb_addr                  (rb_addr), +    .rb_data                  (rb_data)); + + +  //--------------------------------------------------------------------------- +  // DDC Implementation +  //--------------------------------------------------------------------------- + +  // Unused signals +  wire [   NUM_PORTS-1:0] clear_tx_seqnum = 0; +  wire [16*NUM_PORTS-1:0] src_sid         = 0; +  wire [16*NUM_PORTS-1:0] next_dst_sid    = 0; + +  localparam MAX_N = CIC_MAX_DECIM * 2 << (NUM_HB-1); + +  genvar i; +  generate +    for (i = 0; i < NUM_PORTS; i = i + 1) begin : gen_ddc_chains +      wire        set_stb_int      = set_stb[i]; +      wire [7:0]  set_addr_int     = set_addr[8*i+7:8*i]; +      wire [31:0] set_data_int     = set_data[32*i+31:32*i]; +      wire [63:0] set_time_int     = set_time[64*i+63:64*i]; +      wire        set_has_time_int = set_has_time[i]; + +      // Build the expected tuser CHDR header +      cvita_hdr_encoder cvita_hdr_encoder_i ( +        .pkt_type       (2'b0), +        .eob            (m_axis_data_teob[i]), +        .has_time       (m_axis_data_thas_time[i]), +        .seqnum         (12'b0), +        .payload_length (m_axis_data_tlength[16*i +: 16]), +        .src_sid        (16'b0), +        .dst_sid        (16'b0), +        .vita_time      (m_axis_data_ttimestamp[64*i +: 64]), +        .header         (m_axis_data_tuser[128*i+:128]) +      ); + +      // Extract bit fields from outgoing tuser CHDR header +      assign s_axis_data_teob[i]              = s_axis_data_tuser[128*i+124 +:  1]; +      assign s_axis_data_thas_time[i]         = s_axis_data_tuser[128*i+125 +:  1]; +      assign s_axis_data_ttimestamp[64*i+:64] = s_axis_data_tuser[128*i+  0 +: 64]; + +      // TODO: Read-back register for number of FIR filter taps +      always @(*) begin +        case(rb_addr[8*i+7:8*i]) +          RB_COMPAT_NUM    : rb_data[64*i+63:64*i] <= {COMPAT_MAJOR, COMPAT_MINOR}; +          RB_NUM_HB        : rb_data[64*i+63:64*i] <= NUM_HB; +          RB_CIC_MAX_DECIM : rb_data[64*i+63:64*i] <= CIC_MAX_DECIM; +          default          : rb_data[64*i+63:64*i] <= 64'h0BADC0DE0BADC0DE; +        endcase +      end + +      //////////////////////////////////////////////////////////// +      // +      // Timed Commands +      // +      //////////////////////////////////////////////////////////// +      wire [31:0]  m_axis_tagged_tdata; +      wire         m_axis_tagged_tlast; +      wire         m_axis_tagged_tvalid; +      wire         m_axis_tagged_tready; +      wire [127:0] m_axis_tagged_tuser; +      wire         m_axis_tagged_tag; + +      wire         out_set_stb; +      wire [7:0]   out_set_addr; +      wire [31:0]  out_set_data; +      wire         timed_set_stb; +      wire [7:0]   timed_set_addr; +      wire [31:0]  timed_set_data; + +      wire         timed_cmd_fifo_full; + +      axi_tag_time #( +        .NUM_TAGS(1), +        .SR_TAG_ADDRS(SR_FREQ_ADDR)) +      axi_tag_time ( +        .clk(ce_clk), +        .reset(ddc_rst), +        .clear(clear_tx_seqnum[i]), +        .tick_rate(16'd1), +        .timed_cmd_fifo_full(timed_cmd_fifo_full), +        .s_axis_data_tdata(m_axis_data_tdata[i*ITEM_W+:ITEM_W]), .s_axis_data_tlast(m_axis_data_tlast[i]), +        .s_axis_data_tvalid(m_axis_data_tvalid[i]), .s_axis_data_tready(m_axis_data_tready[i]), +        .s_axis_data_tuser(m_axis_data_tuser[128*i+:128]), +        .m_axis_data_tdata(m_axis_tagged_tdata), .m_axis_data_tlast(m_axis_tagged_tlast), +        .m_axis_data_tvalid(m_axis_tagged_tvalid), .m_axis_data_tready(m_axis_tagged_tready), +        .m_axis_data_tuser(m_axis_tagged_tuser), .m_axis_data_tag(m_axis_tagged_tag), +        .in_set_stb(set_stb_int), .in_set_addr(set_addr_int), .in_set_data(set_data_int), +        .in_set_time(set_time_int), .in_set_has_time(set_has_time_int), +        .out_set_stb(out_set_stb), .out_set_addr(out_set_addr), .out_set_data(out_set_data), +        .timed_set_stb(timed_set_stb), .timed_set_addr(timed_set_addr), .timed_set_data(timed_set_data)); + +      // Hold off reading additional commands if internal FIFO is full +      assign rb_stb[i] = ~timed_cmd_fifo_full; + +      //////////////////////////////////////////////////////////// +      // +      // Reduce Rate +      // +      //////////////////////////////////////////////////////////// +      wire [31:0] sample_in_tdata, sample_out_tdata; +      wire sample_in_tuser, sample_in_eob; +      wire sample_in_tvalid, sample_in_tready, sample_in_tlast; +      wire sample_out_tvalid, sample_out_tready; +      wire clear_user; +      wire nc; +      axi_rate_change #( +        .WIDTH(33), +        .MAX_N(MAX_N), +        .MAX_M(1), +        .SR_N_ADDR(SR_N_ADDR), +        .SR_M_ADDR(SR_M_ADDR), +        .SR_CONFIG_ADDR(SR_CONFIG_ADDR)) +      axi_rate_change ( +        .clk(ce_clk), .reset(ddc_rst), .clear(clear_tx_seqnum[i]), .clear_user(clear_user), +        .src_sid(src_sid[16*i+15:16*i]), .dst_sid(next_dst_sid[16*i+15:16*i]), +        .set_stb(out_set_stb), .set_addr(out_set_addr), .set_data(out_set_data), +        .i_tdata({m_axis_tagged_tag,m_axis_tagged_tdata}), .i_tlast(m_axis_tagged_tlast), +        .i_tvalid(m_axis_tagged_tvalid), .i_tready(m_axis_tagged_tready), +        .i_tuser(m_axis_tagged_tuser), +        .o_tdata({nc,s_axis_data_tdata[i*ITEM_W+:ITEM_W]}), .o_tlast(s_axis_data_tlast[i]), .o_tvalid(s_axis_data_tvalid[i]), +        .o_tready(s_axis_data_tready[i]), .o_tuser(s_axis_data_tuser[128*i+:128]), +        .m_axis_data_tdata({sample_in_tuser,sample_in_tdata}), .m_axis_data_tlast(sample_in_tlast), +        .m_axis_data_tvalid(sample_in_tvalid), .m_axis_data_tready(sample_in_tready), +        .s_axis_data_tdata({1'b0,sample_out_tdata}), .s_axis_data_tlast(1'b0), +        .s_axis_data_tvalid(sample_out_tvalid), .s_axis_data_tready(sample_out_tready), +        .warning_long_throttle(), +        .error_extra_outputs(), +        .error_drop_pkt_lockup()); + +      assign sample_in_eob = m_axis_tagged_tuser[124]; //this should align with last packet output from axi_rate_change + +      //////////////////////////////////////////////////////////// +      // +      // Digital Down Converter +      // +      //////////////////////////////////////////////////////////// + +      ddc #( +        .SR_FREQ_ADDR(SR_FREQ_ADDR), +        .SR_SCALE_IQ_ADDR(SR_SCALE_IQ_ADDR), +        .SR_DECIM_ADDR(SR_DECIM_ADDR), +        .SR_MUX_ADDR(SR_MUX_ADDR), +        .SR_COEFFS_ADDR(SR_COEFFS_ADDR), +        .NUM_HB(NUM_HB), +        .CIC_MAX_DECIM(CIC_MAX_DECIM)) +      ddc ( +        .clk(ce_clk), .reset(ddc_rst), +        .clear(clear_user | clear_tx_seqnum[i]), // Use AXI Rate Change's clear user to reset block to initial state after EOB +        .set_stb(out_set_stb), .set_addr(out_set_addr), .set_data(out_set_data), +        .timed_set_stb(timed_set_stb), .timed_set_addr(timed_set_addr), .timed_set_data(timed_set_data), +        .sample_in_tdata(sample_in_tdata), .sample_in_tlast(sample_in_tlast), +        .sample_in_tvalid(sample_in_tvalid), .sample_in_tready(sample_in_tready), +        .sample_in_tuser(sample_in_tuser), .sample_in_eob(sample_in_eob), +        .sample_out_tdata(sample_out_tdata), .sample_out_tlast(), +        .sample_out_tvalid(sample_out_tvalid), .sample_out_tready(sample_out_tready) +        ); + +    end +  endgenerate + +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_ddc/rfnoc_block_ddc_regs.vh b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_ddc/rfnoc_block_ddc_regs.vh new file mode 100644 index 000000000..bc1bf4c46 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_ddc/rfnoc_block_ddc_regs.vh @@ -0,0 +1,27 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_ddc_regs (Header) +// +// Description: Header file for RFNoC DDC functionality. This includes  +// register offsets, bitfields and constants for the radio components. +// + +// For now, these offsets match the original DDC +localparam DDC_BASE_ADDR = 'h00; +localparam DDC_ADDR_W    = 8; + +localparam RB_COMPAT_NUM    = 0; +localparam RB_NUM_HB        = 1; +localparam RB_CIC_MAX_DECIM = 2; +localparam SR_N_ADDR        = 128; +localparam SR_M_ADDR        = 129; +localparam SR_CONFIG_ADDR   = 130; +localparam SR_FREQ_ADDR     = 132; +localparam SR_SCALE_IQ_ADDR = 133; +localparam SR_DECIM_ADDR    = 134; +localparam SR_MUX_ADDR      = 135; +localparam SR_COEFFS_ADDR   = 136; + diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_ddc/rfnoc_block_ddc_tb.sv b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_ddc/rfnoc_block_ddc_tb.sv new file mode 100644 index 000000000..8b0790909 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_ddc/rfnoc_block_ddc_tb.sv @@ -0,0 +1,386 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_ddc_tb +// +// Description:  Testbench for rfnoc_block_ddc +// + + +module rfnoc_block_ddc_tb(); + +  // Include macros and time declarations for use with PkgTestExec +  `include "test_exec.svh" + +  import PkgTestExec::*; +  import PkgChdrUtils::*; +  import PkgRfnocBlockCtrlBfm::*; + +  `include "rfnoc_block_ddc_regs.vh" + + +  //--------------------------------------------------------------------------- +  // Local Parameters +  //--------------------------------------------------------------------------- + +  // Simulation parameters +  localparam real CHDR_CLK_PER   = 5.0;   // CHDR clock rate +  localparam real DDC_CLK_PER    = 4.0;   // DUC IP clock rate +  localparam int  EXTENDED_TEST  = 0;     // Perform a longer test +  localparam int  SPP            = 256;   // Samples per packet +  localparam int  PKT_SIZE_BYTES = SPP*4; // Bytes per packet +  localparam int  STALL_PROB     = 25;    // BFM stall probability + +  // Block configuration +  localparam int CHDR_W        = 64; +  localparam int THIS_PORTID   = 'h123; +  localparam int MTU           = 8; +  localparam int NUM_PORTS     = 1; +  localparam int NUM_HB        = 3; +  localparam int CIC_MAX_DECIM = 255; + + +  //--------------------------------------------------------------------------- +  // Clocks +  //--------------------------------------------------------------------------- + +  bit rfnoc_chdr_clk; +  bit rfnoc_ctrl_clk; +  bit ce_clk; + +  sim_clock_gen #(CHDR_CLK_PER) rfnoc_chdr_clk_gen (.clk(rfnoc_chdr_clk), .rst()); +  sim_clock_gen #(CHDR_CLK_PER) rfnoc_ctrl_clk_gen (.clk(rfnoc_ctrl_clk), .rst()); +  sim_clock_gen #(DDC_CLK_PER)  ddc_clk_gen        (.clk(ce_clk), .rst()); + + +  //--------------------------------------------------------------------------- +  // Bus Functional Models +  //--------------------------------------------------------------------------- + +  RfnocBackendIf        backend            (rfnoc_chdr_clk, rfnoc_ctrl_clk); +  AxiStreamIf #(32)     m_ctrl             (rfnoc_ctrl_clk, 1'b0); +  AxiStreamIf #(32)     s_ctrl             (rfnoc_ctrl_clk, 1'b0); +  AxiStreamIf #(CHDR_W) m_chdr [NUM_PORTS] (rfnoc_chdr_clk, 1'b0); +  AxiStreamIf #(CHDR_W) s_chdr [NUM_PORTS] (rfnoc_chdr_clk, 1'b0); + +  // Bus functional model for a software block controller +  RfnocBlockCtrlBfm #(.CHDR_W(CHDR_W)) blk_ctrl = +    new(backend, m_ctrl, s_ctrl); + +  // Connect block controller to BFMs +  for (genvar i = 0; i < NUM_PORTS; i++) begin : gen_bfm_connections +    initial begin +      blk_ctrl.connect_master_data_port(i, m_chdr[i], PKT_SIZE_BYTES); +      blk_ctrl.connect_slave_data_port(i, s_chdr[i]); +      blk_ctrl.set_master_stall_prob(i, STALL_PROB); +      blk_ctrl.set_slave_stall_prob(i, STALL_PROB); +    end +  end + + +  //--------------------------------------------------------------------------- +  // DUT +  //--------------------------------------------------------------------------- + +  logic [NUM_PORTS*CHDR_W-1:0] s_rfnoc_chdr_tdata; +  logic [       NUM_PORTS-1:0] s_rfnoc_chdr_tlast; +  logic [       NUM_PORTS-1:0] s_rfnoc_chdr_tvalid; +  logic [       NUM_PORTS-1:0] s_rfnoc_chdr_tready; + +  logic [NUM_PORTS*CHDR_W-1:0] m_rfnoc_chdr_tdata; +  logic [       NUM_PORTS-1:0] m_rfnoc_chdr_tlast; +  logic [       NUM_PORTS-1:0] m_rfnoc_chdr_tvalid; +  logic [       NUM_PORTS-1:0] m_rfnoc_chdr_tready; + +  // Map the array of BFMs to a flat vector for the DUT +  genvar i; +  for (i = 0; i < NUM_PORTS; i++) begin : gen_dut_connections +    // Connect BFM master to DUT slave port +    assign s_rfnoc_chdr_tdata[CHDR_W*i+:CHDR_W] = m_chdr[i].tdata; +    assign s_rfnoc_chdr_tlast[i]                = m_chdr[i].tlast; +    assign s_rfnoc_chdr_tvalid[i]               = m_chdr[i].tvalid; +    assign m_chdr[i].tready                     = s_rfnoc_chdr_tready[i]; + +    // Connect BFM slave to DUT master port +    assign s_chdr[i].tdata        = m_rfnoc_chdr_tdata[CHDR_W*i+:CHDR_W]; +    assign s_chdr[i].tlast        = m_rfnoc_chdr_tlast[i]; +    assign s_chdr[i].tvalid       = m_rfnoc_chdr_tvalid[i]; +    assign m_rfnoc_chdr_tready[i] = s_chdr[i].tready; +  end + +  rfnoc_block_ddc #( +    .THIS_PORTID    (THIS_PORTID), +    .CHDR_W         (CHDR_W), +    .NUM_PORTS      (NUM_PORTS), +    .MTU            (MTU), +    .NUM_HB         (NUM_HB), +    .CIC_MAX_DECIM  (CIC_MAX_DECIM) +  ) rfnoc_block_ddc_i ( +    .rfnoc_chdr_clk          (backend.chdr_clk), +    .ce_clk                  (ce_clk), +    .s_rfnoc_chdr_tdata      (s_rfnoc_chdr_tdata), +    .s_rfnoc_chdr_tlast      (s_rfnoc_chdr_tlast), +    .s_rfnoc_chdr_tvalid     (s_rfnoc_chdr_tvalid), +    .s_rfnoc_chdr_tready     (s_rfnoc_chdr_tready), +    .m_rfnoc_chdr_tdata      (m_rfnoc_chdr_tdata), +    .m_rfnoc_chdr_tlast      (m_rfnoc_chdr_tlast), +    .m_rfnoc_chdr_tvalid     (m_rfnoc_chdr_tvalid), +    .m_rfnoc_chdr_tready     (m_rfnoc_chdr_tready), +    .rfnoc_core_config       (backend.cfg), +    .rfnoc_core_status       (backend.sts), +    .rfnoc_ctrl_clk          (backend.ctrl_clk), +    .s_rfnoc_ctrl_tdata      (m_ctrl.tdata), +    .s_rfnoc_ctrl_tlast      (m_ctrl.tlast), +    .s_rfnoc_ctrl_tvalid     (m_ctrl.tvalid), +    .s_rfnoc_ctrl_tready     (m_ctrl.tready), +    .m_rfnoc_ctrl_tdata      (s_ctrl.tdata), +    .m_rfnoc_ctrl_tlast      (s_ctrl.tlast), +    .m_rfnoc_ctrl_tvalid     (s_ctrl.tvalid), +    .m_rfnoc_ctrl_tready     (s_ctrl.tready) +  ); + + +  //--------------------------------------------------------------------------- +  // Helper Tasks +  //--------------------------------------------------------------------------- + +  // Translate the desired register access to a ctrlport write request. +  task automatic write_reg(int port, byte addr, bit [31:0] value); +    blk_ctrl.reg_write(256*8*port + addr*8, value); +  endtask : write_reg + + +  // Translate the desired register access to a ctrlport read request. +  task automatic read_user_reg(int port, byte addr, output logic [63:0] value); +    blk_ctrl.reg_read(256*8*port + addr*8 + 0, value[31: 0]); +    blk_ctrl.reg_read(256*8*port + addr*8 + 4, value[63:32]); +  endtask : read_user_reg + + +  task automatic set_decim_rate(int port, input int decim_rate); +    logic [7:0] cic_rate; +    logic [1:0] hb_enables; +    int _decim_rate; + +    cic_rate = 8'd0; +    hb_enables = 2'b0; +    _decim_rate = decim_rate; + +    // Calculate which half bands to enable and whatever is left over set the CIC +    while ((_decim_rate[0] == 0) && (hb_enables < NUM_HB)) begin +      hb_enables += 1'b1; +      _decim_rate = _decim_rate >> 1; +    end +    // CIC rate cannot be set to 0 +    cic_rate = (_decim_rate[7:0] == 8'd0) ? 8'd1 : _decim_rate[7:0]; +    `ASSERT_ERROR( +      hb_enables <= NUM_HB, +      "Enabled halfbands may not exceed total number of half bands." +    ); +    `ASSERT_ERROR( +      cic_rate > 0 && cic_rate <= CIC_MAX_DECIM, +      "CIC Decimation rate must be positive, not exceed the max cic decimation rate, and cannot equal 0!" +    ); + +    // Setup DDC +    $display("Set decimation to %0d", decim_rate); +    $display("- Number of enabled HBs: %0d", hb_enables); +    $display("- CIC Rate:              %0d", cic_rate); +    write_reg(port, SR_N_ADDR, decim_rate);                  // Set decimation rate in AXI rate change +    write_reg(port, SR_DECIM_ADDR, {hb_enables,cic_rate});   // Enable HBs, set CIC rate +  endtask + + +  task automatic send_ramp ( +    input int unsigned port, +    input int unsigned decim_rate, +    // (Optional) For testing passing through partial packets +    input logic drop_partial_packet = 1'b0, +    input int unsigned extra_samples = 0 +  ); +    set_decim_rate(port, decim_rate); + +    // Setup DDC +    write_reg(port, SR_CONFIG_ADDR, 32'd1);       // Enable clear EOB +    write_reg(port, SR_FREQ_ADDR, 32'd0);         // Phase increment +    write_reg(port, SR_SCALE_IQ_ADDR, (1 << 14)); // Scaling, set to 1 + +    // Send a short ramp, should pass through unchanged +    fork +      begin +        chdr_word_t send_payload[$]; +        packet_info_t pkt_info; + +        pkt_info = 0; +        for (int i = 0; i < decim_rate*(PKT_SIZE_BYTES/8 + extra_samples); i++) begin +          send_payload.push_back({16'(2*i/decim_rate), 16'(2*i/decim_rate), 16'((2*i+1)/decim_rate), 16'((2*i+1)/decim_rate)}); +        end +        $display("Send ramp (%0d words)", send_payload.size()); +        pkt_info.eob = 1; +        blk_ctrl.send_packets(port, send_payload, /*data_bytes*/, /*metadata*/, pkt_info); +        blk_ctrl.wait_complete(port); +        $display("Send ramp complete"); +      end +      begin +        string s; +        logic [63:0]  samples, samples_old; +        chdr_word_t   recv_payload[$], temp_payload[$]; +        chdr_word_t   metadata[$]; +        int           data_bytes; +        packet_info_t pkt_info; + +        $display("Check ramp"); +        if (~drop_partial_packet && (extra_samples > 0)) begin +          blk_ctrl.recv_adv(port, temp_payload, data_bytes, metadata, pkt_info); +          $sformat(s, "Invalid EOB state! Expected %b, Received: %b", 1'b0, pkt_info.eob); +          `ASSERT_ERROR(pkt_info.eob == 1'b0, s); +        end +        $display("Receiving packet"); +        blk_ctrl.recv_adv(port, recv_payload, data_bytes, metadata, pkt_info); +        $display("Received!"); +        $sformat(s, "Invalid EOB state! Expected %b, Received: %b", 1'b1, pkt_info.eob); +        `ASSERT_ERROR(pkt_info.eob == 1'b1, s); +        recv_payload = {temp_payload, recv_payload}; +        if (drop_partial_packet) begin +          $sformat(s, "Incorrect packet size! Expected: %0d, Actual: %0d", PKT_SIZE_BYTES/8, recv_payload.size()); +          `ASSERT_ERROR(recv_payload.size() == PKT_SIZE_BYTES/8, s); +        end else begin +          $sformat(s, "Incorrect packet size! Expected: %0d, Actual: %0d", PKT_SIZE_BYTES/8, recv_payload.size() + extra_samples); +          `ASSERT_ERROR(recv_payload.size() == PKT_SIZE_BYTES/8 + extra_samples, s); +        end +        samples = 64'd0; +        samples_old = 64'd0; +        for (int i = 0; i < PKT_SIZE_BYTES/8; i++) begin +          samples = recv_payload[i]; +          for (int j = 0; j < 4; j++) begin +            // Need to check a range of values due to imperfect gain compensation +            $sformat(s, "Ramp word %0d invalid! Expected: %0d-%0d, Received: %0d", 2*i, +                samples_old[16*j +: 16], samples_old[16*j +: 16]+16'd4, samples[16*j +: 16]); +            `ASSERT_ERROR((samples_old[16*j +: 16]+16'd4 >= samples[16*j +: 16]) && (samples >= samples_old[16*j +: 16]), s); +          end +          samples_old = samples; +        end +        $display("Check complete"); +      end +    join +  endtask + + +  //--------------------------------------------------------------------------- +  // Test Process +  //--------------------------------------------------------------------------- + +  initial begin : tb_main +    const int port = 0; +    test.start_tb("rfnoc_block_ddc_tb"); + +    // Start the BFMs running +    blk_ctrl.run(); + + +    //------------------------------------------------------------------------- +    // Reset +    //------------------------------------------------------------------------- + +    test.start_test("Wait for Reset", 10us); +    fork +      blk_ctrl.reset_chdr(); +      blk_ctrl.reset_ctrl(); +    join; +    test.end_test(); + + +    //------------------------------------------------------------------------- +    // Check NoC ID and Block Info +    //------------------------------------------------------------------------- + +    test.start_test("Verify Block Info", 2us); +    `ASSERT_ERROR(blk_ctrl.get_noc_id() == rfnoc_block_ddc_i.NOC_ID, "Incorrect NOC_ID Value"); +    `ASSERT_ERROR(blk_ctrl.get_num_data_i() == NUM_PORTS, "Incorrect NUM_DATA_I Value"); +    `ASSERT_ERROR(blk_ctrl.get_num_data_o() == NUM_PORTS, "Incorrect NUM_DATA_O Value"); +    `ASSERT_ERROR(blk_ctrl.get_mtu() == MTU, "Incorrect MTU Value"); +    test.end_test(); + + +    //------------------------------------------------------------------------- +    // Test read-back regs +    //------------------------------------------------------------------------- + +    begin +      logic [63:0] val64; +      test.start_test("Test registers", 10us); +      read_user_reg(port, RB_NUM_HB, val64); +      `ASSERT_ERROR(val64 == NUM_HB, "Register NUM_HB didn't read back expected value"); +      read_user_reg(port, RB_CIC_MAX_DECIM, val64); +      `ASSERT_ERROR(val64 == CIC_MAX_DECIM, "Register CIC_MAX_DECIM didn't read back expected value"); +      test.end_test(); +    end + + +    //------------------------------------------------------------------------- +    // Test various decimation rates +    //------------------------------------------------------------------------- + +    begin +      test.start_test("Decimate by 1, 2, 3, 4, 6, 8, 12, 13, 16, 24, 40, 255, 2040", 0.5ms); + +      $display("Note: This test will take a long time!"); + +      // List of rates to catch most issues +      send_ramp(port, 1);                       // HBs enabled: 0, CIC rate: 1 +      send_ramp(port, 2);                       // HBs enabled: 1, CIC rate: 1 +      send_ramp(port, 3);                       // HBs enabled: 0, CIC rate: 3 +      send_ramp(port, 4);                       // HBs enabled: 2, CIC rate: 1 +      if (EXTENDED_TEST) send_ramp(port, 6);    // HBs enabled: 1, CIC rate: 3 +      send_ramp(port, 8);                       // HBs enabled: 3, CIC rate: 1 +      send_ramp(port, 12);                      // HBs enabled: 2, CIC rate: 3 +      send_ramp(port, 13);                      // HBs enabled: 0, CIC rate: 13 +      if (EXTENDED_TEST) send_ramp(port, 16);   // HBs enabled: 3, CIC rate: 2 +      if (EXTENDED_TEST) send_ramp(port, 24);   // HBs enabled: 3, CIC rate: 3 +      send_ramp(port, 40);                      // HBs enabled: 3, CIC rate: 5 +      if (EXTENDED_TEST) send_ramp(port, 200);  // HBs enabled: 3, CIC rate: 25 +      send_ramp(port, 255);                     // HBs enabled: 0, CIC rate: 255 +      if (EXTENDED_TEST) send_ramp(port, 2040); // HBs enabled: 3, CIC rate: 255 + +      test.end_test(); +    end + + +    //------------------------------------------------------------------------- +    // Test timed tune +    //------------------------------------------------------------------------- + +    // This test has not been implemented because the RFNoC FFT has not been +    // ported yet. + + +    //------------------------------------------------------------------------- +    // Test passing through a partial packet +    //------------------------------------------------------------------------- + +    test.start_test("Pass through partial packet"); +    send_ramp(port, 2, 0, 4); +    send_ramp(port, 3, 0, 4); +    send_ramp(port, 4, 0, 4); +    if (EXTENDED_TEST) send_ramp(port, 8, 0, 4); +    send_ramp(port, 13, 0, 4); +    if (EXTENDED_TEST) send_ramp(port, 24, 0, 4); +    test.end_test(); + + +    //------------------------------------------------------------------------- +    // Finish +    //------------------------------------------------------------------------- + +    // 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 +    rfnoc_chdr_clk_gen.kill(); +    rfnoc_ctrl_clk_gen.kill(); +    ddc_clk_gen.kill(); +  end +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_duc/Makefile b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_duc/Makefile new file mode 100644 index 000000000..6d1da3d60 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_duc/Makefile @@ -0,0 +1,67 @@ +# +# Copyright 2019 Ettus Research, A National Instruments Company +# +# 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_preamble after defining BASE_DIR +include $(BASE_DIR)/../tools/make/viv_sim_preamble.mak + +#------------------------------------------------- +# IP Specific +#------------------------------------------------- +# If simulation contains IP, define the IP_DIR and point +# it to the base level IP directory +LIB_IP_DIR = $(BASE_DIR)/../lib/ip + +# Include makefiles and sources for all IP components +# *after* defining the LIB_IP_DIR +include $(LIB_IP_DIR)/axi_hb47/Makefile.inc +include $(LIB_IP_DIR)/complex_multiplier_dds/Makefile.inc +include $(LIB_IP_DIR)/dds_sin_cos_lut_only/Makefile.inc +include $(BASE_DIR)/x300/coregen_dsp/Makefile.srcs + +DESIGN_SRCS += $(abspath \ +$(LIB_IP_AXI_HB47_SRCS) \ +$(LIB_IP_COMPLEX_MULTIPLIER_DDS_SRCS) \ +$(LIB_IP_DDS_SIN_COS_LUT_ONLY_SRCS) \ +$(COREGEN_DSP_SRCS) \ +) + +#------------------------------------------------- +# Design Specific +#------------------------------------------------- +# Include makefiles and sources for the DUT and its dependencies +include $(BASE_DIR)/../lib/rfnoc/core/Makefile.srcs +include $(BASE_DIR)/../lib/rfnoc/utils/Makefile.srcs +include Makefile.srcs + +DESIGN_SRCS += $(abspath \ +$(RFNOC_CORE_SRCS) \ +$(RFNOC_UTIL_SRCS) \ +$(RFNOC_BLOCK_DUC_SRCS) \ +) + +#------------------------------------------------- +# Testbench Specific +#------------------------------------------------- +# Define only one toplevel module +SIM_TOP = rfnoc_block_duc_tb + +# Add test bench, user design under test, and +# additional user created files +SIM_SRCS = \ +$(abspath rfnoc_block_duc_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/blocks/rfnoc_block_duc/Makefile.srcs b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_duc/Makefile.srcs new file mode 100644 index 000000000..69b6eaece --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_duc/Makefile.srcs @@ -0,0 +1,11 @@ +# +# Copyright 2019 Ettus Research, A National Instruments Company +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# + +RFNOC_BLOCK_DUC_SRCS = $(abspath $(addprefix $(BASE_DIR)/../lib/rfnoc/blocks/rfnoc_block_duc/, \ +../rfnoc_block_ddc/noc_shell_ddc.v \ +rfnoc_block_duc_regs.vh \ +rfnoc_block_duc.v \ +)) diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_duc/rfnoc_block_duc.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_duc/rfnoc_block_duc.v new file mode 100644 index 000000000..400e9d270 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_duc/rfnoc_block_duc.v @@ -0,0 +1,387 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_duc +// +// Description:  An digital up-converter block for RFNoC. +// +// Parameters: +// +//   THIS_PORTID    : Control crossbar port to which this block is connected +//   CHDR_W         : AXIS CHDR interface data width +//   NUM_PORTS      : Number of DUC signal processing chains +//   MTU            : Maximum transmission unit (i.e., maximum packet size) in +//                    CHDR words is 2**MTU. +//   CTRL_FIFO_SIZE : Size of the Control Port slave FIFO. This affects the +//                    number of outstanding commands that can be pending. +//   NUM_HB         : Number of half-band filter blocks to include (0-3) +//   CIC_MAX_INTERP : Maximum interpolation to support in the CIC filter +// + +module rfnoc_block_duc #( +  parameter THIS_PORTID    = 0, +  parameter CHDR_W         = 64, +  parameter NUM_PORTS      = 2, +  parameter MTU            = 10, +  parameter CTRL_FIFO_SIZE = 6, +  parameter NUM_HB         = 2, +  parameter CIC_MAX_INTERP = 128 +) ( +  //--------------------------------------------------------------------------- +  // AXIS CHDR Port +  //--------------------------------------------------------------------------- + +  input wire rfnoc_chdr_clk, +  input wire ce_clk, + +  // CHDR inputs from framework +  input  wire [NUM_PORTS*CHDR_W-1:0] s_rfnoc_chdr_tdata, +  input  wire [       NUM_PORTS-1:0] s_rfnoc_chdr_tlast, +  input  wire [       NUM_PORTS-1:0] s_rfnoc_chdr_tvalid, +  output wire [       NUM_PORTS-1:0] s_rfnoc_chdr_tready, + +  // CHDR outputs to framework +  output wire [NUM_PORTS*CHDR_W-1:0] m_rfnoc_chdr_tdata, +  output wire [       NUM_PORTS-1:0] m_rfnoc_chdr_tlast, +  output wire [       NUM_PORTS-1:0] m_rfnoc_chdr_tvalid, +  input  wire [       NUM_PORTS-1:0] m_rfnoc_chdr_tready, + +  // Backend interface +  input  wire [511:0] rfnoc_core_config, +  output wire [511:0] rfnoc_core_status, + +  //--------------------------------------------------------------------------- +  // AXIS CTRL Port +  //--------------------------------------------------------------------------- + +  input wire rfnoc_ctrl_clk, + +  // CTRL port requests from framework +  input  wire [31:0] s_rfnoc_ctrl_tdata, +  input  wire        s_rfnoc_ctrl_tlast, +  input  wire        s_rfnoc_ctrl_tvalid, +  output wire        s_rfnoc_ctrl_tready, + +  // CTRL port requests to framework +  output wire [31:0] m_rfnoc_ctrl_tdata, +  output wire        m_rfnoc_ctrl_tlast, +  output wire        m_rfnoc_ctrl_tvalid, +  input  wire        m_rfnoc_ctrl_tready +); + +  // These are the only supported values for now +  localparam ITEM_W = 32; +  localparam NIPC   = 1; + +  localparam NOC_ID = 'hD0C0_0000; + +  localparam COMPAT_MAJOR  = 16'h0; +  localparam COMPAT_MINOR  = 16'h0; + +  `include "rfnoc_block_duc_regs.vh" +  `include "../../core/rfnoc_axis_ctrl_utils.vh" + + +  //--------------------------------------------------------------------------- +  // Signal Declarations +  //--------------------------------------------------------------------------- + +  wire rfnoc_chdr_rst; + +  wire        ctrlport_req_wr; +  wire        ctrlport_req_rd; +  wire [19:0] ctrlport_req_addr; +  wire [31:0] ctrlport_req_data; +  wire        ctrlport_req_has_time; +  wire [63:0] ctrlport_req_time; +  wire        ctrlport_resp_ack; +  wire [31:0] ctrlport_resp_data; + +  wire [NUM_PORTS*ITEM_W-1:0] m_axis_data_tdata; +  wire [       NUM_PORTS-1:0] m_axis_data_tlast; +  wire [       NUM_PORTS-1:0] m_axis_data_tvalid; +  wire [       NUM_PORTS-1:0] m_axis_data_tready; +  wire [    NUM_PORTS*64-1:0] m_axis_data_ttimestamp; +  wire [       NUM_PORTS-1:0] m_axis_data_thas_time; +  wire [    NUM_PORTS*16-1:0] m_axis_data_tlength; +  wire [       NUM_PORTS-1:0] m_axis_data_teob; +  wire [   NUM_PORTS*128-1:0] m_axis_data_tuser; + +  wire [NUM_PORTS*ITEM_W-1:0] s_axis_data_tdata; +  wire [       NUM_PORTS-1:0] s_axis_data_tlast; +  wire [       NUM_PORTS-1:0] s_axis_data_tvalid; +  wire [       NUM_PORTS-1:0] s_axis_data_tready; +  wire [   NUM_PORTS*128-1:0] s_axis_data_tuser; +  wire [       NUM_PORTS-1:0] s_axis_data_teob; +  wire [    NUM_PORTS*64-1:0] s_axis_data_ttimestamp; +  wire [       NUM_PORTS-1:0] s_axis_data_thas_time; + +  wire duc_rst; + +  // Cross the CHDR reset to the ce_clk domain +  synchronizer duc_rst_sync_i ( +    .clk (ce_clk), +    .rst (1'b0), +    .in  (rfnoc_chdr_rst), +    .out (duc_rst) +  ); + + +  //--------------------------------------------------------------------------- +  // NoC Shell +  //--------------------------------------------------------------------------- + +  // TODO: Replace noc_shell_radio with a customized block +  noc_shell_ddc #( +    .NOC_ID          (NOC_ID), +    .THIS_PORTID     (THIS_PORTID), +    .CHDR_W          (CHDR_W), +    .CTRLPORT_SLV_EN (0), +    .CTRLPORT_MST_EN (1), +    .CTRL_FIFO_SIZE  (CTRL_FIFO_SIZE), +    .NUM_DATA_I      (NUM_PORTS), +    .NUM_DATA_O      (NUM_PORTS), +    .ITEM_W          (ITEM_W), +    .NIPC            (NIPC), +    .PYLD_FIFO_SIZE  (MTU), +    .MTU             (MTU) +  ) noc_shell_ddc_i ( +    .rfnoc_chdr_clk            (rfnoc_chdr_clk), +    .rfnoc_chdr_rst            (rfnoc_chdr_rst), +    .rfnoc_ctrl_clk            (rfnoc_ctrl_clk), +    .rfnoc_ctrl_rst            (), +    .rfnoc_core_config         (rfnoc_core_config), +    .rfnoc_core_status         (rfnoc_core_status), +    .s_rfnoc_chdr_tdata        (s_rfnoc_chdr_tdata), +    .s_rfnoc_chdr_tlast        (s_rfnoc_chdr_tlast), +    .s_rfnoc_chdr_tvalid       (s_rfnoc_chdr_tvalid), +    .s_rfnoc_chdr_tready       (s_rfnoc_chdr_tready), +    .m_rfnoc_chdr_tdata        (m_rfnoc_chdr_tdata), +    .m_rfnoc_chdr_tlast        (m_rfnoc_chdr_tlast), +    .m_rfnoc_chdr_tvalid       (m_rfnoc_chdr_tvalid), +    .m_rfnoc_chdr_tready       (m_rfnoc_chdr_tready), +    .s_rfnoc_ctrl_tdata        (s_rfnoc_ctrl_tdata), +    .s_rfnoc_ctrl_tlast        (s_rfnoc_ctrl_tlast), +    .s_rfnoc_ctrl_tvalid       (s_rfnoc_ctrl_tvalid), +    .s_rfnoc_ctrl_tready       (s_rfnoc_ctrl_tready), +    .m_rfnoc_ctrl_tdata        (m_rfnoc_ctrl_tdata), +    .m_rfnoc_ctrl_tlast        (m_rfnoc_ctrl_tlast), +    .m_rfnoc_ctrl_tvalid       (m_rfnoc_ctrl_tvalid), +    .m_rfnoc_ctrl_tready       (m_rfnoc_ctrl_tready), +    .ctrlport_clk              (ce_clk), +    .ctrlport_rst              (duc_rst), +    .m_ctrlport_req_wr         (ctrlport_req_wr), +    .m_ctrlport_req_rd         (ctrlport_req_rd), +    .m_ctrlport_req_addr       (ctrlport_req_addr), +    .m_ctrlport_req_data       (ctrlport_req_data), +    .m_ctrlport_req_byte_en    (), +    .m_ctrlport_req_has_time   (ctrlport_req_has_time), +    .m_ctrlport_req_time       (ctrlport_req_time), +    .m_ctrlport_resp_ack       (ctrlport_resp_ack), +    .m_ctrlport_resp_status    (AXIS_CTRL_STS_OKAY), +    .m_ctrlport_resp_data      (ctrlport_resp_data), +    .s_ctrlport_req_wr         (1'b0), +    .s_ctrlport_req_rd         (1'b0), +    .s_ctrlport_req_addr       (20'b0), +    .s_ctrlport_req_portid     (10'b0), +    .s_ctrlport_req_rem_epid   (16'b0), +    .s_ctrlport_req_rem_portid (10'b0), +    .s_ctrlport_req_data       (32'b0), +    .s_ctrlport_req_byte_en    (4'b0), +    .s_ctrlport_req_has_time   (1'b0), +    .s_ctrlport_req_time       (64'b0), +    .s_ctrlport_resp_ack       (), +    .s_ctrlport_resp_status    (), +    .s_ctrlport_resp_data      (), +    .axis_data_clk             (ce_clk), +    .axis_data_rst             (duc_rst), +    .m_axis_tdata              (m_axis_data_tdata), +    .m_axis_tkeep              (), +    .m_axis_tlast              (m_axis_data_tlast), +    .m_axis_tvalid             (m_axis_data_tvalid), +    .m_axis_tready             (m_axis_data_tready), +    .m_axis_ttimestamp         (m_axis_data_ttimestamp), +    .m_axis_thas_time          (m_axis_data_thas_time), +    .m_axis_tlength            (m_axis_data_tlength), +    .m_axis_teov               (), +    .m_axis_teob               (m_axis_data_teob), +    .s_axis_tdata              (s_axis_data_tdata), +    .s_axis_tkeep              ({NUM_PORTS*NIPC{1'b1}}), +    .s_axis_tlast              (s_axis_data_tlast), +    .s_axis_tvalid             (s_axis_data_tvalid), +    .s_axis_tready             (s_axis_data_tready), +    .s_axis_ttimestamp         (s_axis_data_ttimestamp), +    .s_axis_thas_time          (s_axis_data_thas_time), +    .s_axis_teov               ({NUM_PORTS{1'b0}}), +    .s_axis_teob               (s_axis_data_teob) +  ); + + +  //--------------------------------------------------------------------------- +  // Register Translation +  //--------------------------------------------------------------------------- +  // +  // Each DUC block is allocated an address spaces. This block translates CTRL +  // port transactions in that space to settings bus. +  // +  //--------------------------------------------------------------------------- + +  wire [ 8*NUM_PORTS-1:0] set_addr; +  wire [32*NUM_PORTS-1:0] set_data; +  wire [   NUM_PORTS-1:0] set_has_time; +  wire [   NUM_PORTS-1:0] set_stb; +  wire [64*NUM_PORTS-1:0] set_time; +  wire [ 8*NUM_PORTS-1:0] rb_addr; +  reg  [64*NUM_PORTS-1:0] rb_data; + +  ctrlport_to_settings_bus # ( +    .NUM_PORTS (NUM_PORTS) +  ) ctrlport_to_settings_bus_i ( +    .ctrlport_clk             (ce_clk), +    .ctrlport_rst             (duc_rst), +    .s_ctrlport_req_wr        (ctrlport_req_wr), +    .s_ctrlport_req_rd        (ctrlport_req_rd), +    .s_ctrlport_req_addr      (ctrlport_req_addr), +    .s_ctrlport_req_data      (ctrlport_req_data), +    .s_ctrlport_req_has_time  (ctrlport_req_has_time), +    .s_ctrlport_req_time      (ctrlport_req_time), +    .s_ctrlport_resp_ack      (ctrlport_resp_ack), +    .s_ctrlport_resp_data     (ctrlport_resp_data), +    .set_data                 (set_data), +    .set_addr                 (set_addr), +    .set_stb                  (set_stb), +    .set_time                 (set_time), +    .set_has_time             (set_has_time), +    .rb_stb                   ({NUM_PORTS{1'b1}}), +    .rb_addr                  (rb_addr), +    .rb_data                  (rb_data)); + + +  //--------------------------------------------------------------------------- +  // DUC Implementation +  //--------------------------------------------------------------------------- + +  // Unused signals +  wire [   NUM_PORTS-1:0] clear_tx_seqnum = 0; +  wire [16*NUM_PORTS-1:0] src_sid         = 0; +  wire [16*NUM_PORTS-1:0] next_dst_sid    = 0; + +  localparam MAX_M = CIC_MAX_INTERP * 2<<(NUM_HB-1); + +  genvar i; +  generate +    for (i = 0; i < NUM_PORTS; i = i + 1) begin : gen_duc_chains +      wire clear_user; +      wire clear_duc = clear_tx_seqnum[i] | clear_user; + +      wire        set_stb_int      = set_stb[i]; +      wire [7:0]  set_addr_int     = set_addr[8*i+7:8*i]; +      wire [31:0] set_data_int     = set_data[32*i+31:32*i]; +      wire [63:0] set_time_int     = set_time[64*i+63:64*i]; +      wire        set_has_time_int = set_has_time[i]; + +      // Build the expected tuser CHDR header +      cvita_hdr_encoder cvita_hdr_encoder_i ( +        .pkt_type       (2'b0), +        .eob            (m_axis_data_teob[i]), +        .has_time       (m_axis_data_thas_time[i]), +        .seqnum         (12'b0), +        .payload_length (m_axis_data_tlength[16*i +: 16]), +        .src_sid        (16'b0), +        .dst_sid        (16'b0), +        .vita_time      (m_axis_data_ttimestamp[64*i +: 64]), +        .header         (m_axis_data_tuser[128*i+:128]) +      ); + +      // Extract bit fields from outgoing tuser CHDR header +      assign s_axis_data_teob[i]              = s_axis_data_tuser[128*i+124 +:  1]; +      assign s_axis_data_thas_time[i]         = s_axis_data_tuser[128*i+125 +:  1]; +      assign s_axis_data_ttimestamp[64*i+:64] = s_axis_data_tuser[128*i+  0 +: 64]; + +      // TODO Readback register for number of FIR filter taps +      always @(*) begin +        case(rb_addr[i*8+7:i*8]) +          RB_COMPAT_NUM     : rb_data[i*64+63:i*64] <= {COMPAT_MAJOR, COMPAT_MINOR}; +          RB_NUM_HB         : rb_data[i*64+63:i*64] <= NUM_HB; +          RB_CIC_MAX_INTERP : rb_data[i*64+63:i*64] <= CIC_MAX_INTERP; +          default           : rb_data[i*64+63:i*64] <= 64'h0BADC0DE0BADC0DE; +        endcase +      end + +      //////////////////////////////////////////////////////////// +      // +      // Timed CORDIC +      // - Implements timed cordic tunes. Placed between AXI Wrapper +      //   and AXI Rate Change due to it needing access to the +      //   vita time of the samples. +      // +      //////////////////////////////////////////////////////////// +      wire [31:0]  m_axis_rc_tdata; +      wire         m_axis_rc_tlast; +      wire         m_axis_rc_tvalid; +      wire         m_axis_rc_tready; +      wire [127:0] m_axis_rc_tuser; + +      dds_timed #( +        .SR_FREQ_ADDR(SR_FREQ_ADDR), +        .SR_SCALE_IQ_ADDR(SR_SCALE_IQ_ADDR)) +      dds_timed ( +        .clk(ce_clk), .reset(duc_rst), .clear(clear_tx_seqnum[i]), +        .timed_cmd_fifo_full(), +        .set_stb(set_stb_int), .set_addr(set_addr_int), .set_data(set_data_int), +        .set_time(set_time_int), .set_has_time(set_has_time_int), +        .i_tdata(m_axis_rc_tdata), .i_tlast(m_axis_rc_tlast), .i_tvalid(m_axis_rc_tvalid), +        .i_tready(m_axis_rc_tready), .i_tuser(m_axis_rc_tuser), +        .o_tdata(s_axis_data_tdata[ITEM_W*i+:ITEM_W]), .o_tlast(s_axis_data_tlast[i]), .o_tvalid(s_axis_data_tvalid[i]), +        .o_tready(s_axis_data_tready[i]), .o_tuser(s_axis_data_tuser[128*i+:128])); + +      //////////////////////////////////////////////////////////// +      // +      // Increase Rate +      // +      //////////////////////////////////////////////////////////// +      wire [31:0] sample_tdata, sample_duc_tdata; +      wire sample_tvalid, sample_tready; +      wire sample_duc_tvalid, sample_duc_tready; +      axi_rate_change #( +        .WIDTH(32), +        .MAX_N(1), +        .MAX_M(MAX_M), +        .SR_N_ADDR(SR_N_ADDR), +        .SR_M_ADDR(SR_M_ADDR), +        .SR_CONFIG_ADDR(SR_CONFIG_ADDR)) +      axi_rate_change ( +        .clk(ce_clk), .reset(duc_rst), .clear(clear_tx_seqnum[i]), .clear_user(clear_user), +        .src_sid(src_sid[16*i+15:16*i]), .dst_sid(next_dst_sid[16*i+15:16*i]), +        .set_stb(set_stb_int), .set_addr(set_addr_int), .set_data(set_data_int), +        .i_tdata(m_axis_data_tdata[ITEM_W*i+:ITEM_W]), .i_tlast(m_axis_data_tlast[i]), .i_tvalid(m_axis_data_tvalid[i]), +        .i_tready(m_axis_data_tready[i]), .i_tuser(m_axis_data_tuser[128*i+:128]), +        .o_tdata(m_axis_rc_tdata), .o_tlast(m_axis_rc_tlast), .o_tvalid(m_axis_rc_tvalid), +        .o_tready(m_axis_rc_tready), .o_tuser(m_axis_rc_tuser), +        .m_axis_data_tdata({sample_tdata}), .m_axis_data_tlast(), .m_axis_data_tvalid(sample_tvalid), +        .m_axis_data_tready(sample_tready), +        .s_axis_data_tdata(sample_duc_tdata), .s_axis_data_tlast(1'b0), .s_axis_data_tvalid(sample_duc_tvalid), +        .s_axis_data_tready(sample_duc_tready), +        .warning_long_throttle(), .error_extra_outputs(), .error_drop_pkt_lockup()); + +      //////////////////////////////////////////////////////////// +      // +      // Digital Up Converter +      // +      //////////////////////////////////////////////////////////// +      duc #( +        .SR_INTERP_ADDR(SR_INTERP_ADDR), +        .NUM_HB(NUM_HB), +        .CIC_MAX_INTERP(CIC_MAX_INTERP)) +      duc ( +        .clk(ce_clk), .reset(duc_rst), .clear(clear_duc), +        .set_stb(set_stb_int), .set_addr(set_addr_int), .set_data(set_data_int), +        .i_tdata(sample_tdata), .i_tuser(128'b0), .i_tvalid(sample_tvalid), .i_tready(sample_tready), +        .o_tdata(sample_duc_tdata), .o_tuser(), .o_tvalid(sample_duc_tvalid), .o_tready(sample_duc_tready)); + +    end +  endgenerate + +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_duc/rfnoc_block_duc_regs.vh b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_duc/rfnoc_block_duc_regs.vh new file mode 100644 index 000000000..fa239857e --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_duc/rfnoc_block_duc_regs.vh @@ -0,0 +1,25 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_duc_regs (Header) +// +// Description: Header file for RFNoC DUC functionality. This includes  +// register offsets, bitfields and constants for the radio components. +// + +// For now, these offsets match the original DUC +localparam DUC_BASE_ADDR = 'h00; +localparam DUC_ADDR_W    = 8; + +localparam RB_COMPAT_NUM     = 0; +localparam RB_NUM_HB         = 1; +localparam RB_CIC_MAX_INTERP = 2; +localparam SR_N_ADDR         = 128; +localparam SR_M_ADDR         = 129; +localparam SR_CONFIG_ADDR    = 130; +localparam SR_INTERP_ADDR    = 131; +localparam SR_FREQ_ADDR      = 132; +localparam SR_SCALE_IQ_ADDR  = 133; + diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_duc/rfnoc_block_duc_tb.sv b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_duc/rfnoc_block_duc_tb.sv new file mode 100644 index 000000000..5bca3f03b --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_duc/rfnoc_block_duc_tb.sv @@ -0,0 +1,387 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_duc_tb +// +// Description:  Testbench for rfnoc_block_duc +// + + +module rfnoc_block_duc_tb(); + +  // Include macros and time declarations for use with PkgTestExec +  `include "test_exec.svh" + +  import PkgTestExec::*; +  import PkgChdrUtils::*; +  import PkgRfnocBlockCtrlBfm::*; + +  `include "rfnoc_block_duc_regs.vh" + + +  //--------------------------------------------------------------------------- +  // Local Parameters +  //--------------------------------------------------------------------------- + +  // Simulation parameters +  localparam real CHDR_CLK_PER   = 5.0;   // CHDR clock rate +  localparam real DUC_CLK_PER    = 4.0;   // DUC IP clock rate +  localparam int  EXTENDED_TEST  = 0;     // Perform a longer test +  localparam int  SPP            = 128;   // Samples per packet +  localparam int  PKT_SIZE_BYTES = SPP*4; // Bytes per packet +  localparam int  STALL_PROB     = 25;    // BFM stall probability + +  // Block configuration +  localparam int CHDR_W         = 64; +  localparam int THIS_PORTID    = 'h123; +  localparam int MTU            = 8; +  localparam int NUM_PORTS      = 1; +  localparam int NUM_HB         = 3; +  localparam int CIC_MAX_INTERP = 128; + + +  //--------------------------------------------------------------------------- +  // Clocks +  //--------------------------------------------------------------------------- + +  bit rfnoc_chdr_clk; +  bit rfnoc_ctrl_clk; + +  sim_clock_gen #(CHDR_CLK_PER) rfnoc_chdr_clk_gen (.clk(rfnoc_chdr_clk), .rst()); +  sim_clock_gen #(CHDR_CLK_PER) rfnoc_ctrl_clk_gen (.clk(rfnoc_ctrl_clk), .rst()); +  sim_clock_gen #(DUC_CLK_PER)  duc_clk_gen        (.clk(ce_clk), .rst()); + + +  //--------------------------------------------------------------------------- +  // Bus Functional Models +  //--------------------------------------------------------------------------- + +  RfnocBackendIf        backend            (rfnoc_chdr_clk, rfnoc_ctrl_clk); +  AxiStreamIf #(32)     m_ctrl             (rfnoc_ctrl_clk, 1'b0); +  AxiStreamIf #(32)     s_ctrl             (rfnoc_ctrl_clk, 1'b0); +  AxiStreamIf #(CHDR_W) m_chdr [NUM_PORTS] (rfnoc_chdr_clk, 1'b0); +  AxiStreamIf #(CHDR_W) s_chdr [NUM_PORTS] (rfnoc_chdr_clk, 1'b0); + +  // Bus functional model for a software block controller +  RfnocBlockCtrlBfm #(.CHDR_W(CHDR_W)) blk_ctrl = +    new(backend, m_ctrl, s_ctrl); + +  // Connect block controller to BFMs +  for (genvar i = 0; i < NUM_PORTS; i++) begin : gen_bfm_connections +    initial begin +      blk_ctrl.connect_master_data_port(i, m_chdr[i], PKT_SIZE_BYTES); +      blk_ctrl.connect_slave_data_port(i, s_chdr[i]); +      blk_ctrl.set_master_stall_prob(i, STALL_PROB); +      blk_ctrl.set_slave_stall_prob(i, STALL_PROB); +    end +  end + + +  //--------------------------------------------------------------------------- +  // DUT +  //--------------------------------------------------------------------------- + +  logic [NUM_PORTS*CHDR_W-1:0] s_rfnoc_chdr_tdata; +  logic [       NUM_PORTS-1:0] s_rfnoc_chdr_tlast; +  logic [       NUM_PORTS-1:0] s_rfnoc_chdr_tvalid; +  logic [       NUM_PORTS-1:0] s_rfnoc_chdr_tready; + +  logic [NUM_PORTS*CHDR_W-1:0] m_rfnoc_chdr_tdata; +  logic [       NUM_PORTS-1:0] m_rfnoc_chdr_tlast; +  logic [       NUM_PORTS-1:0] m_rfnoc_chdr_tvalid; +  logic [       NUM_PORTS-1:0] m_rfnoc_chdr_tready; + +  // Map the array of BFMs to a flat vector for the DUT +  genvar i; +  for (i = 0; i < NUM_PORTS; i++) begin : gen_dut_connections +    // Connect BFM master to DUT slave port +    assign s_rfnoc_chdr_tdata[CHDR_W*i+:CHDR_W] = m_chdr[i].tdata; +    assign s_rfnoc_chdr_tlast[i]                = m_chdr[i].tlast; +    assign s_rfnoc_chdr_tvalid[i]               = m_chdr[i].tvalid; +    assign m_chdr[i].tready                     = s_rfnoc_chdr_tready[i]; + +    // Connect BFM slave to DUT master port +    assign s_chdr[i].tdata        = m_rfnoc_chdr_tdata[CHDR_W*i+:CHDR_W]; +    assign s_chdr[i].tlast        = m_rfnoc_chdr_tlast[i]; +    assign s_chdr[i].tvalid       = m_rfnoc_chdr_tvalid[i]; +    assign m_rfnoc_chdr_tready[i] = s_chdr[i].tready; +  end + +  rfnoc_block_duc #( +    .THIS_PORTID    (THIS_PORTID), +    .CHDR_W         (CHDR_W), +    .NUM_PORTS      (NUM_PORTS), +    .MTU            (MTU), +    .NUM_HB         (NUM_HB), +    .CIC_MAX_INTERP (CIC_MAX_INTERP) +  ) rfnoc_block_duc_i ( +    .rfnoc_chdr_clk          (backend.chdr_clk), +    .ce_clk                  (ce_clk), +    .s_rfnoc_chdr_tdata      (s_rfnoc_chdr_tdata), +    .s_rfnoc_chdr_tlast      (s_rfnoc_chdr_tlast), +    .s_rfnoc_chdr_tvalid     (s_rfnoc_chdr_tvalid), +    .s_rfnoc_chdr_tready     (s_rfnoc_chdr_tready), +    .m_rfnoc_chdr_tdata      (m_rfnoc_chdr_tdata), +    .m_rfnoc_chdr_tlast      (m_rfnoc_chdr_tlast), +    .m_rfnoc_chdr_tvalid     (m_rfnoc_chdr_tvalid), +    .m_rfnoc_chdr_tready     (m_rfnoc_chdr_tready), +    .rfnoc_core_config       (backend.cfg), +    .rfnoc_core_status       (backend.sts), +    .rfnoc_ctrl_clk          (backend.ctrl_clk), +    .s_rfnoc_ctrl_tdata      (m_ctrl.tdata), +    .s_rfnoc_ctrl_tlast      (m_ctrl.tlast), +    .s_rfnoc_ctrl_tvalid     (m_ctrl.tvalid), +    .s_rfnoc_ctrl_tready     (m_ctrl.tready), +    .m_rfnoc_ctrl_tdata      (s_ctrl.tdata), +    .m_rfnoc_ctrl_tlast      (s_ctrl.tlast), +    .m_rfnoc_ctrl_tvalid     (s_ctrl.tvalid), +    .m_rfnoc_ctrl_tready     (s_ctrl.tready) +  ); + + +  //--------------------------------------------------------------------------- +  // Helper Tasks +  //--------------------------------------------------------------------------- + +  // Translate the desired register access to a ctrlport write request. +  task automatic write_reg(int port, byte unsigned addr, bit [31:0] value); +    blk_ctrl.reg_write(256*8*port + addr*8, value); +  endtask : write_reg + + +  // Translate the desired register access to a ctrlport read request. +  task automatic read_user_reg(int port, byte unsigned addr, output logic [63:0] value); +    blk_ctrl.reg_read(256*8*port + addr*8 + 0, value[31: 0]); +    blk_ctrl.reg_read(256*8*port + addr*8 + 4, value[63:32]); +  endtask : read_user_reg + + +  // Set the interpolation rate +  task automatic set_interp_rate(int port, int interp_rate); +    begin +      logic [7:0] cic_rate = 8'd0; +      logic [7:0] hb_enables = 2'b0; + +      int _interp_rate = interp_rate; + +      // Calculate which half bands to enable and whatever is left over set the CIC +      while ((_interp_rate[0] == 0) && (hb_enables < NUM_HB)) begin +        hb_enables += 1'b1; +        _interp_rate = _interp_rate >> 1; +      end + +      // CIC rate cannot be set to 0 +      cic_rate = (_interp_rate[7:0] == 8'd0) ? 8'd1 : _interp_rate[7:0]; +      `ASSERT_ERROR(hb_enables <= NUM_HB, "Enabled halfbands may not exceed total number of half bands."); +      `ASSERT_ERROR(cic_rate > 0 && cic_rate <= CIC_MAX_INTERP, +       "CIC Interpolation rate must be positive, not exceed the max cic interpolation rate, and cannot equal 0!"); + +      // Setup DUC +      $display("Set interpolation to %0d", interp_rate); +      $display("- Number of enabled HBs: %0d", hb_enables); +      $display("- CIC Rate:              %0d", cic_rate); +      write_reg(port, SR_M_ADDR, interp_rate);                 // Set interpolation rate in AXI rate change +      write_reg(port, SR_INTERP_ADDR, {hb_enables, cic_rate}); // Enable HBs, set CIC rate +    end +  endtask + + +  // Test sending packets of ones +  task automatic send_ones(int port, int interp_rate, bit has_time); +    begin +      const bit [63:0] start_time = 64'h0123456789ABCDEF; + +      set_interp_rate(port, interp_rate); + +      // Setup DUC +      write_reg(port, SR_CONFIG_ADDR, 32'd1);         // Enable clear EOB +      write_reg(port, SR_FREQ_ADDR, 32'd0);           // CORDIC phase increment +      write_reg(port, SR_SCALE_IQ_ADDR, (1 << 14));   // Scaling, set to 1 + +      fork +        begin +          chdr_word_t send_payload[$]; +          packet_info_t pkt_info; + +          $display("Send ones"); + +          // Generate a payload of all ones +          send_payload = {}; +          for (int i = 0; i < PKT_SIZE_BYTES/8; i++) begin +            send_payload.push_back({16'hffff, 16'hffff, 16'hffff, 16'hffff}); +          end + +          // Send two packets with EOB on the second packet +          pkt_info = 0; +          pkt_info.has_time  = has_time; +          pkt_info.timestamp = start_time; +          blk_ctrl.send_packets(port, send_payload, /*data_bytes*/, /*metadata*/, pkt_info); +          pkt_info.timestamp = start_time + SPP; +          pkt_info.eob = 1; +          blk_ctrl.send_packets(port, send_payload, /*data_bytes*/, /*metadata*/, pkt_info); +           +          $display("Send ones complete"); +        end +        begin +          string s; +          chdr_word_t samples; +          int data_bytes; +          chdr_word_t recv_payload[$]; +          chdr_word_t metadata[$]; +          packet_info_t pkt_info; + +          $display("Check incoming samples"); +          for (int i = 0; i < 2*interp_rate; i++) begin +            blk_ctrl.recv_adv(port, recv_payload, data_bytes, metadata, pkt_info); + +            // Check the packet size +            $sformat(s, "incorrect (drop) packet size! expected: %0d, actual: %0d", PKT_SIZE_BYTES/8, recv_payload.size()); +            `ASSERT_ERROR(recv_payload.size() == PKT_SIZE_BYTES/8, s); + +            // Check the timestamp +            if (has_time) begin +              bit [63:0] expected_time; +              // Calculate what the timestamp should be +              expected_time = start_time + i * SPP; +              $sformat(s, "Incorrect timestamp: has_time = %0d, timestamp = 0x%0X, expected 0x%0X", +                       pkt_info.has_time, pkt_info.timestamp, expected_time); +              `ASSERT_ERROR(pkt_info.has_time == 1 && pkt_info.timestamp == expected_time, s); +            end else begin +              `ASSERT_ERROR(pkt_info.has_time == 0, "Packet has timestamp when it shouldn't"); +            end + +            // Check EOB +            if (i == 2*interp_rate-1) begin +              `ASSERT_ERROR(pkt_info.eob == 1, "EOB not set on last packet"); +            end else begin +              `ASSERT_ERROR(pkt_info.eob == 0,  +                $sformatf("EOB unexpectedly set on packet %0d", i)); +            end + +            // Check the sample values +            samples = 64'd0; +            for (int j = 0; j < PKT_SIZE_BYTES/8; j++) begin +              samples = recv_payload[j]; +              $sformat(s, "Ramp word %0d invalid! Expected a real value, Received: %0d", 2*j, samples); +              `ASSERT_ERROR(samples >= 0, s); +            end +          end +          $display("Check complete"); +        end +      join +    end +  endtask + + +  //--------------------------------------------------------------------------- +  // Test Process +  //--------------------------------------------------------------------------- + +  initial begin : tb_main +    const int port = 0; +    test.start_tb("rfnoc_block_duc_tb"); + +    // Start the BFMs running +    blk_ctrl.run(); + + +    //------------------------------------------------------------------------- +    // Reset +    //------------------------------------------------------------------------- + +    test.start_test("Wait for Reset", 10us); +    fork +      blk_ctrl.reset_chdr(); +      blk_ctrl.reset_ctrl(); +    join; +    test.end_test(); + + +    //------------------------------------------------------------------------- +    // Check NoC ID and Block Info +    //------------------------------------------------------------------------- + +    test.start_test("Verify Block Info", 2us); +    `ASSERT_ERROR(blk_ctrl.get_noc_id() == rfnoc_block_duc_i.NOC_ID, "Incorrect NOC_ID value"); +    `ASSERT_ERROR(blk_ctrl.get_num_data_i() == NUM_PORTS, "Incorrect NUM_DATA_I value"); +    `ASSERT_ERROR(blk_ctrl.get_num_data_o() == NUM_PORTS, "Incorrect NUM_DATA_O value"); +    `ASSERT_ERROR(blk_ctrl.get_mtu() == MTU, "Incorrect MTU value"); +    test.end_test(); + + +    //------------------------------------------------------------------------- +    // Test read-back regs +    //------------------------------------------------------------------------- + +    begin +      logic [63:0] val64; +      test.start_test("Test registers", 10us); +      read_user_reg(port, RB_NUM_HB, val64); +      `ASSERT_ERROR(val64 == NUM_HB, "Register NUM_HB didn't read back expected value"); +      read_user_reg(port, RB_CIC_MAX_INTERP, val64); +      `ASSERT_ERROR(val64 ==CIC_MAX_INTERP, "Register RB_CIC_MAX_INTERP didn't read back expected value"); +      test.end_test(); +    end + + +    //------------------------------------------------------------------------- +    // Test various interpolation rates (no timestamp) +    //------------------------------------------------------------------------- + +    begin +      test.start_test("Test interpolation rates (with timestamp)", 0.5ms); + +      $display("Note: This test will take a long time!"); +      send_ones(port, 1,  1);   // HBs enabled: 0, CIC rate: 1 +      send_ones(port, 2,  1);   // HBs enabled: 1, CIC rate: 1 +      send_ones(port, 3,  1);   // HBs enabled: 0, CIC rate: 3 +      send_ones(port, 4,  1);   // HBs enabled: 2, CIC rate: 1 +      send_ones(port, 6,  1);   // HBs enabled: 1, CIC rate: 3 +      send_ones(port, 8,  1);   // HBs enabled: 2, CIC rate: 2 +      send_ones(port, 12, 1);   // HBs enabled: 2, CIC rate: 3 +      send_ones(port, 13, 1);   // HBs enabled: 0, CIC rate: 13 +      send_ones(port, 16, 1);   // HBs enabled: 2, CIC rate: 3 +      send_ones(port, 40, 1);   // HBs enabled: 2, CIC rate: 20 + +      test.end_test(); +    end + + +    //------------------------------------------------------------------------- +    // Test various interpolation rates (without timestamp) +    //------------------------------------------------------------------------- + +    begin +      test.start_test("Test interpolation rates (no timestamp)", 0.5ms); + +      send_ones(port, 1,  0);   // HBs enabled: 0, CIC rate: 1 +      send_ones(port, 3,  0);   // HBs enabled: 0, CIC rate: 3 + +      test.end_test(); +    end + + +    //------------------------------------------------------------------------- +    // Test timed tune +    //------------------------------------------------------------------------- + +    // This test has not been implemented because the RFNoC FFT has not been +    // ported yet. + + +    //------------------------------------------------------------------------- +    // Finish +    //------------------------------------------------------------------------- + +    // 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 +    rfnoc_chdr_clk_gen.kill(); +    rfnoc_ctrl_clk_gen.kill(); +    duc_clk_gen.kill(); +  end +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fft/Makefile b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fft/Makefile new file mode 100644 index 000000000..868246fbd --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fft/Makefile @@ -0,0 +1,62 @@ +# +# Copyright 2019 Ettus Research, A National Instruments Company +# +# 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_preamble after defining BASE_DIR +include $(BASE_DIR)/../tools/make/viv_sim_preamble.mak + +#------------------------------------------------- +# IP Specific +#------------------------------------------------- +# If simulation contains IP, define the IP_DIR and point +# it to the base level IP directory +LIB_IP_DIR = $(BASE_DIR)/../lib/ip + +# Include makefiles and sources for all IP components +# *after* defining the LIB_IP_DIR +include $(LIB_IP_DIR)/axi_fft/Makefile.inc +include $(LIB_IP_DIR)/complex_to_magphase/Makefile.inc + +DESIGN_SRCS += $(abspath \ +$(LIB_IP_AXI_FFT_OUTS) \ +) + +#------------------------------------------------- +# Design Specific +#------------------------------------------------- +# Include makefiles and sources for the DUT and its dependencies +include $(BASE_DIR)/../lib/rfnoc/core/Makefile.srcs +include $(BASE_DIR)/../lib/rfnoc/utils/Makefile.srcs +include Makefile.srcs + +DESIGN_SRCS += $(abspath \ +$(RFNOC_CORE_SRCS) \ +$(RFNOC_UTIL_SRCS) \ +$(RFNOC_OOT_SRCS) \ +) + +#------------------------------------------------- +# Testbench Specific +#------------------------------------------------- +# Define only one toplevel module +SIM_TOP = rfnoc_block_fft_tb + +# Add test bench, user design under test, and +# additional user created files +SIM_SRCS = \ +$(abspath rfnoc_block_fft_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/blocks/rfnoc_block_fft/Makefile.srcs b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fft/Makefile.srcs new file mode 100644 index 000000000..21ba967f2 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fft/Makefile.srcs @@ -0,0 +1,10 @@ +# +# Copyright 2019 Ettus Research, A National Instruments Company +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# + +RFNOC_OOT_SRCS += $(abspath $(addprefix $(BASE_DIR)/../lib/rfnoc/blocks/rfnoc_block_fft/, \ +noc_shell_fft.v \ +rfnoc_block_fft.v \ +)) diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fft/noc_shell_fft.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fft/noc_shell_fft.v new file mode 100644 index 000000000..37a60ef31 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fft/noc_shell_fft.v @@ -0,0 +1,294 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: noc_shell_fft +// + +module noc_shell_fft #( +  parameter [31:0] NOC_ID          = 32  'h0, +  parameter [ 9:0] THIS_PORTID     = 10  'd0, +  parameter        CHDR_W          = 64, +  parameter [ 0:0] CTRLPORT_SLV_EN = 1, +  parameter [ 0:0] CTRLPORT_MST_EN = 1, +  parameter        SYNC_CLKS       = 0, +  parameter [ 5:0] NUM_DATA_I      = 1, +  parameter [ 5:0] NUM_DATA_O      = 1, +  parameter        ITEM_W          = 32, +  parameter        NIPC            = 2, +  parameter        PYLD_FIFO_SIZE  = 5, +  parameter        CTXT_FIFO_SIZE  = 5, +  parameter        MTU             = 10 +) ( +  //--------------------------------------------------------------------------- +  // Framework Interface +  //--------------------------------------------------------------------------- + +  // RFNoC Framework Clocks and Resets +  input  wire                           rfnoc_chdr_clk, +  output wire                           rfnoc_chdr_rst, +  input  wire                           rfnoc_ctrl_clk, +  output wire                           rfnoc_ctrl_rst, +  // RFNoC Backend Interface +  input  wire [                  511:0] rfnoc_core_config, +  output wire [                  511:0] rfnoc_core_status, +  // CHDR Input Ports (from framework) +  input  wire [(CHDR_W*NUM_DATA_I)-1:0] s_rfnoc_chdr_tdata, +  input  wire [         NUM_DATA_I-1:0] s_rfnoc_chdr_tlast, +  input  wire [         NUM_DATA_I-1:0] s_rfnoc_chdr_tvalid, +  output wire [         NUM_DATA_I-1:0] s_rfnoc_chdr_tready, +  // CHDR Output Ports (to framework) +  output wire [(CHDR_W*NUM_DATA_O)-1:0] m_rfnoc_chdr_tdata, +  output wire [         NUM_DATA_O-1:0] m_rfnoc_chdr_tlast, +  output wire [         NUM_DATA_O-1:0] m_rfnoc_chdr_tvalid, +  input  wire [         NUM_DATA_O-1:0] m_rfnoc_chdr_tready, +  // AXIS-Ctrl Input Port (from framework) +  input  wire [                   31:0] s_rfnoc_ctrl_tdata, +  input  wire                           s_rfnoc_ctrl_tlast, +  input  wire                           s_rfnoc_ctrl_tvalid, +  output wire                           s_rfnoc_ctrl_tready, +  // AXIS-Ctrl Output Port (to framework) +  output wire [                   31:0] m_rfnoc_ctrl_tdata, +  output wire                           m_rfnoc_ctrl_tlast, +  output wire                           m_rfnoc_ctrl_tvalid, +  input  wire                           m_rfnoc_ctrl_tready, + +  //--------------------------------------------------------------------------- +  // Client Control Port Interface +  //--------------------------------------------------------------------------- + +  // Clock +  input  wire        ctrlport_clk, +  input  wire        ctrlport_rst, +  // Master +  output wire        m_ctrlport_req_wr, +  output wire        m_ctrlport_req_rd, +  output wire [19:0] m_ctrlport_req_addr, +  output wire [31:0] m_ctrlport_req_data, +  output wire [ 3:0] m_ctrlport_req_byte_en, +  output wire        m_ctrlport_req_has_time, +  output wire [63:0] m_ctrlport_req_time, +  input  wire        m_ctrlport_resp_ack, +  input  wire [ 1:0] m_ctrlport_resp_status, +  input  wire [31:0] m_ctrlport_resp_data, +  // Slave +  input  wire        s_ctrlport_req_wr, +  input  wire        s_ctrlport_req_rd, +  input  wire [19:0] s_ctrlport_req_addr, +  input  wire [ 9:0] s_ctrlport_req_portid, +  input  wire [15:0] s_ctrlport_req_rem_epid, +  input  wire [ 9:0] s_ctrlport_req_rem_portid, +  input  wire [31:0] s_ctrlport_req_data, +  input  wire [ 3:0] s_ctrlport_req_byte_en, +  input  wire        s_ctrlport_req_has_time, +  input  wire [63:0] s_ctrlport_req_time, +  output wire        s_ctrlport_resp_ack, +  output wire [ 1:0] s_ctrlport_resp_status, +  output wire [31:0] s_ctrlport_resp_data, + +  //--------------------------------------------------------------------------- +  // Client Data Interface +  //--------------------------------------------------------------------------- + +  // Clock +  input wire axis_data_clk, +  input wire axis_data_rst, + +  // Output data stream (to user logic) +  output wire [(NUM_DATA_I*ITEM_W*NIPC)-1:0] m_axis_payload_tdata, +  output wire [       (NUM_DATA_I*NIPC)-1:0] m_axis_payload_tkeep, +  output wire [              NUM_DATA_I-1:0] m_axis_payload_tlast, +  output wire [              NUM_DATA_I-1:0] m_axis_payload_tvalid, +  input  wire [              NUM_DATA_I-1:0] m_axis_payload_tready, + +  // Input data stream (from user logic) +  input  wire [(NUM_DATA_O*ITEM_W*NIPC)-1:0] s_axis_payload_tdata, +  input  wire [       (NUM_DATA_O*NIPC)-1:0] s_axis_payload_tkeep, +  input  wire [              NUM_DATA_O-1:0] s_axis_payload_tlast, +  input  wire [              NUM_DATA_O-1:0] s_axis_payload_tvalid, +  output wire [              NUM_DATA_O-1:0] s_axis_payload_tready, + +  // Output context stream (to user logic) +  output wire [(NUM_DATA_I*CHDR_W)-1:0] m_axis_context_tdata, +  output wire [     (4*NUM_DATA_I)-1:0] m_axis_context_tuser, +  output wire [         NUM_DATA_I-1:0] m_axis_context_tlast, +  output wire [         NUM_DATA_I-1:0] m_axis_context_tvalid, +  input  wire [         NUM_DATA_I-1:0] m_axis_context_tready, + +  // Input context stream (from user logic) +  input  wire [(NUM_DATA_O*CHDR_W)-1:0] s_axis_context_tdata, +  input  wire [     (4*NUM_DATA_O)-1:0] s_axis_context_tuser, +  input  wire [         NUM_DATA_O-1:0] s_axis_context_tlast, +  input  wire [         NUM_DATA_O-1:0] s_axis_context_tvalid, +  output wire [         NUM_DATA_O-1:0] s_axis_context_tready +); +   +  localparam CTRL_FIFO_SIZE = 5; + + +  //--------------------------------------------------------------------------- +  //  Backend Interface +  //--------------------------------------------------------------------------- + +  wire         data_i_flush_en; +  wire [31:0]  data_i_flush_timeout; +  wire [63:0]  data_i_flush_active; +  wire [63:0]  data_i_flush_done; +  wire         data_o_flush_en; +  wire [31:0]  data_o_flush_timeout; +  wire [63:0]  data_o_flush_active; +  wire [63:0]  data_o_flush_done; + +  backend_iface #( +    .NOC_ID        (NOC_ID), +    .NUM_DATA_I    (NUM_DATA_I), +    .NUM_DATA_O    (NUM_DATA_O), +    .CTRL_FIFOSIZE (CTRL_FIFO_SIZE), +    .MTU           (MTU) +  ) backend_iface_i ( +    .rfnoc_chdr_clk       (rfnoc_chdr_clk), +    .rfnoc_ctrl_clk       (rfnoc_ctrl_clk), +    .rfnoc_core_config    (rfnoc_core_config), +    .rfnoc_core_status    (rfnoc_core_status), +    .rfnoc_chdr_rst       (rfnoc_chdr_rst), +    .rfnoc_ctrl_rst       (rfnoc_ctrl_rst), +    .data_i_flush_en      (data_i_flush_en), +    .data_i_flush_timeout (data_i_flush_timeout), +    .data_i_flush_active  (data_i_flush_active), +    .data_i_flush_done    (data_i_flush_done), +    .data_o_flush_en      (data_o_flush_en), +    .data_o_flush_timeout (data_o_flush_timeout), +    .data_o_flush_active  (data_o_flush_active), +    .data_o_flush_done    (data_o_flush_done) +  ); + +  //--------------------------------------------------------------------------- +  //  Control Path +  //--------------------------------------------------------------------------- + +  ctrlport_endpoint #( +    .THIS_PORTID              (THIS_PORTID    ), +    .SYNC_CLKS                (0              ), +    .AXIS_CTRL_MST_EN         (CTRLPORT_SLV_EN), +    .AXIS_CTRL_SLV_EN         (CTRLPORT_MST_EN), +    .SLAVE_FIFO_SIZE          (CTRL_FIFO_SIZE ) +  ) ctrlport_ep_i ( +    .rfnoc_ctrl_clk           (rfnoc_ctrl_clk           ), +    .rfnoc_ctrl_rst           (rfnoc_ctrl_rst           ), +    .ctrlport_clk             (ctrlport_clk             ), +    .ctrlport_rst             (ctrlport_rst             ), +    .s_rfnoc_ctrl_tdata       (s_rfnoc_ctrl_tdata       ), +    .s_rfnoc_ctrl_tlast       (s_rfnoc_ctrl_tlast       ), +    .s_rfnoc_ctrl_tvalid      (s_rfnoc_ctrl_tvalid      ), +    .s_rfnoc_ctrl_tready      (s_rfnoc_ctrl_tready      ), +    .m_rfnoc_ctrl_tdata       (m_rfnoc_ctrl_tdata       ), +    .m_rfnoc_ctrl_tlast       (m_rfnoc_ctrl_tlast       ), +    .m_rfnoc_ctrl_tvalid      (m_rfnoc_ctrl_tvalid      ), +    .m_rfnoc_ctrl_tready      (m_rfnoc_ctrl_tready      ), +    .m_ctrlport_req_wr        (m_ctrlport_req_wr        ), +    .m_ctrlport_req_rd        (m_ctrlport_req_rd        ), +    .m_ctrlport_req_addr      (m_ctrlport_req_addr      ), +    .m_ctrlport_req_data      (m_ctrlport_req_data      ), +    .m_ctrlport_req_byte_en   (m_ctrlport_req_byte_en   ), +    .m_ctrlport_req_has_time  (m_ctrlport_req_has_time  ), +    .m_ctrlport_req_time      (m_ctrlport_req_time      ), +    .m_ctrlport_resp_ack      (m_ctrlport_resp_ack      ), +    .m_ctrlport_resp_status   (m_ctrlport_resp_status   ), +    .m_ctrlport_resp_data     (m_ctrlport_resp_data     ), +    .s_ctrlport_req_wr        (s_ctrlport_req_wr        ), +    .s_ctrlport_req_rd        (s_ctrlport_req_rd        ), +    .s_ctrlport_req_addr      (s_ctrlport_req_addr      ), +    .s_ctrlport_req_portid    (s_ctrlport_req_portid    ), +    .s_ctrlport_req_rem_epid  (s_ctrlport_req_rem_epid  ), +    .s_ctrlport_req_rem_portid(s_ctrlport_req_rem_portid), +    .s_ctrlport_req_data      (s_ctrlport_req_data      ), +    .s_ctrlport_req_byte_en   (s_ctrlport_req_byte_en   ), +    .s_ctrlport_req_has_time  (s_ctrlport_req_has_time  ), +    .s_ctrlport_req_time      (s_ctrlport_req_time      ), +    .s_ctrlport_resp_ack      (s_ctrlport_resp_ack      ), +    .s_ctrlport_resp_status   (s_ctrlport_resp_status   ), +    .s_ctrlport_resp_data     (s_ctrlport_resp_data     ) +  ); + +  //--------------------------------------------------------------------------- +  //  Data Path +  //--------------------------------------------------------------------------- + +  genvar i; +  generate + +    for (i = 0; i < NUM_DATA_I; i = i + 1) begin: chdr_to_data +      chdr_to_axis_pyld_ctxt #( +        .CHDR_W               (CHDR_W        ), +        .ITEM_W               (ITEM_W        ), +        .NIPC                 (NIPC          ), +        .SYNC_CLKS            (SYNC_CLKS     ), +        .CONTEXT_FIFO_SIZE    (CTXT_FIFO_SIZE), +        .PAYLOAD_FIFO_SIZE    (PYLD_FIFO_SIZE), +        .CONTEXT_PREFETCH_EN  (1             ) +      ) chdr_to_axis_pyld_ctxt_i ( +        .axis_chdr_clk        (rfnoc_chdr_clk                                       ), +        .axis_chdr_rst        (rfnoc_chdr_rst                                       ), +        .axis_data_clk        (axis_data_clk                                        ), +        .axis_data_rst        (axis_data_rst                                        ), +        .s_axis_chdr_tdata    (s_rfnoc_chdr_tdata   [(i*CHDR_W)+:CHDR_W]            ), +        .s_axis_chdr_tlast    (s_rfnoc_chdr_tlast   [i]                             ), +        .s_axis_chdr_tvalid   (s_rfnoc_chdr_tvalid  [i]                             ), +        .s_axis_chdr_tready   (s_rfnoc_chdr_tready  [i]                             ), +        .m_axis_payload_tdata (m_axis_payload_tdata [(i*ITEM_W*NIPC)+:(ITEM_W*NIPC)]), +        .m_axis_payload_tkeep (m_axis_payload_tkeep [(i*NIPC)+:NIPC]                ), +        .m_axis_payload_tlast (m_axis_payload_tlast [i]                             ), +        .m_axis_payload_tvalid(m_axis_payload_tvalid[i]                             ), +        .m_axis_payload_tready(m_axis_payload_tready[i]                             ), +        .m_axis_context_tdata (m_axis_context_tdata [(i*CHDR_W)+:(CHDR_W)]          ), +        .m_axis_context_tuser (m_axis_context_tuser [(i*4)+:4]                      ), +        .m_axis_context_tlast (m_axis_context_tlast [i]                             ), +        .m_axis_context_tvalid(m_axis_context_tvalid[i]                             ), +        .m_axis_context_tready(m_axis_context_tready[i]                             ), +        .flush_en             (data_i_flush_en                                      ), +        .flush_timeout        (data_i_flush_timeout                                 ), +        .flush_active         (data_i_flush_active  [i]                             ), +        .flush_done           (data_i_flush_done    [i]                             ) +      ); +    end + +    for (i = 0; i < NUM_DATA_O; i = i + 1) begin: data_to_chdr +      axis_pyld_ctxt_to_chdr #( +        .CHDR_W               (CHDR_W        ), +        .ITEM_W               (ITEM_W        ), +        .NIPC                 (NIPC          ), +        .SYNC_CLKS            (SYNC_CLKS     ), +        .CONTEXT_FIFO_SIZE    (CTXT_FIFO_SIZE), +        .PAYLOAD_FIFO_SIZE    (PYLD_FIFO_SIZE), +        .CONTEXT_PREFETCH_EN  (1             ), +        .MTU                  (MTU           ) +      ) axis_pyld_ctxt_to_chdr_i ( +        .axis_chdr_clk        (rfnoc_chdr_clk                                       ), +        .axis_chdr_rst        (rfnoc_chdr_rst                                       ), +        .axis_data_clk        (axis_data_clk                                        ), +        .axis_data_rst        (axis_data_rst                                        ), +        .m_axis_chdr_tdata    (m_rfnoc_chdr_tdata   [(i*CHDR_W)+:CHDR_W]            ), +        .m_axis_chdr_tlast    (m_rfnoc_chdr_tlast   [i]                             ), +        .m_axis_chdr_tvalid   (m_rfnoc_chdr_tvalid  [i]                             ), +        .m_axis_chdr_tready   (m_rfnoc_chdr_tready  [i]                             ), +        .s_axis_payload_tdata (s_axis_payload_tdata [(i*ITEM_W*NIPC)+:(ITEM_W*NIPC)]), +        .s_axis_payload_tkeep (s_axis_payload_tkeep [(i*NIPC)+:NIPC]                ), +        .s_axis_payload_tlast (s_axis_payload_tlast [i]                             ), +        .s_axis_payload_tvalid(s_axis_payload_tvalid[i]                             ), +        .s_axis_payload_tready(s_axis_payload_tready[i]                             ), +        .s_axis_context_tdata (s_axis_context_tdata [(i*CHDR_W)+:(CHDR_W)]          ), +        .s_axis_context_tuser (s_axis_context_tuser [(i*4)+:4]                      ), +        .s_axis_context_tlast (s_axis_context_tlast [i]                             ), +        .s_axis_context_tvalid(s_axis_context_tvalid[i]                             ), +        .s_axis_context_tready(s_axis_context_tready[i]                             ), +        .framer_errors        (                                                     ), +        .flush_en             (data_o_flush_en                                      ), +        .flush_timeout        (data_o_flush_timeout                                 ), +        .flush_active         (data_o_flush_active  [i]                             ), +        .flush_done           (data_o_flush_done    [i]                             ) +      ); +    end +  endgenerate + +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fft/rfnoc_block_fft.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fft/rfnoc_block_fft.v new file mode 100644 index 000000000..76ae37524 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fft/rfnoc_block_fft.v @@ -0,0 +1,559 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_fft +// +// Description:  An FFT block for RFNoC. +// +// Parameters: +// +//   THIS_PORTID   : Control crossbar port to which this block is connected +//   CHDR_W        : AXIS CHDR interface data width +//   MTU           : Maximum transmission unit (i.e., maximum packet size) in +//                   CHDR words is 2**MTU. +//   EN_MAGNITUDE_OUT : CORDIC based magnitude calculation +//   EN_MAGNITUDE_APPROX_OUT : Multipler-less, lower resource usage +//   EN_MAGNITUDE_SQ_OUT : Magnitude squared +//   EN_FFT_SHIFT : Center zero frequency bin +// + +module rfnoc_block_fft #( +  parameter THIS_PORTID   = 0, +  parameter CHDR_W        = 64, +  parameter MTU           = 10, + +  parameter EN_MAGNITUDE_OUT = 0,         +  parameter EN_MAGNITUDE_APPROX_OUT = 1, +  parameter EN_MAGNITUDE_SQ_OUT = 1,     +  parameter EN_FFT_SHIFT = 1 +  ) +( +  //--------------------------------------------------------------------------- +  // AXIS CHDR Port +  //--------------------------------------------------------------------------- + +  input wire rfnoc_chdr_clk, +  input wire ce_clk, + +  // CHDR inputs from framework +  input  wire [CHDR_W-1:0] s_rfnoc_chdr_tdata, +  input  wire s_rfnoc_chdr_tlast, +  input  wire s_rfnoc_chdr_tvalid, +  output wire s_rfnoc_chdr_tready, + +  // CHDR outputs to framework +  output wire [CHDR_W-1:0] m_rfnoc_chdr_tdata, +  output wire m_rfnoc_chdr_tlast, +  output wire m_rfnoc_chdr_tvalid, +  input  wire m_rfnoc_chdr_tready, + +  // Backend interface +  input  wire [511:0] rfnoc_core_config, +  output wire [511:0] rfnoc_core_status, + +  //--------------------------------------------------------------------------- +  // AXIS CTRL Port +  //--------------------------------------------------------------------------- + +  input wire rfnoc_ctrl_clk, + +  // CTRL port requests from framework +  input  wire [31:0] s_rfnoc_ctrl_tdata, +  input  wire        s_rfnoc_ctrl_tlast, +  input  wire        s_rfnoc_ctrl_tvalid, +  output wire        s_rfnoc_ctrl_tready, + +  // CTRL port requests to framework +  output wire [31:0] m_rfnoc_ctrl_tdata, +  output wire        m_rfnoc_ctrl_tlast, +  output wire        m_rfnoc_ctrl_tvalid, +  input  wire        m_rfnoc_ctrl_tready +); + +  // These are the only supported values for now +  localparam ITEM_W = 32; +  localparam NIPC   = 1; + +  localparam NOC_ID = 32'hFF70_0000; + +  `include "../../core/rfnoc_axis_ctrl_utils.vh" + +  //--------------------------------------------------------------------------- +  // Signal Declarations +  //--------------------------------------------------------------------------- + +  wire rfnoc_chdr_rst; + +  wire        ctrlport_req_wr; +  wire        ctrlport_req_rd; +  wire [19:0] ctrlport_req_addr; +  wire [31:0] ctrlport_req_data; +  wire        ctrlport_req_has_time; +  wire [63:0] ctrlport_req_time; +  wire        ctrlport_resp_ack; +  wire [31:0] ctrlport_resp_data; + +  wire [ITEM_W-1:0] axis_to_fft_tdata; +  wire  axis_to_fft_tlast; +  wire  axis_to_fft_tvalid; +  wire  axis_to_fft_tready; + +  wire [ITEM_W-1:0] axis_from_fft_tdata; +  wire  axis_from_fft_tlast; +  wire  axis_from_fft_tvalid; +  wire  axis_from_fft_tready; + +  wire [CHDR_W-1:0] m_axis_context_tdata; +  wire [       3:0] m_axis_context_tuser; +  wire [       0:0] m_axis_context_tlast; +  wire [       0:0] m_axis_context_tvalid; +  wire [       0:0] m_axis_context_tready; + +  wire [CHDR_W-1:0] s_axis_context_tdata; +  wire [       3:0] s_axis_context_tuser; +  wire [       0:0] s_axis_context_tlast; +  wire [       0:0] s_axis_context_tvalid; +  wire [       0:0] s_axis_context_tready; + +  wire ce_rst; + +  // Cross the CHDR reset to the radio_clk domain +  pulse_synchronizer #( +    .MODE ("POSEDGE") +  ) ctrl_rst_sync_i ( +    .clk_a   (rfnoc_chdr_clk), +    .rst_a   (1'b0), +    .pulse_a (rfnoc_chdr_rst), +    .busy_a  (), +    .clk_b   (ce_clk), +    .pulse_b (ce_rst) +  ); + +  //--------------------------------------------------------------------------- +  // NoC Shell +  //--------------------------------------------------------------------------- + +  noc_shell_fft #( +    .NOC_ID         (NOC_ID     ), +    .THIS_PORTID    (THIS_PORTID), +    .CHDR_W         (CHDR_W     ), +    .CTRLPORT_SLV_EN(0          ), +    .CTRLPORT_MST_EN(1          ), +    .SYNC_CLKS      (0          ), +    .NUM_DATA_I     (1          ), +    .NUM_DATA_O     (1          ), +    .ITEM_W         (ITEM_W     ), +    .NIPC           (NIPC       ), +    .PYLD_FIFO_SIZE (MTU        ), +    .CTXT_FIFO_SIZE (1          ), +    .MTU            (MTU        ) +  ) noc_shell_fft_i ( +    .rfnoc_chdr_clk           (rfnoc_chdr_clk       ), +    .rfnoc_chdr_rst           (rfnoc_chdr_rst       ), +    .rfnoc_ctrl_clk           (rfnoc_ctrl_clk       ), +    .rfnoc_ctrl_rst           (                     ), +    .rfnoc_core_config        (rfnoc_core_config    ), +    .rfnoc_core_status        (rfnoc_core_status    ), +    .s_rfnoc_chdr_tdata       (s_rfnoc_chdr_tdata   ), +    .s_rfnoc_chdr_tlast       (s_rfnoc_chdr_tlast   ), +    .s_rfnoc_chdr_tvalid      (s_rfnoc_chdr_tvalid  ), +    .s_rfnoc_chdr_tready      (s_rfnoc_chdr_tready  ), +    .m_rfnoc_chdr_tdata       (m_rfnoc_chdr_tdata   ), +    .m_rfnoc_chdr_tlast       (m_rfnoc_chdr_tlast   ), +    .m_rfnoc_chdr_tvalid      (m_rfnoc_chdr_tvalid  ), +    .m_rfnoc_chdr_tready      (m_rfnoc_chdr_tready  ), +    .s_rfnoc_ctrl_tdata       (s_rfnoc_ctrl_tdata   ), +    .s_rfnoc_ctrl_tlast       (s_rfnoc_ctrl_tlast   ), +    .s_rfnoc_ctrl_tvalid      (s_rfnoc_ctrl_tvalid  ), +    .s_rfnoc_ctrl_tready      (s_rfnoc_ctrl_tready  ), +    .m_rfnoc_ctrl_tdata       (m_rfnoc_ctrl_tdata   ), +    .m_rfnoc_ctrl_tlast       (m_rfnoc_ctrl_tlast   ), +    .m_rfnoc_ctrl_tvalid      (m_rfnoc_ctrl_tvalid  ), +    .m_rfnoc_ctrl_tready      (m_rfnoc_ctrl_tready  ), +    .ctrlport_clk             (ce_clk               ), +    .ctrlport_rst             (ce_rst               ), +    .m_ctrlport_req_wr        (ctrlport_req_wr      ), +    .m_ctrlport_req_rd        (ctrlport_req_rd      ), +    .m_ctrlport_req_addr      (ctrlport_req_addr    ), +    .m_ctrlport_req_data      (ctrlport_req_data    ), +    .m_ctrlport_req_byte_en   (                     ), +    .m_ctrlport_req_has_time  (ctrlport_req_has_time), +    .m_ctrlport_req_time      (ctrlport_req_time    ), +    .m_ctrlport_resp_ack      (ctrlport_resp_ack    ), +    .m_ctrlport_resp_status   (AXIS_CTRL_STS_OKAY   ), +    .m_ctrlport_resp_data     (ctrlport_resp_data   ), +    .s_ctrlport_req_wr        (1'b0                 ), +    .s_ctrlport_req_rd        (1'b0                 ), +    .s_ctrlport_req_addr      (20'b0                ), +    .s_ctrlport_req_portid    (10'b0                ), +    .s_ctrlport_req_rem_epid  (16'b0                ), +    .s_ctrlport_req_rem_portid(10'b0                ), +    .s_ctrlport_req_data      (32'b0                ), +    .s_ctrlport_req_byte_en   (4'b0                 ), +    .s_ctrlport_req_has_time  (1'b0                 ), +    .s_ctrlport_req_time      (64'b0                ), +    .s_ctrlport_resp_ack      (                     ), +    .s_ctrlport_resp_status   (                     ), +    .s_ctrlport_resp_data     (                     ), +    .axis_data_clk            (ce_clk               ), +    .axis_data_rst            (ce_rst               ), +    .m_axis_payload_tdata     (axis_to_fft_tdata    ), +    .m_axis_payload_tkeep     (                     ), +    .m_axis_payload_tlast     (axis_to_fft_tlast    ), +    .m_axis_payload_tvalid    (axis_to_fft_tvalid   ), +    .m_axis_payload_tready    (axis_to_fft_tready   ), +    .s_axis_payload_tdata     (axis_from_fft_tdata  ), +    .s_axis_payload_tkeep     ({1*NIPC{1'b1}}       ), +    .s_axis_payload_tlast     (axis_from_fft_tlast  ), +    .s_axis_payload_tvalid    (axis_from_fft_tvalid ), +    .s_axis_payload_tready    (axis_from_fft_tready ), +    .m_axis_context_tdata     (m_axis_context_tdata ), +    .m_axis_context_tuser     (m_axis_context_tuser ), +    .m_axis_context_tlast     (m_axis_context_tlast ), +    .m_axis_context_tvalid    (m_axis_context_tvalid), +    .m_axis_context_tready    (m_axis_context_tready), +    .s_axis_context_tdata     (s_axis_context_tdata ), +    .s_axis_context_tuser     (s_axis_context_tuser ), +    .s_axis_context_tlast     (s_axis_context_tlast ), +    .s_axis_context_tvalid    (s_axis_context_tvalid), +    .s_axis_context_tready    (s_axis_context_tready) +  ); + +  // The input packets are the same configuration as the output packets, so +  // just use the header information for each incoming to create the header for +  // each outgoing packet. This is done by connecting m_axis_context to +  // directly to s_axis_context. +  assign s_axis_context_tdata  = m_axis_context_tdata; +  assign s_axis_context_tuser  = m_axis_context_tuser; +  assign s_axis_context_tlast  = m_axis_context_tlast; +  assign s_axis_context_tvalid = m_axis_context_tvalid; +  assign m_axis_context_tready = s_axis_context_tready; + +  wire [ 8-1:0] set_addr; +  wire [32-1:0] set_data; +  wire  set_has_time; +  wire  set_stb; +  wire [ 8-1:0] rb_addr; +  reg  [64-1:0] rb_data; + +  ctrlport_to_settings_bus # ( +    .NUM_PORTS (1) +  ) ctrlport_to_settings_bus_i ( +    .ctrlport_clk             (ce_clk), +    .ctrlport_rst             (ce_rst), +    .s_ctrlport_req_wr        (ctrlport_req_wr), +    .s_ctrlport_req_rd        (ctrlport_req_rd), +    .s_ctrlport_req_addr      (ctrlport_req_addr), +    .s_ctrlport_req_data      (ctrlport_req_data), +    .s_ctrlport_req_has_time  (ctrlport_req_has_time), +    .s_ctrlport_req_time      (ctrlport_req_time), +    .s_ctrlport_resp_ack      (ctrlport_resp_ack), +    .s_ctrlport_resp_data     (ctrlport_resp_data), +    .set_data                 (set_data), +    .set_addr                 (set_addr), +    .set_stb                  (set_stb), +    .set_time                 (), +    .set_has_time             (set_has_time), +    .rb_stb                   (1'b1), +    .rb_addr                  (rb_addr), +    .rb_data                  (rb_data)); + +  localparam MAX_FFT_SIZE_LOG2          = 11; + +  localparam [31:0] SR_FFT_RESET        = 131; +  localparam [31:0] SR_FFT_SIZE_LOG2    = 132; +  localparam [31:0] SR_MAGNITUDE_OUT    = 133; +  localparam [31:0] SR_FFT_DIRECTION    = 134; +  localparam [31:0] SR_FFT_SCALING      = 135; +  localparam [31:0] SR_FFT_SHIFT_CONFIG = 136; + +  // FFT Output +  localparam [1:0] COMPLEX_OUT = 0; +  localparam [1:0] MAG_OUT     = 1; +  localparam [1:0] MAG_SQ_OUT  = 2; + +  // FFT Direction +  localparam [0:0] FFT_REVERSE = 0; +  localparam [0:0] FFT_FORWARD = 1; + +  wire [1:0]  magnitude_out; +  wire [31:0] fft_data_o_tdata; +  wire        fft_data_o_tlast; +  wire        fft_data_o_tvalid; +  wire        fft_data_o_tready; +  wire [15:0] fft_data_o_tuser; +  wire [31:0] fft_shift_o_tdata; +  wire        fft_shift_o_tlast; +  wire        fft_shift_o_tvalid; +  wire        fft_shift_o_tready; +  wire [31:0] fft_mag_i_tdata, fft_mag_o_tdata, fft_mag_o_tdata_int; +  wire        fft_mag_i_tlast, fft_mag_o_tlast; +  wire        fft_mag_i_tvalid, fft_mag_o_tvalid; +  wire        fft_mag_i_tready, fft_mag_o_tready; +  wire [31:0] fft_mag_sq_i_tdata, fft_mag_sq_o_tdata; +  wire        fft_mag_sq_i_tlast, fft_mag_sq_o_tlast; +  wire        fft_mag_sq_i_tvalid, fft_mag_sq_o_tvalid; +  wire        fft_mag_sq_i_tready, fft_mag_sq_o_tready; +  wire [31:0] fft_mag_round_i_tdata, fft_mag_round_o_tdata; +  wire        fft_mag_round_i_tlast, fft_mag_round_o_tlast; +  wire        fft_mag_round_i_tvalid, fft_mag_round_o_tvalid; +  wire        fft_mag_round_i_tready, fft_mag_round_o_tready; + +  // Settings Registers +  wire fft_reset; +  setting_reg #( +    .my_addr(SR_FFT_RESET), .awidth(8), .width(1)) +  sr_fft_reset ( +    .clk(ce_clk), .rst(ce_rst), +    .strobe(set_stb), .addr(set_addr), .in(set_data), .out(fft_reset), .changed()); + +  // Two instances of FFT size register, one for FFT core and one for FFT shift +  localparam DEFAULT_FFT_SIZE = 8; // 256 +  wire [7:0] fft_size_log2_tdata ,fft_core_size_log2_tdata; +  wire fft_size_log2_tvalid, fft_core_size_log2_tvalid, fft_size_log2_tready, fft_core_size_log2_tready; +  axi_setting_reg #( +    .ADDR(SR_FFT_SIZE_LOG2), .AWIDTH(8), .WIDTH(8), .DATA_AT_RESET(DEFAULT_FFT_SIZE), .VALID_AT_RESET(1)) +  sr_fft_size_log2 ( +    .clk(ce_clk), .reset(ce_rst), +    .set_stb(set_stb), .set_addr(set_addr), .set_data(set_data), +    .o_tdata(fft_size_log2_tdata), .o_tlast(), .o_tvalid(fft_size_log2_tvalid), .o_tready(fft_size_log2_tready)); + +  axi_setting_reg #( +    .ADDR(SR_FFT_SIZE_LOG2), .AWIDTH(8), .WIDTH(8), .DATA_AT_RESET(DEFAULT_FFT_SIZE), .VALID_AT_RESET(1)) +  sr_fft_size_log2_2 ( +    .clk(ce_clk), .reset(ce_rst), +    .set_stb(set_stb), .set_addr(set_addr), .set_data(set_data), +    .o_tdata(fft_core_size_log2_tdata), .o_tlast(), .o_tvalid(fft_core_size_log2_tvalid), .o_tready(fft_core_size_log2_tready)); + +  // Forward = 0, Reverse = 1 +  localparam DEFAULT_FFT_DIRECTION = 0; +  wire fft_direction_tdata; +  wire fft_direction_tvalid, fft_direction_tready; +  axi_setting_reg #( +    .ADDR(SR_FFT_DIRECTION), .AWIDTH(8), .WIDTH(1), .DATA_AT_RESET(DEFAULT_FFT_DIRECTION), .VALID_AT_RESET(1)) +  sr_fft_direction ( +    .clk(ce_clk), .reset(ce_rst), +    .set_stb(set_stb), .set_addr(set_addr), .set_data(set_data), +    .o_tdata(fft_direction_tdata), .o_tlast(), .o_tvalid(fft_direction_tvalid), .o_tready(fft_direction_tready)); + +  localparam [11:0] DEFAULT_FFT_SCALING = 12'b011010101010; // Conservative 1/N scaling +  wire [11:0] fft_scaling_tdata; +  wire fft_scaling_tvalid, fft_scaling_tready; +  axi_setting_reg #( +    .ADDR(SR_FFT_SCALING), .AWIDTH(8), .WIDTH(12), .DATA_AT_RESET(DEFAULT_FFT_SCALING), .VALID_AT_RESET(1)) +  sr_fft_scaling ( +    .clk(ce_clk), .reset(ce_rst), +    .set_stb(set_stb), .set_addr(set_addr), .set_data(set_data), +    .o_tdata(fft_scaling_tdata), .o_tlast(), .o_tvalid(fft_scaling_tvalid), .o_tready(fft_scaling_tready)); + +  wire [1:0] fft_shift_config_tdata; +  wire fft_shift_config_tvalid, fft_shift_config_tready; +  axi_setting_reg #( +    .ADDR(SR_FFT_SHIFT_CONFIG), .AWIDTH(8), .WIDTH(2)) +  sr_fft_shift_config ( +    .clk(ce_clk), .reset(ce_rst), +    .set_stb(set_stb), .set_addr(set_addr), .set_data(set_data), +    .o_tdata(fft_shift_config_tdata), .o_tlast(), .o_tvalid(fft_shift_config_tvalid), .o_tready(fft_shift_config_tready)); + +  // Synchronize writing configuration to the FFT core +  reg fft_config_ready; +  wire fft_config_write = fft_config_ready & axis_to_fft_tvalid & axis_to_fft_tready; +  always @(posedge ce_clk) begin +    if (ce_rst | fft_reset) begin +      fft_config_ready   <= 1'b1; +    end else begin +      if (fft_config_write) begin +        fft_config_ready <= 1'b0; +      end else if (axis_to_fft_tlast) begin +        fft_config_ready <= 1'b1; +      end +    end +  end + +  wire [23:0] fft_config_tdata     = {3'd0, fft_scaling_tdata, fft_direction_tdata, fft_core_size_log2_tdata}; +  wire fft_config_tvalid           = fft_config_write & (fft_scaling_tvalid | fft_direction_tvalid | fft_core_size_log2_tvalid); +  wire fft_config_tready; +  assign fft_core_size_log2_tready = fft_config_tready & fft_config_write; +  assign fft_direction_tready      = fft_config_tready & fft_config_write; +  assign fft_scaling_tready        = fft_config_tready & fft_config_write; +  axi_fft inst_axi_fft ( +    .aclk(ce_clk), .aresetn(~(fft_reset)), +    .s_axis_data_tvalid(axis_to_fft_tvalid), +    .s_axis_data_tready(axis_to_fft_tready), +    .s_axis_data_tlast(axis_to_fft_tlast), +    .s_axis_data_tdata({axis_to_fft_tdata[15:0],axis_to_fft_tdata[31:16]}), +    .m_axis_data_tvalid(fft_data_o_tvalid), +    .m_axis_data_tready(fft_data_o_tready), +    .m_axis_data_tlast(fft_data_o_tlast), +    .m_axis_data_tdata({fft_data_o_tdata[15:0],fft_data_o_tdata[31:16]}), +    .m_axis_data_tuser(fft_data_o_tuser), // FFT index +    .s_axis_config_tdata(fft_config_tdata), +    .s_axis_config_tvalid(fft_config_tvalid), +    .s_axis_config_tready(fft_config_tready), +    .event_frame_started(), +    .event_tlast_unexpected(), +    .event_tlast_missing(), +    .event_status_channel_halt(), +    .event_data_in_channel_halt(), +    .event_data_out_channel_halt()); + +  // Mux control signals +  assign fft_shift_o_tready     = (magnitude_out == MAG_OUT)    ? fft_mag_i_tready         : +                                  (magnitude_out == MAG_SQ_OUT) ? fft_mag_sq_i_tready      : axis_from_fft_tready; +  assign fft_mag_i_tvalid       = (magnitude_out == MAG_OUT)    ? fft_shift_o_tvalid       : 1'b0; +  assign fft_mag_i_tlast        = (magnitude_out == MAG_OUT)    ? fft_shift_o_tlast        : 1'b0; +  assign fft_mag_i_tdata        = fft_shift_o_tdata; +  assign fft_mag_o_tready       = (magnitude_out == MAG_OUT)    ? fft_mag_round_i_tready   : 1'b0; +  assign fft_mag_sq_i_tvalid    = (magnitude_out == MAG_SQ_OUT) ? fft_shift_o_tvalid       : 1'b0; +  assign fft_mag_sq_i_tlast     = (magnitude_out == MAG_SQ_OUT) ? fft_shift_o_tlast        : 1'b0; +  assign fft_mag_sq_i_tdata     = fft_shift_o_tdata; +  assign fft_mag_sq_o_tready    = (magnitude_out == MAG_SQ_OUT) ? fft_mag_round_i_tready   : 1'b0; +  assign fft_mag_round_i_tvalid = (magnitude_out == MAG_OUT)    ? fft_mag_o_tvalid         : +                                  (magnitude_out == MAG_SQ_OUT) ? fft_mag_sq_o_tvalid      : 1'b0; +  assign fft_mag_round_i_tlast  = (magnitude_out == MAG_OUT)    ? fft_mag_o_tlast          : +                                  (magnitude_out == MAG_SQ_OUT) ? fft_mag_sq_o_tlast       : 1'b0; +  assign fft_mag_round_i_tdata  = (magnitude_out == MAG_OUT)    ? fft_mag_o_tdata          : fft_mag_sq_o_tdata; +  assign fft_mag_round_o_tready = axis_from_fft_tready; +  assign axis_from_fft_tvalid     = (magnitude_out == MAG_OUT | magnitude_out == MAG_SQ_OUT) ? fft_mag_round_o_tvalid : fft_shift_o_tvalid; +  assign axis_from_fft_tlast      = (magnitude_out == MAG_OUT | magnitude_out == MAG_SQ_OUT) ? fft_mag_round_o_tlast  : fft_shift_o_tlast; +  assign axis_from_fft_tdata      = (magnitude_out == MAG_OUT | magnitude_out == MAG_SQ_OUT) ? fft_mag_round_o_tdata  : fft_shift_o_tdata; + +  // Conditionally synth magnitude / magnitude^2 logic +  generate +    if (EN_MAGNITUDE_OUT | EN_MAGNITUDE_APPROX_OUT | EN_MAGNITUDE_SQ_OUT) begin : generate_magnitude_out +      setting_reg #( +        .my_addr(SR_MAGNITUDE_OUT), .awidth(8), .width(2)) +      sr_magnitude_out ( +        .clk(ce_clk), .rst(ce_rst), +        .strobe(set_stb), .addr(set_addr), .in(set_data), .out(magnitude_out), .changed()); +    end else begin : generate_magnitude_out_else +      // Magnitude calculation logic not included, so always bypass +      assign magnitude_out = 2'd0; +    end + +    if (EN_FFT_SHIFT) begin : generate_fft_shift +      fft_shift #( +        .MAX_FFT_SIZE_LOG2(MAX_FFT_SIZE_LOG2), +        .WIDTH(32)) +      inst_fft_shift ( +        .clk(ce_clk), .reset(ce_rst | fft_reset), +        .config_tdata(fft_shift_config_tdata), +        .config_tvalid(fft_shift_config_tvalid), +        .config_tready(fft_shift_config_tready), +        .fft_size_log2_tdata(fft_size_log2_tdata[$clog2(MAX_FFT_SIZE_LOG2)-1:0]), +        .fft_size_log2_tvalid(fft_size_log2_tvalid), +        .fft_size_log2_tready(fft_size_log2_tready), +        .i_tdata(fft_data_o_tdata), +        .i_tlast(fft_data_o_tlast), +        .i_tvalid(fft_data_o_tvalid), +        .i_tready(fft_data_o_tready), +        .i_tuser(fft_data_o_tuser[MAX_FFT_SIZE_LOG2-1:0]), +        .o_tdata(fft_shift_o_tdata), +        .o_tlast(fft_shift_o_tlast), +        .o_tvalid(fft_shift_o_tvalid), +        .o_tready(fft_shift_o_tready)); +    end  +    else begin : generate_fft_shift_else  +      assign fft_shift_o_tdata = fft_data_o_tdata; +      assign fft_shift_o_tlast = fft_data_o_tlast; +      assign fft_shift_o_tvalid = fft_data_o_tvalid; +      assign fft_data_o_tready = fft_shift_o_tready; +    end + +    // More accurate magnitude calculation takes precedence if enabled +    if (EN_MAGNITUDE_OUT) begin : generate_complex_to_magphase  +      complex_to_magphase +      inst_complex_to_magphase ( +        .aclk(ce_clk), .aresetn(~(ce_rst | fft_reset)), +        .s_axis_cartesian_tvalid(fft_mag_i_tvalid), +        .s_axis_cartesian_tlast(fft_mag_i_tlast), +        .s_axis_cartesian_tready(fft_mag_i_tready), +        .s_axis_cartesian_tdata(fft_mag_i_tdata), +        .m_axis_dout_tvalid(fft_mag_o_tvalid), +        .m_axis_dout_tlast(fft_mag_o_tlast), +        .m_axis_dout_tdata(fft_mag_o_tdata_int), +        .m_axis_dout_tready(fft_mag_o_tready)); +      assign fft_mag_o_tdata = {1'b0, fft_mag_o_tdata_int[15:0], 15'd0}; +    end  +    else if (EN_MAGNITUDE_APPROX_OUT) begin : generate_complex_to_mag_approx +      complex_to_mag_approx +      inst_complex_to_mag_approx ( +        .clk(ce_clk), .reset(ce_rst | fft_reset), .clear(1'b0), +        .i_tvalid(fft_mag_i_tvalid), +        .i_tlast(fft_mag_i_tlast), +        .i_tready(fft_mag_i_tready), +        .i_tdata(fft_mag_i_tdata), +        .o_tvalid(fft_mag_o_tvalid), +        .o_tlast(fft_mag_o_tlast), +        .o_tready(fft_mag_o_tready), +        .o_tdata(fft_mag_o_tdata_int[15:0])); +      assign fft_mag_o_tdata = {1'b0, fft_mag_o_tdata_int[15:0], 15'd0}; +    end   +    else begin : generate_complex_to_mag_approx_else +      assign fft_mag_o_tdata = fft_mag_i_tdata; +      assign fft_mag_o_tlast = fft_mag_i_tlast; +      assign fft_mag_o_tvalid = fft_mag_i_tvalid; +      assign fft_mag_i_tready = fft_mag_o_tready; +    end + +    if (EN_MAGNITUDE_SQ_OUT) begin : generate_complex_to_magsq +      complex_to_magsq +      inst_complex_to_magsq ( +        .clk(ce_clk), .reset(ce_rst | fft_reset), .clear(1'b0), +        .i_tvalid(fft_mag_sq_i_tvalid), +        .i_tlast(fft_mag_sq_i_tlast), +        .i_tready(fft_mag_sq_i_tready), +        .i_tdata(fft_mag_sq_i_tdata), +        .o_tvalid(fft_mag_sq_o_tvalid), +        .o_tlast(fft_mag_sq_o_tlast), +        .o_tready(fft_mag_sq_o_tready), +        .o_tdata(fft_mag_sq_o_tdata)); +    end +    else begin : generate_complex_to_magsq_else +      assign fft_mag_sq_o_tdata = fft_mag_sq_i_tdata; +      assign fft_mag_sq_o_tlast = fft_mag_sq_i_tlast; +      assign fft_mag_sq_o_tvalid = fft_mag_sq_i_tvalid; +      assign fft_mag_sq_i_tready = fft_mag_sq_o_tready; +    end + +    // Convert to SC16 +    if (EN_MAGNITUDE_OUT | EN_MAGNITUDE_APPROX_OUT | EN_MAGNITUDE_SQ_OUT) begin : generate_axi_round_and_clip +      axi_round_and_clip #( +        .WIDTH_IN(32), +        .WIDTH_OUT(16), +        .CLIP_BITS(1)) +      inst_axi_round_and_clip ( +        .clk(ce_clk), .reset(ce_rst | fft_reset), +        .i_tdata(fft_mag_round_i_tdata), +        .i_tlast(fft_mag_round_i_tlast), +        .i_tvalid(fft_mag_round_i_tvalid), +        .i_tready(fft_mag_round_i_tready), +        .o_tdata(fft_mag_round_o_tdata[31:16]), +        .o_tlast(fft_mag_round_o_tlast), +        .o_tvalid(fft_mag_round_o_tvalid), +        .o_tready(fft_mag_round_o_tready)); +      assign fft_mag_round_o_tdata[15:0] = {16{16'd0}}; +    end  +    else begin : generate_axi_round_and_clip_else +      assign fft_mag_round_o_tdata = fft_mag_round_i_tdata; +      assign fft_mag_round_o_tlast = fft_mag_round_i_tlast; +      assign fft_mag_round_o_tvalid = fft_mag_round_i_tvalid; +      assign fft_mag_round_i_tready = fft_mag_round_o_tready; +    end  +  endgenerate + +  // Readback registers +  always @* +    case(rb_addr) +      3'd0    : rb_data <= {63'd0, fft_reset}; +      3'd1    : rb_data <= {62'd0, magnitude_out}; +      3'd2    : rb_data <= {fft_size_log2_tdata}; +      3'd3    : rb_data <= {63'd0, fft_direction_tdata}; +      3'd4    : rb_data <= {52'd0, fft_scaling_tdata}; +      3'd5    : rb_data <= {62'd0, fft_shift_config_tdata}; +      default : rb_data <= 64'h0BADC0DE0BADC0DE; +  endcase + +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fft/rfnoc_block_fft_tb.sv b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fft/rfnoc_block_fft_tb.sv new file mode 100644 index 000000000..bb46e3cc7 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fft/rfnoc_block_fft_tb.sv @@ -0,0 +1,263 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_fft_tb +// +// Description:  Testbench for rfnoc_block_fft +// + +module rfnoc_block_fft_tb(); + +  // Include macros and time declarations for use with PkgTestExec +  `include "test_exec.svh" + +  import PkgTestExec::*; +  import PkgChdrUtils::*; +  import PkgRfnocBlockCtrlBfm::*; + +  //--------------------------------------------------------------------------- +  // Local Parameters +  //--------------------------------------------------------------------------- + +  // Simulation parameters +  localparam real CHDR_CLK_PER   = 5.0;   // Clock rate +  localparam int  SPP            = 256;   // Samples per packet +  localparam int  PKT_SIZE_BYTES = SPP*4; // Bytes per packet +  localparam int  STALL_PROB     = 25;    // BFM stall probability + +  // Block configuration +  localparam int CHDR_W        = 64; +  localparam int THIS_PORTID   = 'h123; +  localparam int MTU           = 10; +  localparam int NUM_PORTS     = 1; +  localparam int NUM_HB        = 3; +  localparam int CIC_MAX_DECIM = 255; + +  // FFT specific settings +  // FFT settings +  localparam [31:0] FFT_SIZE         = 256; +  localparam [31:0] FFT_SIZE_LOG2    = $clog2(FFT_SIZE); +  const logic [31:0] FFT_DIRECTION    = DUT.FFT_FORWARD;  // Forward +  localparam [31:0] FFT_SCALING      = 12'b011010101010;           // Conservative scaling of 1/N +  localparam [31:0] FFT_SHIFT_CONFIG = 0;                          // Normal FFT shift +  localparam FFT_BIN                 = FFT_SIZE/8 + FFT_SIZE/2;    // 1/8 sample rate freq + FFT shift +  localparam NUM_ITERATIONS  = 10; + +  //--------------------------------------------------------------------------- +  // Clocks +  //--------------------------------------------------------------------------- + +  bit rfnoc_chdr_clk; +  bit rfnoc_ctrl_clk; + +  sim_clock_gen #(CHDR_CLK_PER)  rfnoc_chdr_clk_gen (.clk(rfnoc_chdr_clk), .rst()); +  sim_clock_gen #(CHDR_CLK_PER)  rfnoc_ctrl_clk_gen (.clk(rfnoc_ctrl_clk), .rst()); + +  //--------------------------------------------------------------------------- +  // Bus Functional Models +  //--------------------------------------------------------------------------- + +  RfnocBackendIf        backend            (rfnoc_chdr_clk, rfnoc_ctrl_clk); +  AxiStreamIf #(32)     m_ctrl             (rfnoc_ctrl_clk, 1'b0); +  AxiStreamIf #(32)     s_ctrl             (rfnoc_ctrl_clk, 1'b0); +  AxiStreamIf #(CHDR_W) m_chdr             (rfnoc_chdr_clk, 1'b0); +  AxiStreamIf #(CHDR_W) s_chdr             (rfnoc_chdr_clk, 1'b0); + +  // Bus functional model for a software block controller +  RfnocBlockCtrlBfm #(.CHDR_W(CHDR_W)) blk_ctrl =  +    new(backend, m_ctrl, s_ctrl); + +  // Connect block controller to BFMs +  initial begin +    blk_ctrl.connect_master_data_port(0, m_chdr, PKT_SIZE_BYTES); +    blk_ctrl.connect_slave_data_port(0, s_chdr); +    blk_ctrl.set_master_stall_prob(0, STALL_PROB); +    blk_ctrl.set_slave_stall_prob(0, STALL_PROB); +  end + +  //--------------------------------------------------------------------------- +  // DUT +  //--------------------------------------------------------------------------- + +  rfnoc_block_fft #( +    .THIS_PORTID            (0  ), +    .CHDR_W                 (64 ), +    .MTU                    (MTU), +     +    .EN_MAGNITUDE_OUT       (0  ), +    .EN_MAGNITUDE_APPROX_OUT(1  ), +    .EN_MAGNITUDE_SQ_OUT    (1  ), +    .EN_FFT_SHIFT           (1  ) +  ) DUT ( +    .rfnoc_chdr_clk     (backend.chdr_clk), +    .ce_clk             (backend.chdr_clk), +    .s_rfnoc_chdr_tdata (m_chdr.tdata    ), +    .s_rfnoc_chdr_tlast (m_chdr.tlast    ), +    .s_rfnoc_chdr_tvalid(m_chdr.tvalid   ), +    .s_rfnoc_chdr_tready(m_chdr.tready   ), +     +    .m_rfnoc_chdr_tdata (s_chdr.tdata    ), +    .m_rfnoc_chdr_tlast (s_chdr.tlast    ), +    .m_rfnoc_chdr_tvalid(s_chdr.tvalid   ), +    .m_rfnoc_chdr_tready(s_chdr.tready   ), +     +    .rfnoc_core_config  (backend.cfg     ), +    .rfnoc_core_status  (backend.sts     ), +    .rfnoc_ctrl_clk     (backend.ctrl_clk), +     +    .s_rfnoc_ctrl_tdata (m_ctrl.tdata    ), +    .s_rfnoc_ctrl_tlast (m_ctrl.tlast    ), +    .s_rfnoc_ctrl_tvalid(m_ctrl.tvalid   ), +    .s_rfnoc_ctrl_tready(m_ctrl.tready   ), +     +    .m_rfnoc_ctrl_tdata (s_ctrl.tdata    ), +    .m_rfnoc_ctrl_tlast (s_ctrl.tlast    ), +    .m_rfnoc_ctrl_tvalid(s_ctrl.tvalid   ), +    .m_rfnoc_ctrl_tready(s_ctrl.tready   ) +  ); + +  //--------------------------------------------------------------------------- +  // Helper Tasks +  //--------------------------------------------------------------------------- + +  // Translate the desired register access to a ctrlport write request. +  task automatic write_reg(int port, byte addr, bit [31:0] value); +    blk_ctrl.reg_write(256*8*port + addr*8, value); +  endtask : write_reg + +  // Translate the desired register access to a ctrlport read request. +  task automatic read_user_reg(int port, byte addr, output logic [63:0] value); +    blk_ctrl.reg_read(256*8*port + addr*8 + 0, value[31: 0]); +    blk_ctrl.reg_read(256*8*port + addr*8 + 4, value[63:32]); +  endtask : read_user_reg + +  //--------------------------------------------------------------------------- +  // Test Process +  //--------------------------------------------------------------------------- + +  task automatic send_sine_wave ( +    input int unsigned port +  ); +    // Send a sine wave +    fork +      begin +        chdr_word_t send_payload[$]; + +        for (int n = 0; n < NUM_ITERATIONS; n++) begin +          for (int i = 0; i < (FFT_SIZE/8); i++) begin +            send_payload.push_back({ 16'h5A82, 16'h5A82, 16'h7FFF, 16'h0000}); +            send_payload.push_back({-16'h5A82, 16'h5A82, 16'h0000, 16'h7FFF}); +            send_payload.push_back({-16'h5A82,-16'h5A82,-16'h7FFF, 16'h0000}); +            send_payload.push_back({ 16'h5A82,-16'h5A82, 16'h0000,-16'h7FFF}); +          end + +          blk_ctrl.send(port, send_payload); +          blk_ctrl.wait_complete(port); +          send_payload = {}; +        end +      end + +      begin +        string s; +        chdr_word_t   recv_payload[$], temp_payload[$]; +        int           data_bytes; +        logic [15:0] real_val; +        logic [15:0] cplx_val; + +        for (int n = 0; n < NUM_ITERATIONS; n++) begin +          blk_ctrl.recv(port, recv_payload, data_bytes); + +          `ASSERT_ERROR(recv_payload.size * 2 == FFT_SIZE, "received wrong amount of data"); + +          for (int k = 0; k < FFT_SIZE/2; k++) begin +            chdr_word_t payload_word; +            payload_word = recv_payload.pop_front(); + +            for (int i = 0; i < 2; i++) begin +              {real_val, cplx_val} = payload_word; +              payload_word = payload_word[63:32]; + +              if (2*k+i == FFT_BIN) begin +                // Assert that for the special case of a 1/8th sample rate sine wave input,  +                // the real part of the corresponding 1/8th sample rate FFT bin should always be greater than 0 and +                // the complex part equal to 0. +                 +                `ASSERT_ERROR(real_val > 32'd0, "FFT bin real part is not greater than 0!"); +                `ASSERT_ERROR(cplx_val == 32'd0, "FFT bin complex part is not 0!"); +              end else begin +                // Assert all other FFT bins should be 0 for both complex and real parts +                `ASSERT_ERROR(real_val == 32'd0, "FFT bin real part is not 0!"); +                `ASSERT_ERROR(cplx_val == 32'd0, "FFT bin complex part is not 0!"); +              end +            end +          end +        end +      end +    join +  endtask + +  initial begin : tb_main +    const int port = 0; +    test.start_tb("rfnoc_block_fft_tb"); + +    // Start the BFMs running +    blk_ctrl.run(); + +    //------------------------------------------------------------------------- +    // Reset +    //------------------------------------------------------------------------- +     +    test.start_test("Wait for Reset", 10us); +    fork +      blk_ctrl.reset_chdr(); +      blk_ctrl.reset_ctrl(); +    join; +    test.end_test(); + +     +    //------------------------------------------------------------------------- +    // Check NoC ID and Block Info +    //------------------------------------------------------------------------- +     +    test.start_test("Verify Block Info", 2us); +    `ASSERT_ERROR(blk_ctrl.get_noc_id() == DUT.NOC_ID, "Incorrect NOC_ID Value"); +    `ASSERT_ERROR(blk_ctrl.get_num_data_i() == NUM_PORTS, "Incorrect NUM_DATA_I Value"); +    `ASSERT_ERROR(blk_ctrl.get_num_data_o() == NUM_PORTS, "Incorrect NUM_DATA_O Value"); +    `ASSERT_ERROR(blk_ctrl.get_mtu() == MTU, "Incorrect MTU Value"); +    test.end_test(); + +    //------------------------------------------------------------------------- +    // Setup FFT +    //------------------------------------------------------------------------- + +    test.start_test("Setup FFT", 10us); +    write_reg(port, DUT.SR_FFT_SIZE_LOG2, FFT_SIZE_LOG2); +    write_reg(port, DUT.SR_FFT_DIRECTION, FFT_DIRECTION); +    write_reg(port, DUT.SR_FFT_SCALING, FFT_SCALING); +    write_reg(port, DUT.SR_FFT_SHIFT_CONFIG, FFT_SHIFT_CONFIG); +    write_reg(port, DUT.SR_MAGNITUDE_OUT, DUT.COMPLEX_OUT); // Enable real/imag out +    test.end_test(); + +    //-------------------------------------------------------------------------76 +    // Test sine wave +    //------------------------------------------------------------------------- + +    test.start_test("Test sine wave", 20us); +    send_sine_wave (port); +    test.end_test(); + +    //------------------------------------------------------------------------- +    // Finish +    //------------------------------------------------------------------------- + +    // 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 +    rfnoc_chdr_clk_gen.kill(); +    rfnoc_ctrl_clk_gen.kill(); +  end +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/Makefile b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/Makefile new file mode 100644 index 000000000..7d6d84f82 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/Makefile @@ -0,0 +1,46 @@ +# +# Copyright 2019 Ettus Research, A National Instruments Company +# +# 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_preamble after defining BASE_DIR +include $(BASE_DIR)/../tools/make/viv_sim_preamble.mak + +#------------------------------------------------- +# Design Specific +#------------------------------------------------- +# Include makefiles and sources for the DUT and its dependencies +include $(BASE_DIR)/../lib/rfnoc/core/Makefile.srcs +include $(BASE_DIR)/../lib/rfnoc/utils/Makefile.srcs +include Makefile.srcs + +DESIGN_SRCS += $(abspath \ +$(RFNOC_CORE_SRCS) \ +$(RFNOC_UTIL_SRCS) \ +$(RFNOC_OOT_SRCS) \ +) + +#------------------------------------------------- +# Testbench Specific +#------------------------------------------------- +# Define only one toplevel module +SIM_TOP = rfnoc_block_fir_filter_tb + +# Add test bench, user design under test, and +# additional user created files +SIM_SRCS = \ +$(abspath rfnoc_block_fir_filter_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/blocks/rfnoc_block_fir_filter/Makefile.srcs b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/Makefile.srcs new file mode 100644 index 000000000..f8c696096 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/Makefile.srcs @@ -0,0 +1,12 @@ +# +# Copyright 2019 Ettus Research, A National Instruments Company +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# + +RFNOC_OOT_SRCS += $(abspath $(addprefix $(BASE_DIR)/../lib/rfnoc/blocks/rfnoc_block_fir_filter/, \ +noc_shell_fir_filter.v \ +rfnoc_fir_filter_regs.vh \ +rfnoc_fir_filter_core.v \ +rfnoc_block_fir_filter.v \ +)) diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/noc_shell_fir_filter.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/noc_shell_fir_filter.v new file mode 100644 index 000000000..ce9a66fd9 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/noc_shell_fir_filter.v @@ -0,0 +1,297 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: noc_shell_fir_filter +// +// Description: A NoC Shell for RFNoC. This should eventually be replaced +// by an auto-generated NoC Shell. +// + +module noc_shell_fir_filter #( +  parameter [31:0] NOC_ID          = 32  'h0, +  parameter [ 9:0] THIS_PORTID     = 10  'd0, +  parameter        CHDR_W          = 64, +  parameter [ 0:0] CTRLPORT_SLV_EN = 1, +  parameter [ 0:0] CTRLPORT_MST_EN = 1, +  parameter        SYNC_CLKS       = 0, +  parameter [ 5:0] NUM_DATA_I      = 1, +  parameter [ 5:0] NUM_DATA_O      = 1, +  parameter        ITEM_W          = 32, +  parameter        NIPC            = 2, +  parameter        PYLD_FIFO_SIZE  = 5, +  parameter        CTXT_FIFO_SIZE  = 5, +  parameter        MTU             = 10 +) ( +  //--------------------------------------------------------------------------- +  // Framework Interface +  //--------------------------------------------------------------------------- + +  // RFNoC Framework Clocks and Resets +  input  wire                           rfnoc_chdr_clk, +  output wire                           rfnoc_chdr_rst, +  input  wire                           rfnoc_ctrl_clk, +  output wire                           rfnoc_ctrl_rst, +  // RFNoC Backend Interface +  input  wire [                  511:0] rfnoc_core_config, +  output wire [                  511:0] rfnoc_core_status, +  // CHDR Input Ports (from framework) +  input  wire [(CHDR_W*NUM_DATA_I)-1:0] s_rfnoc_chdr_tdata, +  input  wire [         NUM_DATA_I-1:0] s_rfnoc_chdr_tlast, +  input  wire [         NUM_DATA_I-1:0] s_rfnoc_chdr_tvalid, +  output wire [         NUM_DATA_I-1:0] s_rfnoc_chdr_tready, +  // CHDR Output Ports (to framework) +  output wire [(CHDR_W*NUM_DATA_O)-1:0] m_rfnoc_chdr_tdata, +  output wire [         NUM_DATA_O-1:0] m_rfnoc_chdr_tlast, +  output wire [         NUM_DATA_O-1:0] m_rfnoc_chdr_tvalid, +  input  wire [         NUM_DATA_O-1:0] m_rfnoc_chdr_tready, +  // AXIS-Ctrl Input Port (from framework) +  input  wire [                   31:0] s_rfnoc_ctrl_tdata, +  input  wire                           s_rfnoc_ctrl_tlast, +  input  wire                           s_rfnoc_ctrl_tvalid, +  output wire                           s_rfnoc_ctrl_tready, +  // AXIS-Ctrl Output Port (to framework) +  output wire [                   31:0] m_rfnoc_ctrl_tdata, +  output wire                           m_rfnoc_ctrl_tlast, +  output wire                           m_rfnoc_ctrl_tvalid, +  input  wire                           m_rfnoc_ctrl_tready, + +  //--------------------------------------------------------------------------- +  // Client Control Port Interface +  //--------------------------------------------------------------------------- + +  // Clock +  input  wire        ctrlport_clk, +  input  wire        ctrlport_rst, +  // Master +  output wire        m_ctrlport_req_wr, +  output wire        m_ctrlport_req_rd, +  output wire [19:0] m_ctrlport_req_addr, +  output wire [31:0] m_ctrlport_req_data, +  output wire [ 3:0] m_ctrlport_req_byte_en, +  output wire        m_ctrlport_req_has_time, +  output wire [63:0] m_ctrlport_req_time, +  input  wire        m_ctrlport_resp_ack, +  input  wire [ 1:0] m_ctrlport_resp_status, +  input  wire [31:0] m_ctrlport_resp_data, +  // Slave +  input  wire        s_ctrlport_req_wr, +  input  wire        s_ctrlport_req_rd, +  input  wire [19:0] s_ctrlport_req_addr, +  input  wire [ 9:0] s_ctrlport_req_portid, +  input  wire [15:0] s_ctrlport_req_rem_epid, +  input  wire [ 9:0] s_ctrlport_req_rem_portid, +  input  wire [31:0] s_ctrlport_req_data, +  input  wire [ 3:0] s_ctrlport_req_byte_en, +  input  wire        s_ctrlport_req_has_time, +  input  wire [63:0] s_ctrlport_req_time, +  output wire        s_ctrlport_resp_ack, +  output wire [ 1:0] s_ctrlport_resp_status, +  output wire [31:0] s_ctrlport_resp_data, + +  //--------------------------------------------------------------------------- +  // Client Data Interface +  //--------------------------------------------------------------------------- + +  // Clock +  input wire axis_data_clk, +  input wire axis_data_rst, + +  // Output data stream (to user logic) +  output wire [(NUM_DATA_I*ITEM_W*NIPC)-1:0] m_axis_payload_tdata, +  output wire [       (NUM_DATA_I*NIPC)-1:0] m_axis_payload_tkeep, +  output wire [              NUM_DATA_I-1:0] m_axis_payload_tlast, +  output wire [              NUM_DATA_I-1:0] m_axis_payload_tvalid, +  input  wire [              NUM_DATA_I-1:0] m_axis_payload_tready, + +  // Input data stream (from user logic) +  input  wire [(NUM_DATA_O*ITEM_W*NIPC)-1:0] s_axis_payload_tdata, +  input  wire [       (NUM_DATA_O*NIPC)-1:0] s_axis_payload_tkeep, +  input  wire [              NUM_DATA_O-1:0] s_axis_payload_tlast, +  input  wire [              NUM_DATA_O-1:0] s_axis_payload_tvalid, +  output wire [              NUM_DATA_O-1:0] s_axis_payload_tready, + +  // Output context stream (to user logic) +  output wire [(NUM_DATA_I*CHDR_W)-1:0] m_axis_context_tdata, +  output wire [     (4*NUM_DATA_I)-1:0] m_axis_context_tuser, +  output wire [         NUM_DATA_I-1:0] m_axis_context_tlast, +  output wire [         NUM_DATA_I-1:0] m_axis_context_tvalid, +  input  wire [         NUM_DATA_I-1:0] m_axis_context_tready, + +  // Input context stream (from user logic) +  input  wire [(NUM_DATA_O*CHDR_W)-1:0] s_axis_context_tdata, +  input  wire [     (4*NUM_DATA_O)-1:0] s_axis_context_tuser, +  input  wire [         NUM_DATA_O-1:0] s_axis_context_tlast, +  input  wire [         NUM_DATA_O-1:0] s_axis_context_tvalid, +  output wire [         NUM_DATA_O-1:0] s_axis_context_tready +); +   +  localparam CTRL_FIFO_SIZE = 5; + + +  //--------------------------------------------------------------------------- +  //  Backend Interface +  //--------------------------------------------------------------------------- + +  wire         data_i_flush_en; +  wire [31:0]  data_i_flush_timeout; +  wire [63:0]  data_i_flush_active; +  wire [63:0]  data_i_flush_done; +  wire         data_o_flush_en; +  wire [31:0]  data_o_flush_timeout; +  wire [63:0]  data_o_flush_active; +  wire [63:0]  data_o_flush_done; + +  backend_iface #( +    .NOC_ID        (NOC_ID), +    .NUM_DATA_I    (NUM_DATA_I), +    .NUM_DATA_O    (NUM_DATA_O), +    .CTRL_FIFOSIZE (CTRL_FIFO_SIZE), +    .MTU           (MTU) +  ) backend_iface_i ( +    .rfnoc_chdr_clk       (rfnoc_chdr_clk), +    .rfnoc_ctrl_clk       (rfnoc_ctrl_clk), +    .rfnoc_core_config    (rfnoc_core_config), +    .rfnoc_core_status    (rfnoc_core_status), +    .rfnoc_chdr_rst       (rfnoc_chdr_rst), +    .rfnoc_ctrl_rst       (rfnoc_ctrl_rst), +    .data_i_flush_en      (data_i_flush_en), +    .data_i_flush_timeout (data_i_flush_timeout), +    .data_i_flush_active  (data_i_flush_active), +    .data_i_flush_done    (data_i_flush_done), +    .data_o_flush_en      (data_o_flush_en), +    .data_o_flush_timeout (data_o_flush_timeout), +    .data_o_flush_active  (data_o_flush_active), +    .data_o_flush_done    (data_o_flush_done) +  ); + +  //--------------------------------------------------------------------------- +  //  Control Path +  //--------------------------------------------------------------------------- + +  ctrlport_endpoint #( +    .THIS_PORTID              (THIS_PORTID    ), +    .SYNC_CLKS                (0              ), +    .AXIS_CTRL_MST_EN         (CTRLPORT_SLV_EN), +    .AXIS_CTRL_SLV_EN         (CTRLPORT_MST_EN), +    .SLAVE_FIFO_SIZE          (CTRL_FIFO_SIZE ) +  ) ctrlport_ep_i ( +    .rfnoc_ctrl_clk           (rfnoc_ctrl_clk           ), +    .rfnoc_ctrl_rst           (rfnoc_ctrl_rst           ), +    .ctrlport_clk             (ctrlport_clk             ), +    .ctrlport_rst             (ctrlport_rst             ), +    .s_rfnoc_ctrl_tdata       (s_rfnoc_ctrl_tdata       ), +    .s_rfnoc_ctrl_tlast       (s_rfnoc_ctrl_tlast       ), +    .s_rfnoc_ctrl_tvalid      (s_rfnoc_ctrl_tvalid      ), +    .s_rfnoc_ctrl_tready      (s_rfnoc_ctrl_tready      ), +    .m_rfnoc_ctrl_tdata       (m_rfnoc_ctrl_tdata       ), +    .m_rfnoc_ctrl_tlast       (m_rfnoc_ctrl_tlast       ), +    .m_rfnoc_ctrl_tvalid      (m_rfnoc_ctrl_tvalid      ), +    .m_rfnoc_ctrl_tready      (m_rfnoc_ctrl_tready      ), +    .m_ctrlport_req_wr        (m_ctrlport_req_wr        ), +    .m_ctrlport_req_rd        (m_ctrlport_req_rd        ), +    .m_ctrlport_req_addr      (m_ctrlport_req_addr      ), +    .m_ctrlport_req_data      (m_ctrlport_req_data      ), +    .m_ctrlport_req_byte_en   (m_ctrlport_req_byte_en   ), +    .m_ctrlport_req_has_time  (m_ctrlport_req_has_time  ), +    .m_ctrlport_req_time      (m_ctrlport_req_time      ), +    .m_ctrlport_resp_ack      (m_ctrlport_resp_ack      ), +    .m_ctrlport_resp_status   (m_ctrlport_resp_status   ), +    .m_ctrlport_resp_data     (m_ctrlport_resp_data     ), +    .s_ctrlport_req_wr        (s_ctrlport_req_wr        ), +    .s_ctrlport_req_rd        (s_ctrlport_req_rd        ), +    .s_ctrlport_req_addr      (s_ctrlport_req_addr      ), +    .s_ctrlport_req_portid    (s_ctrlport_req_portid    ), +    .s_ctrlport_req_rem_epid  (s_ctrlport_req_rem_epid  ), +    .s_ctrlport_req_rem_portid(s_ctrlport_req_rem_portid), +    .s_ctrlport_req_data      (s_ctrlport_req_data      ), +    .s_ctrlport_req_byte_en   (s_ctrlport_req_byte_en   ), +    .s_ctrlport_req_has_time  (s_ctrlport_req_has_time  ), +    .s_ctrlport_req_time      (s_ctrlport_req_time      ), +    .s_ctrlport_resp_ack      (s_ctrlport_resp_ack      ), +    .s_ctrlport_resp_status   (s_ctrlport_resp_status   ), +    .s_ctrlport_resp_data     (s_ctrlport_resp_data     ) +  ); + +  //--------------------------------------------------------------------------- +  //  Data Path +  //--------------------------------------------------------------------------- + +  genvar i; +  generate + +    for (i = 0; i < NUM_DATA_I; i = i + 1) begin: chdr_to_data +      chdr_to_axis_pyld_ctxt #( +        .CHDR_W               (CHDR_W        ), +        .ITEM_W               (ITEM_W        ), +        .NIPC                 (NIPC          ), +        .SYNC_CLKS            (SYNC_CLKS     ), +        .CONTEXT_FIFO_SIZE    (CTXT_FIFO_SIZE), +        .PAYLOAD_FIFO_SIZE    (PYLD_FIFO_SIZE), +        .CONTEXT_PREFETCH_EN  (1             ) +      ) chdr_to_axis_pyld_ctxt_i ( +        .axis_chdr_clk        (rfnoc_chdr_clk                                       ), +        .axis_chdr_rst        (rfnoc_chdr_rst                                       ), +        .axis_data_clk        (axis_data_clk                                        ), +        .axis_data_rst        (axis_data_rst                                        ), +        .s_axis_chdr_tdata    (s_rfnoc_chdr_tdata   [(i*CHDR_W)+:CHDR_W]            ), +        .s_axis_chdr_tlast    (s_rfnoc_chdr_tlast   [i]                             ), +        .s_axis_chdr_tvalid   (s_rfnoc_chdr_tvalid  [i]                             ), +        .s_axis_chdr_tready   (s_rfnoc_chdr_tready  [i]                             ), +        .m_axis_payload_tdata (m_axis_payload_tdata [(i*ITEM_W*NIPC)+:(ITEM_W*NIPC)]), +        .m_axis_payload_tkeep (m_axis_payload_tkeep [(i*NIPC)+:NIPC]                ), +        .m_axis_payload_tlast (m_axis_payload_tlast [i]                             ), +        .m_axis_payload_tvalid(m_axis_payload_tvalid[i]                             ), +        .m_axis_payload_tready(m_axis_payload_tready[i]                             ), +        .m_axis_context_tdata (m_axis_context_tdata [(i*CHDR_W)+:(CHDR_W)]          ), +        .m_axis_context_tuser (m_axis_context_tuser [(i*4)+:4]                      ), +        .m_axis_context_tlast (m_axis_context_tlast [i]                             ), +        .m_axis_context_tvalid(m_axis_context_tvalid[i]                             ), +        .m_axis_context_tready(m_axis_context_tready[i]                             ), +        .flush_en             (data_i_flush_en                                      ), +        .flush_timeout        (data_i_flush_timeout                                 ), +        .flush_active         (data_i_flush_active  [i]                             ), +        .flush_done           (data_i_flush_done    [i]                             ) +      ); +    end + +    for (i = 0; i < NUM_DATA_O; i = i + 1) begin: data_to_chdr +      axis_pyld_ctxt_to_chdr #( +        .CHDR_W               (CHDR_W        ), +        .ITEM_W               (ITEM_W        ), +        .NIPC                 (NIPC          ), +        .SYNC_CLKS            (SYNC_CLKS     ), +        .CONTEXT_FIFO_SIZE    (CTXT_FIFO_SIZE), +        .PAYLOAD_FIFO_SIZE    (PYLD_FIFO_SIZE), +        .CONTEXT_PREFETCH_EN  (1             ), +        .MTU                  (MTU           ) +      ) axis_pyld_ctxt_to_chdr_i ( +        .axis_chdr_clk        (rfnoc_chdr_clk                                       ), +        .axis_chdr_rst        (rfnoc_chdr_rst                                       ), +        .axis_data_clk        (axis_data_clk                                        ), +        .axis_data_rst        (axis_data_rst                                        ), +        .m_axis_chdr_tdata    (m_rfnoc_chdr_tdata   [(i*CHDR_W)+:CHDR_W]            ), +        .m_axis_chdr_tlast    (m_rfnoc_chdr_tlast   [i]                             ), +        .m_axis_chdr_tvalid   (m_rfnoc_chdr_tvalid  [i]                             ), +        .m_axis_chdr_tready   (m_rfnoc_chdr_tready  [i]                             ), +        .s_axis_payload_tdata (s_axis_payload_tdata [(i*ITEM_W*NIPC)+:(ITEM_W*NIPC)]), +        .s_axis_payload_tkeep (s_axis_payload_tkeep [(i*NIPC)+:NIPC]                ), +        .s_axis_payload_tlast (s_axis_payload_tlast [i]                             ), +        .s_axis_payload_tvalid(s_axis_payload_tvalid[i]                             ), +        .s_axis_payload_tready(s_axis_payload_tready[i]                             ), +        .s_axis_context_tdata (s_axis_context_tdata [(i*CHDR_W)+:(CHDR_W)]          ), +        .s_axis_context_tuser (s_axis_context_tuser [(i*4)+:4]                      ), +        .s_axis_context_tlast (s_axis_context_tlast [i]                             ), +        .s_axis_context_tvalid(s_axis_context_tvalid[i]                             ), +        .s_axis_context_tready(s_axis_context_tready[i]                             ), +        .framer_errors        (                                                     ), +        .flush_en             (data_o_flush_en                                      ), +        .flush_timeout        (data_o_flush_timeout                                 ), +        .flush_active         (data_o_flush_active  [i]                             ), +        .flush_done           (data_o_flush_done    [i]                             ) +      ); +    end +  endgenerate + +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/rfnoc_block_fir_filter.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/rfnoc_block_fir_filter.v new file mode 100644 index 000000000..f007049cc --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/rfnoc_block_fir_filter.v @@ -0,0 +1,343 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Description: +// +//   Parameterized FIR filter RFNoC block with optional re-loadable +//   coefficients. +// +//   It has several optimizations for resource utilization such as using half +//   the number of DSP slices for symmetric coefficients, skipping coefficients +//   that are always set to zero, and using internal DSP slice registers to +//   hold coefficients. +// +//   For the most efficient DSP slice inference use these settings, set  +//   COEFF_WIDTH to be less than 18. +// +// Parameters: +//  +//   COEFF_WIDTH              : Coefficient width +// +//   NUM_COEFFS               : Number of coefficients / filter taps +// +//   COEFFS_VEC               : Vector of NUM_COEFFS values, each of width +//                              COEFF_WIDTH, to initialize the filter +//                              coefficients. Defaults to an impulse. +// +//   RELOADABLE_COEFFS        : Enable (1) or disable (0) reloading +//                              coefficients at runtime +// +//   SYMMETRIC_COEFFS         : Reduce multiplier usage by approximately half +//                              if coefficients are symmetric +// +//   SKIP_ZERO_COEFFS         : Reduce multiplier usage by assuming zero valued +//                              coefficients in DEFAULT_COEFFS are always zero. +//                              Useful for halfband filters. +// +//   USE_EMBEDDED_REGS_COEFFS : Reduce register usage by only using embedded +//                              registers in DSP slices. Updating taps while +//                              streaming will cause temporary output +//                              corruption! +// +//   Note: If using USE_EMBEDDED_REGS_COEFFS, coefficients must be written at  +//   least once since COEFFS_VEC is ignored! +// + + +module rfnoc_block_fir_filter #( +  // RFNoC Parameters +  parameter THIS_PORTID              = 0, +  parameter CHDR_W                   = 64, +  parameter NUM_PORTS                = 2, +  parameter MTU                      = 10, +  // FIR Filter Parameters +  parameter COEFF_WIDTH              = 16, +  parameter NUM_COEFFS               = 41, +  parameter [NUM_COEFFS*COEFF_WIDTH-1:0] COEFFS_VEC = // Make impulse by default +    {  +      {1'b0, {(COEFF_WIDTH-1){1'b1}} },        // Max positive value +      {(COEFF_WIDTH*(NUM_COEFFS-1)){1'b0}}     // Zero for remaining coefficients +    }, +  parameter RELOADABLE_COEFFS        = 1, +  parameter SYMMETRIC_COEFFS         = 0, +  parameter SKIP_ZERO_COEFFS         = 0, +  parameter USE_EMBEDDED_REGS_COEFFS = 1 +)( +  // Clock to use for signal processing +  input wire ce_clk, + + +  //--------------------------------------------------------------------------- +  // AXIS CHDR Port +  //--------------------------------------------------------------------------- + +  input wire rfnoc_chdr_clk, + +  // CHDR inputs from framework +  input  wire [NUM_PORTS*CHDR_W-1:0] s_rfnoc_chdr_tdata, +  input  wire [       NUM_PORTS-1:0] s_rfnoc_chdr_tlast, +  input  wire [       NUM_PORTS-1:0] s_rfnoc_chdr_tvalid, +  output wire [       NUM_PORTS-1:0] s_rfnoc_chdr_tready, + +  // CHDR outputs to framework +  output wire [NUM_PORTS*CHDR_W-1:0] m_rfnoc_chdr_tdata, +  output wire [       NUM_PORTS-1:0] m_rfnoc_chdr_tlast, +  output wire [       NUM_PORTS-1:0] m_rfnoc_chdr_tvalid, +  input  wire [       NUM_PORTS-1:0] m_rfnoc_chdr_tready, + +  // Backend interface +  input  wire [511:0] rfnoc_core_config, +  output wire [511:0] rfnoc_core_status, + + +  //--------------------------------------------------------------------------- +  // AXIS CTRL Port +  //--------------------------------------------------------------------------- + +  input wire rfnoc_ctrl_clk, + +  // CTRL port requests from framework +  input  wire [31:0] s_rfnoc_ctrl_tdata, +  input  wire        s_rfnoc_ctrl_tlast, +  input  wire        s_rfnoc_ctrl_tvalid, +  output wire        s_rfnoc_ctrl_tready, + +  // CTRL port requests to framework +  output wire [31:0] m_rfnoc_ctrl_tdata, +  output wire        m_rfnoc_ctrl_tlast, +  output wire        m_rfnoc_ctrl_tvalid, +  input  wire        m_rfnoc_ctrl_tready +); + +  `include "rfnoc_fir_filter_regs.vh" + +  // These are the only supported values for now +  localparam ITEM_W = 32; +  localparam NIPC   = 1; + + +  //--------------------------------------------------------------------------- +  // NoC Shell +  //--------------------------------------------------------------------------- + +  wire        ctrlport_reg_req_wr; +  wire        ctrlport_reg_req_rd; +  wire [19:0] ctrlport_reg_req_addr; +  wire [31:0] ctrlport_reg_req_data; +  wire        ctrlport_reg_resp_ack; +  wire [ 1:0] ctrlport_reg_resp_status; +  wire [31:0] ctrlport_reg_resp_data; + +  wire [(NUM_PORTS*ITEM_W*NIPC)-1:0] axis_to_fir_tdata; +  wire [              NUM_PORTS-1:0] axis_to_fir_tlast; +  wire [              NUM_PORTS-1:0] axis_to_fir_tvalid; +  wire [              NUM_PORTS-1:0] axis_to_fir_tready; + +  wire [(NUM_PORTS*ITEM_W*NIPC)-1:0] axis_from_fir_tdata; +  wire [              NUM_PORTS-1:0] axis_from_fir_tlast; +  wire [              NUM_PORTS-1:0] axis_from_fir_tvalid; +  wire [              NUM_PORTS-1:0] axis_from_fir_tready; + +  wire [(NUM_PORTS*CHDR_W)-1:0] m_axis_context_tdata; +  wire [     (4*NUM_PORTS)-1:0] m_axis_context_tuser; +  wire [         NUM_PORTS-1:0] m_axis_context_tlast; +  wire [         NUM_PORTS-1:0] m_axis_context_tvalid; +  wire [         NUM_PORTS-1:0] m_axis_context_tready; + +  wire [(NUM_PORTS*CHDR_W)-1:0] s_axis_context_tdata; +  wire [     (4*NUM_PORTS)-1:0] s_axis_context_tuser; +  wire [         NUM_PORTS-1:0] s_axis_context_tlast; +  wire [         NUM_PORTS-1:0] s_axis_context_tvalid; +  wire [         NUM_PORTS-1:0] s_axis_context_tready; + +  wire rfnoc_chdr_rst; +  wire ce_rst; + +  localparam NOC_ID = 32'hF112_0000; + + +  // Cross the CHDR reset to the ddc_clk domain +  synchronizer ce_rst_sync_i ( +    .clk (ce_clk), +    .rst (1'b0), +    .in  (rfnoc_chdr_rst), +    .out (ce_rst) +  ); +   + +  noc_shell_fir_filter #( +    .NOC_ID          (NOC_ID), +    .THIS_PORTID     (THIS_PORTID), +    .CHDR_W          (CHDR_W), +    .CTRLPORT_SLV_EN (0), +    .CTRLPORT_MST_EN (1), +    .NUM_DATA_I      (NUM_PORTS), +    .NUM_DATA_O      (NUM_PORTS), +    .ITEM_W          (ITEM_W), +    .NIPC            (NIPC), +    .PYLD_FIFO_SIZE  (5), +    .CTXT_FIFO_SIZE  (5), +    .MTU             (MTU) +  ) noc_shell_fir_filter_i ( +    .rfnoc_chdr_clk            (rfnoc_chdr_clk), +    .rfnoc_chdr_rst            (rfnoc_chdr_rst), +    .rfnoc_ctrl_clk            (rfnoc_ctrl_clk), +    .rfnoc_ctrl_rst            (), +    .rfnoc_core_config         (rfnoc_core_config), +    .rfnoc_core_status         (rfnoc_core_status), +    .s_rfnoc_chdr_tdata        (s_rfnoc_chdr_tdata), +    .s_rfnoc_chdr_tlast        (s_rfnoc_chdr_tlast), +    .s_rfnoc_chdr_tvalid       (s_rfnoc_chdr_tvalid), +    .s_rfnoc_chdr_tready       (s_rfnoc_chdr_tready), +    .m_rfnoc_chdr_tdata        (m_rfnoc_chdr_tdata), +    .m_rfnoc_chdr_tlast        (m_rfnoc_chdr_tlast), +    .m_rfnoc_chdr_tvalid       (m_rfnoc_chdr_tvalid), +    .m_rfnoc_chdr_tready       (m_rfnoc_chdr_tready), +    .s_rfnoc_ctrl_tdata        (s_rfnoc_ctrl_tdata), +    .s_rfnoc_ctrl_tlast        (s_rfnoc_ctrl_tlast), +    .s_rfnoc_ctrl_tvalid       (s_rfnoc_ctrl_tvalid), +    .s_rfnoc_ctrl_tready       (s_rfnoc_ctrl_tready), +    .m_rfnoc_ctrl_tdata        (m_rfnoc_ctrl_tdata), +    .m_rfnoc_ctrl_tlast        (m_rfnoc_ctrl_tlast), +    .m_rfnoc_ctrl_tvalid       (m_rfnoc_ctrl_tvalid), +    .m_rfnoc_ctrl_tready       (m_rfnoc_ctrl_tready), +    .ctrlport_clk              (ce_clk), +    .ctrlport_rst              (ce_rst), +    .m_ctrlport_req_wr         (ctrlport_reg_req_wr), +    .m_ctrlport_req_rd         (ctrlport_reg_req_rd), +    .m_ctrlport_req_addr       (ctrlport_reg_req_addr), +    .m_ctrlport_req_data       (ctrlport_reg_req_data), +    .m_ctrlport_req_byte_en    (), +    .m_ctrlport_req_has_time   (), +    .m_ctrlport_req_time       (), +    .m_ctrlport_resp_ack       (ctrlport_reg_resp_ack), +    .m_ctrlport_resp_status    (ctrlport_reg_resp_status), +    .m_ctrlport_resp_data      (ctrlport_reg_resp_data), +    .s_ctrlport_req_wr         (1'b0), +    .s_ctrlport_req_rd         (1'b0), +    .s_ctrlport_req_addr       (20'b0), +    .s_ctrlport_req_portid     (10'b0), +    .s_ctrlport_req_rem_epid   (16'b0), +    .s_ctrlport_req_rem_portid (10'b0), +    .s_ctrlport_req_data       (32'b0), +    .s_ctrlport_req_byte_en    (4'hF), +    .s_ctrlport_req_has_time   (1'b0), +    .s_ctrlport_req_time       (64'b0), +    .s_ctrlport_resp_ack       (), +    .s_ctrlport_resp_status    (), +    .s_ctrlport_resp_data      (), +    .axis_data_clk             (ce_clk), +    .axis_data_rst             (ce_rst), +    .m_axis_payload_tdata      (axis_to_fir_tdata), +    .m_axis_payload_tkeep      (), +    .m_axis_payload_tlast      (axis_to_fir_tlast), +    .m_axis_payload_tvalid     (axis_to_fir_tvalid), +    .m_axis_payload_tready     (axis_to_fir_tready), +    .s_axis_payload_tdata      (axis_from_fir_tdata), +    .s_axis_payload_tkeep      ({NUM_PORTS*NIPC{1'b1}}), +    .s_axis_payload_tlast      (axis_from_fir_tlast), +    .s_axis_payload_tvalid     (axis_from_fir_tvalid), +    .s_axis_payload_tready     (axis_from_fir_tready), +    .m_axis_context_tdata      (m_axis_context_tdata), +    .m_axis_context_tuser      (m_axis_context_tuser), +    .m_axis_context_tlast      (m_axis_context_tlast), +    .m_axis_context_tvalid     (m_axis_context_tvalid), +    .m_axis_context_tready     (m_axis_context_tready), +    .s_axis_context_tdata      (s_axis_context_tdata), +    .s_axis_context_tuser      (s_axis_context_tuser), +    .s_axis_context_tlast      (s_axis_context_tlast), +    .s_axis_context_tvalid     (s_axis_context_tvalid), +    .s_axis_context_tready     (s_axis_context_tready) +  ); + + +  // The input packets are the same configuration as the output packets, so +  // just use the header information for each incoming to create the header for +  // each outgoing packet. This is done by connecting m_axis_context to +  // directly to s_axis_context. +  assign s_axis_context_tdata  = m_axis_context_tdata; +  assign s_axis_context_tuser  = m_axis_context_tuser; +  assign s_axis_context_tlast  = m_axis_context_tlast; +  assign s_axis_context_tvalid = m_axis_context_tvalid; +  assign m_axis_context_tready = s_axis_context_tready; + + +  //--------------------------------------------------------------------------- +  // Control Port Address Decoding +  //--------------------------------------------------------------------------- + +  wire [   NUM_PORTS-1:0] m_ctrlport_req_wr; +  wire [   NUM_PORTS-1:0] m_ctrlport_req_rd; +  wire [20*NUM_PORTS-1:0] m_ctrlport_req_addr; +  wire [32*NUM_PORTS-1:0] m_ctrlport_req_data; +  wire [   NUM_PORTS-1:0] m_ctrlport_resp_ack; +  wire [32*NUM_PORTS-1:0] m_ctrlport_resp_data; + +  ctrlport_decoder #( +    .NUM_SLAVES   (NUM_PORTS), +    .BASE_ADDR    (0), +    .SLAVE_ADDR_W (FIR_FILTER_ADDR_W) +  ) ctrlport_deocder_i ( +    .ctrlport_clk            (ce_clk), +    .ctrlport_rst            (ce_rst), +    .s_ctrlport_req_wr       (ctrlport_reg_req_wr), +    .s_ctrlport_req_rd       (ctrlport_reg_req_rd), +    .s_ctrlport_req_addr     (ctrlport_reg_req_addr), +    .s_ctrlport_req_data     (ctrlport_reg_req_data), +    .s_ctrlport_req_byte_en  (4'b0), +    .s_ctrlport_req_has_time (1'b0), +    .s_ctrlport_req_time     (64'b0), +    .s_ctrlport_resp_ack     (ctrlport_reg_resp_ack), +    .s_ctrlport_resp_status  (ctrlport_reg_resp_status), +    .s_ctrlport_resp_data    (ctrlport_reg_resp_data), +    .m_ctrlport_req_wr       (m_ctrlport_req_wr), +    .m_ctrlport_req_rd       (m_ctrlport_req_rd), +    .m_ctrlport_req_addr     (m_ctrlport_req_addr), +    .m_ctrlport_req_data     (m_ctrlport_req_data), +    .m_ctrlport_req_byte_en  (), +    .m_ctrlport_req_has_time (), +    .m_ctrlport_req_time     (), +    .m_ctrlport_resp_ack     (m_ctrlport_resp_ack), +    .m_ctrlport_resp_status  ({NUM_PORTS{2'b0}}), +    .m_ctrlport_resp_data    (m_ctrlport_resp_data) +  ); + + +  //--------------------------------------------------------------------------- +  // FIR Core Instances +  //--------------------------------------------------------------------------- + +  genvar i; +  for (i = 0; i < NUM_PORTS; i = i+1) begin : gen_rfnoc_fir_filter_cores +    rfnoc_fir_filter_core #( +      .DATA_W                   (ITEM_W*NIPC), +      .COEFF_WIDTH              (COEFF_WIDTH), +      .NUM_COEFFS               (NUM_COEFFS), +      .COEFFS_VEC               (COEFFS_VEC), +      .RELOADABLE_COEFFS        (RELOADABLE_COEFFS), +      .SYMMETRIC_COEFFS         (SYMMETRIC_COEFFS), +      .SKIP_ZERO_COEFFS         (SKIP_ZERO_COEFFS), +      .USE_EMBEDDED_REGS_COEFFS (USE_EMBEDDED_REGS_COEFFS) +    ) rfnoc_fir_filter_core_i ( +      .clk                  (ce_clk), +      .rst                  (ce_rst), +      .s_ctrlport_req_wr    (m_ctrlport_req_wr[i]), +      .s_ctrlport_req_rd    (m_ctrlport_req_rd[i]), +      .s_ctrlport_req_addr  (m_ctrlport_req_addr[20*i +: 20]), +      .s_ctrlport_req_data  (m_ctrlport_req_data[32*i +: 32]), +      .s_ctrlport_resp_ack  (m_ctrlport_resp_ack[i]), +      .s_ctrlport_resp_data (m_ctrlport_resp_data[32*i +: 32]), +      .s_axis_tdata         (axis_to_fir_tdata[i*(ITEM_W*NIPC) +: (ITEM_W*NIPC)]), +      .s_axis_tlast         (axis_to_fir_tlast[i]), +      .s_axis_tvalid        (axis_to_fir_tvalid[i]), +      .s_axis_tready        (axis_to_fir_tready[i]), +      .m_axis_tdata         (axis_from_fir_tdata[i*(ITEM_W*NIPC) +: (ITEM_W*NIPC)]), +      .m_axis_tlast         (axis_from_fir_tlast[i]), +      .m_axis_tvalid        (axis_from_fir_tvalid[i]), +      .m_axis_tready        (axis_from_fir_tready[i]) +    ); +  end + +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/rfnoc_block_fir_filter_tb.sv b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/rfnoc_block_fir_filter_tb.sv new file mode 100644 index 000000000..28b5493ac --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/rfnoc_block_fir_filter_tb.sv @@ -0,0 +1,524 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_fir_filter_tb +// +// Description: Testbench for rfnoc_block_fir_filter +// + + +module rfnoc_block_fir_filter_tb #( +  parameter int NUM_PORTS = 2 +); + +  // Include macros and time declarations for use with PkgTestExec +  `include "test_exec.svh" + +  import PkgTestExec::*; +  import PkgChdrUtils::*; +  import PkgRfnocBlockCtrlBfm::*; + +  `include "rfnoc_fir_filter_regs.vh" + + +  //--------------------------------------------------------------------------- +  // Local Parameters +  //--------------------------------------------------------------------------- + +  // Simulation parameters +  localparam real CHDR_CLK_PER = 6.0; // 166 MHz +  localparam real CE_CLK_PER   = 5.0; // 200 MHz +  localparam int  STALL_PROB   = 25;  // BFM stall probability + +  // DUT parameters to test +  localparam int CHDR_W      = 64; +  localparam int THIS_PORTID = 'h123; +  localparam int MTU         = 8; +  // +  localparam int NUM_COEFFS               = 41; +  localparam int COEFF_WIDTH              = 16; +  localparam int RELOADABLE_COEFFS        = 1; +  localparam int SYMMETRIC_COEFFS         = 1; +  localparam int SKIP_ZERO_COEFFS         = 1; +  localparam int USE_EMBEDDED_REGS_COEFFS = 1; + +  localparam logic [COEFF_WIDTH*NUM_COEFFS-1:0] COEFFS_VEC_0 = { +     16'sd158,   16'sd0,     16'sd33,    -16'sd0,    -16'sd256, +     16'sd553,   16'sd573,   -16'sd542,  -16'sd1012, 16'sd349, +     16'sd1536,  16'sd123,   -16'sd2097, -16'sd1012, 16'sd1633, +     16'sd1608,  -16'sd3077, -16'sd5946, 16'sd3370,  16'sd10513, +     16'sd19295, +     16'sd10513, 16'sd3370,  -16'sd5946, -16'sd3077, 16'sd1608, +     16'sd1633,  -16'sd1012, -16'sd2097, 16'sd123,   16'sd1536, +     16'sd349,   -16'sd1012, -16'sd542,  16'sd573,   16'sd553, +     -16'sd256,  -16'sd0,    16'sd33,    16'sd0,     16'sd158 +  }; + +  localparam logic [COEFF_WIDTH*NUM_COEFFS-1:0] COEFFS_VEC_1 = { +    16'sd32767,  16'sd0,      -16'sd32767, 16'sd0,      16'sd32767, +    -16'sd32767, 16'sd32767,  -16'sd32767, 16'sd32767,  -16'sd32767, +    16'sd32767,  16'sd32767,  16'sd32767,  16'sd32767,  16'sd32767, +    -16'sd32767, -16'sd32767, -16'sd32767, -16'sd32767, -16'sd32767, +    16'sd32767, +    -16'sd32767, -16'sd32767, -16'sd32767, -16'sd32767, -16'sd32767, +    16'sd32767,  16'sd32767,  16'sd32767,  16'sd32767,  16'sd32767, +    -16'sd32767, 16'sd32767,  -16'sd32767, 16'sd32767,  -16'sd32767, +    16'sd32767,  16'sd0,      -16'sd32767, 16'sd0,      16'sd32767 +  }; + +  //--------------------------------------------------------------------------- +  // Clocks +  //--------------------------------------------------------------------------- + +  bit rfnoc_chdr_clk; +  bit rfnoc_ctrl_clk; + +  sim_clock_gen #(CHDR_CLK_PER) rfnoc_chdr_clk_gen (.clk(rfnoc_chdr_clk), .rst()); +  sim_clock_gen #(CHDR_CLK_PER) rfnoc_ctrl_clk_gen (.clk(rfnoc_ctrl_clk), .rst()); +  sim_clock_gen #(CE_CLK_PER)   ce_clk_gen         (.clk(ce_clk),         .rst()); + + +  //--------------------------------------------------------------------------- +  // Bus Functional Models +  //--------------------------------------------------------------------------- + +  RfnocBackendIf        backend            (rfnoc_chdr_clk, rfnoc_ctrl_clk); +  AxiStreamIf #(32)     m_ctrl             (rfnoc_ctrl_clk, 1'b0); +  AxiStreamIf #(32)     s_ctrl             (rfnoc_ctrl_clk, 1'b0); +  AxiStreamIf #(CHDR_W) m_chdr [NUM_PORTS] (rfnoc_chdr_clk, 1'b0); +  AxiStreamIf #(CHDR_W) s_chdr [NUM_PORTS] (rfnoc_chdr_clk, 1'b0); + +  // Bus functional model for a software block controller +  RfnocBlockCtrlBfm #(.CHDR_W(CHDR_W)) blk_ctrl = new(backend, m_ctrl, s_ctrl); + +  // Connect block controller to BFMs +  for (genvar i = 0; i < NUM_PORTS; i++) begin : gen_bfm_connections +    initial begin +      blk_ctrl.connect_master_data_port(i, m_chdr[i]); +      blk_ctrl.connect_slave_data_port(i, s_chdr[i]); +      blk_ctrl.set_master_stall_prob(i, STALL_PROB); +      blk_ctrl.set_slave_stall_prob(i, STALL_PROB); +    end +  end + + +  //--------------------------------------------------------------------------- +  // DUT +  //--------------------------------------------------------------------------- + +  logic [NUM_PORTS*CHDR_W-1:0] s_rfnoc_chdr_tdata; +  logic [       NUM_PORTS-1:0] s_rfnoc_chdr_tlast; +  logic [       NUM_PORTS-1:0] s_rfnoc_chdr_tvalid; +  logic [       NUM_PORTS-1:0] s_rfnoc_chdr_tready; + +  logic [NUM_PORTS*CHDR_W-1:0] m_rfnoc_chdr_tdata; +  logic [       NUM_PORTS-1:0] m_rfnoc_chdr_tlast; +  logic [       NUM_PORTS-1:0] m_rfnoc_chdr_tvalid; +  logic [       NUM_PORTS-1:0] m_rfnoc_chdr_tready; + +  // Map the array of BFMs to a flat vector for the DUT +  genvar i; +  for (i = 0; i < NUM_PORTS; i++) begin : gen_dut_connections +    // Connect BFM master to DUT slave port +    assign s_rfnoc_chdr_tdata[CHDR_W*i+:CHDR_W] = m_chdr[i].tdata; +    assign s_rfnoc_chdr_tlast[i]                = m_chdr[i].tlast; +    assign s_rfnoc_chdr_tvalid[i]               = m_chdr[i].tvalid; +    assign m_chdr[i].tready                     = s_rfnoc_chdr_tready[i]; + +    // Connect BFM slave to DUT master port +    assign s_chdr[i].tdata        = m_rfnoc_chdr_tdata[CHDR_W*i+:CHDR_W]; +    assign s_chdr[i].tlast        = m_rfnoc_chdr_tlast[i]; +    assign s_chdr[i].tvalid       = m_rfnoc_chdr_tvalid[i]; +    assign m_rfnoc_chdr_tready[i] = s_chdr[i].tready; +  end + + +  rfnoc_block_fir_filter #( +    .THIS_PORTID              (THIS_PORTID), +    .CHDR_W                   (CHDR_W), +    .NUM_PORTS                (NUM_PORTS), +    .MTU                      (MTU), +    .COEFF_WIDTH              (COEFF_WIDTH), +    .NUM_COEFFS               (NUM_COEFFS), +    .COEFFS_VEC               (COEFFS_VEC_0), +    .RELOADABLE_COEFFS        (RELOADABLE_COEFFS), +    .SYMMETRIC_COEFFS         (SYMMETRIC_COEFFS), +    .SKIP_ZERO_COEFFS         (SKIP_ZERO_COEFFS), +    .USE_EMBEDDED_REGS_COEFFS (USE_EMBEDDED_REGS_COEFFS) +  ) rfnoc_block_fir_filter_i ( +    .ce_clk              (ce_clk), +    .rfnoc_chdr_clk      (rfnoc_chdr_clk), +    .s_rfnoc_chdr_tdata  (s_rfnoc_chdr_tdata), +    .s_rfnoc_chdr_tlast  (s_rfnoc_chdr_tlast), +    .s_rfnoc_chdr_tvalid (s_rfnoc_chdr_tvalid), +    .s_rfnoc_chdr_tready (s_rfnoc_chdr_tready), +    .m_rfnoc_chdr_tdata  (m_rfnoc_chdr_tdata), +    .m_rfnoc_chdr_tlast  (m_rfnoc_chdr_tlast), +    .m_rfnoc_chdr_tvalid (m_rfnoc_chdr_tvalid), +    .m_rfnoc_chdr_tready (m_rfnoc_chdr_tready), +    .rfnoc_core_config   (backend.cfg), +    .rfnoc_core_status   (backend.sts), +    .rfnoc_ctrl_clk      (backend.ctrl_clk), +    .s_rfnoc_ctrl_tdata  (m_ctrl.tdata), +    .s_rfnoc_ctrl_tlast  (m_ctrl.tlast), +    .s_rfnoc_ctrl_tvalid (m_ctrl.tvalid), +    .s_rfnoc_ctrl_tready (m_ctrl.tready), +    .m_rfnoc_ctrl_tdata  (s_ctrl.tdata), +    .m_rfnoc_ctrl_tlast  (s_ctrl.tlast), +    .m_rfnoc_ctrl_tvalid (s_ctrl.tvalid), +    .m_rfnoc_ctrl_tready (s_ctrl.tready) +  ); + + + +  //--------------------------------------------------------------------------- +  // Helper Tasks +  //--------------------------------------------------------------------------- + +  // Translate the desired register access to a ctrlport write request. +  task automatic write_reg(int port, byte addr, bit [31:0] value); +    blk_ctrl.reg_write(port * (2**FIR_FILTER_ADDR_W) + addr, value); +  endtask : write_reg + +  // Translate the desired register access to a ctrlport read request. +  task automatic read_reg(int port, byte addr, output logic [31:0] value); +    blk_ctrl.reg_read(port * (2**FIR_FILTER_ADDR_W), value); +  endtask : read_reg + + + +  //--------------------------------------------------------------------------- +  // Test Process +  //--------------------------------------------------------------------------- + +  initial begin : tb_main +    // Display testbench start message +    test.start_tb("rfnoc_block_fir_filter_tb"); + +    // Start the BFMs running +    blk_ctrl.run(); + + +    //------------------------------------------------------------------------- +    // Reset +    //------------------------------------------------------------------------- +     +    test.start_test("Wait for Reset", 10us); +    fork +      blk_ctrl.reset_chdr(); +      blk_ctrl.reset_ctrl(); +    join; +    test.end_test(); + + +    //------------------------------------------------------------------------- +    // Check NoC ID and Block Info +    //------------------------------------------------------------------------- +     +    test.start_test("Verify Block Info", 2us); +    `ASSERT_ERROR(blk_ctrl.get_noc_id() == rfnoc_block_fir_filter_i.NOC_ID, "Incorrect NOC_ID Value"); +    `ASSERT_ERROR(blk_ctrl.get_num_data_i() == NUM_PORTS, "Incorrect NUM_DATA_I Value"); +    `ASSERT_ERROR(blk_ctrl.get_num_data_o() == NUM_PORTS, "Incorrect NUM_DATA_O Value"); +    `ASSERT_ERROR(blk_ctrl.get_mtu() == MTU, "Incorrect MTU Value"); +    test.end_test(); + + +    // Test all ports +    for (int port = 0; port < NUM_PORTS; port++) begin : port_loop + +      //----------------------------------------------------------------------- +      // Check filter length +      //----------------------------------------------------------------------- + +      begin +        int num_coeffs, num_coeffs_to_send; + +        test.start_test("Check filter length", 20us); + +        read_reg(port, REG_FIR_NUM_COEFFS, num_coeffs); +        `ASSERT_ERROR(num_coeffs, "Incorrect number of coefficients"); + +        // If using symmetric coefficients, send just first half +        if (SYMMETRIC_COEFFS) begin +          num_coeffs_to_send = num_coeffs/2 + num_coeffs[0]; +        end else begin +          num_coeffs_to_send = num_coeffs; +        end + +        // If using embedded register, coefficients must be preloaded +        if (USE_EMBEDDED_REGS_COEFFS) begin +          int i; +          for (i = 0; i < num_coeffs_to_send-1; i++) begin +            write_reg(port, REG_FIR_LOAD_COEFF, COEFFS_VEC_0[COEFF_WIDTH*i +: COEFF_WIDTH]); +          end +          write_reg(port, REG_FIR_LOAD_COEFF_LAST, COEFFS_VEC_0[COEFF_WIDTH*i +: COEFF_WIDTH]); +        end +         +        test.end_test(); +      end + + +      //----------------------------------------------------------------------- +      // Test impulse response with default coefficients +      //----------------------------------------------------------------------- +      // +      // Sending an impulse should cause the coefficients to be output. +      // +      //----------------------------------------------------------------------- + +      begin +        chdr_word_t send_payload[$]; +        chdr_word_t recv_payload[$]; +        int num_bytes; +        logic signed [15:0] i_samp, q_samp, i_coeff, q_coeff; +        string s; + +        test.start_test("Test impulse response (default coefficients)", 20us); + +        // Generate packet containing an impulse and enqueue it for transfer +        send_payload = {}; +        send_payload.push_back({16'b0, 16'b0, 16'h7FFF, 16'h7FFF}); +        for (int i = 0; i < NUM_COEFFS/2; i++) begin +          send_payload.push_back(0); +        end +        blk_ctrl.send(port, send_payload, NUM_COEFFS*4); + +        // Enqueue two packets with zeros to push out the impulse from the +        // pipeline (one to push out the data and one to overcome some pipeline +        // registering). +        send_payload = {}; +        for (int i = 0; i < NUM_COEFFS/2+1; i++) begin +          send_payload.push_back(0); +        end +        for (int n = 0; n < 2; n++) begin +          blk_ctrl.send(port, send_payload, NUM_COEFFS*4); +        end + +        // Receive the result +        blk_ctrl.recv(port, recv_payload, num_bytes); + +        // Check the length of the packet +        `ASSERT_ERROR( +          num_bytes == NUM_COEFFS*4, +          "Received packet didn't have expected length" +        ); + +        for (int i = 0; i < NUM_COEFFS; i++) begin +          // Compute the expected sample +          i_coeff = $signed(COEFFS_VEC_0[COEFF_WIDTH*i +: COEFF_WIDTH]); +          q_coeff = i_coeff; + +          // Grab the next sample +          {i_samp, q_samp} = recv_payload[i/2][i[0]*32 +: 32]; + +          // Check I / Q values +          $sformat( +            s, "Incorrect I value received on sample %0d! Expected: %0d, Received: %0d",  +            i, i_coeff, i_samp); +          `ASSERT_ERROR( +            (i_samp == i_coeff) || (i_samp-1 == i_coeff) || (i_samp+1 == i_coeff), s); +          $sformat( +            s, "Incorrect Q value received on sample %0d! Expected: %0d, Received: %0d",  +            i, q_coeff, q_samp); +          `ASSERT_ERROR( +            (q_samp == q_coeff) || (q_samp-1 == q_coeff) || (q_samp+1 == q_coeff), s); +        end + +        test.end_test(); +      end + + +      //----------------------------------------------------------------------- +      // Load new coefficients +      //----------------------------------------------------------------------- + +      begin +        int i; +        int num_coeffs_to_send; + +        // If using symmetric coefficients, send just first half +        if (SYMMETRIC_COEFFS) begin +          num_coeffs_to_send = NUM_COEFFS/2 + NUM_COEFFS[0]; +        end else begin +          num_coeffs_to_send = NUM_COEFFS; +        end + +        test.start_test("Load new coefficients", 20us); +        for (i = 0; i < num_coeffs_to_send-1; i++) begin +          write_reg(port, REG_FIR_LOAD_COEFF, COEFFS_VEC_1[COEFF_WIDTH*i +: COEFF_WIDTH]); +        end +        write_reg(port, REG_FIR_LOAD_COEFF_LAST, COEFFS_VEC_1[COEFF_WIDTH*i +: COEFF_WIDTH]); +        test.end_test();     +      end + + +      //----------------------------------------------------------------------- +      // Test impulse response with new coefficients +      //----------------------------------------------------------------------- +      // +      // Sending an impulse should cause the coefficients to be output. +      // +      //----------------------------------------------------------------------- + +      begin +        chdr_word_t send_payload[$]; +        chdr_word_t recv_payload[$]; +        int num_bytes; +        logic signed [15:0] i_samp, q_samp, i_coeff, q_coeff; +        string s; + +        test.start_test("Test impulse response (loaded coefficients)", 20us); + +        // Generate packet containing an impulse and enqueue it for transfer +        send_payload = {}; +        send_payload.push_back({16'b0, 16'b0, 16'h7FFF, 16'h7FFF}); +        for (int i = 0; i < NUM_COEFFS/2; i++) begin +          send_payload.push_back(0); +        end +        blk_ctrl.send(port, send_payload, NUM_COEFFS*4); + +        // Enqueue two packets with zeros to push out the impulse from the +        // pipeline (one to push out the data and one to overcome some pipeline +        // registering). +        send_payload = {}; +        for (int i = 0; i < NUM_COEFFS/2+1; i++) begin +          send_payload.push_back(0); +        end +        for (int n = 0; n < 2; n++) begin +          blk_ctrl.send(port, send_payload, NUM_COEFFS*4); +        end + +        // Ignore the first two packets (discard the extra data we put in when +        // we checked the default coefficients). +        blk_ctrl.recv(port, recv_payload, num_bytes); +        blk_ctrl.recv(port, recv_payload, num_bytes); + +        // Receive the result +        blk_ctrl.recv(port, recv_payload, num_bytes); + +        // Check the length of the packet +        `ASSERT_ERROR( +          num_bytes == NUM_COEFFS*4, +          "Received packet didn't have expected length" +        ); + +        for (int i = 0; i < NUM_COEFFS; i++) begin +          // Compute the expected sample +          i_coeff = $signed(COEFFS_VEC_1[COEFF_WIDTH*i +: COEFF_WIDTH]); +          q_coeff = i_coeff; + +          // Grab the next sample +          {i_samp, q_samp} = recv_payload[i/2][i[0]*32 +: 32]; + +          // Check I / Q values +          $sformat( +            s, "Incorrect I value received on sample %0d! Expected: %0d, Received: %0d",  +            i, i_coeff, i_samp); +          `ASSERT_ERROR( +            (i_samp == i_coeff) || (i_samp-1 == i_coeff) || (i_samp+1 == i_coeff), s); +          $sformat( +            s, "Incorrect Q value received on sample %0d! Expected: %0d, Received: %0d",  +            i, q_coeff, q_samp); +          `ASSERT_ERROR( +            (q_samp == q_coeff) || (q_samp-1 == q_coeff) || (q_samp+1 == q_coeff), s); +        end + +        test.end_test(); +      end + + +      //----------------------------------------------------------------------- +      // Test step response +      //----------------------------------------------------------------------- + +      begin +        chdr_word_t send_payload[$]; +        chdr_word_t recv_payload[$]; +        int num_bytes; +        int coeff_sum; +        logic signed [15:0] i_samp, q_samp; +        string s; + +        test.start_test("Test step response", 20us); + +        // Generate a step function packet +        send_payload = {}; +        for (int i = 0; i < NUM_COEFFS/2+1; i++) begin +          send_payload.push_back({16'h7FFF,16'h7FFF,16'h7FFF,16'h7FFF}); +        end + +        // Enqueue step function two times, once to fill up the pipeline and +        // another to get the actual response. +        for (int n = 0; n < 2; n++) begin +          blk_ctrl.send(port, send_payload, NUM_COEFFS*4); +        end + +        // Enqueue two packets with zeros to push out the impulse from the +        // pipeline (one to push out the data and one to overcome some pipeline +        // registering). +        send_payload = {}; +        for (int i = 0; i < NUM_COEFFS/2+1; i++) begin +          send_payload.push_back(0); +        end +        for (int n = 0; n < 2; n++) begin +          blk_ctrl.send(port, send_payload, NUM_COEFFS*4); +        end + +        // Ignore the first two packets (discard the extra data we put in +        // during the previous test). +        for (int n = 0; n < 3; n++) begin +          blk_ctrl.recv(port, recv_payload, num_bytes); +        end + +        // Receive the result +        blk_ctrl.recv(port, recv_payload, num_bytes); + +        // Check the length of the packet +        `ASSERT_ERROR( +          num_bytes == NUM_COEFFS*4, +          "Received packet didn't have expected length" +        ); + +        // Calculate sum of all the coefficients +        coeff_sum = 0; +        for (int i = 0; i < NUM_COEFFS; i++) begin +            coeff_sum += $signed(COEFFS_VEC_1[COEFF_WIDTH*i +: COEFF_WIDTH]); +        end + +        for (int i = 0; i < NUM_COEFFS; i++) begin +          // Grab the next sample +          {i_samp, q_samp} = recv_payload[i/2][i[0]*32 +: 32]; + +          // Check I / Q values +          $sformat( +            s, "Incorrect I value received on sample %0d! Expected: %0d, Received: %0d",  +            i, coeff_sum, i_samp); +          `ASSERT_ERROR( +            (i_samp == coeff_sum) || (i_samp-1 == coeff_sum) || (i_samp+1 == coeff_sum), +            s +          ); +          $sformat( +            s, "Incorrect Q value received on sample %0d! Expected: %0d, Received: %0d",  +            i, coeff_sum, q_samp); +          `ASSERT_ERROR( +            (q_samp == coeff_sum) || (q_samp-1 == coeff_sum) || (q_samp+1 == coeff_sum), +            s +          ); +        end + +        test.end_test(); +      end + +    end : port_loop + + +    //------------------------------------------------------------------------- +    // All done! +    //------------------------------------------------------------------------- + +    test.end_tb(1); + +  end : tb_main +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/rfnoc_fir_filter_core.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/rfnoc_fir_filter_core.v new file mode 100644 index 000000000..774f43761 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/rfnoc_fir_filter_core.v @@ -0,0 +1,228 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Description: +// +//   Core module for a single instance of an FIR filter, implementing the +//   registers and signal processing for a single I/Q filter. It assumes the +//   data stream is an IQ pair with I in the upper 32 bits and Q is the lower +//   32 bits. +// +// Parameters: +// +//   DATA_W                   : Width of the input/output data stream to +//                              process. +// +//   BASE_ADDR                : Control port base address to which this block +//                              responds. +//  +//   COEFF_WIDTH              : Coefficient width +// +//   NUM_COEFFS               : Number of coefficients / filter taps +// +//   COEFFS_VEC               : Vector of NUM_COEFFS values, each of width +//                              COEFF_WIDTH, to initialize the filter +//                              coefficients. Defaults to an impulse. +// +//   RELOADABLE_COEFFS        : Enable (1) or disable (0) reloading +//                              coefficients at runtime +// +//   SYMMETRIC_COEFFS         : Reduce multiplier usage by approximately half +//                              if coefficients are symmetric +// +//   SKIP_ZERO_COEFFS         : Reduce multiplier usage by assuming zero valued +//                              coefficients in DEFAULT_COEFFS are always zero. +//                              Useful for halfband filters. +// +//   USE_EMBEDDED_REGS_COEFFS : Reduce register usage by only using embedded +//                              registers in DSP slices. Updating taps while +//                              streaming will cause temporary output +//                              corruption! +// +//   Note: If using USE_EMBEDDED_REGS_COEFFS, coefficients must be written at  +//   least once since COEFFS_VEC is ignored! + + +module rfnoc_fir_filter_core #( +  parameter        DATA_W            = 32, +  parameter [19:0] BASE_ADDR         = 0, + +  // FIR Filter Parameters +  parameter COEFF_WIDTH              = 16, +  parameter NUM_COEFFS               = 41, +  parameter [NUM_COEFFS*COEFF_WIDTH-1:0] COEFFS_VEC = // Make impulse by default +    {  +      {1'b0, {(COEFF_WIDTH-1){1'b1}} },        // Max positive value +      {(COEFF_WIDTH*(NUM_COEFFS-1)){1'b0}}     // Zero for remaining coefficients +    }, +  parameter RELOADABLE_COEFFS        = 1, +  parameter SYMMETRIC_COEFFS         = 0, +  parameter SKIP_ZERO_COEFFS         = 0, +  parameter USE_EMBEDDED_REGS_COEFFS = 1 +) ( + +  input  wire clk, +  input  wire rst, + +  //--------------------------------------------------------------------------- +  // AXIS CTRL Port +  //--------------------------------------------------------------------------- + +  // Master +  input  wire        s_ctrlport_req_wr, +  input  wire        s_ctrlport_req_rd, +  input  wire [19:0] s_ctrlport_req_addr, +  input  wire [31:0] s_ctrlport_req_data, +  output reg         s_ctrlport_resp_ack, +  output reg  [31:0] s_ctrlport_resp_data, + +  //--------------------------------------------------------------------------- +  // Data Interface +  //--------------------------------------------------------------------------- + +  // Input data stream +  input  wire [DATA_W-1:0] s_axis_tdata, +  input  wire              s_axis_tlast, +  input  wire              s_axis_tvalid, +  output wire              s_axis_tready, + +  // Output data stream +  output wire [DATA_W-1:0] m_axis_tdata, +  output wire              m_axis_tlast, +  output wire              m_axis_tvalid, +  input  wire              m_axis_tready +); + +  reg  [COEFF_WIDTH-1:0] m_axis_reload_tdata; +  reg                    m_axis_reload_tvalid; +  reg                    m_axis_reload_tlast; +  wire                   m_axis_reload_tready; + + +  //--------------------------------------------------------------------------- +  // Registers +  //--------------------------------------------------------------------------- + +  `include "rfnoc_fir_filter_regs.vh" + +  // Separate the address into the block and register portions. Ignore the byte +  // offset. +  wire [20:0] block_addr = s_ctrlport_req_addr[19:FIR_FILTER_ADDR_W]; +  wire [19:0] reg_addr   = { s_ctrlport_req_addr[FIR_FILTER_ADDR_W:2], 2'b0 }; + +  always @(posedge clk) begin +    if (rst) begin +      s_ctrlport_resp_ack  <= 0; +      m_axis_reload_tvalid <= 0; +      s_ctrlport_resp_data <= {32{1'bX}}; +      m_axis_reload_tdata  <= {DATA_W{1'bX}}; +      m_axis_reload_tlast  <= 1'bX; +    end else if (block_addr == BASE_ADDR) begin +      // Default assignments +      s_ctrlport_resp_ack  <= 0; +      s_ctrlport_resp_data <= 0; + +      // Handle write acknowledgments. Don't ack the register write until it +      // gets accepted by the FIR filter. +      if (m_axis_reload_tvalid && m_axis_reload_tready) begin +        m_axis_reload_tvalid <= 1'b0; +        s_ctrlport_resp_ack  <= 1'b1; +      end + +      // Handle register writes +      if (s_ctrlport_req_wr) begin +        if (reg_addr == REG_FIR_LOAD_COEFF) begin +          m_axis_reload_tdata  <= s_ctrlport_req_data[COEFF_WIDTH-1:0]; +          m_axis_reload_tvalid <= 1'b1; +          m_axis_reload_tlast  <= 1'b0; +        end else if (reg_addr == REG_FIR_LOAD_COEFF_LAST) begin +          m_axis_reload_tdata  <= s_ctrlport_req_data[COEFF_WIDTH-1:0]; +          m_axis_reload_tvalid <= 1'b1; +          m_axis_reload_tlast  <= 1'b1; +        end +      end + +      // Handle register reads +      if (s_ctrlport_req_rd) begin +        // Ignore the upper bits so the we respond to any port +        if (reg_addr == REG_FIR_NUM_COEFFS) begin +          s_ctrlport_resp_data <= NUM_COEFFS; +          s_ctrlport_resp_ack  <= 1; +        end +      end +    end +  end + + +  //--------------------------------------------------------------------------- +  // FIR Filter Instances +  //--------------------------------------------------------------------------- + +  localparam IN_WIDTH  = DATA_W/2; +  localparam OUT_WIDTH = DATA_W/2; + +  // I +  axi_fir_filter #( +    .IN_WIDTH                 (IN_WIDTH), +    .COEFF_WIDTH              (COEFF_WIDTH), +    .OUT_WIDTH                (OUT_WIDTH), +    .NUM_COEFFS               (NUM_COEFFS), +    .COEFFS_VEC               (COEFFS_VEC), +    .RELOADABLE_COEFFS        (RELOADABLE_COEFFS), +    .BLANK_OUTPUT             (1), +    // Optional optimizations +    .SYMMETRIC_COEFFS         (SYMMETRIC_COEFFS), +    .SKIP_ZERO_COEFFS         (SKIP_ZERO_COEFFS), +    .USE_EMBEDDED_REGS_COEFFS (USE_EMBEDDED_REGS_COEFFS) +  ) inst_axi_fir_filter_i ( +    .clk                  (clk), +    .reset                (rst), +    .clear                (1'b0), +    .s_axis_data_tdata    (s_axis_tdata[2*IN_WIDTH-1:IN_WIDTH]), +    .s_axis_data_tlast    (s_axis_tlast), +    .s_axis_data_tvalid   (s_axis_tvalid), +    .s_axis_data_tready   (s_axis_tready), +    .m_axis_data_tdata    (m_axis_tdata[2*OUT_WIDTH-1:OUT_WIDTH]), +    .m_axis_data_tlast    (m_axis_tlast), +    .m_axis_data_tvalid   (m_axis_tvalid), +    .m_axis_data_tready   (m_axis_tready), +    .s_axis_reload_tdata  (m_axis_reload_tdata), +    .s_axis_reload_tlast  (m_axis_reload_tlast), +    .s_axis_reload_tvalid (m_axis_reload_tvalid), +    .s_axis_reload_tready (m_axis_reload_tready) +  ); + +  // Q +  axi_fir_filter #( +    .IN_WIDTH                 (IN_WIDTH), +    .COEFF_WIDTH              (COEFF_WIDTH), +    .OUT_WIDTH                (OUT_WIDTH), +    .NUM_COEFFS               (NUM_COEFFS), +    .COEFFS_VEC               (COEFFS_VEC), +    .RELOADABLE_COEFFS        (RELOADABLE_COEFFS), +    .BLANK_OUTPUT             (1), +    // Optional optimizations +    .SYMMETRIC_COEFFS         (SYMMETRIC_COEFFS), +    .SKIP_ZERO_COEFFS         (SKIP_ZERO_COEFFS), +    .USE_EMBEDDED_REGS_COEFFS (USE_EMBEDDED_REGS_COEFFS) +  ) inst_axi_fir_filter_q ( +    .clk                  (clk), +    .reset                (rst), +    .clear                (1'b0), +    .s_axis_data_tdata    (s_axis_tdata[IN_WIDTH-1:0]), +    .s_axis_data_tlast    (s_axis_tlast), +    .s_axis_data_tvalid   (s_axis_tvalid), +    .s_axis_data_tready   (), +    .m_axis_data_tdata    (m_axis_tdata[OUT_WIDTH-1:0]), +    .m_axis_data_tlast    (), +    .m_axis_data_tvalid   (), +    .m_axis_data_tready   (m_axis_tready), +    .s_axis_reload_tdata  (m_axis_reload_tdata), +    .s_axis_reload_tlast  (m_axis_reload_tlast), +    .s_axis_reload_tvalid (m_axis_reload_tvalid), +    .s_axis_reload_tready () +  ); + +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/rfnoc_fir_filter_regs.vh b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/rfnoc_fir_filter_regs.vh new file mode 100644 index 000000000..0e070e3a3 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/rfnoc_fir_filter_regs.vh @@ -0,0 +1,51 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module:  fir_filter_regs (Header) +// +// Description:  Header file for rfnoc_block_fir_filter. All registers are  +//               32-bit words from software's perspective. +// + +// Address space size, per FIR filter. That is, each filter is separated in the  +// CTRL Port address space by 2^FIR_FILTER_ADDR_W bytes. +localparam FIR_FILTER_ADDR_W = 4; + + + +// REG_FIR_NUM_COEFFS (R) +// +// Contains the number of coefficients for the filter. +// +// [31:0] : Returns the number of coefficients (read-only) +// +localparam REG_FIR_NUM_COEFFS = 'h0; + + +// REG_FIR_LOAD_COEFF (R) +// +// Register for inputting the next coefficient to be loaded into the filter. To +// load a new set of filter coefficients, write NUM_COEFFS-1 coefficients to +// this register, then write the last coefficient to REG_FIR_LOAD_COEFF_LAST. +// The width of each coefficient is set by the COEFF_WIDTH parameter on the +// block. +// +// [31:(32-COEFF_WIDTH)] : Reserved +// [COEFF_WIDTH-1:0]     : The next coefficient to be loaded +// +localparam REG_FIR_LOAD_COEFF = 'h4; + + +// REG_FIR_LOAD_COEFF_LAST (R) +// +// Register for inputting the last coefficient to be loaded into the filter. To +// load a new set of filter coefficients, write NUM_COEFFS-1 coefficients to +// REG_FIR_LOAD_COEFF, then write the last coefficient to this register. The +// width of each coefficient is set by the COEFF_WIDTH parameter on the block. +// +// [31:(32-COEFF_WIDTH)] : Reserved +// [COEFF_WIDTH-1:0]     : The next coefficient to be loaded +// +localparam REG_FIR_LOAD_COEFF_LAST = 'h8;
\ No newline at end of file diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_null_src_sink/Makefile b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_null_src_sink/Makefile new file mode 100644 index 000000000..30ce14aec --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_null_src_sink/Makefile @@ -0,0 +1,45 @@ +# +# Copyright 2019 Ettus Research, A National Instruments Company +# +# 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_preamble after defining BASE_DIR +include $(BASE_DIR)/../tools/make/viv_sim_preamble.mak + +#------------------------------------------------- +# Design Specific +#------------------------------------------------- +# Include makefiles and sources for the DUT and its dependencies +include $(BASE_DIR)/../lib/rfnoc/core/Makefile.srcs +include $(BASE_DIR)/../lib/rfnoc/utils/Makefile.srcs +include Makefile.srcs + +DESIGN_SRCS += $(abspath \ +$(RFNOC_CORE_SRCS) \ +$(RFNOC_UTIL_SRCS) \ +$(RFNOC_OOT_SRCS) \ +) + +#------------------------------------------------- +# Testbench Specific +#------------------------------------------------- +SIM_TOP = rfnoc_block_null_src_sink_tb + +SIM_SRCS = \ +$(abspath rfnoc_block_null_src_sink_tb.sv) \ + +# MODELSIM_USER_DO = $(abspath wave.do) + +#------------------------------------------------- +# 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/blocks/rfnoc_block_null_src_sink/Makefile.srcs b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_null_src_sink/Makefile.srcs new file mode 100644 index 000000000..a99bec7db --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_null_src_sink/Makefile.srcs @@ -0,0 +1,12 @@ +# +# Copyright 2019 Ettus Research, A National Instruments Brand +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# + +################################################## +# RFNoC Utility Sources +################################################## +RFNOC_OOT_SRCS += $(abspath $(addprefix $(BASE_DIR)/../lib/rfnoc/blocks/rfnoc_block_null_src_sink/, \ +rfnoc_block_null_src_sink.v \ +)) diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_null_src_sink/rfnoc_block_null_src_sink.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_null_src_sink/rfnoc_block_null_src_sink.v new file mode 100644 index 000000000..f4f4d7651 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_null_src_sink/rfnoc_block_null_src_sink.v @@ -0,0 +1,338 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_null_src_sink +// Description: +// +// Parameters: +// +// Signals: + +module rfnoc_block_null_src_sink #( +  parameter  [9:0] THIS_PORTID = 10'd0, +  parameter        CHDR_W      = 64, +  parameter        NIPC        = 2, +  parameter  [5:0] MTU         = 10 +)( +  // RFNoC Framework Clocks and Resets +  input  wire                   rfnoc_chdr_clk, +  input  wire                   rfnoc_ctrl_clk, +  // RFNoC Backend Interface     +  input  wire [511:0]           rfnoc_core_config, +  output wire [511:0]           rfnoc_core_status, +  // 2 CHDR Input Ports (from framework)         +  input  wire [(CHDR_W*2)-1:0]  s_rfnoc_chdr_tdata, +  input  wire [1:0]             s_rfnoc_chdr_tlast, +  input  wire [1:0]             s_rfnoc_chdr_tvalid, +  output wire [1:0]             s_rfnoc_chdr_tready, +  // 2 CHDR Output Ports (to framework)          +  output wire [(CHDR_W*2)-1:0]  m_rfnoc_chdr_tdata, +  output wire [1:0]             m_rfnoc_chdr_tlast, +  output wire [1:0]             m_rfnoc_chdr_tvalid, +  input  wire [1:0]             m_rfnoc_chdr_tready, +  // AXIS-Ctrl Input Port (from framework) +  input  wire [31:0]            s_rfnoc_ctrl_tdata, +  input  wire                   s_rfnoc_ctrl_tlast, +  input  wire                   s_rfnoc_ctrl_tvalid, +  output wire                   s_rfnoc_ctrl_tready, +  // AXIS-Ctrl Output Port (to framework) +  output wire [31:0]            m_rfnoc_ctrl_tdata, +  output wire                   m_rfnoc_ctrl_tlast, +  output wire                   m_rfnoc_ctrl_tvalid, +  input  wire                   m_rfnoc_ctrl_tready +); + +  `include "../../core/rfnoc_chdr_utils.vh" + +  localparam [19:0] REG_CTRL_STATUS       = 20'h00; +  localparam [19:0] REG_SRC_LINES_PER_PKT = 20'h04; +  localparam [19:0] REG_SRC_BYTES_PER_PKT = 20'h08; +  localparam [19:0] REG_SRC_THROTTLE_CYC  = 20'h0C; +  localparam [19:0] REG_SNK_LINE_CNT_LO   = 20'h10; +  localparam [19:0] REG_SNK_LINE_CNT_HI   = 20'h14; +  localparam [19:0] REG_SNK_PKT_CNT_LO    = 20'h18; +  localparam [19:0] REG_SNK_PKT_CNT_HI    = 20'h1C; +  localparam [19:0] REG_SRC_LINE_CNT_LO   = 20'h20; +  localparam [19:0] REG_SRC_LINE_CNT_HI   = 20'h24; +  localparam [19:0] REG_SRC_PKT_CNT_LO    = 20'h28; +  localparam [19:0] REG_SRC_PKT_CNT_HI    = 20'h2C; +  localparam [19:0] REG_LOOP_LINE_CNT_LO  = 20'h30; +  localparam [19:0] REG_LOOP_LINE_CNT_HI  = 20'h34; +  localparam [19:0] REG_LOOP_PKT_CNT_LO   = 20'h38; +  localparam [19:0] REG_LOOP_PKT_CNT_HI   = 20'h3C; + +  wire                 rfnoc_chdr_rst; +  wire                 rfnoc_ctrl_rst; + +  wire                 ctrlport_req_wr; +  wire                 ctrlport_req_rd; +  wire [19:0]          ctrlport_req_addr; +  wire [31:0]          ctrlport_req_data; +  reg                  ctrlport_resp_ack; +  reg  [31:0]          ctrlport_resp_data; + +  wire [(32*NIPC)-1:0] src_pyld_tdata , snk_pyld_tdata , loop_pyld_tdata ; +  wire [NIPC-1:0]      src_pyld_tkeep , snk_pyld_tkeep , loop_pyld_tkeep ; +  wire                 src_pyld_tlast , snk_pyld_tlast , loop_pyld_tlast ; +  wire                 src_pyld_tvalid, snk_pyld_tvalid, loop_pyld_tvalid; +  wire                 src_pyld_tready, snk_pyld_tready, loop_pyld_tready; + +  wire [CHDR_W-1:0]    src_ctxt_tdata , snk_ctxt_tdata , loop_ctxt_tdata ; +  wire [3:0]           src_ctxt_tuser , snk_ctxt_tuser , loop_ctxt_tuser ; +  wire                 src_ctxt_tlast , snk_ctxt_tlast , loop_ctxt_tlast ; +  wire                 src_ctxt_tvalid, snk_ctxt_tvalid, loop_ctxt_tvalid; +  wire                 src_ctxt_tready, snk_ctxt_tready, loop_ctxt_tready; + +  // NoC Shell +  // --------------------------- +  noc_shell_generic_ctrlport_pyld_chdr #( +    .NOC_ID                   (32'h0000_0001), +    .THIS_PORTID              (THIS_PORTID), +    .CHDR_W                   (CHDR_W), +    .CTRL_FIFOSIZE            (5), +    .CTRLPORT_SLV_EN          (0), +    .NUM_DATA_I               (2), +    .NUM_DATA_O               (2), +    .ITEM_W                   (32), +    .NIPC                     (NIPC), +    .MTU                      (MTU), +    .CTXT_FIFOSIZE            (1), +    .PYLD_FIFOSIZE            (1) +  ) noc_shell_i ( +    .rfnoc_chdr_clk           (rfnoc_chdr_clk                     ), +    .rfnoc_chdr_rst           (rfnoc_chdr_rst                     ), +    .rfnoc_ctrl_clk           (rfnoc_ctrl_clk                     ), +    .rfnoc_ctrl_rst           (rfnoc_ctrl_rst                     ), +    .rfnoc_core_config        (rfnoc_core_config                  ), +    .rfnoc_core_status        (rfnoc_core_status                  ), +    .s_rfnoc_chdr_tdata       (s_rfnoc_chdr_tdata                 ), +    .s_rfnoc_chdr_tlast       (s_rfnoc_chdr_tlast                 ), +    .s_rfnoc_chdr_tvalid      (s_rfnoc_chdr_tvalid                ), +    .s_rfnoc_chdr_tready      (s_rfnoc_chdr_tready                ), +    .m_rfnoc_chdr_tdata       (m_rfnoc_chdr_tdata                 ), +    .m_rfnoc_chdr_tlast       (m_rfnoc_chdr_tlast                 ), +    .m_rfnoc_chdr_tvalid      (m_rfnoc_chdr_tvalid                ), +    .m_rfnoc_chdr_tready      (m_rfnoc_chdr_tready                ), +    .s_rfnoc_ctrl_tdata       (s_rfnoc_ctrl_tdata                 ), +    .s_rfnoc_ctrl_tlast       (s_rfnoc_ctrl_tlast                 ), +    .s_rfnoc_ctrl_tvalid      (s_rfnoc_ctrl_tvalid                ), +    .s_rfnoc_ctrl_tready      (s_rfnoc_ctrl_tready                ), +    .m_rfnoc_ctrl_tdata       (m_rfnoc_ctrl_tdata                 ), +    .m_rfnoc_ctrl_tlast       (m_rfnoc_ctrl_tlast                 ), +    .m_rfnoc_ctrl_tvalid      (m_rfnoc_ctrl_tvalid                ), +    .m_rfnoc_ctrl_tready      (m_rfnoc_ctrl_tready                ), +    .m_ctrlport_req_wr        (ctrlport_req_wr                    ), +    .m_ctrlport_req_rd        (ctrlport_req_rd                    ), +    .m_ctrlport_req_addr      (ctrlport_req_addr                  ), +    .m_ctrlport_req_data      (ctrlport_req_data                  ), +    .m_ctrlport_req_byte_en   (                                   ), +    .m_ctrlport_req_has_time  (                                   ), +    .m_ctrlport_req_time      (                                   ), +    .m_ctrlport_resp_ack      (ctrlport_resp_ack                  ), +    .m_ctrlport_resp_status   (2'd0                               ), +    .m_ctrlport_resp_data     (ctrlport_resp_data                 ), +    .s_ctrlport_req_wr        ('h0                                ), +    .s_ctrlport_req_rd        ('h0                                ), +    .s_ctrlport_req_addr      ('h0                                ), +    .s_ctrlport_req_portid    ('h0                                ), +    .s_ctrlport_req_rem_epid  ('h0                                ), +    .s_ctrlport_req_rem_portid('h0                                ), +    .s_ctrlport_req_data      ('h0                                ), +    .s_ctrlport_req_byte_en   ('h0                                ), +    .s_ctrlport_req_has_time  ('h0                                ), +    .s_ctrlport_req_time      ('h0                                ), +    .s_ctrlport_resp_ack      (                                   ), +    .s_ctrlport_resp_status   (                                   ), +    .s_ctrlport_resp_data     (                                   ), +    .m_axis_payload_tdata     ({loop_pyld_tdata , snk_pyld_tdata }), +    .m_axis_payload_tkeep     ({loop_pyld_tkeep , snk_pyld_tkeep }), +    .m_axis_payload_tlast     ({loop_pyld_tlast , snk_pyld_tlast }), +    .m_axis_payload_tvalid    ({loop_pyld_tvalid, snk_pyld_tvalid}), +    .m_axis_payload_tready    ({loop_pyld_tready, snk_pyld_tready}), +    .m_axis_context_tdata     ({loop_ctxt_tdata , snk_ctxt_tdata }), +    .m_axis_context_tuser     ({loop_ctxt_tuser , snk_ctxt_tuser }), +    .m_axis_context_tlast     ({loop_ctxt_tlast , snk_ctxt_tlast }), +    .m_axis_context_tvalid    ({loop_ctxt_tvalid, snk_ctxt_tvalid}), +    .m_axis_context_tready    ({loop_ctxt_tready, snk_ctxt_tready}), +    .s_axis_payload_tdata     ({loop_pyld_tdata , src_pyld_tdata }), +    .s_axis_payload_tkeep     ({loop_pyld_tkeep , src_pyld_tkeep }), +    .s_axis_payload_tlast     ({loop_pyld_tlast , src_pyld_tlast }), +    .s_axis_payload_tvalid    ({loop_pyld_tvalid, src_pyld_tvalid}), +    .s_axis_payload_tready    ({loop_pyld_tready, src_pyld_tready}), +    .s_axis_context_tdata     ({loop_ctxt_tdata , src_ctxt_tdata }), +    .s_axis_context_tuser     ({loop_ctxt_tuser , src_ctxt_tuser }), +    .s_axis_context_tlast     ({loop_ctxt_tlast , src_ctxt_tlast }), +    .s_axis_context_tvalid    ({loop_ctxt_tvalid, src_ctxt_tvalid}), +    .s_axis_context_tready    ({loop_ctxt_tready, src_ctxt_tready}) +  ); + +  // Packet Counters +  // --------------------------- +  reg        reg_clear_cnts = 1'b0; +  reg [63:0] snk_line_cnt  = 64'd0, snk_pkt_cnt  = 64'd0; +  reg [63:0] src_line_cnt  = 64'd0, src_pkt_cnt  = 64'd0; +  reg [63:0] loop_line_cnt = 64'd0, loop_pkt_cnt = 64'd0; + +  always @(posedge rfnoc_chdr_clk) begin +    if (rfnoc_chdr_rst | reg_clear_cnts) begin +      snk_line_cnt  <= 64'd0; +      snk_pkt_cnt   <= 64'd0; +      src_line_cnt  <= 64'd0; +      src_pkt_cnt   <= 64'd0; +      loop_line_cnt <= 64'd0; +      loop_pkt_cnt  <= 64'd0; +    end else begin +      if (snk_pyld_tvalid & snk_pyld_tready) begin +        snk_line_cnt <= snk_line_cnt + 1; +        if (snk_pyld_tlast) +          snk_pkt_cnt <= snk_pkt_cnt + 1; +      end +      if (src_pyld_tvalid & src_pyld_tready) begin +        src_line_cnt <= src_line_cnt + 1; +        if (src_pyld_tlast) +          src_pkt_cnt <= src_pkt_cnt + 1; +      end +      if (loop_pyld_tvalid & loop_pyld_tready) begin +        loop_line_cnt <= loop_line_cnt + 1; +        if (loop_pyld_tlast) +          loop_pkt_cnt <= loop_pkt_cnt + 1; +      end +    end +  end + +  // NULL Sink +  // --------------------------- +  assign snk_pyld_tready = 1'b1; +  assign snk_ctxt_tready = 1'b1; + +  // NULL Source +  // --------------------------- +  reg        reg_src_en = 1'b0; +  reg [11:0] reg_src_lpp = 12'd0; +  reg [15:0] reg_src_bpp = 16'd0; +  reg [9:0]  reg_throttle_cyc = 10'd0; + +  localparam [1:0] ST_HDR  = 2'd0; +  localparam [1:0] ST_PYLD = 2'd1; +  localparam [1:0] ST_WAIT = 2'd2; + +  reg  [1:0] state = ST_HDR; +  reg [11:0] lines_left = 12'd0; +  reg  [9:0] throttle_cntr = 10'd0; + +  always @(posedge rfnoc_chdr_clk) begin +    if (rfnoc_chdr_rst) begin +      state <= ST_HDR; +    end else begin +      case (state) +        ST_HDR: begin +          if (src_ctxt_tvalid && src_ctxt_tready) begin +            state <= ST_PYLD; +            lines_left <= reg_src_lpp; +          end +        end +        ST_PYLD: begin +          if (src_pyld_tvalid && src_pyld_tready) begin +            if (src_pyld_tlast) begin +              if (reg_throttle_cyc == 10'd0) begin +                state <= ST_HDR; +              end else begin +                state <= ST_WAIT; +                throttle_cntr <= reg_throttle_cyc; +              end +            end else begin +              lines_left <= lines_left - 12'd1; +            end +          end +        end +        ST_WAIT: begin +          if (throttle_cntr == 10'd0) +            state <= ST_HDR; +          else +            throttle_cntr <= throttle_cntr - 10'd1; +        end +        default: begin +          state <= ST_HDR; +        end +      endcase +    end +  end + +  assign src_pyld_tdata  = {NIPC{{~src_line_cnt[15:0], src_line_cnt[15:0]}}}; +  assign src_pyld_tkeep  = {NIPC{1'b1}}; +  assign src_pyld_tlast  = (lines_left == 12'd0); +  assign src_pyld_tvalid = (state == ST_PYLD); + +  assign src_ctxt_tdata  = chdr_build_header( +    6'd0, 1'b0, 1'b0, CHDR_PKT_TYPE_DATA, CHDR_NO_MDATA, src_pkt_cnt[15:0], reg_src_bpp, 16'd0); +  assign src_ctxt_tuser  = CONTEXT_FIELD_HDR; +  assign src_ctxt_tlast  = 1'b1; +  assign src_ctxt_tvalid = (state == ST_HDR && reg_src_en); + + +  // Register Interface +  // --------------------------- +  always @(posedge rfnoc_chdr_clk) begin +    if (rfnoc_chdr_rst) begin +      ctrlport_resp_ack <= 1'b0; +    end else begin +      // All transactions finish in 1 cycle +      ctrlport_resp_ack <= ctrlport_req_wr | ctrlport_req_rd; +      // Handle register writes +      if (ctrlport_req_wr) begin +        case(ctrlport_req_addr) +          REG_CTRL_STATUS: +            {reg_src_en, reg_clear_cnts} <= ctrlport_req_data[1:0]; +          REG_SRC_LINES_PER_PKT: +            reg_src_lpp <= ctrlport_req_data[11:0]; +          REG_SRC_BYTES_PER_PKT: +            reg_src_bpp <= ctrlport_req_data[15:0]; +          REG_SRC_THROTTLE_CYC: +            reg_throttle_cyc <= ctrlport_req_data[9:0]; +        endcase +      end +      // Handle register reads +      if (ctrlport_req_rd) begin +        case(ctrlport_req_addr) +          REG_CTRL_STATUS: +            ctrlport_resp_data <= {NIPC[7:0], 8'd32, state, 12'h0, reg_src_en, reg_clear_cnts}; +          REG_SRC_LINES_PER_PKT: +            ctrlport_resp_data <= {20'h0, reg_src_lpp}; +          REG_SRC_BYTES_PER_PKT: +            ctrlport_resp_data <= {16'h0, reg_src_bpp}; +          REG_SRC_THROTTLE_CYC: +            ctrlport_resp_data <= {22'h0, reg_throttle_cyc}; +          REG_SNK_LINE_CNT_LO: +            ctrlport_resp_data <= snk_line_cnt[31:0]; +          REG_SNK_LINE_CNT_HI: +            ctrlport_resp_data <= snk_line_cnt[63:32]; +          REG_SNK_PKT_CNT_LO: +            ctrlport_resp_data <= snk_pkt_cnt[31:0]; +          REG_SNK_PKT_CNT_HI: +            ctrlport_resp_data <= snk_pkt_cnt[63:32]; +          REG_SRC_LINE_CNT_LO: +            ctrlport_resp_data <= src_line_cnt[31:0]; +          REG_SRC_LINE_CNT_HI: +            ctrlport_resp_data <= src_line_cnt[63:32]; +          REG_SRC_PKT_CNT_LO: +            ctrlport_resp_data <= src_pkt_cnt[31:0]; +          REG_SRC_PKT_CNT_HI: +            ctrlport_resp_data <= src_pkt_cnt[63:32]; +          REG_LOOP_LINE_CNT_LO: +            ctrlport_resp_data <= loop_line_cnt[31:0]; +          REG_LOOP_LINE_CNT_HI: +            ctrlport_resp_data <= loop_line_cnt[63:32]; +          REG_LOOP_PKT_CNT_LO: +            ctrlport_resp_data <= loop_pkt_cnt[31:0]; +          REG_LOOP_PKT_CNT_HI: +            ctrlport_resp_data <= loop_pkt_cnt[63:32]; +          default: +            ctrlport_resp_data <= 32'h0; +        endcase +      end +    end +  end + +endmodule // rfnoc_block_null_src_sink diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_null_src_sink/rfnoc_block_null_src_sink_tb.sv b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_null_src_sink/rfnoc_block_null_src_sink_tb.sv new file mode 100644 index 000000000..f25e762b3 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_null_src_sink/rfnoc_block_null_src_sink_tb.sv @@ -0,0 +1,268 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_null_src_sink_tb +// + +`default_nettype none + + +module rfnoc_block_null_src_sink_tb; + +  // Include macros and time declarations for use with PkgTestExec +  `include "test_exec.svh" + +  import PkgTestExec::*; +  import PkgChdrUtils::*; +  import PkgRfnocBlockCtrlBfm::*; +  import PkgRfnocItemUtils::*; + +  // Parameters +  localparam [9:0]  THIS_PORTID = 10'h17; +  localparam [15:0] THIS_EPID   = 16'hDEAD; +  localparam int    CHDR_W      = 64; +  localparam int    SPP         = 201; +  localparam int    LPP         = ((SPP+1)/2); +  localparam int    NUM_PKTS    = 50; + +  localparam int    PORT_SRCSNK = 0; +  localparam int    PORT_LOOP   = 1; + +  // Clock and Reset Definition +  bit rfnoc_chdr_clk; +  sim_clock_gen #(2.5) rfnoc_chdr_clk_gen (rfnoc_chdr_clk); // 400 MHz + +  // ---------------------------------------- +  // Instantiate DUT +  // ---------------------------------------- + +  // Connections to DUT as interfaces: +  RfnocBackendIf        backend (rfnoc_chdr_clk, rfnoc_chdr_clk); // Required backend iface +  AxiStreamIf #(32)     m_ctrl  (rfnoc_chdr_clk);                 // Required control iface +  AxiStreamIf #(32)     s_ctrl  (rfnoc_chdr_clk);                 // Required control iface +  AxiStreamIf #(CHDR_W) m0_chdr (rfnoc_chdr_clk);                 // Optional data iface +  AxiStreamIf #(CHDR_W) m1_chdr (rfnoc_chdr_clk);                 // Optional data iface +  AxiStreamIf #(CHDR_W) s0_chdr (rfnoc_chdr_clk);                 // Optional data iface +  AxiStreamIf #(CHDR_W) s1_chdr (rfnoc_chdr_clk);                 // Optional data iface + +  // Bus functional model for a software block controller +  RfnocBlockCtrlBfm #(.CHDR_W(CHDR_W)) blk_ctrl; + +  // DUT +  rfnoc_block_null_src_sink #( +    .THIS_PORTID        (THIS_PORTID), +    .CHDR_W             (CHDR_W), +    .NIPC               (2), +    .MTU                (10) +  ) dut ( +    .rfnoc_chdr_clk     (backend.chdr_clk), +    .rfnoc_ctrl_clk     (backend.ctrl_clk), +    .rfnoc_core_config  (backend.slave.cfg), +    .rfnoc_core_status  (backend.slave.sts), +    .s_rfnoc_chdr_tdata ({m1_chdr.slave.tdata  , m0_chdr.slave.tdata  }), +    .s_rfnoc_chdr_tlast ({m1_chdr.slave.tlast  , m0_chdr.slave.tlast  }), +    .s_rfnoc_chdr_tvalid({m1_chdr.slave.tvalid , m0_chdr.slave.tvalid }), +    .s_rfnoc_chdr_tready({m1_chdr.slave.tready , m0_chdr.slave.tready }), +    .m_rfnoc_chdr_tdata ({s1_chdr.master.tdata , s0_chdr.master.tdata }), +    .m_rfnoc_chdr_tlast ({s1_chdr.master.tlast , s0_chdr.master.tlast }), +    .m_rfnoc_chdr_tvalid({s1_chdr.master.tvalid, s0_chdr.master.tvalid}), +    .m_rfnoc_chdr_tready({s1_chdr.master.tready, s0_chdr.master.tready}), +    .s_rfnoc_ctrl_tdata (m_ctrl.slave.tdata  ), +    .s_rfnoc_ctrl_tlast (m_ctrl.slave.tlast  ), +    .s_rfnoc_ctrl_tvalid(m_ctrl.slave.tvalid ), +    .s_rfnoc_ctrl_tready(m_ctrl.slave.tready ), +    .m_rfnoc_ctrl_tdata (s_ctrl.master.tdata ), +    .m_rfnoc_ctrl_tlast (s_ctrl.master.tlast ), +    .m_rfnoc_ctrl_tvalid(s_ctrl.master.tvalid), +    .m_rfnoc_ctrl_tready(s_ctrl.master.tready) +  ); + +  // ---------------------------------------- +  // Test Process +  // ---------------------------------------- + +  initial begin +    // Shared Variables +    // ---------------------------------------- +    timeout_t    timeout; +    ctrl_word_t  rvalue = 0; + +    // Initialize +    // ---------------------------------------- +    test.start_tb("rfnoc_block_null_src_sink_tb"); + +    // Start the stream endpoint BFM +    blk_ctrl = new(backend, m_ctrl, s_ctrl); +    blk_ctrl.add_master_data_port(m0_chdr); +    blk_ctrl.add_slave_data_port(s0_chdr); +    blk_ctrl.add_master_data_port(m1_chdr); +    blk_ctrl.add_slave_data_port(s1_chdr); +    blk_ctrl.run(); + +    // Startup block (Software initialization) +    // ---------------------------------------- +    test.start_test("Flush block then reset it"); +    begin +      test.start_timeout(timeout, 10us, "Waiting for flush_and_reset"); +      #100;  //Wait for GSR to deassert +      blk_ctrl.flush_and_reset(); +      test.end_timeout(timeout); +    end +    test.end_test(); + +    // Run Tests +    // ---------------------------------------- +    test.start_test("Read Block Info"); +    begin +      test.start_timeout(timeout, 1us, "Waiting for block info response"); +      // Get static block info and validate it +      `ASSERT_ERROR(blk_ctrl.get_noc_id() == 1, "Incorrect noc_id Value"); +      `ASSERT_ERROR(blk_ctrl.get_num_data_i() == 2, "Incorrect num_data_i Value"); +      `ASSERT_ERROR(blk_ctrl.get_num_data_o() == 2, "Incorrect num_data_o Value"); +      `ASSERT_ERROR(blk_ctrl.get_ctrl_fifosize() == 5, "Incorrect ctrl_fifosize Value"); +      `ASSERT_ERROR(blk_ctrl.get_mtu() == 10, "Incorrect mtu Value"); + +      // Read status register and validate it +      blk_ctrl.reg_read(dut.REG_CTRL_STATUS, rvalue); +      `ASSERT_ERROR(rvalue[31:24] == 2, "Incorrect NIPC Value"); +      `ASSERT_ERROR(rvalue[23:16] == 32, "Incorrect ITEM_W Value"); +      test.end_timeout(timeout); +    end +    test.end_test(); + +    test.start_test("Stream Data Through Loopback Port"); +    begin +      // Send and receive packets +      repeat (NUM_PKTS) begin +        chdr_word_t rx_data[$]; +        int rx_bytes; +        automatic ItemDataBuff #(logic[31:0]) tx_dbuff = new, rx_dbuff = new; +        for (int i = 0; i < SPP; i++) +          tx_dbuff.put($urandom()); +        test.start_timeout(timeout, 5us, "Waiting for pkt to loop back"); +        blk_ctrl.send(PORT_LOOP, tx_dbuff.to_chdr_payload(), tx_dbuff.get_bytes()); +        blk_ctrl.recv(PORT_LOOP, rx_data, rx_bytes); +        rx_dbuff.from_chdr_payload(rx_data, rx_bytes); +        `ASSERT_ERROR(rx_dbuff.equal(tx_dbuff), "Data mismatch"); +        test.end_timeout(timeout); +      end + +      // Read item and packet counts on loopback port +      blk_ctrl.reg_read(dut.REG_LOOP_LINE_CNT_LO, rvalue); +      `ASSERT_ERROR(rvalue == (LPP*NUM_PKTS), "Incorrect REG_LOOP_LINE_CNT_LO value"); +      blk_ctrl.reg_read(dut.REG_LOOP_PKT_CNT_LO, rvalue); +      `ASSERT_ERROR(rvalue == NUM_PKTS, "Incorrect REG_LOOP_PKT_CNT_LO value"); + +      // Read item and packet counts on source port +      blk_ctrl.reg_read(dut.REG_SRC_LINE_CNT_LO, rvalue); +      `ASSERT_ERROR(rvalue == 0, "Incorrect REG_SRC_LINE_CNT_LO value"); +      blk_ctrl.reg_read(dut.REG_SRC_PKT_CNT_LO, rvalue); +      `ASSERT_ERROR(rvalue == 0, "Incorrect REG_SRC_PKT_CNT_LO value"); + +      // Read item and packet counts on sink port +      blk_ctrl.reg_read(dut.REG_SNK_LINE_CNT_LO, rvalue); +      `ASSERT_ERROR(rvalue == 0, "Incorrect REG_SNK_LINE_CNT_LO value"); +      blk_ctrl.reg_read(dut.REG_SNK_PKT_CNT_LO, rvalue); +      `ASSERT_ERROR(rvalue == 0, "Incorrect REG_SNK_PKT_CNT_LO value"); +    end +    test.end_test(); + +    test.start_test("Stream Data To Sink Port"); +    begin +      // Send packets +      repeat (NUM_PKTS) begin +        chdr_word_t rx_data[$]; +        int rx_bytes; +        automatic ItemDataBuff #(logic[31:0]) tx_dbuff = new; +        for (int i = 0; i < SPP; i++) +          tx_dbuff.put($urandom()); +        test.start_timeout(timeout, 5us, "Waiting for pkt to loop back"); +        blk_ctrl.send(PORT_SRCSNK, tx_dbuff.to_chdr_payload(), tx_dbuff.get_bytes()); +        test.end_timeout(timeout); +      end +      repeat (NUM_PKTS * SPP * 2) @(posedge rfnoc_chdr_clk); + +      // Read item and packet counts on loopback port +      blk_ctrl.reg_read(dut.REG_LOOP_LINE_CNT_LO, rvalue); +      `ASSERT_ERROR(rvalue == (LPP*NUM_PKTS), "Incorrect REG_LOOP_LINE_CNT_LO value"); +      blk_ctrl.reg_read(dut.REG_LOOP_PKT_CNT_LO, rvalue); +      `ASSERT_ERROR(rvalue == NUM_PKTS, "Incorrect REG_LOOP_PKT_CNT_LO value"); + +      // Read item and packet counts on source port +      blk_ctrl.reg_read(dut.REG_SRC_LINE_CNT_LO, rvalue); +      `ASSERT_ERROR(rvalue == 0, "Incorrect REG_SRC_LINE_CNT_LO value"); +      blk_ctrl.reg_read(dut.REG_SRC_PKT_CNT_LO, rvalue); +      `ASSERT_ERROR(rvalue == 0, "Incorrect REG_SRC_PKT_CNT_LO value"); + +      // Read item and packet counts on sink port +      blk_ctrl.reg_read(dut.REG_SNK_LINE_CNT_LO, rvalue); +      `ASSERT_ERROR(rvalue == (LPP*NUM_PKTS), "Incorrect REG_SNK_LINE_CNT_LO value"); +      blk_ctrl.reg_read(dut.REG_SNK_PKT_CNT_LO, rvalue); +      `ASSERT_ERROR(rvalue == NUM_PKTS, "Incorrect REG_SNK_PKT_CNT_LO value"); +    end +    test.end_test(); + +    test.start_test("Stream Data From Source Port"); +    begin +      // Turn on the source for some time then stop it +      blk_ctrl.reg_write(dut.REG_SRC_LINES_PER_PKT, LPP-1); +      blk_ctrl.reg_write(dut.REG_SRC_BYTES_PER_PKT, (LPP+1)*8); +      blk_ctrl.reg_write(dut.REG_CTRL_STATUS, 2'b10); +      repeat ((NUM_PKTS / 10) * LPP) @(posedge rfnoc_chdr_clk); +      blk_ctrl.reg_write(dut.REG_CTRL_STATUS, 2'b00); +      blk_ctrl.reg_read(dut.REG_SRC_PKT_CNT_LO, rvalue); +      repeat (rvalue * LPP * 2) @(posedge rfnoc_chdr_clk); +      blk_ctrl.reg_read(dut.REG_SRC_PKT_CNT_LO, rvalue); + +      // Gather the accumulated packets and verify contents +      for (int p = 0; p < rvalue; p++) begin +        chdr_word_t exp_data[$]; +        chdr_word_t rx_data[$]; +        int rx_bytes; +        test.start_timeout(timeout, 5us, "Waiting for pkt to arrive"); +        exp_data.delete(); +        for (int i = p*LPP; i < (p+1)*LPP; i++) +          exp_data.push_back({~i[15:0], i[15:0], ~i[15:0], i[15:0]}); +        blk_ctrl.recv(PORT_SRCSNK, rx_data, rx_bytes); +        `ASSERT_ERROR(blk_ctrl.compare_data(exp_data, rx_data), "Data mismatch"); +        test.end_timeout(timeout); +      end +    end +    test.end_test(); + +    test.start_test("Clear Counts"); +    begin +      test.start_timeout(timeout, 1us, "Waiting for clear and readbacks"); +      // Clear +      blk_ctrl.reg_write(dut.REG_CTRL_STATUS, 2'b01); + +      // Read item and packet counts on loopback port +      blk_ctrl.reg_read(dut.REG_LOOP_LINE_CNT_LO, rvalue); +      `ASSERT_ERROR(rvalue == 0, "Incorrect REG_LOOP_LINE_CNT_LO value"); +      blk_ctrl.reg_read(dut.REG_LOOP_PKT_CNT_LO, rvalue); +      `ASSERT_ERROR(rvalue == 0, "Incorrect REG_LOOP_PKT_CNT_LO value"); + +      // Read item and packet counts on source port +      blk_ctrl.reg_read(dut.REG_SRC_LINE_CNT_LO, rvalue); +      `ASSERT_ERROR(rvalue == 0, "Incorrect REG_SRC_LINE_CNT_LO value"); +      blk_ctrl.reg_read(dut.REG_SRC_PKT_CNT_LO, rvalue); +      `ASSERT_ERROR(rvalue == 0, "Incorrect REG_SRC_PKT_CNT_LO value"); + +      // Read item and packet counts on sink port +      blk_ctrl.reg_read(dut.REG_SNK_LINE_CNT_LO, rvalue); +      `ASSERT_ERROR(rvalue == 0, "Incorrect REG_SNK_LINE_CNT_LO value"); +      blk_ctrl.reg_read(dut.REG_SNK_PKT_CNT_LO, rvalue); +      `ASSERT_ERROR(rvalue == 0, "Incorrect REG_SNK_PKT_CNT_LO value"); +      test.end_timeout(timeout); +    end +    test.end_test(); + +    // Finish Up +    // ---------------------------------------- +    // Display final statistics and results +    test.end_tb(); +  end + +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/Makefile b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/Makefile new file mode 100644 index 000000000..63d6f1851 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/Makefile @@ -0,0 +1,47 @@ +# +# Copyright 2019 Ettus Research, A National Instruments Company +# +# 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_preamble after defining BASE_DIR +include $(BASE_DIR)/../tools/make/viv_sim_preamble.mak + +#------------------------------------------------- +# Design Specific +#------------------------------------------------- +# Include makefiles and sources for the DUT and its dependencies +include $(BASE_DIR)/../lib/rfnoc/core/Makefile.srcs +include $(BASE_DIR)/../lib/rfnoc/utils/Makefile.srcs +include Makefile.srcs + +DESIGN_SRCS += $(abspath \ +$(RFNOC_CORE_SRCS) \ +$(RFNOC_UTIL_SRCS) \ +$(RFNOC_BLOCK_RADIO_SRCS) \ +) + +#------------------------------------------------- +# Testbench Specific +#------------------------------------------------- +SIM_TOP = rfnoc_block_radio_all_tb + +SIM_SRCS = \ +$(abspath sim_radio_gen.sv) \ +$(abspath rfnoc_block_radio_tb.sv) \ +$(abspath rfnoc_block_radio_all_tb.sv) + +# MODELSIM_USER_DO = $(abspath wave.do) + +#------------------------------------------------- +# 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/blocks/rfnoc_block_radio/Makefile.srcs b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/Makefile.srcs new file mode 100644 index 000000000..84dd01541 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/Makefile.srcs @@ -0,0 +1,20 @@ +# +# Copyright 2018 Ettus Research, A National Instruments Company +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# + +################################################## +# RFNoC Utility Sources +################################################## +RFNOC_BLOCK_RADIO_SRCS = $(abspath $(addprefix $(BASE_DIR)/../lib/rfnoc/blocks/rfnoc_block_radio/, \ +rfnoc_block_radio_regs.vh \ +radio_rx_core.v \ +radio_tx_core.v \ +radio_core.v \ +noc_shell_radio.v \ +rfnoc_block_radio.v \ +rx_frontend_gen3.v \ +tx_frontend_gen3.v \ +quarter_rate_downconverter.v \ +)) diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/noc_shell_radio.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/noc_shell_radio.v new file mode 100644 index 000000000..32ab32b63 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/noc_shell_radio.v @@ -0,0 +1,290 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: noc_shell_radio +// +// Description: A NoC Shell for RFNoC. This should eventually be replaced +// by an auto-generated NoC Shell. +// + +module noc_shell_radio #( +  parameter [31:0] NOC_ID          = 32'h0, +  parameter [ 9:0] THIS_PORTID     = 10'd0, +  parameter        CHDR_W          = 64, +  parameter [ 0:0] CTRLPORT_SLV_EN = 1, +  parameter [ 0:0] CTRLPORT_MST_EN = 1, +  parameter [ 5:0] CTRL_FIFO_SIZE  = 9, +  parameter [ 5:0] NUM_DATA_I      = 1, +  parameter [ 5:0] NUM_DATA_O      = 1, +  parameter        ITEM_W          = 32, +  parameter        NIPC            = 2, +  parameter        PYLD_FIFO_SIZE  = 10, +  parameter        MTU             = 10 +)( +  //--------------------------------------------------------------------------- +  // Framework Interface +  //--------------------------------------------------------------------------- + +  // RFNoC Framework Clocks and Resets +  input  wire                           rfnoc_chdr_clk, +  output wire                           rfnoc_chdr_rst, +  input  wire                           rfnoc_ctrl_clk, +  output wire                           rfnoc_ctrl_rst, +  // RFNoC Backend Interface +  input  wire [                  511:0] rfnoc_core_config, +  output wire [                  511:0] rfnoc_core_status, +  // CHDR Input Ports (from framework) +  input  wire [(CHDR_W*NUM_DATA_I)-1:0] s_rfnoc_chdr_tdata, +  input  wire [         NUM_DATA_I-1:0] s_rfnoc_chdr_tlast, +  input  wire [         NUM_DATA_I-1:0] s_rfnoc_chdr_tvalid, +  output wire [         NUM_DATA_I-1:0] s_rfnoc_chdr_tready, +  // CHDR Output Ports (to framework) +  output wire [(CHDR_W*NUM_DATA_O)-1:0] m_rfnoc_chdr_tdata, +  output wire [         NUM_DATA_O-1:0] m_rfnoc_chdr_tlast, +  output wire [         NUM_DATA_O-1:0] m_rfnoc_chdr_tvalid, +  input  wire [         NUM_DATA_O-1:0] m_rfnoc_chdr_tready, +  // AXIS-Ctrl Input Port (from framework) +  input  wire [                   31:0] s_rfnoc_ctrl_tdata, +  input  wire                           s_rfnoc_ctrl_tlast, +  input  wire                           s_rfnoc_ctrl_tvalid, +  output wire                           s_rfnoc_ctrl_tready, +  // AXIS-Ctrl Output Port (to framework) +  output wire [                   31:0] m_rfnoc_ctrl_tdata, +  output wire                           m_rfnoc_ctrl_tlast, +  output wire                           m_rfnoc_ctrl_tvalid, +  input  wire                           m_rfnoc_ctrl_tready, + +  //--------------------------------------------------------------------------- +  // Client Control Port Interface +  //--------------------------------------------------------------------------- + +  // Clock +  input  wire        ctrlport_clk, +  input  wire        ctrlport_rst, +  // Master +  output wire        m_ctrlport_req_wr, +  output wire        m_ctrlport_req_rd, +  output wire [19:0] m_ctrlport_req_addr, +  output wire [31:0] m_ctrlport_req_data, +  output wire [ 3:0] m_ctrlport_req_byte_en, +  output wire        m_ctrlport_req_has_time, +  output wire [63:0] m_ctrlport_req_time, +  input  wire        m_ctrlport_resp_ack, +  input  wire [ 1:0] m_ctrlport_resp_status, +  input  wire [31:0] m_ctrlport_resp_data, +  // Slave +  input  wire        s_ctrlport_req_wr, +  input  wire        s_ctrlport_req_rd, +  input  wire [19:0] s_ctrlport_req_addr, +  input  wire [ 9:0] s_ctrlport_req_portid, +  input  wire [15:0] s_ctrlport_req_rem_epid, +  input  wire [ 9:0] s_ctrlport_req_rem_portid, +  input  wire [31:0] s_ctrlport_req_data, +  input  wire [ 3:0] s_ctrlport_req_byte_en, +  input  wire        s_ctrlport_req_has_time, +  input  wire [63:0] s_ctrlport_req_time, +  output wire        s_ctrlport_resp_ack, +  output wire [ 1:0] s_ctrlport_resp_status, +  output wire [31:0] s_ctrlport_resp_data, + +  //--------------------------------------------------------------------------- +  // Client Data Interface +  //--------------------------------------------------------------------------- + +  // Clock +  input  wire                                axis_data_clk, +  input  wire                                axis_data_rst, + +  // Output data stream (to user logic) +  output wire [(NUM_DATA_I*ITEM_W*NIPC)-1:0] m_axis_tdata, +  output wire [       (NUM_DATA_I*NIPC)-1:0] m_axis_tkeep, +  output wire [              NUM_DATA_I-1:0] m_axis_tlast, +  output wire [              NUM_DATA_I-1:0] m_axis_tvalid, +  input  wire [              NUM_DATA_I-1:0] m_axis_tready, +  // Sideband information +  output wire [         (NUM_DATA_I*64)-1:0] m_axis_ttimestamp, +  output wire [              NUM_DATA_I-1:0] m_axis_thas_time, +  output wire [              NUM_DATA_I-1:0] m_axis_teov, +  output wire [              NUM_DATA_I-1:0] m_axis_teob, + +  // Input data stream (from user logic) +  input  wire [(NUM_DATA_O*ITEM_W*NIPC)-1:0] s_axis_tdata, +  input  wire [       (NUM_DATA_O*NIPC)-1:0] s_axis_tkeep, +  input  wire [              NUM_DATA_O-1:0] s_axis_tlast, +  input  wire [              NUM_DATA_O-1:0] s_axis_tvalid, +  output wire [              NUM_DATA_O-1:0] s_axis_tready, +  // Sideband info (sampled on the first cycle of the packet) +  input  wire [         (NUM_DATA_O*64)-1:0] s_axis_ttimestamp, +  input  wire [              NUM_DATA_O-1:0] s_axis_thas_time, +  input  wire [              NUM_DATA_O-1:0] s_axis_teov, +  input  wire [              NUM_DATA_O-1:0] s_axis_teob +); +   +  localparam SNK_INFO_FIFO_SIZE = 4; +  localparam SNK_PYLD_FIFO_SIZE = PYLD_FIFO_SIZE; +  localparam SRC_INFO_FIFO_SIZE = 4; +  localparam SRC_PYLD_FIFO_SIZE = MTU; + +  //--------------------------------------------------------------------------- +  //  Backend Interface +  //--------------------------------------------------------------------------- + +  wire         data_i_flush_en; +  wire [31:0]  data_i_flush_timeout; +  wire [63:0]  data_i_flush_active; +  wire [63:0]  data_i_flush_done; +  wire         data_o_flush_en; +  wire [31:0]  data_o_flush_timeout; +  wire [63:0]  data_o_flush_active; +  wire [63:0]  data_o_flush_done; + +  backend_iface #( +    .NOC_ID        (NOC_ID), +    .NUM_DATA_I    (NUM_DATA_I), +    .NUM_DATA_O    (NUM_DATA_O), +    .CTRL_FIFOSIZE (CTRL_FIFO_SIZE), +    .MTU           (MTU) +  ) backend_iface_i ( +    .rfnoc_chdr_clk       (rfnoc_chdr_clk), +    .rfnoc_ctrl_clk       (rfnoc_ctrl_clk), +    .rfnoc_core_config    (rfnoc_core_config), +    .rfnoc_core_status    (rfnoc_core_status), +    .rfnoc_chdr_rst       (rfnoc_chdr_rst), +    .rfnoc_ctrl_rst       (rfnoc_ctrl_rst), +    .data_i_flush_en      (data_i_flush_en), +    .data_i_flush_timeout (data_i_flush_timeout), +    .data_i_flush_active  (data_i_flush_active), +    .data_i_flush_done    (data_i_flush_done), +    .data_o_flush_en      (data_o_flush_en), +    .data_o_flush_timeout (data_o_flush_timeout), +    .data_o_flush_active  (data_o_flush_active), +    .data_o_flush_done    (data_o_flush_done) +  ); + +  //--------------------------------------------------------------------------- +  //  Control Path +  //--------------------------------------------------------------------------- + +  ctrlport_endpoint #( +    .THIS_PORTID              (THIS_PORTID    ), +    .SYNC_CLKS                (0              ), +    .AXIS_CTRL_MST_EN         (CTRLPORT_SLV_EN), +    .AXIS_CTRL_SLV_EN         (CTRLPORT_MST_EN), +    .SLAVE_FIFO_SIZE          (CTRL_FIFO_SIZE ) +  ) ctrlport_ep_i ( +    .rfnoc_ctrl_clk           (rfnoc_ctrl_clk           ), +    .rfnoc_ctrl_rst           (rfnoc_ctrl_rst           ), +    .ctrlport_clk             (ctrlport_clk             ), +    .ctrlport_rst             (ctrlport_rst             ), +    .s_rfnoc_ctrl_tdata       (s_rfnoc_ctrl_tdata       ), +    .s_rfnoc_ctrl_tlast       (s_rfnoc_ctrl_tlast       ), +    .s_rfnoc_ctrl_tvalid      (s_rfnoc_ctrl_tvalid      ), +    .s_rfnoc_ctrl_tready      (s_rfnoc_ctrl_tready      ), +    .m_rfnoc_ctrl_tdata       (m_rfnoc_ctrl_tdata       ), +    .m_rfnoc_ctrl_tlast       (m_rfnoc_ctrl_tlast       ), +    .m_rfnoc_ctrl_tvalid      (m_rfnoc_ctrl_tvalid      ), +    .m_rfnoc_ctrl_tready      (m_rfnoc_ctrl_tready      ), +    .m_ctrlport_req_wr        (m_ctrlport_req_wr        ), +    .m_ctrlport_req_rd        (m_ctrlport_req_rd        ), +    .m_ctrlport_req_addr      (m_ctrlport_req_addr      ), +    .m_ctrlport_req_data      (m_ctrlport_req_data      ), +    .m_ctrlport_req_byte_en   (m_ctrlport_req_byte_en   ), +    .m_ctrlport_req_has_time  (m_ctrlport_req_has_time  ), +    .m_ctrlport_req_time      (m_ctrlport_req_time      ), +    .m_ctrlport_resp_ack      (m_ctrlport_resp_ack      ), +    .m_ctrlport_resp_status   (m_ctrlport_resp_status   ), +    .m_ctrlport_resp_data     (m_ctrlport_resp_data     ), +    .s_ctrlport_req_wr        (s_ctrlport_req_wr        ), +    .s_ctrlport_req_rd        (s_ctrlport_req_rd        ), +    .s_ctrlport_req_addr      (s_ctrlport_req_addr      ), +    .s_ctrlport_req_portid    (s_ctrlport_req_portid    ), +    .s_ctrlport_req_rem_epid  (s_ctrlport_req_rem_epid  ), +    .s_ctrlport_req_rem_portid(s_ctrlport_req_rem_portid), +    .s_ctrlport_req_data      (s_ctrlport_req_data      ), +    .s_ctrlport_req_byte_en   (s_ctrlport_req_byte_en   ), +    .s_ctrlport_req_has_time  (s_ctrlport_req_has_time  ), +    .s_ctrlport_req_time      (s_ctrlport_req_time      ), +    .s_ctrlport_resp_ack      (s_ctrlport_resp_ack      ), +    .s_ctrlport_resp_status   (s_ctrlport_resp_status   ), +    .s_ctrlport_resp_data     (s_ctrlport_resp_data     ) +  ); + +  //--------------------------------------------------------------------------- +  //  Data Path +  //--------------------------------------------------------------------------- + +  genvar i; +  generate + +    for (i = 0; i < NUM_DATA_I; i = i + 1) begin: chdr_to_data +      chdr_to_axis_data #( +        .CHDR_W         (CHDR_W), +        .ITEM_W         (ITEM_W), +        .NIPC           (NIPC), +        .SYNC_CLKS      (0), +        .INFO_FIFO_SIZE (SNK_INFO_FIFO_SIZE), +        .PYLD_FIFO_SIZE (SNK_PYLD_FIFO_SIZE) +      ) chdr_to_axis_data_i ( +        .axis_chdr_clk      (rfnoc_chdr_clk), +        .axis_chdr_rst      (rfnoc_chdr_rst), +        .axis_data_clk      (axis_data_clk), +        .axis_data_rst      (axis_data_rst), +        .s_axis_chdr_tdata  (s_rfnoc_chdr_tdata  [(i*CHDR_W)+:CHDR_W]), +        .s_axis_chdr_tlast  (s_rfnoc_chdr_tlast  [i]), +        .s_axis_chdr_tvalid (s_rfnoc_chdr_tvalid [i]), +        .s_axis_chdr_tready (s_rfnoc_chdr_tready [i]), +        .m_axis_tdata       (m_axis_tdata  [i*ITEM_W*NIPC +: ITEM_W*NIPC]), +        .m_axis_tkeep       (m_axis_tkeep  [i*NIPC +: NIPC]), +        .m_axis_tlast       (m_axis_tlast  [i]), +        .m_axis_tvalid      (m_axis_tvalid [i]), +        .m_axis_tready      (m_axis_tready [i]), +        .m_axis_ttimestamp  (m_axis_ttimestamp [i*64 +: 64]), +        .m_axis_thas_time   (m_axis_thas_time  [i]), +        .m_axis_tlength     (), +        .m_axis_teov        (m_axis_teov       [i]), +        .m_axis_teob        (m_axis_teob       [i]), +        .flush_en           (data_i_flush_en), +        .flush_timeout      (data_i_flush_timeout), +        .flush_active       (data_i_flush_active [i]), +        .flush_done         (data_i_flush_done   [i]) +      ); +    end + +    for (i = 0; i < NUM_DATA_O; i = i + 1) begin: data_to_chdr +      axis_data_to_chdr #( +        .CHDR_W         (CHDR_W), +        .ITEM_W         (ITEM_W), +        .NIPC           (NIPC), +        .SYNC_CLKS      (0), +        .INFO_FIFO_SIZE (4), +        .PYLD_FIFO_SIZE (SRC_INFO_FIFO_SIZE), +        .MTU            (SRC_PYLD_FIFO_SIZE) +      ) axis_data_to_chdr_i ( +        .axis_chdr_clk       (rfnoc_chdr_clk), +        .axis_chdr_rst       (rfnoc_chdr_rst), +        .axis_data_clk       (axis_data_clk), +        .axis_data_rst       (axis_data_rst), +        .m_axis_chdr_tdata   (m_rfnoc_chdr_tdata  [i*CHDR_W +: CHDR_W]), +        .m_axis_chdr_tlast   (m_rfnoc_chdr_tlast  [i]), +        .m_axis_chdr_tvalid  (m_rfnoc_chdr_tvalid [i]), +        .m_axis_chdr_tready  (m_rfnoc_chdr_tready [i]), +        .s_axis_tdata        (s_axis_tdata  [i*ITEM_W*NIPC +: ITEM_W*NIPC]), +        .s_axis_tkeep        (s_axis_tkeep  [i*NIPC +: NIPC]), +        .s_axis_tlast        (s_axis_tlast  [i]), +        .s_axis_tvalid       (s_axis_tvalid [i]), +        .s_axis_tready       (s_axis_tready [i]), +        .s_axis_ttimestamp   (s_axis_ttimestamp [i*64 +: 64]), +        .s_axis_thas_time    (s_axis_thas_time  [i]), +        .s_axis_teov         (s_axis_teov       [i]), +        .s_axis_teob         (s_axis_teob       [i]), +        .flush_en            (data_o_flush_en), +        .flush_timeout       (data_o_flush_timeout), +        .flush_active        (data_o_flush_active [i]), +        .flush_done          (data_o_flush_done   [i]) +      ); +    end +  endgenerate + +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/quarter_rate_downconverter.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/quarter_rate_downconverter.v new file mode 100644 index 000000000..ded9a8c0b --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/quarter_rate_downconverter.v @@ -0,0 +1,138 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +// mixer with 90 degree angles, i.e., multiplying the input signal with 1, i, -1, -i: + +// Let S(t) = I(t) + i*Q(t) be the input signal based on inputs i_in and q_in +// Multiplying with (1,i,-1,-i) then becomes: +// S(t) * 1  =  I(t) + i*Q(t) +// S(t) * i  = -Q(t) + i*I(t) +// S(t) * -1 = -I(t) - i*Q(t) +// S(t) * -i =  Q(t) - i*I(t) + +// To control the direction of rotation, the dirctn input is used +// When set to 0, the phase is increased with pi/2 every sample, i.e., rotating counter clock wise +// When set to 1, the phase is increased with -pi/2 every sample, i.e., rotating clock wise + +// the input is the concatenation of the i and q signal: {i_in, q_in} + +module quarter_rate_downconverter #( +  parameter WIDTH=24 +)( +  input clk, +  input reset, +  input phase_sync, + +  input [2*WIDTH-1:0] i_tdata, +  input i_tlast, +  input i_tvalid, +  output i_tready, + +  output [2*WIDTH-1:0] o_tdata, +  output o_tlast, +  output o_tvalid, +  input o_tready, + +  input dirctn +); + +  // temporary signals for i and q after rotation +  reg [WIDTH-1:0] tmp_i = {WIDTH{1'b0}}; +  reg [WIDTH-1:0] tmp_q = {WIDTH{1'b0}}; + +  // State machine types and reg +  localparam S0=0, S1=1, S2=2, S3=3; +  reg[1:0] cur_state; + +  // split input into i and q signal +  wire[WIDTH-1:0] i_in, q_in; +  assign i_in = i_tdata[2*WIDTH-1:WIDTH]; +  assign q_in = i_tdata[WIDTH-1:0]; + +  // The state machine doing the rotations among states +  always @(posedge clk) begin +    if(reset || phase_sync) begin +       cur_state <= S0; +    end else begin +      case (cur_state) +        S0: begin +            if(i_tvalid == 1'b1 && i_tready == 1'b1) +              if(dirctn == 1'b0) +                cur_state <= S1; +              else +                cur_state <= S3; +            else +              cur_state <= S0; +          end +        S1: begin +            if(i_tvalid == 1'b1 && i_tready == 1'b1) +              if(dirctn == 1'b0) +                cur_state <= S2; +              else +                cur_state <= S0; +            else +              cur_state <= S1; +          end +        S2: begin +            if(i_tvalid == 1'b1 && i_tready == 1'b1) +              if(dirctn == 1'b0) +                cur_state <= S3; +              else +                cur_state <= S1; +            else +              cur_state <= S2; +          end +        S3: begin +            if(i_tvalid == 1'b1 && i_tready == 1'b1) +              if(dirctn == 1'b0) +                cur_state <= S0; +              else +                cur_state <= S2; +            else +              cur_state <= S3; +          end +      endcase +    end +  end + +  // Multiplication of input IQ signal with (1,i,-1,-i): +  always @(*) begin +    case (cur_state) +      S0: begin +          // S(t) * 1 = I(t) + iQ(t): +          tmp_i = i_in; +          tmp_q = q_in; +        end +      S1: begin +          // S(t) * i = -Q(t) + iI(t): +          tmp_i = -q_in; +          tmp_q = i_in; +        end +      S2: begin +          // S(t) * -1 = -I(t) - iQ(t): +          tmp_i = -i_in; +          tmp_q = -q_in; +        end +      S3: begin +          // S(t) * -i = Q(t) - iI(t): +          tmp_i = q_in; +          tmp_q = -i_in; +        end +      default: begin +          tmp_i = i_in; +          tmp_q = q_in; +        end +    endcase +  end + +  // Flop for valid and ready signals and shortening of comb. paths. +  axi_fifo #(.WIDTH(2*WIDTH + 1), .SIZE(1)) flop ( +    .clk(clk), .reset(reset), .clear(1'b0), +    .i_tdata({i_tlast, tmp_i, tmp_q}), .i_tvalid(i_tvalid), .i_tready(i_tready), +    .o_tdata({o_tlast, o_tdata}), .o_tvalid(o_tvalid), .o_tready(o_tready), +    .occupied(), .space()); + +endmodule // quarter_rate_downconverter diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/radio_core.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/radio_core.v new file mode 100644 index 000000000..9456fc398 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/radio_core.v @@ -0,0 +1,370 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: radio_core +// +// Description: +// +// A radio core for RFNoC. This core contains all logic in the radio clock  +// domain for interfacing to a single RX/TX radio. It includes registers shared  +// by both Rx and Tx logic and instantiates Rx and Tx interface cores. +// +// Parameters: +// +//   BASE_ADDR : Base address for this radio block instance +//   SAMP_W    : Width of a radio sample +//   NSPC      : Number of radio samples per radio clock cycle +// + + +module radio_core #( +  parameter SAMP_W    = 32, +  parameter NSPC      = 1 +) ( +  input wire radio_clk, +  input wire radio_rst, + + +  //--------------------------------------------------------------------------- +  // Control Interface +  //--------------------------------------------------------------------------- + +  // Slave +  input  wire        s_ctrlport_req_wr, +  input  wire        s_ctrlport_req_rd, +  input  wire [19:0] s_ctrlport_req_addr, +  input  wire [31:0] s_ctrlport_req_data, +  output wire        s_ctrlport_resp_ack, +  output wire [31:0] s_ctrlport_resp_data, + +  // Master +  output wire        m_ctrlport_req_wr, +  output wire [19:0] m_ctrlport_req_addr, +  output wire [ 9:0] m_ctrlport_req_portid, +  output wire [15:0] m_ctrlport_req_rem_epid, +  output wire [ 9:0] m_ctrlport_req_rem_portid, +  output wire [31:0] m_ctrlport_req_data, +  output wire        m_ctrlport_req_has_time, +  output wire [63:0] m_ctrlport_req_time, +  input  wire        m_ctrlport_resp_ack, + + +  //--------------------------------------------------------------------------- +  // Data Interface +  //--------------------------------------------------------------------------- + +  // Tx Radio Data Stream +  input  wire [(SAMP_W*NSPC)-1:0] s_axis_tdata, +  input  wire                     s_axis_tlast, +  input  wire                     s_axis_tvalid, +  output wire                     s_axis_tready, +  // Sideband info +  input  wire [             63:0] s_axis_ttimestamp, +  input  wire                     s_axis_thas_time, +  input  wire                     s_axis_teob, + +  // Rx Radio Data Stream +  output wire [(SAMP_W*NSPC)-1:0] m_axis_tdata, +  output wire                     m_axis_tlast, +  output wire                     m_axis_tvalid, +  input  wire                     m_axis_tready, +  // Sideband info +  output wire [             63:0] m_axis_ttimestamp, +  output wire                     m_axis_thas_time, +  output wire                     m_axis_teob, + + +  //--------------------------------------------------------------------------- +  // Radio Interface +  //--------------------------------------------------------------------------- + +  input wire [63:0] radio_time, + +  // Radio Rx Interface +  input  wire [SAMP_W*NSPC-1:0] radio_rx_data, +  input  wire                   radio_rx_stb, +  output wire                   radio_rx_running, + +  // Radio Tx Interface +  output wire [SAMP_W*NSPC-1:0] radio_tx_data, +  input  wire                   radio_tx_stb, +  output wire                   radio_tx_running +); + +  `include "rfnoc_block_radio_regs.vh" + + +  //--------------------------------------------------------------------------- +  // Split Control Port Interface +  //--------------------------------------------------------------------------- +  // +  // This block splits the single slave interface of the radio core into  +  // multiple interfaces, one for each subcomponent. The responses from each  +  // subcomponent are merged into a single response and sent back out the slave  +  // interface. +  // +  //--------------------------------------------------------------------------- + +  // Registers shared by Rx and Tx +  wire        ctrlport_general_req_wr; +  wire        ctrlport_general_req_rd; +  wire [19:0] ctrlport_general_req_addr; +  wire [31:0] ctrlport_general_req_data; +  reg         ctrlport_general_resp_ack  = 1'b0; +  reg  [31:0] ctrlport_general_resp_data = 0; + +  // Tx core registers +  wire        ctrlport_tx_req_wr; +  wire        ctrlport_tx_req_rd; +  wire [19:0] ctrlport_tx_req_addr; +  wire [31:0] ctrlport_tx_req_data; +  wire        ctrlport_tx_resp_ack; +  wire [31:0] ctrlport_tx_resp_data; + +  // Rx core registers +  wire        ctrlport_rx_req_wr; +  wire        ctrlport_rx_req_rd; +  wire [19:0] ctrlport_rx_req_addr; +  wire [31:0] ctrlport_rx_req_data; +  wire        ctrlport_rx_resp_ack; +  wire [31:0] ctrlport_rx_resp_data; + +  ctrlport_splitter #( +    .NUM_SLAVES (3) +  ) ctrlport_decoder_i ( +    .ctrlport_clk            (radio_clk), +    .ctrlport_rst            (radio_rst), +    .s_ctrlport_req_wr       (s_ctrlport_req_wr), +    .s_ctrlport_req_rd       (s_ctrlport_req_rd), +    .s_ctrlport_req_addr     (s_ctrlport_req_addr), +    .s_ctrlport_req_data     (s_ctrlport_req_data), +    .s_ctrlport_req_byte_en  (4'b0), +    .s_ctrlport_req_has_time (1'b0), +    .s_ctrlport_req_time     (64'b0), +    .s_ctrlport_resp_ack     (s_ctrlport_resp_ack), +    .s_ctrlport_resp_status  (), +    .s_ctrlport_resp_data    (s_ctrlport_resp_data), +    .m_ctrlport_req_wr       ({ctrlport_general_req_wr, +                               ctrlport_tx_req_wr, +                               ctrlport_rx_req_wr}), +    .m_ctrlport_req_rd       ({ctrlport_general_req_rd, +                               ctrlport_tx_req_rd, +                               ctrlport_rx_req_rd}), +    .m_ctrlport_req_addr     ({ctrlport_general_req_addr, +                               ctrlport_tx_req_addr, +                               ctrlport_rx_req_addr}), +    .m_ctrlport_req_data     ({ctrlport_general_req_data, +                               ctrlport_tx_req_data, +                               ctrlport_rx_req_data}), +    .m_ctrlport_req_byte_en  (), +    .m_ctrlport_req_has_time (), +    .m_ctrlport_req_time     (), +    .m_ctrlport_resp_ack     ({ctrlport_general_resp_ack, +                               ctrlport_tx_resp_ack, +                               ctrlport_rx_resp_ack}), +    .m_ctrlport_resp_status  (6'b0), +    .m_ctrlport_resp_data    ({ctrlport_general_resp_data, +                               ctrlport_tx_resp_data, +                               ctrlport_rx_resp_data}) +  ); + + +  //--------------------------------------------------------------------------- +  // Merge Control Port Interfaces +  //--------------------------------------------------------------------------- +  // +  // This block merges the master control port interfaces of the Rx and Tx  +  // cores into a single master control port interface. Both the Rx and Tx  +  // cores support error reporting by writing to a control port interface. This  +  // block arbitrates the requests between the Rx and Tx cores. Rx and Tx only  +  // support writes for error reporting, not reads. Time and byte enables are  +  // also not needed. Hence, several ports are unconnected. +  // +  //--------------------------------------------------------------------------- + +  // Tx and Rx error reporting signals +  wire        ctrlport_err_tx_req_wr,         ctrlport_err_rx_req_wr; +  wire [19:0] ctrlport_err_tx_req_addr,       ctrlport_err_rx_req_addr; +  wire [31:0] ctrlport_err_tx_req_data,       ctrlport_err_rx_req_data; +  wire        ctrlport_err_tx_req_has_time,   ctrlport_err_rx_req_has_time; +  wire [63:0] ctrlport_err_tx_req_time,       ctrlport_err_rx_req_time; +  wire [ 9:0] ctrlport_err_tx_req_portid,     ctrlport_err_rx_req_portid; +  wire [15:0] ctrlport_err_tx_req_rem_epid,   ctrlport_err_rx_req_rem_epid; +  wire [ 9:0] ctrlport_err_tx_req_rem_portid, ctrlport_err_rx_req_rem_portid; +  wire        ctrlport_err_tx_resp_ack,       ctrlport_err_rx_resp_ack; + + +  ctrlport_combiner #( +    .NUM_MASTERS (2), +    .PRIORITY    (0) +  ) ctrlport_req_combine_i ( +    .ctrlport_clk              (radio_clk), +    .ctrlport_rst              (radio_rst), +    .s_ctrlport_req_wr         ({ctrlport_err_tx_req_wr,         ctrlport_err_rx_req_wr}), +    .s_ctrlport_req_rd         (2'b0), +    .s_ctrlport_req_addr       ({ctrlport_err_tx_req_addr,       ctrlport_err_rx_req_addr}), +    .s_ctrlport_req_portid     ({ctrlport_err_tx_req_portid,     ctrlport_err_rx_req_portid}), +    .s_ctrlport_req_rem_epid   ({ctrlport_err_tx_req_rem_epid,   ctrlport_err_rx_req_rem_epid}), +    .s_ctrlport_req_rem_portid ({ctrlport_err_tx_req_rem_portid, ctrlport_err_rx_req_rem_portid}), +    .s_ctrlport_req_data       ({ctrlport_err_tx_req_data,       ctrlport_err_rx_req_data}), +    .s_ctrlport_req_byte_en    (8'hFF), +    .s_ctrlport_req_has_time   ({ctrlport_err_tx_req_has_time,   ctrlport_err_rx_req_has_time}), +    .s_ctrlport_req_time       ({ctrlport_err_tx_req_time,       ctrlport_err_rx_req_time}), +    .s_ctrlport_resp_ack       ({ctrlport_err_tx_resp_ack,       ctrlport_err_rx_resp_ack}), +    .s_ctrlport_resp_status    (), +    .s_ctrlport_resp_data      (), +    .m_ctrlport_req_wr         (m_ctrlport_req_wr), +    .m_ctrlport_req_rd         (), +    .m_ctrlport_req_addr       (m_ctrlport_req_addr), +    .m_ctrlport_req_portid     (m_ctrlport_req_portid), +    .m_ctrlport_req_rem_epid   (m_ctrlport_req_rem_epid), +    .m_ctrlport_req_rem_portid (m_ctrlport_req_rem_portid), +    .m_ctrlport_req_data       (m_ctrlport_req_data), +    .m_ctrlport_req_byte_en    (), +    .m_ctrlport_req_has_time   (m_ctrlport_req_has_time), +    .m_ctrlport_req_time       (m_ctrlport_req_time), +    .m_ctrlport_resp_ack       (m_ctrlport_resp_ack), +    .m_ctrlport_resp_status    (2'b0), +    .m_ctrlport_resp_data      (0) +  ); + + +  //--------------------------------------------------------------------------- +  // General Registers +  //--------------------------------------------------------------------------- +  // +  // These are registers that apply to both Rx and Tx and are shared by both. +  // +  //--------------------------------------------------------------------------- + +  reg reg_loopback_en = 1'b0; + +  always @(posedge radio_clk) begin +    if (radio_rst) begin +      ctrlport_general_resp_ack  <= 0; +      ctrlport_general_resp_data <= 0; +      reg_loopback_en            <= 0; +    end else begin +      // Default assignments +      ctrlport_general_resp_ack  <= 0; +      ctrlport_general_resp_data <= 0; + +      // Handle register writes +      if (ctrlport_general_req_wr) begin +        case (ctrlport_general_req_addr) +          REG_LOOPBACK_EN: begin +            reg_loopback_en           <= ctrlport_general_req_data[0]; +            ctrlport_general_resp_ack <= 1; +          end +        endcase +      end + +      // Handle register reads +      if (ctrlport_general_req_rd) begin +        case (ctrlport_general_req_addr) +          REG_LOOPBACK_EN: begin +            ctrlport_general_resp_data    <= 0; +            ctrlport_general_resp_data[0] <= reg_loopback_en; +            ctrlport_general_resp_ack     <= 1; +          end +          REG_RADIO_WIDTH: begin +            ctrlport_general_resp_data <= { SAMP_W[15:0], NSPC[15:0] }; +            ctrlport_general_resp_ack  <= 1; +          end +        endcase +      end +    end +  end + + +  //--------------------------------------------------------------------------- +  // Tx to Rx Loopback +  //--------------------------------------------------------------------------- + +  wire [SAMP_W*NSPC-1:0] radio_rx_data_mux; +  wire                   radio_rx_stb_mux; + +  assign radio_rx_data_mux = reg_loopback_en ? radio_tx_data : radio_rx_data; +  assign radio_rx_stb_mux  = reg_loopback_en ? radio_tx_stb  : radio_rx_stb; + + +  //--------------------------------------------------------------------------- +  // Tx Core +  //--------------------------------------------------------------------------- + +  radio_tx_core #( +    .SAMP_W    (SAMP_W), +    .NSPC      (NSPC) +  ) radio_tx_core_i ( +    .radio_clk                 (radio_clk), +    .radio_rst                 (radio_rst), +    .s_ctrlport_req_wr         (ctrlport_tx_req_wr), +    .s_ctrlport_req_rd         (ctrlport_tx_req_rd), +    .s_ctrlport_req_addr       (ctrlport_tx_req_addr), +    .s_ctrlport_req_data       (ctrlport_tx_req_data), +    .s_ctrlport_resp_ack       (ctrlport_tx_resp_ack), +    .s_ctrlport_resp_data      (ctrlport_tx_resp_data), +    .m_ctrlport_req_wr         (ctrlport_err_tx_req_wr), +    .m_ctrlport_req_addr       (ctrlport_err_tx_req_addr), +    .m_ctrlport_req_data       (ctrlport_err_tx_req_data), +    .m_ctrlport_req_has_time   (ctrlport_err_tx_req_has_time), +    .m_ctrlport_req_time       (ctrlport_err_tx_req_time), +    .m_ctrlport_req_portid     (ctrlport_err_tx_req_portid), +    .m_ctrlport_req_rem_epid   (ctrlport_err_tx_req_rem_epid), +    .m_ctrlport_req_rem_portid (ctrlport_err_tx_req_rem_portid), +    .m_ctrlport_resp_ack       (ctrlport_err_tx_resp_ack), +    .radio_time                (radio_time), +    .radio_tx_data             (radio_tx_data), +    .radio_tx_stb              (radio_tx_stb), +    .radio_tx_running          (radio_tx_running), +    .s_axis_tdata              (s_axis_tdata), +    .s_axis_tlast              (s_axis_tlast), +    .s_axis_tvalid             (s_axis_tvalid), +    .s_axis_tready             (s_axis_tready), +    .s_axis_ttimestamp         (s_axis_ttimestamp), +    .s_axis_thas_time          (s_axis_thas_time), +    .s_axis_teob               (s_axis_teob) +  ); + + +  //--------------------------------------------------------------------------- +  // Rx Core +  //--------------------------------------------------------------------------- + +  radio_rx_core #( +    .SAMP_W    (SAMP_W), +    .NSPC      (NSPC) +  ) radio_rx_core_i ( +    .radio_clk                 (radio_clk), +    .radio_rst                 (radio_rst), +    .s_ctrlport_req_wr         (ctrlport_rx_req_wr), +    .s_ctrlport_req_rd         (ctrlport_rx_req_rd), +    .s_ctrlport_req_addr       (ctrlport_rx_req_addr), +    .s_ctrlport_req_data       (ctrlport_rx_req_data), +    .s_ctrlport_resp_ack       (ctrlport_rx_resp_ack), +    .s_ctrlport_resp_data      (ctrlport_rx_resp_data), +    .m_ctrlport_req_wr         (ctrlport_err_rx_req_wr), +    .m_ctrlport_req_addr       (ctrlport_err_rx_req_addr), +    .m_ctrlport_req_data       (ctrlport_err_rx_req_data), +    .m_ctrlport_req_has_time   (ctrlport_err_rx_req_has_time), +    .m_ctrlport_req_time       (ctrlport_err_rx_req_time), +    .m_ctrlport_req_portid     (ctrlport_err_rx_req_portid), +    .m_ctrlport_req_rem_epid   (ctrlport_err_rx_req_rem_epid), +    .m_ctrlport_req_rem_portid (ctrlport_err_rx_req_rem_portid), +    .m_ctrlport_resp_ack       (ctrlport_err_rx_resp_ack), +    .radio_time                (radio_time), +    .radio_rx_data             (radio_rx_data_mux), +    .radio_rx_stb              (radio_rx_stb_mux), +    .radio_rx_running          (radio_rx_running), +    .m_axis_tdata              (m_axis_tdata), +    .m_axis_tlast              (m_axis_tlast), +    .m_axis_tvalid             (m_axis_tvalid), +    .m_axis_tready             (m_axis_tready), +    .m_axis_ttimestamp         (m_axis_ttimestamp), +    .m_axis_thas_time          (m_axis_thas_time), +    .m_axis_teob               (m_axis_teob) +  ); + +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/radio_rx_core.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/radio_rx_core.v new file mode 100644 index 000000000..ee7774fd7 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/radio_rx_core.v @@ -0,0 +1,521 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: radio_rx_core +// +// Description: +// +// This module contains the core Rx radio acquisition logic. It retrieves  +// sample data from the radio interface, as indicated by the radio's strobe  +// signal, and outputs the data via AXI-Stream. +// +// The receiver is operated by writing a time (optionally) to the  +// REG_RX_CMD_TIME_* registers and a number of words (optionally) to  +// REG_RX_CMD_NUM_WORDS_* registers followed by writing a command word to  +// REG_RX_CMD. The command word indicates whether it is a finite ("num samps  +// and done") or continuous acquisition and whether or not the acquisition  +// should start at the time indicated byREG_RX_CMD_TIME_*. A stop command will  +// stop any acquisition that's waiting to start or is in progress. +// +// The REG_RX_MAX_WORDS_PER_PKT and REG_RX_ERR_* registers should be  +// initialized prior to the first acquisition. +// +// Parameters: +// +//   SAMP_W    : Width of a radio sample +//   NSPC      : Number of radio samples per radio clock cycle +// +`default_nettype none + + +module radio_rx_core #( +  parameter SAMP_W    = 32, +  parameter NSPC      = 1 +) ( +  input wire radio_clk, +  input wire radio_rst, + + +  //--------------------------------------------------------------------------- +  // Control Interface +  //--------------------------------------------------------------------------- + +  // Slave (Register Reads and Writes) +  input  wire        s_ctrlport_req_wr, +  input  wire        s_ctrlport_req_rd, +  input  wire [19:0] s_ctrlport_req_addr, +  input  wire [31:0] s_ctrlport_req_data, +  output reg         s_ctrlport_resp_ack  = 1'b0, +  output reg  [31:0] s_ctrlport_resp_data, + +  // Master (Error Reporting) +  output reg         m_ctrlport_req_wr = 1'b0, +  output reg  [19:0] m_ctrlport_req_addr, +  output reg  [31:0] m_ctrlport_req_data, +  output wire        m_ctrlport_req_has_time, +  output reg  [63:0] m_ctrlport_req_time, +  output wire [ 9:0] m_ctrlport_req_portid, +  output wire [15:0] m_ctrlport_req_rem_epid, +  output wire [ 9:0] m_ctrlport_req_rem_portid, +  input  wire        m_ctrlport_resp_ack, + + +  //--------------------------------------------------------------------------- +  // Radio Interface +  //--------------------------------------------------------------------------- + +  input wire [63:0] radio_time, + +  input  wire [SAMP_W*NSPC-1:0] radio_rx_data, +  input  wire                   radio_rx_stb, + +  // Status indicator (true when receiving) +  output wire radio_rx_running, + + +  //--------------------------------------------------------------------------- +  // AXI-Stream Data Output +  //--------------------------------------------------------------------------- + +  output wire [SAMP_W*NSPC-1:0] m_axis_tdata, +  output wire                   m_axis_tlast, +  output wire                   m_axis_tvalid, +  input  wire                   m_axis_tready, +  // Sideband info +  output wire [           63:0] m_axis_ttimestamp, +  output wire                   m_axis_thas_time, +  output wire                   m_axis_teob +); + +  `include "rfnoc_block_radio_regs.vh" +  `include "../../core/rfnoc_chdr_utils.vh" + +  localparam NUM_WORDS_LEN = RX_CMD_NUM_WORDS_LEN; + + +  //--------------------------------------------------------------------------- +  // Register Read/Write Logic +  //--------------------------------------------------------------------------- + +  reg                      reg_cmd_valid        = 0;  // Indicates when the CMD_FIFO has been written +  reg  [   RX_CMD_LEN-1:0] reg_cmd_word         = 0;  // Command to execute +  reg  [NUM_WORDS_LEN-1:0] reg_cmd_num_words    = 0;  // Number of words for the command +  reg  [             63:0] reg_cmd_time         = 0;  // Time for the command +  reg                      reg_cmd_timed        = 0;  // Indicates if this is a timed command +  reg  [             31:0] reg_max_pkt_len      = 64; // Maximum words per packet +  reg  [              9:0] reg_error_portid     = 0;  // Port ID to use for error reporting +  reg  [             15:0] reg_error_rem_epid   = 0;  // Remote EPID to use for error reporting +  reg  [              9:0] reg_error_rem_portid = 0;  // Remote port ID to use for error reporting +  reg  [             19:0] reg_error_addr       = 0;  // Address to use for error reporting +  reg                      reg_has_time         = 1;  // Whether or not to use timestamps on data + +  wire [15:0] cmd_fifo_space;   // Empty space in the command FIFO +  reg         cmd_stop     = 0; // Indicates a full stop request +  wire        cmd_stop_ack;     // Acknowledgment that a stop has completed +  reg         clear_fifo   = 0; // Signal to clear the command FIFO + +  assign m_axis_thas_time = reg_has_time; + +  always @(posedge radio_clk) begin +    if (radio_rst) begin +      s_ctrlport_resp_ack  <= 0; +      reg_cmd_valid        <= 0; +      reg_cmd_word         <= 0; +      reg_cmd_num_words    <= 0; +      reg_cmd_time         <= 0; +      reg_cmd_timed        <= 0; +      reg_max_pkt_len      <= 64; +      reg_error_portid     <= 0; +      reg_error_rem_epid   <= 0; +      reg_error_rem_portid <= 0; +      reg_error_addr       <= 0; +      reg_has_time         <= 1; +      clear_fifo           <= 0; +      cmd_stop             <= 0; +    end else begin +      // Default assignments +      s_ctrlport_resp_ack  <= 0; +      s_ctrlport_resp_data <= 0; +      reg_cmd_valid        <= 0; +      clear_fifo           <= 0; + +      // Clear stop register when we enter the STOP state +      if (cmd_stop_ack) cmd_stop <= 1'b0; + +      // Handle register writes +      if (s_ctrlport_req_wr) begin +        case (s_ctrlport_req_addr) +          REG_RX_CMD: begin +            // All commands go into the command FIFO except STOP +            reg_cmd_valid       <= (s_ctrlport_req_data[RX_CMD_LEN-1:0] != RX_CMD_STOP); +            reg_cmd_word        <=  s_ctrlport_req_data[RX_CMD_LEN-1:0]; +            reg_cmd_timed       <=  s_ctrlport_req_data[RX_CMD_TIMED_POS]; +            s_ctrlport_resp_ack <= 1; + +            // cmd_stop must remain asserted until it has completed +            if (!cmd_stop || cmd_stop_ack) begin +              cmd_stop <= (s_ctrlport_req_data[RX_CMD_LEN-1:0] == RX_CMD_STOP); +            end +            clear_fifo <= (s_ctrlport_req_data[RX_CMD_LEN-1:0] == RX_CMD_STOP); +          end +          REG_RX_CMD_NUM_WORDS_LO: begin +            reg_cmd_num_words[31:0] <= s_ctrlport_req_data; +            s_ctrlport_resp_ack     <= 1; +          end +          REG_RX_CMD_NUM_WORDS_HI: begin +            reg_cmd_num_words[NUM_WORDS_LEN-1:32] <= s_ctrlport_req_data[NUM_WORDS_LEN-32-1:0]; +            s_ctrlport_resp_ack                   <= 1; +          end +          REG_RX_CMD_TIME_LO: begin +            reg_cmd_time[31:0]  <= s_ctrlport_req_data; +            s_ctrlport_resp_ack <= 1; +          end +          REG_RX_CMD_TIME_HI: begin +            reg_cmd_time[63:32] <= s_ctrlport_req_data; +            s_ctrlport_resp_ack <= 1; +          end +          REG_RX_MAX_WORDS_PER_PKT: begin +            reg_max_pkt_len     <= s_ctrlport_req_data; +            s_ctrlport_resp_ack <= 1; +          end +          REG_RX_ERR_PORT: begin +            reg_error_portid    <= s_ctrlport_req_data[9:0]; +            s_ctrlport_resp_ack <= 1; +          end +          REG_RX_ERR_REM_PORT: begin +            reg_error_rem_portid <= s_ctrlport_req_data[9:0]; +            s_ctrlport_resp_ack  <= 1; +          end +          REG_RX_ERR_REM_EPID: begin +            reg_error_rem_epid  <= s_ctrlport_req_data[15:0]; +            s_ctrlport_resp_ack <= 1; +          end +          REG_RX_ERR_ADDR: begin +            reg_error_addr      <= s_ctrlport_req_data[19:0]; +            s_ctrlport_resp_ack <= 1; +          end +          REG_RX_HAS_TIME: begin +            reg_has_time        <= s_ctrlport_req_data[0:0]; +            s_ctrlport_resp_ack <= 1; +          end +        endcase +      end + +      // Handle register reads +      if (s_ctrlport_req_rd) begin +        case (s_ctrlport_req_addr) +          REG_RX_STATUS: begin +            s_ctrlport_resp_data[CMD_FIFO_SPACE_POS+:CMD_FIFO_SPACE_LEN]  +                                <= cmd_fifo_space[CMD_FIFO_SPACE_LEN-1:0]; +            s_ctrlport_resp_ack <= 1; +          end +          REG_RX_CMD: begin +            s_ctrlport_resp_data[RX_CMD_LEN-1:0]   <= reg_cmd_word; +            s_ctrlport_resp_data[RX_CMD_TIMED_POS] <= reg_cmd_timed; +            s_ctrlport_resp_ack                    <= 1; +          end +          REG_RX_CMD_NUM_WORDS_LO: begin +            s_ctrlport_resp_data <= reg_cmd_num_words[31:0]; +            s_ctrlport_resp_ack  <= 1; +          end +          REG_RX_CMD_NUM_WORDS_HI: begin +            s_ctrlport_resp_data[NUM_WORDS_LEN-32-1:0] <= reg_cmd_num_words[NUM_WORDS_LEN-1:32]; +            s_ctrlport_resp_ack                        <= 1; +          end +          REG_RX_CMD_TIME_LO: begin +            s_ctrlport_resp_data <= reg_cmd_time[31:0]; +            s_ctrlport_resp_ack  <= 1; +          end +          REG_RX_CMD_TIME_HI: begin +            s_ctrlport_resp_data <= reg_cmd_time[63:32]; +            s_ctrlport_resp_ack  <= 1; +          end +          REG_RX_MAX_WORDS_PER_PKT: begin +            s_ctrlport_resp_data <= reg_max_pkt_len; +            s_ctrlport_resp_ack  <= 1; +          end +          REG_RX_ERR_PORT: begin +            s_ctrlport_resp_data[9:0] <= reg_error_portid; +            s_ctrlport_resp_ack       <= 1; +          end +          REG_RX_ERR_REM_PORT: begin +            s_ctrlport_resp_data[9:0] <= reg_error_rem_portid; +            s_ctrlport_resp_ack       <= 1; +          end +          REG_RX_ERR_REM_EPID: begin +            s_ctrlport_resp_data[15:0] <= reg_error_rem_epid; +            s_ctrlport_resp_ack        <= 1; +          end +          REG_RX_ERR_ADDR: begin +            s_ctrlport_resp_data[19:0] <= reg_error_addr; +            s_ctrlport_resp_ack        <= 1; +          end +          REG_RX_DATA: begin +            s_ctrlport_resp_data <= radio_rx_data; +            s_ctrlport_resp_ack  <= 1; +          end +          REG_RX_HAS_TIME: begin +            s_ctrlport_resp_data[0] <= reg_has_time; +            s_ctrlport_resp_ack     <= 1; +          end +        endcase +      end + +    end +  end + + +  //--------------------------------------------------------------------------- +  // Command Queue +  //--------------------------------------------------------------------------- + +  wire [             63:0] cmd_time;        // Time for next start of command +  wire                     cmd_timed;       // Command is timed (use cmd_time) +  wire [NUM_WORDS_LEN-1:0] cmd_num_words;   // Number of words for next command +  wire                     cmd_continuous;  // Command is continuous (ignore cmd_num_words) +  wire                     cmd_valid;       // cmd_* is a valid command +  wire                     cmd_done;        // Command has completed and can be popped from FIFO + +  axi_fifo #( +    .WIDTH (64 + 1 + NUM_WORDS_LEN + 1), +    .SIZE  (5)      // Ideally, this size will lead to an SRL-based FIFO +  ) cmd_fifo ( +    .clk      (radio_clk), +    .reset    (radio_rst), +    .clear    (clear_fifo), +    .i_tdata  ({ reg_cmd_time, reg_cmd_timed, reg_cmd_num_words, (reg_cmd_word == RX_CMD_CONTINUOUS) }), +    .i_tvalid (reg_cmd_valid), +    .i_tready (), +    .o_tdata  ({ cmd_time, cmd_timed, cmd_num_words, cmd_continuous }), +    .o_tvalid (cmd_valid), +    .o_tready (cmd_done), +    .space    (cmd_fifo_space), +    .occupied () +  ); + + +  //--------------------------------------------------------------------------- +  // Receiver State Machine +  //--------------------------------------------------------------------------- + +  // FSM state values +  localparam ST_IDLE               = 0; +  localparam ST_TIME_CHECK         = 1; +  localparam ST_RUNNING            = 2; +  localparam ST_STOP               = 3; +  localparam ST_REPORT_ERR         = 4; +  localparam ST_REPORT_ERR_WAIT    = 5; + +  reg [              2:0] state   = ST_IDLE; // Current state +  reg [NUM_WORDS_LEN-1:0] words_left;        // Words left in current command +  reg [             31:0] words_left_pkt;    // Words left in current packet +  reg                     first_word = 1'b1; // Next word is first in packet +  reg [             15:0] seq_num = 0;       // Sequence number (packet count) +  reg [             63:0] error_time;        // Time at which overflow occurred +  reg [ERR_RX_CODE_W-1:0] error_code;        // Error code register + +  // Output FIFO signals +  wire [           15:0] out_fifo_space; +  reg  [SAMP_W*NSPC-1:0] out_fifo_tdata; +  reg                    out_fifo_tlast; +  reg                    out_fifo_tvalid = 1'b0; +  reg  [           63:0] out_fifo_timestamp; +  reg                    out_fifo_teob; +  reg                    out_fifo_almost_full; + +  reg [63:0] radio_time_low_samp, radio_time_hi_samp; +  reg        time_now, time_past; + +  // All ctrlport requests have a time +  assign m_ctrlport_req_has_time = 1'b1; + +  // Acknowledge STOP requests and pop the command FIFO in the STOP state +  assign cmd_stop_ack = (state == ST_STOP); +  assign cmd_done     = (state == ST_STOP); + +  always @(posedge radio_clk) begin +    if (radio_rst) begin +      state             <= ST_IDLE; +      out_fifo_tvalid   <= 1'b0; +      seq_num           <=  'd0; +      m_ctrlport_req_wr <= 1'b0; +      first_word        <= 1'b1; +    end else begin +      // Default assignments +      out_fifo_tvalid         <= 1'b0; +      out_fifo_tlast          <= 1'b0; +      out_fifo_teob           <= 1'b0; +      m_ctrlport_req_wr       <= 1'b0; + +      if (radio_rx_stb) begin +        // Get the time for the low sample and the high sample of the radio +        // word (needed when NISPC > 1). Compensate for the delay required to +        // check the time by adding 3 clock cycles worth of samples. +        radio_time_low_samp <= (radio_time + 3*NSPC); +        radio_time_hi_samp  <= (radio_time + 3*NSPC + (NSPC-1)); + +        // Register the time comparisons so they don't become the critical path +        time_now  <= (cmd_time >= radio_time_low_samp && +                      cmd_time <= radio_time_hi_samp); +        time_past <= (cmd_time <  radio_time_low_samp); +      end + +      case (state) +        ST_IDLE : begin +          // Wait for a new command to arrive and allow a cycle for the time  +          // comparisons to update. +          if (cmd_valid && radio_rx_stb) begin +            state <= ST_TIME_CHECK; +          end else if (cmd_stop) begin +            state <= ST_STOP; +          end +          first_word <= 1'b1; +        end + +        ST_TIME_CHECK : begin +          if (cmd_stop) begin +            // Nothing to do but stop (timed STOP commands are not supported) +            state <= ST_STOP; +          end else if (cmd_timed && time_past && radio_rx_stb) begin +            // Got this command later than its execution time +            //synthesis translate_off +            $display("WARNING: radio_rx_core: Late command error"); +            //synthesis translate_on +            error_code <= ERR_RX_LATE_CMD; +            error_time <= radio_time; +            state      <= ST_REPORT_ERR; +          end else if (!cmd_timed || (time_now && radio_rx_stb)) begin +            // Either it's time to run this command or it should run +            // immediately. +            words_left     <= cmd_num_words; +            words_left_pkt <= reg_max_pkt_len; +            state          <= ST_RUNNING; +          end +        end + +        ST_RUNNING : begin +          if (radio_rx_stb) begin +            // Output the next word +            out_fifo_tvalid    <= 1'b1; +            out_fifo_tdata     <= radio_rx_data; +            if (first_word) begin +              out_fifo_timestamp <= radio_time; +              first_word         <= 1'b0; +            end + +            // Update word counters +            words_left     <= words_left - 1; +            words_left_pkt <= words_left_pkt - 1; + +            if ((words_left == 1 && !cmd_continuous) || cmd_stop) begin +              // This command has finished, or we've been asked to stop. +              state          <= ST_STOP; +              out_fifo_tlast <= 1'b1; +              out_fifo_teob  <= 1'b1; +              first_word     <= 1'b1; +            end else if (words_left_pkt == 1) begin +              // We've finished building a packet +              seq_num        <= seq_num + 1; +              words_left_pkt <= reg_max_pkt_len; +              out_fifo_tlast <= 1'b1; +              first_word     <= 1'b1; +            end + +            // Check for overflow. Note that we've left enough room in the +            // output FIFO so that we can end the packet cleanly. +            if (out_fifo_almost_full) begin +              // End the command and terminate packet early +              //synthesis translate_off +              $display("WARNING: radio_rx_core: Overrun error"); +              //synthesis translate_on +              out_fifo_tlast <= 1'b1; +              out_fifo_teob  <= 1'b1; +              seq_num        <= seq_num + 1; +              error_time     <= radio_time; +              error_code     <= ERR_RX_OVERRUN; +              state          <= ST_REPORT_ERR; +            end + +          end +        end + +        ST_STOP : begin +          // This single-cycle state allows time for STOP to be acknowledged  +          // and for the command FIFO to be popped. +          state <= ST_IDLE; +        end + +        ST_REPORT_ERR : begin +          // Setup write of error code +          m_ctrlport_req_wr                      <= 1'b1; +          m_ctrlport_req_data                    <= 0; +          m_ctrlport_req_data[ERR_RX_CODE_W-1:0] <= error_code; +          m_ctrlport_req_addr                    <= reg_error_addr; +          m_ctrlport_req_time                    <= error_time; +          state                                  <= ST_REPORT_ERR_WAIT; +        end + +        ST_REPORT_ERR_WAIT : begin +          // Wait for write of error code and timestamp to complete +          if (m_ctrlport_resp_ack) begin +            state <= ST_STOP; +          end +        end + +        default : state <= ST_IDLE; +      endcase +    end +  end + + +  assign radio_rx_running = (state == ST_RUNNING);  // We're actively acquiring + +  // Directly connect the port ID, remote port ID, and remote EPID since they  +  // are only used for error reporting. +  assign m_ctrlport_req_portid     = reg_error_portid; +  assign m_ctrlport_req_rem_epid   = reg_error_rem_epid; +  assign m_ctrlport_req_rem_portid = reg_error_rem_portid; + + +  //--------------------------------------------------------------------------- +  // Output FIFO +  //--------------------------------------------------------------------------- +  // +  // Here we buffer output samples and monitor FIFO fullness to be able to +  // detect overflows. +  // +  //--------------------------------------------------------------------------- + +  axi_fifo #( +    .WIDTH (1+64+1+SAMP_W*NSPC), +    .SIZE  (5)      // Ideally, this size will lead to an SRL-based FIFO +  ) output_fifo ( +    .clk      (radio_clk), +    .reset    (radio_rst), +    .clear    (1'b0), +    .i_tdata  ({out_fifo_teob, out_fifo_timestamp, out_fifo_tlast, out_fifo_tdata}), +    .i_tvalid (out_fifo_tvalid), +    .i_tready (), +    .o_tdata  ({m_axis_teob, m_axis_ttimestamp, m_axis_tlast, m_axis_tdata}), +    .o_tvalid (m_axis_tvalid), +    .o_tready (m_axis_tready), +    .space    (out_fifo_space), +    .occupied () +  ); + +  // Create a register to indicate if the output FIFO is about to overflow +  always @(posedge radio_clk) begin +    if (radio_rst) begin +      out_fifo_almost_full <= 1'b0; +    end else begin +      out_fifo_almost_full <= (out_fifo_space < 5); +    end +  end + + +endmodule + + +`default_nettype wire diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/radio_tx_core.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/radio_tx_core.v new file mode 100644 index 000000000..d40db5122 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/radio_tx_core.v @@ -0,0 +1,417 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: radio_tx_core +// +// Description: +// +// This module contains the core Tx radio data-path logic. It receives samples  +// over AXI-Stream that it then sends to the radio interface coincident with a  +// strobe signal that must be provided by the radio interface. +// +// There are no registers for starting or stopping the transmitter. It is  +// operated simply by providing data packets via its AXI-Stream data interface.  +// The end-of-burst (EOB) signal is used to indicate when the transmitter is  +// allowed to stop transmitting. Packet timestamps can be used to indicate when  +// transmission should start. +// +// Care must be taken to provide data to the transmitter at a rate that is  +// faster than the radio needs it so that underflows do not occur. Similarly,  +// timed packets must be delivered before the timestamp expires. If a packet  +// arrives late, then it will be dropped and the error will be reported via the  +// CTRL port interface. +// +// Parameters: +// +//   SAMP_W : Width of a radio sample +//   NSPC   : Number of radio samples per radio clock cycle +// + + +module radio_tx_core #( +  parameter SAMP_W = 32, +  parameter NSPC   = 1 +) ( +  input wire radio_clk, +  input wire radio_rst, + + +  //--------------------------------------------------------------------------- +  // Control Interface +  //--------------------------------------------------------------------------- + +  // Slave (Register Reads and Writes) +  input  wire        s_ctrlport_req_wr, +  input  wire        s_ctrlport_req_rd, +  input  wire [19:0] s_ctrlport_req_addr, +  input  wire [31:0] s_ctrlport_req_data, +  output reg         s_ctrlport_resp_ack  = 1'b0, +  output reg  [31:0] s_ctrlport_resp_data, + +  // Master (Error Reporting) +  output reg         m_ctrlport_req_wr = 1'b0, +  output reg  [19:0] m_ctrlport_req_addr, +  output reg  [31:0] m_ctrlport_req_data, +  output wire        m_ctrlport_req_has_time, +  output reg  [63:0] m_ctrlport_req_time, +  output wire [ 9:0] m_ctrlport_req_portid, +  output wire [15:0] m_ctrlport_req_rem_epid, +  output wire [ 9:0] m_ctrlport_req_rem_portid, +  input  wire        m_ctrlport_resp_ack, + + +  //--------------------------------------------------------------------------- +  // Radio Interface +  //--------------------------------------------------------------------------- + +  input wire [63:0] radio_time, + +  output wire [SAMP_W*NSPC-1:0] radio_tx_data, +  input  wire                   radio_tx_stb, + +  // Status indicator (true when transmitting) +  output wire radio_tx_running, + + +  //--------------------------------------------------------------------------- +  // AXI-Stream Data Input +  //--------------------------------------------------------------------------- + +  input  wire [SAMP_W*NSPC-1:0] s_axis_tdata, +  input  wire                   s_axis_tlast, +  input  wire                   s_axis_tvalid, +  output wire                   s_axis_tready, +  // Sideband info +  input  wire [           63:0] s_axis_ttimestamp, +  input  wire                   s_axis_thas_time, +  input  wire                   s_axis_teob +); + +  `include "rfnoc_block_radio_regs.vh" +  `include "../../core/rfnoc_chdr_utils.vh" + + +  //--------------------------------------------------------------------------- +  // Register Read/Write Logic +  //--------------------------------------------------------------------------- + +  reg [SAMP_W-1:0] reg_idle_value       = 0; // Value to output when transmitter is idle +  reg [       9:0] reg_error_portid     = 0; // Port ID to use for error reporting +  reg [      15:0] reg_error_rem_epid   = 0; // Remote EPID to use for error reporting +  reg [       9:0] reg_error_rem_portid = 0; // Remote port ID to use for error reporting +  reg [      19:0] reg_error_addr       = 0; // Address to use for error reporting + +  reg [TX_ERR_POLICY_LEN-1:0] reg_policy = TX_ERR_POLICY_PACKET; + +  always @(posedge radio_clk) begin +    if (radio_rst) begin +      s_ctrlport_resp_ack  <= 0; +      reg_idle_value       <= 0; +      reg_error_portid     <= 0; +      reg_error_rem_epid   <= 0; +      reg_error_rem_portid <= 0; +      reg_error_addr       <= 0; +      reg_policy           <= TX_ERR_POLICY_PACKET; +    end else begin +      // Default assignments +      s_ctrlport_resp_ack  <= 0; +      s_ctrlport_resp_data <= 0; + +      // Handle register writes +      if (s_ctrlport_req_wr) begin +        case (s_ctrlport_req_addr) +          REG_TX_IDLE_VALUE: begin +            reg_idle_value      <= s_ctrlport_req_data[SAMP_W-1:0]; +            s_ctrlport_resp_ack <= 1; +          end +          REG_TX_ERROR_POLICY: begin +            // Only allow valid configurations +            case (s_ctrlport_req_data[TX_ERR_POLICY_LEN-1:0]) +              TX_ERR_POLICY_PACKET : reg_policy <= TX_ERR_POLICY_PACKET; +              TX_ERR_POLICY_BURST  : reg_policy <= TX_ERR_POLICY_BURST; +              default              : reg_policy <= TX_ERR_POLICY_PACKET; +            endcase +            s_ctrlport_resp_ack <= 1; +          end +          REG_TX_ERR_PORT: begin +            reg_error_portid    <= s_ctrlport_req_data[9:0]; +            s_ctrlport_resp_ack <= 1; +          end +          REG_TX_ERR_REM_PORT: begin +            reg_error_rem_portid <= s_ctrlport_req_data[9:0]; +            s_ctrlport_resp_ack  <= 1; +          end +          REG_TX_ERR_REM_EPID: begin +            reg_error_rem_epid  <= s_ctrlport_req_data[15:0]; +            s_ctrlport_resp_ack <= 1; +          end +          REG_TX_ERR_ADDR: begin +            reg_error_addr      <= s_ctrlport_req_data[19:0]; +            s_ctrlport_resp_ack <= 1; +          end +        endcase +      end + +      // Handle register reads +      if (s_ctrlport_req_rd) begin +        case (s_ctrlport_req_addr) +          REG_TX_IDLE_VALUE: begin +            s_ctrlport_resp_data[SAMP_W-1:0] <= reg_idle_value; +            s_ctrlport_resp_ack              <= 1; +          end +          REG_TX_ERROR_POLICY: begin +            s_ctrlport_resp_data[TX_ERR_POLICY_LEN-1:0] <= reg_policy; +            s_ctrlport_resp_ack                         <= 1; +          end +          REG_TX_ERR_PORT: begin +            s_ctrlport_resp_data[9:0] <= reg_error_portid; +            s_ctrlport_resp_ack       <= 1; +          end +          REG_TX_ERR_REM_PORT: begin +            s_ctrlport_resp_data[9:0] <= reg_error_rem_portid; +            s_ctrlport_resp_ack       <= 1; +          end +          REG_TX_ERR_REM_EPID: begin +            s_ctrlport_resp_data[15:0] <= reg_error_rem_epid; +            s_ctrlport_resp_ack        <= 1; +          end +          REG_TX_ERR_ADDR: begin +            s_ctrlport_resp_data[19:0] <= reg_error_addr; +            s_ctrlport_resp_ack        <= 1; +          end +        endcase +      end +    end +  end + + +  //--------------------------------------------------------------------------- +  // Transmitter State Machine +  //--------------------------------------------------------------------------- + +  // FSM state values +  localparam ST_IDLE        = 0; +  localparam ST_TIME_CHECK  = 1; +  localparam ST_TRANSMIT    = 2; +  localparam ST_POLICY_WAIT = 3; + +  reg [1:0] state = ST_IDLE; + +  reg sop = 1'b1;  // Start of packet + +  reg [ERR_TX_CODE_W-1:0] new_error_code; +  reg [             63:0] new_error_time; +  reg                     new_error_valid = 1'b0; + +  reg time_now, time_past; + + +  always @(posedge radio_clk) begin +    if (radio_rst) begin +      state           <= ST_IDLE; +      sop             <= 1'b1; +      new_error_valid <= 1'b0; +    end else begin +      new_error_valid <= 1'b0; + +      // Register time comparisons so they don't become the critical path +      time_now  <= (radio_time == s_axis_ttimestamp); +      time_past <= (radio_time >  s_axis_ttimestamp); + +      // Track if the next word will be the start of a packet (sop) +      if (s_axis_tvalid && s_axis_tready) begin +        sop <= s_axis_tlast; +      end + +      case (state) +        ST_IDLE : begin +          // Wait for a new packet to arrive and allow a cycle for the time  +          // comparisons to update. +          if (s_axis_tvalid) begin +            state <= ST_TIME_CHECK; +          end +        end + +        ST_TIME_CHECK : begin +          if (!s_axis_thas_time || time_now) begin +            // We have a new packet without a timestamp, or a new packet +            // whose time has arrived. +            state <= ST_TRANSMIT; +          end else if (time_past) begin +            // We have a new packet with a timestamp, but the time has passed. +            //synthesis translate off +            $display("WARNING: radio_tx_core: Late data error"); +            //synthesis translate_on +            new_error_code  <= ERR_TX_LATE_DATA; +            new_error_time  <= radio_time; +            new_error_valid <= 1'b1; +            state           <= ST_POLICY_WAIT; +          end +        end + +        ST_TRANSMIT : begin +          if (radio_tx_stb) begin +            if (!s_axis_tvalid) begin +              // The radio strobed for new data but we don't have any to give +              //synthesis translate off +              $display("WARNING: radio_tx_core: Underrun error"); +              //synthesis translate_on +              new_error_code  <= ERR_TX_UNDERRUN; +              new_error_time  <= radio_time; +              new_error_valid <= 1'b1; +              state           <= ST_POLICY_WAIT; +            end else if (s_axis_tlast && s_axis_teob) begin +              // We're done with this burst of packets, so acknowledge EOB and +              // go back to idle. +              new_error_code  <= ERR_TX_EOB_ACK; +              new_error_time  <= radio_time; +              new_error_valid <= 1'b1; +              state <= ST_IDLE; +            end +          end +        end + +        ST_POLICY_WAIT : begin +          // If we came here from ST_TIME_CHECK or ST_TRANSMIT and we're in the +          // middle of a packet then we just wait until we reach the end of the +          // packet. +          if (s_axis_tvalid && s_axis_tlast) begin +            // We're either at the end of a packet or between packets +            if (reg_policy == TX_ERR_POLICY_PACKET || +               (reg_policy == TX_ERR_POLICY_BURST  && s_axis_teob)) begin +              state <= ST_IDLE; +            end + +          // If we came from ST_TRANSMIT and we happen to already be between +          // packets (i.e., we underflowed while waiting for the next packet). +          end else if (!s_axis_tvalid && sop) begin +            if (reg_policy == TX_ERR_POLICY_PACKET) state <= ST_IDLE; +          end +        end + +        default : state <= ST_IDLE; +      endcase +    end +  end + + +  // Output the current sample whenever we're transmitting and the sample is +  // valid. Otherwise, output the idle value. +  assign radio_tx_data = (s_axis_tvalid && state == ST_TRANSMIT) ? +                         s_axis_tdata : +                         {NSPC{reg_idle_value[SAMP_W-1:0]}}; + +  // Read packet in the transmit state or dump it in the error state +  assign s_axis_tready = (radio_tx_stb && (state == ST_TRANSMIT)) || +                         (state == ST_POLICY_WAIT); + +  // Indicate whether Tx interface is actively transmitting +  assign radio_tx_running = (state == ST_TRANSMIT); + + +  //--------------------------------------------------------------------------- +  // Error FIFO +  //--------------------------------------------------------------------------- +  // +  // This FIFO queues up errors in case we get multiple errors in a row faster +  // than they can be reported. If the FIFO fills then new errors will be +  // ignored. +  // +  //--------------------------------------------------------------------------- + +  // Error information +  wire [ERR_TX_CODE_W-1:0] next_error_code; +  wire [             63:0] next_error_time; +  wire                     next_error_valid; +  reg                      next_error_ready = 1'b0; + +  wire new_error_ready; + +  axi_fifo_short #( +    .WIDTH (64 + ERR_TX_CODE_W) +  ) error_fifo ( +    .clk      (radio_clk), +    .reset    (radio_rst), +    .clear    (1'b0), +    .i_tdata  ({new_error_time, new_error_code}), +    .i_tvalid (new_error_valid & new_error_ready),   // Mask with ready to prevent FIFO corruption +    .i_tready (new_error_ready), +    .o_tdata  ({next_error_time, next_error_code}), +    .o_tvalid (next_error_valid), +    .o_tready (next_error_ready), +    .space    (), +    .occupied () +  ); + +  //synthesis translate_off +  // Output a message if the error FIFO overflows +  always @(posedge radio_clk) begin +    if (new_error_valid && !new_error_ready) begin +      $display("WARNING: Tx error report dropped!"); +    end +  end +  //synthesis translate_on + + +  //--------------------------------------------------------------------------- +  // Error Reporting State Machine +  //--------------------------------------------------------------------------- +  // +  // This state machine reports errors that have been queued up in the error +  // FIFO. +  // +  //--------------------------------------------------------------------------- + +  localparam ST_ERR_IDLE     = 0; +  localparam ST_ERR_CODE     = 1; + +  reg [0:0] err_state = ST_ERR_IDLE; + +  // All ctrlport requests have a time +  assign m_ctrlport_req_has_time = 1'b1; + +  always @(posedge radio_clk) begin +    if (radio_rst) begin +      m_ctrlport_req_wr <= 1'b0; +      err_state         <= ST_ERR_IDLE; +      next_error_ready  <= 1'b0; +    end else begin +      m_ctrlport_req_wr       <= 1'b0; +      next_error_ready        <= 1'b0; + +      case (err_state) +        ST_ERR_IDLE : begin +          if (next_error_valid) begin +            // Setup write of error code +            m_ctrlport_req_wr       <= 1'b1; +            m_ctrlport_req_addr     <= reg_error_addr; +            m_ctrlport_req_data     <= {{(32-ERR_TX_CODE_W){1'b0}}, next_error_code}; +            m_ctrlport_req_time     <= next_error_time; +            next_error_ready        <= 1'b1; +            err_state               <= ST_ERR_CODE; +          end +        end + +        ST_ERR_CODE : begin +          // Wait for write of error code and timestamp +          if (m_ctrlport_resp_ack) begin +            err_state <= ST_ERR_IDLE; +          end +        end + +        default : err_state <= ST_ERR_IDLE; +      endcase +    end +  end + + +  // Directly connect the port ID, remote port ID, remote EPID since they are  +  // only used for error reporting. +  assign m_ctrlport_req_portid     = reg_error_portid; +  assign m_ctrlport_req_rem_epid   = reg_error_rem_epid; +  assign m_ctrlport_req_rem_portid = reg_error_rem_portid; + + +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/rfnoc_block_radio.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/rfnoc_block_radio.v new file mode 100644 index 000000000..a97b141c0 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/rfnoc_block_radio.v @@ -0,0 +1,546 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_radio +// +// Description:  This is the top-level file for the RFNoC radio block. +// +// Parameters: +// +//   THIS_PORTID      : CTRL port ID to which this block is connected +//   CHDR_W           : CHDR AXI-Stream data bus width +//   NIPC             : Number of radio samples per radio clock cycle +//   ITEM_W           : Radio sample width +//   NUM_PORTS        : Number of radio channels (RX/TX pairs) +//   MTU              : Maximum transmission unit (i.e., maximum packet size)  +//                      in CHDR words is 2**MTU. +//   CTRL_FIFO_SIZE   : Size of the Control Port slave FIFO. This affects the +//                      number of outstanding commands that can be pending. +//   PERIPH_BASE_ADDR : CTRL port peripheral window base address +//   PERIPH_ADDR_W    : CTRL port peripheral address space = 2**PERIPH_ADDR_W +// + + +module rfnoc_block_radio #( +  parameter THIS_PORTID      = 0, +  parameter CHDR_W           = 64, +  parameter NIPC             = 1, +  parameter ITEM_W           = 32, +  parameter NUM_PORTS        = 2, +  parameter MTU              = 10, +  parameter CTRL_FIFO_SIZE   = 9, +  parameter PERIPH_BASE_ADDR = 20'h80000, +  parameter PERIPH_ADDR_W    = 19 +) ( +  //--------------------------------------------------------------------------- +  // AXIS CHDR Port +  //--------------------------------------------------------------------------- + +  input wire rfnoc_chdr_clk, + +  // CHDR inputs from framework +  input  wire [CHDR_W*NUM_PORTS-1:0] s_rfnoc_chdr_tdata, +  input  wire [       NUM_PORTS-1:0] s_rfnoc_chdr_tlast, +  input  wire [       NUM_PORTS-1:0] s_rfnoc_chdr_tvalid, +  output wire [       NUM_PORTS-1:0] s_rfnoc_chdr_tready, + +  // CHDR outputs to framework +  output wire [CHDR_W*NUM_PORTS-1:0] m_rfnoc_chdr_tdata, +  output wire [       NUM_PORTS-1:0] m_rfnoc_chdr_tlast, +  output wire [       NUM_PORTS-1:0] m_rfnoc_chdr_tvalid, +  input  wire [       NUM_PORTS-1:0] m_rfnoc_chdr_tready, + +  // Backend interface +  input  wire [511:0] rfnoc_core_config, +  output wire [511:0] rfnoc_core_status, + + +  //--------------------------------------------------------------------------- +  // AXIS CTRL Port +  //--------------------------------------------------------------------------- + +  input wire rfnoc_ctrl_clk, + +  // CTRL port requests from framework +  input  wire [31:0] s_rfnoc_ctrl_tdata, +  input  wire        s_rfnoc_ctrl_tlast, +  input  wire        s_rfnoc_ctrl_tvalid, +  output wire        s_rfnoc_ctrl_tready, + +  // CTRL port requests to framework +  output wire [31:0] m_rfnoc_ctrl_tdata, +  output wire        m_rfnoc_ctrl_tlast, +  output wire        m_rfnoc_ctrl_tvalid, +  input  wire        m_rfnoc_ctrl_tready, + + +  //--------------------------------------------------------------------------- +  // CTRL Port Peripheral Interface +  //--------------------------------------------------------------------------- + +  output wire        m_ctrlport_req_wr, +  output wire        m_ctrlport_req_rd, +  output wire [19:0] m_ctrlport_req_addr, +  output wire [31:0] m_ctrlport_req_data, +  output wire [ 3:0] m_ctrlport_req_byte_en, +  output wire        m_ctrlport_req_has_time, +  output wire [63:0] m_ctrlport_req_time, +  input  wire        m_ctrlport_resp_ack, +  input  wire [ 1:0] m_ctrlport_resp_status, +  input  wire [31:0] m_ctrlport_resp_data, + + +  //--------------------------------------------------------------------------- +  // Radio Interface +  //--------------------------------------------------------------------------- + +  input wire radio_clk, + +  // Timekeeper interface +  input wire [63:0] radio_time, + +  // Radio Rx interface +  input  wire [(ITEM_W*NIPC)*NUM_PORTS-1:0] radio_rx_data, +  input  wire [              NUM_PORTS-1:0] radio_rx_stb, +  output wire [              NUM_PORTS-1:0] radio_rx_running, + +  // Radio Tx interface +  output wire [(ITEM_W*NIPC)*NUM_PORTS-1:0] radio_tx_data, +  input  wire [              NUM_PORTS-1:0] radio_tx_stb, +  output wire [              NUM_PORTS-1:0] radio_tx_running +); + +  `include "rfnoc_block_radio_regs.vh" +  `include "../../core/rfnoc_axis_ctrl_utils.vh" + +  localparam NOC_ID  = 32'h12AD1000; +  localparam RADIO_W = NIPC*ITEM_W; + + +  // Radio Tx data stream +  wire [RADIO_W*NUM_PORTS-1:0] axis_tx_tdata; +  wire [        NUM_PORTS-1:0] axis_tx_tlast; +  wire [        NUM_PORTS-1:0] axis_tx_tvalid; +  wire [        NUM_PORTS-1:0] axis_tx_tready; +  wire [     64*NUM_PORTS-1:0] axis_tx_ttimestamp; +  wire [        NUM_PORTS-1:0] axis_tx_thas_time; +  wire [        NUM_PORTS-1:0] axis_tx_teob; + +  // Radio Rx data stream +  wire [RADIO_W*NUM_PORTS-1:0] axis_rx_tdata; +  wire [        NUM_PORTS-1:0] axis_rx_tlast; +  wire [        NUM_PORTS-1:0] axis_rx_tvalid; +  wire [        NUM_PORTS-1:0] axis_rx_tready; +  wire [     64*NUM_PORTS-1:0] axis_rx_ttimestamp; +  wire [        NUM_PORTS-1:0] axis_rx_thas_time; +  wire [        NUM_PORTS-1:0] axis_rx_teob; + +  // Control port signals used for register access (NoC shell masters user logic) +  wire        ctrlport_reg_req_wr; +  wire        ctrlport_reg_req_rd; +  wire [19:0] ctrlport_reg_req_addr; +  wire        ctrlport_reg_has_time; +  wire [63:0] ctrlport_reg_time; +  wire [31:0] ctrlport_reg_req_data; +  wire [31:0] ctrlport_reg_resp_data; +  wire        ctrlport_reg_resp_ack; + +  // Control port signals used for error reporting (user logic masters to NoC shell) +  wire        ctrlport_err_req_wr; +  wire [19:0] ctrlport_err_req_addr; +  wire [ 9:0] ctrlport_err_req_portid; +  wire [15:0] ctrlport_err_req_rem_epid; +  wire [ 9:0] ctrlport_err_req_rem_portid; +  wire [31:0] ctrlport_err_req_data; +  wire        ctrlport_err_req_has_time; +  wire [63:0] ctrlport_err_req_time; +  wire        ctrlport_err_resp_ack; + + +  //--------------------------------------------------------------------------- +  // NoC Shell +  //--------------------------------------------------------------------------- + +  wire rfnoc_chdr_rst; +  wire radio_rst; + +  noc_shell_radio #( +    .NOC_ID          (NOC_ID), +    .THIS_PORTID     (THIS_PORTID), +    .CHDR_W          (CHDR_W), +    .CTRLPORT_SLV_EN (1), +    .CTRLPORT_MST_EN (1), +    .CTRL_FIFO_SIZE  (CTRL_FIFO_SIZE), +    .NUM_DATA_I      (NUM_PORTS), +    .NUM_DATA_O      (NUM_PORTS), +    .ITEM_W          (ITEM_W), +    .NIPC            (NIPC), +    .PYLD_FIFO_SIZE  (MTU), +    .MTU             (MTU) +  ) noc_shell_radio_i ( +    .rfnoc_chdr_clk            (rfnoc_chdr_clk), +    .rfnoc_chdr_rst            (rfnoc_chdr_rst), +    .rfnoc_ctrl_clk            (rfnoc_ctrl_clk), +    .rfnoc_ctrl_rst            (), +    .rfnoc_core_config         (rfnoc_core_config), +    .rfnoc_core_status         (rfnoc_core_status), +    .s_rfnoc_chdr_tdata        (s_rfnoc_chdr_tdata), +    .s_rfnoc_chdr_tlast        (s_rfnoc_chdr_tlast), +    .s_rfnoc_chdr_tvalid       (s_rfnoc_chdr_tvalid), +    .s_rfnoc_chdr_tready       (s_rfnoc_chdr_tready), +    .m_rfnoc_chdr_tdata        (m_rfnoc_chdr_tdata), +    .m_rfnoc_chdr_tlast        (m_rfnoc_chdr_tlast), +    .m_rfnoc_chdr_tvalid       (m_rfnoc_chdr_tvalid), +    .m_rfnoc_chdr_tready       (m_rfnoc_chdr_tready), +    .s_rfnoc_ctrl_tdata        (s_rfnoc_ctrl_tdata), +    .s_rfnoc_ctrl_tlast        (s_rfnoc_ctrl_tlast), +    .s_rfnoc_ctrl_tvalid       (s_rfnoc_ctrl_tvalid), +    .s_rfnoc_ctrl_tready       (s_rfnoc_ctrl_tready), +    .m_rfnoc_ctrl_tdata        (m_rfnoc_ctrl_tdata), +    .m_rfnoc_ctrl_tlast        (m_rfnoc_ctrl_tlast), +    .m_rfnoc_ctrl_tvalid       (m_rfnoc_ctrl_tvalid), +    .m_rfnoc_ctrl_tready       (m_rfnoc_ctrl_tready), +    .ctrlport_clk              (radio_clk), +    .ctrlport_rst              (radio_rst), +    .m_ctrlport_req_wr         (ctrlport_reg_req_wr), +    .m_ctrlport_req_rd         (ctrlport_reg_req_rd), +    .m_ctrlport_req_addr       (ctrlport_reg_req_addr), +    .m_ctrlport_req_data       (ctrlport_reg_req_data), +    .m_ctrlport_req_byte_en    (), +    .m_ctrlport_req_has_time   (ctrlport_reg_has_time), +    .m_ctrlport_req_time       (ctrlport_reg_time), +    .m_ctrlport_resp_ack       (ctrlport_reg_resp_ack), +    .m_ctrlport_resp_status    (AXIS_CTRL_STS_OKAY), +    .m_ctrlport_resp_data      (ctrlport_reg_resp_data), +    .s_ctrlport_req_wr         (ctrlport_err_req_wr), +    .s_ctrlport_req_rd         (1'b0), +    .s_ctrlport_req_addr       (ctrlport_err_req_addr), +    .s_ctrlport_req_portid     (ctrlport_err_req_portid), +    .s_ctrlport_req_rem_epid   (ctrlport_err_req_rem_epid), +    .s_ctrlport_req_rem_portid (ctrlport_err_req_rem_portid), +    .s_ctrlport_req_data       (ctrlport_err_req_data), +    .s_ctrlport_req_byte_en    (4'hF), +    .s_ctrlport_req_has_time   (ctrlport_err_req_has_time), +    .s_ctrlport_req_time       (ctrlport_err_req_time), +    .s_ctrlport_resp_ack       (ctrlport_err_resp_ack), +    .s_ctrlport_resp_status    (), +    .s_ctrlport_resp_data      (), +    .axis_data_clk             (radio_clk), +    .axis_data_rst             (radio_rst), +    .m_axis_tdata              (axis_tx_tdata), +    .m_axis_tkeep              (),                          // Radio only transmits full words +    .m_axis_tlast              (axis_tx_tlast), +    .m_axis_tvalid             (axis_tx_tvalid), +    .m_axis_tready             (axis_tx_tready), +    .m_axis_ttimestamp         (axis_tx_ttimestamp), +    .m_axis_thas_time          (axis_tx_thas_time), +    .m_axis_teov               (), +    .m_axis_teob               (axis_tx_teob), +    .s_axis_tdata              (axis_rx_tdata), +    .s_axis_tkeep              ({NUM_PORTS*NIPC{1'b1}}), // Radio only receives full words +    .s_axis_tlast              (axis_rx_tlast), +    .s_axis_tvalid             (axis_rx_tvalid), +    .s_axis_tready             (axis_rx_tready), +    .s_axis_ttimestamp         (axis_rx_ttimestamp), +    .s_axis_thas_time          (axis_rx_thas_time), +    .s_axis_teov               ({NUM_PORTS{1'b0}}), +    .s_axis_teob               (axis_rx_teob) +  ); + +  // Cross the CHDR reset to the radio_clk domain +  pulse_synchronizer #( +    .MODE ("POSEDGE") +  ) ctrl_rst_sync_i ( +    .clk_a   (rfnoc_chdr_clk), +    .rst_a   (1'b0), +    .pulse_a (rfnoc_chdr_rst), +    .busy_a  (), +    .clk_b   (radio_clk), +    .pulse_b (radio_rst) +  ); + + +  //--------------------------------------------------------------------------- +  // Decode Control Port Addresses +  //--------------------------------------------------------------------------- +  // +  // This block splits the NoC shell's single master control port interface  +  // into three masters, connected to the shared registers, radio cores, and  +  // the external CTRL port peripheral interface. The responses from each of  +  // these are merged into a single response and sent back to the NoC shell. +  // +  //--------------------------------------------------------------------------- + +  wire        ctrlport_shared_req_wr; +  wire        ctrlport_shared_req_rd; +  wire [19:0] ctrlport_shared_req_addr; +  wire [31:0] ctrlport_shared_req_data; +  wire [ 3:0] ctrlport_shared_req_byte_en; +  wire        ctrlport_shared_req_has_time; +  wire [63:0] ctrlport_shared_req_time; +  reg         ctrlport_shared_resp_ack  = 1'b0; +  reg  [31:0] ctrlport_shared_resp_data = 0; + +  wire        ctrlport_core_req_wr; +  wire        ctrlport_core_req_rd; +  wire [19:0] ctrlport_core_req_addr; +  wire [31:0] ctrlport_core_req_data; +  wire [ 3:0] ctrlport_core_req_byte_en; +  wire        ctrlport_core_req_has_time; +  wire [63:0] ctrlport_core_req_time; +  wire        ctrlport_core_resp_ack; +  wire [31:0] ctrlport_core_resp_data; + +  ctrlport_decoder_param #( +    .NUM_SLAVES (3), +    .PORT_BASE  ({PERIPH_BASE_ADDR, RADIO_BASE_ADDR,                  SHARED_BASE_ADDR}), +    .PORT_ADDR_W({PERIPH_ADDR_W,    RADIO_ADDR_W + $clog2(NUM_PORTS), SHARED_ADDR_W}) +  ) ctrlport_decoder_param_i ( +    .ctrlport_clk            (radio_clk), +    .ctrlport_rst            (radio_rst), +    .s_ctrlport_req_wr       (ctrlport_reg_req_wr), +    .s_ctrlport_req_rd       (ctrlport_reg_req_rd), +    .s_ctrlport_req_addr     (ctrlport_reg_req_addr), +    .s_ctrlport_req_data     (ctrlport_reg_req_data), +    .s_ctrlport_req_byte_en  (4'b0), +    .s_ctrlport_req_has_time (ctrlport_reg_has_time), +    .s_ctrlport_req_time     (ctrlport_reg_time), +    .s_ctrlport_resp_ack     (ctrlport_reg_resp_ack), +    .s_ctrlport_resp_status  (), +    .s_ctrlport_resp_data    (ctrlport_reg_resp_data), +    .m_ctrlport_req_wr       ({m_ctrlport_req_wr, +                               ctrlport_core_req_wr, +                               ctrlport_shared_req_wr}), +    .m_ctrlport_req_rd       ({m_ctrlport_req_rd, +                               ctrlport_core_req_rd, +                               ctrlport_shared_req_rd}), +    .m_ctrlport_req_addr     ({m_ctrlport_req_addr, +                               ctrlport_core_req_addr, +                               ctrlport_shared_req_addr}), +    .m_ctrlport_req_data     ({m_ctrlport_req_data, +                               ctrlport_core_req_data, +                               ctrlport_shared_req_data}), +    .m_ctrlport_req_byte_en  ({m_ctrlport_req_byte_en, +                               ctrlport_core_req_byte_en, +                               ctrlport_shared_req_byte_en}), +    .m_ctrlport_req_has_time ({m_ctrlport_req_has_time, +                               ctrlport_core_req_has_time, +                               ctrlport_shared_req_has_time}), +    .m_ctrlport_req_time     ({m_ctrlport_req_time, +                               ctrlport_core_req_time, +                               ctrlport_shared_req_time}), +    .m_ctrlport_resp_ack     ({m_ctrlport_resp_ack, +                               ctrlport_core_resp_ack, +                               ctrlport_shared_resp_ack}), +    .m_ctrlport_resp_status  ({m_ctrlport_resp_status, +                              2'b00, +                              2'b00}), +    .m_ctrlport_resp_data    ({m_ctrlport_resp_data, +                               ctrlport_core_resp_data, +                               ctrlport_shared_resp_data +                               }) +  ); + + +  //--------------------------------------------------------------------------- +  // Split Radio Control Port Interfaces +  //--------------------------------------------------------------------------- + +  wire [   NUM_PORTS-1:0] ctrlport_radios_req_wr; +  wire [   NUM_PORTS-1:0] ctrlport_radios_req_rd; +  wire [20*NUM_PORTS-1:0] ctrlport_radios_req_addr; +  wire [32*NUM_PORTS-1:0] ctrlport_radios_req_data; +  wire [   NUM_PORTS-1:0] ctrlport_radios_resp_ack; +  wire [32*NUM_PORTS-1:0] ctrlport_radios_resp_data; + +  ctrlport_decoder #( +    .NUM_SLAVES   (NUM_PORTS), +    .BASE_ADDR    (0), +    .SLAVE_ADDR_W (RADIO_ADDR_W) +  ) ctrlport_decoder_i ( +    .ctrlport_clk            (radio_clk), +    .ctrlport_rst            (radio_rst), +    .s_ctrlport_req_wr       (ctrlport_core_req_wr), +    .s_ctrlport_req_rd       (ctrlport_core_req_rd), +    .s_ctrlport_req_addr     (ctrlport_core_req_addr), +    .s_ctrlport_req_data     (ctrlport_core_req_data), +    .s_ctrlport_req_byte_en  (4'b0), +    .s_ctrlport_req_has_time (1'b0), +    .s_ctrlport_req_time     (64'b0), +    .s_ctrlport_resp_ack     (ctrlport_core_resp_ack), +    .s_ctrlport_resp_status  (), +    .s_ctrlport_resp_data    (ctrlport_core_resp_data), +    .m_ctrlport_req_wr       (ctrlport_radios_req_wr), +    .m_ctrlport_req_rd       (ctrlport_radios_req_rd), +    .m_ctrlport_req_addr     (ctrlport_radios_req_addr), +    .m_ctrlport_req_data     (ctrlport_radios_req_data), +    .m_ctrlport_req_byte_en  (), +    .m_ctrlport_req_has_time (), +    .m_ctrlport_req_time     (), +    .m_ctrlport_resp_ack     (ctrlport_radios_resp_ack), +    .m_ctrlport_resp_status  ({NUM_PORTS{2'b00}}), +    .m_ctrlport_resp_data    (ctrlport_radios_resp_data) +  ); + + +  //--------------------------------------------------------------------------- +  // Merge Control Port Interfaces +  //--------------------------------------------------------------------------- +  // +  // This block merges the master control port interfaces of all radio_cores  +  // into a single master for the NoC shell. +  // +  //--------------------------------------------------------------------------- + +  wire [   NUM_PORTS-1:0] ctrlport_err_radio_req_wr; +  wire [20*NUM_PORTS-1:0] ctrlport_err_radio_req_addr; +  wire [10*NUM_PORTS-1:0] ctrlport_err_radio_req_portid; +  wire [16*NUM_PORTS-1:0] ctrlport_err_radio_req_rem_epid; +  wire [10*NUM_PORTS-1:0] ctrlport_err_radio_req_rem_portid; +  wire [32*NUM_PORTS-1:0] ctrlport_err_radio_req_data; +  wire [   NUM_PORTS-1:0] ctrlport_err_radio_req_has_time; +  wire [64*NUM_PORTS-1:0] ctrlport_err_radio_req_time; +  wire [   NUM_PORTS-1:0] ctrlport_err_radio_resp_ack; + +  ctrlport_combiner #( +    .NUM_MASTERS (NUM_PORTS), +    .PRIORITY    (0) +  ) ctrlport_combiner_i ( +    .ctrlport_clk              (radio_clk), +    .ctrlport_rst              (radio_rst), +    .s_ctrlport_req_wr         (ctrlport_err_radio_req_wr), +    .s_ctrlport_req_rd         ({NUM_PORTS{1'b0}}), +    .s_ctrlport_req_addr       (ctrlport_err_radio_req_addr), +    .s_ctrlport_req_portid     (ctrlport_err_radio_req_portid), +    .s_ctrlport_req_rem_epid   (ctrlport_err_radio_req_rem_epid), +    .s_ctrlport_req_rem_portid (ctrlport_err_radio_req_rem_portid), +    .s_ctrlport_req_data       (ctrlport_err_radio_req_data), +    .s_ctrlport_req_byte_en    ({4*NUM_PORTS{1'b1}}), +    .s_ctrlport_req_has_time   (ctrlport_err_radio_req_has_time), +    .s_ctrlport_req_time       (ctrlport_err_radio_req_time), +    .s_ctrlport_resp_ack       (ctrlport_err_radio_resp_ack), +    .s_ctrlport_resp_status    (), +    .s_ctrlport_resp_data      (), +    .m_ctrlport_req_wr         (ctrlport_err_req_wr), +    .m_ctrlport_req_rd         (), +    .m_ctrlport_req_addr       (ctrlport_err_req_addr), +    .m_ctrlport_req_portid     (ctrlport_err_req_portid), +    .m_ctrlport_req_rem_epid   (ctrlport_err_req_rem_epid), +    .m_ctrlport_req_rem_portid (ctrlport_err_req_rem_portid), +    .m_ctrlport_req_data       (ctrlport_err_req_data), +    .m_ctrlport_req_byte_en    (), +    .m_ctrlport_req_has_time   (ctrlport_err_req_has_time), +    .m_ctrlport_req_time       (ctrlport_err_req_time), +    .m_ctrlport_resp_ack       (ctrlport_err_resp_ack), +    .m_ctrlport_resp_status    (2'b0), +    .m_ctrlport_resp_data      (32'b0) +  ); + + +  //--------------------------------------------------------------------------- +  // Shared Registers +  //--------------------------------------------------------------------------- +  // +  // These registers are shared by all radio channels. +  // +  //--------------------------------------------------------------------------- + +  localparam [15:0] compat_major = 16'd0; +  localparam [15:0] compat_minor = 16'd0; + +  always @(posedge radio_clk) begin +    if (radio_rst) begin +      ctrlport_shared_resp_ack  <= 0; +      ctrlport_shared_resp_data <= 0; +    end else begin +      // Default assignments +      ctrlport_shared_resp_ack  <= 0; +      ctrlport_shared_resp_data <= 0; + +      // Handle register reads +      if (ctrlport_shared_req_rd) begin +        case (ctrlport_shared_req_addr) +          REG_COMPAT_NUM: begin +            ctrlport_shared_resp_ack  <= 1; +            ctrlport_shared_resp_data <= { compat_major, compat_minor }; +          end +        endcase +      end +    end +  end + + +  //--------------------------------------------------------------------------- +  // Radio Cores +  //--------------------------------------------------------------------------- +  // +  // This generate block instantiates one radio core for each channel that is +  // requested by NUM_PORTS. +  // +  //--------------------------------------------------------------------------- + +  genvar i; +  generate +    for (i = 0; i < NUM_PORTS; i = i+1) begin : radio_core_gen + +      // The radio core contains all the logic related to a single radio channel. +      radio_core #( +        .SAMP_W (ITEM_W), +        .NSPC   (NIPC) +      ) radio_core_i ( +        .radio_clk                 (radio_clk), +        .radio_rst                 (radio_rst), + +        // Slave Control Port (Register Access) +        .s_ctrlport_req_wr         (ctrlport_radios_req_wr[i]), +        .s_ctrlport_req_rd         (ctrlport_radios_req_rd[i]), +        .s_ctrlport_req_addr       (ctrlport_radios_req_addr[i*20 +: 20]), +        .s_ctrlport_req_data       (ctrlport_radios_req_data[i*32 +: 32]), +        .s_ctrlport_resp_ack       (ctrlport_radios_resp_ack[i]), +        .s_ctrlport_resp_data      (ctrlport_radios_resp_data[i*32 +: 32]), + +        // Master Control Port (Error Reporting) +        .m_ctrlport_req_wr         (ctrlport_err_radio_req_wr[i]), +        .m_ctrlport_req_addr       (ctrlport_err_radio_req_addr[i*20 +: 20]), +        .m_ctrlport_req_portid     (ctrlport_err_radio_req_portid[i*10 +: 10]), +        .m_ctrlport_req_rem_epid   (ctrlport_err_radio_req_rem_epid[i*16 +: 16]), +        .m_ctrlport_req_rem_portid (ctrlport_err_radio_req_rem_portid[i*10 +: 10]), +        .m_ctrlport_req_data       (ctrlport_err_radio_req_data[i*32 +: 32]), +        .m_ctrlport_req_has_time   (ctrlport_err_radio_req_has_time[i]), +        .m_ctrlport_req_time       (ctrlport_err_radio_req_time[i*64 +: 64]), +        .m_ctrlport_resp_ack       (ctrlport_err_radio_resp_ack[i]), + +        // Tx Data Stream +        .s_axis_tdata              (axis_tx_tdata[RADIO_W*i +: RADIO_W]), +        .s_axis_tlast              (axis_tx_tlast[i]), +        .s_axis_tvalid             (axis_tx_tvalid[i]), +        .s_axis_tready             (axis_tx_tready[i]), +        // Sideband Info +        .s_axis_ttimestamp         (axis_tx_ttimestamp[i*64 +: 64]), +        .s_axis_thas_time          (axis_tx_thas_time[i]), +        .s_axis_teob               (axis_tx_teob[i]), + +        // Rx Data Stream +        .m_axis_tdata              (axis_rx_tdata[RADIO_W*i +: RADIO_W]), +        .m_axis_tlast              (axis_rx_tlast[i]), +        .m_axis_tvalid             (axis_rx_tvalid[i]), +        .m_axis_tready             (axis_rx_tready[i]), +        // Sideband Info +        .m_axis_ttimestamp         (axis_rx_ttimestamp[i*64 +: 64]), +        .m_axis_thas_time          (axis_rx_thas_time[i]), +        .m_axis_teob               (axis_rx_teob[i]), + +        // Radio Data +        .radio_time                (radio_time), +        .radio_rx_data             (radio_rx_data[(RADIO_W)*i +: (RADIO_W)]), +        .radio_rx_stb              (radio_rx_stb[i]), +        .radio_rx_running          (radio_rx_running[i]), +        .radio_tx_data             (radio_tx_data[(RADIO_W)*i +: (RADIO_W)]), +        .radio_tx_stb              (radio_tx_stb[i]), +        .radio_tx_running          (radio_tx_running[i]) +      ); +    end +  endgenerate +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/rfnoc_block_radio_all_tb.sv b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/rfnoc_block_radio_all_tb.sv new file mode 100644 index 000000000..ea99692bb --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/rfnoc_block_radio_all_tb.sv @@ -0,0 +1,68 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_radio_all_tb +// +// Description: This is the testbench for rfnoc_block_radio that instantiates  +// several variations of rfnoc_block_radio_tb to test different configurations. +// + + +module rfnoc_block_radio_all_tb; + +  timeunit 1ns; +  timeprecision 1ps; + +  import PkgTestExec::*; + + +  //--------------------------------------------------------------------------- +  // Test Definitions +  //--------------------------------------------------------------------------- + +  typedef struct { +    int CHDR_W; +    int ITEM_W; +    int NIPC; +    int NUM_PORTS; +    int STALL_PROB; +    int STB_PROB; +    bit TEST_REGS; +  } test_config_t; + +  localparam NUM_TESTS = 9; + +  localparam test_config_t test[NUM_TESTS] = '{ +    '{CHDR_W:  64, ITEM_W: 16, NIPC: 1, NUM_PORTS: 3, STALL_PROB: 10, STB_PROB: 100, TEST_REGS: 1 }, +    '{CHDR_W:  64, ITEM_W: 16, NIPC: 1, NUM_PORTS: 2, STALL_PROB: 25, STB_PROB:  80, TEST_REGS: 1 }, +    '{CHDR_W:  64, ITEM_W: 16, NIPC: 2, NUM_PORTS: 1, STALL_PROB: 25, STB_PROB:  80, TEST_REGS: 0 }, +    '{CHDR_W:  64, ITEM_W: 32, NIPC: 1, NUM_PORTS: 1, STALL_PROB: 25, STB_PROB:  80, TEST_REGS: 0 }, +    '{CHDR_W:  64, ITEM_W: 32, NIPC: 2, NUM_PORTS: 1, STALL_PROB: 10, STB_PROB:  80, TEST_REGS: 0 }, +    '{CHDR_W: 128, ITEM_W: 32, NIPC: 1, NUM_PORTS: 3, STALL_PROB: 10, STB_PROB: 100, TEST_REGS: 1 }, +    '{CHDR_W: 128, ITEM_W: 32, NIPC: 1, NUM_PORTS: 2, STALL_PROB: 25, STB_PROB:  80, TEST_REGS: 0 }, +    '{CHDR_W: 128, ITEM_W: 32, NIPC: 2, NUM_PORTS: 1, STALL_PROB: 25, STB_PROB:  80, TEST_REGS: 0 }, +    '{CHDR_W: 128, ITEM_W: 32, NIPC: 4, NUM_PORTS: 1, STALL_PROB: 10, STB_PROB:  80, TEST_REGS: 0 } +  }; + + +  //--------------------------------------------------------------------------- +  // DUT Instances +  //--------------------------------------------------------------------------- + +  genvar i; +  for (i = 0; i < NUM_TESTS; i++) begin : gen_test_config +    rfnoc_block_radio_tb #( +      .CHDR_W     (test[i].CHDR_W    ), +      .ITEM_W     (test[i].ITEM_W    ), +      .NIPC       (test[i].NIPC      ), +      .NUM_PORTS  (test[i].NUM_PORTS ), +      .STALL_PROB (test[i].STALL_PROB), +      .STB_PROB   (test[i].STB_PROB  ), +      .TEST_REGS  (test[i].TEST_REGS ) +    ) rfnoc_block_radio_tb_i (); +  end : gen_test_config + + +endmodule : rfnoc_block_radio_all_tb diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/rfnoc_block_radio_regs.vh b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/rfnoc_block_radio_regs.vh new file mode 100644 index 000000000..41f9a144e --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/rfnoc_block_radio_regs.vh @@ -0,0 +1,125 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_radio_regs (Header) +// +// Description: Header file for RFNoC radio functionality. This includes  +// register offsets, bitfields and constants for the radio components. +// + + +//----------------------------------------------------------------------------- +// Shared Register Offsets (One Set Per Radio NoC Block) +//----------------------------------------------------------------------------- + +localparam SHARED_BASE_ADDR = 20'h00;  // Base address for shared radio registers +localparam SHARED_ADDR_W    = 4;       // Address space size for shared registers + +localparam REG_COMPAT_NUM = 'h00;  // Compatibility number register offset + + +//----------------------------------------------------------------------------- +// Radio Core Register Offsets (One Set Per Radio Port) +//----------------------------------------------------------------------------- +// +// These registers are replicated depending on the number of radio channels  +// requested. They start at BASE_ADDR_RADIO and repeat every RADIO_ADDR_SPACE  +// bytes. +// +// WARNING: All registers larger than a single 32-bit word must be read and  +//          written least significant word first to guarantee coherency. +// +//----------------------------------------------------------------------------- + +localparam RADIO_BASE_ADDR = 20'h1000; // Base address of first radio. Choose a  +                                       // nice big power of 2 so we can just pass  +                                       // the lower bits to the radio cores. +localparam RADIO_ADDR_W    = 7;        // Address space size per radio + +// General Radio Registers +localparam REG_LOOPBACK_EN   = 'h00;   // Loopback enable (connect Tx output to Rx input) +localparam REG_RADIO_WIDTH   = 'h04;   // Upper 16 bits is sample width, lower 16 bits is NSPC + +// RX Control Registers +localparam REG_RX_STATUS            = 'h10; // Status of Rx radio +localparam REG_RX_CMD               = 'h14; // The next radio command to execute +localparam REG_RX_CMD_NUM_WORDS_LO  = 'h18; // Number of radio words for the next command (low word) +localparam REG_RX_CMD_NUM_WORDS_HI  = 'h1C; // Number of radio words for the next command (high word) +localparam REG_RX_CMD_TIME_LO       = 'h20; // Time for the next command (low word) +localparam REG_RX_CMD_TIME_HI       = 'h24; // Time for the next command (high word) +localparam REG_RX_MAX_WORDS_PER_PKT = 'h28; // Maximum packet length to build from Rx data +localparam REG_RX_ERR_PORT          = 'h2C; // Port ID for error reporting +localparam REG_RX_ERR_REM_PORT      = 'h30; // Remote port ID for error reporting +localparam REG_RX_ERR_REM_EPID      = 'h34; // Remote EPID (endpoint ID) for error reporting +localparam REG_RX_ERR_ADDR          = 'h38; // Offset to write error code to +localparam REG_RX_DATA              = 'h3C; // Read the current Rx output of the radio +localparam REG_RX_HAS_TIME          = 'h70; // Controls whether or not a channel has timestamps + +// TX Control Registers +localparam REG_TX_IDLE_VALUE   = 'h40; // Value to output when transmitter is idle +localparam REG_TX_ERROR_POLICY = 'h44; // Tx error policy +localparam REG_TX_ERR_PORT     = 'h48; // Port ID for error reporting +localparam REG_TX_ERR_REM_PORT = 'h4C; // Remote port ID for error reporting +localparam REG_TX_ERR_REM_EPID = 'h50; // Remote EPID (endpoint ID) for error reporting +localparam REG_TX_ERR_ADDR     = 'h54; // Offset to write error code to + + +//----------------------------------------------------------------------------- +// Register Bit Fields +//----------------------------------------------------------------------------- + +// REG_RX_CMD bit fields +localparam RX_CMD_POS       = 0;  // Location of the command bit field +localparam RX_CMD_LEN       = 2;  // Bit length of the command bit field +localparam RX_CMD_TIMED_POS = 31; // Location of the bit indicating if this is +                                  // a timed command or not. + +// REG_RX_CMD_NUM_WORDS_HI/LO length field +localparam RX_CMD_NUM_WORDS_LEN = 48; // Number of bits that are used in the 64-bit +                                      // NUM_WORDS register (must be in range [33:64]). + +// REG_RX_STATUS bit fields +localparam CMD_FIFO_SPACE_POS = 0;  // Indicates if radio is busy executing a command. +localparam CMD_FIFO_SPACE_LEN = 6;  // Length of the FIFO_SPACE field +localparam CMD_FIFO_SPACE_MAX = 32; // Size of command FIFO + +// REG_TX_ERROR_POLICY bit fields +localparam TX_ERR_POLICY_LEN  = 2; // Length of error policy bit field + + +//----------------------------------------------------------------------------- +// Rx Radio Commands +//----------------------------------------------------------------------------- + +localparam [RX_CMD_LEN-1:0] RX_CMD_STOP       = 0; // Stop acquiring at end of next packet +localparam [RX_CMD_LEN-1:0] RX_CMD_FINITE     = 1; // Acquire NUM_SAMPS then stop +localparam [RX_CMD_LEN-1:0] RX_CMD_CONTINUOUS = 2; // Acquire until stopped + + +//----------------------------------------------------------------------------- +// Tx Error Policies +//----------------------------------------------------------------------------- + +localparam TX_ERR_POLICY_PACKET = 1;  // Wait for end of packet after error +localparam TX_ERR_POLICY_BURST  = 2;  // Wait for end of burst after error + + +//----------------------------------------------------------------------------- +// Error Codes +//----------------------------------------------------------------------------- + +// Rx Error Codes +localparam ERR_RX_CODE_W = 2;  // Bit width of error code values +// +localparam ERR_RX_LATE_CMD = 1;  // Late command (arrived after indicated time) +localparam ERR_RX_OVERRUN  = 2;  // FIFO overflow + + +// Tx Error Codes +localparam ERR_TX_CODE_W = 2;  // Bit width of error code values +// +localparam ERR_TX_UNDERRUN  = 1;  // Data underflow (data not available when needed) +localparam ERR_TX_LATE_DATA = 2;  // Late data (arrived after indicated time) +localparam ERR_TX_EOB_ACK   = 3;  // Acknowledge end-of-burst (this is not an error) diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/rfnoc_block_radio_tb.sv b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/rfnoc_block_radio_tb.sv new file mode 100644 index 000000000..706e0f185 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/rfnoc_block_radio_tb.sv @@ -0,0 +1,1382 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_radio_tb +// +// Description: This is the testbench for rfnoc_block_radio. +// + + +module rfnoc_block_radio_tb #( +  parameter int CHDR_W     = 128, // CHDR bus width +  parameter int ITEM_W     = 32,  // Sample width +  parameter int NIPC       = 2,   // Number of samples per radio clock cycle +  parameter int NUM_PORTS  = 2,   // Number of radio channels +  parameter int STALL_PROB = 25,  // Probability of AXI BFM stall +  parameter int STB_PROB   = 80,  // Probability of radio STB asserting +  parameter bit TEST_REGS  = 1    // Do register tests +); + +  // Include macros and time declarations for use with PkgTestExec +  `include "test_exec.svh" + +  import PkgTestExec::*; +  import PkgChdrUtils::*; +  import PkgRfnocBlockCtrlBfm::*; +  import PkgAxisCtrlBfm::*; +  import PkgChdrBfm::*; +  import PkgRfnocItemUtils::*; + +  // Pull in radio register offsets and constants +  `include "rfnoc_block_radio_regs.vh" + + +  // Simulation Parameters +  localparam logic [ 9:0] THIS_PORTID    = 10'h17; +  localparam logic [15:0] THIS_EPID      = 16'hDEAD; +  localparam int          MTU            = 8; +  localparam int          RADIO_W        = NIPC * ITEM_W;       // Radio word size +  localparam int          SPP            = 64;                  // Samples per packet +  localparam int          WPP            = SPP*ITEM_W/RADIO_W;  // Radio words per packet +  localparam int          CHDR_CLK_PER   = 5;                   // rfnoc_chdr_clk period in ns +  localparam int          CTRL_CLK_PER   = 25;                  // rfnoc_ctrl_clk period in ns +  localparam int          RADIO_CLK_PER  = 10;                  // radio_clk_per period in ns + +  // Amount of time to wait for a packet to be fully acquired +  localparam realtime MAX_PKT_WAIT = 4*WPP*(RADIO_CLK_PER+CTRL_CLK_PER)*1ns; + +  // Error reporting values to use +  localparam bit [ 9:0] TX_ERR_DST_PORT     = 10'h2B5; +  localparam bit [ 9:0] TX_ERR_REM_DST_PORT = 10'h14C; +  localparam bit [15:0] TX_ERR_REM_DST_EPID = 16'hA18E; +  localparam bit [19:0] TX_ERR_ADDRESS      = 20'hA31D3; + + + +  //--------------------------------------------------------------------------- +  // Clocks and Resets +  //--------------------------------------------------------------------------- + +  bit rfnoc_chdr_clk; +  bit rfnoc_ctrl_clk; +  bit radio_clk; + +  // Don't start the clocks automatically (AUTOSTART=0), since we expect +  // multiple instances of this testbench to run in sequence. They will be +  // started before the first test. +  sim_clock_gen #(.PERIOD(CHDR_CLK_PER), .AUTOSTART(0)) +    rfnoc_chdr_clk_gen (.clk(rfnoc_chdr_clk), .rst()); +  sim_clock_gen #(.PERIOD(CTRL_CLK_PER), .AUTOSTART(0)) +    rfnoc_ctrl_clk_gen (.clk(rfnoc_ctrl_clk), .rst()); +  sim_clock_gen #(.PERIOD(RADIO_CLK_PER), .AUTOSTART(0))  +    radio_clk_gen (.clk(radio_clk), .rst()); + + + +  //--------------------------------------------------------------------------- +  // Bus Functional Models +  //--------------------------------------------------------------------------- + +  // Connections to DUT as interfaces: +  RfnocBackendIf        backend               (rfnoc_chdr_clk, rfnoc_ctrl_clk); +  AxiStreamIf #(32)     m_ctrl                (rfnoc_ctrl_clk, 1'b0); +  AxiStreamIf #(32)     s_ctrl                (rfnoc_ctrl_clk, 1'b0); +  AxiStreamIf #(CHDR_W) m_chdr [NUM_PORTS] (rfnoc_chdr_clk, 1'b0); +  AxiStreamIf #(CHDR_W) s_chdr [NUM_PORTS] (rfnoc_chdr_clk, 1'b0); + +  // Bus functional model for a software block controller +  RfnocBlockCtrlBfm #(.CHDR_W(CHDR_W)) blk_ctrl; + + + +  //--------------------------------------------------------------------------- +  // Radio Data Model +  //--------------------------------------------------------------------------- + +  bit [NUM_PORTS*RADIO_W-1:0] radio_rx_data; +  bit [        NUM_PORTS-1:0] radio_rx_stb; + +  bit [63:0] radio_time; +  bit        radio_pps; + +  // Radio data generation +  sim_radio_gen #( +    .NSPC         (NIPC), +    .SAMP_W       (ITEM_W), +    .NUM_CHANNELS (NUM_PORTS), +    .STB_PROB     (STB_PROB), +    .INCREMENT    (NIPC), +    .PPS_PERIOD   (NIPC * 250) +  ) radio_gen ( +    .radio_clk     (radio_clk), +    .radio_rst     (1'b0), +    .radio_rx_data (radio_rx_data), +    .radio_rx_stb  (radio_rx_stb), +    .radio_time    (radio_time), +    .radio_pps     (radio_pps) +  ); + + + +  //--------------------------------------------------------------------------- +  // DUT +  //--------------------------------------------------------------------------- + +  logic [NUM_PORTS-1:0] radio_rx_running; + +  logic [NUM_PORTS*RADIO_W-1:0] radio_tx_data; +  logic [        NUM_PORTS-1:0] radio_tx_stb; +  logic [        NUM_PORTS-1:0] radio_tx_running; + +  logic [NUM_PORTS*CHDR_W-1:0] s_rfnoc_chdr_tdata_flat; +  logic [       NUM_PORTS-1:0] s_rfnoc_chdr_tlast_flat; +  logic [       NUM_PORTS-1:0] s_rfnoc_chdr_tvalid_flat; +  logic [       NUM_PORTS-1:0] s_rfnoc_chdr_tready_flat; + +  logic [NUM_PORTS*CHDR_W-1:0] m_rfnoc_chdr_tdata_flat; +  logic [       NUM_PORTS-1:0] m_rfnoc_chdr_tlast_flat; +  logic [       NUM_PORTS-1:0] m_rfnoc_chdr_tvalid_flat; +  logic [       NUM_PORTS-1:0] m_rfnoc_chdr_tready_flat; + +  semaphore port_sem = new(0); + +  // Use the same strobe for both Rx and Tx +  assign radio_tx_stb = radio_rx_stb; + + +  // Flatten the data stream arrays into concatenated vectors +  genvar i; +  for (i = 0; i < NUM_PORTS; i++) begin : gen_radio_connections +    assign s_rfnoc_chdr_tdata_flat[CHDR_W*i+:CHDR_W] = m_chdr[i].tdata; +    assign s_rfnoc_chdr_tlast_flat[i]                = m_chdr[i].tlast; +    assign s_rfnoc_chdr_tvalid_flat[i]               = m_chdr[i].tvalid; +    assign m_chdr[i].tready                          = s_rfnoc_chdr_tready_flat[i]; + +    assign s_chdr[i].tdata             = m_rfnoc_chdr_tdata_flat[CHDR_W*i+:CHDR_W]; +    assign s_chdr[i].tlast             = m_rfnoc_chdr_tlast_flat[i]; +    assign s_chdr[i].tvalid            = m_rfnoc_chdr_tvalid_flat[i]; +    assign m_rfnoc_chdr_tready_flat[i] = s_chdr[i].tready; + +    // Connect each interface to the BFM. This is done in a generate block  +    // since the interface indices must be constant in SystemVerilog :( +    initial begin +      // Get the port number (plus 1) from the semaphore. This will block until  +      // the semaphore is incremented to this port number (plus 1). +      port_sem.get(i+1); +      // Connect the master and slave interfaces to the BFM +      void'(blk_ctrl.add_master_data_port(m_chdr[i])); +      void'(blk_ctrl.add_slave_data_port(s_chdr[i])); +      // Put the port number to communicate that we're done +      port_sem.put(i+1); +    end +  end + + +  rfnoc_block_radio #( +    .THIS_PORTID (THIS_PORTID), +    .CHDR_W      (CHDR_W), +    .NIPC        (NIPC), +    .ITEM_W      (ITEM_W), +    .NUM_PORTS   (NUM_PORTS), +    .MTU         (MTU) +  ) rfnoc_block_radio_i ( +    .rfnoc_chdr_clk          (backend.chdr_clk), +    .s_rfnoc_chdr_tdata      (s_rfnoc_chdr_tdata_flat), +    .s_rfnoc_chdr_tlast      (s_rfnoc_chdr_tlast_flat), +    .s_rfnoc_chdr_tvalid     (s_rfnoc_chdr_tvalid_flat), +    .s_rfnoc_chdr_tready     (s_rfnoc_chdr_tready_flat), +    .m_rfnoc_chdr_tdata      (m_rfnoc_chdr_tdata_flat), +    .m_rfnoc_chdr_tlast      (m_rfnoc_chdr_tlast_flat), +    .m_rfnoc_chdr_tvalid     (m_rfnoc_chdr_tvalid_flat), +    .m_rfnoc_chdr_tready     (m_rfnoc_chdr_tready_flat), +    .rfnoc_core_config       (backend.cfg), +    .rfnoc_core_status       (backend.sts), +    .rfnoc_ctrl_clk          (backend.ctrl_clk), +    .s_rfnoc_ctrl_tdata      (m_ctrl.tdata), +    .s_rfnoc_ctrl_tlast      (m_ctrl.tlast), +    .s_rfnoc_ctrl_tvalid     (m_ctrl.tvalid), +    .s_rfnoc_ctrl_tready     (m_ctrl.tready), +    .m_rfnoc_ctrl_tdata      (s_ctrl.tdata), +    .m_rfnoc_ctrl_tlast      (s_ctrl.tlast), +    .m_rfnoc_ctrl_tvalid     (s_ctrl.tvalid), +    .m_rfnoc_ctrl_tready     (s_ctrl.tready), +    .m_ctrlport_req_wr       (), +    .m_ctrlport_req_rd       (), +    .m_ctrlport_req_addr     (), +    .m_ctrlport_req_data     (), +    .m_ctrlport_req_byte_en  (), +    .m_ctrlport_req_has_time (), +    .m_ctrlport_req_time     (), +    .m_ctrlport_resp_ack     (1'b0), +    .m_ctrlport_resp_status  (2'b0), +    .m_ctrlport_resp_data    (32'b0), +    .radio_clk               (radio_clk), +    .radio_time              (radio_time), +    .radio_rx_data           (radio_rx_data), +    .radio_rx_stb            (radio_rx_stb), +    .radio_rx_running        (radio_rx_running), +    .radio_tx_data           (radio_tx_data), +    .radio_tx_stb            (radio_tx_stb), +    .radio_tx_running        (radio_tx_running) +  ); + + + +  //--------------------------------------------------------------------------- +  // Helper Tasks +  //--------------------------------------------------------------------------- + +  // Read a 32-bit register at offset "addr" from shared radio registers +  task automatic read_shared(logic [19:0] addr, output logic [31:0] data); +    addr = addr + SHARED_BASE_ADDR; +    blk_ctrl.reg_read(addr, data); +  endtask : read_shared + +  // Write a 32-bit register at offset "addr" in shared radio registers +  task automatic write_shared(logic [19:0] addr, logic [31:0] data); +    addr = addr + SHARED_BASE_ADDR; +    blk_ctrl.reg_write(addr, data); +  endtask : write_shared + +  // Read a 32-bit register at offset "addr" from radio "radio_num" +  task automatic read_radio(int radio_num, logic [19:0] addr, output logic [31:0] data); +    addr = addr + RADIO_BASE_ADDR + (radio_num * 2**RADIO_ADDR_W); +    blk_ctrl.reg_read(addr, data); +  endtask : read_radio + +  // Read a 64-bit register at offset "addr" from radio "radio_num" +  task automatic read_radio_64(int radio_num, logic [19:0] addr, output logic [63:0] data); +    addr = addr + RADIO_BASE_ADDR + (radio_num * 2**RADIO_ADDR_W); +    blk_ctrl.reg_read(addr,   data[31:0]); +    blk_ctrl.reg_read(addr+4, data[63:32]); +  endtask : read_radio_64 + +  // Write a 32-bit register at offset "addr" in radio "radio_num" +  task automatic write_radio(int radio_num, logic [19:0] addr, logic [31:0] data); +    addr = addr + RADIO_BASE_ADDR + (radio_num * 2**RADIO_ADDR_W); +    blk_ctrl.reg_write(addr, data); +  endtask : write_radio + +  // Write a 64-bit register at offset "addr" in radio "radio_num" +  task automatic write_radio_64(int radio_num, logic [19:0] addr, logic [63:0] data); +    addr = addr + RADIO_BASE_ADDR + (radio_num * 2**RADIO_ADDR_W); +    blk_ctrl.reg_write(addr,   data[31:0]); +    blk_ctrl.reg_write(addr+4, data[63:32]); +  endtask : write_radio_64 + + +  // Start an Rx acquisition +  task automatic start_rx ( +    int        radio_num,     // Radio channel to use +    bit [63:0] num_words = 0  // Number of radio words +  ); +    logic [31:0] cmd; + +    if (num_words == 0) begin +      // Do a continuous acquisition +      $display("Radio %0d: Start RX, continuous receive", radio_num); +      cmd = RX_CMD_CONTINUOUS; +    end else begin +      // Do a finite acquisition (num samps and done) +      $display("Radio %0d: Start RX, receive %0d words", radio_num, num_words); +      write_radio_64(radio_num, REG_RX_CMD_NUM_WORDS_LO, num_words); +      cmd = RX_CMD_FINITE; +    end + +    // Write command to radio +    write_radio(radio_num, REG_RX_CMD, cmd); +  endtask : start_rx + + +  // Start an Rx acquisition at a specific time +  task automatic start_rx_timed ( +    int        radio_num,      // Radio channel to use +    bit [63:0] num_words = 0,  // Number of radio words +    bit [63:0] start_time +  ); +    logic [31:0] cmd; + +    if (num_words == 0) begin +      // Do a continuous acquisition +      $display("Radio %0d: Start RX, continuous receive (timed)", radio_num); +      cmd = RX_CMD_CONTINUOUS; +    end else begin +      // Do a finite acquisition (num samps and done) +      $display("Radio %0d: Start RX, receive %0d words (timed)", radio_num, num_words); +      write_radio_64(radio_num, REG_RX_CMD_NUM_WORDS_LO, num_words); +      cmd = RX_CMD_FINITE; +    end + +    // Mark that this is a timed command +    cmd[RX_CMD_TIMED_POS] = 1'b1; + +    // Set start time for command +    write_radio_64(radio_num, REG_RX_CMD_TIME_LO, start_time); + +    // Write command to radio +    write_radio(radio_num, REG_RX_CMD, cmd); +  endtask : start_rx_timed + + +  // Send the Rx stop command to the indicated radio channel +  task automatic stop_rx(int radio_num); +    $display("Radio %0d: Stop RX", radio_num); +    write_radio(radio_num, REG_RX_CMD, RX_CMD_STOP); +  endtask : stop_rx + + +  // Receive num_words from the indicated radio channel and verify that it's  +  // sequential and contiguous data aligned on packet boundaries. +  task automatic check_rx( +    int radio_num,    // Radio to receive from and check +    int num_words     // Number of radio words to expect +  ); +    int              sample_count;    // Counter to track number of samples generated +    bit [ITEM_W-1:0] sample_val;      // Value of the next sample +    chdr_word_t      data[$];         // Array of data for the received packet +    int              num_samples;     // Number of samples to send +    int              byte_length;     // Number of data bytes in next packet +    int              expected_length; // Expected byte length of the next packet +    int              valid_words;     // Number of valid chdr_word_t in next packet + +    num_samples = num_words * NIPC; + +    sample_count = 0; +    while (sample_count < num_samples) begin +      // Fetch the next packet +      blk_ctrl.recv(radio_num, data, byte_length); + +      // Take the first sample as a starting count for the remaining samples +      if (sample_count == 0) begin +        sample_val = data[0][ITEM_W-1:0]; +      end + +      // Calculate expected length in bytes +      if (num_samples - sample_count >= SPP) begin +        // Expecting a full packet +        expected_length = SPP*ITEM_W/8; +      end else begin +        // Expecting partial packet +        expected_length = (num_samples - sample_count) * ITEM_W/8; +      end + +      // Check that the length matches our expectation +      `ASSERT_ERROR( +        byte_length == expected_length, +        "Received packet didn't have expected length." +      ); + +      // Loop over the packet, one chdr_word_t at a time +      valid_words = int'($ceil(real'(byte_length) / ($bits(chdr_word_t)/8))); +      for (int i = 0; i < valid_words; i++) begin +        // Check each sample of the next chdr_word_t value +        for (int sub_sample = 0; sub_sample < $bits(chdr_word_t)/ITEM_W; sub_sample++) begin +          chdr_word_t word; +          word = data[i][ITEM_W*sub_sample +: ITEM_W];  // Work around Vivado 2018.3 issue +          `ASSERT_ERROR( +            word == sample_val, +            $sformatf( +              "Sample %0d (0x%X) didn't match expected value (0x%X)", +              sample_count, data[i][ITEM_W*sub_sample +: ITEM_W],  sample_val +            ) +          ); +          sample_val++; +          sample_count++; + +          // Check if the word is only partially full +          if (sample_count >= num_samples) break; +        end +      end +    end +  endtask : check_rx + + +  // Send num_words to the indicated radio for transmission at the given time. +  task automatic start_tx_timed ( +    int                radio_num,       // Radio channel to transmit on +    bit   [63:0]       num_words,       // Number of radio words to transmit +    logic [63:0]       start_time = 'X, // Time at which to begin transmit +    bit   [ITEM_W-1:0] start_val  = 1,  // Initial sample value +    bit                eob        = 1   // Set EOB flag at the end +  ); +    int              sample_count;    // Counter to track number of samples generated +    bit [ITEM_W-1:0] sample_val;      // Value of the next sample +    chdr_word_t      data[$];         // Array of data for the packet +    int              num_samples;     // Number of samples to send +    int              byte_length;     // Number of bytes for next packet +    chdr_word_t      chdr_word;       // Next word to send to BFM +    packet_info_t    pkt_info = 0;    // Flags/timestamp for next packet + +    $display("Radio %0d: Start TX, send %0d words", radio_num, num_words); + +    num_samples = num_words * NIPC; + +    if (!$isunknown(start_time)) pkt_info.has_time = 1; + +    sample_val   = start_val; +    sample_count = 0; +    while (sample_count < num_samples) begin +      // Calculate timestamp for this packet +      if (pkt_info.has_time) begin +        pkt_info.timestamp = start_time + sample_count; +      end + +      // Clear the payload +      data = {}; + +      // Loop until we've built up a packet +      forever begin +        // Generate the next word +        for (int sub_sample = 0; sub_sample < $bits(chdr_word_t)/ITEM_W; sub_sample++) begin +          chdr_word[ITEM_W*sub_sample +: ITEM_W] = sample_val; +          sample_val++; +          sample_count++; +        end + +        // Add next word to the queue +        data.push_back(chdr_word); + +        // Send the packet if we're at a packet boundary +        if (sample_count % SPP == 0) begin +          pkt_info.eob = (sample_count == num_samples && eob) ? 1 : 0; +          byte_length = SPP * ITEM_W/8; +          blk_ctrl.send(radio_num, data, byte_length, {}, pkt_info); +          break; +        end else if (sample_count >= num_samples) begin +          pkt_info.eob = eob; +          byte_length = (sample_count % SPP) * ITEM_W/8; +          blk_ctrl.send(radio_num, data, byte_length, {}, pkt_info); +          break; +        end +      end +    end +  endtask : start_tx_timed + + +  // Send num_words to the indicated radio for transmission. +  task automatic start_tx ( +    int              radio_num,       // Radio channel to transmit on +    bit [63:0]       num_words,       // Number of radio words to transmit +    bit [ITEM_W-1:0] start_val = 1,   // Initial sample value +    bit              eob = 1          // Set EOB flag at the end +  ); +    // Passing 'X tells the underlying BFM to not insert a timestamp +    start_tx_timed(radio_num, num_words, 'X, start_val, eob); +  endtask : start_tx + + +  // Verify the output of a packet, expecting it at a specific time +  task automatic check_tx_timed ( +    int                radio_num,        // Radio channel to transmit on +    bit   [63:0]       num_words,        // Number of radio words to expect +    logic [63:0]       start_time = 'X,  // Expected start time +    bit   [ITEM_W-1:0] start_val  = 1    // Initial sample value +  ); +    int sample_val;           // Expected value of next sample + +    sample_val = start_val; + +    // Wait for the packet to start +    wait(radio_tx_data[radio_num*RADIO_W +: ITEM_W] == start_val); + +    // Check the time +    if (!$isunknown(start_time)) begin +      `ASSERT_ERROR( +        radio_time - start_time <= NIPC*2, +        $sformatf("Packet transmitted at radio time 0x%0X but expected 0x%0X", radio_time, start_time) +      ); +    end + +    // Verify output one word at a time +    for (int word_count = 0; word_count < num_words; word_count++) begin +      // Wait for the next radio word to be output +      do begin +        @(posedge radio_clk); +      end while (radio_tx_stb[radio_num] == 0); + +      // Check each sample of the radio word +      for (int sub_sample = 0; sub_sample < NIPC; sub_sample++) begin +        `ASSERT_ERROR( +          radio_tx_data[radio_num*RADIO_W + ITEM_W*sub_sample +: ITEM_W] == sample_val, +          "Radio output doesn't match expected value" +        ); +        sample_val++; +      end +    end +  endtask : check_tx_timed + + +  // Verify the output of a packet +  task automatic check_tx ( +    int              radio_num,        // Radio to transmit on +    bit [63:0]       num_words,        // Number of radio words to expect +    bit [ITEM_W-1:0] start_val  = 1    // Initial sample value +  ); +    check_tx_timed(radio_num, num_words, 'X, start_val); +  endtask : check_tx + + +  // When we expect and error, this task will check that control packets were +  // received and that they have the expected values. +  task check_error (int error); +    AxisCtrlPacket ctrl_packet; +    chdr_word_t word; + +    // Get error code +    blk_ctrl.get_ctrl_bfm().get_ctrl(ctrl_packet); +    word = ctrl_packet.data[0];   // Work around Vivado 2018.3 issue +    `ASSERT_ERROR( +      word                            == error && +      ctrl_packet.op_word.op_code     == CTRL_OP_WRITE && +      ctrl_packet.op_word.address     == TX_ERR_ADDRESS && +      ctrl_packet.header.is_ack       == 1'b0 && +      ctrl_packet.header.has_time     == 1'b1 && +      ctrl_packet.header.num_data     == 1 && +      ctrl_packet.header.dst_port     == TX_ERR_DST_PORT && +      ctrl_packet.header.rem_dst_port == TX_ERR_REM_DST_PORT && +      ctrl_packet.header.rem_dst_epid == TX_ERR_REM_DST_EPID, +      "Unexpected error code response"); + +    // Send acknowledgment +    ctrl_packet.header = 0; +    ctrl_packet.header.is_ack = 1; +    blk_ctrl.get_ctrl_bfm().put_ctrl(ctrl_packet); +  endtask : check_error + + + +  //--------------------------------------------------------------------------- +  // Test Procedures +  //--------------------------------------------------------------------------- + +  task automatic test_block_info(); +    test.start_test("Verify Block Info", 2us); + +    // Get static block info and validate it +    `ASSERT_ERROR(blk_ctrl.get_noc_id() == rfnoc_block_radio_i.NOC_ID, "Incorrect noc_id Value"); +    `ASSERT_ERROR(blk_ctrl.get_num_data_i() == NUM_PORTS, "Incorrect num_data_i Value"); +    `ASSERT_ERROR(blk_ctrl.get_num_data_o() == NUM_PORTS, "Incorrect num_data_o Value"); +    `ASSERT_ERROR(blk_ctrl.get_ctrl_fifosize() == rfnoc_block_radio_i.noc_shell_radio_i.CTRL_FIFO_SIZE, +      "Incorrect ctrl_fifosize Value"); +    `ASSERT_ERROR(blk_ctrl.get_mtu() == MTU, "Incorrect mtu Value"); + +    test.end_test(); +  endtask : test_block_info + + + +  task automatic test_shared_registers(); +    logic [31:0] val; +    test.start_test("Shared Registers", 10us); + +    // Compatibility number +    read_shared(REG_COMPAT_NUM, val); +    `ASSERT_ERROR( +      val == { +        rfnoc_block_radio_i.compat_major, +        rfnoc_block_radio_i.compat_minor +      }, +      "REG_COMPAT_NUM didn't read correctly" +    ); +    test.end_test(); +  endtask : test_shared_registers + + + +  task automatic test_general_registers(int radio_num); +    logic [31:0] val; +    test.start_test("General Registers", 10us); + +    // Test loopback enable register (read/write) +    read_radio(radio_num, REG_LOOPBACK_EN, val); +    `ASSERT_ERROR(val == 0, "Initial value of REG_LOOPBACK_EN is incorrect"); +    write_radio(radio_num, REG_LOOPBACK_EN, 32'hFFFFFFFF); +    read_radio(radio_num, REG_LOOPBACK_EN, val); +    `ASSERT_ERROR(val == 1, "REG_LOOPBACK_EN didn't update correctly"); +    write_radio(radio_num, REG_LOOPBACK_EN, 0); + +    // Read ITEM_W and NIPC (read only) +    read_radio(radio_num, REG_RADIO_WIDTH, val); +    `ASSERT_ERROR(val[15:0] == NIPC, "Value of NIPC register is incorrect"); +    `ASSERT_ERROR(val[31:16] == ITEM_W, "Value of ITEM_W register is incorrect"); + +    test.end_test(); +  endtask : test_general_registers + + + +  task test_rx_registers(int radio_num); +    logic [63:0] val, temp, expected; +    localparam int num_words_len = RX_CMD_NUM_WORDS_LEN; + +    test.start_test("Rx Registers", 50us); + +    // REG_RX_CMD_STATUS (read only) +    expected = CMD_FIFO_SPACE_MAX; +    read_radio(radio_num, REG_RX_STATUS, val); +    `ASSERT_ERROR(val == expected, "REG_RX_STATUS not initially CMD_FIFO_SPACE_MAX"); + +    // REG_RX_CMD (read/write). Test a bogus timed stop command just to check  +    // read/write of the register. +    expected = 0; +    expected[RX_CMD_POS +: RX_CMD_LEN] = RX_CMD_STOP; +    expected[RX_CMD_TIMED_POS] = 1'b1; +    write_radio(radio_num, REG_RX_CMD, expected); +    read_radio(radio_num, REG_RX_CMD, val); +    `ASSERT_ERROR(val == expected, "REG_RX_CMD didn't update correctly"); + +    // REG_RX_CMD_NUM_WORDS (read/write) +    read_radio_64(radio_num, REG_RX_CMD_NUM_WORDS_LO, val); +    `ASSERT_ERROR(val == 0, "REG_RX_CMD_NUM_WORDS not initially 0"); +    expected = 64'hFEDCBA9876543210; +    write_radio_64(radio_num, REG_RX_CMD_NUM_WORDS_LO, expected); +    read_radio_64(radio_num, REG_RX_CMD_NUM_WORDS_LO, val); +    `ASSERT_ERROR( +      val == expected[num_words_len-1:0], +     "REG_RX_CMD_NUM_WORDS didn't update correctly" +    ); + +    // REG_RX_CMD_TIME (read/write) +    read_radio_64(radio_num, REG_RX_CMD_TIME_LO, val); +    `ASSERT_ERROR(val == 0, "REG_RX_CMD_TIME not initially 0"); +    expected = 64'hBEADFEED0123F1FE; +    write_radio_64(radio_num, REG_RX_CMD_TIME_LO, expected); +    read_radio_64(radio_num, REG_RX_CMD_TIME_LO, val); +    `ASSERT_ERROR(val == expected, "REG_RX_CMD_TIME didn't update correctly"); + +    // REG_RX_MAX_WORDS_PER_PKT (read/write) +    read_radio(radio_num, REG_RX_MAX_WORDS_PER_PKT, val); +    `ASSERT_ERROR(val == 64, "REG_RX_MAX_WORDS_PER_PKT not initially 64"); +    expected = 32'hABBEC001; +    write_radio(radio_num, REG_RX_MAX_WORDS_PER_PKT, expected); +    read_radio(radio_num, REG_RX_MAX_WORDS_PER_PKT, val); +    `ASSERT_ERROR(val == expected, "REG_RX_MAX_WORDS_PER_PKT didn't update correctly"); + +    // REG_RX_ERR_PORT (read/write) +    read_radio(radio_num, REG_RX_ERR_PORT, val); +    `ASSERT_ERROR(val == 0, "REG_RX_ERR_PORT not initially 0"); +    expected = $urandom() & 32'h000001FF; +    write_radio(radio_num, REG_RX_ERR_PORT, expected); +    read_radio(radio_num, REG_RX_ERR_PORT, val); +    `ASSERT_ERROR(val == expected, "REG_RX_ERR_PORT didn't update correctly"); + +    // REG_RX_ERR_REM_PORT (read/write) +    read_radio(radio_num, REG_RX_ERR_REM_PORT, val); +    `ASSERT_ERROR(val == 0, "REG_RX_ERR_REM_PORT not initially 0"); +    expected = $urandom() & 32'h000001FF; +    write_radio(radio_num, REG_RX_ERR_REM_PORT, expected); +    read_radio(radio_num, REG_RX_ERR_REM_PORT, val); +    `ASSERT_ERROR(val == expected, "REG_RX_ERR_REM_PORT didn't update correctly"); + +    // REG_RX_ERR_REM_EPID (read/write) +    read_radio(radio_num, REG_RX_ERR_REM_EPID, val); +    `ASSERT_ERROR(val == 0, "REG_RX_ERR_REM_EPID not initially 0"); +    expected = $urandom() & 32'h0000FFFF; +    write_radio(radio_num, REG_RX_ERR_REM_EPID, expected); +    read_radio(radio_num, REG_RX_ERR_REM_EPID, val); +    `ASSERT_ERROR(val == expected, "REG_RX_ERR_REM_EPID didn't update correctly"); + +    // REG_RX_ERR_ADDR (read/write) +    read_radio(radio_num, REG_RX_ERR_ADDR, val); +    `ASSERT_ERROR(val == 0, "REG_RX_ERR_ADDR not initially 0"); +    expected = $urandom() & 32'h000FFFFF; +    write_radio(radio_num, REG_RX_ERR_ADDR, expected); +    read_radio(radio_num, REG_RX_ERR_ADDR, val); +    `ASSERT_ERROR(val == expected, "REG_RX_ERR_ADDR didn't update correctly"); + +    // REG_RX_DATA (read-only) +    temp = radio_tx_data[RADIO_W*radio_num +: RADIO_W]; +    read_radio(radio_num, REG_RX_DATA, val); +    `ASSERT_ERROR( +      radio_rx_data[RADIO_W*radio_num +: RADIO_W] >= val && val >= temp,  +      "REG_RX_DATA wasn't in the expected range"); +    read_radio(radio_num, REG_RX_DATA, temp); +    `ASSERT_ERROR(temp != val, "REG_RX_DATA didn't update"); + +    test.end_test(); +  endtask : test_rx_registers + + + +  task automatic test_tx_registers(int radio_num); +    logic [31:0] val, expected; + +    test.start_test("Tx Registers", 50us); + +    // REG_TX_IDLE_VALUE (read/write) +    read_radio(radio_num, REG_TX_IDLE_VALUE, val); +    `ASSERT_ERROR(val == 0, "REG_TX_IDLE_VALUE not initially 0"); +    expected = $urandom() & {ITEM_W{1'b1}}; +    write_radio(radio_num, REG_TX_IDLE_VALUE, expected); +    read_radio(radio_num, REG_TX_IDLE_VALUE, val); +    `ASSERT_ERROR(val == expected, "REG_TX_IDLE_VALUE didn't update correctly"); + +    // REG_TX_ERROR_POLICY (read/write) +    read_radio(radio_num, REG_TX_ERROR_POLICY, val); +    expected = TX_ERR_POLICY_PACKET; +    `ASSERT_ERROR(val == expected, "REG_TX_ERROR_POLICY not initially 'PACKET'"); +    expected = TX_ERR_POLICY_BURST; +    write_radio(radio_num, REG_TX_ERROR_POLICY, expected); +    read_radio(radio_num, REG_TX_ERROR_POLICY, val); +    `ASSERT_ERROR(val == expected, "REG_TX_ERROR_POLICY didn't update to 'BURST'"); +    expected = TX_ERR_POLICY_PACKET; +    write_radio(radio_num, REG_TX_ERROR_POLICY, 32'h03); // Try to set both bits! +    read_radio(radio_num, REG_TX_ERROR_POLICY, val); +    `ASSERT_ERROR(val == expected, "REG_TX_ERROR_POLICY didn't revert to 'PACKET'"); + +    // REG_TX_ERR_PORT (read/write) +    read_radio(radio_num, REG_TX_ERR_PORT, val); +    `ASSERT_ERROR(val == 0, "REG_TX_ERR_PORT not initially 0"); +    expected = $urandom() & 32'h000001FF; +    write_radio(radio_num, REG_TX_ERR_PORT, expected); +    read_radio(radio_num, REG_TX_ERR_PORT, val); +    `ASSERT_ERROR(val == expected, "REG_TX_ERR_PORT didn't update correctly"); + +    // REG_TX_ERR_REM_PORT (read/write) +    read_radio(radio_num, REG_TX_ERR_REM_PORT, val); +    `ASSERT_ERROR(val == 0, "REG_TX_ERR_REM_PORT not initially 0"); +    expected = $urandom() & 32'h000001FF; +    write_radio(radio_num, REG_TX_ERR_REM_PORT, expected); +    read_radio(radio_num, REG_TX_ERR_REM_PORT, val); +    `ASSERT_ERROR(val == expected, "REG_TX_ERR_REM_PORT didn't update correctly"); + +    // REG_TX_ERR_REM_EPID (read/write) +    read_radio(radio_num, REG_TX_ERR_REM_EPID, val); +    `ASSERT_ERROR(val == 0, "REG_TX_ERR_REM_EPID not initially 0"); +    expected = $urandom() & 32'h0000FFFF; +    write_radio(radio_num, REG_TX_ERR_REM_EPID, expected); +    read_radio(radio_num, REG_TX_ERR_REM_EPID, val); +    `ASSERT_ERROR(val == expected, "REG_TX_ERR_REM_EPID didn't update correctly"); + +    // REG_TX_ERR_ADDR (read/write) +    read_radio(radio_num, REG_TX_ERR_ADDR, val); +    `ASSERT_ERROR(val == 0, "REG_TX_ERR_ADDR not initially 0"); +    expected = $urandom() & 32'h000FFFFF; +    write_radio(radio_num, REG_TX_ERR_ADDR, expected); +    read_radio(radio_num, REG_TX_ERR_ADDR, val); +    `ASSERT_ERROR(val == expected, "REG_TX_ERR_ADDR didn't update correctly"); + +    test.end_test(); +  endtask : test_tx_registers + + + +  task automatic test_rx(int radio_num); + +    //--------------------- +    // Finite Acquisitions +    //--------------------- + +    test.start_test("Rx (finite)", 50us); + +    // Set packet length +    write_radio(radio_num, REG_RX_MAX_WORDS_PER_PKT, WPP); + +    // Grab and verify a partial packet +    start_rx(radio_num, WPP/2); +    check_rx(radio_num, WPP/2); + +    // Grab a minimally-sized packet +    start_rx(radio_num, 1); +    check_rx(radio_num, 1); + +    // Grab and verify several packets +    start_rx(radio_num, WPP*15/2); +    check_rx(radio_num, WPP*15/2); + +    // Wait long enough to receive another packet and then make sure we didn't  +    // receive anything. That is, make sure Rx actually stopped. +    #MAX_PKT_WAIT; +    `ASSERT_ERROR( +      blk_ctrl.num_received(radio_num) == 0, +      "Received more packets than expected" +    ); + +    test.end_test(); + + +    //------------------------- +    // Continuous Acquisitions +    //------------------------- + +    test.start_test("Rx (continuous)", 100us); + +    start_rx(radio_num); + +    // Grab and verify several packets +    check_rx(radio_num, WPP*7); +    stop_rx(radio_num); + +    // Grab and discard any remaining packets +    do begin +      while (blk_ctrl.num_received(radio_num) != 0) begin +        ChdrPacket #(CHDR_W) chdr_packet; +        blk_ctrl.get_chdr(radio_num, chdr_packet); +      end +      #MAX_PKT_WAIT; +    end while (blk_ctrl.num_received(radio_num) != 0); + +    test.end_test(); + + +    //-------------------------- +    // Finite Timed Acquisition +    //-------------------------- + +    begin +      ChdrPacket #(CHDR_W) chdr_packet; +      chdr_word_t expected_time; + +      test.start_test("Rx (finite, timed)", 100us); + +      // Send Rx command with time in the future +      expected_time = radio_time + 2000; +      start_rx_timed(radio_num, WPP, expected_time); + +      // Take a peak at the timestamp in the received packet to check it +      blk_ctrl.peek_chdr(radio_num, chdr_packet); +      `ASSERT_ERROR( +        chdr_packet.timestamp == expected_time, +        "Received packet didn't have expected timestamp" +      ); + +      // Verify the packet data +      check_rx(radio_num, WPP); +      test.end_test(); +    end + + +    //------------------------------ +    // Continuous Timed Acquisition +    //------------------------------ + +    begin +      ChdrPacket #(CHDR_W) chdr_packet; +      chdr_word_t expected_time; + +      test.start_test("Rx (continuous, timed)", 100us); + +      // Send Rx command with time in the future +      expected_time = radio_time + 2000; +      start_rx_timed(radio_num, 0, expected_time); + +      // Take a peak at the timestamp in the received packet to check it +      blk_ctrl.peek_chdr(radio_num, chdr_packet); +      `ASSERT_ERROR( +        chdr_packet.timestamp == expected_time, +        "Received packet didn't have expected timestamp" +      ); + +      // Verify a few packets +      check_rx(radio_num, WPP*3); +      stop_rx(radio_num); + +      // Grab and discard any remaining packets +      do begin +        while (blk_ctrl.num_received(radio_num) != 0) begin +          ChdrPacket #(CHDR_W) chdr_packet; +          blk_ctrl.get_chdr(radio_num, chdr_packet); +        end +        #(MAX_PKT_WAIT); +      end while (blk_ctrl.num_received(radio_num) != 0); + +      test.end_test(); +    end + + +    //------------- +    // Rx Overflow +    //------------- +    begin +      logic [31:0] val; + +      test.start_test("Rx (now, overflow)", 200us); + +      // Configure the error reporting registers +      write_radio(radio_num, REG_RX_ERR_PORT, TX_ERR_DST_PORT); +      write_radio(radio_num, REG_RX_ERR_REM_PORT, TX_ERR_REM_DST_PORT); +      write_radio(radio_num, REG_RX_ERR_REM_EPID, TX_ERR_REM_DST_EPID); +      write_radio(radio_num, REG_RX_ERR_ADDR, TX_ERR_ADDRESS); + +      // Stall the BFM to force a backup of data +      blk_ctrl.set_slave_stall_prob(radio_num, 100); + +      // Acquire continuously until we get an error +      start_rx(radio_num); + +      // Check that we're acquiring +      read_radio(radio_num, REG_RX_STATUS, val); +      `ASSERT_ERROR( +        val[CMD_FIFO_SPACE_POS +: CMD_FIFO_SPACE_LEN] != CMD_FIFO_SPACE_MAX, +        "Rx radio reports that it is not busy" +      ); + +      // Verify that we receive an error +      check_error(ERR_RX_OVERRUN); + +      // Restore the BFM stall probability +      blk_ctrl.set_slave_stall_prob(radio_num, STALL_PROB); + +      // Verify that Rx stopped +      read_radio(radio_num, REG_RX_STATUS, val); +      `ASSERT_ERROR( +        val[CMD_FIFO_SPACE_POS +: CMD_FIFO_SPACE_LEN] == CMD_FIFO_SPACE_MAX, +        "Rx radio reports that it is still busy after overflow" +      ); + +      // Discard any packets we received. Rx should eventually stop  +      // automatically after an overflow. +      do begin +        while (blk_ctrl.num_received(radio_num) != 0) begin +          ChdrPacket #(CHDR_W) chdr_packet; +          blk_ctrl.get_chdr(radio_num, chdr_packet); +        end +        #(MAX_PKT_WAIT); +      end while (blk_ctrl.num_received(radio_num) != 0); + +     test.end_test(); +    end + + +    //-------------- +    // Late Command +    //-------------- + +    test.start_test("Rx (timed, late)", 100us); + +    start_rx_timed(radio_num, WPP, radio_time); +    check_error(ERR_RX_LATE_CMD); + +    // Late command should be ignored. Make sure we didn't receive any packets. +    begin +      ChdrPacket #(CHDR_W) chdr_packet; +      #(MAX_PKT_WAIT); +      `ASSERT_ERROR( +        blk_ctrl.num_received(radio_num) == 0, +        "Packets received for late Rx command" +      ); + +      // Discard any remaining packets +      while (blk_ctrl.num_received(radio_num)) blk_ctrl.get_chdr(radio_num, chdr_packet); +    end + +    test.end_test(); + + +    //--------------- +    // Command Queue +    //--------------- + +    test.start_test("Rx (queue multiple commands)"); + +    begin +      logic [31:0] expected, val; + +      // Send one continuous command and verify the queue fullness +      start_rx(radio_num); +      expected = CMD_FIFO_SPACE_MAX-1; +      read_radio(radio_num, REG_RX_STATUS, val); +      `ASSERT_ERROR( +        val[CMD_FIFO_SPACE_POS+:CMD_FIFO_SPACE_LEN] == expected,  +        "CMD_FIFO_SPACE did not decrement" +      ); + +      // Fill the command FIFO, going one over +      for (int i = 0; i < CMD_FIFO_SPACE_MAX; i++) begin +        start_rx(radio_num, WPP); +      end +      expected = 0; +      read_radio(radio_num, REG_RX_STATUS, val); +      `ASSERT_ERROR( +        val[CMD_FIFO_SPACE_POS+:CMD_FIFO_SPACE_LEN] == expected,  +        "CMD_FIFO_SPACE did not reach 0" +      ); + +      // Issue stop command and verify that the FIFO empties +      stop_rx(radio_num); +      expected = CMD_FIFO_SPACE_MAX; +      read_radio(radio_num, REG_RX_STATUS, val); +      `ASSERT_ERROR( +        val[CMD_FIFO_SPACE_POS+:CMD_FIFO_SPACE_LEN] == expected,  +        "CMD_FIFO_SPACE did not return to max" +      ); + +      // Grab and discard any remaining packets +      do begin +        while (blk_ctrl.num_received(radio_num) != 0) begin +          ChdrPacket #(CHDR_W) chdr_packet; +          blk_ctrl.get_chdr(radio_num, chdr_packet); +        end +        #MAX_PKT_WAIT; +      end while (blk_ctrl.num_received(radio_num) != 0); + +      // Queue several long commands back-to-back and make sure they all  +      // complete. The lengths are unique to ensure we execute the right  +      // commands in the expected order. +      for (int i = 0; i < 3; i++) start_rx(radio_num, WPP*20+i); +      for (int i = 0; i < 3; i++) check_rx(radio_num, WPP*20+i); + +      // Make sure we don't get any more data +      do begin +        while (blk_ctrl.num_received(radio_num) != 0) begin +          `ASSERT_ERROR(0, "Received unexpected packets"); +        end +        #MAX_PKT_WAIT; +      end while (blk_ctrl.num_received(radio_num) != 0); +    end + +    test.end_test(); + +  endtask : test_rx + + + +  task automatic test_tx(int radio_num); +    logic [RADIO_W-1:0] radio_data; +    enum { WAIT_FOR_EOP, WAIT_FOR_EOB } policy; + +    //------- +    // Setup +    //------- + +    test.start_test("Tx Init", 50us); + +    // Configure the error reporting registers +    write_radio(radio_num, REG_TX_ERR_PORT, TX_ERR_DST_PORT); +    write_radio(radio_num, REG_TX_ERR_REM_PORT, TX_ERR_REM_DST_PORT); +    write_radio(radio_num, REG_TX_ERR_REM_EPID, TX_ERR_REM_DST_EPID); +    write_radio(radio_num, REG_TX_ERR_ADDR, TX_ERR_ADDRESS); + +    test.end_test(); + + +    //--------------- +    // Test Tx (now) +    //--------------- + +    test.start_test("Tx (now)", 50us); + +    // Grab and verify a partial packet +    start_tx(radio_num, WPP*3/4); +    check_tx(radio_num, WPP*3/4); +    check_error(ERR_TX_EOB_ACK); + +    // Grab and verify multiple packets +    start_tx(radio_num, WPP*3/2); +    check_tx(radio_num, WPP*3/2); +    check_error(ERR_TX_EOB_ACK); + +    // Test a minimally-sized packet +    start_tx(radio_num, 1); +    check_tx(radio_num, 1); +    check_error(ERR_TX_EOB_ACK); + +    test.end_test(); + + +    //--------------------- +    // Test Tx (underflow) +    //--------------------- + +    test.start_test("Tx (now, underflow)", 50us); + +    // Send some bursts without EOB +    start_tx(radio_num, WPP*3/4, 1, 0);  // Skip EOB +    check_tx(radio_num, WPP*3/4); +    check_error(ERR_TX_UNDERRUN); + +    start_tx(radio_num, WPP*2, 1, 0);  // Skip EOB +    check_tx(radio_num, WPP*2); +    check_error(ERR_TX_UNDERRUN); + +    test.end_test(); + + +    //----------------- +    // Test Tx (timed) +    //----------------- + +    test.start_test("Tx (timed)", 50us); + +    // Grab and verify a partial packet +    start_tx_timed(radio_num, WPP*3/4, radio_time + 200); +    check_tx_timed(radio_num, WPP*3/4, radio_time + 200); +    check_error(ERR_TX_EOB_ACK); + +    // Grab and verify whole packets +    start_tx_timed(radio_num, WPP*2, radio_time + 200); +    check_tx_timed(radio_num, WPP*2, radio_time + 200); +    check_error(ERR_TX_EOB_ACK); + +    test.end_test(); + + +    //----------------- +    // Test Tx (timed, underflow) +    //----------------- + +    test.start_test("Tx (timed, underflow)", 50us); + +    // Send some bursts without EOB +    start_tx_timed(radio_num, WPP*3/4, radio_time + 200, 1, 0); +    check_tx_timed(radio_num, WPP*3/4, radio_time + 200); +    check_error(ERR_TX_UNDERRUN); + +    start_tx_timed(radio_num, WPP*2, radio_time + 200, 1, 0); +    check_tx_timed(radio_num, WPP*2, radio_time + 200); +    check_error(ERR_TX_UNDERRUN); + +    test.end_test(); + + +    //--------------------------- +    // Test Tx (timed, late) +    //--------------------------- + +    test.start_test("Tx (timed, late)", 50us); + +    // Test each error policy +    policy = policy.first(); +    do begin +      // Set the policy +      if (policy == WAIT_FOR_EOP) begin +        write_radio(radio_num, REG_TX_ERROR_POLICY, TX_ERR_POLICY_PACKET); +      end else if (policy == WAIT_FOR_EOB) begin +        write_radio(radio_num, REG_TX_ERROR_POLICY, TX_ERR_POLICY_BURST); +      end + +// Commenting out the fork code for now due to Vivado 2018.3 bug. +//      radio_data = radio_tx_data[radio_num]; +//      fork : tx_fork +        // In this branch of the fork, we send the packets +        repeat (2) begin +          // Send late packets with random start value +          start_tx_timed(radio_num, WPP*3, 0, $urandom()); + +          if (policy == WAIT_FOR_EOP) begin +            // We should get three errors, one for each packet +            repeat (3) check_error(ERR_TX_LATE_DATA); +          end else if (policy == WAIT_FOR_EOB) begin +            // We should get one error for the entire burst +            check_error(ERR_TX_LATE_DATA); +          end +        end + +//        // The packets sent in the above branch of the fork should be  +//        // dropped. In this branch of the fork we make sure that the Tx  +//        // output doesn't change. +//        begin +//          forever begin +//            @(posedge radio_clk) +//            `ASSERT_ERROR( +//              radio_data === radio_tx_data[radio_num],  +//              "Radio Tx output changed when late Tx packet should have been ignored" +//            ); +//          end +//        end +//      join_any +// +//      // Stop checking the output +//      disable tx_fork; + +      policy = policy.next(); +    end while (policy != policy.first()); + +    // Make sure good transmissions can go through now. +    start_tx_timed(radio_num, WPP, radio_time + 200); +    check_tx_timed(radio_num, WPP, radio_time + 200); +    check_error(ERR_TX_EOB_ACK); + +    test.end_test(); + +  endtask : test_tx + + + +  // Test internal loopback and idle value +  task automatic test_loopback_and_idle(int radio_num); +    int              byte_length; +    chdr_word_t      data[$]; +    bit [ITEM_W-1:0] idle; + +    //---------------------------- +    // Use IDLE value to loopback +    //---------------------------- + +    test.start_test("Idle Loopback", 50us); + +    // Turn on loopback +    write_radio(radio_num, REG_LOOPBACK_EN, 1); + +    // This test ensures we get the Tx output on Rx and not the TB's simulated  +    // radio data. It also tests updating the idle value. Run the test twice to  +    // make sure the IDLE value updates. +    repeat (2) begin +      // Set idle value +      idle = $urandom(); +      write_radio(radio_num, REG_TX_IDLE_VALUE, idle); + +      // Grab a radio word and check that it equals the IDLE value +      write_radio_64(radio_num, REG_RX_CMD_NUM_WORDS_LO, 1); +      write_radio(radio_num, REG_RX_CMD, RX_CMD_FINITE); +      blk_ctrl.recv(radio_num, data, byte_length); + +      // Check the length +      `ASSERT_ERROR(byte_length == RADIO_W/8, "Didn't receive expected length"); + +      // Check the payload +      foreach (data[i]) begin +        chdr_word_t word; +        word = data[i];   // Work around Vivado 2018.3 issue +        `ASSERT_ERROR( +          word == {$bits(chdr_word_t)/ITEM_W{idle}}, +          "Loopback data didn't match expected" +        ); +      end +    end + +    test.end_test(); + + +    //--------------------- +    // Loopback Tx packets +    //--------------------- + +    test.start_test("Tx Loopback", 50us); + +    // This test ensures that loopback isn't reordering words or anything else +    // unexpected. + +    // Configure the Tx error reporting registers +    write_radio(radio_num, REG_TX_ERR_PORT,     TX_ERR_DST_PORT); +    write_radio(radio_num, REG_TX_ERR_REM_PORT, TX_ERR_REM_DST_PORT); +    write_radio(radio_num, REG_TX_ERR_REM_EPID, TX_ERR_REM_DST_EPID); +    write_radio(radio_num, REG_TX_ERR_ADDR,     TX_ERR_ADDRESS); + +    // Set packet length +    write_radio(radio_num, REG_RX_MAX_WORDS_PER_PKT, WPP); + +    // Loopback a few packets, back-to-back. This code has a race condition  +    // since there's a delay between when we start Tx and when Rx starts, due  +    // to how long it takes to write the Rx registers. Therefore, we transmit a  +    // lot more packets than we receive to ensure we're still transmitting by  +    // the time we receive. +    start_tx(radio_num, WPP*16); +    start_rx(radio_num, WPP*2); + +    // Check the results +    check_rx(radio_num, WPP*2); +    check_error(ERR_TX_EOB_ACK); + +    // Turn off loopback +    write_radio(radio_num, REG_LOOPBACK_EN, 0); + +    test.end_test(); +  endtask : test_loopback_and_idle; + + + +  //--------------------------------------------------------------------------- +  // Test Process +  //--------------------------------------------------------------------------- + +  timeout_t timeout; + +  initial begin : main +    string tb_name; + +    //------------------------------------------------------------------------- +    // Initialization +    //------------------------------------------------------------------------- + +    // Generate a string for the name of this instance of the testbench +    tb_name = $sformatf( +      "rfnoc_block_radio_tb\nCHDR_W = %0D, ITEM_W = %0D, NIPC = %0D, NUM_PORTS = %0D, STALL_PROB = %0D, STB_PROB = %0D, TEST_REGS = %0D", +      CHDR_W, ITEM_W, NIPC, NUM_PORTS, STALL_PROB, STB_PROB, TEST_REGS +    ); + +    test.start_tb(tb_name); + +    // 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. +    rfnoc_chdr_clk_gen.start(); +    rfnoc_ctrl_clk_gen.start(); +    radio_clk_gen.start(); + +    // Setup and start the stream endpoint BFM +    blk_ctrl = new(backend, m_ctrl, s_ctrl); +    for (int i = 0; i < NUM_PORTS; i++) begin +      // I'd love to do this: +      //     void'(blk_ctrl.add_master_data_port(m_chdr[i])); +      //     void'(blk_ctrl.add_slave_data_port(s_chdr[i])); +      // But interface indices must be constant. So instead, we use a semaphore  +      // to trigger port initialization and control the order of initialization  +      // in the generate block gen_radio_connections. + +      // Put the port number in the semaphore to cause its initializer to run +      port_sem.put(i+1); +      // Delay to allow gen_radio_connections to run +      #0; +      // Get the port number again to know when it's done +      port_sem.get(i+1); + +      // Set the CHDR BFM stall probability +      blk_ctrl.set_master_stall_prob(i, STALL_PROB); +      blk_ctrl.set_slave_stall_prob(i, STALL_PROB); +    end +    blk_ctrl.run(); + + +    //------------------------------------------------------------------------- +    // Reset +    //------------------------------------------------------------------------- + +    test.start_test("Flush block then reset it", 10us); +    blk_ctrl.flush_and_reset(); +    test.end_test(); + + +    //------------------------------------------------------------------------- +    // Test Sequences +    //------------------------------------------------------------------------- + +    // Run register tests first, since they check that initial values are  +    // correct. + +    test_block_info(); +    if (TEST_REGS) test_shared_registers(); + +    for (int radio_num = 0; radio_num < NUM_PORTS; radio_num++) begin +      $display("************************************************************"); +      $display("Testing Radio Channel %0d", radio_num); +      $display("************************************************************"); +      if (TEST_REGS) begin +        test_general_registers(radio_num); +        test_rx_registers(radio_num); +        test_tx_registers(radio_num); +      end +      test_rx(radio_num); +      test_tx(radio_num); +      test_loopback_and_idle(radio_num); +    end + + +    //------------------------------------------------------------------------- +    // Finish +    //------------------------------------------------------------------------- + +    // 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 +    rfnoc_chdr_clk_gen.kill(); +    rfnoc_ctrl_clk_gen.kill(); +    radio_clk_gen.kill(); + +  end : main + +endmodule : rfnoc_block_radio_tb diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/rx_frontend_gen3.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/rx_frontend_gen3.v new file mode 100644 index 000000000..54529136b --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/rx_frontend_gen3.v @@ -0,0 +1,246 @@ +// +// Copyright 2015 Ettus Research LLC +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +module rx_frontend_gen3 #( +  parameter SR_MAG_CORRECTION = 0, +  parameter SR_PHASE_CORRECTION = 1, +  parameter SR_OFFSET_I = 2, +  parameter SR_OFFSET_Q = 3, +  parameter SR_IQ_MAPPING = 4, +  parameter SR_HET_PHASE_INCR = 5, +  parameter BYPASS_DC_OFFSET_CORR = 0, +  parameter BYPASS_IQ_COMP = 0, +  parameter BYPASS_REALMODE_DSP = 0, +  parameter DEVICE = "7SERIES" +)( +  input clk, input reset, input sync_in, +  input set_stb, input [7:0] set_addr, input [31:0] set_data, +  input adc_stb, input [15:0] adc_i, input [15:0] adc_q, +  output rx_stb, output [15:0] rx_i, output [15:0] rx_q +); + +  wire               realmode; +  wire               swap_iq; +  wire               invert_i; +  wire               invert_q; +  wire               realmode_decim; +  wire               bypass_all; +  wire [1:0]         iq_map_reserved; +  wire [17:0]        mag_corr, phase_corr; +  wire               phase_dir; +  wire               phase_sync; + +  reg  [23:0]        adc_i_mux, adc_q_mux; +  reg                adc_mux_stb; +  wire [23:0]        adc_i_ofs, adc_q_ofs, adc_i_comp, adc_q_comp; +  reg  [23:0]        adc_i_ofs_dly, adc_q_ofs_dly; +  wire               adc_ofs_stb, adc_comp_stb; +  reg  [1:0]         adc_ofs_stb_dly; +  wire [23:0]        adc_i_dsp, adc_q_dsp; +  wire               adc_dsp_stb; +  wire [35:0]        corr_i, corr_q; +  wire [15:0]        rx_i_out, rx_q_out; + +  /******************************************************** +  ** Settings Bus Registers +  ********************************************************/ +  setting_reg #(.my_addr(SR_MAG_CORRECTION),.width(18)) sr_mag_corr ( +    .clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr), +    .in(set_data),.out(mag_corr),.changed()); + +  setting_reg #(.my_addr(SR_PHASE_CORRECTION),.width(18)) sr_phase_corr ( +    .clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr), +    .in(set_data),.out(phase_corr),.changed()); + +  setting_reg #(.my_addr(SR_IQ_MAPPING), .width(8)) sr_mux_sel ( +    .clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr), +    .in(set_data),.out({bypass_all,iq_map_reserved,realmode_decim,invert_i,invert_q,realmode,swap_iq}),.changed()); + +  // Setting reg: 1 bit to set phase direction: default to 0: +  //   direction bit == 0: the phase is increased by pi/2 (counter clockwise) +  //   direction bit == 1: the phase is increased by -pi/2 (clockwise) +  setting_reg #(.my_addr(SR_HET_PHASE_INCR), .width(1)) sr_phase_dir ( +    .clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr), +    .in(set_data),.out(phase_dir),.changed(phase_sync)); + +  /******************************************************** +  ** IQ Mapping (swapping, inversion, real-mode) +  ********************************************************/ +  // MUX so we can do realmode signals on either input +  always @(posedge clk) begin +    if (swap_iq) begin +      adc_i_mux[23:8] <= invert_q ? ~adc_q   : adc_q; +      adc_q_mux[23:8] <= realmode ? 16'd0 : invert_i ? ~adc_i : adc_i; +    end else begin +      adc_i_mux[23:8] <= invert_i ? ~adc_i   : adc_i; +      adc_q_mux[23:8] <= realmode ? 16'd0 : invert_q ? ~adc_q : adc_q; +    end +    adc_mux_stb <= adc_stb; +    adc_i_mux[7:0] <= 8'd0; +    adc_q_mux[7:0] <= 8'd0; +  end + +  /******************************************************** +  ** DC offset Correction +  ********************************************************/ +  generate +    if (BYPASS_DC_OFFSET_CORR == 0) begin + +      rx_dcoffset #(.WIDTH(24),.ADDR(SR_OFFSET_I)) rx_dcoffset_i ( +        .clk(clk),.rst(reset),.set_stb(set_stb),.set_addr(set_addr),.set_data(set_data), +        .in_stb(adc_mux_stb),.in(adc_i_mux), +        .out_stb(adc_ofs_stb),.out(adc_i_ofs)); +      rx_dcoffset #(.WIDTH(24),.ADDR(SR_OFFSET_Q)) rx_dcoffset_q ( +        .clk(clk),.rst(reset),.set_stb(set_stb),.set_addr(set_addr),.set_data(set_data), +        .in_stb(adc_mux_stb),.in(adc_q_mux), +        .out_stb(),.out(adc_q_ofs)); + +    end else begin +      assign adc_ofs_stb = adc_mux_stb; +      assign adc_i_ofs   = adc_i_mux; +      assign adc_q_ofs   = adc_q_mux; +    end +  endgenerate + +  /******************************************************** +  ** IQ Imbalance Compensation +  ********************************************************/ +  generate +    if (BYPASS_IQ_COMP == 0) begin + +      mult_add_clip #( +        .WIDTH_A(18), +        .BIN_PT_A(17), +        .WIDTH_B(18), +        .BIN_PT_B(17), +        .WIDTH_C(24), +        .BIN_PT_C(23), +        .WIDTH_O(24), +        .BIN_PT_O(23), +        .LATENCY(2) +      ) mult_i ( +        .clk(clk), +        .reset(reset), +        .CE(1'b1), +        .A(adc_i_ofs[23:6]), +        .B(mag_corr), +        .C(adc_i_ofs), +        .O(adc_i_comp) +      ); + +      mult_add_clip #( +        .WIDTH_A(18), +        .BIN_PT_A(17), +        .WIDTH_B(18), +        .BIN_PT_B(17), +        .WIDTH_C(24), +        .BIN_PT_C(23), +        .WIDTH_O(24), +        .BIN_PT_O(23), +        .LATENCY(2) +      ) mult_q ( +        .clk(clk), +        .reset(reset), +        .CE(1'b1), +        .A(adc_i_ofs[23:6]), +        .B(phase_corr), +        .C(adc_q_ofs), +        .O(adc_q_comp) +      ); + +      // Delay to match path latencies +      always @(posedge clk) begin +        if (reset) begin +          adc_ofs_stb_dly <= 2'b0; +        end else begin +          adc_ofs_stb_dly <= {adc_ofs_stb_dly[0], adc_ofs_stb}; +        end +      end + +      assign adc_comp_stb = adc_ofs_stb_dly[1]; + +    end else begin +      assign adc_comp_stb = adc_ofs_stb; +      assign adc_i_comp   = adc_i_ofs; +      assign adc_q_comp   = adc_q_ofs; +    end +  endgenerate + +  /******************************************************** +  ** Realmode DSP: +  *  - Heterodyne frequency translation +  *  - Realmode decimation (by 2) +  ********************************************************/ +  generate +    if (BYPASS_REALMODE_DSP == 0) begin + +      wire [24:0] adc_i_dsp_cout, adc_q_dsp_cout; +      wire [23:0] adc_i_cclip, adc_q_cclip; +      wire [23:0] adc_i_hb, adc_q_hb; +      wire [23:0] adc_i_dec, adc_q_dec; +      wire        adc_dsp_cout_stb; +      wire        adc_cclip_stb; +      wire        adc_hb_stb; + +      wire valid_hbf0; +      wire valid_hbf1; +      wire valid_dec0; +      wire valid_dec1; + +      // 90 degree mixer +      quarter_rate_downconverter #(.WIDTH(24)) qr_dc_i( +        .clk(clk), .reset(reset || sync_in), .phase_sync(phase_sync), +        .i_tdata({adc_i_comp, adc_q_comp}), .i_tlast(1'b1), .i_tvalid(adc_comp_stb), .i_tready(), +        .o_tdata({adc_i_dsp_cout, adc_q_dsp_cout}), .o_tlast(), .o_tvalid(adc_dsp_cout_stb), .o_tready(1'b1), +        .dirctn(phase_dir)); + +      // Double FIR and decimator block +      localparam HB_COEFS = {-18'd62, 18'd0, 18'd194, 18'd0, -18'd440, 18'd0, 18'd855, 18'd0, -18'd1505, 18'd0, 18'd2478, 18'd0, +        -18'd3900, 18'd0, 18'd5990, 18'd0, -18'd9187, 18'd0, 18'd14632, 18'd0, -18'd26536, 18'd0, 18'd83009, 18'd131071, 18'd83009, +        18'd0, -18'd26536, 18'd0, 18'd14632, 18'd0, -18'd9187, 18'd0, 18'd5990, 18'd0, -18'd3900, 18'd0, 18'd2478, 18'd0, -18'd1505, +        18'd0, 18'd855, 18'd0, -18'd440, 18'd0, 18'd194, 18'd0, -18'd62}; + +      axi_fir_filter_dec #( +          .WIDTH(24), +          .COEFF_WIDTH(18), +          .NUM_COEFFS(47), +          .COEFFS_VEC(HB_COEFS), +          .BLANK_OUTPUT(0) +        ) ffd0 ( +        .clk(clk), .reset(reset || sync_in), + +        .i_tdata({adc_i_dsp_cout, adc_q_dsp_cout}), +        .i_tlast(1'b1), +        .i_tvalid(adc_dsp_cout_stb), +        .i_tready(), + +        .o_tdata({adc_i_dec, adc_q_dec}), +        .o_tlast(), +        .o_tvalid(adc_hb_stb), +        .o_tready(1'b1)); + +      assign adc_dsp_stb = realmode_decim ? adc_hb_stb : adc_comp_stb; +      assign adc_i_dsp   = realmode_decim ? adc_i_dec : adc_i_comp; +      assign adc_q_dsp   = realmode_decim ? adc_q_dec : adc_q_comp; + +    end else begin +      assign adc_dsp_stb = adc_comp_stb; +      assign adc_i_dsp   = adc_i_comp; +      assign adc_q_dsp   = adc_q_comp; +    end +  endgenerate + +  // Round to short complex (sc16) +  round_sd #(.WIDTH_IN(24),.WIDTH_OUT(16)) round_i ( +    .clk(clk),.reset(reset), .in(adc_i_dsp),.strobe_in(adc_dsp_stb), .out(rx_i_out), .strobe_out(rx_stb)); +  round_sd #(.WIDTH_IN(24),.WIDTH_OUT(16)) round_q ( +    .clk(clk),.reset(reset), .in(adc_q_dsp),.strobe_in(adc_dsp_stb), .out(rx_q_out), .strobe_out()); + +  assign rx_i = bypass_all ? adc_i : rx_i_out; +  assign rx_q = bypass_all ? adc_q : rx_q_out; + +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/sim_radio_gen.sv b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/sim_radio_gen.sv new file mode 100644 index 000000000..a6f827f8f --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/sim_radio_gen.sv @@ -0,0 +1,104 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: sim_radio_gen +// +// Description: Generate radio data for simulation purposes. The strobe pattern  +// is random, which is not like a normal radio but covers every possibility.  +// The data pattern is an incrementing sequence of samples, with each channel  +// starting at a different value to differentiate them. Strobe and time are  +// common between channels. +// + +module sim_radio_gen #( +  parameter int NSPC         = 1,  // Number of samples per clock cycle +  parameter int SAMP_W       = 32, // Length of each radio sample +  parameter int NUM_CHANNELS = 1,  // Number of radio RX ports +  parameter int STB_PROB     = 50, // Probability of STB being asserted on each clock cycle +  parameter int INCREMENT    = 2,  // Amount by which to increment +  parameter int PPS_PERIOD   = 50  // Period of the PPS output +) ( +  input  bit                                radio_clk, +  input  bit                                radio_rst, +  output bit [NUM_CHANNELS*SAMP_W*NSPC-1:0] radio_rx_data, +  output bit [            NUM_CHANNELS-1:0] radio_rx_stb, +  output bit [                        63:0] radio_time, +  output bit                                radio_pps +); + +  localparam int RADIO_W = SAMP_W*NSPC; +  typedef bit [RADIO_W-1:0] radio_t;    // Radio output word +  typedef bit [SAMP_W-1:0] sample_t;    // Single sample + +  initial assert (PPS_PERIOD % INCREMENT == 0) else +    $fatal(1, "PPS_PERIOD must be a multiple of INCREMENT"); + + +  // Generate an initial value all radio channels +  function radio_t [NUM_CHANNELS-1:0] radio_init(); +    radio_t [NUM_CHANNELS-1:0] ret_val; +    +    for (int n = 0; n < NUM_CHANNELS; n++) begin +      sample_t sample; + +      // Calculate the value of first sample in this radio channel +      sample = sample_t'((2.0 ** SAMP_W) / NUM_CHANNELS * n); + +      // Calculate the value of subsequent samples in the channel +      for (int s = 0; s < NSPC; s++) begin +        ret_val[n][s*SAMP_W +: SAMP_W] = sample + s; +      end +    end + +    return ret_val; +  endfunction : radio_init + + +  //--------------------------------------------------------------------------- +  // Radio Data Generation +  //--------------------------------------------------------------------------- + +  radio_t [NUM_CHANNELS-1:0] data = radio_init(); + +  assign radio_rx_data = data; + +  always @(posedge radio_clk) begin : radio_data_count_reg +    if (radio_rst) begin +      data         <= radio_init(); +      radio_rx_stb <= '0; +    end else begin +      radio_rx_stb <= '0; +      if ($urandom_range(100) < STB_PROB) begin +        for (int n = 0; n < NUM_CHANNELS; n++) begin +          for (int s = 0; s < NSPC; s++) begin +            data[n][s*SAMP_W +: SAMP_W] <= data[n][s*SAMP_W +: SAMP_W] + NSPC; +          end +        end +        radio_rx_stb <= '1; +      end +    end +  end : radio_data_count_reg + + +  //--------------------------------------------------------------------------- +  // Radio Time +  //--------------------------------------------------------------------------- + +  always @(posedge radio_clk) begin +    if (radio_rst) begin +      radio_time <= 64'b0; +      radio_pps  <=  1'b0; +    end else begin +      radio_pps  <= 1'b0; +      if (radio_rx_stb[0]) begin +        radio_time <= radio_time + INCREMENT; +        if (radio_time % PPS_PERIOD == 0 && radio_time != 0) begin +          radio_pps <= 1'b1; +        end +      end +    end +  end + +endmodule : sim_radio_gen
\ No newline at end of file diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/tx_frontend_gen3.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/tx_frontend_gen3.v new file mode 100644 index 000000000..f5435787d --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/tx_frontend_gen3.v @@ -0,0 +1,173 @@ +// +// Copyright 2015 Ettus Research LLC +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +module tx_frontend_gen3 #( +  parameter SR_OFFSET_I = 0, +  parameter SR_OFFSET_Q = 1, +  parameter SR_MAG_CORRECTION = 2, +  parameter SR_PHASE_CORRECTION = 3, +  parameter SR_MUX = 4, +  parameter BYPASS_DC_OFFSET_CORR = 0, +  parameter BYPASS_IQ_COMP = 0, +  parameter DEVICE = "7SERIES" +)( +  input clk, input reset, +  input set_stb, input [7:0] set_addr, input [31:0] set_data, +  input tx_stb, input [15:0] tx_i, input [15:0] tx_q, +  output reg dac_stb, output reg [15:0] dac_i, output reg [15:0] dac_q +); + +  wire [23:0]        i_dco, q_dco; +  wire [7:0]         mux_ctrl; +  wire [17:0]        mag_corr, phase_corr; + +  wire [35:0]        corr_i, corr_q; +  reg  [1:0]         tx_stb_dly; +  reg  [23:0]        tx_i_dly, tx_q_dly; +  wire               tx_comp_stb, tx_ofs_stb; +  wire [23:0]        tx_i_comp, tx_q_comp, tx_i_ofs, tx_q_ofs; +  wire               tx_round_stb; +  wire [15:0]        tx_i_round, tx_q_round; + +  /******************************************************** +  ** Settings Registers +  ********************************************************/ +  setting_reg #(.my_addr(SR_OFFSET_I), .width(24)) sr_i_dc_offset ( +    .clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr), +    .in(set_data),.out(i_dco),.changed()); + +  setting_reg #(.my_addr(SR_OFFSET_Q), .width(24)) sr_q_dc_offset ( +    .clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr), +    .in(set_data),.out(q_dco),.changed()); + +  setting_reg #(.my_addr(SR_MAG_CORRECTION),.width(18)) sr_mag_corr ( +    .clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr), +    .in(set_data),.out(mag_corr),.changed()); + +  setting_reg #(.my_addr(SR_PHASE_CORRECTION),.width(18)) sr_phase_corr ( +    .clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr), +    .in(set_data),.out(phase_corr),.changed()); + +  setting_reg #(.my_addr(SR_MUX), .width(8), .at_reset(8'h10)) sr_mux_ctrl ( +    .clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr), +    .in(set_data),.out(mux_ctrl),.changed()); + +  /******************************************************** +  ** DSP +  ********************************************************/ +  // I/Q compensation with option to bypass +  generate +    if (BYPASS_IQ_COMP == 0) begin + +      mult_add_clip #( +        .WIDTH_A(16), +        .BIN_PT_A(15), +        .WIDTH_B(18), +        .BIN_PT_B(17), +        .WIDTH_C(16), +        .BIN_PT_C(15), +        .WIDTH_O(24), +        .BIN_PT_O(23), +        .LATENCY(2) +      ) mult_i ( +        .clk(clk), +        .reset(reset), +        .CE(1'b1), +        .A(tx_i), +        .B(mag_corr), +        .C(tx_i), +        .O(tx_i_comp) +      ); + +      mult_add_clip #( +        .WIDTH_A(16), +        .BIN_PT_A(15), +        .WIDTH_B(18), +        .BIN_PT_B(17), +        .WIDTH_C(16), +        .BIN_PT_C(15), +        .WIDTH_O(24), +        .BIN_PT_O(23), +        .LATENCY(2) +      ) mult_q ( +        .clk(clk), +        .reset(reset), +        .CE(1'b1), +        .A(tx_i), +        .B(phase_corr), +        .C(tx_q), +        .O(tx_q_comp) +      ); + +      // Delay to match path latencies +      always @(posedge clk) begin +        if (reset) begin +          tx_stb_dly <= 2'b0; +        end else begin +          tx_stb_dly <= {tx_stb_dly[0], tx_stb}; +        end +      end + +      assign tx_comp_stb = tx_stb_dly[1]; + +    end else begin +      assign tx_comp_stb = tx_stb; +      assign tx_i_comp = {tx_i,8'd0}; +      assign tx_q_comp = {tx_q,8'd0}; +    end +  endgenerate + +  // DC offset correction +  generate +    if (BYPASS_DC_OFFSET_CORR == 0) begin +      add2_and_clip_reg #(.WIDTH(24)) add_dco_i ( +        .clk(clk), .rst(reset), .in1(i_dco), .in2(tx_i_comp), .strobe_in(tx_comp_stb), .sum(tx_i_ofs), .strobe_out(tx_ofs_stb)); +      add2_and_clip_reg #(.WIDTH(24)) add_dco_q ( +        .clk(clk), .rst(reset), .in1(q_dco), .in2(tx_q_comp), .strobe_in(tx_comp_stb), .sum(tx_q_ofs), .strobe_out()); +    end else begin +      assign tx_ofs_stb = tx_comp_stb; +      assign tx_i_ofs = tx_i_comp; +      assign tx_q_ofs = tx_q_comp; +    end +  endgenerate + +  // Round to short complex (sc16) +  round_sd #(.WIDTH_IN(24),.WIDTH_OUT(16)) round_i ( +    .clk(clk),.reset(reset), .in(tx_i_ofs),.strobe_in(tx_ofs_stb), .out(tx_i_round), .strobe_out(tx_round_stb)); +  round_sd #(.WIDTH_IN(24),.WIDTH_OUT(16)) round_q ( +    .clk(clk),.reset(reset), .in(tx_q_ofs),.strobe_in(tx_ofs_stb), .out(tx_q_round), .strobe_out()); + +  // Mux +  // Muxing logic matches that in tx_frontend.v, and what tx_frontend_core_200.cpp expects. +  // +  // mux_ctrl ! 0+0  ! 0+16 ! 1+0  ! 1+16 +  // =========!======!======!======!======== +  // DAC_I    ! tx_i ! tx_i ! tx_q ! tx_q +  // DAC_Q    ! tx_i ! tx_q ! tx_i ! tx_q +  // +  // Most daughterboards will thus use 0x01 or 0x10 as the mux_ctrl value. +  always @(posedge clk) begin +    if (reset) begin +      dac_stb <= 1'b0; +      dac_i   <= 16'd0; +      dac_q   <= 16'd0; +    end else begin +      dac_stb <= tx_round_stb; +      case(mux_ctrl[3:0]) +        0 : dac_i <= tx_i_round; +        1 : dac_i <= tx_q_round; +        default : dac_i <= 0; +      endcase +      case(mux_ctrl[7:4]) +        0 : dac_q <= tx_i_round; +        1 : dac_q <= tx_q_round; +        default : dac_q <= 0; +      endcase +    end +  end + +endmodule  | 
