aboutsummaryrefslogtreecommitdiffstats
path: root/fpga/usrp3/lib/axi/axis_width_conv.v
diff options
context:
space:
mode:
Diffstat (limited to 'fpga/usrp3/lib/axi/axis_width_conv.v')
-rw-r--r--fpga/usrp3/lib/axi/axis_width_conv.v232
1 files changed, 232 insertions, 0 deletions
diff --git a/fpga/usrp3/lib/axi/axis_width_conv.v b/fpga/usrp3/lib/axi/axis_width_conv.v
new file mode 100644
index 000000000..2cf19ece8
--- /dev/null
+++ b/fpga/usrp3/lib/axi/axis_width_conv.v
@@ -0,0 +1,232 @@
+//
+// 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}
+//
+// 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"
+)(
+ // 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) && (
+ ((PIPELINE == "IN" || PIPELINE == "INOUT") && (UPSIZE_RATIO == 1)) ||
+ ((PIPELINE == "OUT" || PIPELINE == "INOUT") && (DOWNSIZE_RATIO == 1))
+ );
+ localparam FIFO_SIZE = 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
+ if (SYNC_CLKS) begin
+ axi_fifo #(.WIDTH(INT_DATA_W+INT_KEEP_W+1), .SIZE(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(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