// Copyright © 2019-2023
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

`include "VX_platform.vh"

`TRACING_OFF
module VX_stream_switch #(
    parameter NUM_INPUTS    = 1,
    parameter NUM_OUTPUTS   = 1,
    parameter DATAW         = 1,
    parameter OUT_REG       = 0,
    parameter NUM_REQS      = (NUM_INPUTS > NUM_OUTPUTS) ? ((NUM_INPUTS + NUM_OUTPUTS - 1) / NUM_OUTPUTS) : ((NUM_OUTPUTS + NUM_INPUTS - 1) / NUM_INPUTS),
    parameter SEL_COUNT     = `MIN(NUM_INPUTS, NUM_OUTPUTS),
    parameter LOG_NUM_REQS  = `CLOG2(NUM_REQS)
) (
    input wire                              clk,
    input wire                              reset,

    input wire  [SEL_COUNT-1:0][`UP(LOG_NUM_REQS)-1:0] sel_in,

    input  wire [NUM_INPUTS-1:0]            valid_in,
    input  wire [NUM_INPUTS-1:0][DATAW-1:0] data_in,
    output wire [NUM_INPUTS-1:0]            ready_in,

    output wire [NUM_OUTPUTS-1:0]           valid_out,
    output wire [NUM_OUTPUTS-1:0][DATAW-1:0] data_out,    
    input  wire [NUM_OUTPUTS-1:0]           ready_out
);
    if (NUM_INPUTS > NUM_OUTPUTS) begin

        wire [NUM_OUTPUTS-1:0][NUM_REQS-1:0]             valid_in_r;
        wire [NUM_OUTPUTS-1:0][NUM_REQS-1:0][DATAW-1:0]  data_in_r;

        for (genvar i = 0; i < NUM_OUTPUTS; ++i) begin
            for (genvar j = 0; j < NUM_REQS; ++j) begin
                localparam ii = i * NUM_REQS + j;
                if (ii < NUM_INPUTS) begin
                    assign valid_in_r[i][j] = valid_in[ii];
                    assign data_in_r[i][j]  = data_in[ii];
                end else begin
                    assign valid_in_r[i][j] = 0;
                    assign data_in_r[i][j]  = '0;
                end
            end
        end        

        wire [NUM_OUTPUTS-1:0]            valid_out_r;
        wire [NUM_OUTPUTS-1:0][DATAW-1:0] data_out_r;
        wire [NUM_OUTPUTS-1:0]            ready_out_r;

        for (genvar i = 0; i < NUM_OUTPUTS; ++i) begin
            assign valid_out_r[i] = valid_in_r[i][sel_in[i]];
            assign data_out_r[i]  = data_in_r[i][sel_in[i]];
        end

        for (genvar i = 0; i < NUM_OUTPUTS; ++i) begin
            for (genvar j = 0; j < NUM_REQS; ++j) begin
                localparam ii = i * NUM_REQS + j;                
                if (ii < NUM_INPUTS) begin                    
                    assign ready_in[ii] = ready_out_r[i] & (sel_in[i] == LOG_NUM_REQS'(j));
                end
            end
        end

        for (genvar i = 0; i < NUM_OUTPUTS; ++i) begin

            `RESET_RELAY_EN (out_buf_reset, reset, (NUM_OUTPUTS > 1));

            VX_elastic_buffer #(
                .DATAW   (DATAW),
                .SIZE    (`OUT_REG_TO_EB_SIZE(OUT_REG)),
                .OUT_REG (`OUT_REG_TO_EB_REG(OUT_REG))
            ) out_buf (
                .clk       (clk),
                .reset     (out_buf_reset),
                .valid_in  (valid_out_r[i]),                    
                .ready_in  (ready_out_r[i]),
                .data_in   (data_out_r[i]),
                .data_out  (data_out[i]),
                .valid_out (valid_out[i]),
                .ready_out (ready_out[i])
            );
        end

    end else if (NUM_OUTPUTS > NUM_INPUTS) begin
    
        wire [NUM_INPUTS-1:0][NUM_REQS-1:0] valid_out_r;
        wire [NUM_INPUTS-1:0][NUM_REQS-1:0] ready_out_r;

        for (genvar i = 0; i < NUM_INPUTS; ++i) begin
            for (genvar j = 0; j < NUM_REQS; ++j) begin
                assign valid_out_r[i][j] = valid_in[i] & (sel_in[i] == LOG_NUM_REQS'(j));
            end
            assign ready_in[i] = ready_out_r[i][sel_in[i]];
        end

        for (genvar i = 0; i < NUM_INPUTS; ++i) begin
            for (genvar j = 0; j < NUM_REQS; ++j) begin
                localparam ii = i * NUM_REQS + j;
                if (ii < NUM_OUTPUTS) begin

                    `RESET_RELAY (out_buf_reset, reset);

                    VX_elastic_buffer #(
                        .DATAW    (DATAW),
                        .SIZE     (`OUT_REG_TO_EB_SIZE(OUT_REG)),
                        .OUT_REG  (`OUT_REG_TO_EB_REG(OUT_REG))
                    ) out_buf (
                        .clk       (clk),
                        .reset     (out_buf_reset),
                        .valid_in  (valid_out_r[i][j]),
                        .ready_in  (ready_out_r[i][j]),
                        .data_in   (data_in[i]),                                                     
                        .data_out  (data_out[ii]),
                        .valid_out (valid_out[ii]),
                        .ready_out (ready_out[ii])
                    );
                end else begin
                    `UNUSED_VAR (valid_out_r[i][j])
                    assign ready_out_r[i][j] = '0;
                end                
            end
        end
    
    end else begin

        // #Inputs == #Outputs
    
        `UNUSED_VAR (sel_in)

        for (genvar i = 0; i < NUM_OUTPUTS; ++i) begin

            `RESET_RELAY_EN (out_buf_reset, reset, (NUM_OUTPUTS > 1));

            VX_elastic_buffer #(
                .DATAW    (DATAW),
                .SIZE     (`OUT_REG_TO_EB_SIZE(OUT_REG)),
                .OUT_REG  (`OUT_REG_TO_EB_REG(OUT_REG))
            ) out_buf (
                .clk       (clk),
                .reset     (out_buf_reset),
                .valid_in  (valid_in[i]),
                .ready_in  (ready_in[i]),
                .data_in   (data_in[i]),
                .data_out  (data_out[i]),
                .valid_out (valid_out[i]),
                .ready_out (ready_out[i])
            );
        end

    end
    
endmodule
`TRACING_ON
