//
// 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
