// SPDX-License-Identifier: CERN-OHL-S-2.0
/*

Copyright (c) 2017-2025 FPGA Ninja, LLC

Authors:
- Alex Forencich

*/

`timescale 1ns / 1ps

/*
 * XFCP 1xN switch
 */
module taxi_xfcp_switch #
(
    parameter PORTS = 4,
    parameter logic [15:0] XFCP_ID_TYPE = 16'h0100,
    parameter XFCP_ID_STR = "XFCP Switch",
    parameter logic [8*16-1:0] XFCP_EXT_ID = 0,
    parameter XFCP_EXT_ID_STR = ""
)
(
    input  wire logic  clk,
    input  wire logic  rst,

    /*
     * XFCP upstream port
     */
    taxi_axis_if.snk   xfcp_usp_ds,
    taxi_axis_if.src   xfcp_usp_us,

    /*
     * XFCP downstream ports
     */
    taxi_axis_if.src   xfcp_dsp_ds[PORTS],
    taxi_axis_if.snk   xfcp_dsp_us[PORTS]
);

localparam CL_PORTS = PORTS > 1 ? $clog2(PORTS) : 1;
localparam CL_PORTS_P1 = $clog2(PORTS+1);

// check configuration
if (PORTS < 1 || PORTS > 256)
    $fatal(0, "Error: PORTS out of range; must be between 1 and 256");

localparam START_TAG = 8'hFF;
localparam RPATH_TAG = 8'hFE;
localparam ID_REQ = 8'hFE;
localparam ID_RESP = 8'hFF;

// ID ROM
localparam ID_PTR_W = (XFCP_EXT_ID != 0 || XFCP_EXT_ID_STR != 0) ? 6 : 5;
localparam ID_ROM_SIZE = 2**ID_PTR_W;
reg [7:0] id_rom[ID_ROM_SIZE];

reg [ID_PTR_W-1:0] id_ptr_reg = '0, id_ptr_next;

integer j;

initial begin
    // init ID ROM
    for (integer i = 0; i < ID_ROM_SIZE; i = i + 1) begin
        id_rom[i] = 0;
    end

    // binary part
    {id_rom[1], id_rom[0]} = 16'h0100 | (XFCP_ID_TYPE & 16'h00FF); // module type (switch)
    id_rom[2] = 8'd1; // upstream port count
    id_rom[3] = 8'(PORTS); // downstream port count

    // string part
    // find string length
    j = 0;
    for (integer i = 1; i <= 16; i = i + 1) begin
        if (j == i-1 && (XFCP_ID_STR >> (i*8)) > 0) begin
            j = i;
        end
    end

    // pack string
    for (integer i = 0; i <= j; i = i + 1) begin
        id_rom[i+16] = XFCP_ID_STR[8*(j-i) +: 8];
    end

    if (XFCP_EXT_ID != 0 || XFCP_EXT_ID_STR != 0) begin
        // extended ID

        // binary part
        for (integer i = 0; i < 16; i = i + 1) begin
            id_rom[i+32] = XFCP_EXT_ID[8*i +: 8];
        end

        // string part
        // find string length
        j = 0;
        for (integer i = 1; i <= 16; i = i + 1) begin
            if (j == i-1 && (XFCP_EXT_ID_STR >> (i*8)) > 0) begin
                j = i;
            end
        end

        // pack string
        for (integer i = 0; i <= j; i = i + 1) begin
            id_rom[i+48] = XFCP_EXT_ID_STR[8*(j-i) +: 8];
        end
    end
end

localparam [2:0]
    DN_STATE_IDLE = 3'd0,
    DN_STATE_TRANSFER = 3'd1,
    DN_STATE_HEADER = 3'd2,
    DN_STATE_PKT = 3'd3,
    DN_STATE_ID = 3'd4;

reg [2:0] dn_state_reg = DN_STATE_IDLE, dn_state_next;

localparam [0:0]
    UP_STATE_IDLE = 1'd0,
    UP_STATE_TRANSFER = 1'd1;

reg [0:0] up_state_reg = UP_STATE_IDLE, up_state_next;

reg [CL_PORTS-1:0] dn_select_reg = '0, dn_select_next;
reg dn_frame_reg = 1'b0, dn_frame_next;
reg dn_enable_reg = 1'b0, dn_enable_next;

reg [CL_PORTS_P1-1:0] up_select_reg = '0, up_select_next;
reg up_frame_reg = 1'b0, up_frame_next;

reg xfcp_usp_ds_tready_reg = 1'b0, xfcp_usp_ds_tready_next;

reg [PORTS-1:0] xfcp_dsp_us_tready_reg = '0, xfcp_dsp_us_tready_next;

wire [PORTS-1:0] xfcp_dsp_ds_tready;
wire [PORTS-1:0] xfcp_dsp_ds_tvalid;

// internal datapath
reg [7:0] xfcp_usp_us_tdata_int;
reg       xfcp_usp_us_tvalid_int;
reg       xfcp_usp_us_tready_int_reg = 1'b0;
reg       xfcp_usp_us_tlast_int;
reg       xfcp_usp_us_tuser_int;
wire      xfcp_usp_us_tready_int_early;

reg [7:0]       xfcp_dsp_ds_tdata_int;
reg [PORTS-1:0] xfcp_dsp_ds_tvalid_int;
reg             xfcp_dsp_ds_tready_int_reg = 1'b0;
reg             xfcp_dsp_ds_tlast_int;
reg             xfcp_dsp_ds_tuser_int;
wire            xfcp_dsp_ds_tready_int_early;

reg [7:0] int_loop_tdata_reg = 8'd0, int_loop_tdata_next;
reg       int_loop_tvalid_reg = 1'b0, int_loop_tvalid_next;
reg       int_loop_tready;
reg       int_loop_tready_early;
reg       int_loop_tlast_reg = 1'b0, int_loop_tlast_next;
reg       int_loop_tuser_reg = 1'b0, int_loop_tuser_next;

assign xfcp_usp_ds.tready = xfcp_usp_ds_tready_reg;

// unpack interface array
wire [PORTS+1-1:0]  xfcp_dsp_us_tready;
wire [7:0]          xfcp_dsp_us_tdata[PORTS+1];
wire [PORTS+1-1:0]  xfcp_dsp_us_tvalid;
wire [PORTS+1-1:0]  xfcp_dsp_us_tlast;
wire                xfcp_dsp_us_tuser[PORTS+1];

for (genvar n = 0; n < PORTS; n = n + 1) begin
    assign xfcp_dsp_us_tdata[n] = xfcp_dsp_us[n].tdata;
    assign xfcp_dsp_us_tvalid[n] = xfcp_dsp_us[n].tvalid;
    assign xfcp_dsp_us[n].tready = xfcp_dsp_us_tready_reg[n];
    assign xfcp_dsp_us_tlast[n] = xfcp_dsp_us[n].tlast;
    assign xfcp_dsp_us_tuser[n] = xfcp_dsp_us[n].tuser;

    assign xfcp_dsp_us_tready[n] = xfcp_dsp_us[n].tready;
end

assign xfcp_dsp_us_tdata[PORTS] = int_loop_tdata_reg;
assign xfcp_dsp_us_tvalid[PORTS] = int_loop_tvalid_reg;
assign xfcp_dsp_us_tlast[PORTS] = int_loop_tlast_reg;
assign xfcp_dsp_us_tuser[PORTS] = int_loop_tuser_reg;

assign xfcp_dsp_us_tready[PORTS] = int_loop_tready;

// mux for downstream output control signals
wire current_output_tvalid = xfcp_dsp_ds_tvalid[dn_select_reg];
wire current_output_tready = xfcp_dsp_ds_tready[dn_select_reg];

// mux for incoming downstream packet
wire [7:0] current_input_tdata  = xfcp_dsp_us_tdata[up_select_reg];
wire       current_input_tvalid = xfcp_dsp_us_tvalid[up_select_reg];
wire       current_input_tready = xfcp_dsp_us_tready[up_select_reg];
wire       current_input_tlast  = xfcp_dsp_us_tlast[up_select_reg];
wire       current_input_tuser  = xfcp_dsp_us_tuser[up_select_reg];

// downstream control logic
always_comb begin
    dn_state_next = DN_STATE_IDLE;

    dn_select_next = dn_select_reg;
    dn_frame_next = dn_frame_reg;
    dn_enable_next = dn_enable_reg;

    id_ptr_next = id_ptr_reg;

    xfcp_usp_ds_tready_next = 1'b0;

    xfcp_dsp_ds_tdata_int = xfcp_usp_ds.tdata;
    xfcp_dsp_ds_tvalid_int = PORTS'(xfcp_usp_ds.tvalid && xfcp_usp_ds.tready && dn_enable_reg) << dn_select_reg;
    xfcp_dsp_ds_tlast_int = xfcp_usp_ds.tlast;
    xfcp_dsp_ds_tuser_int = xfcp_usp_ds.tuser;

    int_loop_tdata_next = int_loop_tdata_reg;
    int_loop_tvalid_next = int_loop_tvalid_reg && !int_loop_tready;
    int_loop_tlast_next = int_loop_tlast_reg;
    int_loop_tuser_next = int_loop_tuser_reg;

    if (xfcp_usp_ds.tready & xfcp_usp_ds.tvalid) begin
        // end of frame detection
        if (xfcp_usp_ds.tlast) begin
            dn_frame_next = 1'b0;
            dn_enable_next = 1'b0;
        end
    end

    case (dn_state_reg)
        DN_STATE_IDLE: begin
            // wait for incoming upstream packet
            xfcp_usp_ds_tready_next = 1'b1;
            id_ptr_next = '0;

            if (!dn_frame_reg && xfcp_usp_ds.tready && xfcp_usp_ds.tvalid) begin
                // start of frame
                dn_frame_next = 1'b1;
                if (xfcp_usp_ds.tdata == RPATH_TAG || xfcp_usp_ds.tdata == START_TAG) begin
                    // packet for us

                    int_loop_tdata_next = xfcp_usp_ds.tdata;
                    int_loop_tvalid_next = xfcp_usp_ds.tvalid;
                    int_loop_tlast_next = xfcp_usp_ds.tlast;
                    int_loop_tuser_next = xfcp_usp_ds.tuser;

                    xfcp_usp_ds_tready_next = int_loop_tready_early;

                    if (xfcp_usp_ds.tdata == RPATH_TAG) begin
                        // has rpath
                        dn_state_next = DN_STATE_HEADER;
                    end else begin
                        // no rpath
                        dn_state_next = DN_STATE_PKT;
                    end
                end else begin
                    // route packet
                    dn_enable_next = 1'b1;
                    dn_select_next = CL_PORTS'(xfcp_usp_ds.tdata);
                    xfcp_usp_ds_tready_next = xfcp_dsp_ds_tready_int_early;
                    dn_state_next = DN_STATE_TRANSFER;
                    if (xfcp_usp_ds.tdata >= 8'(PORTS)) begin
                        // out of range
                        dn_enable_next = 1'b0;
                    end
                end
            end else begin
                dn_state_next = DN_STATE_IDLE;
            end
        end
        DN_STATE_TRANSFER: begin
            // transfer upstream packet through proper downstream port
            if (xfcp_usp_ds.tready && xfcp_usp_ds.tvalid) begin
                // end of frame detection
                if (xfcp_usp_ds.tlast) begin
                    dn_frame_next = 1'b0;
                    dn_enable_next = 1'b0;
                    dn_state_next = DN_STATE_IDLE;
                end else begin
                    dn_state_next = DN_STATE_TRANSFER;
                end
            end else begin
                dn_state_next = DN_STATE_TRANSFER;
            end
            xfcp_usp_ds_tready_next = xfcp_dsp_ds_tready_int_early && dn_frame_next;
        end
        DN_STATE_HEADER: begin
            // loop back header

            xfcp_usp_ds_tready_next = int_loop_tready_early;

            if (xfcp_usp_ds.tready && xfcp_usp_ds.tvalid) begin
                int_loop_tdata_next = xfcp_usp_ds.tdata;
                int_loop_tvalid_next = 1'b1;
                int_loop_tlast_next = xfcp_usp_ds.tlast;
                int_loop_tuser_next = xfcp_usp_ds.tuser;

                // end of header detection
                if (xfcp_usp_ds.tdata == START_TAG) begin
                    dn_state_next = DN_STATE_PKT;
                end else begin
                    dn_state_next = DN_STATE_HEADER;
                end
            end else begin
                dn_state_next = DN_STATE_HEADER;
            end
        end
        DN_STATE_PKT: begin
            // packet type

            xfcp_usp_ds_tready_next = int_loop_tready_early;

            if (xfcp_usp_ds.tready && xfcp_usp_ds.tvalid) begin
                int_loop_tdata_next = xfcp_usp_ds.tdata;
                int_loop_tvalid_next = 1'b1;
                int_loop_tlast_next = xfcp_usp_ds.tlast;
                int_loop_tuser_next = xfcp_usp_ds.tuser;

                if (xfcp_usp_ds.tdata == ID_REQ) begin
                    // ID packet
                    int_loop_tdata_next = ID_RESP;
                    int_loop_tlast_next = 1'b0;
                    dn_state_next = DN_STATE_ID;
                end else begin
                    // something else
                    int_loop_tlast_next = 1'b1;
                    int_loop_tuser_next = 1'b1;
                    dn_state_next = DN_STATE_IDLE;
                end
            end else begin
                dn_state_next = DN_STATE_PKT;
            end
        end
        DN_STATE_ID: begin
            // send ID

            xfcp_usp_ds_tready_next = dn_frame_next;

            if (int_loop_tready) begin
                int_loop_tdata_next = id_rom[id_ptr_reg];
                int_loop_tvalid_next = 1'b1;
                int_loop_tlast_next = 1'b0;
                int_loop_tuser_next = 1'b0;

                id_ptr_next = id_ptr_reg + 1;
                if (id_ptr_reg == ID_ROM_SIZE-1) begin
                    int_loop_tlast_next = 1'b1;
                    dn_state_next = DN_STATE_IDLE;
                end else begin
                    dn_state_next = DN_STATE_ID;
                end
            end else begin
                dn_state_next = DN_STATE_ID;
            end
        end
        default: begin
            dn_state_next = DN_STATE_IDLE;
        end
    endcase
end

// upstream control logic
wire [PORTS+1-1:0] req;
wire [PORTS+1-1:0] ack;
wire [PORTS+1-1:0] grant;
wire grant_valid;
wire [CL_PORTS_P1-1:0] grant_index;

// arbiter instance
taxi_arbiter #(
    .PORTS(PORTS+1),
    .ARB_ROUND_ROBIN(1),
    .ARB_BLOCK(1),
    .ARB_BLOCK_ACK(1),
    .LSB_HIGH_PRIO(1)
)
arb_inst (
    .clk(clk),
    .rst(rst),
    .req(req),
    .ack(ack),
    .grant(grant),
    .grant_valid(grant_valid),
    .grant_index(grant_index)
);

assign req = xfcp_dsp_us_tvalid & ~grant;
assign ack = grant & xfcp_dsp_us_tvalid & xfcp_dsp_us_tready & xfcp_dsp_us_tlast;

always_comb begin
    up_state_next = UP_STATE_IDLE;

    up_select_next = up_select_reg;
    up_frame_next = up_frame_reg;


    xfcp_usp_us_tdata_int = current_input_tdata;
    xfcp_usp_us_tvalid_int = current_input_tvalid && current_input_tready && up_frame_reg;
    xfcp_usp_us_tlast_int = current_input_tlast;
    xfcp_usp_us_tuser_int = current_input_tuser;

    if (current_input_tready && current_input_tvalid) begin
        if (current_input_tlast) begin
            // end of frame detection
            up_frame_next = 1'b0;
        end
    end

    case (up_state_reg)
        UP_STATE_IDLE: begin
            // wait for incoming downstream packet
            if (grant_valid && xfcp_usp_us_tready_int_reg) begin
                up_frame_next = 1'b1;
                up_select_next = grant_index;
                up_state_next = UP_STATE_TRANSFER;

                if (up_select_next == CL_PORTS_P1'(PORTS)) begin
                    // internal loop; don't add port
                end else begin
                    // prepend port to packet
                    xfcp_usp_us_tdata_int = 8'(grant_index);
                    xfcp_usp_us_tvalid_int = 1'b1;
                    xfcp_usp_us_tlast_int = 1'b0;
                    xfcp_usp_us_tuser_int = 1'b0;
                end
            end else begin
                up_state_next = UP_STATE_IDLE;
            end
        end
        UP_STATE_TRANSFER: begin
            // transfer downstream packet out through upstream port
            if (current_input_tvalid && current_input_tready) begin
                if (current_input_tlast) begin
                    up_frame_next = 1'b0;
                    up_state_next = UP_STATE_IDLE;
                end else begin
                    up_state_next = UP_STATE_TRANSFER;
                end
            end else begin
                up_state_next = UP_STATE_TRANSFER;
            end
        end
    endcase
end

always_comb begin
    xfcp_dsp_us_tready_next = '0;

    // int_loop_tready_early = 1'b0;

    // generate ready signal on selected port
    if (up_select_next == CL_PORTS_P1'(PORTS)) begin
        // int_loop_tready_early = xfcp_usp_us_tready_int_early && up_frame_next;
    end else begin
        xfcp_dsp_us_tready_next = PORTS'(xfcp_usp_us_tready_int_early && up_frame_next) << up_select_next;
    end
end

always_comb begin
    int_loop_tready_early = xfcp_usp_us_tready_int_early && up_frame_next;
end

always_comb begin
    int_loop_tready = xfcp_usp_us_tready_int_reg && up_frame_reg;
end

always_ff @(posedge clk) begin
    dn_state_reg <= dn_state_next;
    up_state_reg <= up_state_next;

    id_ptr_reg <= id_ptr_next;

    dn_select_reg <= dn_select_next;
    dn_frame_reg <= dn_frame_next;
    dn_enable_reg <= dn_enable_next;

    up_select_reg <= up_select_next;
    up_frame_reg <= up_frame_next;

    xfcp_usp_ds_tready_reg <= xfcp_usp_ds_tready_next;
    xfcp_dsp_us_tready_reg <= xfcp_dsp_us_tready_next;

    int_loop_tdata_reg <= int_loop_tdata_next;
    int_loop_tvalid_reg <= int_loop_tvalid_next;
    int_loop_tlast_reg <= int_loop_tlast_next;
    int_loop_tuser_reg <= int_loop_tuser_next;

    if (rst) begin
        dn_state_reg <= DN_STATE_IDLE;
        up_state_reg <= UP_STATE_IDLE;
        dn_select_reg <= '0;
        dn_frame_reg <= 1'b0;
        dn_enable_reg <= 1'b0;
        up_select_reg <= '0;
        up_frame_reg <= 1'b0;
        xfcp_usp_ds_tready_reg <= 1'b0;
        xfcp_dsp_us_tready_reg <= '0;
        int_loop_tvalid_reg <= 1'b0;
    end
end

// upstream output datapath logic
reg [7:0]  xfcp_usp_us_tdata_reg = 8'd0;
reg        xfcp_usp_us_tvalid_reg = 1'b0, xfcp_usp_us_tvalid_next;
reg        xfcp_usp_us_tlast_reg = 1'b0;
reg        xfcp_usp_us_tuser_reg = 1'b0;

reg [7:0]  temp_xfcp_usp_us_tdata_reg = 8'd0;
reg        temp_xfcp_usp_us_tvalid_reg = 1'b0, temp_xfcp_usp_us_tvalid_next;
reg        temp_xfcp_usp_us_tlast_reg = 1'b0;
reg        temp_xfcp_usp_us_tuser_reg = 1'b0;

// datapath control
reg store_xfcp_usp_us_int_to_output;
reg store_xfcp_usp_us_int_to_temp;
reg store_xfcp_usp_us_temp_to_output;

assign xfcp_usp_us.tdata = xfcp_usp_us_tdata_reg;
assign xfcp_usp_us.tkeep = '1;
assign xfcp_usp_us.tstrb = xfcp_usp_us.tkeep;
assign xfcp_usp_us.tvalid = xfcp_usp_us_tvalid_reg;
assign xfcp_usp_us.tlast = xfcp_usp_us_tlast_reg;
assign xfcp_usp_us.tid = '0;
assign xfcp_usp_us.tdest = '0;
assign xfcp_usp_us.tuser = xfcp_usp_us_tuser_reg;

// enable ready input next cycle if output is ready or the temp reg will not be filled on the next cycle (output reg empty or no input)
assign xfcp_usp_us_tready_int_early = xfcp_usp_us.tready || (!temp_xfcp_usp_us_tvalid_reg && (!xfcp_usp_us_tvalid_reg || !xfcp_usp_us_tvalid_int));

always_comb begin
    // transfer sink ready state to source
    xfcp_usp_us_tvalid_next = xfcp_usp_us_tvalid_reg;
    temp_xfcp_usp_us_tvalid_next = temp_xfcp_usp_us_tvalid_reg;

    store_xfcp_usp_us_int_to_output = 1'b0;
    store_xfcp_usp_us_int_to_temp = 1'b0;
    store_xfcp_usp_us_temp_to_output = 1'b0;

    if (xfcp_usp_us_tready_int_reg) begin
        // input is ready
        if (xfcp_usp_us.tready || !xfcp_usp_us_tvalid_reg) begin
            // output is ready or currently not valid, transfer data to output
            xfcp_usp_us_tvalid_next = xfcp_usp_us_tvalid_int;
            store_xfcp_usp_us_int_to_output = 1'b1;
        end else begin
            // output is not ready, store input in temp
            temp_xfcp_usp_us_tvalid_next = xfcp_usp_us_tvalid_int;
            store_xfcp_usp_us_int_to_temp = 1'b1;
        end
    end else if (xfcp_usp_us.tready) begin
        // input is not ready, but output is ready
        xfcp_usp_us_tvalid_next = temp_xfcp_usp_us_tvalid_reg;
        temp_xfcp_usp_us_tvalid_next = 1'b0;
        store_xfcp_usp_us_temp_to_output = 1'b1;
    end
end

always_ff @(posedge clk) begin
    if (rst) begin
        xfcp_usp_us_tvalid_reg <= 1'b0;
        xfcp_usp_us_tready_int_reg <= 1'b0;
        temp_xfcp_usp_us_tvalid_reg <= 1'b0;
    end else begin
        xfcp_usp_us_tvalid_reg <= xfcp_usp_us_tvalid_next;
        xfcp_usp_us_tready_int_reg <= xfcp_usp_us_tready_int_early;
        temp_xfcp_usp_us_tvalid_reg <= temp_xfcp_usp_us_tvalid_next;
    end

    // datapath
    if (store_xfcp_usp_us_int_to_output) begin
        xfcp_usp_us_tdata_reg <= xfcp_usp_us_tdata_int;
        xfcp_usp_us_tlast_reg <= xfcp_usp_us_tlast_int;
        xfcp_usp_us_tuser_reg <= xfcp_usp_us_tuser_int;
    end else if (store_xfcp_usp_us_temp_to_output) begin
        xfcp_usp_us_tdata_reg <= temp_xfcp_usp_us_tdata_reg;
        xfcp_usp_us_tlast_reg <= temp_xfcp_usp_us_tlast_reg;
        xfcp_usp_us_tuser_reg <= temp_xfcp_usp_us_tuser_reg;
    end

    if (store_xfcp_usp_us_int_to_temp) begin
        temp_xfcp_usp_us_tdata_reg <= xfcp_usp_us_tdata_int;
        temp_xfcp_usp_us_tlast_reg <= xfcp_usp_us_tlast_int;
        temp_xfcp_usp_us_tuser_reg <= xfcp_usp_us_tuser_int;
    end
end

// downstream output datapath logic
reg [7:0]       xfcp_dsp_ds_tdata_reg = 8'd0;
reg [PORTS-1:0] xfcp_dsp_ds_tvalid_reg = '0, xfcp_dsp_ds_tvalid_next;
reg             xfcp_dsp_ds_tlast_reg = 1'b0;
reg             xfcp_dsp_ds_tuser_reg = 1'b0;

reg [7:0]       temp_xfcp_dsp_ds_tdata_reg = 8'd0;
reg [PORTS-1:0] temp_xfcp_dsp_ds_tvalid_reg = '0, temp_xfcp_dsp_ds_tvalid_next;
reg             temp_xfcp_dsp_ds_tlast_reg = 1'b0;
reg             temp_xfcp_dsp_ds_tuser_reg = 1'b0;

// datapath control
reg store_xfcp_dsp_ds_to_output;
reg store_xfcp_dsp_ds_to_temp;
reg store_xfcp_dsp_ds_temp_to_output;

assign xfcp_dsp_ds_tvalid = xfcp_dsp_ds_tvalid_reg;

for (genvar k = 0; k < PORTS; k = k + 1) begin
    assign xfcp_dsp_ds[k].tdata  = xfcp_dsp_ds_tdata_reg;
    assign xfcp_dsp_ds[k].tkeep  = '1;
    assign xfcp_dsp_ds[k].tstrb  = xfcp_dsp_ds[k].tkeep;
    assign xfcp_dsp_ds[k].tvalid = xfcp_dsp_ds_tvalid_reg[k];
    assign xfcp_dsp_ds[k].tlast  = xfcp_dsp_ds_tlast_reg;
    assign xfcp_dsp_ds[k].tid    = '0;
    assign xfcp_dsp_ds[k].tdest  = '0;
    assign xfcp_dsp_ds[k].tuser  = xfcp_dsp_ds_tuser_reg;

    assign xfcp_dsp_ds_tready[k] = xfcp_dsp_ds[k].tready;
end

// enable ready input next cycle if output is ready or the temp reg will not be filled on the next cycle (output reg empty or no input)
assign xfcp_dsp_ds_tready_int_early = ((xfcp_dsp_ds_tready & xfcp_dsp_ds_tvalid) != 0) || ((temp_xfcp_dsp_ds_tvalid_reg == 0) && ((xfcp_dsp_ds_tvalid == 0) || (xfcp_dsp_ds_tvalid_int == 0)));

always_comb begin
    // transfer sink ready state to source
    xfcp_dsp_ds_tvalid_next = xfcp_dsp_ds_tvalid_reg;
    temp_xfcp_dsp_ds_tvalid_next = temp_xfcp_dsp_ds_tvalid_reg;

    store_xfcp_dsp_ds_to_output = 1'b0;
    store_xfcp_dsp_ds_to_temp = 1'b0;
    store_xfcp_dsp_ds_temp_to_output = 1'b0;

    if (xfcp_dsp_ds_tready_int_reg) begin
        // input is ready
        if (((xfcp_dsp_ds_tready & xfcp_dsp_ds_tvalid) != 0) || (xfcp_dsp_ds_tvalid == 0)) begin
            // output is ready or currently not valid, transfer data to output
            xfcp_dsp_ds_tvalid_next = xfcp_dsp_ds_tvalid_int;
            store_xfcp_dsp_ds_to_output = 1'b1;
        end else begin
            // output is not ready, store input in temp
            temp_xfcp_dsp_ds_tvalid_next = xfcp_dsp_ds_tvalid_int;
            store_xfcp_dsp_ds_to_temp = 1'b1;
        end
    end else if ((xfcp_dsp_ds_tready & xfcp_dsp_ds_tvalid) != 0) begin
        // input is not ready, but output is ready
        xfcp_dsp_ds_tvalid_next = temp_xfcp_dsp_ds_tvalid_reg;
        temp_xfcp_dsp_ds_tvalid_next = '0;
        store_xfcp_dsp_ds_temp_to_output = 1'b1;
    end
end

always_ff @(posedge clk) begin
    if (rst) begin
        xfcp_dsp_ds_tvalid_reg <= '0;
        xfcp_dsp_ds_tready_int_reg <= 1'b0;
        temp_xfcp_dsp_ds_tvalid_reg <= '0;
    end else begin
        xfcp_dsp_ds_tvalid_reg <= xfcp_dsp_ds_tvalid_next;
        xfcp_dsp_ds_tready_int_reg <= xfcp_dsp_ds_tready_int_early;
        temp_xfcp_dsp_ds_tvalid_reg <= temp_xfcp_dsp_ds_tvalid_next;
    end

    // datapath
    if (store_xfcp_dsp_ds_to_output) begin
        xfcp_dsp_ds_tdata_reg <= xfcp_dsp_ds_tdata_int;
        xfcp_dsp_ds_tlast_reg <= xfcp_dsp_ds_tlast_int;
        xfcp_dsp_ds_tuser_reg <= xfcp_dsp_ds_tuser_int;
    end else if (store_xfcp_dsp_ds_temp_to_output) begin
        xfcp_dsp_ds_tdata_reg <= temp_xfcp_dsp_ds_tdata_reg;
        xfcp_dsp_ds_tlast_reg <= temp_xfcp_dsp_ds_tlast_reg;
        xfcp_dsp_ds_tuser_reg <= temp_xfcp_dsp_ds_tuser_reg;
    end

    if (store_xfcp_dsp_ds_to_temp) begin
        temp_xfcp_dsp_ds_tdata_reg <= xfcp_dsp_ds_tdata_int;
        temp_xfcp_dsp_ds_tlast_reg <= xfcp_dsp_ds_tlast_int;
        temp_xfcp_dsp_ds_tuser_reg <= xfcp_dsp_ds_tuser_int;
    end
end

endmodule
