fpga: lib: Add AXI-Stream load split and merge modules
Original-commit: 32fb626ef1b1d3078fd331143b03cd38d1e155f0
This commit is contained in:
@@ -35,6 +35,8 @@ axis_width_conv.v \
|
|||||||
axis_split.v \
|
axis_split.v \
|
||||||
axis_split_bus.v \
|
axis_split_bus.v \
|
||||||
axis_combine.v \
|
axis_combine.v \
|
||||||
|
axis_load_split.sv \
|
||||||
|
axis_load_merge.sv \
|
||||||
axis_packetize.v \
|
axis_packetize.v \
|
||||||
axis_pkt_throttle.sv \
|
axis_pkt_throttle.sv \
|
||||||
axis_data_if_packetize.v \
|
axis_data_if_packetize.v \
|
||||||
|
|||||||
@@ -0,0 +1,302 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2025 Ettus Research, a National Instruments Brand
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
//
|
||||||
|
// Module: axis_load_merge
|
||||||
|
//
|
||||||
|
// Description:
|
||||||
|
//
|
||||||
|
// Takes in multiple AXI4-Stream interfaces and merges the contents into a
|
||||||
|
// single stream, packet by packet. The packets will be merged sequentially,
|
||||||
|
// first accepting a packet from port 0, then from port 1, and so on, in a
|
||||||
|
// circular manner. The module will wait for the packet to arrive on the
|
||||||
|
// expected port before moving onto the next port.
|
||||||
|
//
|
||||||
|
// This module matches the behavior of axis_load_split, such that the results
|
||||||
|
// from multiple processing instances can be merged back together in the same
|
||||||
|
// order they were received by the axis_load_split module.
|
||||||
|
//
|
||||||
|
// The handling of TUSER deserves some explanation. If the input-port width
|
||||||
|
// is the same as the output-port width, then the TUSER input is passed
|
||||||
|
// through along with the corresponding TDATA like normal. But, if the
|
||||||
|
// output-word size is larger than the input-word size, then there are
|
||||||
|
// multiple N TUSER inputs for each TUSER output. The USER_SEL parameter
|
||||||
|
// selects which one will be output. It can be in the range [0,N-1], where 0
|
||||||
|
// selects the first of the N TUSER inputs for the next TUSER output and N-1
|
||||||
|
// selects the last of the N TUSER inputs.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
//
|
||||||
|
// IN_DATA_W : Width of TDATA in bits for the input port.
|
||||||
|
// IN_FIFO_SIZE : Log base-2 of the input FIFO size, in units of
|
||||||
|
// IN_DATA_W-sized words. Typically, this would be large
|
||||||
|
// enough to buffer one entire packet. Set to -1 to remove.
|
||||||
|
// IN_NUM_PORTS : The number of input streams across which to distribute
|
||||||
|
// the packets.
|
||||||
|
// OUT_DATA_W : Width of TDATA in bits for the output port.
|
||||||
|
// OUT_FIFO_SIZE : Log base-2 of the output FIFO size, in units of
|
||||||
|
// OUT_DATA_W-sized words. Set to -1 to remove, 1 to add a
|
||||||
|
// register that cuts timing paths, or whichever size you
|
||||||
|
// desire.
|
||||||
|
// USER_W : Width of TUSER in bits for both input and output ports.
|
||||||
|
// USER_SEL : When the output word size is a multiple of the input word
|
||||||
|
// size, USER_SEL indicates which TUSER input value will be
|
||||||
|
// passed along with each TDATA output value. A value of 0
|
||||||
|
// indicates that the first TUSER value that was input for a
|
||||||
|
// corresponding output word will be used for that output
|
||||||
|
// word. It can be in the range from 0 up to the number of
|
||||||
|
// output words per input words.
|
||||||
|
//
|
||||||
|
|
||||||
|
`default_nettype none
|
||||||
|
|
||||||
|
|
||||||
|
module axis_load_merge #(
|
||||||
|
int IN_DATA_W = 32,
|
||||||
|
int IN_FIFO_SIZE = 10,
|
||||||
|
int IN_NUM_PORTS = 2,
|
||||||
|
int OUT_DATA_W = 64,
|
||||||
|
int OUT_FIFO_SIZE = 1,
|
||||||
|
int USER_W = 1,
|
||||||
|
int USER_SEL = 0
|
||||||
|
) (
|
||||||
|
input wire clk,
|
||||||
|
input wire rst,
|
||||||
|
|
||||||
|
// Input streams
|
||||||
|
input wire [IN_DATA_W-1:0] i_tdata [IN_NUM_PORTS],
|
||||||
|
input wire [ USER_W-1:0] i_tuser [IN_NUM_PORTS],
|
||||||
|
input wire i_tlast [IN_NUM_PORTS],
|
||||||
|
input wire i_tvalid [IN_NUM_PORTS],
|
||||||
|
output logic i_tready [IN_NUM_PORTS],
|
||||||
|
|
||||||
|
// Single output stream
|
||||||
|
output logic [OUT_DATA_W-1:0] o_tdata,
|
||||||
|
output logic [ USER_W-1:0] o_tuser,
|
||||||
|
output logic o_tlast,
|
||||||
|
output logic o_tvalid,
|
||||||
|
input wire o_tready
|
||||||
|
);
|
||||||
|
|
||||||
|
// Elaboration-time assertions
|
||||||
|
if (IN_DATA_W > OUT_DATA_W) begin : check_size
|
||||||
|
$error("IN_DATA_W must not exceed OUT_DATA_W");
|
||||||
|
end
|
||||||
|
if (OUT_DATA_W % IN_DATA_W != 0) begin : check_multiple
|
||||||
|
$error("OUT_DATA_W must be a multiple of IN_DATA_W");
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------------
|
||||||
|
// Resize
|
||||||
|
//-------------------------------------------------------------------------
|
||||||
|
|
||||||
|
logic [OUT_DATA_W-1:0] resize_tdata [IN_NUM_PORTS];
|
||||||
|
logic [ USER_W-1:0] resize_tuser [IN_NUM_PORTS];
|
||||||
|
logic resize_tlast [IN_NUM_PORTS];
|
||||||
|
logic resize_tvalid [IN_NUM_PORTS];
|
||||||
|
logic resize_tready [IN_NUM_PORTS];
|
||||||
|
|
||||||
|
if (IN_DATA_W == OUT_DATA_W) begin : gen_no_resize
|
||||||
|
assign resize_tdata = i_tdata;
|
||||||
|
assign resize_tuser = i_tuser;
|
||||||
|
assign resize_tlast = i_tlast;
|
||||||
|
assign resize_tvalid = i_tvalid;
|
||||||
|
assign i_tready = resize_tready;
|
||||||
|
end else begin : gen_resize
|
||||||
|
for (genvar idx = 0; idx < IN_NUM_PORTS; idx++) begin : gen_axis_width_conv
|
||||||
|
localparam int OUT_WORDS = OUT_DATA_W/IN_DATA_W;
|
||||||
|
localparam int COUNT_W = $clog2(OUT_WORDS);
|
||||||
|
|
||||||
|
if (USER_SEL < 0 || USER_SEL >= OUT_WORDS) begin : check_parameters
|
||||||
|
$error("USER_SEL is outside of allowed range");
|
||||||
|
end
|
||||||
|
|
||||||
|
//-----------------------------------------
|
||||||
|
// Data Width Conversion
|
||||||
|
//-----------------------------------------
|
||||||
|
|
||||||
|
axis_width_conv #(
|
||||||
|
.WORD_W (IN_DATA_W),
|
||||||
|
.IN_WORDS (1 ),
|
||||||
|
.OUT_WORDS(OUT_WORDS),
|
||||||
|
.SYNC_CLKS(1 ),
|
||||||
|
.PIPELINE ("INOUT" )
|
||||||
|
) axis_width_conv_i (
|
||||||
|
.s_axis_aclk (clk ),
|
||||||
|
.s_axis_rst (rst ),
|
||||||
|
.s_axis_tdata (i_tdata[idx] ),
|
||||||
|
.s_axis_tkeep ('1 ),
|
||||||
|
.s_axis_tlast (i_tlast[idx] ),
|
||||||
|
.s_axis_tvalid(i_tvalid[idx] ),
|
||||||
|
.s_axis_tready(i_tready[idx] ),
|
||||||
|
.m_axis_aclk (clk ),
|
||||||
|
.m_axis_rst (rst ),
|
||||||
|
.m_axis_tdata (resize_tdata[idx] ),
|
||||||
|
.m_axis_tkeep ( ),
|
||||||
|
.m_axis_tlast (resize_tlast[idx] ),
|
||||||
|
.m_axis_tvalid(resize_tvalid[idx]),
|
||||||
|
.m_axis_tready(resize_tready[idx])
|
||||||
|
);
|
||||||
|
|
||||||
|
//-----------------------------------------
|
||||||
|
// TUSER Handling
|
||||||
|
//-----------------------------------------
|
||||||
|
|
||||||
|
// We write to the TUSER FIFO at a rate of 1/OUT_WORDS, but read from
|
||||||
|
// the TUSER FIFO in lock-step with the output of axis_width_conv
|
||||||
|
// module. The USER_SEL parameter selects which TUSER gets input.
|
||||||
|
|
||||||
|
logic user_fifo_i_tvalid;
|
||||||
|
logic user_fifo_i_tready;
|
||||||
|
logic user_fifo_o_tvalid;
|
||||||
|
logic [COUNT_W-1:0] word_count = '0;
|
||||||
|
|
||||||
|
// This counter tells when we're inputting the selected TUSER for the
|
||||||
|
// current output word, so we know when to write it to the FIFO. The
|
||||||
|
// counter is initialized such that it will assert after the first
|
||||||
|
// USER_SEL cycles, then repeats every OUT_WORDS cycles after that.
|
||||||
|
always_ff @(posedge clk) begin
|
||||||
|
if (i_tvalid[idx] && i_tready[idx]) begin
|
||||||
|
if (word_count == OUT_WORDS-1) begin
|
||||||
|
word_count <= '0;
|
||||||
|
end else begin
|
||||||
|
word_count <= word_count + 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if (rst) begin
|
||||||
|
word_count <= '0;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
assign user_fifo_i_tvalid = (word_count == USER_SEL) && i_tvalid[idx] && i_tready[idx];
|
||||||
|
|
||||||
|
axi_fifo #(
|
||||||
|
.WIDTH(USER_W),
|
||||||
|
.SIZE (2 )
|
||||||
|
) axi_fifo_tuser (
|
||||||
|
.clk (clk ),
|
||||||
|
.reset (rst ),
|
||||||
|
.clear ('0 ),
|
||||||
|
.i_tdata (i_tuser[idx] ),
|
||||||
|
.i_tvalid(user_fifo_i_tvalid ),
|
||||||
|
.i_tready(user_fifo_i_tready ),
|
||||||
|
.o_tdata (resize_tuser[idx] ),
|
||||||
|
.o_tvalid(user_fifo_o_tvalid ),
|
||||||
|
.o_tready(resize_tvalid[idx] && resize_tready[idx]),
|
||||||
|
.occupied( ),
|
||||||
|
.space ( )
|
||||||
|
);
|
||||||
|
|
||||||
|
// Make sure we don't overflow/underflow the TUSER FIFO
|
||||||
|
//synthesis translate_off
|
||||||
|
always_ff @(posedge clk) begin
|
||||||
|
if (user_fifo_i_tvalid && !user_fifo_i_tready) begin
|
||||||
|
$error("TUSER FIFO overflow");
|
||||||
|
end
|
||||||
|
if (resize_tvalid[idx] && resize_tready[idx] && !user_fifo_o_tvalid) begin
|
||||||
|
$error("TUSER FIFO underflow");
|
||||||
|
end
|
||||||
|
end
|
||||||
|
//synthesis translate_on
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
// Input FIFOs
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
logic [OUT_DATA_W-1:0] fifo_tdata [IN_NUM_PORTS];
|
||||||
|
logic [ USER_W-1:0] fifo_tuser [IN_NUM_PORTS];
|
||||||
|
logic fifo_tlast [IN_NUM_PORTS];
|
||||||
|
logic fifo_tvalid [IN_NUM_PORTS];
|
||||||
|
logic fifo_tready [IN_NUM_PORTS];
|
||||||
|
|
||||||
|
for (genvar idx = 0; idx < IN_NUM_PORTS; idx++) begin : gen_axi_fifos
|
||||||
|
axi_fifo #(
|
||||||
|
.WIDTH(1 + USER_W + OUT_DATA_W ),
|
||||||
|
.SIZE (IN_FIFO_SIZE - $clog2(OUT_DATA_W/IN_DATA_W))
|
||||||
|
) axi_fifo_in (
|
||||||
|
.clk (clk ),
|
||||||
|
.reset (rst ),
|
||||||
|
.clear (1'b0 ),
|
||||||
|
.i_tdata ({resize_tlast[idx], resize_tuser[idx], resize_tdata[idx]}),
|
||||||
|
.i_tvalid(resize_tvalid[idx] ),
|
||||||
|
.i_tready(resize_tready[idx] ),
|
||||||
|
.o_tdata ({fifo_tlast[idx], fifo_tuser[idx], fifo_tdata[idx]} ),
|
||||||
|
.o_tvalid(fifo_tvalid[idx] ),
|
||||||
|
.o_tready(fifo_tready[idx] ),
|
||||||
|
.space ( ),
|
||||||
|
.occupied( )
|
||||||
|
);
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
// Merge Logic
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
logic [OUT_DATA_W-1:0] merge_tdata;
|
||||||
|
logic [ USER_W-1:0] merge_tuser;
|
||||||
|
logic merge_tlast;
|
||||||
|
logic merge_tvalid;
|
||||||
|
logic merge_tready;
|
||||||
|
|
||||||
|
// Currently selected port
|
||||||
|
logic [$clog2(IN_NUM_PORTS)-1:0] st_port;
|
||||||
|
|
||||||
|
// Merge state machine. Tracks and advances the currently selected port.
|
||||||
|
always_ff @(posedge clk) begin
|
||||||
|
if (rst) begin
|
||||||
|
st_port <= '0;
|
||||||
|
end else begin
|
||||||
|
if (merge_tvalid && merge_tready && merge_tlast) begin
|
||||||
|
if (st_port == IN_NUM_PORTS-1) begin
|
||||||
|
st_port <= '0;
|
||||||
|
end else begin
|
||||||
|
st_port <= st_port + 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
// Connect the selected input to the output
|
||||||
|
assign merge_tdata = fifo_tdata[st_port];
|
||||||
|
assign merge_tuser = fifo_tuser[st_port];
|
||||||
|
assign merge_tlast = fifo_tlast[st_port];
|
||||||
|
assign merge_tvalid = fifo_tvalid[st_port];
|
||||||
|
|
||||||
|
for (genvar idx = 0; idx < IN_NUM_PORTS; idx++) begin : gen_merge
|
||||||
|
assign fifo_tready[idx] = merge_tready && (st_port == idx);
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
// Output FIFOs
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
axi_fifo #(
|
||||||
|
.WIDTH(1 + USER_W + OUT_DATA_W),
|
||||||
|
.SIZE (OUT_FIFO_SIZE )
|
||||||
|
) axi_fifo_out (
|
||||||
|
.clk (clk ),
|
||||||
|
.reset (rst ),
|
||||||
|
.clear (1'b0 ),
|
||||||
|
.i_tdata ({merge_tlast, merge_tuser, merge_tdata}),
|
||||||
|
.i_tvalid(merge_tvalid ),
|
||||||
|
.i_tready(merge_tready ),
|
||||||
|
.o_tdata ({o_tlast, o_tuser, o_tdata} ),
|
||||||
|
.o_tvalid(o_tvalid ),
|
||||||
|
.o_tready(o_tready ),
|
||||||
|
.space ( ),
|
||||||
|
.occupied( )
|
||||||
|
);
|
||||||
|
|
||||||
|
endmodule : axis_load_merge
|
||||||
|
|
||||||
|
|
||||||
|
`default_nettype wire
|
||||||
@@ -0,0 +1,280 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2025 Ettus Research, a National Instruments Brand
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
//
|
||||||
|
// Module: axis_load_split
|
||||||
|
//
|
||||||
|
// Description:
|
||||||
|
//
|
||||||
|
// Takes an AXI4-Stream interface and distributes the input packets evenly
|
||||||
|
// among the output ports. The packets will be distributed sequentially, with
|
||||||
|
// the first packet going to output port 0, the second to port 1, and so on,
|
||||||
|
// in a circular manner. The data output can be optionally resized to a new
|
||||||
|
// data width.
|
||||||
|
//
|
||||||
|
// The purpose of this module is to take a high-throughput input that needs
|
||||||
|
// to be processed and farm it out to multiple instances of a processing
|
||||||
|
// module, sending one packet to each instance.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
//
|
||||||
|
// IN_DATA_W : Width of TDATA in bits for the input port.
|
||||||
|
// IN_FIFO_SIZE : Log base-2 of the input FIFO size, in units of
|
||||||
|
// IN_DATA_W-sized words. Set to -1 to remove, 1 to add a
|
||||||
|
// register that cuts timing paths, or whichever size you
|
||||||
|
// desire.
|
||||||
|
// OUT_DATA_W : Width of TDATA in bits for the output port.
|
||||||
|
// OUT_FIFO_SIZE : Log base-2 of the output FIFO size used for each output,
|
||||||
|
// in units of OUT_DATA_W-sized words. Typically, this would
|
||||||
|
// be large enough to buffer one entire packet. Set to -1 to
|
||||||
|
// remove.
|
||||||
|
// OUT_NUM_PORTS : The number of output streams across which to distribute
|
||||||
|
// the packets.
|
||||||
|
// USER_W : Width of TUSER in bits for both input and output ports.
|
||||||
|
//
|
||||||
|
|
||||||
|
`default_nettype none
|
||||||
|
|
||||||
|
|
||||||
|
module axis_load_split #(
|
||||||
|
int IN_DATA_W = 64,
|
||||||
|
int IN_FIFO_SIZE = 1,
|
||||||
|
int OUT_DATA_W = 32,
|
||||||
|
int OUT_FIFO_SIZE = 10,
|
||||||
|
int OUT_NUM_PORTS = 2,
|
||||||
|
int USER_W = 1
|
||||||
|
) (
|
||||||
|
input wire clk,
|
||||||
|
input wire rst,
|
||||||
|
|
||||||
|
// Single input stream
|
||||||
|
input wire [IN_DATA_W-1:0] i_tdata,
|
||||||
|
input wire [ USER_W-1:0] i_tuser,
|
||||||
|
input wire i_tlast,
|
||||||
|
input wire i_tvalid,
|
||||||
|
output logic i_tready,
|
||||||
|
|
||||||
|
// Output streams
|
||||||
|
output logic [OUT_DATA_W-1:0] o_tdata [OUT_NUM_PORTS],
|
||||||
|
output wire [ USER_W-1:0] o_tuser [OUT_NUM_PORTS],
|
||||||
|
output logic o_tlast [OUT_NUM_PORTS],
|
||||||
|
output logic o_tvalid [OUT_NUM_PORTS],
|
||||||
|
input wire o_tready [OUT_NUM_PORTS]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Elaboration-time assertions
|
||||||
|
if (OUT_DATA_W > IN_DATA_W) begin : check_size
|
||||||
|
$error("OUT_DATA_W must not exceed IN_DATA_W");
|
||||||
|
end
|
||||||
|
if (IN_DATA_W % OUT_DATA_W != 0) begin : check_multiple
|
||||||
|
$error("IN_DATA_W must be a multiple of OUT_DATA_W");
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
// Input FIFO
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
logic [IN_DATA_W-1:0] in_fifo_tdata;
|
||||||
|
logic [ USER_W-1:0] in_fifo_tuser;
|
||||||
|
logic in_fifo_tlast;
|
||||||
|
logic in_fifo_tvalid;
|
||||||
|
logic in_fifo_tready;
|
||||||
|
|
||||||
|
axi_fifo #(
|
||||||
|
.WIDTH(1 + USER_W + IN_DATA_W),
|
||||||
|
.SIZE (IN_FIFO_SIZE )
|
||||||
|
) axi_fifo_in (
|
||||||
|
.clk (clk ),
|
||||||
|
.reset (rst ),
|
||||||
|
.clear (1'b0 ),
|
||||||
|
.i_tdata ({i_tlast, i_tuser, i_tdata} ),
|
||||||
|
.i_tvalid(i_tvalid ),
|
||||||
|
.i_tready(i_tready ),
|
||||||
|
.o_tdata ({in_fifo_tlast, in_fifo_tuser, in_fifo_tdata}),
|
||||||
|
.o_tvalid(in_fifo_tvalid ),
|
||||||
|
.o_tready(in_fifo_tready ),
|
||||||
|
.space ( ),
|
||||||
|
.occupied( )
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
// Splitter Logic
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
logic [IN_DATA_W-1:0] split_tdata [OUT_NUM_PORTS];
|
||||||
|
logic [ USER_W-1:0] split_tuser [OUT_NUM_PORTS];
|
||||||
|
logic split_tlast [OUT_NUM_PORTS];
|
||||||
|
logic split_tvalid [OUT_NUM_PORTS];
|
||||||
|
logic split_tready [OUT_NUM_PORTS];
|
||||||
|
|
||||||
|
// Currently selected port
|
||||||
|
logic [$clog2(OUT_NUM_PORTS)-1:0] st_port;
|
||||||
|
|
||||||
|
// Splitter state machine. Tracks and advances the currently selected port.
|
||||||
|
always_ff @(posedge clk) begin
|
||||||
|
if (rst) begin
|
||||||
|
st_port <= '0;
|
||||||
|
end else begin
|
||||||
|
if (in_fifo_tvalid && in_fifo_tready && in_fifo_tlast) begin
|
||||||
|
if (st_port == OUT_NUM_PORTS-1) begin
|
||||||
|
st_port <= '0;
|
||||||
|
end else begin
|
||||||
|
st_port <= st_port + 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
// Distribute the input to each output. Only the currently selected output
|
||||||
|
// port will receive the data.
|
||||||
|
assign in_fifo_tready = split_tready[st_port];
|
||||||
|
for (genvar idx = 0; idx < OUT_NUM_PORTS; idx++) begin : gen_splitter
|
||||||
|
assign split_tdata [idx] = in_fifo_tdata;
|
||||||
|
assign split_tuser [idx] = in_fifo_tuser;
|
||||||
|
assign split_tlast [idx] = in_fifo_tlast;
|
||||||
|
assign split_tvalid[idx] = in_fifo_tvalid && (st_port == idx);
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
// Output FIFOs
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
logic [IN_DATA_W-1:0] out_fifo_tdata [OUT_NUM_PORTS];
|
||||||
|
logic [ USER_W-1:0] out_fifo_tuser [OUT_NUM_PORTS];
|
||||||
|
logic out_fifo_tlast [OUT_NUM_PORTS];
|
||||||
|
logic out_fifo_tvalid [OUT_NUM_PORTS];
|
||||||
|
logic out_fifo_tready [OUT_NUM_PORTS];
|
||||||
|
|
||||||
|
for (genvar idx = 0; idx < OUT_NUM_PORTS; idx++) begin : gen_axi_fifos
|
||||||
|
axi_fifo #(
|
||||||
|
.WIDTH(1 + USER_W + IN_DATA_W ),
|
||||||
|
.SIZE (OUT_FIFO_SIZE - $clog2(IN_DATA_W/OUT_DATA_W))
|
||||||
|
) axi_fifo_out (
|
||||||
|
.clk (clk ),
|
||||||
|
.reset (rst ),
|
||||||
|
.clear (1'b0 ),
|
||||||
|
.i_tdata ({split_tlast[idx], split_tuser[idx], split_tdata[idx]} ),
|
||||||
|
.i_tvalid(split_tvalid[idx] ),
|
||||||
|
.i_tready(split_tready[idx] ),
|
||||||
|
.o_tdata ({out_fifo_tlast[idx], out_fifo_tuser[idx], out_fifo_tdata[idx]}),
|
||||||
|
.o_tvalid(out_fifo_tvalid[idx] ),
|
||||||
|
.o_tready(out_fifo_tready[idx] ),
|
||||||
|
.space ( ),
|
||||||
|
.occupied( )
|
||||||
|
);
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------------
|
||||||
|
// Resize
|
||||||
|
//-------------------------------------------------------------------------
|
||||||
|
|
||||||
|
if (IN_DATA_W == OUT_DATA_W) begin : gen_no_resize
|
||||||
|
assign o_tdata = out_fifo_tdata;
|
||||||
|
assign o_tuser = out_fifo_tuser;
|
||||||
|
assign o_tlast = out_fifo_tlast;
|
||||||
|
assign o_tvalid = out_fifo_tvalid;
|
||||||
|
assign out_fifo_tready = o_tready;
|
||||||
|
end else begin : gen_resize
|
||||||
|
for (genvar idx = 0; idx < OUT_NUM_PORTS; idx++) begin : gen_axis_width_conv
|
||||||
|
localparam IN_WORDS = IN_DATA_W/OUT_DATA_W;
|
||||||
|
localparam COUNT_W = $clog2(IN_WORDS);
|
||||||
|
|
||||||
|
//-----------------------------------------
|
||||||
|
// Data Width Conversion
|
||||||
|
//-----------------------------------------
|
||||||
|
|
||||||
|
axis_width_conv #(
|
||||||
|
.WORD_W (OUT_DATA_W),
|
||||||
|
.IN_WORDS (IN_WORDS ),
|
||||||
|
.OUT_WORDS(1 ),
|
||||||
|
.SYNC_CLKS(1 ),
|
||||||
|
.PIPELINE ("INOUT" )
|
||||||
|
) axis_width_conv_i (
|
||||||
|
.s_axis_aclk (clk ),
|
||||||
|
.s_axis_rst (rst ),
|
||||||
|
.s_axis_tdata (out_fifo_tdata[idx] ),
|
||||||
|
.s_axis_tkeep ('1 ),
|
||||||
|
.s_axis_tlast (out_fifo_tlast[idx] ),
|
||||||
|
.s_axis_tvalid(out_fifo_tvalid[idx]),
|
||||||
|
.s_axis_tready(out_fifo_tready[idx]),
|
||||||
|
.m_axis_aclk (clk ),
|
||||||
|
.m_axis_rst (rst ),
|
||||||
|
.m_axis_tdata (o_tdata[idx] ),
|
||||||
|
.m_axis_tkeep ( ),
|
||||||
|
.m_axis_tlast (o_tlast[idx] ),
|
||||||
|
.m_axis_tvalid(o_tvalid[idx] ),
|
||||||
|
.m_axis_tready(o_tready[idx] )
|
||||||
|
);
|
||||||
|
|
||||||
|
//-----------------------------------------
|
||||||
|
// TUSER Handling
|
||||||
|
//-----------------------------------------
|
||||||
|
|
||||||
|
// We write to the TUSER FIFO in lock-step with the axis_width_conv
|
||||||
|
// module. But read it out at a rate of 1/IN_WORDS so that the TUSER
|
||||||
|
// output matches the same data it did on the way in.
|
||||||
|
|
||||||
|
logic user_fifo_i_tready;
|
||||||
|
logic user_fifo_o_tvalid;
|
||||||
|
logic user_fifo_o_tready;
|
||||||
|
logic [COUNT_W-1:0] word_count;
|
||||||
|
|
||||||
|
// This counter tells when we're outputting the last TUSER for the
|
||||||
|
// current input word, so we know when to pop the output from the FIFO.
|
||||||
|
always_ff @(posedge clk) begin
|
||||||
|
if (o_tvalid[idx] && o_tready[idx]) begin
|
||||||
|
if (word_count == IN_WORDS-1) begin
|
||||||
|
word_count <= '0;
|
||||||
|
end else begin
|
||||||
|
word_count <= word_count + 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if (rst) begin
|
||||||
|
word_count <= '0;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
assign user_fifo_o_tready = (word_count == IN_WORDS-1) && o_tvalid[idx] && o_tready[idx];
|
||||||
|
|
||||||
|
axi_fifo #(
|
||||||
|
.WIDTH(USER_W),
|
||||||
|
.SIZE (2 )
|
||||||
|
) axi_fifo_tuser (
|
||||||
|
.clk (clk ),
|
||||||
|
.reset (rst ),
|
||||||
|
.clear ('0 ),
|
||||||
|
.i_tdata (out_fifo_tuser[idx] ),
|
||||||
|
.i_tvalid(out_fifo_tvalid[idx] && out_fifo_tready[idx]),
|
||||||
|
.i_tready(user_fifo_i_tready ),
|
||||||
|
.o_tdata (o_tuser[idx] ),
|
||||||
|
.o_tvalid(user_fifo_o_tvalid ),
|
||||||
|
.o_tready(user_fifo_o_tready ),
|
||||||
|
.occupied( ),
|
||||||
|
.space ( )
|
||||||
|
);
|
||||||
|
|
||||||
|
// Make sure we don't overflow/underflow the TUSER FIFO
|
||||||
|
//synthesis translate_off
|
||||||
|
always_ff @(posedge clk) begin
|
||||||
|
if (out_fifo_tvalid[idx] && out_fifo_tready[idx] && !user_fifo_i_tready) begin
|
||||||
|
$error("TUSER FIFO overflow");
|
||||||
|
end
|
||||||
|
if (user_fifo_o_tready && !user_fifo_o_tvalid) begin
|
||||||
|
$error("TUSER FIFO underflow");
|
||||||
|
end
|
||||||
|
end
|
||||||
|
//synthesis translate_on
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
endmodule : axis_load_split
|
||||||
|
|
||||||
|
|
||||||
|
`default_nettype wire
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#
|
||||||
|
# Copyright 2024 Ettus Research, a National Instruments Brand
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
#
|
||||||
|
|
||||||
|
#-------------------------------------------------
|
||||||
|
# Top-of-Makefile
|
||||||
|
#-------------------------------------------------
|
||||||
|
# Define BASE_DIR to point to the "top" dir
|
||||||
|
BASE_DIR = $(abspath ../../../../top)
|
||||||
|
# Include viv_sim_preamble after defining BASE_DIR
|
||||||
|
include $(BASE_DIR)/../tools/make/viv_sim_preamble.mak
|
||||||
|
|
||||||
|
#-------------------------------------------------
|
||||||
|
# Testbench Specific
|
||||||
|
#-------------------------------------------------
|
||||||
|
# Define only one top-level module
|
||||||
|
SIM_TOP = axis_load_split_merge_all_tb
|
||||||
|
|
||||||
|
# Add test bench, user design under test, and
|
||||||
|
# additional user created files
|
||||||
|
SIM_SRCS = $(abspath \
|
||||||
|
axis_load_split_merge_tb.sv \
|
||||||
|
axis_load_split_merge_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
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2025 Ettus Research, a National Instruments Brand
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
//
|
||||||
|
// Module: axis_load_split_merge_all_tb
|
||||||
|
//
|
||||||
|
// Description:
|
||||||
|
//
|
||||||
|
// Top-level testbench for axis_load_split and axis_load_merge modules. This
|
||||||
|
// instantiates multiple instances of axis_load_split_merge_tb to test
|
||||||
|
// different configurations.
|
||||||
|
//
|
||||||
|
|
||||||
|
module axis_load_split_merge_all_tb;
|
||||||
|
|
||||||
|
localparam int MAX_PKT_LEN = 16;
|
||||||
|
localparam int INT_FIFO_SIZE = $clog2(MAX_PKT_LEN);
|
||||||
|
localparam int DATA_W = 8;
|
||||||
|
localparam int USER_W = 4;
|
||||||
|
|
||||||
|
axis_load_split_merge_tb #(
|
||||||
|
.MAX_PKT_LEN (MAX_PKT_LEN),
|
||||||
|
.DATA_W (4*DATA_W),
|
||||||
|
.USER_W (USER_W),
|
||||||
|
.USER_SEL (3),
|
||||||
|
.INT_DATA_W (DATA_W),
|
||||||
|
.NUM_PORTS (4),
|
||||||
|
.INT_FIFO_SIZE(INT_FIFO_SIZE),
|
||||||
|
.EXT_FIFO_SIZE(1)
|
||||||
|
) tb_32b_to_4x8b();
|
||||||
|
|
||||||
|
axis_load_split_merge_tb #(
|
||||||
|
.MAX_PKT_LEN (MAX_PKT_LEN),
|
||||||
|
.DATA_W (2*DATA_W),
|
||||||
|
.USER_W (USER_W),
|
||||||
|
.USER_SEL (0),
|
||||||
|
.INT_DATA_W (DATA_W),
|
||||||
|
.NUM_PORTS (2),
|
||||||
|
.INT_FIFO_SIZE(INT_FIFO_SIZE),
|
||||||
|
.EXT_FIFO_SIZE(1)
|
||||||
|
) tb_16b_to_2x8b();
|
||||||
|
|
||||||
|
axis_load_split_merge_tb #(
|
||||||
|
.MAX_PKT_LEN (MAX_PKT_LEN),
|
||||||
|
.DATA_W (4*DATA_W),
|
||||||
|
.USER_W (USER_W),
|
||||||
|
.USER_SEL (1),
|
||||||
|
.INT_DATA_W (DATA_W),
|
||||||
|
.NUM_PORTS (3),
|
||||||
|
.INT_FIFO_SIZE(INT_FIFO_SIZE),
|
||||||
|
.EXT_FIFO_SIZE(1)
|
||||||
|
) tb_32b_to_3x8b();
|
||||||
|
|
||||||
|
axis_load_split_merge_tb #(
|
||||||
|
.MAX_PKT_LEN (MAX_PKT_LEN),
|
||||||
|
.DATA_W (4*DATA_W),
|
||||||
|
.USER_W (USER_W),
|
||||||
|
.USER_SEL (2),
|
||||||
|
.INT_DATA_W (DATA_W),
|
||||||
|
.NUM_PORTS (2),
|
||||||
|
.INT_FIFO_SIZE(INT_FIFO_SIZE),
|
||||||
|
.EXT_FIFO_SIZE(1)
|
||||||
|
) tb_32b_to_2x8b();
|
||||||
|
|
||||||
|
axis_load_split_merge_tb #(
|
||||||
|
.MAX_PKT_LEN (MAX_PKT_LEN),
|
||||||
|
.DATA_W (DATA_W),
|
||||||
|
.USER_W (USER_W),
|
||||||
|
.USER_SEL (0),
|
||||||
|
.INT_DATA_W (DATA_W),
|
||||||
|
.NUM_PORTS (4),
|
||||||
|
.INT_FIFO_SIZE(INT_FIFO_SIZE),
|
||||||
|
.EXT_FIFO_SIZE(4)
|
||||||
|
) tb_8b_to_4x8b();
|
||||||
|
|
||||||
|
axis_load_split_merge_tb #(
|
||||||
|
.MAX_PKT_LEN (MAX_PKT_LEN),
|
||||||
|
.DATA_W (2*DATA_W),
|
||||||
|
.USER_W (USER_W),
|
||||||
|
.USER_SEL (0),
|
||||||
|
.INT_DATA_W (DATA_W),
|
||||||
|
.NUM_PORTS (4),
|
||||||
|
.INT_FIFO_SIZE(INT_FIFO_SIZE),
|
||||||
|
.EXT_FIFO_SIZE(4)
|
||||||
|
) tb_16b_to_4x8b();
|
||||||
|
|
||||||
|
endmodule
|
||||||
@@ -0,0 +1,352 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2025 Ettus Research, a National Instruments Brand
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
//
|
||||||
|
// Module: axis_load_split_merge_tb
|
||||||
|
//
|
||||||
|
// Description:
|
||||||
|
//
|
||||||
|
// Testbench for axis_load_split and axis_load_merge modules. It tests both
|
||||||
|
// modules together but taking an input data stream and splitting it into
|
||||||
|
// multiple streams, then combining them back together to recover the
|
||||||
|
// original data stream.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
//
|
||||||
|
// MAX_PKT_LEN : Maximum packet length to test, in number of DATA_W words.
|
||||||
|
// DATA_W : Width of the input data port to axis_load_split and the
|
||||||
|
// output data port of axis_load_merge.
|
||||||
|
// USER_W : Width of TUSER to test.
|
||||||
|
// USER_SEL : USER_SEL value to test.
|
||||||
|
// INT_DATA_W : Internal data width, at the output of axis_load_split and
|
||||||
|
// the input to axis_load_merge.
|
||||||
|
// NUM_PORTS : The number of ports to split into and merge.
|
||||||
|
// INT_FIFO_SIZE : Log base 2 of the FIFO size (in units of DATA_W sized
|
||||||
|
// words) to configure for the output FIFOs in
|
||||||
|
// axis_load_split and the input FIFOs in axis_load_merge.
|
||||||
|
// EXT_FIFO_SIZE : Log base 2 of the FIFO size (in units of DATA_W sized
|
||||||
|
// words) to configure for the input FIFO in axis_load_split
|
||||||
|
// and the output FIFO in axis_load_merge.
|
||||||
|
//
|
||||||
|
|
||||||
|
module axis_load_split_merge_tb #(
|
||||||
|
int MAX_PKT_LEN = 16,
|
||||||
|
int DATA_W = 32,
|
||||||
|
int USER_W = 4,
|
||||||
|
int USER_SEL = 0,
|
||||||
|
int INT_DATA_W = 8,
|
||||||
|
int NUM_PORTS = DATA_W/INT_DATA_W,
|
||||||
|
int INT_FIFO_SIZE = $clog2(MAX_PKT_LEN),
|
||||||
|
int EXT_FIFO_SIZE = 1
|
||||||
|
);
|
||||||
|
|
||||||
|
`include "test_exec.svh"
|
||||||
|
`include "usrp_utils.svh"
|
||||||
|
|
||||||
|
import PkgTestExec::*;
|
||||||
|
import PkgAxiStreamBfm::*;
|
||||||
|
import PkgRandom::*;
|
||||||
|
|
||||||
|
// Use random or sequential data (easier for debug)
|
||||||
|
localparam bit USE_RANDOM = 1;
|
||||||
|
|
||||||
|
localparam real CLK_PER = 5;
|
||||||
|
|
||||||
|
// Number of packets to test
|
||||||
|
localparam NUM_PKTS = NUM_PORTS*100;
|
||||||
|
|
||||||
|
// Width of packet count that's put in each packet
|
||||||
|
localparam int PKT_COUNT_W = 8;
|
||||||
|
|
||||||
|
if (INT_DATA_W < PKT_COUNT_W) begin : check_data_width
|
||||||
|
$error("INT_DATA_W must be at least PKT_COUNT_W");
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
// Clocks and Resets
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
bit clk;
|
||||||
|
bit rst;
|
||||||
|
|
||||||
|
sim_clock_gen #(.PERIOD(CLK_PER), .AUTOSTART(0))
|
||||||
|
clk_gen (.clk(clk), .rst(rst));
|
||||||
|
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
// Bus Functional Models
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// AXI-Stream Interfaces
|
||||||
|
AxiStreamIf #(DATA_W, USER_W) i_axis (clk, rst);
|
||||||
|
AxiStreamIf #(DATA_W, USER_W) o_axis (clk, rst);
|
||||||
|
|
||||||
|
// AXI-Stream BFMs
|
||||||
|
typedef AxiStreamBfm #(DATA_W, USER_W) bfm_t;
|
||||||
|
typedef bfm_t::AxisPacket_t pkt_t;
|
||||||
|
bfm_t bfm = new(i_axis, o_axis);
|
||||||
|
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
// Device Under Test (DUT)
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
logic [INT_DATA_W-1:0] split_tdata [NUM_PORTS];
|
||||||
|
logic [ USER_W-1:0] split_tuser [NUM_PORTS];
|
||||||
|
logic split_tlast [NUM_PORTS];
|
||||||
|
logic split_tvalid [NUM_PORTS];
|
||||||
|
logic split_tready [NUM_PORTS];
|
||||||
|
|
||||||
|
// Convert the internal FIFO size from the external word size (DATA_W) to the
|
||||||
|
// internal word size (INT_DATA_W).
|
||||||
|
localparam int INT_WORD_FIFO_SIZE = INT_FIFO_SIZE + $clog2(DATA_W/INT_DATA_W);
|
||||||
|
|
||||||
|
logic [USER_W-1:0] masked_tuser [NUM_PORTS];
|
||||||
|
|
||||||
|
axis_load_split #(
|
||||||
|
.IN_DATA_W (DATA_W ),
|
||||||
|
.IN_FIFO_SIZE (EXT_FIFO_SIZE ),
|
||||||
|
.OUT_DATA_W (INT_DATA_W ),
|
||||||
|
.OUT_FIFO_SIZE(INT_WORD_FIFO_SIZE),
|
||||||
|
.OUT_NUM_PORTS(NUM_PORTS ),
|
||||||
|
.USER_W (USER_W )
|
||||||
|
) axis_load_split_i (
|
||||||
|
.clk (clk ),
|
||||||
|
.rst (rst ),
|
||||||
|
.i_tdata (i_axis.tdata ),
|
||||||
|
.i_tuser (i_axis.tuser ),
|
||||||
|
.i_tlast (i_axis.tlast ),
|
||||||
|
.i_tvalid(i_axis.tvalid),
|
||||||
|
.i_tready(i_axis.tready),
|
||||||
|
.o_tdata (split_tdata ),
|
||||||
|
.o_tuser (split_tuser ),
|
||||||
|
.o_tlast (split_tlast ),
|
||||||
|
.o_tvalid(split_tvalid ),
|
||||||
|
.o_tready(split_tready )
|
||||||
|
);
|
||||||
|
|
||||||
|
axis_load_merge #(
|
||||||
|
.IN_DATA_W (INT_DATA_W ),
|
||||||
|
.IN_FIFO_SIZE (INT_WORD_FIFO_SIZE),
|
||||||
|
.IN_NUM_PORTS (NUM_PORTS ),
|
||||||
|
.OUT_DATA_W (DATA_W ),
|
||||||
|
.OUT_FIFO_SIZE(EXT_FIFO_SIZE ),
|
||||||
|
.USER_W (USER_W ),
|
||||||
|
.USER_SEL (USER_SEL )
|
||||||
|
) axis_load_merge_i (
|
||||||
|
.clk (clk ),
|
||||||
|
.rst (rst ),
|
||||||
|
.i_tdata (split_tdata ),
|
||||||
|
.i_tuser (masked_tuser ),
|
||||||
|
.i_tlast (split_tlast ),
|
||||||
|
.i_tvalid(split_tvalid ),
|
||||||
|
.i_tready(split_tready ),
|
||||||
|
.o_tdata (o_axis.tdata ),
|
||||||
|
.o_tuser (o_axis.tuser ),
|
||||||
|
.o_tlast (o_axis.tlast ),
|
||||||
|
.o_tvalid(o_axis.tvalid),
|
||||||
|
.o_tready(o_axis.tready)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
// User Mask
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// In order to verify that TUSER is used as expected, we mask the TUSER words
|
||||||
|
// that should be ignored by axis_load_merge by changing them to X.
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
for (genvar port = 0; port < NUM_PORTS; port++) begin : gen_user_check
|
||||||
|
int count;
|
||||||
|
|
||||||
|
assign masked_tuser[port] = (count == USER_SEL) ? split_tuser[port] : 'X;
|
||||||
|
|
||||||
|
always_ff @(posedge clk) begin
|
||||||
|
if (split_tvalid[port] && split_tready[port]) begin
|
||||||
|
if (split_tlast[port] || count == DATA_W/INT_DATA_W-1) begin
|
||||||
|
count <= 0;
|
||||||
|
end else begin
|
||||||
|
count <= count + 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if (rst) begin
|
||||||
|
count <= 0;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
// Check Port Order
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Here we double check that the ports are used in a round robin order as
|
||||||
|
// expected. This is done by checking the counter that's embedded in each
|
||||||
|
// packet and making sure it corresponds to the correct port.
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
bit pkt_count_rst;
|
||||||
|
|
||||||
|
for (genvar port = 0; port < NUM_PORTS; port++) begin : gen_port_check
|
||||||
|
bit [PKT_COUNT_W-1:0] pkt_count = port;
|
||||||
|
bit start_of_packet = 1;
|
||||||
|
|
||||||
|
always_ff @(posedge clk) begin
|
||||||
|
if (split_tvalid[port] && split_tready[port]) begin
|
||||||
|
if (start_of_packet) begin
|
||||||
|
`ASSERT_ERROR(
|
||||||
|
split_tdata[port][0+:PKT_COUNT_W] == pkt_count,
|
||||||
|
$sformatf(
|
||||||
|
"Unexpected packet count on port %0d. Expected %0d, read %0d.",
|
||||||
|
port, pkt_count, split_tdata[port]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
pkt_count <= pkt_count + NUM_PORTS;
|
||||||
|
end
|
||||||
|
start_of_packet <= split_tlast[port];
|
||||||
|
end
|
||||||
|
|
||||||
|
if (pkt_count_rst) begin
|
||||||
|
pkt_count <= port;
|
||||||
|
start_of_packet <= 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
// Test Procedures
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
task automatic test_packets(int num_pkts = NUM_PKTS);
|
||||||
|
pkt_t::data_t data_count = 0;
|
||||||
|
mailbox pkt_mb = new();
|
||||||
|
|
||||||
|
// Reset the packet counters
|
||||||
|
clk_gen.clk_wait_f();
|
||||||
|
pkt_count_rst <= 1;
|
||||||
|
clk_gen.clk_wait_f();
|
||||||
|
pkt_count_rst <= 0;
|
||||||
|
|
||||||
|
fork
|
||||||
|
begin : send_thread
|
||||||
|
for (int pkt_count = 0; pkt_count < num_pkts; pkt_count++) begin
|
||||||
|
pkt_t pkt;
|
||||||
|
int pkt_length;
|
||||||
|
logic [DATA_W-1:0] data;
|
||||||
|
logic [USER_W-1:0] user;
|
||||||
|
|
||||||
|
pkt = new();
|
||||||
|
pkt_length = $urandom_range(1, MAX_PKT_LEN);
|
||||||
|
repeat (pkt_length) begin
|
||||||
|
data = USE_RANDOM ? Rand#(DATA_W)::rand_logic() : data_count;
|
||||||
|
user = USE_RANDOM ? Rand#(DATA_W)::rand_logic() : ~data_count;
|
||||||
|
pkt.data.push_back(data);
|
||||||
|
pkt.user.push_back(user);
|
||||||
|
data_count++;
|
||||||
|
end
|
||||||
|
// Put the packet count in the first byte of the packet so we can
|
||||||
|
// easily tell the packets apart.
|
||||||
|
pkt.data[0][0+:PKT_COUNT_W] = pkt_count;
|
||||||
|
bfm.put(pkt);
|
||||||
|
pkt_mb.put(pkt.copy());
|
||||||
|
end
|
||||||
|
end
|
||||||
|
begin : recv_thread
|
||||||
|
for (int pkt_count = 0; pkt_count < num_pkts; pkt_count++) begin
|
||||||
|
pkt_t act_pkt, exp_pkt;
|
||||||
|
bfm.get(act_pkt);
|
||||||
|
act_pkt.keep = {}; // Not using keep, so remove the X's
|
||||||
|
pkt_mb.get(exp_pkt);
|
||||||
|
|
||||||
|
// Verify that we got the expected data
|
||||||
|
`ASSERT_ERROR(
|
||||||
|
exp_pkt.equal(act_pkt),
|
||||||
|
$sformatf("On packet %0d, actual does not match expected", pkt_count)
|
||||||
|
);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
join
|
||||||
|
endtask : test_packets
|
||||||
|
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
// Main
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
initial begin : main
|
||||||
|
string tb_name;
|
||||||
|
|
||||||
|
// Generate a string for the name of this instance of the testbench
|
||||||
|
tb_name = $sformatf({
|
||||||
|
"rfnoc_block_fft_tb\n",
|
||||||
|
"\tMAX_PKT_LEN = %0d\n",
|
||||||
|
"\tDATA_W = %0d\n",
|
||||||
|
"\tUSER_W = %0d\n",
|
||||||
|
"\tUSER_SEL = %0d\n",
|
||||||
|
"\tINT_DATA_W = %0d\n",
|
||||||
|
"\tNUM_PORTS = %0d\n",
|
||||||
|
"\tINT_FIFO_SIZE = %0d\n",
|
||||||
|
"\tEXT_FIFO_SIZE = %0d"},
|
||||||
|
MAX_PKT_LEN, DATA_W, USER_W, USER_SEL, INT_DATA_W, NUM_PORTS,
|
||||||
|
INT_FIFO_SIZE, EXT_FIFO_SIZE
|
||||||
|
);
|
||||||
|
|
||||||
|
// Initialize the test exec object for this testbench
|
||||||
|
test.start_tb(tb_name);
|
||||||
|
|
||||||
|
// Start the clocks
|
||||||
|
clk_gen.start();
|
||||||
|
|
||||||
|
// Start the BFMs
|
||||||
|
bfm.run();
|
||||||
|
|
||||||
|
//--------------------------------
|
||||||
|
// Reset
|
||||||
|
//--------------------------------
|
||||||
|
|
||||||
|
test.start_test("Reset");
|
||||||
|
clk_gen.reset();
|
||||||
|
@(negedge rst);
|
||||||
|
test.end_test();
|
||||||
|
|
||||||
|
//--------------------------------
|
||||||
|
// Tests
|
||||||
|
//--------------------------------
|
||||||
|
|
||||||
|
test.start_test("Packet tests (normal)");
|
||||||
|
bfm.set_slave_stall_prob(50);
|
||||||
|
bfm.set_master_stall_prob(50);
|
||||||
|
test_packets();
|
||||||
|
test.end_test();
|
||||||
|
|
||||||
|
test.start_test("Packet tests (back-pressure)");
|
||||||
|
bfm.set_slave_stall_prob(95);
|
||||||
|
bfm.set_master_stall_prob(5);
|
||||||
|
test_packets();
|
||||||
|
test.end_test();
|
||||||
|
|
||||||
|
test.start_test("Packet tests (underflow)");
|
||||||
|
bfm.set_slave_stall_prob(95);
|
||||||
|
bfm.set_master_stall_prob(5);
|
||||||
|
test_packets();
|
||||||
|
test.end_test();
|
||||||
|
|
||||||
|
//--------------------------------
|
||||||
|
// Finish Up
|
||||||
|
//--------------------------------
|
||||||
|
|
||||||
|
// Display final statistics and results
|
||||||
|
test.end_tb(0);
|
||||||
|
|
||||||
|
// Kill the clocks to end this instance of the testbench
|
||||||
|
clk_gen.kill();
|
||||||
|
end
|
||||||
|
|
||||||
|
endmodule
|
||||||
Reference in New Issue
Block a user