//
// Copyright 2018-2019 Ettus Research, A National Instruments Company
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: chdr_stream_endpoint
// Description:
//   The implementation of a stream endpoint. This module serves as
//   an endpoint for a bidirectional stream. It implement a control
//   and a data path, both of which can be individually enabled using
//   parameters. The control path contains a bidirectional CHDR to
//   AXIS-Control converter. The data path has a stream input and
//   output port.
//
// Parameters:
//   - DEVICE_FAMILY: The FPGA device family (e.g., "7SERIES" or "ULTRASCALE")
//   - PROTOVER: RFNoC protocol version {8'd<major>, 8'd<minor>}
//   - CHDR_W: Width of the CHDR bus in bits on the crossbar side (axis_chdr)
//   - BLOCK_CHDR_W: Width of the CHDR bus in bits on the block side (axis_data)
//   - INST_NUM: The instance number of this module
//   - CTRL_XBAR_PORT: The port index on the control crossbar that
//                     this module's control path will connect to
//   - AXIS_CTRL_EN: Enable control traffic (axis_ctrl port)
//   - AXIS_DATA_EN: Enable data traffic (axis_data port)
//   - NUM_DATA_I: Number of AXIS data slave ports
//   - NUM_DATA_O: Number of AXIS data master ports
//   - INGRESS_BUFF_SIZE: Buffer size in log2 of the number of words
//                        in the ingress buffer for the stream
//   - MTU: Log2 of the maximum packet size in CHDR_W words
//   - REPORT_STRM_ERRS: Report data stream errors upstream
//   - SIM_SPEEDUP: Set to 1 in simulation, and 0 otherwise
//   - THROTTLE: Enable (1) or disable (0) the throttle feature
//
// Signals:
//   - device_id     : The ID of the device that has instantiated this module
//   - *_axis_chdr_* : Input/output CHDR stream (AXI-Stream)
//   - *_axis_ctrl_* : Input/output AXIS-Control streams (AXI-Stream)
//   - *_axis_data_* : Input/output CHDR Data streams (AXI-Stream)
//   - strm_*_err_stb: The stream encountered an error
//   - signal_*_err  : Notify upstream that we encountered an error

