aboutsummaryrefslogtreecommitdiffstats
path: root/fpga/usrp3/lib/axi/axis_width_conv.v
blob: 2cf19ece8d7ac0a38a16f8f095f254893fbe8b9f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
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