fpga: rfnoc: Add RFNoC CHDR resize module
Original-commit: a94ea11f00bba2c4227c9ab173eb4b6ef1049bad
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
#
|
||||
# Copyright 2021 Ettus Research, a National Instruments Brand
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
#
|
||||
|
||||
#-------------------------------------------------
|
||||
# Top-of-Makefile
|
||||
#-------------------------------------------------
|
||||
# Define BASE_DIR to point to the "top" dir.
|
||||
BASE_DIR = $(abspath ../../../../top)
|
||||
# Include viv_sim_preample after defining BASE_DIR
|
||||
include $(BASE_DIR)/../tools/make/viv_sim_preamble.mak
|
||||
|
||||
#-------------------------------------------------
|
||||
# Design Specific
|
||||
#-------------------------------------------------
|
||||
|
||||
DESIGN_SRCS += $(abspath \
|
||||
$(abspath ../../utils/chdr_convert_up.v) \
|
||||
$(abspath ../../utils/chdr_convert_down.v) \
|
||||
$(abspath ../../utils/chdr_resize.v) \
|
||||
)
|
||||
|
||||
#-------------------------------------------------
|
||||
# Testbench Specific
|
||||
#-------------------------------------------------
|
||||
SIM_TOP = chdr_resize_all_tb
|
||||
SIM_SRCS = \
|
||||
$(abspath chdr_resize_tb.sv) \
|
||||
$(abspath chdr_resize_all_tb.sv) \
|
||||
|
||||
#-------------------------------------------------
|
||||
# Bottom-of-Makefile
|
||||
#-------------------------------------------------
|
||||
# Include all simulator specific makefiles here
|
||||
# Each should define a unique target to simulate
|
||||
# e.g. xsim, vsim, etc and a common "clean" target
|
||||
include $(BASE_DIR)/../tools/make/viv_simulator.mak
|
||||
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// Copyright 2021 Ettus Research, a National Instruments Brand
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
//
|
||||
// Module: chdr_resize_all_tb
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// Top-level testbench for chdr_resize, testing different configurations of
|
||||
// the module.
|
||||
//
|
||||
|
||||
module chdr_resize_all_tb;
|
||||
// Up-size CHDR bus
|
||||
chdr_resize_tb #(.I_CHDR_W( 64), .O_CHDR_W(128), .PIPELINE("NONE" )) chdr_resize_tb_00();
|
||||
chdr_resize_tb #(.I_CHDR_W( 64), .O_CHDR_W(256), .PIPELINE("IN" )) chdr_resize_tb_01();
|
||||
chdr_resize_tb #(.I_CHDR_W(128), .O_CHDR_W(256), .PIPELINE("OUT" )) chdr_resize_tb_02();
|
||||
chdr_resize_tb #(.I_CHDR_W(128), .O_CHDR_W(512), .PIPELINE("INOUT")) chdr_resize_tb_03();
|
||||
chdr_resize_tb #(.I_CHDR_W(256), .O_CHDR_W(512), .PIPELINE("NONE" )) chdr_resize_tb_04();
|
||||
// Down-size CHDR bus
|
||||
chdr_resize_tb #(.I_CHDR_W(128), .O_CHDR_W( 64), .PIPELINE("NONE" )) chdr_resize_tb_10();
|
||||
chdr_resize_tb #(.I_CHDR_W(256), .O_CHDR_W( 64), .PIPELINE("IN" )) chdr_resize_tb_11();
|
||||
chdr_resize_tb #(.I_CHDR_W(256), .O_CHDR_W(128), .PIPELINE("OUT" )) chdr_resize_tb_12();
|
||||
chdr_resize_tb #(.I_CHDR_W(512), .O_CHDR_W(128), .PIPELINE("INOUT")) chdr_resize_tb_13();
|
||||
chdr_resize_tb #(.I_CHDR_W(512), .O_CHDR_W(256), .PIPELINE("NONE" )) chdr_resize_tb_14();
|
||||
|
||||
// Up-size CHDR encoding (keep bus width)
|
||||
chdr_resize_tb #(.I_CHDR_W( 64), .O_CHDR_W(128), .I_DATA_W( 64), .O_DATA_W( 64), .PIPELINE("NONE" )) chdr_resize_tb_20();
|
||||
chdr_resize_tb #(.I_CHDR_W( 64), .O_CHDR_W(512), .I_DATA_W( 64), .O_DATA_W( 64), .PIPELINE("IN" )) chdr_resize_tb_21();
|
||||
chdr_resize_tb #(.I_CHDR_W(128), .O_CHDR_W(256), .I_DATA_W(128), .O_DATA_W(128), .PIPELINE("OUT" )) chdr_resize_tb_22();
|
||||
chdr_resize_tb #(.I_CHDR_W(128), .O_CHDR_W(512), .I_DATA_W(128), .O_DATA_W(128), .PIPELINE("INOUT")) chdr_resize_tb_23();
|
||||
chdr_resize_tb #(.I_CHDR_W(256), .O_CHDR_W(512), .I_DATA_W(256), .O_DATA_W(256), .PIPELINE("NONE" )) chdr_resize_tb_24();
|
||||
// Down-size CHDR encoding (keep bus width)
|
||||
chdr_resize_tb #(.I_CHDR_W(128), .O_CHDR_W( 64), .I_DATA_W( 64), .O_DATA_W( 64), .PIPELINE("NONE" )) chdr_resize_tb_30();
|
||||
chdr_resize_tb #(.I_CHDR_W(512), .O_CHDR_W( 64), .I_DATA_W( 64), .O_DATA_W( 64), .PIPELINE("IN" )) chdr_resize_tb_31();
|
||||
chdr_resize_tb #(.I_CHDR_W(256), .O_CHDR_W(128), .I_DATA_W(128), .O_DATA_W(128), .PIPELINE("OUT" )) chdr_resize_tb_32();
|
||||
chdr_resize_tb #(.I_CHDR_W(512), .O_CHDR_W(128), .I_DATA_W(128), .O_DATA_W(128), .PIPELINE("INOUT")) chdr_resize_tb_33();
|
||||
chdr_resize_tb #(.I_CHDR_W(512), .O_CHDR_W(256), .I_DATA_W(256), .O_DATA_W(256), .PIPELINE("NONE" )) chdr_resize_tb_34();
|
||||
|
||||
// No resize (pass through)
|
||||
chdr_resize_tb #(.I_CHDR_W(512), .O_CHDR_W(512), .PIPELINE("NONE" )) chdr_resize_tb_40();
|
||||
chdr_resize_tb #(.I_CHDR_W(256), .O_CHDR_W(256), .PIPELINE("IN" )) chdr_resize_tb_41();
|
||||
chdr_resize_tb #(.I_CHDR_W(128), .O_CHDR_W(128), .PIPELINE("OUT" )) chdr_resize_tb_42();
|
||||
chdr_resize_tb #(.I_CHDR_W( 64), .O_CHDR_W( 64), .PIPELINE("INOUT")) chdr_resize_tb_43();
|
||||
endmodule : chdr_resize_all_tb
|
||||
@@ -0,0 +1,666 @@
|
||||
//
|
||||
// Copyright 2021 Ettus Research, a National Instruments Brand
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
//
|
||||
// Module: chdr_resize_tb
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// Testbench for chdr_resize.
|
||||
//
|
||||
|
||||
`default_nettype none
|
||||
|
||||
|
||||
module chdr_resize_tb #(
|
||||
parameter I_CHDR_W = 64,
|
||||
parameter O_CHDR_W = 128,
|
||||
parameter I_DATA_W = I_CHDR_W,
|
||||
parameter O_DATA_W = O_CHDR_W,
|
||||
parameter USER_W = 4,
|
||||
parameter PIPELINE = "NONE"
|
||||
);
|
||||
|
||||
// Include macros and time declarations for use with PkgTestExec
|
||||
`include "test_exec.svh"
|
||||
|
||||
`define MIN(A, B) ((A)<(B)?(A):(B))
|
||||
`define DIV_CEIL(N, D) (((N)+(D)-1)/(D)) // ceiling(N/D)
|
||||
|
||||
import PkgTestExec::*;
|
||||
import PkgChdrUtils::*;
|
||||
import PkgChdrBfm::*;
|
||||
import PkgRandom::*;
|
||||
|
||||
// Clock periods
|
||||
localparam real CLK_PERIOD = 10.0;
|
||||
|
||||
// Widths for BFMs to use
|
||||
localparam ITEM_W = 8;
|
||||
|
||||
// Test parameters
|
||||
localparam NUM_PACKETS = 1000; // Number of packets to test
|
||||
localparam MAX_MDATA_WORDS = 31; // Maximum number of metadata words (31
|
||||
// is the max supported by CHDR).
|
||||
localparam MAX_PYLD_WORDS = 64; // Maximum number of payload words.
|
||||
localparam USE_RANDOM = 1; // Use random vs. sequential data.
|
||||
localparam DEBUG = 0; // Display extra debug info
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Clocks and Resets
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
bit clk;
|
||||
bit rst;
|
||||
|
||||
sim_clock_gen #(.PERIOD(CLK_PERIOD), .AUTOSTART(0))
|
||||
clk_gen (.clk(clk), .rst(rst));
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Bus Functional Models
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
// Connections to DUT as interfaces:
|
||||
AxiStreamIf #(I_CHDR_W) m_chdr (clk, 1'b0);
|
||||
AxiStreamIf #(O_CHDR_W) s_chdr (clk, 1'b0);
|
||||
|
||||
// CHDR BFMs. Because the input and output have different CHDR widths, we
|
||||
// need two BFMs, one for each CHDR width.
|
||||
ChdrBfm #(I_CHDR_W) m_bfm = new(m_chdr, null);
|
||||
ChdrBfm #(O_CHDR_W) s_bfm = new(null, s_chdr);
|
||||
|
||||
// CHDR data types
|
||||
typedef ChdrPacket #(I_CHDR_W)::ChdrPacket_t IChdrPacket_t;
|
||||
typedef ChdrPacket #(O_CHDR_W)::ChdrPacket_t OChdrPacket_t;
|
||||
typedef ChdrData #(I_CHDR_W, ITEM_W)::chdr_word_t i_chdr_word_t;
|
||||
typedef ChdrData #(O_CHDR_W, ITEM_W)::chdr_word_t o_chdr_word_t;
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Device Under Test (DUT)
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// To simplify the testbench we randomly generate CHDR packets, resize them,
|
||||
// then change their size back. This tests up-sizing and down-sizing
|
||||
// simultaneously and simplifies output checking. It's possible, however,
|
||||
// that we make a mistake up-sizing, then undo that mistake when downsizing,
|
||||
// so there is some risk to this simplification.
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
wire [I_DATA_W-1:0] i_data_tdata;
|
||||
wire [ USER_W-1:0] i_data_tuser;
|
||||
wire i_data_tlast;
|
||||
wire i_data_tvalid;
|
||||
wire i_data_tready;
|
||||
|
||||
wire [O_DATA_W-1:0] o_data_tdata;
|
||||
wire [ USER_W-1:0] o_data_tuser;
|
||||
wire o_data_tlast;
|
||||
wire o_data_tvalid;
|
||||
wire o_data_tready;
|
||||
|
||||
chdr_resize #(
|
||||
.I_CHDR_W (I_CHDR_W),
|
||||
.O_CHDR_W (O_CHDR_W),
|
||||
.I_DATA_W (I_DATA_W),
|
||||
.O_DATA_W (O_DATA_W),
|
||||
.USER_W (USER_W),
|
||||
.PIPELINE (PIPELINE)
|
||||
) chdr_resize_i (
|
||||
.clk (clk),
|
||||
.rst (rst),
|
||||
.i_chdr_tdata (i_data_tdata),
|
||||
.i_chdr_tuser (i_data_tuser),
|
||||
.i_chdr_tlast (i_data_tlast),
|
||||
.i_chdr_tvalid (i_data_tvalid),
|
||||
.i_chdr_tready (i_data_tready),
|
||||
.o_chdr_tdata (o_data_tdata),
|
||||
.o_chdr_tuser (o_data_tuser),
|
||||
.o_chdr_tlast (o_data_tlast),
|
||||
.o_chdr_tvalid (o_data_tvalid),
|
||||
.o_chdr_tready (o_data_tready)
|
||||
);
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Bus Resizer for DUT
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Resize the DUT inputs/outputs to match match the widths used by our BFMs.
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
localparam IN_WORD_W = `MIN(I_CHDR_W, I_DATA_W);
|
||||
localparam OUT_WORD_W = `MIN(O_CHDR_W, O_DATA_W);
|
||||
localparam OUT_KEEP_W = O_CHDR_W/OUT_WORD_W;
|
||||
|
||||
wire [I_CHDR_W-1:0] i_chdr_tdata;
|
||||
wire i_chdr_tlast;
|
||||
wire i_chdr_tvalid;
|
||||
wire i_chdr_tready;
|
||||
|
||||
wire [ O_CHDR_W-1:0] o_chdr_tdata_unmasked;
|
||||
wire [ O_CHDR_W-1:0] o_chdr_tdata;
|
||||
wire [OUT_KEEP_W-1:0] o_chdr_tkeep;
|
||||
wire o_chdr_tlast;
|
||||
wire o_chdr_tvalid;
|
||||
wire o_chdr_tready;
|
||||
|
||||
axis_width_conv #(
|
||||
.WORD_W (IN_WORD_W),
|
||||
.IN_WORDS (I_CHDR_W/IN_WORD_W),
|
||||
.OUT_WORDS (I_DATA_W/IN_WORD_W),
|
||||
.SYNC_CLKS (1),
|
||||
.PIPELINE ("NONE")
|
||||
) axis_width_conv_in (
|
||||
.s_axis_aclk (clk),
|
||||
.s_axis_rst (rst),
|
||||
.s_axis_tdata (i_chdr_tdata),
|
||||
.s_axis_tkeep ('1),
|
||||
.s_axis_tlast (i_chdr_tlast),
|
||||
.s_axis_tvalid (i_chdr_tvalid),
|
||||
.s_axis_tready (i_chdr_tready),
|
||||
.m_axis_aclk (clk),
|
||||
.m_axis_rst (rst),
|
||||
.m_axis_tdata (i_data_tdata),
|
||||
.m_axis_tkeep (),
|
||||
.m_axis_tlast (i_data_tlast),
|
||||
.m_axis_tvalid (i_data_tvalid),
|
||||
.m_axis_tready (i_data_tready)
|
||||
);
|
||||
|
||||
axis_width_conv #(
|
||||
.WORD_W (OUT_WORD_W),
|
||||
.IN_WORDS (O_DATA_W/OUT_WORD_W),
|
||||
.OUT_WORDS (O_CHDR_W/OUT_WORD_W),
|
||||
.SYNC_CLKS (1),
|
||||
.PIPELINE ("NONE")
|
||||
) axis_width_conv_out (
|
||||
.s_axis_aclk (clk),
|
||||
.s_axis_rst (rst),
|
||||
.s_axis_tdata (o_data_tdata),
|
||||
.s_axis_tkeep ('1),
|
||||
.s_axis_tlast (o_data_tlast),
|
||||
.s_axis_tvalid (o_data_tvalid),
|
||||
.s_axis_tready (o_data_tready),
|
||||
.m_axis_aclk (clk),
|
||||
.m_axis_rst (rst),
|
||||
.m_axis_tdata (o_chdr_tdata_unmasked),
|
||||
.m_axis_tkeep (o_chdr_tkeep),
|
||||
.m_axis_tlast (o_chdr_tlast),
|
||||
.m_axis_tvalid (o_chdr_tvalid),
|
||||
.m_axis_tready (o_chdr_tready)
|
||||
);
|
||||
|
||||
|
||||
// Invalidate the bits we shouldn't be keeping by changing them to X. This
|
||||
// ensures we aren't checking bits that aren't there and thinking they're OK
|
||||
// because they happen to be 0.
|
||||
reg [O_CHDR_W-1:0] o_chdr_keep_mask;
|
||||
|
||||
always_comb begin
|
||||
for (int w = 0; w < OUT_KEEP_W; w++) begin
|
||||
if (o_chdr_tkeep[w] == 1'b1) begin
|
||||
o_chdr_keep_mask[w*OUT_WORD_W+:OUT_WORD_W] = {OUT_WORD_W{1'b1}};
|
||||
end else begin
|
||||
o_chdr_keep_mask[w*OUT_WORD_W+:OUT_WORD_W] = {OUT_WORD_W{1'bX}};
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// This mask operation leaves the bits we're keeping unmodified but changes
|
||||
// the ones we aren't keeping to X.
|
||||
assign o_chdr_tdata = o_chdr_tdata_unmasked & o_chdr_keep_mask;
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// BFM Connections
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
// Input
|
||||
assign i_chdr_tdata = m_chdr.tdata;
|
||||
assign i_chdr_tlast = m_chdr.tlast;
|
||||
assign i_chdr_tvalid = m_chdr.tvalid;
|
||||
assign m_chdr.tready = i_chdr_tready;
|
||||
|
||||
// Output
|
||||
assign s_chdr.tdata = o_chdr_tdata;
|
||||
assign s_chdr.tlast = o_chdr_tlast;
|
||||
assign s_chdr.tvalid = o_chdr_tvalid;
|
||||
assign o_chdr_tready = s_chdr.tready;
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Debug Monitors
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Display packet info as packets go in/out of the DUT to make packets easier
|
||||
// to find in the simulator.
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
if (DEBUG) begin
|
||||
bit i_chdr_sop = 1;
|
||||
bit mid_chdr_sop = 1;
|
||||
bit o_chdr_sop = 1;
|
||||
|
||||
always @(posedge clk) begin
|
||||
chdr_header_t header;
|
||||
|
||||
if (i_chdr_tvalid && i_chdr_tready) begin
|
||||
if (i_chdr_sop) begin
|
||||
header = i_chdr_tdata;
|
||||
$display("In packet, @%0t: 0x%16X %p", $realtime, header, header);
|
||||
i_chdr_sop <= 0;
|
||||
end
|
||||
i_chdr_sop <= i_chdr_tlast;
|
||||
end
|
||||
|
||||
if (o_chdr_tvalid && o_chdr_tready) begin
|
||||
if (o_chdr_sop) begin
|
||||
header = o_chdr_tdata;
|
||||
$display("Out packet, @%0t: 0x%16X %p", $realtime, header, header);
|
||||
o_chdr_sop <= 0;
|
||||
end
|
||||
o_chdr_sop <= o_chdr_tlast;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// TUSER Generation
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Create a simple counter on TUSER than increments for each packet.
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
reg [USER_W-1:0] i_user_count = 0;
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (rst) begin
|
||||
i_user_count <= 0;
|
||||
end else if (i_data_tvalid && i_data_tready) begin
|
||||
if (i_data_tlast) begin
|
||||
i_user_count <= i_user_count + 1;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// Ensure that i_data_tuser is only valid during the packet to guarantee the
|
||||
// DUT doesn't sample it outside that.
|
||||
assign i_data_tuser =
|
||||
(i_data_tvalid && i_data_tready) ? i_user_count : 'X;
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// TUSER Checking
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Verify that each output packet has the expected count on TUSER.
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
reg [USER_W-1:0] o_user_count = 0;
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (rst) begin
|
||||
o_user_count <= 0;
|
||||
end else if (o_data_tvalid && o_data_tready) begin
|
||||
`ASSERT_ERROR(
|
||||
o_data_tuser == o_user_count,
|
||||
"TUSER output doesn't match expected count."
|
||||
);
|
||||
if (o_data_tlast) begin
|
||||
o_user_count <= o_user_count+1;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Helper Logic
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
// Convert the input packet to the output packet, based on the configured
|
||||
// I_CHDR_W and O_CHDR_W. Returns the expected output packet. This function
|
||||
// does to the input ChdrPacket what the DUT is supposed to do to the CHDR
|
||||
// AXI-Stream packet.
|
||||
function automatic OChdrPacket_t convert_packet(IChdrPacket_t i_packet);
|
||||
OChdrPacket_t o_packet = new;
|
||||
int num_mdata;
|
||||
int max_num_mdata = 2**$bits(o_packet.header.num_mdata)-1;
|
||||
|
||||
// Check if we're doing any conversion or resizing
|
||||
if (I_CHDR_W == O_CHDR_W && I_CHDR_W == I_DATA_W && O_CHDR_W == O_DATA_W) begin
|
||||
// We don't modify the packet at all in this case. We should just pass it
|
||||
// through.
|
||||
o_packet = OChdrPacket_t'(i_packet.copy());
|
||||
return o_packet;
|
||||
end
|
||||
|
||||
//---------------------------------
|
||||
// Update the header
|
||||
|
||||
o_packet.header = i_packet.header;
|
||||
|
||||
// NumMData
|
||||
if (I_CHDR_W > O_CHDR_W) begin
|
||||
// Make sure there isn't too much metadata for a smaller CHDR_W packet
|
||||
num_mdata = i_packet.header.num_mdata * I_CHDR_W / O_CHDR_W;
|
||||
if (num_mdata > max_num_mdata) num_mdata = max_num_mdata;
|
||||
end else begin
|
||||
// Round up to the nearest whole O_CHDR_W word
|
||||
num_mdata = `DIV_CEIL(i_packet.header.num_mdata * I_CHDR_W, O_CHDR_W);
|
||||
end
|
||||
o_packet.header.num_mdata = num_mdata;
|
||||
|
||||
// Length
|
||||
o_packet.header.length =
|
||||
// Header
|
||||
(O_CHDR_W/8) +
|
||||
// Timestamp (goes in same word as header, unless O_CHDR_W is 64-bit)
|
||||
((o_packet.header.pkt_type == CHDR_DATA_WITH_TS && O_CHDR_W == 64) ? (O_CHDR_W/8) : 0) +
|
||||
// Metadata
|
||||
(O_CHDR_W/8)*o_packet.header.num_mdata +
|
||||
// Payload data. We expect the length of the output packet to be a
|
||||
// multiple of CHDR_W for management packets.
|
||||
((o_packet.header.pkt_type == CHDR_MANAGEMENT) ?
|
||||
`DIV_CEIL(i_packet.data_bytes(), I_CHDR_W/8) * (O_CHDR_W/8) : i_packet.data_bytes());
|
||||
|
||||
// Timestamp
|
||||
o_packet.timestamp = i_packet.timestamp;
|
||||
|
||||
//---------------------------------
|
||||
// Copy the data and metadata
|
||||
|
||||
if (I_CHDR_W > O_CHDR_W) begin
|
||||
// Drop any metadata beyond what CHDR allows
|
||||
o_packet.metadata = ChdrData#(I_CHDR_W, O_CHDR_W)::chdr_to_item(i_packet.metadata,
|
||||
num_mdata * O_CHDR_W/8);
|
||||
// Drop any O_CHDR_W words that aren't part of the payload
|
||||
o_packet.data = ChdrData#(I_CHDR_W, O_CHDR_W)::chdr_to_item(i_packet.data,
|
||||
`DIV_CEIL(i_packet.data_bytes(), O_CHDR_W/8) * O_CHDR_W/8);
|
||||
end else begin
|
||||
o_chdr_word_t last_mdata;
|
||||
o_packet.metadata = ChdrData#(O_CHDR_W, I_CHDR_W)::item_to_chdr(i_packet.metadata);
|
||||
o_packet.data = ChdrData#(O_CHDR_W, I_CHDR_W)::item_to_chdr(i_packet.data);
|
||||
|
||||
// When I_CHDR_W < O_CHDR_W, the number of metadata bytes might not be a
|
||||
// multiple of O_CHDR_W. The DUT should zero these extra bytes.
|
||||
if (o_packet.metadata.size() > 0) begin
|
||||
last_mdata = o_packet.metadata[$];
|
||||
for (int i = 0; i < o_packet.mdata_bytes()-i_packet.mdata_bytes(); i++) begin
|
||||
last_mdata[$bits(o_chdr_word_t)-i*8-1 -: 8] = 8'd0;
|
||||
end
|
||||
o_packet.metadata[$] = last_mdata;
|
||||
end
|
||||
end
|
||||
|
||||
//---------------------------------
|
||||
// Copy management packet data
|
||||
|
||||
if (o_packet.header.pkt_type == CHDR_MANAGEMENT) begin
|
||||
// Management packets don't get serialized, so we just copy each word,
|
||||
// ignoring the upper bits.
|
||||
o_packet.data = {};
|
||||
for (int i = 0; i < i_packet.data.size(); i++) begin
|
||||
if (i == 0) begin
|
||||
// Update the CHDR width in the management header word
|
||||
o_chdr_word_t word;
|
||||
word = i_packet.data[i];
|
||||
word[47:45] = translate_chdr_w(O_CHDR_W);
|
||||
o_packet.data.push_back(word);
|
||||
end else begin
|
||||
o_packet.data.push_back(i_packet.data[i]);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return o_packet;
|
||||
endfunction : convert_packet
|
||||
|
||||
|
||||
// Compare the output packet to what we expect the output packet to be.
|
||||
function automatic string compare_packets(
|
||||
input OChdrPacket_t packet,
|
||||
input OChdrPacket_t exp_packet
|
||||
);
|
||||
string msg = "";
|
||||
|
||||
// Check that the headers match (including num_mdata and length)
|
||||
if(packet.header != exp_packet.header) begin
|
||||
msg = { msg, "Headers do not match\n" };
|
||||
end
|
||||
|
||||
// Check that the timestamps match
|
||||
if (exp_packet.header.pkt_type == CHDR_DATA_WITH_TS) begin
|
||||
if(packet.timestamp != exp_packet.timestamp) begin
|
||||
msg = { msg, "Timestamps do not match\n" };
|
||||
end
|
||||
end
|
||||
|
||||
// Check that the metadata matches
|
||||
if(!exp_packet.chdr_word_queues_equal(
|
||||
packet.metadata, exp_packet.metadata, packet.mdata_bytes())
|
||||
) begin
|
||||
msg = { msg, "Metadata does not match\n" };
|
||||
end
|
||||
|
||||
// Check that the payloads match
|
||||
if(!exp_packet.chdr_word_queues_equal(
|
||||
packet.data, exp_packet.data, exp_packet.data_bytes())
|
||||
) begin
|
||||
msg = { msg, "Payloads do not match\n" };
|
||||
end
|
||||
|
||||
return msg;
|
||||
endfunction : compare_packets
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Tests
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
// Perform a randomized test with the given stall probabilities in the input
|
||||
// and output ports.
|
||||
task automatic test_random(int in_stall_prob, int out_stall_prob);
|
||||
longint word_count = 0;
|
||||
bit enable_input = 0;
|
||||
string msg;
|
||||
mailbox #(IChdrPacket_t) packets = new;
|
||||
|
||||
msg = $sformatf("Test Random Packets (%0d%%, %0d%%)",
|
||||
in_stall_prob, out_stall_prob);
|
||||
test.start_test(msg, 10ms);
|
||||
|
||||
m_bfm.set_master_stall_prob(in_stall_prob);
|
||||
s_bfm.set_slave_stall_prob(out_stall_prob);
|
||||
|
||||
fork
|
||||
//-------------------------------
|
||||
// Input Process
|
||||
//-------------------------------
|
||||
|
||||
begin : input_process
|
||||
IChdrPacket_t chdr_packet = new;
|
||||
i_chdr_word_t data[$];
|
||||
i_chdr_word_t mdata[$];
|
||||
chdr_header_t header;
|
||||
chdr_timestamp_t timestamp;
|
||||
int data_byte_length;
|
||||
int count;
|
||||
|
||||
repeat (NUM_PACKETS) begin
|
||||
//-------------------------------
|
||||
// Generate a random packet
|
||||
//-------------------------------
|
||||
|
||||
// Start with a random header. Make sure the packet type is legal.
|
||||
do begin
|
||||
header = Rand#($bits(chdr_header_t))::rand_logic();
|
||||
end while (header.pkt_type == CHDR_RESERVED_0 || header.pkt_type == CHDR_RESERVED_1);
|
||||
|
||||
// Generate timestamp
|
||||
if (header.pkt_type == CHDR_DATA_WITH_TS) begin
|
||||
if (USE_RANDOM) timestamp = Rand#($bits(chdr_header_t))::rand_logic();
|
||||
else timestamp = 64'h0123456789ABCDEF;
|
||||
end else begin
|
||||
timestamp = 0;
|
||||
end
|
||||
|
||||
// Generate random metadata (50% chance of no metadata)
|
||||
mdata = {};
|
||||
if ($urandom_range(0, 1)) begin
|
||||
count = 0;
|
||||
repeat ($urandom_range(1, MAX_MDATA_WORDS)) begin
|
||||
if (USE_RANDOM) mdata.push_back(Rand#(I_CHDR_W)::rand_logic());
|
||||
else mdata.push_back(64'hA1000000 + count++);
|
||||
end
|
||||
end
|
||||
|
||||
// Generate random data (always at least one word)
|
||||
data = {};
|
||||
count = 0;
|
||||
repeat ($urandom_range(1, MAX_PYLD_WORDS)) begin
|
||||
if (USE_RANDOM) data.push_back(Rand#(I_CHDR_W)::rand_logic());
|
||||
else data.push_back(64'hB2000000 + count++);
|
||||
end
|
||||
|
||||
// Calculate the size of data minus one word, in bytes
|
||||
data_byte_length = (data.size()-1) * (I_CHDR_W/8);
|
||||
// Add from 1 byte to a full word of bytes to test partially filling
|
||||
// the last word.
|
||||
if (header.pkt_type == CHDR_MANAGEMENT) begin
|
||||
// For management packets, the spec is not explicit about whether
|
||||
// the length must include the padding of the last word. We assume
|
||||
// the worst case, that either case is possible and we expect the
|
||||
// DUT to handle both correctly.
|
||||
data_byte_length += $urandom_range(1, I_CHDR_W/64) * 8;
|
||||
end else begin
|
||||
data_byte_length += $urandom_range(1, I_CHDR_W/8);
|
||||
end
|
||||
|
||||
// Build packet
|
||||
chdr_packet.write_raw(
|
||||
header,
|
||||
data,
|
||||
mdata,
|
||||
timestamp,
|
||||
data_byte_length
|
||||
);
|
||||
|
||||
// Queue the packet
|
||||
m_bfm.put_chdr(chdr_packet);
|
||||
|
||||
// Queue up what we sent for the output process to check
|
||||
packets.put(chdr_packet.copy());
|
||||
end
|
||||
$display("Done inputting packets.");
|
||||
end : input_process
|
||||
|
||||
//-------------------------------
|
||||
// Output Process
|
||||
//-------------------------------
|
||||
|
||||
begin : output_process
|
||||
IChdrPacket_t i_packet;
|
||||
OChdrPacket_t o_packet;
|
||||
OChdrPacket_t exp_packet;
|
||||
int packet_count;
|
||||
string msg;
|
||||
|
||||
repeat (NUM_PACKETS) begin
|
||||
s_bfm.get_chdr(o_packet);
|
||||
packets.get(i_packet);
|
||||
exp_packet = convert_packet(i_packet);
|
||||
msg = compare_packets(o_packet, exp_packet);
|
||||
if(msg != "") begin
|
||||
$display("Sent packet:");
|
||||
i_packet.print(0);
|
||||
$display("Received packet:");
|
||||
o_packet.print(0);
|
||||
$display("Expected packet:");
|
||||
exp_packet.print(0);
|
||||
`ASSERT_ERROR(0, $sformatf(
|
||||
"Output packet is incorrect for the following reasons:\n%s", msg)
|
||||
);
|
||||
end
|
||||
end
|
||||
$display("Done processing packets.");
|
||||
end : output_process
|
||||
join
|
||||
|
||||
test.end_test();
|
||||
|
||||
endtask : test_random
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Main Test Process
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
initial begin : tb_main
|
||||
string msg;
|
||||
string tb_name;
|
||||
|
||||
tb_name = $sformatf( {
|
||||
"chdr_resize_tb\n",
|
||||
"I_CHDR_W = %03d\n",
|
||||
"O_CHDR_W = %03d\n",
|
||||
"I_DATA_W = %03d\n",
|
||||
"O_DATA_W = %03d\n",
|
||||
"PIPLINE = %s" },
|
||||
I_CHDR_W, O_CHDR_W, I_DATA_W, O_DATA_W, PIPELINE
|
||||
);
|
||||
test.start_tb(tb_name, 100ms);
|
||||
|
||||
// Don't start the clocks until after start_tb() returns. This ensures that
|
||||
// the clocks aren't toggling while other instances of this testbench are
|
||||
// running, which speeds up simulation time.
|
||||
clk_gen.start();
|
||||
|
||||
// Start the BFM
|
||||
m_bfm.run();
|
||||
s_bfm.run();
|
||||
|
||||
//--------------------------------
|
||||
// Reset
|
||||
//--------------------------------
|
||||
|
||||
test.start_test("Reset", 10us);
|
||||
clk_gen.reset();
|
||||
if (rst) @rst;
|
||||
test.end_test();
|
||||
|
||||
//--------------------------------
|
||||
// Test Sequences
|
||||
//--------------------------------
|
||||
|
||||
test_random(50, 50); // Test 50% push-back
|
||||
test_random( 0, 0); // Test no push-back
|
||||
test_random(50, 0); // Test for underflow
|
||||
test_random( 0, 50); // Test for overflow
|
||||
|
||||
//--------------------------------
|
||||
// Finish Up
|
||||
//--------------------------------
|
||||
|
||||
// End the TB, but don't $finish, since we don't want to kill other
|
||||
// instances of this testbench that may be running.
|
||||
test.end_tb(0);
|
||||
|
||||
// Kill the clocks to end this instance of the testbench
|
||||
clk_gen.kill();
|
||||
|
||||
end : tb_main
|
||||
|
||||
endmodule : chdr_resize_tb
|
||||
|
||||
|
||||
`default_nettype wire
|
||||
@@ -10,6 +10,9 @@
|
||||
RFNOC_UTIL_SRCS = $(abspath $(addprefix $(BASE_DIR)/../lib/rfnoc/utils/, \
|
||||
chdr_trim_payload.v \
|
||||
chdr_pad_packet.v \
|
||||
chdr_resize.v \
|
||||
chdr_convert_up.v \
|
||||
chdr_convert_down.v \
|
||||
context_handler_sync.v \
|
||||
context_builder.v \
|
||||
context_parser.v \
|
||||
|
||||
@@ -0,0 +1,451 @@
|
||||
//
|
||||
// Copyright 2021 Ettus Research, a National Instruments Brand
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
//
|
||||
// Module: chdr_convert_down
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// Takes a CHDR packet data stream that was generated using a CHDR width
|
||||
// (I_CHDR_W) that is wider than the current bus width (DATA_W) and reformats
|
||||
// the packet stream to use the CHDR_W equal to that of the current bus width
|
||||
// (DATA_W). It does not resize the bus, but rather only changes the CHDR_W
|
||||
// of the encoded packets.
|
||||
//
|
||||
// Packets with different CHDR width have a different maximum number of
|
||||
// metadata bytes. This module repacks the the metadata into the new word
|
||||
// size, little-endian ordered. If there is too much metadata for the smaller
|
||||
// DATA_W packet, then the excess metadata will be discarded.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// I_CHDR_W : CHDR_W for the input data stream on i_chdr. Must be larger than
|
||||
// DATA_W.
|
||||
// DATA_W : Width of the data bus, and the new CHDR_W for the output data
|
||||
// stream on o_chdr.
|
||||
// PIPELINE : Indicates whether to add pipeline stages to the input and/or
|
||||
// output. This can be: "NONE", "IN", "OUT", or "INOUT".
|
||||
|
||||
`default_nettype none
|
||||
|
||||
|
||||
module chdr_convert_down #(
|
||||
parameter I_CHDR_W = 512,
|
||||
parameter DATA_W = 64,
|
||||
parameter PIPELINE = "NONE"
|
||||
) (
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
// Input
|
||||
input wire [DATA_W-1:0] i_chdr_tdata,
|
||||
input wire i_chdr_tlast,
|
||||
input wire i_chdr_tvalid,
|
||||
output wire i_chdr_tready,
|
||||
|
||||
// Output
|
||||
output wire [DATA_W-1:0] o_chdr_tdata,
|
||||
output wire o_chdr_tlast,
|
||||
output wire o_chdr_tvalid,
|
||||
input wire o_chdr_tready
|
||||
);
|
||||
|
||||
`include "../core/rfnoc_chdr_utils.vh"
|
||||
`include "../core/rfnoc_chdr_internal_utils.vh"
|
||||
|
||||
// Calculate ceiling(N/D)
|
||||
`define DIV_CEIL(N,D) (((N)+(D)-1)/(D))
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Check Parameters
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
generate
|
||||
if (!(
|
||||
// Must be reducing the CHDR width
|
||||
(I_CHDR_W > DATA_W) &&
|
||||
// CHDR widths must be valid (at least 64 and powers of 2)
|
||||
(I_CHDR_W >= 64) &&
|
||||
(DATA_W >= 64) &&
|
||||
(2**$clog2(I_CHDR_W) == I_CHDR_W) &&
|
||||
(2**$clog2(DATA_W) == DATA_W) &&
|
||||
// I_CHDR_W must be a multiple of DATA_W
|
||||
(I_CHDR_W % DATA_W == 0)
|
||||
)) begin : gen_error
|
||||
ERROR__Invalid_CHDR_or_data_width_parameters();
|
||||
end
|
||||
endgenerate
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Input Register
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
wire [DATA_W-1:0] i_pipe_tdata;
|
||||
wire i_pipe_tlast;
|
||||
wire i_pipe_tvalid;
|
||||
reg i_pipe_tready;
|
||||
|
||||
if (PIPELINE == "IN" || PIPELINE == "INOUT") begin : gen_in_pipeline
|
||||
// Add a pipeline stage
|
||||
axi_fifo_flop2 #(
|
||||
.WIDTH (1 + DATA_W)
|
||||
) axi_fifo_flop2_i (
|
||||
.clk (clk),
|
||||
.reset (rst),
|
||||
.clear (1'b0),
|
||||
.i_tdata ({i_chdr_tlast, i_chdr_tdata}),
|
||||
.i_tvalid (i_chdr_tvalid),
|
||||
.i_tready (i_chdr_tready),
|
||||
.o_tdata ({i_pipe_tlast, i_pipe_tdata}),
|
||||
.o_tvalid (i_pipe_tvalid),
|
||||
.o_tready (i_pipe_tready),
|
||||
.space (),
|
||||
.occupied ()
|
||||
);
|
||||
end else begin : gen_no_in_pipeline
|
||||
assign i_pipe_tdata = i_chdr_tdata;
|
||||
assign i_pipe_tlast = i_chdr_tlast;
|
||||
assign i_pipe_tvalid = i_chdr_tvalid;
|
||||
assign i_chdr_tready = i_pipe_tready;
|
||||
end
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Downsize State Machine
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// This state machine does the translation from the larger CHDR_W to the
|
||||
// smaller CHDR_W by updating the header and dropping empty words.
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
// States
|
||||
localparam [2:0] ST_HDR = 3'd0; // CHDR header
|
||||
localparam [2:0] ST_TS = 3'd1; // CHDR timestamp
|
||||
localparam [2:0] ST_HDR_DROP = 3'd2; // CHDR header, drop unused words
|
||||
localparam [2:0] ST_MDATA = 3'd3; // CHDR metadata words
|
||||
localparam [2:0] ST_MDATA_DROP = 3'd4; // CHDR metadata, drop unused words
|
||||
localparam [2:0] ST_PYLD = 3'd5; // CHDR payload words
|
||||
localparam [2:0] ST_PYLD_DROP = 3'd6; // CHDR payload, drop unused words
|
||||
localparam [2:0] ST_MGMT_PYLD = 3'd7; // CHDR management payload words
|
||||
|
||||
reg [2:0] state = ST_HDR;
|
||||
|
||||
// Determine the number of bits needed to represent the new number of
|
||||
// metadata words, which might be bigger than the allowed value of 31.
|
||||
localparam NUM_MDATA_W = $clog2(31*I_CHDR_W/DATA_W + 1);
|
||||
|
||||
// Number of output words per input word
|
||||
localparam NUM_WORDS = I_CHDR_W/DATA_W;
|
||||
|
||||
// Determine the number of bits needed to represent a counter to track which
|
||||
// CHDR words are valid and which are unused and need to be dropped.
|
||||
localparam COUNT_W = $clog2(NUM_WORDS);
|
||||
|
||||
// Determine the maximum number DATA_W-sized payload words. The maximum
|
||||
// packet size is 2**16-1 bytes, then subtract one word for the smallest
|
||||
// possible header and convert that to a number of whole CHDR words.
|
||||
localparam NUM_PYLD_WORDS = `DIV_CEIL((2**16-1) - (DATA_W/8), DATA_W/8);
|
||||
|
||||
// Determine the number of bits needed to represent a counter to track which
|
||||
// O_DATA_W payload word we are processing.
|
||||
localparam PYLD_COUNT_W = $clog2(NUM_PYLD_WORDS + 1);
|
||||
|
||||
// Header info we need to save
|
||||
reg [ NUM_MDATA_W-1:0] i_num_mdata_reg; // Input packet NumMData in terms of DATA_W words
|
||||
reg [ 4:0] o_num_mdata_reg; // Output packet NumMData to keep
|
||||
reg [ 2:0] pkt_type_reg; // Packet type
|
||||
reg [PYLD_COUNT_W-1:0] pyld_len_reg; // Packet payload length in DATA_W words
|
||||
reg [PYLD_COUNT_W-1:0] mgmt_pyld_len_reg; // Management payload length in DATA_W words
|
||||
|
||||
// Counters (number of DATA_W sized words processed on the input)
|
||||
reg [ NUM_MDATA_W-1:0] mdata_count;
|
||||
reg [PYLD_COUNT_W-1:0] pyld_count;
|
||||
reg [ COUNT_W-1:0] word_count; // Zero based (starts at 0)
|
||||
|
||||
// Shortcuts for CHDR header info
|
||||
wire [ 2:0] pkt_type = chdr_get_pkt_type(i_pipe_tdata[63:0]);
|
||||
wire [15:0] pyld_len_bytes = chdr_calc_payload_length(I_CHDR_W, i_pipe_tdata[63:0]);
|
||||
|
||||
// Calculate the payload length in DATA_W words
|
||||
wire [PYLD_COUNT_W-1:0] pyld_len = `DIV_CEIL(pyld_len_bytes, DATA_W/8);
|
||||
|
||||
// Calculate the payload length of a management packet in words (management
|
||||
// packets have the same number of payload words, regardless of CHDR width).
|
||||
wire [PYLD_COUNT_W-1:0] mgmt_pyld_len =
|
||||
`DIV_CEIL(chdr_calc_payload_length(I_CHDR_W, i_pipe_tdata), I_CHDR_W/8);
|
||||
|
||||
// Calculate NumMData from input packet in terms of DATA_W words
|
||||
wire [NUM_MDATA_W-1:0] i_num_mdata =
|
||||
chdr_get_num_mdata(i_pipe_tdata[63:0]) * (I_CHDR_W/DATA_W);
|
||||
// Calculate NumMData for output packet (limit to max of 31)
|
||||
wire [4:0] o_num_mdata = (i_num_mdata <= 31) ? i_num_mdata : 31;
|
||||
|
||||
// Generate packet headers with updated NumMData and Length fields
|
||||
reg [DATA_W-1:0] new_header;
|
||||
always @(*) begin
|
||||
new_header = i_pipe_tdata;
|
||||
// Update NumMData
|
||||
new_header[63:0] = chdr_set_num_mdata(new_header, o_num_mdata);
|
||||
// Update packet length
|
||||
new_header[63:0] = chdr_update_length(DATA_W, new_header,
|
||||
(pkt_type == CHDR_PKT_TYPE_MGMT) ? mgmt_pyld_len * (DATA_W/8) : pyld_len_bytes);
|
||||
end
|
||||
|
||||
reg [DATA_W-1:0] new_mgmt_header;
|
||||
always @(*) begin
|
||||
// Update the CHDRWidth field in the management header.
|
||||
new_mgmt_header = i_pipe_tdata;
|
||||
new_mgmt_header[63:0] =
|
||||
chdr_mgmt_set_chdr_w(i_pipe_tdata[63:0], chdr_w_to_enum(DATA_W));
|
||||
end
|
||||
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= ST_HDR;
|
||||
mdata_count <= 'bX;
|
||||
pyld_count <= 'bX;
|
||||
word_count <= 'bX;
|
||||
pkt_type_reg <= 'bX;
|
||||
pyld_len_reg <= 'bX;
|
||||
mgmt_pyld_len_reg <= 'bX;
|
||||
i_num_mdata_reg <= 'bX;
|
||||
o_num_mdata_reg <= 'bX;
|
||||
end else if (i_pipe_tvalid & i_pipe_tready) begin
|
||||
// Default assignment
|
||||
word_count <= word_count + 1;
|
||||
|
||||
case (state)
|
||||
|
||||
// ST_HDR: CHDR Header
|
||||
ST_HDR: begin
|
||||
mdata_count <= 1; // The first metadata word will be word 1
|
||||
pyld_count <= 1; // The first payload word will be word 1
|
||||
word_count <= 1; // Word 0 is the current word (header)
|
||||
pkt_type_reg <= pkt_type;
|
||||
pyld_len_reg <= pyld_len;
|
||||
mgmt_pyld_len_reg <= mgmt_pyld_len;
|
||||
// Save number of DATA_W words of mdata we expect
|
||||
i_num_mdata_reg <= i_num_mdata;
|
||||
// Save the number of DATA_W words of mdata we can keep
|
||||
o_num_mdata_reg <= o_num_mdata;
|
||||
if (DATA_W == 64) begin
|
||||
if (pkt_type == CHDR_PKT_TYPE_DATA_TS) begin
|
||||
// Next word must be the timestamp
|
||||
state <= ST_TS;
|
||||
end else begin
|
||||
// Next word(s) must be empty, so drop it
|
||||
state <= ST_HDR_DROP;
|
||||
end
|
||||
end else begin
|
||||
// DATA_W >= 128. We should have received the header word and
|
||||
// timestamp (if present) this clock cycle. Since I_CHDR_W >
|
||||
// DATA_W, there must be extra words with the header that we need
|
||||
// to drop.
|
||||
state <= ST_HDR_DROP;
|
||||
end
|
||||
end
|
||||
|
||||
// ST_TS: Timestamp (DATA_W == 64 only)
|
||||
ST_TS: begin
|
||||
if (I_CHDR_W > 128) begin
|
||||
state <= ST_HDR_DROP;
|
||||
end else if (o_num_mdata_reg != 0) begin
|
||||
state <= ST_MDATA;
|
||||
end else begin
|
||||
state <= ST_PYLD;
|
||||
end
|
||||
end
|
||||
|
||||
// ST_HDR_DROP: CHDR header, drop unused words
|
||||
ST_HDR_DROP: begin
|
||||
if (word_count == NUM_WORDS-1) begin
|
||||
if (o_num_mdata_reg != 0) begin
|
||||
state <= ST_MDATA;
|
||||
end else if(pkt_type_reg == CHDR_PKT_TYPE_MGMT) begin
|
||||
state <= ST_MGMT_PYLD;
|
||||
end else begin
|
||||
state <= ST_PYLD;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// ST_MDATA: Metadata words
|
||||
ST_MDATA: begin
|
||||
mdata_count <= mdata_count + 1;
|
||||
if (mdata_count == o_num_mdata_reg) begin
|
||||
if (mdata_count < i_num_mdata_reg) begin
|
||||
// There are more MDATA words to deal with than we can fit, so we
|
||||
// need to drop the rest.
|
||||
state <= ST_MDATA_DROP;
|
||||
end else if (pkt_type_reg == CHDR_PKT_TYPE_MGMT) begin
|
||||
state <= ST_MGMT_PYLD;
|
||||
end else begin
|
||||
state <= ST_PYLD;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// ST_MDATA_DROP: Drop excess metadata words
|
||||
ST_MDATA_DROP: begin
|
||||
mdata_count <= mdata_count + 1;
|
||||
if (mdata_count == i_num_mdata_reg) begin
|
||||
if (pkt_type_reg == CHDR_PKT_TYPE_MGMT) begin
|
||||
state <= ST_MGMT_PYLD;
|
||||
end else begin
|
||||
state <= ST_PYLD;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// ST_PYLD: Payload words
|
||||
ST_PYLD: begin
|
||||
pyld_count <= pyld_count + 1;
|
||||
if (i_pipe_tlast) begin
|
||||
state <= ST_HDR;
|
||||
end else if (pyld_count == pyld_len_reg) begin
|
||||
state <= ST_PYLD_DROP;
|
||||
end
|
||||
end
|
||||
|
||||
// ST_PYLD_DROP: Payload, drop unused words
|
||||
ST_PYLD_DROP: begin
|
||||
// The input packet may have had empty words at the end if the
|
||||
// payload didn't fill the last CHDR word. We remove those here.
|
||||
if (i_pipe_tlast) begin
|
||||
state <= ST_HDR;
|
||||
end
|
||||
end
|
||||
|
||||
// ST_MGMT_PYLD: Management words
|
||||
ST_MGMT_PYLD: begin
|
||||
// Management packets are different from other packet types in that
|
||||
// the payload is not serialized. In the new DATA_W, we'll have empty
|
||||
// words we need to discard. When word_count is zero, that's when we
|
||||
// have a valid word. For all other counts, we want to discard words.
|
||||
if (word_count == 0) begin
|
||||
pyld_count <= pyld_count + 1;
|
||||
end
|
||||
if (i_pipe_tlast) begin
|
||||
state <= ST_HDR;
|
||||
end
|
||||
end
|
||||
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
//-----------------------------
|
||||
// State machine output logic
|
||||
//-----------------------------
|
||||
|
||||
reg [DATA_W-1:0] o_pipe_tdata;
|
||||
reg o_pipe_tlast;
|
||||
reg o_pipe_tvalid;
|
||||
wire o_pipe_tready;
|
||||
|
||||
always @(*) begin
|
||||
case (state)
|
||||
ST_HDR : begin
|
||||
o_pipe_tdata = new_header;
|
||||
o_pipe_tlast = i_pipe_tlast;
|
||||
o_pipe_tvalid = i_pipe_tvalid;
|
||||
i_pipe_tready = o_pipe_tready;
|
||||
end
|
||||
ST_TS : begin
|
||||
o_pipe_tdata = i_pipe_tdata;
|
||||
o_pipe_tlast = i_pipe_tlast;
|
||||
o_pipe_tvalid = i_pipe_tvalid;
|
||||
i_pipe_tready = o_pipe_tready;
|
||||
end
|
||||
ST_HDR_DROP : begin
|
||||
o_pipe_tdata = { DATA_W {1'bX} };
|
||||
o_pipe_tlast = 1'bX;
|
||||
o_pipe_tvalid = 1'b0;
|
||||
i_pipe_tready = 1'b1;
|
||||
end
|
||||
ST_MDATA : begin
|
||||
o_pipe_tdata = i_pipe_tdata;
|
||||
o_pipe_tlast = i_pipe_tlast;
|
||||
o_pipe_tvalid = i_pipe_tvalid;
|
||||
i_pipe_tready = o_pipe_tready;
|
||||
end
|
||||
ST_MDATA_DROP : begin
|
||||
o_pipe_tdata = { DATA_W {1'bX} };
|
||||
o_pipe_tlast = 1'bX;
|
||||
o_pipe_tvalid = 1'b0;
|
||||
i_pipe_tready = 1'b1;
|
||||
end
|
||||
ST_PYLD : begin
|
||||
o_pipe_tdata = i_pipe_tdata;
|
||||
o_pipe_tlast = (pyld_count == pyld_len_reg);
|
||||
o_pipe_tvalid = i_pipe_tvalid;
|
||||
i_pipe_tready = o_pipe_tready;
|
||||
end
|
||||
ST_PYLD_DROP : begin
|
||||
o_pipe_tdata = { DATA_W {1'bX} };
|
||||
o_pipe_tlast = 1'bX;
|
||||
o_pipe_tvalid = 1'b0;
|
||||
i_pipe_tready = 1'b1;
|
||||
end
|
||||
ST_MGMT_PYLD : begin
|
||||
if (word_count == 0) begin
|
||||
o_pipe_tdata = (pyld_count == 1) ? new_mgmt_header : i_pipe_tdata;
|
||||
o_pipe_tlast = (pyld_count == mgmt_pyld_len_reg);
|
||||
o_pipe_tvalid = i_pipe_tvalid;
|
||||
i_pipe_tready = o_pipe_tready;
|
||||
end else begin
|
||||
// Drop unused management payload words
|
||||
o_pipe_tdata = { DATA_W {1'bX} };
|
||||
o_pipe_tlast = 1'bX;
|
||||
o_pipe_tvalid = 1'b0;
|
||||
i_pipe_tready = 1'b1;
|
||||
end
|
||||
end
|
||||
default : begin
|
||||
o_pipe_tdata = { DATA_W {1'bX} };
|
||||
o_pipe_tlast = 1'bX;
|
||||
o_pipe_tvalid = 1'bX;
|
||||
i_pipe_tready = 1'bX;
|
||||
end
|
||||
endcase
|
||||
end
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Output Register
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
if (PIPELINE == "OUT" || PIPELINE == "INOUT") begin : gen_out_pipeline
|
||||
// Add a pipeline stage
|
||||
axi_fifo_flop2 #(
|
||||
.WIDTH (1 + DATA_W)
|
||||
) axi_fifo_flop2_i (
|
||||
.clk (clk),
|
||||
.reset (rst),
|
||||
.clear (1'b0),
|
||||
.i_tdata ({ o_pipe_tlast, o_pipe_tdata }),
|
||||
.i_tvalid (o_pipe_tvalid),
|
||||
.i_tready (o_pipe_tready),
|
||||
.o_tdata ({ o_chdr_tlast, o_chdr_tdata }),
|
||||
.o_tvalid (o_chdr_tvalid),
|
||||
.o_tready (o_chdr_tready),
|
||||
.space (),
|
||||
.occupied ()
|
||||
);
|
||||
end else begin : gen_no_out_pipeline
|
||||
assign o_chdr_tdata = o_pipe_tdata;
|
||||
assign o_chdr_tlast = o_pipe_tlast;
|
||||
assign o_chdr_tvalid = o_pipe_tvalid;
|
||||
assign o_pipe_tready = o_chdr_tready;
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
|
||||
`default_nettype wire
|
||||
@@ -0,0 +1,448 @@
|
||||
//
|
||||
// Copyright 2021 Ettus Research, a National Instruments Brand
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
//
|
||||
// Module: chdr_convert_up
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// Takes a CHDR packet data stream that was generated using a CHDR width
|
||||
// equal to the current bust width (DATA_W) and reformats the packet stream
|
||||
// to use a wider width (O_CHDR_W). It does not resize the bus, but rather
|
||||
// only changes the CHDR_W of the encoded packets.
|
||||
//
|
||||
// The metadata might not be a nice multiple of O_CHDR_W sized words. This
|
||||
// module repacks the metadata into the new word size, little-endian ordered,
|
||||
// and pads the last metadata word with zeros if necessary.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// DATA_W : The width of the data bus and the input CHDR width for the
|
||||
// input data stream on i_chdr.
|
||||
// O_CHDR_W : CHDR_W for the output data stream on o_chdr. Must be larger
|
||||
// than DATA_W.
|
||||
// PIPELINE : Indicates whether to add pipeline stages to the input and/or
|
||||
// output. This can be: "NONE", "IN", "OUT", or "INOUT".
|
||||
//
|
||||
|
||||
`default_nettype none
|
||||
|
||||
|
||||
module chdr_convert_up #(
|
||||
parameter DATA_W = 64,
|
||||
parameter O_CHDR_W = 512,
|
||||
parameter PIPELINE = "NONE"
|
||||
) (
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
// Input
|
||||
input wire [DATA_W-1:0] i_chdr_tdata,
|
||||
input wire i_chdr_tlast,
|
||||
input wire i_chdr_tvalid,
|
||||
output wire i_chdr_tready,
|
||||
|
||||
// Output
|
||||
output wire [DATA_W-1:0] o_chdr_tdata,
|
||||
output wire o_chdr_tlast,
|
||||
output wire o_chdr_tvalid,
|
||||
input wire o_chdr_tready
|
||||
);
|
||||
|
||||
`include "../core/rfnoc_chdr_utils.vh"
|
||||
`include "../core/rfnoc_chdr_internal_utils.vh"
|
||||
|
||||
// Calculate ceiling(N/D)
|
||||
`define DIV_CEIL(N,D) (((N)+(D)-1)/(D))
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Check Parameters
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
generate
|
||||
if (!(
|
||||
// Must be up-sizing
|
||||
(DATA_W < O_CHDR_W) &&
|
||||
// CHDR widths must be valid (at least 64 and powers of 2)
|
||||
(DATA_W >= 64) &&
|
||||
(O_CHDR_W >= 64) &&
|
||||
(2**$clog2(DATA_W) == DATA_W) &&
|
||||
(2**$clog2(O_CHDR_W) == O_CHDR_W) &&
|
||||
// O_CHDR_W must be a multiple of DATA_W
|
||||
(O_CHDR_W % DATA_W == 0)
|
||||
)) begin : gen_error
|
||||
ERROR__Invalid_CHDR_W_parameters();
|
||||
end
|
||||
endgenerate
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Input Register
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
wire [DATA_W-1:0] i_pipe_tdata;
|
||||
wire i_pipe_tlast;
|
||||
wire i_pipe_tvalid;
|
||||
reg i_pipe_tready;
|
||||
|
||||
if (PIPELINE == "IN" || PIPELINE == "INOUT") begin : gen_in_pipeline
|
||||
// Add a pipeline stage
|
||||
axi_fifo_flop2 #(
|
||||
.WIDTH (1 + DATA_W)
|
||||
) axi_fifo_flop2_i (
|
||||
.clk (clk),
|
||||
.reset (rst),
|
||||
.clear (1'b0),
|
||||
.i_tdata ({i_chdr_tlast, i_chdr_tdata}),
|
||||
.i_tvalid (i_chdr_tvalid),
|
||||
.i_tready (i_chdr_tready),
|
||||
.o_tdata ({i_pipe_tlast, i_pipe_tdata}),
|
||||
.o_tvalid (i_pipe_tvalid),
|
||||
.o_tready (i_pipe_tready),
|
||||
.space (),
|
||||
.occupied ()
|
||||
);
|
||||
end else begin : gen_no_in_pipeline
|
||||
assign i_pipe_tdata = i_chdr_tdata;
|
||||
assign i_pipe_tlast = i_chdr_tlast;
|
||||
assign i_pipe_tvalid = i_chdr_tvalid;
|
||||
assign i_chdr_tready = i_pipe_tready;
|
||||
end
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Up-size State Machine
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// This state machine does the translation from the smaller CHDR_W to the
|
||||
// larger CHDR_W by updating the header and padding words as needed.
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
// States
|
||||
localparam [3:0] ST_HDR = 4'd0; // CHDR header
|
||||
localparam [3:0] ST_TS = 4'd1; // CHDR timestamp
|
||||
localparam [3:0] ST_HDR_PAD = 4'd2; // CHDR header padding
|
||||
localparam [3:0] ST_MDATA = 4'd3; // CHDR metadata words
|
||||
localparam [3:0] ST_MDATA_PAD = 4'd4; // CHDR metadata padding
|
||||
localparam [3:0] ST_PYLD = 4'd5; // CHDR payload words
|
||||
localparam [3:0] ST_MGMT_HDR = 4'd6; // CHDR management header word
|
||||
localparam [3:0] ST_MGMT_PYLD = 4'd7; // CHDR management payload words
|
||||
localparam [3:0] ST_MGMT_PAD = 4'd8; // CHDR management word padding
|
||||
localparam [3:0] ST_LAST_PAD = 4'd9; // Pad the last CHDR word
|
||||
|
||||
reg [3:0] state = ST_HDR;
|
||||
|
||||
// Number of input words per output word
|
||||
localparam NUM_WORDS = O_CHDR_W/DATA_W;
|
||||
|
||||
// Determine the number of bits needed to represent a counter to track
|
||||
// which CHDR words are valid and which are padding.
|
||||
localparam COUNT_W = $clog2(NUM_WORDS);
|
||||
|
||||
// Determine the maximum number DATA_W-sized payload words. The maximum
|
||||
// packet size is 2**16-1 bytes, then subtract one word for the smallest
|
||||
// possible header and convert that to a number of whole CHDR words.
|
||||
localparam NUM_PYLD_WORDS = `DIV_CEIL((2**16-1) - (DATA_W/8), DATA_W/8);
|
||||
|
||||
// Determine the number of bits needed to represent a counter to track which
|
||||
// I_DATA_W payload word we are processing.
|
||||
localparam PYLD_COUNT_W = $clog2(NUM_PYLD_WORDS + 1);
|
||||
|
||||
// Header info we need to save
|
||||
reg [4:0] num_mdata_reg;
|
||||
reg [2:0] pkt_type_reg;
|
||||
|
||||
// Counters (number of DATA_W sized words processed on the input)
|
||||
reg [ 4:0] mdata_count;
|
||||
reg [COUNT_W-1:0] word_count; // Zero based (starts at 0)
|
||||
|
||||
// Shortcuts for CHDR header info
|
||||
wire [2:0] pkt_type = chdr_get_pkt_type(i_pipe_tdata[63:0]);
|
||||
wire [4:0] num_mdata = chdr_get_num_mdata(i_pipe_tdata[63:0]);
|
||||
|
||||
// Calculate payload length in bytes
|
||||
wire [15:0] pyld_len_bytes = chdr_calc_payload_length(DATA_W, i_pipe_tdata[63:0]);
|
||||
|
||||
// Calculate the payload length of a management packet in words (management
|
||||
// packets have the same number of payload words, regardless of CHDR width).
|
||||
wire [PYLD_COUNT_W-1:0] mgmt_pyld_len = `DIV_CEIL(pyld_len_bytes, DATA_W/8);
|
||||
|
||||
// Determine the number of metadata words for the output packet
|
||||
wire [4:0] o_num_mdata = `DIV_CEIL(num_mdata, O_CHDR_W/DATA_W);
|
||||
|
||||
// Generate packet headers with updated NumMData and Length fields
|
||||
reg [DATA_W-1:0] new_header;
|
||||
always @(*) begin
|
||||
// Pass through upper bits unchanged (e.g., timestamp)
|
||||
new_header = i_pipe_tdata;
|
||||
// Update NumMData
|
||||
new_header[63:0] = chdr_set_num_mdata(new_header, o_num_mdata);
|
||||
// Update packet length
|
||||
new_header[63:0] = chdr_update_length(O_CHDR_W, new_header,
|
||||
(pkt_type == CHDR_PKT_TYPE_MGMT) ? mgmt_pyld_len * (O_CHDR_W/8) : pyld_len_bytes);
|
||||
end
|
||||
|
||||
reg [DATA_W-1:0] new_mgmt_header;
|
||||
always @(*) begin
|
||||
// Update the CHDRWidth field in the management header.
|
||||
new_mgmt_header = i_pipe_tdata;
|
||||
new_mgmt_header[63:0] =
|
||||
chdr_mgmt_set_chdr_w(i_pipe_tdata[63:0], chdr_w_to_enum(O_CHDR_W));
|
||||
end
|
||||
|
||||
reg [DATA_W-1:0] o_pipe_tdata;
|
||||
reg o_pipe_tlast;
|
||||
reg o_pipe_tvalid;
|
||||
wire o_pipe_tready;
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= ST_HDR;
|
||||
mdata_count <= 'bX;
|
||||
word_count <= 'bX;
|
||||
num_mdata_reg <= 'bX;
|
||||
pkt_type_reg <= 'bX;
|
||||
end else if (o_pipe_tvalid & o_pipe_tready) begin
|
||||
// Default assignment
|
||||
word_count <= word_count + 1;
|
||||
|
||||
case (state)
|
||||
|
||||
// ST_HDR: CHDR Header
|
||||
ST_HDR: begin
|
||||
mdata_count <= 1; // The first metadata word will be word 1
|
||||
word_count <= 1; // Word 0 is the current word (header)
|
||||
pkt_type_reg <= pkt_type;
|
||||
// Save the number of DATA_W sized metadata words
|
||||
num_mdata_reg <= num_mdata;
|
||||
if (DATA_W == 64) begin
|
||||
// When CHDR_W == 64, the timestamp comes after the header.
|
||||
if (pkt_type == CHDR_PKT_TYPE_DATA_TS) begin
|
||||
state <= ST_TS;
|
||||
end else begin
|
||||
// O_CHDR_W must be at least 128, so there must be at least one
|
||||
// word of header padding.
|
||||
state <= ST_HDR_PAD;
|
||||
end
|
||||
end else begin
|
||||
// If DATA_W > 64 then O_CHDR_W must be at least 256, so we know
|
||||
// there must be some header padding needed.
|
||||
state <= ST_HDR_PAD;
|
||||
end
|
||||
end
|
||||
|
||||
// ST_TS: Timestamp (DATA_W == 64 only)
|
||||
ST_TS: begin
|
||||
if (O_CHDR_W > 128) begin
|
||||
state <= ST_HDR_PAD;
|
||||
end else begin
|
||||
if (num_mdata_reg != 0) begin
|
||||
state <= ST_MDATA;
|
||||
end else begin
|
||||
state <= ST_PYLD;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// ST_HDR_PAD: CHDR header padding to fill out the last O_CHDR_W
|
||||
ST_HDR_PAD: begin
|
||||
if (word_count == NUM_WORDS-1) begin
|
||||
if (num_mdata_reg != 0) begin
|
||||
state <= ST_MDATA;
|
||||
end else if (pkt_type_reg == CHDR_PKT_TYPE_MGMT) begin
|
||||
state <= ST_MGMT_HDR;
|
||||
end else begin
|
||||
state <= ST_PYLD;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// ST_MDATA: Metadata words
|
||||
ST_MDATA: begin
|
||||
mdata_count <= mdata_count + 1;
|
||||
if (mdata_count == num_mdata_reg) begin
|
||||
// If we've input a multiple of O_CHDR_W, then we're done with
|
||||
// metadata. Otherwise, we need to add some padding words.
|
||||
if (word_count == NUM_WORDS-1) begin
|
||||
if (pkt_type_reg == CHDR_PKT_TYPE_MGMT) begin
|
||||
state <= ST_MGMT_HDR;
|
||||
end else begin
|
||||
state <= ST_PYLD;
|
||||
end
|
||||
end else begin
|
||||
state <= ST_MDATA_PAD;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// ST_MDATA_PAD: Add metadata padding to fill out the last O_CHDR_W
|
||||
ST_MDATA_PAD: begin
|
||||
if (word_count == NUM_WORDS-1) begin
|
||||
if (pkt_type_reg == CHDR_PKT_TYPE_MGMT) begin
|
||||
state <= ST_MGMT_HDR;
|
||||
end else begin
|
||||
state <= ST_PYLD;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// ST_PYLD: Payload words
|
||||
ST_PYLD: begin
|
||||
if (i_pipe_tlast) begin
|
||||
// We don't pad data words because unused bytes are not sent or
|
||||
// expected on the transport.
|
||||
state <= ST_HDR;
|
||||
end
|
||||
end
|
||||
|
||||
// ST_MGMT_HDR: Management header
|
||||
ST_MGMT_HDR: begin
|
||||
// Management packets are different from other packet types in that
|
||||
// the payload is not serialized. So we need to pad each word to make
|
||||
// it a full O_CHDR_W size.
|
||||
if (i_pipe_tlast) begin
|
||||
state <= ST_LAST_PAD;
|
||||
end else begin
|
||||
state <= ST_MGMT_PAD;
|
||||
end
|
||||
end
|
||||
|
||||
// ST_MGMT_PYLD: Management operation words
|
||||
ST_MGMT_PYLD: begin
|
||||
if (i_pipe_tlast) begin
|
||||
state <= ST_LAST_PAD;
|
||||
end else begin
|
||||
state <= ST_MGMT_PAD;
|
||||
end
|
||||
end
|
||||
|
||||
// ST_MGMT_PAD: Management word padding
|
||||
ST_MGMT_PAD: begin
|
||||
if (word_count == NUM_WORDS-1) begin
|
||||
state <= ST_MGMT_PYLD;
|
||||
end
|
||||
end
|
||||
|
||||
// ST_LAST_PAD: Pad the last word so output is a multiple of O_CHDR_W
|
||||
ST_LAST_PAD : begin
|
||||
if (word_count == NUM_WORDS-1) begin
|
||||
state <= ST_HDR;
|
||||
end
|
||||
end
|
||||
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
//-----------------------------
|
||||
// State machine output logic
|
||||
//-----------------------------
|
||||
|
||||
always @(*) begin
|
||||
case (state)
|
||||
ST_HDR : begin
|
||||
o_pipe_tdata = new_header;
|
||||
o_pipe_tvalid = i_pipe_tvalid;
|
||||
o_pipe_tlast = i_pipe_tlast;
|
||||
i_pipe_tready = o_pipe_tready;
|
||||
end
|
||||
ST_TS : begin
|
||||
o_pipe_tdata = i_pipe_tdata;
|
||||
o_pipe_tvalid = i_pipe_tvalid;
|
||||
o_pipe_tlast = i_pipe_tlast;
|
||||
i_pipe_tready = o_pipe_tready;
|
||||
end
|
||||
ST_HDR_PAD : begin
|
||||
o_pipe_tdata = { DATA_W {1'b0} };
|
||||
o_pipe_tvalid = 1'b1;
|
||||
o_pipe_tlast = 1'b0;
|
||||
i_pipe_tready = 1'b0;
|
||||
end
|
||||
ST_MDATA : begin
|
||||
o_pipe_tdata = i_pipe_tdata;
|
||||
o_pipe_tvalid = i_pipe_tvalid;
|
||||
o_pipe_tlast = 1'b0;
|
||||
i_pipe_tready = o_pipe_tready;
|
||||
end
|
||||
ST_MDATA_PAD : begin
|
||||
o_pipe_tdata = { DATA_W {1'b0} };
|
||||
o_pipe_tvalid = 1'b1;
|
||||
o_pipe_tlast = 1'b0;
|
||||
i_pipe_tready = 1'b0;
|
||||
end
|
||||
ST_PYLD : begin
|
||||
o_pipe_tdata = i_pipe_tdata;
|
||||
o_pipe_tvalid = i_pipe_tvalid;
|
||||
o_pipe_tlast = i_pipe_tlast;
|
||||
i_pipe_tready = o_pipe_tready;
|
||||
end
|
||||
ST_MGMT_HDR : begin
|
||||
o_pipe_tdata = new_mgmt_header;
|
||||
o_pipe_tvalid = i_pipe_tvalid;
|
||||
o_pipe_tlast = 1'b0;
|
||||
i_pipe_tready = o_pipe_tready;
|
||||
end
|
||||
ST_MGMT_PYLD : begin
|
||||
o_pipe_tdata = i_pipe_tdata;
|
||||
o_pipe_tvalid = i_pipe_tvalid;
|
||||
o_pipe_tlast = 1'b0;
|
||||
i_pipe_tready = o_pipe_tready;
|
||||
end
|
||||
ST_MGMT_PAD : begin
|
||||
o_pipe_tdata = { DATA_W {1'b0} };
|
||||
o_pipe_tvalid = 1'b1;
|
||||
o_pipe_tlast = 1'b0;
|
||||
i_pipe_tready = 1'b0;
|
||||
end
|
||||
ST_LAST_PAD : begin
|
||||
o_pipe_tdata = { DATA_W {1'b0} };
|
||||
o_pipe_tvalid = 1'b1;
|
||||
o_pipe_tlast = (word_count == NUM_WORDS-1);
|
||||
i_pipe_tready = 1'b0;
|
||||
end
|
||||
default : begin
|
||||
o_pipe_tdata = 'bX;
|
||||
o_pipe_tvalid = 1'bX;
|
||||
o_pipe_tlast = 1'bX;
|
||||
i_pipe_tready = 1'bX;
|
||||
end
|
||||
endcase
|
||||
end
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Output Register
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
if (PIPELINE == "OUT" || PIPELINE == "INOUT") begin : gen_out_pipeline
|
||||
// Add a pipeline stage
|
||||
axi_fifo_flop2 #(
|
||||
.WIDTH (1 + DATA_W)
|
||||
) axi_fifo_flop2_i (
|
||||
.clk (clk),
|
||||
.reset (rst),
|
||||
.clear (1'b0),
|
||||
.i_tdata ({ o_pipe_tlast, o_pipe_tdata }),
|
||||
.i_tvalid (o_pipe_tvalid),
|
||||
.i_tready (o_pipe_tready),
|
||||
.o_tdata ({ o_chdr_tlast, o_chdr_tdata }),
|
||||
.o_tvalid (o_chdr_tvalid),
|
||||
.o_tready (o_chdr_tready),
|
||||
.space (),
|
||||
.occupied ()
|
||||
);
|
||||
end else begin : gen_no_out_pipeline
|
||||
assign o_chdr_tdata = o_pipe_tdata;
|
||||
assign o_chdr_tlast = o_pipe_tlast;
|
||||
assign o_chdr_tvalid = o_pipe_tvalid;
|
||||
assign o_pipe_tready = o_chdr_tready;
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
|
||||
`default_nettype wire
|
||||
@@ -0,0 +1,378 @@
|
||||
//
|
||||
// Copyright 2021 Ettus Research, a National Instruments Brand
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
//
|
||||
// Module: chdr_resize
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// Takes a CHDR packet data stream and converts it from one CHDR width to a
|
||||
// different CHDR width. It can also do CHDR width conversion without
|
||||
// changing the bus width, if the bus width is the same size as the smaller
|
||||
// CHDR width.
|
||||
//
|
||||
// For example, to convert from a 64-bit CHDR_W to a 256-bit CHDR_W, you
|
||||
// would set I_CHDR_W to 64 and O_CHDR_W to 256 (by default, I_DATA_W will be
|
||||
// set to 64 and O_DATA_W will be set to 256).
|
||||
//
|
||||
// But you could also convert from 64-bit CHDR to 256-bit CHDR while keeping
|
||||
// the bus width at 64 bits. In this case you would set I_CHDR_W to 64 and
|
||||
// O_CHDR_W to 256, but set both I_DATA_W and O_DATA_W to 64.
|
||||
//
|
||||
// There are some restrictions, including the requirement that I_CHDR_W ==
|
||||
// I_DATA_W or O_CHDR_W == O_DATA_W, and that MIN(I_DATA_W, O_DATA_W) ==
|
||||
// MIN(I_CHDR_W, O_CHDR_W). Basically, it can't do CHDR width conversion
|
||||
// where the smaller CHDR width is smaller than the bus width(s). For
|
||||
// example, you could not do conversion from 64-bit to 256-bit CHDR with
|
||||
// input and output bus widths of 256.
|
||||
//
|
||||
// TUSER is supported, but is not resized. TUSER is sampled along with the
|
||||
// first word of the input packet and is assumed to be the same for the
|
||||
// duration of the packet.
|
||||
//
|
||||
// Also, note that packets with different CHDR_W have a different maximum
|
||||
// number of metadata bytes. This module repacks the metadata in
|
||||
// little-endian order in the new word size. If there is too much metadata
|
||||
// for a smaller CHDR_W packet, the extra data will be discarded.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// I_CHDR_W : CHDR_W for the input data stream on i_chdr.
|
||||
// O_CHDR_W : CHDR_W for the output data stream on o_chdr.
|
||||
// I_DATA_W : Bus width for i_chdr_tdata.
|
||||
// O_DATA_W : Bus width for o_chdr_tdata.
|
||||
// USER_W : Width for i_chdr_tuser and o_chdr_tuser.
|
||||
// PIPELINE : Indicates whether to add pipeline stages to the input and/or
|
||||
// output. This can be: "NONE", "IN", "OUT", or "INOUT".
|
||||
//
|
||||
|
||||
`default_nettype none
|
||||
|
||||
|
||||
module chdr_resize #(
|
||||
parameter I_CHDR_W = 64,
|
||||
parameter O_CHDR_W = 512,
|
||||
parameter I_DATA_W = I_CHDR_W,
|
||||
parameter O_DATA_W = O_CHDR_W,
|
||||
parameter USER_W = 1,
|
||||
parameter PIPELINE = "NONE"
|
||||
) (
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
// Input
|
||||
input wire [I_DATA_W-1:0] i_chdr_tdata,
|
||||
input wire [ USER_W-1:0] i_chdr_tuser,
|
||||
input wire i_chdr_tlast,
|
||||
input wire i_chdr_tvalid,
|
||||
output wire i_chdr_tready,
|
||||
|
||||
// Input
|
||||
output wire [O_DATA_W-1:0] o_chdr_tdata,
|
||||
output wire [ USER_W-1:0] o_chdr_tuser,
|
||||
output wire o_chdr_tlast,
|
||||
output wire o_chdr_tvalid,
|
||||
input wire o_chdr_tready
|
||||
);
|
||||
|
||||
`define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
|
||||
|
||||
// Determine the bus width of the CHDR converter, which is always the smaller
|
||||
// bus width of the input and output.
|
||||
localparam CONVERT_W = `MIN(I_DATA_W, O_DATA_W);
|
||||
// Determine if we need the bus down-sizer
|
||||
localparam DO_DOWNSIZE = (I_DATA_W > O_DATA_W);
|
||||
// Determine if we need the CHDR width converter
|
||||
localparam DO_CONVERT = (I_CHDR_W != O_CHDR_W);
|
||||
// Determine if we need the bus up-sizer
|
||||
localparam DO_UPSIZE = (I_DATA_W < O_DATA_W);
|
||||
|
||||
// Determine the pipeline settings. We want pipeline stages on the input
|
||||
// and/or output, depending on the PIPELINE parameter, as well as between the
|
||||
// up-sizer and converter, and between the converter and down-sizer, any of
|
||||
// which may or may not be present. We don't, however, want back-to-back
|
||||
// pipeline stages (e.g., on the output of the down-sizer and the input to
|
||||
// the converter). If both an up/down-sizer and converter are used, the
|
||||
// settings below will turn on the adjacent pipeline stage in the converter
|
||||
// and turn off the corresponding pipeline stage in the up/down-sizer.
|
||||
localparam DOWNSIZE_PIPELINE =
|
||||
(PIPELINE == "IN" && DO_CONVERT) ? "IN" :
|
||||
(PIPELINE == "IN" && !DO_CONVERT) ? "IN" :
|
||||
(PIPELINE == "INOUT" && DO_CONVERT) ? "IN" :
|
||||
(PIPELINE == "INOUT" && !DO_CONVERT) ? "INOUT" :
|
||||
(PIPELINE == "OUT" && DO_CONVERT) ? "NONE" :
|
||||
(PIPELINE == "OUT" && !DO_CONVERT) ? "OUT" :
|
||||
"NONE" ;
|
||||
localparam CONVERT_PIPELINE =
|
||||
(PIPELINE == "IN" && DO_DOWNSIZE) ? "IN" :
|
||||
(PIPELINE == "IN" && DO_UPSIZE ) ? "INOUT" :
|
||||
(PIPELINE == "IN" /* neither */) ? "IN" :
|
||||
(PIPELINE == "INOUT" && DO_DOWNSIZE) ? "INOUT" :
|
||||
(PIPELINE == "INOUT" && DO_UPSIZE ) ? "INOUT" :
|
||||
(PIPELINE == "INOUT" /* neither */) ? "INOUT" :
|
||||
(PIPELINE == "OUT" && DO_DOWNSIZE) ? "INOUT" :
|
||||
(PIPELINE == "OUT" && DO_UPSIZE ) ? "OUT" :
|
||||
(PIPELINE == "OUT" /* neither */) ? "OUT" :
|
||||
"NONE" ;
|
||||
localparam UPSIZE_PIPELINE =
|
||||
(PIPELINE == "IN" && DO_CONVERT) ? "NONE" :
|
||||
(PIPELINE == "IN" && !DO_CONVERT) ? "IN" :
|
||||
(PIPELINE == "INOUT" && DO_CONVERT) ? "OUT" :
|
||||
(PIPELINE == "INOUT" && !DO_CONVERT) ? "INOUT" :
|
||||
(PIPELINE == "OUT" && DO_CONVERT) ? "OUT" :
|
||||
(PIPELINE == "OUT" && !DO_CONVERT) ? "OUT" :
|
||||
"NONE" ;
|
||||
|
||||
|
||||
generate
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Check Parameters
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
if (!(
|
||||
// All widths must be valid CHDR widths (at least 64 and powers of 2)
|
||||
(2**$clog2(I_CHDR_W) == I_CHDR_W) &&
|
||||
(2**$clog2(O_CHDR_W) == O_CHDR_W) &&
|
||||
(2**$clog2(I_DATA_W) == I_DATA_W) &&
|
||||
(2**$clog2(O_DATA_W) == O_DATA_W) &&
|
||||
(I_CHDR_W >= 64) &&
|
||||
(O_CHDR_W >= 64) &&
|
||||
(I_DATA_W >= 64) &&
|
||||
(O_DATA_W >= 64) &&
|
||||
// The converter width must match the smaller bus width. It doesn't work
|
||||
// on buses wider than the CHDR width.
|
||||
(CONVERT_W == `MIN(I_CHDR_W, O_CHDR_W))
|
||||
)) begin : gen_error
|
||||
ERROR__Invalid_CHDR_or_data_width_parameters();
|
||||
end
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// TUSER Data Path
|
||||
//-------------------------------------------------------------------------
|
||||
//
|
||||
// Sample TUSER at the beginning of the input packet and output it for the
|
||||
// duration of the output packet.
|
||||
//
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
if (DO_DOWNSIZE || DO_UPSIZE || DO_CONVERT || PIPELINE == "INOUT") begin : gen_tuser_buffer
|
||||
if (!DO_DOWNSIZE && !DO_UPSIZE && DO_CONVERT && PIPELINE == "NONE") begin : gen_tuser_reg
|
||||
// In this case, there's a combinatorial path from i_chdr to o_chdr, so
|
||||
// we can't use a FIFO to buffer TUSER.
|
||||
|
||||
// Track start of packet on o_chdr
|
||||
reg o_chdr_sop = 1;
|
||||
always @(posedge clk) begin
|
||||
if (rst) begin
|
||||
o_chdr_sop <= 1;
|
||||
end else if (o_chdr_tvalid && o_chdr_tready) begin
|
||||
o_chdr_sop <= o_chdr_tlast;
|
||||
end
|
||||
end
|
||||
|
||||
reg [USER_W-1:0] o_tuser_reg;
|
||||
always @(posedge clk) begin
|
||||
if (rst) begin
|
||||
o_tuser_reg <= {USER_W{1'bX}};
|
||||
end else if (o_chdr_tvalid && o_chdr_tready && o_chdr_sop) begin
|
||||
o_tuser_reg <= i_chdr_tuser;
|
||||
end
|
||||
end
|
||||
|
||||
// Pass through TUSER for first word in the packet, then use a holding
|
||||
// register for the rest of the packet.
|
||||
assign o_chdr_tuser = (o_chdr_sop) ? i_chdr_tuser : o_tuser_reg;
|
||||
|
||||
end else begin : gen_tuser_fifo
|
||||
// In this case we use a FIFO to buffer TUSER.
|
||||
|
||||
// Track start of packet on i_chdr
|
||||
reg i_chdr_sop = 1;
|
||||
always @(posedge clk) begin
|
||||
if (rst) begin
|
||||
i_chdr_sop <= 1;
|
||||
end else if (i_chdr_tvalid && i_chdr_tready) begin
|
||||
i_chdr_sop <= i_chdr_tlast;
|
||||
end
|
||||
end
|
||||
|
||||
axi_fifo_short #(
|
||||
.WIDTH (USER_W)
|
||||
) axi_fifo_short_i (
|
||||
.clk (clk),
|
||||
.reset (rst),
|
||||
.clear (1'b0),
|
||||
.i_tdata (i_chdr_tuser),
|
||||
.i_tvalid (i_chdr_tvalid && i_chdr_tready && i_chdr_sop),
|
||||
.i_tready (),
|
||||
.o_tdata (o_chdr_tuser),
|
||||
.o_tvalid (),
|
||||
.o_tready (o_chdr_tready && o_chdr_tvalid && o_chdr_tlast),
|
||||
.space (),
|
||||
.occupied ()
|
||||
);
|
||||
end
|
||||
end else begin : gen_tuser_pass_through
|
||||
// In this case there's no logic on the data path, so we can pass TUSER
|
||||
// through directly.
|
||||
assign o_chdr_tuser = i_chdr_tuser;
|
||||
end
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Down-Size Input Bus Width
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
wire [CONVERT_W-1:0] resized_tdata;
|
||||
wire resized_tlast;
|
||||
wire resized_tvalid;
|
||||
wire resized_tready;
|
||||
|
||||
if (DO_DOWNSIZE) begin : gen_bus_downsize
|
||||
axis_width_conv #(
|
||||
.WORD_W (CONVERT_W),
|
||||
.IN_WORDS (I_DATA_W / CONVERT_W),
|
||||
.OUT_WORDS (1),
|
||||
.SYNC_CLKS (1),
|
||||
.PIPELINE (DOWNSIZE_PIPELINE)
|
||||
) axis_width_conv_i (
|
||||
.s_axis_aclk (clk),
|
||||
.s_axis_rst (rst),
|
||||
.s_axis_tdata (i_chdr_tdata),
|
||||
.s_axis_tkeep ({(I_DATA_W / CONVERT_W){1'b1}}),
|
||||
.s_axis_tlast (i_chdr_tlast),
|
||||
.s_axis_tvalid (i_chdr_tvalid),
|
||||
.s_axis_tready (i_chdr_tready),
|
||||
.m_axis_aclk (clk),
|
||||
.m_axis_rst (rst),
|
||||
.m_axis_tdata (resized_tdata),
|
||||
.m_axis_tkeep (),
|
||||
.m_axis_tlast (resized_tlast),
|
||||
.m_axis_tvalid (resized_tvalid),
|
||||
.m_axis_tready (resized_tready)
|
||||
);
|
||||
end else begin : gen_no_bus_downsize
|
||||
assign resized_tdata = i_chdr_tdata;
|
||||
assign resized_tlast = i_chdr_tlast;
|
||||
assign resized_tvalid = i_chdr_tvalid;
|
||||
assign i_chdr_tready = resized_tready;
|
||||
end
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// CHDR Width Protocol Conversion
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
wire [CONVERT_W-1:0] converted_tdata;
|
||||
wire converted_tlast;
|
||||
wire converted_tvalid;
|
||||
wire converted_tready;
|
||||
|
||||
if (DO_CONVERT) begin : gen_convert
|
||||
if (I_CHDR_W > O_CHDR_W) begin : gen_chdr_convert_down
|
||||
chdr_convert_down #(
|
||||
.I_CHDR_W (I_CHDR_W),
|
||||
.DATA_W (CONVERT_W),
|
||||
.PIPELINE (CONVERT_PIPELINE)
|
||||
) chdr_convert_down_i (
|
||||
.clk (clk),
|
||||
.rst (rst),
|
||||
.i_chdr_tdata (resized_tdata),
|
||||
.i_chdr_tlast (resized_tlast),
|
||||
.i_chdr_tvalid (resized_tvalid),
|
||||
.i_chdr_tready (resized_tready),
|
||||
.o_chdr_tdata (o_chdr_tdata),
|
||||
.o_chdr_tlast (o_chdr_tlast),
|
||||
.o_chdr_tvalid (o_chdr_tvalid),
|
||||
.o_chdr_tready (o_chdr_tready)
|
||||
);
|
||||
end else if (I_CHDR_W < O_CHDR_W) begin : gen_chdr_convert_up
|
||||
chdr_convert_up #(
|
||||
.DATA_W (CONVERT_W),
|
||||
.O_CHDR_W (O_CHDR_W),
|
||||
.PIPELINE (PIPELINE)
|
||||
) chdr_convert_up_i (
|
||||
.clk (clk),
|
||||
.rst (rst),
|
||||
.i_chdr_tdata (resized_tdata),
|
||||
.i_chdr_tlast (resized_tlast),
|
||||
.i_chdr_tvalid (resized_tvalid),
|
||||
.i_chdr_tready (resized_tready),
|
||||
.o_chdr_tdata (converted_tdata),
|
||||
.o_chdr_tlast (converted_tlast),
|
||||
.o_chdr_tvalid (converted_tvalid),
|
||||
.o_chdr_tready (converted_tready)
|
||||
);
|
||||
end
|
||||
end else begin : gen_no_convert
|
||||
if (PIPELINE == "INOUT" && !DO_DOWNSIZE && !DO_UPSIZE) begin : gen_pipeline
|
||||
// In this case there's no conversion or up-size/down-size, so we're
|
||||
// just passing the data through unchanged. However, if PIPELINE is set
|
||||
// to INOUT then we should have a pipeline stage, so we add that here.
|
||||
axi_fifo_flop2 #(
|
||||
.WIDTH (1 + CONVERT_W)
|
||||
) axi_fifo_flop2_i (
|
||||
.clk (clk),
|
||||
.reset (rst),
|
||||
.clear (1'b0),
|
||||
.i_tdata ({ resized_tlast, resized_tdata }),
|
||||
.i_tvalid (resized_tvalid),
|
||||
.i_tready (resized_tready),
|
||||
.o_tdata ({ converted_tlast, converted_tdata }),
|
||||
.o_tvalid (converted_tvalid),
|
||||
.o_tready (converted_tready),
|
||||
.space (),
|
||||
.occupied ()
|
||||
);
|
||||
end else begin : gen_convert_bypass
|
||||
assign converted_tdata = resized_tdata;
|
||||
assign converted_tlast = resized_tlast;
|
||||
assign converted_tvalid = resized_tvalid;
|
||||
assign resized_tready = converted_tready;
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Up-Size Output Bus Width
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
if (DO_UPSIZE) begin : gen_bus_upsize
|
||||
axis_width_conv #(
|
||||
.WORD_W (CONVERT_W),
|
||||
.IN_WORDS (1),
|
||||
.OUT_WORDS (O_DATA_W / CONVERT_W),
|
||||
.SYNC_CLKS (1),
|
||||
.PIPELINE (UPSIZE_PIPELINE)
|
||||
) axis_width_conv_i (
|
||||
.s_axis_aclk (clk),
|
||||
.s_axis_rst (rst),
|
||||
.s_axis_tdata (converted_tdata),
|
||||
.s_axis_tkeep (1'b1),
|
||||
.s_axis_tlast (converted_tlast),
|
||||
.s_axis_tvalid (converted_tvalid),
|
||||
.s_axis_tready (converted_tready),
|
||||
.m_axis_aclk (clk),
|
||||
.m_axis_rst (rst),
|
||||
.m_axis_tdata (o_chdr_tdata),
|
||||
.m_axis_tkeep (),
|
||||
.m_axis_tlast (o_chdr_tlast),
|
||||
.m_axis_tvalid (o_chdr_tvalid),
|
||||
.m_axis_tready (o_chdr_tready)
|
||||
);
|
||||
end else begin : gen_no_bus_upsize
|
||||
assign o_chdr_tdata = converted_tdata;
|
||||
assign o_chdr_tlast = converted_tlast;
|
||||
assign o_chdr_tvalid = converted_tvalid;
|
||||
assign converted_tready = o_chdr_tready;
|
||||
end
|
||||
|
||||
endgenerate
|
||||
|
||||
endmodule
|
||||
|
||||
|
||||
`default_nettype wire
|
||||
Reference in New Issue
Block a user