fpga: rfnoc: Add RFNoC Keep One in N block

Original-commit: b39021ac2e642c22349183b6bc697daae01da5e9
This commit is contained in:
Aaron Rossetto
2020-08-05 07:47:22 -05:00
parent 104a73e623
commit 466e61d3ef
7 changed files with 1432 additions and 0 deletions
@@ -0,0 +1,42 @@
#
# Copyright 2020 Ettus Research, A National Instruments Brand
#
# SPDX-License-Identifier: LGPL-3.0-or-later
#
#-------------------------------------------------
# Top-of-Makefile
#-------------------------------------------------
BASE_DIR = $(abspath ../../../../top)
# Include viv_sim_preample 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_keep_one_in_n_tb
SIM_SRCS = \
$(abspath rfnoc_block_keep_one_in_n_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
@@ -0,0 +1,24 @@
#
# Copyright 2020 Ettus Research, A National Instruments Brand
#
# SPDX-License-Identifier: LGPL-3.0-or-later
#
##################################################
# RFNoC Block Sources
##################################################
# Here, list all the files that are necessary to synthesize this block. Don't
# include testbenches!
# Make sure that the source files are nicely detectable by a regex. Best to put
# one on each line.
# The first argument to addprefix is the current path to this Makefile, so the
# path list is always absolute, regardless of from where we're including or
# calling this file. RFNOC_OOT_SRCS needs to be a simply expanded variable
# (not a recursively expanded variable), and we take care of that in the build
# infrastructure.
RFNOC_OOT_SRCS += $(addprefix $(dir $(abspath $(lastword $(MAKEFILE_LIST)))), \
rfnoc_block_keep_one_in_n.v \
rfnoc_keep_one_in_n.v \
rfnoc_keep_one_in_n_regs.vh \
noc_shell_keep_one_in_n.v \
)
@@ -0,0 +1,304 @@
//
// Copyright 2020 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: noc_shell_keep_one_in_n
//
// Description:
//
// This is a tool-generated NoC-shell for the keep_one_in_n block.
// See the RFNoC specification for more information about NoC shells.
//
// Parameters:
//
// THIS_PORTID : Control crossbar port to which this block is connected
// CHDR_W : AXIS-CHDR data bus width
// MTU : Maximum transmission unit (i.e., maximum packet size in
//
`default_nettype none
module noc_shell_keep_one_in_n #(
parameter [9:0] THIS_PORTID = 10'd0,
parameter CHDR_W = 64,
parameter NUM_PORTS = 1,
parameter [5:0] MTU = 10
) (
//---------------------
// Framework Interface
//---------------------
// RFNoC Framework Clocks
input wire rfnoc_chdr_clk,
input wire rfnoc_ctrl_clk,
input wire ce_clk,
// NoC Shell Generated Resets
output wire rfnoc_chdr_rst,
output wire rfnoc_ctrl_rst,
output wire ce_rst,
// RFNoC Backend Interface
input wire [511:0] rfnoc_core_config,
output wire [511:0] rfnoc_core_status,
// AXIS-CHDR Input Ports (from framework)
input wire [(0+NUM_PORTS)*CHDR_W-1:0] s_rfnoc_chdr_tdata,
input wire [(0+NUM_PORTS)-1:0] s_rfnoc_chdr_tlast,
input wire [(0+NUM_PORTS)-1:0] s_rfnoc_chdr_tvalid,
output wire [(0+NUM_PORTS)-1:0] s_rfnoc_chdr_tready,
// AXIS-CHDR Output Ports (to framework)
output wire [(0+NUM_PORTS)*CHDR_W-1:0] m_rfnoc_chdr_tdata,
output wire [(0+NUM_PORTS)-1:0] m_rfnoc_chdr_tlast,
output wire [(0+NUM_PORTS)-1:0] m_rfnoc_chdr_tvalid,
input wire [(0+NUM_PORTS)-1:0] m_rfnoc_chdr_tready,
// AXIS-Ctrl Control Input Port (from framework)
input wire [31:0] s_rfnoc_ctrl_tdata,
input wire s_rfnoc_ctrl_tlast,
input wire s_rfnoc_ctrl_tvalid,
output wire s_rfnoc_ctrl_tready,
// AXIS-Ctrl Control Output Port (to framework)
output wire [31:0] m_rfnoc_ctrl_tdata,
output wire m_rfnoc_ctrl_tlast,
output wire m_rfnoc_ctrl_tvalid,
input wire m_rfnoc_ctrl_tready,
//---------------------
// Client Interface
//---------------------
// CtrlPort Clock and Reset
output wire ctrlport_clk,
output wire ctrlport_rst,
// CtrlPort Master
output wire m_ctrlport_req_wr,
output wire m_ctrlport_req_rd,
output wire [19:0] m_ctrlport_req_addr,
output wire [31:0] m_ctrlport_req_data,
input wire m_ctrlport_resp_ack,
input wire [31:0] m_ctrlport_resp_data,
// AXI-Stream Data Clock and Reset
output wire axis_data_clk,
output wire axis_data_rst,
// Data Stream to User Logic: in
output wire [NUM_PORTS*32*1-1:0] m_in_axis_tdata,
output wire [NUM_PORTS*1-1:0] m_in_axis_tkeep,
output wire [NUM_PORTS-1:0] m_in_axis_tlast,
output wire [NUM_PORTS-1:0] m_in_axis_tvalid,
input wire [NUM_PORTS-1:0] m_in_axis_tready,
output wire [NUM_PORTS*64-1:0] m_in_axis_ttimestamp,
output wire [NUM_PORTS-1:0] m_in_axis_thas_time,
output wire [NUM_PORTS*16-1:0] m_in_axis_tlength,
output wire [NUM_PORTS-1:0] m_in_axis_teov,
output wire [NUM_PORTS-1:0] m_in_axis_teob,
// Data Stream to User Logic: out
input wire [NUM_PORTS*32*1-1:0] s_out_axis_tdata,
input wire [NUM_PORTS*1-1:0] s_out_axis_tkeep,
input wire [NUM_PORTS-1:0] s_out_axis_tlast,
input wire [NUM_PORTS-1:0] s_out_axis_tvalid,
output wire [NUM_PORTS-1:0] s_out_axis_tready,
input wire [NUM_PORTS*64-1:0] s_out_axis_ttimestamp,
input wire [NUM_PORTS-1:0] s_out_axis_thas_time,
input wire [NUM_PORTS*16-1:0] s_out_axis_tlength,
input wire [NUM_PORTS-1:0] s_out_axis_teov,
input wire [NUM_PORTS-1:0] s_out_axis_teob
);
//---------------------------------------------------------------------------
// Backend Interface
//---------------------------------------------------------------------------
wire data_i_flush_en;
wire [31:0] data_i_flush_timeout;
wire [63:0] data_i_flush_active;
wire [63:0] data_i_flush_done;
wire data_o_flush_en;
wire [31:0] data_o_flush_timeout;
wire [63:0] data_o_flush_active;
wire [63:0] data_o_flush_done;
backend_iface #(
.NOC_ID (32'h02460000),
.NUM_DATA_I (0+NUM_PORTS),
.NUM_DATA_O (0+NUM_PORTS),
.CTRL_FIFOSIZE ($clog2(32)),
.MTU (MTU)
) backend_iface_i (
.rfnoc_chdr_clk (rfnoc_chdr_clk),
.rfnoc_chdr_rst (rfnoc_chdr_rst),
.rfnoc_ctrl_clk (rfnoc_ctrl_clk),
.rfnoc_ctrl_rst (rfnoc_ctrl_rst),
.rfnoc_core_config (rfnoc_core_config),
.rfnoc_core_status (rfnoc_core_status),
.data_i_flush_en (data_i_flush_en),
.data_i_flush_timeout (data_i_flush_timeout),
.data_i_flush_active (data_i_flush_active),
.data_i_flush_done (data_i_flush_done),
.data_o_flush_en (data_o_flush_en),
.data_o_flush_timeout (data_o_flush_timeout),
.data_o_flush_active (data_o_flush_active),
.data_o_flush_done (data_o_flush_done)
);
//---------------------------------------------------------------------------
// Reset Generation
//---------------------------------------------------------------------------
wire ce_rst_pulse;
pulse_synchronizer #(.MODE ("POSEDGE")) pulse_synchronizer_ce (
.clk_a(rfnoc_chdr_clk), .rst_a(1'b0), .pulse_a (rfnoc_chdr_rst), .busy_a (),
.clk_b(ce_clk), .pulse_b (ce_rst_pulse)
);
pulse_stretch_min #(.LENGTH(32)) pulse_stretch_min_ce (
.clk(ce_clk), .rst(1'b0),
.pulse_in(ce_rst_pulse), .pulse_out(ce_rst)
);
//---------------------------------------------------------------------------
// Control Path
//---------------------------------------------------------------------------
assign ctrlport_clk = ce_clk;
assign ctrlport_rst = ce_rst;
ctrlport_endpoint #(
.THIS_PORTID (THIS_PORTID),
.SYNC_CLKS (0),
.AXIS_CTRL_MST_EN (0),
.AXIS_CTRL_SLV_EN (1),
.SLAVE_FIFO_SIZE ($clog2(32))
) ctrlport_endpoint_i (
.rfnoc_ctrl_clk (rfnoc_ctrl_clk),
.rfnoc_ctrl_rst (rfnoc_ctrl_rst),
.ctrlport_clk (ctrlport_clk),
.ctrlport_rst (ctrlport_rst),
.s_rfnoc_ctrl_tdata (s_rfnoc_ctrl_tdata),
.s_rfnoc_ctrl_tlast (s_rfnoc_ctrl_tlast),
.s_rfnoc_ctrl_tvalid (s_rfnoc_ctrl_tvalid),
.s_rfnoc_ctrl_tready (s_rfnoc_ctrl_tready),
.m_rfnoc_ctrl_tdata (m_rfnoc_ctrl_tdata),
.m_rfnoc_ctrl_tlast (m_rfnoc_ctrl_tlast),
.m_rfnoc_ctrl_tvalid (m_rfnoc_ctrl_tvalid),
.m_rfnoc_ctrl_tready (m_rfnoc_ctrl_tready),
.m_ctrlport_req_wr (m_ctrlport_req_wr),
.m_ctrlport_req_rd (m_ctrlport_req_rd),
.m_ctrlport_req_addr (m_ctrlport_req_addr),
.m_ctrlport_req_data (m_ctrlport_req_data),
.m_ctrlport_req_byte_en (),
.m_ctrlport_req_has_time (),
.m_ctrlport_req_time (),
.m_ctrlport_resp_ack (m_ctrlport_resp_ack),
.m_ctrlport_resp_status (2'b0),
.m_ctrlport_resp_data (m_ctrlport_resp_data),
.s_ctrlport_req_wr (1'b0),
.s_ctrlport_req_rd (1'b0),
.s_ctrlport_req_addr (20'b0),
.s_ctrlport_req_portid (10'b0),
.s_ctrlport_req_rem_epid (16'b0),
.s_ctrlport_req_rem_portid (10'b0),
.s_ctrlport_req_data (32'b0),
.s_ctrlport_req_byte_en (4'hF),
.s_ctrlport_req_has_time (1'b0),
.s_ctrlport_req_time (64'b0),
.s_ctrlport_resp_ack (),
.s_ctrlport_resp_status (),
.s_ctrlport_resp_data ()
);
//---------------------------------------------------------------------------
// Data Path
//---------------------------------------------------------------------------
genvar i;
assign axis_data_clk = ce_clk;
assign axis_data_rst = ce_rst;
//---------------------
// Input Data Paths
//---------------------
for (i = 0; i < NUM_PORTS; i = i + 1) begin: gen_input_in
chdr_to_axis_data #(
.CHDR_W (CHDR_W),
.ITEM_W (32),
.NIPC (1),
.SYNC_CLKS (0),
.INFO_FIFO_SIZE ($clog2(32)),
.PYLD_FIFO_SIZE ($clog2(32))
) chdr_to_axis_data_in_in (
.axis_chdr_clk (rfnoc_chdr_clk),
.axis_chdr_rst (rfnoc_chdr_rst),
.axis_data_clk (axis_data_clk),
.axis_data_rst (axis_data_rst),
.s_axis_chdr_tdata (s_rfnoc_chdr_tdata[((0+i)*CHDR_W)+:CHDR_W]),
.s_axis_chdr_tlast (s_rfnoc_chdr_tlast[0+i]),
.s_axis_chdr_tvalid (s_rfnoc_chdr_tvalid[0+i]),
.s_axis_chdr_tready (s_rfnoc_chdr_tready[0+i]),
.m_axis_tdata (m_in_axis_tdata[(32*1)*i+:(32*1)]),
.m_axis_tkeep (m_in_axis_tkeep[1*i+:1]),
.m_axis_tlast (m_in_axis_tlast[i]),
.m_axis_tvalid (m_in_axis_tvalid[i]),
.m_axis_tready (m_in_axis_tready[i]),
.m_axis_ttimestamp (m_in_axis_ttimestamp[64*i+:64]),
.m_axis_thas_time (m_in_axis_thas_time[i]),
.m_axis_tlength (m_in_axis_tlength[16*i+:16]),
.m_axis_teov (m_in_axis_teov[i]),
.m_axis_teob (m_in_axis_teob[i]),
.flush_en (data_i_flush_en),
.flush_timeout (data_i_flush_timeout),
.flush_active (data_i_flush_active[0+i]),
.flush_done (data_i_flush_done[0+i])
);
end
//---------------------
// Output Data Paths
//---------------------
for (i = 0; i < NUM_PORTS; i = i + 1) begin: gen_output_out
axis_data_to_chdr #(
.CHDR_W (CHDR_W),
.ITEM_W (32),
.NIPC (1),
.SYNC_CLKS (0),
.INFO_FIFO_SIZE ($clog2(32)),
.PYLD_FIFO_SIZE ($clog2(MTU)),
.MTU (MTU),
.SIDEBAND_AT_END (1)
) axis_data_to_chdr_out_out (
.axis_chdr_clk (rfnoc_chdr_clk),
.axis_chdr_rst (rfnoc_chdr_rst),
.axis_data_clk (axis_data_clk),
.axis_data_rst (axis_data_rst),
.m_axis_chdr_tdata (m_rfnoc_chdr_tdata[(0+i)*CHDR_W+:CHDR_W]),
.m_axis_chdr_tlast (m_rfnoc_chdr_tlast[0+i]),
.m_axis_chdr_tvalid (m_rfnoc_chdr_tvalid[0+i]),
.m_axis_chdr_tready (m_rfnoc_chdr_tready[0+i]),
.s_axis_tdata (s_out_axis_tdata[(32*1)*i+:(32*1)]),
.s_axis_tkeep (s_out_axis_tkeep[1*i+:1]),
.s_axis_tlast (s_out_axis_tlast[i]),
.s_axis_tvalid (s_out_axis_tvalid[i]),
.s_axis_tready (s_out_axis_tready[i]),
.s_axis_ttimestamp (s_out_axis_ttimestamp[64*i+:64]),
.s_axis_thas_time (s_out_axis_thas_time[i]),
.s_axis_tlength (s_out_axis_tlength[16*i+:16]),
.s_axis_teov (s_out_axis_teov[i]),
.s_axis_teob (s_out_axis_teob[i]),
.flush_en (data_o_flush_en),
.flush_timeout (data_o_flush_timeout),
.flush_active (data_o_flush_active[0+i]),
.flush_done (data_o_flush_done[0+i])
);
end
endmodule // noc_shell_keep_one_in_n
`default_nettype wire
@@ -0,0 +1,284 @@
//
// Copyright 2020 Ettus Research, A National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: rfnoc_block_keep_one_in_n
//
// Description:
// The Keep One in N block has two modes: sample mode and packet mode.
// In sample mode, the first sample is kept and then N-1 samples are dropped.
// Packet mode is similar to sample mode, except the first packet of samples
// is kept and then N-1 packets are dropped. The packet size is determined
// automatically from tlast.
//
// Parameters:
//
// WIDTH_N : Bit width of N parameter, must be 31 bits or less
// THIS_PORTID : Control crossbar port to which this block is connected
// CHDR_W : AXIS-CHDR data bus width
// NUM_PORTS : Number of block instances
// MTU : Maximum transmission unit (i.e., maximum packet size in
// CHDR words is 2**MTU).
//
`default_nettype none
module rfnoc_block_keep_one_in_n #(
parameter WIDTH_N = 24, // Must be 31 bits or less
parameter [9:0] THIS_PORTID = 10'd0,
parameter CHDR_W = 64,
parameter NUM_PORTS = 1,
parameter [5:0] MTU = 10
)(
// RFNoC Framework Clocks and Resets
input wire rfnoc_chdr_clk,
input wire rfnoc_ctrl_clk,
input wire ce_clk,
// RFNoC Backend Interface
input wire [511:0] rfnoc_core_config,
output wire [511:0] rfnoc_core_status,
// AXIS-CHDR Input Ports (from framework)
input wire [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,
// AXIS-CHDR Output Ports (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,
// 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
);
//---------------------------------------------------------------------------
// Signal Declarations
//---------------------------------------------------------------------------
// Clocks and Resets
wire ctrlport_clk;
wire ctrlport_rst;
// CtrlPort Master
wire m_ctrlport_req_wr;
wire m_ctrlport_req_rd;
wire [19:0] m_ctrlport_req_addr;
wire [31:0] m_ctrlport_req_data;
wire m_ctrlport_resp_ack;
wire [31:0] m_ctrlport_resp_data;
// Data Stream to User Logic: in
wire [32*NUM_PORTS-1:0] m_in_axis_tdata;
wire [ NUM_PORTS-1:0] m_in_axis_tlast;
wire [ NUM_PORTS-1:0] m_in_axis_tvalid;
wire [ NUM_PORTS-1:0] m_in_axis_tready;
wire [64*NUM_PORTS-1:0] m_in_axis_ttimestamp;
wire [ NUM_PORTS-1:0] m_in_axis_thas_time;
wire [16*NUM_PORTS-1:0] m_in_axis_tlength;
wire [ NUM_PORTS-1:0] m_in_axis_teov;
wire [ NUM_PORTS-1:0] m_in_axis_teob;
// Data Stream from User Logic: out
wire [32*NUM_PORTS-1:0] s_out_axis_tdata;
wire [ NUM_PORTS-1:0] s_out_axis_tlast;
wire [ NUM_PORTS-1:0] s_out_axis_tvalid;
wire [ NUM_PORTS-1:0] s_out_axis_tready;
wire [64*NUM_PORTS-1:0] s_out_axis_ttimestamp;
wire [ NUM_PORTS-1:0] s_out_axis_thas_time;
wire [ NUM_PORTS-1:0] s_out_axis_teov;
wire [ NUM_PORTS-1:0] s_out_axis_teob;
wire ce_rst;
//---------------------------------------------------------------------------
// NoC Shell
//---------------------------------------------------------------------------
noc_shell_keep_one_in_n #(
.CHDR_W (CHDR_W),
.THIS_PORTID (THIS_PORTID),
.NUM_PORTS (NUM_PORTS),
.MTU (MTU)
) noc_shell_keep_one_in_n_i (
//// Framework Interface
// Clock Inputs
.rfnoc_chdr_clk (rfnoc_chdr_clk),
.rfnoc_ctrl_clk (rfnoc_ctrl_clk),
.ce_clk (ce_clk),
// Reset Outputs
.rfnoc_chdr_rst (),
.rfnoc_ctrl_rst (),
.ce_rst (ce_rst),
// RFNoC Backend Interface
.rfnoc_core_config (rfnoc_core_config),
.rfnoc_core_status (rfnoc_core_status),
// CHDR Input Ports (from framework)
.s_rfnoc_chdr_tdata (s_rfnoc_chdr_tdata),
.s_rfnoc_chdr_tlast (s_rfnoc_chdr_tlast),
.s_rfnoc_chdr_tvalid (s_rfnoc_chdr_tvalid),
.s_rfnoc_chdr_tready (s_rfnoc_chdr_tready),
// CHDR Output Ports (to framework)
.m_rfnoc_chdr_tdata (m_rfnoc_chdr_tdata),
.m_rfnoc_chdr_tlast (m_rfnoc_chdr_tlast),
.m_rfnoc_chdr_tvalid (m_rfnoc_chdr_tvalid),
.m_rfnoc_chdr_tready (m_rfnoc_chdr_tready),
// AXIS-Ctrl Input Port (from framework)
.s_rfnoc_ctrl_tdata (s_rfnoc_ctrl_tdata),
.s_rfnoc_ctrl_tlast (s_rfnoc_ctrl_tlast),
.s_rfnoc_ctrl_tvalid (s_rfnoc_ctrl_tvalid),
.s_rfnoc_ctrl_tready (s_rfnoc_ctrl_tready),
// AXIS-Ctrl Output Port (to framework)
.m_rfnoc_ctrl_tdata (m_rfnoc_ctrl_tdata),
.m_rfnoc_ctrl_tlast (m_rfnoc_ctrl_tlast),
.m_rfnoc_ctrl_tvalid (m_rfnoc_ctrl_tvalid),
.m_rfnoc_ctrl_tready (m_rfnoc_ctrl_tready),
//// Client Interface
// CtrlPort Clock and Reset
.ctrlport_clk (ctrlport_clk),
.ctrlport_rst (ctrlport_rst),
// CtrlPort Master
.m_ctrlport_req_wr (m_ctrlport_req_wr),
.m_ctrlport_req_rd (m_ctrlport_req_rd),
.m_ctrlport_req_addr (m_ctrlport_req_addr),
.m_ctrlport_req_data (m_ctrlport_req_data),
.m_ctrlport_resp_ack (m_ctrlport_resp_ack),
.m_ctrlport_resp_data (m_ctrlport_resp_data),
// AXI-Stream Payload Context Clock and Reset
.axis_data_clk (),
.axis_data_rst (),
// Data Stream to User Logic: in
.m_in_axis_tdata (m_in_axis_tdata),
.m_in_axis_tkeep (),
.m_in_axis_tlast (m_in_axis_tlast),
.m_in_axis_tvalid (m_in_axis_tvalid),
.m_in_axis_tready (m_in_axis_tready),
.m_in_axis_ttimestamp (m_in_axis_ttimestamp),
.m_in_axis_thas_time (m_in_axis_thas_time),
.m_in_axis_tlength (m_in_axis_tlength),
.m_in_axis_teov (m_in_axis_teov),
.m_in_axis_teob (m_in_axis_teob),
// Data Stream from User Logic: out
.s_out_axis_tdata (s_out_axis_tdata),
.s_out_axis_tkeep ({(NUM_PORTS){1'b1}}),
.s_out_axis_tlast (s_out_axis_tlast),
.s_out_axis_tvalid (s_out_axis_tvalid),
.s_out_axis_tready (s_out_axis_tready),
.s_out_axis_ttimestamp (s_out_axis_ttimestamp),
.s_out_axis_thas_time (s_out_axis_thas_time),
.s_out_axis_teov (s_out_axis_teov),
.s_out_axis_teob (s_out_axis_teob)
);
wire [ 8*NUM_PORTS-1:0] set_addr;
wire [32*NUM_PORTS-1:0] set_data;
wire [ NUM_PORTS-1:0] set_stb;
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 (ctrlport_clk),
.ctrlport_rst (ctrlport_rst),
.s_ctrlport_req_wr (m_ctrlport_req_wr),
.s_ctrlport_req_rd (m_ctrlport_req_rd),
.s_ctrlport_req_addr (m_ctrlport_req_addr),
.s_ctrlport_req_data (m_ctrlport_req_data),
.s_ctrlport_req_has_time (1'b0),
.s_ctrlport_req_time (64'd0),
.s_ctrlport_resp_ack (m_ctrlport_resp_ack),
.s_ctrlport_resp_data (m_ctrlport_resp_data),
.set_data (set_data),
.set_addr (set_addr),
.set_stb (set_stb),
.set_time (),
.set_has_time (),
.rb_stb (rb_stb),
.rb_addr (rb_addr),
.rb_data (rb_data),
.timestamp ());
//---------------------------------------------------------------------------
// User Logic
//---------------------------------------------------------------------------
`include "rfnoc_keep_one_in_n_regs.vh"
wire [REG_N_LEN*NUM_PORTS-1:0] n;
wire [REG_MODE_LEN*NUM_PORTS-1:0] mode;
genvar i;
for (i = 0; i < NUM_PORTS; i = i+1) begin
setting_reg #(
.my_addr (REG_N),
.awidth (8),
.width (REG_N_LEN))
inst_setting_reg_n (
.clk (ce_clk),
.rst (ce_rst),
.strobe (set_stb[i]),
.addr (set_addr[8*(i+1)-1:8*i]),
.in (set_data[32*(i+1)-1:32*i]),
.out (n[WIDTH_N*(i+1)-1:WIDTH_N*i]),
.changed ());
setting_reg #(
.my_addr (REG_MODE),
.awidth (8),
.width (REG_MODE_LEN))
inst_setting_reg_mode (
.clk (ce_clk),
.rst (ce_rst),
.strobe (set_stb[i]),
.addr (set_addr[8*(i+1)-1:8*i]),
.in (set_data[32*(i+1)-1:32*i]),
.out (mode[i]),
.changed ());
// Readback
assign rb_stb[i] = 1'b1;
always @*
case (rb_addr[8*(i+1)-1:8*i])
REG_N : rb_data[64*(i+1)-1:64*i] <= {{(64-REG_N_LEN){1'b0}}, n[WIDTH_N*(i+1)-1:WIDTH_N*i]};
REG_MODE : rb_data[64*(i+1)-1:64*i] <= {{(64-REG_MODE_LEN){1'b0}}, mode[i]};
REG_WIDTH_N : rb_data[64*(i+1)-1:64*i] <= {{(64-REG_WIDTH_N){1'b0}}, WIDTH_N};
default : rb_data[64*(i+1)-1:64*i] <= 64'h0BADC0DE0BADC0DE;
endcase
rfnoc_keep_one_in_n #(
.WIDTH (32),
.WIDTH_N (WIDTH_N))
inst_rfnoc_keep_one_in_n (
.clk (ce_clk),
.reset (ce_rst),
.mode (mode[i]),
.n (n[WIDTH_N*(i+1)-1:WIDTH_N*i]),
.s_axis_tdata (m_in_axis_tdata[32*(i+1)-1:32*i]),
.s_axis_tlast (m_in_axis_tlast[i]),
.s_axis_tvalid (m_in_axis_tvalid[i]),
.s_axis_tready (m_in_axis_tready[i]),
.s_axis_ttimestamp (m_in_axis_ttimestamp[64*(i+1)-1:64*i]),
.s_axis_thas_time (m_in_axis_thas_time[i]),
.s_axis_tlength (m_in_axis_tlength[16*(i+1)-1:16*i]),
.s_axis_teov (m_in_axis_teov[i]),
.s_axis_teob (m_in_axis_teob[i]),
.m_axis_tdata (s_out_axis_tdata[32*(i+1)-1:32*i]),
.m_axis_tlast (s_out_axis_tlast[i]),
.m_axis_tvalid (s_out_axis_tvalid[i]),
.m_axis_tready (s_out_axis_tready[i]),
.m_axis_ttimestamp (s_out_axis_ttimestamp[64*(i+1)-1:64*i]),
.m_axis_thas_time (s_out_axis_thas_time[i]),
.m_axis_teov (s_out_axis_teov[i]),
.m_axis_teob (s_out_axis_teob[i]));
end
endmodule // rfnoc_block_keep_one_in_n
`default_nettype wire
@@ -0,0 +1,484 @@
//
// Copyright 2020 Ettus Research, A National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: rfnoc_block_keep_one_in_n_tb
//
// Description: Testbench for the keep_one_in_n RFNoC block.
//
`default_nettype none
module rfnoc_block_keep_one_in_n_tb;
`include "test_exec.svh"
import PkgTestExec::*;
import PkgChdrUtils::*;
import PkgRfnocBlockCtrlBfm::*;
import PkgRfnocItemUtils::*;
//---------------------------------------------------------------------------
// Testbench Configuration
//---------------------------------------------------------------------------
localparam [ 9:0] THIS_PORTID = 10'h123;
localparam [31:0] NOC_ID = 32'h02460000;
localparam int CHDR_W = 64;
localparam int ITEM_W = 32;
localparam int NUM_PORTS = 2;
localparam int MTU = 13;
localparam int SPP = 64;
localparam int PKT_SIZE_BYTES = SPP * (ITEM_W/8);
localparam int STALL_PROB = 50; // Default BFM stall probability
localparam real CHDR_CLK_PER = 5.0; // 200 MHz
localparam real CTRL_CLK_PER = 25.0; // 40 MHz
localparam real CE_CLK_PER = 5.0; // 200 MHz
localparam WIDTH_N = 8;
localparam bit SAMPLE_MODE = 0;
localparam bit PACKET_MODE = 1;
//---------------------------------------------------------------------------
// Clocks and Resets
//---------------------------------------------------------------------------
bit rfnoc_chdr_clk;
bit rfnoc_ctrl_clk;
bit ce_clk;
sim_clock_gen #(CHDR_CLK_PER) rfnoc_chdr_clk_gen (.clk(rfnoc_chdr_clk), .rst());
sim_clock_gen #(CTRL_CLK_PER) rfnoc_ctrl_clk_gen (.clk(rfnoc_ctrl_clk), .rst());
sim_clock_gen #(CE_CLK_PER) ce_clk_gen (.clk(ce_clk), .rst());
//---------------------------------------------------------------------------
// Bus Functional Models
//---------------------------------------------------------------------------
// Backend Interface
RfnocBackendIf backend (rfnoc_chdr_clk, rfnoc_ctrl_clk);
// AXIS-Ctrl Interface
AxiStreamIf #(32) m_ctrl (rfnoc_ctrl_clk, 1'b0);
AxiStreamIf #(32) s_ctrl (rfnoc_ctrl_clk, 1'b0);
// AXIS-CHDR Interfaces
AxiStreamIf #(CHDR_W) m_chdr [NUM_PORTS] (rfnoc_chdr_clk, 1'b0);
AxiStreamIf #(CHDR_W) s_chdr [NUM_PORTS] (rfnoc_chdr_clk, 1'b0);
// Block Controller BFM
RfnocBlockCtrlBfm #(CHDR_W, ITEM_W) blk_ctrl = new(backend, m_ctrl, s_ctrl);
// CHDR word and item/sample data types
typedef ChdrData #(CHDR_W, ITEM_W)::chdr_word_t chdr_word_t;
typedef ChdrData #(CHDR_W, ITEM_W)::item_t item_t;
// Connect block controller to BFMs
for (genvar i = 0; i < NUM_PORTS; i++) begin : gen_bfm_input_connections
initial begin
blk_ctrl.connect_master_data_port(i, m_chdr[i], PKT_SIZE_BYTES);
blk_ctrl.set_master_stall_prob(i, STALL_PROB);
end
end
for (genvar i = 0; i < NUM_PORTS; i++) begin : gen_bfm_output_connections
initial begin
blk_ctrl.connect_slave_data_port(i, s_chdr[i]);
blk_ctrl.set_slave_stall_prob(i, STALL_PROB);
end
end
//---------------------------------------------------------------------------
// Device Under Test (DUT)
//---------------------------------------------------------------------------
// DUT Slave (Input) Port Signals
logic [CHDR_W*NUM_PORTS-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;
// DUT Master (Output) Port Signals
logic [CHDR_W*NUM_PORTS-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 connections
for (genvar i = 0; i < NUM_PORTS; i++) begin : gen_dut_input_connections
// Connect BFM master to DUT slave port
assign s_rfnoc_chdr_tdata[CHDR_W*i+:CHDR_W] = m_chdr[i].tdata;
assign s_rfnoc_chdr_tlast[i] = m_chdr[i].tlast;
assign s_rfnoc_chdr_tvalid[i] = m_chdr[i].tvalid;
assign m_chdr[i].tready = s_rfnoc_chdr_tready[i];
end
for (genvar i = 0; i < NUM_PORTS; i++) begin : gen_dut_output_connections
// Connect BFM slave to DUT master port
assign s_chdr[i].tdata = m_rfnoc_chdr_tdata[CHDR_W*i+:CHDR_W];
assign s_chdr[i].tlast = m_rfnoc_chdr_tlast[i];
assign s_chdr[i].tvalid = m_rfnoc_chdr_tvalid[i];
assign m_rfnoc_chdr_tready[i] = s_chdr[i].tready;
end
rfnoc_block_keep_one_in_n #(
.WIDTH_N (WIDTH_N),
.THIS_PORTID (THIS_PORTID),
.CHDR_W (CHDR_W),
.MTU (MTU),
.NUM_PORTS (NUM_PORTS)
) dut (
.rfnoc_chdr_clk (rfnoc_chdr_clk),
.rfnoc_ctrl_clk (rfnoc_ctrl_clk),
.ce_clk (ce_clk),
.rfnoc_core_config (backend.cfg),
.rfnoc_core_status (backend.sts),
.s_rfnoc_chdr_tdata (s_rfnoc_chdr_tdata),
.s_rfnoc_chdr_tlast (s_rfnoc_chdr_tlast),
.s_rfnoc_chdr_tvalid (s_rfnoc_chdr_tvalid),
.s_rfnoc_chdr_tready (s_rfnoc_chdr_tready),
.m_rfnoc_chdr_tdata (m_rfnoc_chdr_tdata),
.m_rfnoc_chdr_tlast (m_rfnoc_chdr_tlast),
.m_rfnoc_chdr_tvalid (m_rfnoc_chdr_tvalid),
.m_rfnoc_chdr_tready (m_rfnoc_chdr_tready),
.s_rfnoc_ctrl_tdata (m_ctrl.tdata),
.s_rfnoc_ctrl_tlast (m_ctrl.tlast),
.s_rfnoc_ctrl_tvalid (m_ctrl.tvalid),
.s_rfnoc_ctrl_tready (m_ctrl.tready),
.m_rfnoc_ctrl_tdata (s_ctrl.tdata),
.m_rfnoc_ctrl_tlast (s_ctrl.tlast),
.m_rfnoc_ctrl_tvalid (s_ctrl.tvalid),
.m_rfnoc_ctrl_tready (s_ctrl.tready)
);
//---------------------------------------------------------------------------
// Helper Logic
//---------------------------------------------------------------------------
// Translate the desired register access to a ctrlport write request.
task automatic write_reg(input logic [$clog2(NUM_PORTS)-1:0] port, input ctrl_address_t addr, input logic [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(input logic [$clog2(NUM_PORTS)-1:0] port, input ctrl_address_t 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
typedef struct {
item_t samples[$];
chdr_word_t mdata[$];
packet_info_t pkt_info;
} test_packet_t;
// Check if input test packets are identical
function automatic void compare_test_packets(const ref test_packet_t a, b);
string str;
// Packet payload
$sformat(str,
"Packet payload size incorrect! Expected: %4d, Received: %4d",
a.samples.size(), b.samples.size());
`ASSERT_ERROR(a.samples.size() == b.samples.size(), str);
for (int i = 0; i < a.samples.size(); i++) begin
$sformat(str,
"Packet payload word %4d incorrect! Expected: 0x%8X, Received: 0x%8X",
i, a.samples[i], b.samples[i]);
`ASSERT_ERROR(a.samples[i] == b.samples[i], str);
end
// Packet metadata
$sformat(str,
"Packet metadata size incorrect! Expected: %4d, Received: %4d",
a.mdata.size(), b.mdata.size());
`ASSERT_ERROR(a.mdata.size() == b.mdata.size(), str);
for (int i = 0; i < a.mdata.size(); i++) begin
$sformat(str,
"Packet metadata word %04d incorrect! Expected: 0x%8X, Received: 0x%8X",
i, a.mdata[i], b.mdata[i]);
`ASSERT_ERROR(a.mdata[i] == b.mdata[i], str);
end
// Packet info
$sformat(str,
"Packet info field 'vc' incorrect! Expected: %2d, Received: %2d",
a.pkt_info.vc, b.pkt_info.vc);
`ASSERT_ERROR(a.pkt_info.vc == b.pkt_info.vc, str);
$sformat(str,
"Packet info field 'eob' incorrect! Expected: %1d, Received: %1d",
a.pkt_info.eob, b.pkt_info.eob);
`ASSERT_ERROR(a.pkt_info.eob == b.pkt_info.eob, str);
$sformat(str,
"Packet info field 'eov' incorrect! Expected: %1d, Received: %1d",
a.pkt_info.eov, b.pkt_info.eov);
`ASSERT_ERROR(a.pkt_info.eov == b.pkt_info.eov, str);
$sformat(str,
"Packet info field 'has_time' incorrect! Expected: %1d, Received: %1d",
a.pkt_info.has_time, b.pkt_info.has_time);
`ASSERT_ERROR(a.pkt_info.has_time == b.pkt_info.has_time, str);
$sformat(str,
"Packet info field 'timestamp' incorrect! Expected: 0x%16X, Received: 0x%16X",
a.pkt_info.timestamp, b.pkt_info.timestamp);
`ASSERT_ERROR(a.pkt_info.timestamp == b.pkt_info.timestamp, str);
endfunction
//---------------------------------------------------------------------------
// Test Tasks
//---------------------------------------------------------------------------
task automatic test_keep_one_in_n (
input bit mode,
input int n,
input int num_packets,
input int port = 0,
input int spp = SPP,
input int stall_prob = STALL_PROB
);
mailbox #(test_packet_t) tb_send_packets = new();
blk_ctrl.set_master_stall_prob(port, stall_prob);
blk_ctrl.set_slave_stall_prob(port, stall_prob);
$display("N = %3d, Number of Packets = %3d, Port Number = %1d", n, num_packets, port);
begin
logic [63:0] readback;
string str;
write_reg(port, dut.REG_MODE, mode);
read_user_reg(port, dut.REG_MODE, readback);
$sformat(str,
"Mode incorrect! Expected: %1d, Received: %1d",
mode, readback[0]);
`ASSERT_ERROR(readback[0] == mode, str);
write_reg(port, dut.REG_N, n);
read_user_reg(port, dut.REG_N, readback);
$sformat(str,
"N incorrect! Expected: %5d, Received: %5d",
n, readback);
`ASSERT_ERROR(readback == n, str);
end
fork
// TX
begin
for (int i = 0; i < num_packets; i++) begin
test_packet_t tb_send_pkt;
for (int k = 0; k < spp; k++) begin
tb_send_pkt.samples.push_back($urandom());
end
tb_send_pkt.mdata = {};
tb_send_pkt.pkt_info = '{
vc: 0,
eob: (i == num_packets-1),
eov: bit'($urandom()),
has_time: 1'b1,
timestamp: {$urandom(),$urandom()}};
blk_ctrl.send_items(port, tb_send_pkt.samples, tb_send_pkt.mdata, tb_send_pkt.pkt_info);
tb_send_packets.put(tb_send_pkt);
end
end
// RX
begin
int l = 0;
mailbox #(test_packet_t) tb_recv_packets = new();
int num_packets_expected = int'($ceil(real'(num_packets)/real'(n)));
for (int i = 0; i < num_packets_expected; i++) begin
test_packet_t tb_recv_pkt;
blk_ctrl.recv_items_adv(port, tb_recv_pkt.samples, tb_recv_pkt.mdata, tb_recv_pkt.pkt_info);
tb_recv_packets.put(tb_recv_pkt);
end
for (int i = 0; i < num_packets; i = i + n) begin
test_packet_t tb_recv_pkt, tb_send_pkt, tb_dropped_pkt;
// Packet mode keeps first packet, drops n-1
if (mode) begin
tb_send_packets.get(tb_send_pkt);
for (int k = 0; k < n-1; k++) begin
if (!tb_send_packets.try_get(tb_dropped_pkt)) break;
tb_send_pkt.pkt_info.eob = tb_send_pkt.pkt_info.eob | tb_dropped_pkt.pkt_info.eob;
tb_send_pkt.pkt_info.eov = tb_send_pkt.pkt_info.eov | tb_dropped_pkt.pkt_info.eov;
end
// Sample mode loops through n packets, keeps 1 in n samples
// from all the packets
end else begin
item_t samples_pruned[$];
// Peek first packet to grab mdata and packet info
tb_send_packets.peek(tb_send_pkt);
// Loop through n packets, grabbing 1 in n samples.
for (int k = 0; k < n; k++) begin
if (!tb_send_packets.try_get(tb_dropped_pkt)) break;
tb_send_pkt.pkt_info.eob = tb_send_pkt.pkt_info.eob | tb_dropped_pkt.pkt_info.eob;
tb_send_pkt.pkt_info.eov = tb_send_pkt.pkt_info.eov | tb_dropped_pkt.pkt_info.eov;
while (l < tb_dropped_pkt.samples.size()) begin
samples_pruned.push_back(tb_dropped_pkt.samples[l]);
l = l + n;
end
// Account for wrap around when dropping samples between packet boundaries
l = l - tb_dropped_pkt.samples.size();
end
// Replace packet samples with 1 in n samples of n packets
tb_send_pkt.samples = samples_pruned;
end
tb_recv_packets.get(tb_recv_pkt);
compare_test_packets(tb_send_pkt, tb_recv_pkt);
end
begin
string str;
$sformat(str,
"Sent packets queue not empty! Number of extra items: %2d",
tb_send_packets.num);
`ASSERT_ERROR(tb_send_packets.num == 0, str);
$sformat(str,
"Receive packets queue not empty! Number of extra items: %2d",
tb_recv_packets.num);
`ASSERT_ERROR(tb_recv_packets.num == 0, str);
end
end
join
endtask;
//---------------------------------------------------------------------------
// Main Test Process
//---------------------------------------------------------------------------
initial begin : tb_main
// Initialize the test exec object for this testbench
test.start_tb("rfnoc_block_keep_one_in_n_tb");
// Start the BFMs running
blk_ctrl.run();
//--------------------------------
// Reset
//--------------------------------
test.start_test("Flush block then reset it", 10us);
blk_ctrl.flush_and_reset();
test.end_test();
//--------------------------------
// Verify Block Info
//--------------------------------
test.start_test("Verify Block Info", 2us);
`ASSERT_ERROR(blk_ctrl.get_noc_id() == NOC_ID, "Incorrect NOC_ID Value");
`ASSERT_ERROR(blk_ctrl.get_num_data_i() == NUM_PORTS, "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 Sequences
//--------------------------------
// Packet mode
test.start_test("Test packet mode", NUM_PORTS*1100us);
for (int port = 0; port < NUM_PORTS; port++) begin
test_keep_one_in_n(PACKET_MODE, 1, 1, port); // mode, n, num_packets, port
test_keep_one_in_n(PACKET_MODE, 1, 2, port);
test_keep_one_in_n(PACKET_MODE, 1, 10, port);
test_keep_one_in_n(PACKET_MODE, 2, 1, port);
test_keep_one_in_n(PACKET_MODE, 2, 2, port);
test_keep_one_in_n(PACKET_MODE, 2, 3, port);
test_keep_one_in_n(PACKET_MODE, 2, 10, port);
test_keep_one_in_n(PACKET_MODE, 2, 51, port);
test_keep_one_in_n(PACKET_MODE, 3, 1, port);
test_keep_one_in_n(PACKET_MODE, 3, 2, port);
test_keep_one_in_n(PACKET_MODE, 3, 3, port);
test_keep_one_in_n(PACKET_MODE, 3, 4, port);
test_keep_one_in_n(PACKET_MODE, 3, 10, port);
test_keep_one_in_n(PACKET_MODE, 3, 53, port);
test_keep_one_in_n(PACKET_MODE, 11, 1, port);
test_keep_one_in_n(PACKET_MODE, 11, 7, port);
test_keep_one_in_n(PACKET_MODE, 11, 10, port);
test_keep_one_in_n(PACKET_MODE, 11, 11, port);
test_keep_one_in_n(PACKET_MODE, 11, 12, port);
test_keep_one_in_n(PACKET_MODE, 11, 13, port);
test_keep_one_in_n(PACKET_MODE, 11, 20, port);
test_keep_one_in_n(PACKET_MODE, 11, 21, port);
test_keep_one_in_n(PACKET_MODE, 11, 32, port);
test_keep_one_in_n(PACKET_MODE, 11, 33, port);
test_keep_one_in_n(PACKET_MODE, 11, 34, port);
test_keep_one_in_n(PACKET_MODE, 2**WIDTH_N-1, 2**WIDTH_N-2, port);
test_keep_one_in_n(PACKET_MODE, 2**WIDTH_N-1, 2**WIDTH_N-1, port);
test_keep_one_in_n(PACKET_MODE, 2**WIDTH_N-1, 2**WIDTH_N , port);
test_keep_one_in_n(PACKET_MODE, 2**WIDTH_N-1, 2*(2**WIDTH_N-3), port);
test_keep_one_in_n(PACKET_MODE, 2**WIDTH_N-1, 2*(2**WIDTH_N-2), port);
test_keep_one_in_n(PACKET_MODE, 2**WIDTH_N-1, 2*(2**WIDTH_N-1), port);
end
test.end_test();
// Packet mode
test.start_test("Test sample mode", NUM_PORTS*1100us);
for (int port = 0; port < NUM_PORTS; port++) begin
test_keep_one_in_n(SAMPLE_MODE, 1, 1, port);
test_keep_one_in_n(SAMPLE_MODE, 1, 2, port);
test_keep_one_in_n(SAMPLE_MODE, 1, 10, port);
test_keep_one_in_n(SAMPLE_MODE, 2, 1, port);
test_keep_one_in_n(SAMPLE_MODE, 2, 2, port);
test_keep_one_in_n(SAMPLE_MODE, 2, 3, port);
test_keep_one_in_n(SAMPLE_MODE, 2, 10, port);
test_keep_one_in_n(SAMPLE_MODE, 2, 51, port);
test_keep_one_in_n(SAMPLE_MODE, 3, 1, port);
test_keep_one_in_n(SAMPLE_MODE, 3, 2, port);
test_keep_one_in_n(SAMPLE_MODE, 3, 3, port);
test_keep_one_in_n(SAMPLE_MODE, 3, 4, port);
test_keep_one_in_n(SAMPLE_MODE, 3, 10, port);
test_keep_one_in_n(SAMPLE_MODE, 3, 53, port);
test_keep_one_in_n(SAMPLE_MODE, 11, 1, port);
test_keep_one_in_n(SAMPLE_MODE, 11, 7, port);
test_keep_one_in_n(SAMPLE_MODE, 11, 10, port);
test_keep_one_in_n(SAMPLE_MODE, 11, 11, port);
test_keep_one_in_n(SAMPLE_MODE, 11, 12, port);
test_keep_one_in_n(SAMPLE_MODE, 11, 13, port);
test_keep_one_in_n(SAMPLE_MODE, 11, 20, port);
test_keep_one_in_n(SAMPLE_MODE, 11, 21, port);
test_keep_one_in_n(SAMPLE_MODE, 11, 32, port);
test_keep_one_in_n(SAMPLE_MODE, 11, 33, port);
test_keep_one_in_n(SAMPLE_MODE, 11, 34, port);
test_keep_one_in_n(SAMPLE_MODE, 2**WIDTH_N-1, 2**WIDTH_N-2, port);
test_keep_one_in_n(SAMPLE_MODE, 2**WIDTH_N-1, 2**WIDTH_N-1, port);
test_keep_one_in_n(SAMPLE_MODE, 2**WIDTH_N-1, 2**WIDTH_N , port);
test_keep_one_in_n(SAMPLE_MODE, 2**WIDTH_N-1, 2*(2**WIDTH_N-3), port);
test_keep_one_in_n(SAMPLE_MODE, 2**WIDTH_N-1, 2*(2**WIDTH_N-2), port);
test_keep_one_in_n(SAMPLE_MODE, 2**WIDTH_N-1, 2*(2**WIDTH_N-1), port);
end
test.end_test();
//--------------------------------
// Finish Up
//--------------------------------
// Display final statistics and results
test.end_tb();
end : tb_main
endmodule : rfnoc_block_keep_one_in_n_tb
`default_nettype wire
@@ -0,0 +1,269 @@
//
// Copyright 2020 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: rfnoc_keep_one_in_n
//
// Keep one sample and drop N-1 samples. Also, handles timestamp and setting EOB / EOV.
//
// n: Drop n-1 samples or packets
// mode: 0 = operate on samples, 1 = operate on packets that are delineated by tlast
//
`default_nettype none
module rfnoc_keep_one_in_n #(
parameter WIDTH = 32,
parameter WIDTH_N = 16
)(
input wire clk,
input wire reset,
input wire mode,
input wire [WIDTH_N-1:0] n,
input wire [WIDTH-1:0] s_axis_tdata,
input wire s_axis_tlast,
input wire s_axis_tvalid,
output wire s_axis_tready,
input wire [63:0] s_axis_ttimestamp,
input wire s_axis_thas_time,
input wire [15:0] s_axis_tlength,
input wire s_axis_teov,
input wire s_axis_teob,
output wire [WIDTH-1:0] m_axis_tdata,
output wire m_axis_tlast,
output wire m_axis_tvalid,
input wire m_axis_tready,
output wire [63:0] m_axis_ttimestamp,
output wire m_axis_thas_time,
output wire m_axis_teov,
output wire m_axis_teob
);
reg first_samp = 1'b1;
always @(posedge clk) begin
if (reset) begin
first_samp <= 1'b1;
end else begin
if (s_axis_tvalid & s_axis_tready) begin
first_samp <= s_axis_tlast;
end
end
end
// Packet mode state machine
// - Keeps first packet which is delinated by tlast
// and drops the following n-1 packets.
// - EOB signifies the end of a stream and causes the
// state machine to stay in or immediately return to the
// "keep" state.
reg state_pkt;
localparam S_PKT_KEEP = 1'd0;
localparam S_PKT_DROP = 1'd1;
reg [WIDTH_N-1:0] cnt_pkt_n = 2;
always @(posedge clk) begin
if (reset) begin
cnt_pkt_n <= 2;
state_pkt <= S_PKT_KEEP;
end else begin
case (state_pkt)
S_PKT_KEEP : begin
cnt_pkt_n <= 2;
if (s_axis_tvalid & s_axis_tready & s_axis_tlast) begin
// If EOB or n == 1, stay in this state
if (~s_axis_teob & n != 1) begin
state_pkt <= S_PKT_DROP;
end
end
end
S_PKT_DROP : begin
if (s_axis_tvalid & s_axis_tready & s_axis_tlast) begin
if (s_axis_teob) begin
state_pkt <= S_PKT_KEEP;
end else begin
cnt_pkt_n <= cnt_pkt_n + 1;
if (cnt_pkt_n >= n) begin
cnt_pkt_n <= 2;
state_pkt <= S_PKT_KEEP;
end
end
end
end
default : state_pkt <= S_PKT_KEEP;
endcase
end
end
// Sample mode state machine
// - Keeps first sample and drops n-1 samples.
// - EOB also causes this state machine stay in or return
// to the "keep" state.
reg [WIDTH_N-1:0] cnt_samp_n;
reg state_samp;
localparam S_SAMP_KEEP = 1'd0;
localparam S_SAMP_DROP = 1'd1;
always @(posedge clk) begin
if (reset) begin
cnt_samp_n <= 2;
state_samp <= S_SAMP_KEEP;
end else begin
case (state_samp)
S_SAMP_KEEP : begin
cnt_samp_n <= 2;
if (s_axis_tvalid & s_axis_tready) begin
// If EOB or n == 1, stay in this state
if (~(s_axis_tlast & s_axis_teob) & n != 1) begin
state_samp <= S_SAMP_DROP;
end
end
end
S_SAMP_DROP : begin
if (s_axis_tvalid & s_axis_tready) begin
if (s_axis_tlast & s_axis_teob) begin
state_samp <= S_SAMP_KEEP;
end else begin
cnt_samp_n <= cnt_samp_n + 1;
if (cnt_samp_n >= n) begin
cnt_samp_n <= 2;
state_samp <= S_SAMP_KEEP;
end
end
end
end
default : state_samp <= S_SAMP_KEEP;
endcase
end
end
wire keep_sample = mode ? (state_pkt == S_PKT_KEEP) : (state_samp == S_SAMP_KEEP);
// Output state machine
reg [1:0] state_o;
localparam S_O_FIRST_SAMP = 2'd0;
localparam S_O_OUTPUT = 2'd1;
localparam S_O_LAST_SAMP = 2'd2;
localparam S_O_LAST_SAMP_EOB = 2'd3;
reg [WIDTH-1:0] sample_reg;
reg [63:0] timestamp_reg;
reg has_time_reg;
reg eov_reg;
reg [15-WIDTH/8:0] length_reg;
reg [15-WIDTH/8:0] length_cnt;
always @(posedge clk) begin
if (reset) begin
length_cnt <= 2;
sample_reg <= 'd0;
timestamp_reg <= 'd0;
has_time_reg <= 1'b0;
length_reg <= 'd0;
eov_reg <= 1'b0;
state_o <= S_O_FIRST_SAMP;
end else begin
case (state_o)
// Preload the output register sample_reg. This is necessary
// so the state machine can have a sample to output if
// an EOB arrives while dropping samples / packets. If the
// state machine did not have that sample to output when
// an EOB arrives, then the EOB would be dropped because
// you cannot send packets without a payload with RFNoC.
S_O_FIRST_SAMP : begin
length_cnt <= 2;
if (keep_sample & s_axis_tvalid & s_axis_tready) begin
sample_reg <= s_axis_tdata;
timestamp_reg <= s_axis_ttimestamp;
// If this sample isn't the first sample in the input packet,
// then the vita time does not correspond to this sample
// and should be ignored. This situation can happen if the
// packet length is not consistent.
has_time_reg <= s_axis_thas_time & first_samp;
length_reg <= s_axis_tlength[15:$clog2(WIDTH/8)];
eov_reg <= s_axis_teov;
// First sample is also EOB, so it will be immediately released
if (s_axis_tlast & s_axis_teob) begin
state_o <= S_O_LAST_SAMP_EOB;
// Packet size is 1 sample
end else if (s_axis_tlength[15:$clog2(WIDTH/8)] == 1) begin
state_o <= S_O_LAST_SAMP;
end else begin
state_o <= S_O_OUTPUT;
end
end
end
// Output samples until either we need to either
// set tlast or encounter an EOB
S_O_OUTPUT : begin
if (s_axis_tvalid & s_axis_tready) begin
// Make EOV bit sticky
eov_reg <= eov_reg | s_axis_teov;
if (keep_sample) begin
sample_reg <= s_axis_tdata;
length_cnt <= length_cnt + 1;
end
if (s_axis_tlast & s_axis_teob) begin
state_o <= S_O_LAST_SAMP_EOB;
end else if (keep_sample) begin
// Use length from input packet to set tlast
if (length_cnt >= length_reg) begin
state_o <= S_O_LAST_SAMP;
end
end
end
end
S_O_LAST_SAMP : begin
length_cnt <= 2;
if (s_axis_tvalid & s_axis_tready) begin
if (keep_sample) begin
sample_reg <= s_axis_tdata;
timestamp_reg <= s_axis_ttimestamp;
has_time_reg <= s_axis_thas_time & first_samp;
length_reg <= s_axis_tlength[15:$clog2(WIDTH/8)];
eov_reg <= s_axis_teov;
end else begin
eov_reg <= eov_reg | s_axis_teov;
end
if (s_axis_tlast & s_axis_teob) begin
state_o <= S_O_LAST_SAMP_EOB;
end else if (keep_sample) begin
if (s_axis_tlength[15:$clog2(WIDTH/8)] > 1) begin
state_o <= S_O_OUTPUT;
end
end
end
end
S_O_LAST_SAMP_EOB : begin
if (s_axis_tready) begin
state_o <= S_O_FIRST_SAMP;
end
end
default : state_o <= S_O_FIRST_SAMP;
endcase
end
end
assign m_axis_tdata = sample_reg;
assign m_axis_tlast = (state_o == S_O_LAST_SAMP) || (state_o == S_O_LAST_SAMP_EOB);
assign m_axis_ttimestamp = timestamp_reg;
assign m_axis_thas_time = has_time_reg;
assign m_axis_teov = eov_reg;
assign m_axis_teob = (state_o == S_O_LAST_SAMP_EOB);
assign m_axis_tvalid = (state_o == S_O_FIRST_SAMP) ? 1'b0 :
(state_o == S_O_OUTPUT) ? keep_sample & s_axis_tvalid :
(state_o == S_O_LAST_SAMP) ? keep_sample & s_axis_tvalid :
(state_o == S_O_LAST_SAMP_EOB) ? 1'b1 :
1'b0;
assign s_axis_tready = (state_o == S_O_FIRST_SAMP) ? 1'b1 :
(state_o == S_O_OUTPUT) ? m_axis_tready :
(state_o == S_O_LAST_SAMP) ? m_axis_tready :
(state_o == S_O_LAST_SAMP_EOB) ? m_axis_tready :
1'b0;
endmodule
`default_nettype wire
@@ -0,0 +1,25 @@
//
// Copyright 2020 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: rfnoc_keep_one_in_n_regs
//
// Description: Header file for rfnoc_block_keep_one_in_n_regs.
//
// Offset in bytes between each ports's bank of registers. This is a hardcoded
// value in ctrlport_to_settings_bus.v put here for documentation purposes only.
localparam REG_BANK_OFFSET = 2**11; // 2048
// [WIDTH_N-1:0] : N, drop N-1 samples or packets
localparam REG_N = 0;
localparam REG_N_LEN = WIDTH_N;
// [0:0] : 0 = Sample Mode, 1 = Packet Mode
localparam REG_MODE = 1;
localparam REG_MODE_LEN = 1;
// [31:0] : Bit width of N, Read Only
localparam REG_WIDTH_N = 2;
localparam REG_WIDTH_N_LEN = 32;