449 lines
14 KiB
Verilog
449 lines
14 KiB
Verilog
//
|
|
// 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
|