File: chdr_stream_input.v

package info (click to toggle)
uhd 4.9.0.0%2Bds1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 184,180 kB
  • sloc: cpp: 262,887; python: 112,011; ansic: 102,670; vhdl: 57,031; tcl: 19,924; xml: 8,581; makefile: 3,028; sh: 2,812; pascal: 230; javascript: 120; csh: 94; asm: 20; perl: 11
file content (601 lines) | stat: -rw-r--r-- 23,621 bytes parent folder | download
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
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
//
// Copyright 2018-2019 Ettus Research, A National Instruments Company
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: chdr_stream_input
// Description:
//   Implements the CHDR input port for a stream endpoint.
//   The module accepts stream command and data packets and
//   emits stream status packets. Flow control and error state
//   is communicated using stream status packets. There are no
//   external config interfaces because all configuration is done
//   using stream command packets.
//
// Parameters:
//   - DEVICE_FAMILY: The FPGA device family (e.g., "7SERIES" or "ULTRASCALE")
//   - CHDR_W: Width of the CHDR bus in bits
//   - BUFF_SIZE: Buffer size in log2 of the number of words in the
//                ingress buffer for the stream
//   - FLUSH_TIMEOUT_W: log2 of the number of cycles to wait in order
//                      to flush the input stream
//   - SIGNAL_ERRS: If set to 1 then all stream errors will be notified
//                  upstream, otherwise ALL errors are ignored
//
// Signals:
//   - s_axis_chdr_* : Input CHDR stream (AXI-Stream)
//   - m_axis_chdr_* : Output flow-controlled CHDR stream (AXI-Stream)
//   - m_axis_strs_* : Output stream status (AXI-Stream)
//   - data_err_stb  : If asserted, a data error notification is sent upstream
//

