Files
b210-k7-fpga/lib/axi/axis_load_merge.sv
T
Wade Fife bb2bfbfe95 fpga: lib: Add AXI-Stream load split and merge modules
Original-commit: 32fb626ef1b1d3078fd331143b03cd38d1e155f0
2025-02-14 15:59:19 -06:00

303 lines
12 KiB
Systemverilog

//
// 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