diff options
Diffstat (limited to 'fpga/usrp3/lib/axi/axis_width_conv.v')
-rw-r--r-- | fpga/usrp3/lib/axi/axis_width_conv.v | 232 |
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 |