module chdr_stream_input #(
  parameter DEVICE_FAMILY   = "7SERIES",
  parameter CHDR_W          = 256,
  parameter BUFF_SIZE       = 14,
  parameter FLUSH_TIMEOUT_W = 14,
  parameter MONITOR_EN      = 1,
  parameter SIGNAL_ERRS     = 1
)(
  // Clock, reset and settings
  input  wire              clk,
  input  wire              rst,
  // CHDR in (AXI-Stream)
  input  wire [CHDR_W-1:0] s_axis_chdr_tdata,
  input  wire              s_axis_chdr_tlast,
  input  wire              s_axis_chdr_tvalid,
  output wire              s_axis_chdr_tready,
  // Flow controlled data out (AXI-Stream)
  output wire [CHDR_W-1:0] m_axis_data_tdata,
  output wire              m_axis_data_tlast,
  output wire              m_axis_data_tvalid,
  input  wire              m_axis_data_tready,
  // Stream status out (AXI-Stream)
  output reg  [CHDR_W-1:0] m_axis_strs_tdata,
  output wire              m_axis_strs_tlast,
  output wire              m_axis_strs_tvalid,
  input  wire              m_axis_strs_tready,
  // External stream error signal
  input  wire              data_err_stb
);

  // The buffer size depends on the BUFF_SIZE parameter
  localparam [40:0] BUFF_SIZE_BYTES = ((41'h1 << BUFF_SIZE) * (CHDR_W / 8)) - 41'h1;
  // This is a flit-buffer. No packet limits
  localparam [23:0] BUFF_SIZE_PKTS  = 24'hFFFFFF;

  // ---------------------------------------------------
  //  RFNoC Includes
  // ---------------------------------------------------
  `include "rfnoc_chdr_utils.vh"
  `include "rfnoc_chdr_internal_utils.vh"

  // ---------------------------------------------------
  //  Ingress Buffer and Flow Control Logic
  // ---------------------------------------------------
  wire [CHDR_W-1:0] buff_tdata;
  wire              buff_tlast, buff_tvalid;
  reg               buff_tready;
  wire [15:0]       buff_info;

  chdr_ingress_fifo #(
    .DEVICE(DEVICE_FAMILY), .WIDTH(CHDR_W), .SIZE(BUFF_SIZE)
  ) ingress_fifo_i (
    .clk(clk), .reset(rst), .clear(1'b0),
    .i_tdata(s_axis_chdr_tdata), .i_tlast(s_axis_chdr_tlast),
    .i_tvalid(s_axis_chdr_tvalid), .i_tready(s_axis_chdr_tready),
    .o_tdata(buff_tdata), .o_tlast(buff_tlast),
    .o_tvalid(buff_tvalid), .o_tready(buff_tready)
  );

  generate if (MONITOR_EN) begin
    wire [BUFF_SIZE:0] occ_lines;
    axis_fifo_monitor #( .COUNT_W(BUFF_SIZE+1) ) fifo_mon_i (
      .clk(clk), .reset(rst),
      .i_tlast(s_axis_chdr_tlast), .i_tvalid(s_axis_chdr_tvalid), .i_tready(s_axis_chdr_tready),
      .o_tlast(buff_tlast), .o_tvalid(buff_tvalid), .o_tready(buff_tready),
      .i_sop(), .i_eop(), .o_sop(), .o_eop(),
      .occupied(occ_lines), .occupied_pkts()
    );
    // buff_info represents a fraction of the fullness of the buffer
    // fullness percentage = (buff_info / 32768) * 100
    if (BUFF_SIZE + 1 >= 16)
      assign buff_info = occ_lines[BUFF_SIZE:(BUFF_SIZE-15)];
    else
      assign buff_info = {occ_lines, {(15-BUFF_SIZE){1'b0}}};
  end else begin
    assign buff_info = 16'd0;
  end endgenerate

  // Flow Control State
  // xfer_cnt: Total transfer count since fc_enabled = 1
  // accum: Transfer count since last FC response
  // fc_freq: The threshold for sending an FC response
  reg [63:0] xfer_cnt_bytes = 64'd0;
  reg [39:0] xfer_cnt_pkts  = 40'd0;
  reg [63:0] accum_bytes    = 64'd0;
  reg [39:0] accum_pkts     = 40'd0;
  reg [63:0] fc_freq_bytes  = 64'd0;
  reg [39:0] fc_freq_pkts   = 40'd0;

  // State machine transition signals info
  reg        fc_enabled = 1'b0;   // Is flow control enabled?
  wire       fc_ping;             // A flow control response was requested
  wire       fc_first_resp;       // Send the first flow control response
  wire       fc_refresh;          // Refresh accumulated values
  wire       fc_override;         // Override total xfer counts
  reg        fc_override_del = 1'b0;
  reg  [3:0] fc_due_shreg = 4'hF; // Is a response due? (shift register)

  // Endpoint IDs of this endpoint and the stream source
  reg [15:0] this_epid = 16'd0, return_epid = 16'd0;

  // Cached values from a stream command
  reg [63:0] strc_num_bytes;
  reg [39:0] strc_num_pkts;
  reg [3:0]  strc_op_data;  // Unused for now
  reg [3:0]  strc_op_code;

  // Total transfer count updater
  always @(posedge clk) begin
    if (rst || !fc_enabled) begin
      // Reset
      xfer_cnt_bytes <= 64'd0;
      xfer_cnt_pkts  <= 40'd0;
    end else if (fc_override) begin
      // Override
      xfer_cnt_bytes <= strc_num_bytes;
      xfer_cnt_pkts  <= strc_num_pkts;
    end else if (buff_tvalid && buff_tready) begin
      // Count
      xfer_cnt_bytes <= xfer_cnt_bytes + (CHDR_W/8);
      if (buff_tlast)
        xfer_cnt_pkts <= xfer_cnt_pkts + 40'd1;
    end
  end

  // Accumulated transfer count updater
  always @(posedge clk) begin
    if (rst || !fc_enabled || fc_refresh) begin
      // Reset
      accum_bytes <= 64'd0;
      accum_pkts  <= 40'd0;
    end else if (buff_tvalid && buff_tready) begin
      // Count
      accum_bytes <= accum_bytes + (CHDR_W/8);
      if (buff_tlast)
        accum_pkts <= accum_pkts + 40'd1;
    end
  end

  // Flow control trigger
  // Why a shift-register here?
  // 1. For edge detection
  // 2. To allow the tools to re-time the wide comparators.
  //    We don't care about the latency here because stream
  //    status messages are asynchronous wrt the input.
  always @(posedge clk) begin
    if (rst || !fc_enabled) begin
      // Reset to all ones so we don't send an extra stream status packet
      // immediately after flow control is re-enabled. This also ensures we
      // don't send an extra status packet when we get the first init command
      // which has zero for num_bytes and num_pkts.
      fc_due_shreg <= 4'hF;
    end else begin
      fc_due_shreg <= {
        fc_due_shreg[2:0],
        (accum_bytes >= fc_freq_bytes) || (accum_pkts >= fc_freq_pkts)
      };
    end
  end
  wire fc_resp_due = fc_due_shreg[2] && !fc_due_shreg[3];

   // ---------------------------------------------------
  //  Stream Command Handler
  // ---------------------------------------------------
  localparam [2:0] ST_IN_HDR    = 3'd0;   // The CHDR header of an input pkt
  localparam [2:0] ST_IN_DATA   = 3'd1;   // The CHDR body (incl. mdata) of an input pkt
  localparam [2:0] ST_STRC_W0   = 3'd2;   // The first word of a stream command
  localparam [2:0] ST_STRC_W1   = 3'd3;   // The second word of a stream command
  localparam [2:0] ST_STRC_EXEC = 3'd4;   // A stream command is executing
  localparam [2:0] ST_FLUSH     = 3'd5;   // Input is flushing
  localparam [2:0] ST_DROP      = 3'd6;   // Current packet is being dropped

  reg [2:0]   state = ST_IN_HDR;          // State of the input state machine
  reg         pkt_too_long = 1'b0;        // Error case. Packet is too long
  reg         is_first_strc_pkt = 1'b1;   // Is this the strm cmd data pkt after fc_enabled = 1?
  reg [15:0]  exp_data_seq_num = 16'd0;   // Expected sequence number for the next data pkt
  reg [15:0]  exp_strc_seq_num = 16'd0;   // Expected sequence number for the next stream cmd pkt
  reg [15:0]  strc_dst_epid = 16'd0;      // EPID in CHDR header of STRC packet
  reg [15:0]  last_strc_seq_num= 16'd0;      // Last sequence number of a stream command packet

  reg         stop_on_seq_err_en = 1'b0;  // State configured from the host to terminate stream on sequence errors
  reg         seq_error_occured = 1'b0;   // Kept after sequence error to terminate subsequent packets

  reg [FLUSH_TIMEOUT_W-1:0] flush_counter = {FLUSH_TIMEOUT_W{1'b0}};

  // Shortcuts
  wire is_data_pkt =
    chdr_get_pkt_type(buff_tdata[63:0]) == CHDR_PKT_TYPE_DATA ||
    chdr_get_pkt_type(buff_tdata[63:0]) == CHDR_PKT_TYPE_DATA_TS;
  wire is_strc_pkt =
    chdr_get_pkt_type(buff_tdata[63:0]) == CHDR_PKT_TYPE_STRC;

  // Error Logic
  wire data_seq_err_stb = (state == ST_IN_HDR) && is_data_pkt &&
    (chdr_get_seq_num(buff_tdata[63:0]) != exp_data_seq_num);
  wire strc_seq_err_stb = (state == ST_IN_HDR) && is_strc_pkt && !is_first_strc_pkt &&
    (chdr_get_seq_num(buff_tdata[63:0]) != exp_strc_seq_num);
  wire seq_err_stb = (data_seq_err_stb || strc_seq_err_stb) && buff_tvalid && buff_tready;

  wire route_err_stb = buff_tvalid && buff_tready && (state == ST_IN_HDR) &&
    (chdr_get_dst_epid(buff_tdata[63:0]) != this_epid);

  // Break critical paths to response FIFO
  reg [47:0] stream_err_info = 48'h0;
  reg        stream_err_stb = 1'b0;
  reg [3:0]  stream_err_status = CHDR_STRS_STATUS_OKAY;

  always @(posedge clk) begin
    if (rst || (SIGNAL_ERRS == 0)) begin
      stream_err_stb <= 1'b0;
    end else begin
      stream_err_stb <= seq_err_stb | route_err_stb | data_err_stb;
      if (seq_err_stb) begin
        stream_err_status <= CHDR_STRS_STATUS_SEQERR;
        // The extended info has the packet type (to detect which stream
        // had an error), the expected and actual sequence number.
        stream_err_info <= {2'b0, seq_error_occured, stop_on_seq_err_en, 9'b0, chdr_get_pkt_type(buff_tdata[63:0]),
            data_seq_err_stb ? exp_data_seq_num : exp_strc_seq_num,
            chdr_get_seq_num(buff_tdata[63:0])};
      end else if (route_err_stb) begin
        stream_err_status <= CHDR_STRS_STATUS_RTERR;
        // The extended info has the expected and actual destination EPID.
        stream_err_info <= {16'd0, this_epid, chdr_get_dst_epid(buff_tdata[63:0])};
      end else begin
        stream_err_status <= CHDR_STRS_STATUS_DATAERR;
        // The extended info has the expected and actual destination EPID.
        stream_err_info <= {16'd0, this_epid, chdr_get_dst_epid(buff_tdata[63:0])};
      end
    end
  end

  // Input State Machine
  // - Pass data packets forward
  // - Consume stream cmd packets
  always @(posedge clk) begin
    if (rst) begin
      state <= ST_IN_HDR;
      pkt_too_long <= 1'b0;
      fc_enabled <= 1'b0;
      seq_error_occured <= 1'b0;
    end else begin
      case (state)
        ST_IN_HDR: begin
          if (buff_tvalid && buff_tready) begin
            if (!buff_tlast) begin
              // Classify packet and...
              if (is_strc_pkt) begin
                // ...consume if it is a stream command or...
                state <= ST_STRC_W0;
                last_strc_seq_num <= chdr_get_seq_num(buff_tdata[63:0]);
              end else if (is_data_pkt) begin
                // .. drop the packet if seq error occurred or occurs with this packet
                // FIXME: Looks like there's a bug here where if the packet is a single cycle, then it won't get dropped because we only get here if !buff_tlast.
                if (seq_error_occured || (data_seq_err_stb && stop_on_seq_err_en)) begin
                  seq_error_occured <= stop_on_seq_err_en;
                  state <= ST_DROP;
                // ...pass to output if it is a data packet...
                end else begin
                  state <= ST_IN_DATA;
                end
              end else begin
                // ... otherwise drop.
                state <= ST_DROP;
              end
            end
            // Update other state vars
            pkt_too_long <= 1'b0;
            if (is_strc_pkt) begin
              is_first_strc_pkt <= 1'b0;
              strc_dst_epid <= chdr_get_dst_epid(buff_tdata[63:0]);
              exp_strc_seq_num <= chdr_get_seq_num(buff_tdata[63:0]) + 16'd1;
            end else if (is_data_pkt) begin
              // stall the counter in error case when configured for reliable transmission
              if (~stop_on_seq_err_en || (~data_seq_err_stb && ~seq_error_occured)) begin
                exp_data_seq_num <= chdr_get_seq_num(buff_tdata[63:0]) + 16'd1;
              end
            end
          end
        end
        ST_IN_DATA: begin
          // Pass the data packet forward
          if (buff_tvalid && buff_tready && buff_tlast)
            state <= ST_IN_HDR;
        end
        ST_STRC_W0: begin
          if (buff_tvalid && buff_tready) begin
            // Consume the first word of a stream command packet
            if (CHDR_W > 64) begin
              strc_num_bytes <= chdr128_strc_get_num_bytes(buff_tdata[127:0]);
              strc_num_pkts  <= chdr128_strc_get_num_pkts (buff_tdata[127:0]);
              strc_op_data   <= chdr128_strc_get_op_data  (buff_tdata[127:0]);
              strc_op_code   <= chdr128_strc_get_op_code  (buff_tdata[127:0]);
              return_epid    <= chdr128_strs_get_src_epid (buff_tdata[127:0]);
              state <= ST_STRC_EXEC;
              pkt_too_long <= ~buff_tlast;
            end else begin
              strc_num_pkts <= chdr64_strc_get_num_pkts(buff_tdata[63:0]);
              strc_op_data  <= chdr64_strc_get_op_data (buff_tdata[63:0]);
              strc_op_code  <= chdr64_strc_get_op_code (buff_tdata[63:0]);
              return_epid   <= chdr64_strs_get_src_epid(buff_tdata[63:0]);
              state <= ST_STRC_W1;
            end
          end
        end
        ST_STRC_W1: begin
          if (buff_tvalid && buff_tready) begin
            // Consume the second word of a stream command packet
            strc_num_bytes <= chdr64_strc_get_num_bytes(buff_tdata[63:0]);
            state <= ST_STRC_EXEC;
            pkt_too_long <= ~buff_tlast;
          end
        end
        ST_STRC_EXEC: begin
          case (strc_op_code)
            CHDR_STRC_OPCODE_INIT: begin
              // Configure FC but disable it temporarily
              fc_freq_bytes <= strc_num_bytes;
              fc_freq_pkts <= strc_num_pkts;
              this_epid <= strc_dst_epid;
              fc_enabled <= 1'b0;
              // Flush the input
              state <= ST_FLUSH;
              flush_counter <= {FLUSH_TIMEOUT_W{1'b1}};
              stop_on_seq_err_en <= strc_op_data[0];
              seq_error_occured <= 1'b0;
              exp_data_seq_num <= 16'd0;
            end
            CHDR_STRC_OPCODE_PING: begin
              // Ping can complete in 1 cycle
              state <= pkt_too_long ? ST_DROP : ST_IN_HDR;
            end
            CHDR_STRC_OPCODE_RESYNC: begin
              // Resync can complete in 1 cycle
              state <= pkt_too_long ? ST_DROP : ST_IN_HDR;
            end
            default: begin
              state <= pkt_too_long ? ST_DROP : ST_IN_HDR;
            end
          endcase
        end
        ST_FLUSH: begin
          // Drop until the next packet arrives
          if (buff_tvalid && buff_tready) begin
            flush_counter <= {FLUSH_TIMEOUT_W{1'b1}};
          end else begin
            flush_counter <= flush_counter - 'd1;
            if (flush_counter == {FLUSH_TIMEOUT_W{1'b0}}) begin
              // Done flushing. Re-arm flow control and reset packet
              // sequence check info.
              fc_enabled <= 1'b1;
              is_first_strc_pkt <= 1'b1;
              state <= ST_IN_HDR;
            end
          end
        end
        ST_DROP: begin
          // Drop until the next packet arrives
          if (buff_tvalid && buff_tready && buff_tlast)
            state <= ST_IN_HDR;
        end
        default: begin
          // We should never get here
          state <= ST_IN_HDR;
        end
      endcase
    end
  end

  always @(*) begin
    case (state)
      ST_IN_HDR:
        buff_tready = m_axis_data_tready || !is_data_pkt;
      ST_IN_DATA:
        buff_tready = m_axis_data_tready;
      ST_STRC_W0:
        buff_tready = 1'b1;
      ST_STRC_W1:
        buff_tready = 1'b1;
      ST_FLUSH:
        buff_tready = 1'b1;
      ST_DROP:
        buff_tready = 1'b1;
      default:
        buff_tready = 1'b0;
    endcase
  end

  // Figure out if we're dropping this packet due to a sequence error
  wire going_to_drop = is_data_pkt &&
    (seq_error_occured || (data_seq_err_stb && stop_on_seq_err_en));

  // Logic to drive output port
  assign m_axis_data_tdata  = buff_tdata;
  assign m_axis_data_tlast  = buff_tlast;
  assign m_axis_data_tvalid = buff_tvalid &&
    ((state == ST_IN_HDR && is_data_pkt && !going_to_drop) || state == ST_IN_DATA);

  // Logic to drive triggers
  assign fc_ping = (state == ST_STRC_EXEC) && (strc_op_code == CHDR_STRC_OPCODE_PING);
  assign fc_first_resp = (state == ST_FLUSH) && (flush_counter == {FLUSH_TIMEOUT_W{1'b0}});
  assign fc_override = (state == ST_STRC_EXEC) && (strc_op_code == CHDR_STRC_OPCODE_RESYNC);
  always @(posedge clk) fc_override_del <= fc_override;

  wire [51:0] resp_o_tdata;
  wire        resp_o_tvalid;
  wire        resp_o_tready;
  reg  [51:0] resp_i_tdata;
  reg         resp_i_tvalid = 1'b0;

  // Send a stream status packet for the following cases:
  // - Immediately after initialization
  // - If a response is explicitly requested (ping)
  // - If a response is due i.e. we have exceeded the frequency
  // - If FC is resynchronized via a stream cmd
  // - If an error is detected in the stream
  always @(posedge clk) begin
    if (rst) begin
      resp_i_tvalid <= 1'b0;
      resp_i_tdata  <= 52'h0;
    end else begin
      resp_i_tvalid <= fc_first_resp || fc_ping || fc_resp_due || fc_override_del || stream_err_stb;
      resp_i_tdata  <= stream_err_stb ?
        {stream_err_info, stream_err_status} :
        {fc_resp_due, 1'b0, seq_error_occured, stop_on_seq_err_en, 12'b0,
         exp_data_seq_num, last_strc_seq_num, CHDR_STRS_STATUS_OKAY};
    end
  end

  // ---------------------------------------------------
  //  Stream Status Responder
  // ---------------------------------------------------
  localparam [2:0] ST_STRS_IDLE = 3'd0;   // Waiting for response to post
  localparam [2:0] ST_STRS_HDR  = 3'd1;   // Sending response CHDR header
  localparam [2:0] ST_STRS_W0   = 3'd2;   // Sending first response word
  localparam [2:0] ST_STRS_W1   = 3'd3;   // Sending second response word
  localparam [2:0] ST_STRS_W2   = 3'd4;   // Sending third response word
  localparam [2:0] ST_STRS_W3   = 3'd5;   // Sending fourth response word
  localparam [2:0] ST_STRS_DONE = 3'd6;   // Consuming response

  reg [2:0]  resp_state = ST_STRS_IDLE;   // State of the responder
  reg [15:0] resp_seq_num = 16'd0;        // Current sequence number of response

  assign fc_refresh = (resp_state == ST_STRS_DONE);

  assign resp_o_tready = (resp_state == ST_STRS_DONE || !fc_enabled);

  // A FIFO that holds up to 32 posted responses and status information
  // NOTE: This is a lossy FIFO. If the downstream response port is clogged
  // then we will drop responses. That should never happen in a normal operating
  // scenario.
  axi_fifo #(.WIDTH(48 + 4), .SIZE(5)) resp_fifo_i (
    .clk(clk), .reset(rst), .clear(1'b0),
    .i_tdata(resp_i_tdata), .i_tvalid(resp_i_tvalid), .i_tready(/* Lossy FIFO */),
    .o_tdata(resp_o_tdata), .o_tvalid(resp_o_tvalid), .o_tready(resp_o_tready),
    .space(), .occupied()
  );

  // Responder State Machine
  // - Wait for response to appear in FIFO
  // - Output a full packet (different # of xfers depending on CHDR_W)
  always @(posedge clk) begin
    if (rst || !fc_enabled) begin
      resp_state <= ST_STRS_IDLE;
      resp_seq_num <= 16'd0;
    end else begin
      case (resp_state)
        ST_STRS_IDLE: begin
          if (resp_o_tvalid)
            resp_state <= ST_STRS_HDR;
        end
        ST_STRS_HDR: begin
          if (m_axis_strs_tready)
            resp_state <= ST_STRS_W0;
        end
        ST_STRS_W0: begin
          if (m_axis_strs_tready)
            if (CHDR_W < 256)
              resp_state <= ST_STRS_W1;
            else
              resp_state <= ST_STRS_DONE;
        end
        ST_STRS_W1: begin
          if (m_axis_strs_tready)
            if (CHDR_W < 128)
              resp_state <= ST_STRS_W2;
            else
              resp_state <= ST_STRS_DONE;
        end
        ST_STRS_W2: begin
          if (m_axis_strs_tready)
            resp_state <= ST_STRS_W3;
        end
        ST_STRS_W3: begin
          if (m_axis_strs_tready)
            resp_state <= ST_STRS_DONE;
        end
        ST_STRS_DONE: begin
          resp_state <= ST_STRS_IDLE;
          resp_seq_num <= resp_seq_num + 16'd1;
        end
        default: begin
          // We should never get here
          resp_state <= ST_STRS_IDLE;
        end
      endcase
    end
  end

  // Output data. Header and Payload
  wire [63:0] strs_header = chdr_build_header(
    /*VC*/ 6'd0, /*eob*/ 1'b0, /*eov*/ 1'b0, CHDR_PKT_TYPE_STRS, CHDR_NO_MDATA,
    resp_seq_num, 16'd32+(CHDR_W/8), return_epid);
  wire [255:0] strs_payload = chdr256_strs_build(
    /*statusinfo*/ resp_o_tdata[51:4], buff_info,
    xfer_cnt_bytes, xfer_cnt_pkts,
    BUFF_SIZE_PKTS[23:0], BUFF_SIZE_BYTES[39:0],
    resp_o_tdata[3:0], this_epid);

  // m_axis_strs_* signal values depend on CHDR_W
  generate
    if (CHDR_W == 64) begin
      // Response spans 5 transfers (header + 4 words)
      assign m_axis_strs_tlast = (resp_state == ST_STRS_W3);
      always @(*) begin
        case (resp_state)
          ST_STRS_W0:
            m_axis_strs_tdata = strs_payload[63:0];
          ST_STRS_W1:
            m_axis_strs_tdata = strs_payload[127:64];
          ST_STRS_W2:
            m_axis_strs_tdata = strs_payload[191:128];
          ST_STRS_W3:
            m_axis_strs_tdata = strs_payload[255:192];
          default:
            m_axis_strs_tdata = strs_header;
        endcase
      end
    end else if (CHDR_W == 128) begin
      // Response spans 3 transfers (header + 2 words)
      assign m_axis_strs_tlast = (resp_state == ST_STRS_W1);
      always @(*) begin
        case (resp_state)
          ST_STRS_W0:
            m_axis_strs_tdata = strs_payload[127:0];
          ST_STRS_W1:
            m_axis_strs_tdata = strs_payload[255:128];
          default:
            m_axis_strs_tdata = {64'h0, strs_header};
        endcase
      end
    end else begin
      // Response spans 2 transfers (header + word)
      assign m_axis_strs_tlast = (resp_state == ST_STRS_W0);
      always @(*) begin
        case (resp_state)
          ST_STRS_W0:
            m_axis_strs_tdata[255:0] = strs_payload;
          default:
            m_axis_strs_tdata[255:0] = {192'h0, strs_header};
        endcase
        if (CHDR_W > 256) begin
          m_axis_strs_tdata[CHDR_W-1:256] = 'h0;
        end
      end
    end
  endgenerate

  assign m_axis_strs_tvalid = (resp_state != ST_STRS_IDLE) && (resp_state != ST_STRS_DONE);

endmodule // chdr_stream_input