`default_nettype none


module chdr_stream_endpoint #(
  parameter        DEVICE_FAMILY     = "7SERIES",
  parameter [15:0] PROTOVER          = {8'd1, 8'd0},
  parameter        CHDR_W            = 64,
  parameter        BLOCK_CHDR_W      = CHDR_W,
  parameter [ 9:0] INST_NUM          = 0,
  parameter [ 9:0] CTRL_XBAR_PORT    = 0,
  parameter [ 0:0] AXIS_CTRL_EN      = 1,
  parameter [ 0:0] AXIS_DATA_EN      = 1,
  parameter [ 5:0] NUM_DATA_I        = 1,
  parameter [ 5:0] NUM_DATA_O        = 1,
  parameter [ 5:0] INGRESS_BUFF_SIZE = 12,
  parameter [ 5:0] MTU               = 10,
  parameter [ 0:0] REPORT_STRM_ERRS  = 1,
  parameter [ 0:0] THROTTLE          = 1,
  parameter [ 0:0] SIM_SPEEDUP       = 0
)(
  // Clock, reset and settings
  input  wire                           rfnoc_chdr_clk,
  input  wire                           rfnoc_chdr_rst,
  input  wire                           rfnoc_ctrl_clk,
  input  wire                           rfnoc_ctrl_rst,
  // Device info
  input  wire [                   15:0] device_id,
  // Full CHDR bus (AXI-Stream crossbar connections)
  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,
  output wire [             CHDR_W-1:0] m_axis_chdr_tdata,
  output wire                           m_axis_chdr_tlast,
  output wire                           m_axis_chdr_tvalid,
  input  wire                           m_axis_chdr_tready,
  // Data-only CHDR buses (AXI-Stream RFNoC block connections)
  input  wire [BLOCK_CHDR_W*NUM_DATA_I-1:0] s_axis_data_tdata,
  input  wire [             NUM_DATA_I-1:0] s_axis_data_tlast,
  input  wire [             NUM_DATA_I-1:0] s_axis_data_tvalid,
  output wire [             NUM_DATA_I-1:0] s_axis_data_tready,
  output wire [BLOCK_CHDR_W*NUM_DATA_O-1:0] m_axis_data_tdata,
  output wire [             NUM_DATA_O-1:0] m_axis_data_tlast,
  output wire [             NUM_DATA_O-1:0] m_axis_data_tvalid,
  input  wire [             NUM_DATA_O-1:0] m_axis_data_tready,
  // Control in (AXI-Stream)
  input  wire [                   31:0] s_axis_ctrl_tdata,
  input  wire                           s_axis_ctrl_tlast,
  input  wire                           s_axis_ctrl_tvalid,
  output wire                           s_axis_ctrl_tready,
  // Control out (AXI-Stream)
  output wire [                   31:0] m_axis_ctrl_tdata,
  output wire                           m_axis_ctrl_tlast,
  output wire                           m_axis_ctrl_tvalid,
  input  wire                           m_axis_ctrl_tready,
  // Stream status specific
  output wire                           strm_seq_err_stb,
  output wire                           strm_data_err_stb,
  output wire                           strm_route_err_stb,
  input  wire                           signal_data_err
);

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

  // ---------------------------------------------------
  //  Filter packets by type
  // ---------------------------------------------------
  wire [CHDR_W-1:0] ctrl_i_tdata,  ctrl_o_tdata;
  wire              ctrl_i_tlast,  ctrl_o_tlast;
  wire              ctrl_i_tvalid, ctrl_o_tvalid;
  wire              ctrl_i_tready, ctrl_o_tready;

  wire [CHDR_W-1:0] data_i_tdata,  data_o_tdata;
  wire              data_i_tlast,  data_o_tlast;
  wire              data_i_tvalid, data_o_tvalid;
  wire              data_i_tready, data_o_tready;

  wire [CHDR_W-1:0] strs_i_tdata,  strs_o_tdata;
  wire              strs_i_tlast,  strs_o_tlast;
  wire              strs_i_tvalid, strs_o_tvalid;
  wire              strs_i_tready, strs_o_tready;

  wire [CHDR_W-1:0] mgmt_i_tdata,  mgmt_o_tdata;
  wire              mgmt_i_tlast,  mgmt_o_tlast;
  wire              mgmt_i_tvalid, mgmt_o_tvalid;
  wire              mgmt_i_tready, mgmt_o_tready;

  function [1:0] compute_demux_dest;
    input [63:0] hdr;
    if (chdr_get_pkt_type(hdr) == CHDR_PKT_TYPE_CTRL)
      // Control
      compute_demux_dest = 2'd2;
    else if (chdr_get_pkt_type(hdr) == CHDR_PKT_TYPE_STRC ||
             chdr_get_pkt_type(hdr) == CHDR_PKT_TYPE_DATA ||
             chdr_get_pkt_type(hdr) == CHDR_PKT_TYPE_DATA_TS)
      // Data and stream command
      compute_demux_dest = 2'd1;
    else if (chdr_get_pkt_type(hdr) == CHDR_PKT_TYPE_STRS)
      // Stream status
      compute_demux_dest = 2'd0;
    else
      // Management (all packets must return to sender)
      compute_demux_dest = 2'd3;
  endfunction

  // We give the demux a FIFO large enough to buffer short packets
  // Flow control will ensure that data does not back up through
  // this demux but we might have the other packet types block
  // each other.
  localparam DEMUX_FIFO_SIZE = 5;

  wire [CHDR_W-1:0] chdr_header;
  axi_demux #(
    .WIDTH(CHDR_W), .SIZE(4), .PRE_FIFO_SIZE(DEMUX_FIFO_SIZE), .POST_FIFO_SIZE(1)
  ) mgmt_demux_i (
    .clk(rfnoc_chdr_clk), .reset(rfnoc_chdr_rst), .clear(1'b0),
    .header(chdr_header), .dest(compute_demux_dest(chdr_header[63:0])),
    .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 ({mgmt_i_tdata,  ctrl_i_tdata,  data_i_tdata,  strs_i_tdata }),
    .o_tlast ({mgmt_i_tlast,  ctrl_i_tlast,  data_i_tlast,  strs_i_tlast }),
    .o_tvalid({mgmt_i_tvalid, ctrl_i_tvalid, data_i_tvalid, strs_i_tvalid}),
    .o_tready({mgmt_i_tready, ctrl_i_tready, data_i_tready, strs_i_tready})
  );

  axi_mux #(
    .WIDTH(CHDR_W), .SIZE(4), .PRIO(1), .PRE_FIFO_SIZE(0), .POST_FIFO_SIZE(1)
  ) mgmt_mux_i (
    .clk(rfnoc_chdr_clk), .reset(rfnoc_chdr_rst), .clear(1'b0),
    .i_tdata ({mgmt_o_tdata,  data_o_tdata,  strs_o_tdata,  ctrl_o_tdata }),
    .i_tlast ({mgmt_o_tlast,  data_o_tlast,  strs_o_tlast,  ctrl_o_tlast }),
    .i_tvalid({mgmt_o_tvalid, data_o_tvalid, strs_o_tvalid, ctrl_o_tvalid}),
    .i_tready({mgmt_o_tready, data_o_tready, strs_o_tready, ctrl_o_tready}),
    .o_tdata (m_axis_chdr_tdata ),
    .o_tlast (m_axis_chdr_tlast ),
    .o_tvalid(m_axis_chdr_tvalid),
    .o_tready(m_axis_chdr_tready)
  );

  // ---------------------------------------------------
  //  Management Path
  // ---------------------------------------------------
  wire              ctrlport_req_wr, ctrlport_req_rd;
  reg               ctrlport_resp_ack = 1'b0;
  wire [15:0]       ctrlport_req_addr;
  wire [31:0]       ctrlport_req_data;
  reg  [31:0]       ctrlport_resp_data;

  localparam [17:0] EXTENDED_INFO = {
    3'b0, REPORT_STRM_ERRS, NUM_DATA_O, NUM_DATA_I, AXIS_DATA_EN, AXIS_CTRL_EN};

  // Handle management packets here
  chdr_mgmt_pkt_handler #(
    .PROTOVER(PROTOVER), .CHDR_W(CHDR_W), .MGMT_ONLY(1)
  ) mgmt_ep_i (
    .clk(rfnoc_chdr_clk), .rst(rfnoc_chdr_rst),
    .node_info(chdr_mgmt_build_node_info(EXTENDED_INFO, INST_NUM, NODE_TYPE_STREAM_EP, device_id)),
    .s_axis_chdr_tdata(mgmt_i_tdata), .s_axis_chdr_tlast(mgmt_i_tlast),
    .s_axis_chdr_tvalid(mgmt_i_tvalid), .s_axis_chdr_tready(mgmt_i_tready),
    .s_axis_chdr_tuser(1'd0),
    .m_axis_chdr_tdata(mgmt_o_tdata), .m_axis_chdr_tlast(mgmt_o_tlast),
    .m_axis_chdr_tdest(/* unused */), .m_axis_chdr_tid(/* unused */),
    .m_axis_chdr_tvalid(mgmt_o_tvalid), .m_axis_chdr_tready(mgmt_o_tready),
    .ctrlport_req_wr(ctrlport_req_wr), .ctrlport_req_rd(ctrlport_req_rd),
    .ctrlport_req_addr(ctrlport_req_addr), .ctrlport_req_data(ctrlport_req_data),
    .ctrlport_resp_ack(ctrlport_resp_ack), .ctrlport_resp_data(ctrlport_resp_data),
    .op_stb(/* unused */), .op_dst_epid(/* unused */), .op_src_epid(/* unused */),
    .op_data(/* unused */)
  );

  // ============================== REGISTERS ==============================
  // * REG_EPID_SELF (Read-Write):
  //   The endpoint ID of this stream endpoint
  //   - [15:0]: Endpoint ID
  // * REG_RESET_AND_FLUSH (Write-Only):
  //   Reset and flush register
  //   - [0]: Flush data path
  //   - [1]: Flush control path
  // * REG_OSTRM_CTRL_STATUS (Read-Write):
  //   Control and status register for the output stream
  //   - [0]   : Configuration start (strobe)
  //   - [1]   : Is this transport lossy?
  //   - [3:2] : Payload SW buff (0=u64, 1=u32, 2=u16, 3=u8)
  //   - [5:4] : Metadata SW buff (0=u64, 1=u32, 2=u16, 3=u8)
  //   - [6]   : Swap endianness
  //   - [28:7]: <Unused>
  //   - [29]  : Configuration is pending (read-only)
  //   - [30]  : Configuration has failed (read-only)
  //   - [31]  : Is flow-control enabled? (read-only)
  // * REG_OSTRM_DST_EPID (Write-Only):
  //   The endpoint ID of a downstream stream endpoint
  //   - [15:0]: Endpoint ID
  // * REG_OSTRM_FC_FREQ_BYTES_LO, REG_OSTRM_FC_FREQ_BYTES_HI (Write-Only):
  //   Number of bytes between flow control status messages
  // * REG_OSTRM_FC_FREQ_PKTS (Write-Only):
  //   Number of packets between flow control status messages
  // * REG_OSTRM_FC_HEADROOM (Write-Only):
  //   Flow control headroom register
  //   - [15:0]: Bytes of headroom
  //   - [23:16]: Packets of headroom
  // * REG_OSTRM_BUFF_CAP_BYTES_LO, REG_OSTRM_BUFF_CAP_BYTES_HI (Read-Only):
  //   Number of bytes in the downstream buffer
  // * REG_OSTRM_BUFF_CAP_PKTS (Read-Only):
  //   Number of packets in the downstream buffer
  // * REG_OSTRM_SEQ_ERR_CNT (Read-Only):
  //   Number of sequence errors since initialization
  // * REG_OSTRM_DATA_ERR_CNT (Read-Only):
  //   Number of data integrity errors since initialization
  // * REG_OSTRM_ROUTE_ERR_CNT (Read-Only):
  //   Number of routing errors since initialization
  // * REG_ISTRM_CTRL_STATUS (Read-Write):
  //   Control and status register for the input stream
  //   - [0]  : Reserved
  //   - [1]  : Reserved
  //   - [3:2]: Payload SW buff (0=u64, 1=u32, 2=u16, 3=u8)
  //   - [5:4]: Metadata SW buff (0=u64, 1=u32, 2=u16, 3=u8)
  //   - [6]  : Swap endianness
  // * REG_OSTRM_THROTTLE (Write-Only):
  //   Control the maximum rate of the output stream
  // =======================================================================

  localparam [15:0] REG_EPID_SELF               = 16'h00;   //RW
  localparam [15:0] REG_RESET_AND_FLUSH         = 16'h04;   //W
  localparam [15:0] REG_OSTRM_CTRL_STATUS       = 16'h08;   //RW
  localparam [15:0] REG_OSTRM_DST_EPID          = 16'h0C;   //W
  localparam [15:0] REG_OSTRM_FC_FREQ_BYTES_LO  = 16'h10;   //W
  localparam [15:0] REG_OSTRM_FC_FREQ_BYTES_HI  = 16'h14;   //W
  localparam [15:0] REG_OSTRM_FC_FREQ_PKTS      = 16'h18;   //W
  localparam [15:0] REG_OSTRM_FC_HEADROOM       = 16'h1C;   //W
  localparam [15:0] REG_OSTRM_BUFF_CAP_BYTES_LO = 16'h20;   //R
  localparam [15:0] REG_OSTRM_BUFF_CAP_BYTES_HI = 16'h24;   //R
  localparam [15:0] REG_OSTRM_BUFF_CAP_PKTS     = 16'h28;   //R
  localparam [15:0] REG_OSTRM_SEQ_ERR_CNT       = 16'h2C;   //R
  localparam [15:0] REG_OSTRM_DATA_ERR_CNT      = 16'h30;   //R
  localparam [15:0] REG_OSTRM_ROUTE_ERR_CNT     = 16'h34;   //R
  localparam [15:0] REG_ISTRM_CTRL_STATUS       = 16'h38;   //RW
  localparam [15:0] REG_OSTRM_THROTTLE          = 16'h3C;   //W

  localparam THROTTLE_W = 16;

  // Configurable registers
  reg  [15:0] reg_epid_self = 16'h0;
  reg         reg_ctrl_reset = 1'b0;
  reg         reg_istrm_reset = 1'b0;
  reg         reg_ostrm_reset = 1'b0;
  reg         reg_ostrm_cfg_start = 1'b0;
  wire        reg_ostrm_cfg_pending;
  wire        reg_ostrm_cfg_failed;
  reg         reg_ostrm_cfg_lossy_xport = 1'b0;
  reg  [1:0]  reg_ostrm_cfg_pyld_sw_buff = 2'd0;
  reg  [1:0]  reg_ostrm_cfg_mdata_sw_buff = 2'd0;
  reg         reg_ostrm_cfg_swap_endian = 1'b0;
  reg  [15:0] reg_ostrm_dst_epid = 16'h0;
  reg  [39:0] reg_fc_freq_bytes = 40'h0;
  reg  [23:0] reg_fc_freq_pkts = 24'h0;
  reg  [15:0] reg_fc_headroom_bytes = 16'd0;
  reg  [7:0]  reg_fc_headroom_pkts = 8'd0;
  reg  [1:0]  reg_istrm_cfg_pyld_sw_buff = 2'd0;
  reg  [1:0]  reg_istrm_cfg_mdata_sw_buff = 2'd0;
  reg         reg_istrm_cfg_swap_endian = 1'b0;
  wire        reg_fc_enabled;
  wire [39:0] reg_buff_cap_bytes;
  wire [23:0] reg_buff_cap_pkts;
  wire [31:0] reg_seq_err_cnt;
  wire [31:0] reg_data_err_cnt;
  wire [31:0] reg_route_err_cnt;

  reg [THROTTLE_W-1:0] throttle = 0;

  always @(posedge rfnoc_chdr_clk) begin
    if (rfnoc_chdr_rst) begin
      ctrlport_resp_ack <= 1'b0;
    end else begin
      // All transactions finish in 1 cycle
      ctrlport_resp_ack <= ctrlport_req_wr | ctrlport_req_rd;
      // Handle register writes
      if (ctrlport_req_wr) begin
        case(ctrlport_req_addr)
          REG_EPID_SELF:
            reg_epid_self <= ctrlport_req_data[15:0];
          REG_RESET_AND_FLUSH:
            {reg_ctrl_reset, reg_istrm_reset, reg_ostrm_reset} <= ctrlport_req_data[2:0];
          REG_OSTRM_CTRL_STATUS:
            {reg_ostrm_cfg_swap_endian, reg_ostrm_cfg_mdata_sw_buff, reg_ostrm_cfg_pyld_sw_buff,
             reg_ostrm_cfg_lossy_xport, reg_ostrm_cfg_start} <= ctrlport_req_data[6:0];
          REG_OSTRM_DST_EPID:
            reg_ostrm_dst_epid <= ctrlport_req_data[15:0];
          REG_OSTRM_FC_FREQ_BYTES_LO:
            reg_fc_freq_bytes[31:0] <= ctrlport_req_data[31:0];
          REG_OSTRM_FC_FREQ_BYTES_HI:
            reg_fc_freq_bytes[39:32] <= ctrlport_req_data[7:0];
          REG_OSTRM_FC_FREQ_PKTS:
            reg_fc_freq_pkts <= ctrlport_req_data[23:0];
          REG_OSTRM_FC_HEADROOM:
            {reg_fc_headroom_pkts, reg_fc_headroom_bytes} <= ctrlport_req_data[23:0];
          REG_ISTRM_CTRL_STATUS:
            {reg_istrm_cfg_swap_endian, reg_istrm_cfg_mdata_sw_buff, reg_istrm_cfg_pyld_sw_buff}
              <= ctrlport_req_data[6:2];
          REG_OSTRM_THROTTLE: begin
            if (THROTTLE) begin
              throttle <= ctrlport_req_data[THROTTLE_W-1:0];
            end
          end
        endcase
      end else begin
        // Strobed registers
        reg_ostrm_cfg_start <= 1'b0;
        reg_ctrl_reset <= 1'b0;
        reg_ostrm_reset <= 1'b0;
        reg_istrm_reset <= 1'b0;
      end
      // Handle register reads
      if (ctrlport_req_rd) begin
        case(ctrlport_req_addr)
          REG_EPID_SELF:
            ctrlport_resp_data <= {16'h0, reg_epid_self};
          REG_OSTRM_CTRL_STATUS:
            ctrlport_resp_data <= {
              reg_fc_enabled, reg_ostrm_cfg_failed, reg_ostrm_cfg_pending, 23'h0,
              reg_ostrm_cfg_mdata_sw_buff, reg_ostrm_cfg_pyld_sw_buff,
              reg_ostrm_cfg_lossy_xport, 1'b0};
          REG_OSTRM_BUFF_CAP_BYTES_LO:
            ctrlport_resp_data <= reg_buff_cap_bytes[31:0];
          REG_OSTRM_BUFF_CAP_BYTES_HI:
            ctrlport_resp_data <= {24'h0, reg_buff_cap_bytes[39:32]};
          REG_OSTRM_BUFF_CAP_PKTS:
            ctrlport_resp_data <= {8'h0, reg_buff_cap_pkts};
          REG_OSTRM_SEQ_ERR_CNT:
            ctrlport_resp_data <= reg_seq_err_cnt;
          REG_OSTRM_DATA_ERR_CNT:
            ctrlport_resp_data <= reg_data_err_cnt;
          REG_OSTRM_ROUTE_ERR_CNT:
            ctrlport_resp_data <= reg_route_err_cnt;
          REG_ISTRM_CTRL_STATUS:
            ctrlport_resp_data <= {26'h0,
              reg_istrm_cfg_mdata_sw_buff, reg_istrm_cfg_pyld_sw_buff, 2'b0};
          default:
            ctrlport_resp_data <= 32'h0;
        endcase
      end
    end
  end

  // ---------------------------------------------------
  //  Data Path
  // ---------------------------------------------------

  wire [CHDR_W*NUM_DATA_I-1:0] rs_i_data_tdata;
  wire [       NUM_DATA_I-1:0] rs_i_data_tlast;
  wire [       NUM_DATA_I-1:0] rs_i_data_tvalid;
  wire [       NUM_DATA_I-1:0] rs_i_data_tready;
  wire [CHDR_W*NUM_DATA_O-1:0] rs_o_data_tdata;
  wire [       NUM_DATA_O-1:0] rs_o_data_tlast;
  wire [       NUM_DATA_O-1:0] rs_o_data_tvalid;
  wire [       NUM_DATA_O-1:0] rs_o_data_tready;

  genvar i;
  generate if (AXIS_DATA_EN) begin: gen_data_path
    localparam INPUT_FLUSH_TIMEOUT_W = SIM_SPEEDUP ? 6 : 14;

    // Data => CHDR
    //-------------
    wire [CHDR_W-1:0] axis_di_tdata,  axis_dis_tdata, axis_di_tdata_pre;
    wire [5:0]        axis_di_tdest;
    wire              axis_di_tlast,  axis_dis_tlast;
    wire              axis_di_tvalid, axis_dis_tvalid;
    wire              axis_di_tready, axis_dis_tready;

    // Optional MUX to combine multiple input data ports into a single one
    if (NUM_DATA_I == 6'd1) begin
      axi_fifo #(.WIDTH(CHDR_W+1), .SIZE(1)) axis_s_reg_i (
        .clk(rfnoc_chdr_clk), .reset(rfnoc_chdr_rst | reg_ostrm_reset), .clear(1'b0),
        .i_tdata({rs_i_data_tlast, rs_i_data_tdata}),
        .i_tvalid(rs_i_data_tvalid), .i_tready(rs_i_data_tready),
        .o_tdata({axis_di_tlast, axis_di_tdata_pre}),
        .o_tvalid(axis_di_tvalid), .o_tready(axis_di_tready),
        .space(), .occupied()
      );
      assign axis_di_tdest = 6'd0;
    end else begin
      wire [((CHDR_W+6)*NUM_DATA_I)-1:0] rs_i_data_tdata_tmp;
      for (i = 0; i < NUM_DATA_I; i=i+1) begin
        assign rs_i_data_tdata_tmp[(i*(CHDR_W+6))+:(CHDR_W+6)] = {i[5:0], rs_i_data_tdata[(i*CHDR_W)+:CHDR_W]};
      end

      axi_mux #(
        .WIDTH(CHDR_W+6), .SIZE(NUM_DATA_I), .PRIO(0), .PRE_FIFO_SIZE(1), .POST_FIFO_SIZE(1)
      ) axis_s_mux_i (
        .clk(rfnoc_chdr_clk), .reset(rfnoc_chdr_rst | reg_ostrm_reset), .clear(1'b0),
        .i_tdata(rs_i_data_tdata_tmp), .i_tlast(rs_i_data_tlast),
        .i_tvalid(rs_i_data_tvalid), .i_tready(rs_i_data_tready),
        .o_tdata({axis_di_tdest, axis_di_tdata_pre}), .o_tlast(axis_di_tlast),
        .o_tvalid(axis_di_tvalid), .o_tready(axis_di_tready)
      );
    end

    // Logic to correctly fill in the VC field in the CHDR header
    reg axis_di_hdr = 1'b1;
    always @(posedge rfnoc_chdr_clk) begin
      if (rfnoc_chdr_rst | reg_ostrm_reset)
        axis_di_hdr <= 1'b1;
      else if (axis_di_tvalid && axis_di_tready)
        axis_di_hdr <= axis_di_tlast;
    end
    assign axis_di_tdata[63:0] = axis_di_hdr ? chdr_set_vc(axis_di_tdata_pre[63:0], axis_di_tdest) :
                                               axis_di_tdata_pre[63:0];
    if (CHDR_W > 64) begin
      assign axis_di_tdata[CHDR_W-1:64] = axis_di_tdata_pre[CHDR_W-1:64];
    end

    // Module to swap words in the payload and metadata depending on SW settings
    chdr_data_swapper #( .CHDR_W(CHDR_W)) di_swap_i (
      .clk            (rfnoc_chdr_clk),
      .rst            (rfnoc_chdr_rst | reg_ostrm_reset),
      .payload_sw_buff(reg_ostrm_cfg_pyld_sw_buff),
      .mdata_sw_buff  (reg_ostrm_cfg_mdata_sw_buff),
      .swap_endianness(reg_ostrm_cfg_swap_endian),
      .s_axis_tdata   (axis_di_tdata),
      .s_axis_tlast   (axis_di_tlast),
      .s_axis_tvalid  (axis_di_tvalid),
      .s_axis_tready  (axis_di_tready),
      .m_axis_tdata   (axis_dis_tdata),
      .m_axis_tlast   (axis_dis_tlast),
      .m_axis_tvalid  (axis_dis_tvalid),
      .m_axis_tready  (axis_dis_tready)
    );

    wire [CHDR_W-1:0] stream_o_tdata;
    wire              stream_o_tlast;
    wire              stream_o_tvalid;
    wire              stream_o_tready;

    // Stream endpoint flow-control output module
    chdr_stream_output #(
      .CHDR_W(CHDR_W), .MTU(MTU)
    ) strm_output_i (
      .clk                  (rfnoc_chdr_clk),
      .rst                  (rfnoc_chdr_rst | reg_ostrm_reset),
      .m_axis_chdr_tdata    (stream_o_tdata),
      .m_axis_chdr_tlast    (stream_o_tlast),
      .m_axis_chdr_tvalid   (stream_o_tvalid),
      .m_axis_chdr_tready   (stream_o_tready),
      .s_axis_data_tdata    (axis_dis_tdata),
      .s_axis_data_tlast    (axis_dis_tlast),
      .s_axis_data_tvalid   (axis_dis_tvalid),
      .s_axis_data_tready   (axis_dis_tready),
      .s_axis_strs_tdata    (strs_i_tdata),
      .s_axis_strs_tlast    (strs_i_tlast),
      .s_axis_strs_tvalid   (strs_i_tvalid),
      .s_axis_strs_tready   (strs_i_tready),
      .cfg_start            (reg_ostrm_cfg_start),
      .cfg_pending          (reg_ostrm_cfg_pending),
      .cfg_failed           (reg_ostrm_cfg_failed),
      .cfg_lossy_xport      (reg_ostrm_cfg_lossy_xport),
      .cfg_dst_epid         (reg_ostrm_dst_epid),
      .cfg_this_epid        (reg_epid_self),
      .cfg_fc_freq_bytes    (reg_fc_freq_bytes),
      .cfg_fc_freq_pkts     (reg_fc_freq_pkts),
      .cfg_fc_headroom_bytes(reg_fc_headroom_bytes),
      .cfg_fc_headroom_pkts (reg_fc_headroom_pkts),
      .fc_enabled           (reg_fc_enabled),
      .capacity_bytes       (reg_buff_cap_bytes),
      .capacity_pkts        (reg_buff_cap_pkts),
      .seq_err_stb          (strm_seq_err_stb),
      .seq_err_cnt          (reg_seq_err_cnt),
      .data_err_stb         (strm_data_err_stb),
      .data_err_cnt         (reg_data_err_cnt),
      .route_err_stb        (strm_route_err_stb),
      .route_err_cnt        (reg_route_err_cnt)
    );

    if (THROTTLE) begin : gen_throttle
      // Output stream throttle control
      axis_pkt_throttle #(
        .THROTTLE_W(THROTTLE_W),
        .DATA_W    (CHDR_W    ),
        .MTU       (MTU       )
      ) axis_pkt_throttle_i (
        .clk     (rfnoc_chdr_clk                  ),
        .rst     (rfnoc_chdr_rst | reg_ostrm_reset),
        .throttle(throttle                        ),
        .i_tdata (stream_o_tdata                  ),
        .i_tlast (stream_o_tlast                  ),
        .i_tvalid(stream_o_tvalid                 ),
        .i_tready(stream_o_tready                 ),
        .o_tdata (data_o_tdata                    ),
        .o_tlast (data_o_tlast                    ),
        .o_tvalid(data_o_tvalid                   ),
        .o_tready(data_o_tready                   )
      );
    end else begin : gen_no_throttle
      assign data_o_tdata    = stream_o_tdata;
      assign data_o_tlast    = stream_o_tlast;
      assign data_o_tvalid   = stream_o_tvalid;
      assign stream_o_tready = data_o_tready;
    end

    // CHDR => Data
    //-------------
    wire [CHDR_W-1:0] axis_do_tdata,  axis_dos_tdata;
    wire              axis_do_tlast,  axis_dos_tlast;
    wire              axis_do_tvalid, axis_dos_tvalid;
    wire              axis_do_tready, axis_dos_tready;

    // Stream endpoint flow-control input module
    chdr_stream_input #(
      .DEVICE_FAMILY(DEVICE_FAMILY),
      .CHDR_W(CHDR_W), .BUFF_SIZE(INGRESS_BUFF_SIZE),
      .FLUSH_TIMEOUT_W(INPUT_FLUSH_TIMEOUT_W),
      .MONITOR_EN(0), .SIGNAL_ERRS(REPORT_STRM_ERRS)
    ) strm_input_i (
      .clk               (rfnoc_chdr_clk),
      .rst               (rfnoc_chdr_rst | reg_istrm_reset),
      .s_axis_chdr_tdata (data_i_tdata),
      .s_axis_chdr_tlast (data_i_tlast),
      .s_axis_chdr_tvalid(data_i_tvalid),
      .s_axis_chdr_tready(data_i_tready),
      .m_axis_data_tdata (axis_do_tdata),
      .m_axis_data_tlast (axis_do_tlast),
      .m_axis_data_tvalid(axis_do_tvalid),
      .m_axis_data_tready(axis_do_tready),
      .m_axis_strs_tdata (strs_o_tdata),
      .m_axis_strs_tlast (strs_o_tlast),
      .m_axis_strs_tvalid(strs_o_tvalid),
      .m_axis_strs_tready(strs_o_tready),
      .data_err_stb      (signal_data_err)
    );

    // Module to swap words in the payload and metadata depending on SW settings
    chdr_data_swapper #( .CHDR_W(CHDR_W)) do_swap_i (
      .clk            (rfnoc_chdr_clk),
      .rst            (rfnoc_chdr_rst | reg_istrm_reset),
      .payload_sw_buff(reg_istrm_cfg_pyld_sw_buff),
      .mdata_sw_buff  (reg_istrm_cfg_mdata_sw_buff),
      .swap_endianness(reg_istrm_cfg_swap_endian),
      .s_axis_tdata   (axis_do_tdata),
      .s_axis_tlast   (axis_do_tlast),
      .s_axis_tvalid  (axis_do_tvalid),
      .s_axis_tready  (axis_do_tready),
      .m_axis_tdata   (axis_dos_tdata),
      .m_axis_tlast   (axis_dos_tlast),
      .m_axis_tvalid  (axis_dos_tvalid),
      .m_axis_tready  (axis_dos_tready)
    );

    // Optional DEMUX to split multiple single stream into multiple outputs
    // Packets with an invalid (out of bounds) VC goes to port 0
    if (NUM_DATA_O == 6'd1) begin
      axi_fifo #(.WIDTH(CHDR_W+1), .SIZE(1)) axis_m_reg_i (
        .clk(rfnoc_chdr_clk), .reset(rfnoc_chdr_rst | reg_istrm_reset), .clear(1'b0),
        .i_tdata({axis_dos_tlast, axis_dos_tdata}),
        .i_tvalid(axis_dos_tvalid), .i_tready(axis_dos_tready),
        .o_tdata({rs_o_data_tlast, rs_o_data_tdata}),
        .o_tvalid(rs_o_data_tvalid), .o_tready(rs_o_data_tready),
        .space(), .occupied()
      );
    end else begin
      wire [CHDR_W-1:0] data_header;
      wire [5:0]        data_vc = chdr_get_vc(data_header[63:0]);
      axi_demux #(
        .WIDTH(CHDR_W), .SIZE(NUM_DATA_O), .PRE_FIFO_SIZE(1), .POST_FIFO_SIZE(1)
      ) axis_m_demux_i (
        .clk(rfnoc_chdr_clk), .reset(rfnoc_chdr_rst | reg_istrm_reset), .clear(1'b0),
        .header(data_header),
        .dest((data_vc < NUM_DATA_O) ? data_vc[$clog2(NUM_DATA_O)-1:0] : {$clog2(NUM_DATA_O){1'b0}}),
        .i_tdata(axis_dos_tdata), .i_tlast(axis_dos_tlast),
        .i_tvalid(axis_dos_tvalid), .i_tready(axis_dos_tready),
        .o_tdata(rs_o_data_tdata), .o_tlast(rs_o_data_tlast),
        .o_tvalid(rs_o_data_tvalid), .o_tready(rs_o_data_tready)
      );
    end

    // -------------
    //  CHDR Resize
    // -------------

    // We put resize on the block side, rather than the crossbar side, to
    // minimize the impact on streaming performance.

    if (CHDR_W != BLOCK_CHDR_W) begin : gen_chdr_resize
      for (i = 0; i < NUM_DATA_O; i=i+1) begin : gen_outputs
        chdr_resize #(
          .I_CHDR_W(CHDR_W      ),
          .O_CHDR_W(BLOCK_CHDR_W),
          .USER_W  (1           ),
          .PIPELINE("OUT"       )
        ) chdr_resize_out (
          .clk          (rfnoc_chdr_clk                                    ),
          .rst          (rfnoc_chdr_rst                                    ),
          .i_chdr_tdata (rs_o_data_tdata   [i*CHDR_W +: CHDR_W]            ),
          .i_chdr_tuser (1'b1                                              ),
          .i_chdr_tlast (rs_o_data_tlast   [i]                             ),
          .i_chdr_tvalid(rs_o_data_tvalid  [i]                             ),
          .i_chdr_tready(rs_o_data_tready  [i]                             ),
          .o_chdr_tdata (m_axis_data_tdata [i*BLOCK_CHDR_W +: BLOCK_CHDR_W]),
          .o_chdr_tuser (                                                  ),
          .o_chdr_tlast (m_axis_data_tlast [i]                             ),
          .o_chdr_tvalid(m_axis_data_tvalid[i]                             ),
          .o_chdr_tready(m_axis_data_tready[i]                             )
        );
      end

      for (i = 0; i < NUM_DATA_I; i=i+1) begin: gen_inputs
        chdr_resize #(
          .I_CHDR_W(BLOCK_CHDR_W),
          .O_CHDR_W(CHDR_W      ),
          .USER_W  (1           ),
          .PIPELINE("IN"        )
        ) chdr_resize_in (
          .clk          (rfnoc_chdr_clk                                    ),
          .rst          (rfnoc_chdr_rst                                    ),
          .i_chdr_tdata (s_axis_data_tdata [i*BLOCK_CHDR_W +: BLOCK_CHDR_W]),
          .i_chdr_tuser (1'b1                                              ),
          .i_chdr_tlast (s_axis_data_tlast [i]                             ),
          .i_chdr_tvalid(s_axis_data_tvalid[i]                             ),
          .i_chdr_tready(s_axis_data_tready[i]                             ),
          .o_chdr_tdata (rs_i_data_tdata   [i*CHDR_W +: CHDR_W]            ),
          .o_chdr_tuser (                                                  ),
          .o_chdr_tlast (rs_i_data_tlast   [i]                             ),
          .o_chdr_tvalid(rs_i_data_tvalid  [i]                             ),
          .o_chdr_tready(rs_i_data_tready  [i]                             )
        );
      end

    end else begin : gen_no_chdr_resize

      assign m_axis_data_tdata  = rs_o_data_tdata;
      assign m_axis_data_tlast  = rs_o_data_tlast;
      assign m_axis_data_tvalid = rs_o_data_tvalid;
      assign rs_o_data_tready   = m_axis_data_tready;

      assign rs_i_data_tdata    = s_axis_data_tdata;
      assign rs_i_data_tlast    = s_axis_data_tlast;
      assign rs_i_data_tvalid   = s_axis_data_tvalid;
      assign s_axis_data_tready = rs_i_data_tready;

    end

  end else begin : gen_no_data_path

    assign data_i_tready = 1'b1;
    assign data_o_tdata  = {CHDR_W{1'b0}};
    assign data_o_tlast  = 1'b0;
    assign data_o_tvalid = 1'b0;

    assign strs_i_tready = 1'b1;
    assign strs_o_tdata  = {CHDR_W{1'b0}};
    assign strs_o_tlast  = 1'b0;
    assign strs_o_tvalid = 1'b0;

    assign s_axis_data_tready = {NUM_DATA_I{1'b1}};
    assign m_axis_data_tdata  = {(CHDR_W*NUM_DATA_O){1'b0}};
    assign m_axis_data_tlast  = {NUM_DATA_O{1'b0}};
    assign m_axis_data_tvalid = {NUM_DATA_O{1'b0}};

  end endgenerate

  // ---------------------------------------------------
  //  Control Path
  // ---------------------------------------------------

  generate if (AXIS_CTRL_EN) begin: gen_ctrlpath

    // Convert from a CHDR control packet to an AXIS control packet
    chdr_to_axis_ctrl #(
      .CHDR_W(CHDR_W), .THIS_PORTID(CTRL_XBAR_PORT)
    ) chdr_ctrl_adapter_i (
      .rfnoc_chdr_clk     (rfnoc_chdr_clk),
      .rfnoc_chdr_rst     (rfnoc_chdr_rst | reg_ctrl_reset),
      .this_epid          (reg_epid_self),
      .s_rfnoc_chdr_tdata (ctrl_i_tdata),
      .s_rfnoc_chdr_tlast (ctrl_i_tlast),
      .s_rfnoc_chdr_tvalid(ctrl_i_tvalid),
      .s_rfnoc_chdr_tready(ctrl_i_tready),
      .m_rfnoc_chdr_tdata (ctrl_o_tdata),
      .m_rfnoc_chdr_tlast (ctrl_o_tlast),
      .m_rfnoc_chdr_tvalid(ctrl_o_tvalid),
      .m_rfnoc_chdr_tready(ctrl_o_tready),
      .rfnoc_ctrl_clk     (rfnoc_ctrl_clk),
      .rfnoc_ctrl_rst     (rfnoc_ctrl_rst),
      .s_rfnoc_ctrl_tdata (s_axis_ctrl_tdata),
      .s_rfnoc_ctrl_tlast (s_axis_ctrl_tlast),
      .s_rfnoc_ctrl_tvalid(s_axis_ctrl_tvalid),
      .s_rfnoc_ctrl_tready(s_axis_ctrl_tready),
      .m_rfnoc_ctrl_tdata (m_axis_ctrl_tdata),
      .m_rfnoc_ctrl_tlast (m_axis_ctrl_tlast),
      .m_rfnoc_ctrl_tvalid(m_axis_ctrl_tvalid),
      .m_rfnoc_ctrl_tready(m_axis_ctrl_tready)
    );

  end else begin : gen_no_ctrl_path

    assign ctrl_i_tready = 1'b1;
    assign ctrl_o_tdata  = {CHDR_W{1'b0}};
    assign ctrl_o_tlast  = 1'b0;
    assign ctrl_o_tvalid = 1'b0;

    assign s_axis_ctrl_tready = 1'b1;
    assign m_axis_ctrl_tdata  = 32'h0;
    assign m_axis_ctrl_tlast  = 1'b0;
    assign m_axis_ctrl_tvalid = 1'b0;

  end endgenerate

endmodule // chdr_stream_endpoint


`default_nettype wire
