uhd/fpga/usrp3/lib/axi/axis_width_conv.v

241 lines
9.7 KiB
Verilog

//
// Copyright 2018 Ettus Research, A National Instruments Company
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: axis_width_conv
// Description:
// An AXI-Stream width conversion module that can convert from
// an arbitrary input width to an arbitrary output width. The
// module also supports an optional clock crossing. Data bits
// are grouped into words which will be rearranged by this module.
// The contents of a word are not rearranged.
// Example (WORD_W=4, IN_WORDS=4, OUT_WORDS=6):
// Input : 3_1_2_0, x_6_5_4 (comma-delimited packets)
// Output : 5_4_3_2_1_0, x_x_x_x_x_6 (comma-delimited packets)
// NOTE: The use of tkeep in this module is a slight deviation from
// the AXI standard where the bits are "byte qualifiers". In
// this module, tkeep is a "word qualifier" where the width
// of a word can be arbitrary. If WORD_W = 8, the behavior
// of this module is identical to an AXI width converter.
//
// Parameters:
// - WORD_W: Bitwidth of a word
// - IN_WORDS: Number of words in the input stream
// - OUT_WORDS: Number of words in the output stream
// - SYNC_CLKS: Are s_axis_aclk and m_axis_aclk synchronous to each other?
// - PIPELINE: Which ports to pipeline? {NONE, IN, OUT, INOUT}
// - FIFO_SIZE: The size of the FIFO given as log2 of the number of
// WORD_W-bit words.
//
// Signals:
// - s_axis_* : Input sample stream (AXI-Stream)
// - m_axis_* : Output sample stream (AXI-Stream)
module axis_width_conv #(
parameter WORD_W = 8,
parameter IN_WORDS = 4,
parameter OUT_WORDS = 6,
parameter SYNC_CLKS = 0,
parameter PIPELINE = "NONE",
parameter FIFO_SIZE = 1
)(
// Data In (AXI-Stream)
input wire s_axis_aclk, // Input stream Clock
input wire s_axis_rst, // Input stream Reset
input wire [(IN_WORDS*WORD_W)-1:0] s_axis_tdata, // Input stream tdata
input wire [IN_WORDS-1:0] s_axis_tkeep, // Input stream tkeep
input wire s_axis_tlast, // Input stream tlast
input wire s_axis_tvalid, // Input stream tvalid
output wire s_axis_tready, // Input stream tready
// Data Out (AXI-Stream)
input wire m_axis_aclk, // Output stream Clock
input wire m_axis_rst, // Output stream Reset
output wire [(OUT_WORDS*WORD_W)-1:0] m_axis_tdata, // Output stream tdata
output wire [OUT_WORDS-1:0] m_axis_tkeep, // Output stream tkeep
output wire m_axis_tlast, // Output stream tlast
output wire m_axis_tvalid, // Output stream tvalid
input wire m_axis_tready // Output stream tready
);
//----------------------------------------------
// Pipeline Logic
//----------------------------------------------
// Add optional input and output pipeline stages
wire [(IN_WORDS*WORD_W)-1:0] i_tdata;
wire [IN_WORDS-1:0] i_tkeep;
wire i_tlast, i_tvalid, i_tready;
wire [(OUT_WORDS*WORD_W)-1:0] o_tdata;
wire [OUT_WORDS-1:0] o_tkeep;
wire o_tlast, o_tvalid, o_tready;
generate
if (PIPELINE == "IN" || PIPELINE == "INOUT") begin
axi_fifo_flop2 #(.WIDTH((IN_WORDS*(WORD_W+1))+1)) in_pipe_i (
.clk(s_axis_aclk), .reset(s_axis_rst), .clear(1'b0),
.i_tdata({s_axis_tlast, s_axis_tkeep, s_axis_tdata}),
.i_tvalid(s_axis_tvalid), .i_tready(s_axis_tready),
.o_tdata({i_tlast, i_tkeep, i_tdata}),
.o_tvalid(i_tvalid), .o_tready(i_tready),
.space(), .occupied()
);
end else begin
assign {i_tlast, i_tkeep, i_tdata} = {s_axis_tlast, s_axis_tkeep, s_axis_tdata};
assign i_tvalid = s_axis_tvalid;
assign s_axis_tready = i_tready;
end
if (PIPELINE == "OUT" || PIPELINE == "INOUT") begin
axi_fifo_flop2 #(.WIDTH((OUT_WORDS*(WORD_W+1))+1)) out_pipe_i (
.clk(m_axis_aclk), .reset(m_axis_rst), .clear(1'b0),
.i_tdata({o_tlast, o_tkeep, o_tdata}),
.i_tvalid(o_tvalid), .i_tready(o_tready),
.o_tdata({m_axis_tlast, m_axis_tkeep, m_axis_tdata}),
.o_tvalid(m_axis_tvalid), .o_tready(m_axis_tready),
.space(), .occupied()
);
end else begin
assign {m_axis_tlast, m_axis_tkeep, m_axis_tdata} = {o_tlast, o_tkeep, o_tdata};
assign m_axis_tvalid = o_tvalid;
assign o_tready = m_axis_tready;
end
endgenerate
//----------------------------------------------
// Intermediate Data Bus
//----------------------------------------------
// To perform an M to N width conversion, we first
// convert from M to LCM(M, N), then to N
// Function to compute the least common multiple
// of two numbers (parameters or localparams only)
function integer lcm;
input integer a;
input integer b;
integer x, y, swap;
reg done;
begin
done = 1'b0;
x = a;
y = b;
while (!done) begin
if (x < y) begin
swap = x;
x = y;
y = swap;
end else if (y != 0) begin
x = x - y;
end else begin
done = 1'b1;
end
end
// x is the greatest common divisor
// LCM = (a*b)/GCD
lcm = (a*b)/x;
end
endfunction
// Intermediate bus parameters
localparam integer INT_KEEP_W = lcm(IN_WORDS, OUT_WORDS);
localparam integer INT_DATA_W = INT_KEEP_W * WORD_W;
localparam integer UPSIZE_RATIO = INT_KEEP_W / IN_WORDS;
localparam integer DOWNSIZE_RATIO = INT_KEEP_W / OUT_WORDS;
wire [INT_DATA_W-1:0] fifo_i_tdata, fifo_o_tdata;
wire [INT_KEEP_W-1:0] fifo_i_tkeep, fifo_o_tkeep;
wire fifo_i_tlast, fifo_i_tvalid, fifo_i_tready;
wire fifo_o_tlast, fifo_o_tvalid, fifo_o_tready;
// Skip the intermediate FIFO if
// - The input and output clocks are the same
// - The upsizer is effectively a passthrough and input registering is requested
// - The downsizer is effectively a passthrough and output registering is requested
localparam [0:0] SKIP_FIFO = (SYNC_CLKS == 1) && (FIFO_SIZE <= 1) && (
((PIPELINE == "IN" || PIPELINE == "INOUT") && (UPSIZE_RATIO == 1)) ||
((PIPELINE == "OUT" || PIPELINE == "INOUT") && (DOWNSIZE_RATIO == 1))
);
//----------------------------------------------
// In => Upsizer => FIFO => Downsizer => Out
//----------------------------------------------
wire [INT_KEEP_W-1:0] up_keep_flat;
wire [UPSIZE_RATIO-1:0] up_keep_keep;
wire [DOWNSIZE_RATIO-1:0] down_keep_keep;
axis_upsizer #(
.IN_DATA_W(IN_WORDS*WORD_W), .IN_USER_W(IN_WORDS),
.RATIO(UPSIZE_RATIO)
) upsizer_i (
.clk(s_axis_aclk), .reset(s_axis_rst),
.s_axis_tdata(i_tdata), .s_axis_tuser(i_tkeep),
.s_axis_tlast(i_tlast), .s_axis_tvalid(i_tvalid), .s_axis_tready(i_tready),
.m_axis_tdata(fifo_i_tdata), .m_axis_tuser(up_keep_flat), .m_axis_tkeep(up_keep_keep),
.m_axis_tlast(fifo_i_tlast), .m_axis_tvalid(fifo_i_tvalid), .m_axis_tready(fifo_i_tready)
);
// tkeep unmasking logic after upsizer
genvar i;
generate for (i = 0; i < INT_KEEP_W; i = i + 1) begin
// tkeep is assumed to be valid only when tlast is asserted
// otherwise it is 1
assign fifo_i_tkeep[i] = ~fifo_i_tlast |
(up_keep_keep[i/IN_WORDS] ? up_keep_flat[i] : 1'b0);
end endgenerate
generate
if (SKIP_FIFO) begin
assign fifo_o_tdata = fifo_i_tdata;
assign fifo_o_tkeep = fifo_i_tkeep;
assign fifo_o_tlast = fifo_i_tlast;
assign fifo_o_tvalid = fifo_i_tvalid;
assign fifo_i_tready = fifo_o_tready;
end else begin
// calculate the actual FIFO size as the width might be different than// WORD_W
localparam integer FIFO_ELEMENTS = $ceil((2.0 ** FIFO_SIZE) / INT_KEEP_W);
localparam integer FIFO_ELEMENTS_MIN = FIFO_ELEMENTS > 0 ? FIFO_ELEMENTS : 1;
localparam integer REQUESTED_FIFO_SIZE = $clog2(FIFO_ELEMENTS_MIN);
localparam integer ADJUSTED_FIFO_SIZE = REQUESTED_FIFO_SIZE > 0 ? REQUESTED_FIFO_SIZE : 1;
if (SYNC_CLKS) begin
axi_fifo #(.WIDTH(INT_DATA_W+INT_KEEP_W+1), .SIZE(ADJUSTED_FIFO_SIZE)) fifo_i (
.clk(s_axis_aclk), .reset(s_axis_rst), .clear(1'b0),
.i_tdata({fifo_i_tlast, fifo_i_tkeep, fifo_i_tdata}),
.i_tvalid(fifo_i_tvalid), .i_tready(fifo_i_tready),
.o_tdata({fifo_o_tlast, fifo_o_tkeep, fifo_o_tdata}),
.o_tvalid(fifo_o_tvalid), .o_tready(fifo_o_tready),
.space(), .occupied()
);
end else begin
axi_fifo_2clk #(.WIDTH(INT_DATA_W+INT_KEEP_W+1), .SIZE(ADJUSTED_FIFO_SIZE)) fifo_i (
.reset(s_axis_rst),
.i_aclk(s_axis_aclk),
.i_tdata({fifo_i_tlast, fifo_i_tkeep, fifo_i_tdata}),
.i_tvalid(fifo_i_tvalid), .i_tready(fifo_i_tready),
.o_aclk(m_axis_aclk),
.o_tdata({fifo_o_tlast, fifo_o_tkeep, fifo_o_tdata}),
.o_tvalid(fifo_o_tvalid), .o_tready(fifo_o_tready)
);
end
end
endgenerate
// tkeep masking logic after downsizer
generate for (i = 0; i < DOWNSIZE_RATIO; i = i + 1) begin
assign down_keep_keep[i] = |fifo_o_tkeep[i*OUT_WORDS+:OUT_WORDS];
end endgenerate
axis_downsizer #(
.OUT_DATA_W(OUT_WORDS*WORD_W), .OUT_USER_W(OUT_WORDS),
.RATIO(DOWNSIZE_RATIO)
) downsizer_i (
.clk(m_axis_aclk), .reset(m_axis_rst),
.s_axis_tdata(fifo_o_tdata), .s_axis_tuser(fifo_o_tkeep), .s_axis_tkeep(down_keep_keep),
.s_axis_tlast(fifo_o_tlast), .s_axis_tvalid(fifo_o_tvalid), .s_axis_tready(fifo_o_tready),
.m_axis_tdata(o_tdata), .m_axis_tuser(o_tkeep),
.m_axis_tlast(o_tlast), .m_axis_tvalid(o_tvalid), .m_axis_tready(o_tready)
);
endmodule // axis_width_conv