fpga: rfnoc: Add RFNoC CHDR resize module
Original-commit: a94ea11f00bba2c4227c9ab173eb4b6ef1049bad
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user