mirror of
https://github.com/EttusResearch/uhd.git
synced 2026-10-25 17:36:10 +00:00
241 lines
9.7 KiB
Verilog
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
|