// 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_define.vh"

`TRACING_OFF
module VX_stream_xbar #(
    parameter NUM_INPUTS    = 4,
    parameter NUM_OUTPUTS   = 4,
    parameter DATAW         = 4,
    parameter IN_WIDTH      = `LOG2UP(NUM_INPUTS),
    parameter OUT_WIDTH     = `LOG2UP(NUM_OUTPUTS),
    parameter ARBITER       = "P",
    parameter OUT_REG       = 0,
    parameter MAX_FANOUT    = `MAX_FANOUT,
    parameter PERF_CTR_BITS = `CLOG2(NUM_INPUTS+1)
) (
    input wire                              clk,
    input wire                              reset,

    output wire [PERF_CTR_BITS-1:0]         collisions,

    input wire [NUM_INPUTS-1:0]             valid_in,
    input wire [NUM_INPUTS-1:0][DATAW-1:0]  data_in,
    input wire [NUM_INPUTS-1:0][OUT_WIDTH-1:0] sel_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,  
    output wire [NUM_OUTPUTS-1:0][IN_WIDTH-1:0] sel_out,
    input  wire [NUM_OUTPUTS-1:0]           ready_out
);
    `UNUSED_VAR (clk)
    `UNUSED_VAR (reset)

    if (NUM_INPUTS != 1) begin

        if (NUM_OUTPUTS != 1) begin

            // (#inputs > 1) and (#outputs > 1)

            wire [NUM_OUTPUTS-1:0][NUM_INPUTS-1:0] per_output_ready_in;

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

                wire [NUM_INPUTS-1:0] valid_in_q;
                for (genvar j = 0; j < NUM_INPUTS; ++j) begin
                    assign valid_in_q[j] = valid_in[j] && (sel_in[j] == i);
                end

                `RESET_RELAY (slice_reset, reset);

                VX_stream_arb #(
                    .NUM_INPUTS  (NUM_INPUTS),
                    .NUM_OUTPUTS (1),
                    .DATAW       (DATAW),
                    .ARBITER     (ARBITER),
                    .MAX_FANOUT  (MAX_FANOUT),
                    .OUT_REG     (OUT_REG)
                ) xbar_arb (
                    .clk       (clk),
                    .reset     (slice_reset),
                    .valid_in  (valid_in_q),
                    .data_in   (data_in),
                    .ready_in  (per_output_ready_in[i]),
                    .valid_out (valid_out[i]),
                    .data_out  (data_out[i]),
                    .sel_out   (sel_out[i]),
                    .ready_out (ready_out[i])
                );
            end

            for (genvar i = 0; i < NUM_INPUTS; ++i) begin
                assign ready_in[i] = per_output_ready_in[sel_in[i]][i];
            end

        end else begin

            // (#inputs >= 1) and (#outputs == 1)

            VX_stream_arb #(
                .NUM_INPUTS  (NUM_INPUTS),
                .NUM_OUTPUTS (1),
                .DATAW       (DATAW),
                .ARBITER     (ARBITER),
                .MAX_FANOUT  (MAX_FANOUT),
                .OUT_REG     (OUT_REG)
            ) xbar_arb (
                .clk       (clk),
                .reset     (reset),
                .valid_in  (valid_in),
                .data_in   (data_in),
                .ready_in  (ready_in),
                .valid_out (valid_out),
                .data_out  (data_out),
                .sel_out   (sel_out),
                .ready_out (ready_out)
            );

            `UNUSED_VAR (sel_in)
        end

    end else if (NUM_OUTPUTS != 1) begin

        // (#inputs == 1) and (#outputs > 1)

        logic [NUM_OUTPUTS-1:0] valid_out_r, ready_out_r;
        logic [NUM_OUTPUTS-1:0][DATAW-1:0] data_out_r;
        always @(*) begin
            valid_out_r = '0;
            valid_out_r[sel_in] = valid_in;
        end
        assign data_out_r = {NUM_OUTPUTS{data_in}};
        assign ready_in = ready_out_r[sel_in];

        for (genvar i = 0; i < NUM_OUTPUTS; ++i) 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]),
                .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

        assign sel_out = 0;

    end else begin

        // (#inputs == 1) and (#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     (reset),
            .valid_in  (valid_in),
            .ready_in  (ready_in),
            .data_in   (data_in),
            .data_out  (data_out),
            .valid_out (valid_out),
            .ready_out (ready_out)
        );

        `UNUSED_VAR (sel_in)
        assign sel_out = 0;

    end

    // compute inputs collision
    // we have a collision when there exists a valid transfer with multiple input candicates
    // we count the unique duplicates each cycle.
    
    reg [NUM_INPUTS-1:0] per_cycle_collision, per_cycle_collision_r;
    wire [`CLOG2(NUM_INPUTS+1)-1:0] collision_count;
    reg [PERF_CTR_BITS-1:0] collisions_r;

    always @(*) begin
        per_cycle_collision = 0;
        for (integer i = 0; i < NUM_INPUTS; ++i) begin
            for (integer j = 1; j < (NUM_INPUTS-i); ++j) begin
                per_cycle_collision[i] |= valid_in[i]
                                       && valid_in[j+i] 
                                       && (sel_in[i] == sel_in[j+i])
                                       && (ready_in[i] | ready_in[j+i]);
            end
        end
    end
    
    `BUFFER(per_cycle_collision_r, per_cycle_collision);    
    `POP_COUNT(collision_count, per_cycle_collision_r);

    always @(posedge clk) begin
        if (reset) begin
            collisions_r <= '0;
        end else begin
            collisions_r <= collisions_r + PERF_CTR_BITS'(collision_count);
        end
    end

    assign collisions = collisions_r;

endmodule
`TRACING_ON
