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

module VX_gather_unit import VX_gpu_pkg::*; #(
    parameter BLOCK_SIZE = 1,
    parameter NUM_LANES  = 1,
    parameter OUT_REG    = 0
) ( 
    input  wire         clk,
    input  wire         reset,

    // inputs
    VX_commit_if.slave  commit_in_if [BLOCK_SIZE],
    
    // outputs
    VX_commit_if.master commit_out_if [`ISSUE_WIDTH]

);
    localparam BLOCK_SIZE_W = `LOG2UP(BLOCK_SIZE);
    localparam PID_BITS     = `CLOG2(`NUM_THREADS / NUM_LANES);
    localparam PID_WIDTH    = `UP(PID_BITS);
    localparam DATAW        = `UUID_WIDTH + `NW_WIDTH + NUM_LANES + `XLEN + 1 + `NR_BITS + NUM_LANES * `XLEN + 1 + PID_WIDTH + 1 + 1;
    localparam DATA_WIS_OFF = DATAW - (`UUID_WIDTH + `NW_WIDTH);

    wire [BLOCK_SIZE-1:0] commit_in_valid;
    wire [BLOCK_SIZE-1:0][DATAW-1:0] commit_in_data;
    wire [BLOCK_SIZE-1:0] commit_in_ready;
    wire [BLOCK_SIZE-1:0][ISSUE_ISW_W-1:0] commit_in_isw;

    for (genvar i = 0; i < BLOCK_SIZE; ++i) begin
        assign commit_in_valid[i] = commit_in_if[i].valid;
        assign commit_in_data[i] = commit_in_if[i].data;
        assign commit_in_if[i].ready = commit_in_ready[i];
        if (BLOCK_SIZE != `ISSUE_WIDTH) begin
            if (BLOCK_SIZE != 1) begin
                assign commit_in_isw[i] = {commit_in_data[i][DATA_WIS_OFF+BLOCK_SIZE_W +: (ISSUE_ISW_W-BLOCK_SIZE_W)], BLOCK_SIZE_W'(i)};
            end else begin
                assign commit_in_isw[i] = commit_in_data[i][DATA_WIS_OFF +: ISSUE_ISW_W];
            end
        end else begin
            assign commit_in_isw[i] = BLOCK_SIZE_W'(i);
        end
    end

    reg [`ISSUE_WIDTH-1:0] commit_out_valid;
    reg [`ISSUE_WIDTH-1:0][DATAW-1:0] commit_out_data;
    wire [`ISSUE_WIDTH-1:0] commit_out_ready;

    always @(*) begin
        commit_out_valid = '0;
        for (integer i = 0; i < `ISSUE_WIDTH; ++i) begin
            commit_out_data[i] = 'x;
        end
        for (integer i = 0; i < BLOCK_SIZE; ++i) begin
            commit_out_valid[commit_in_isw[i]] = commit_in_valid[i];
            commit_out_data[commit_in_isw[i]] = commit_in_data[i];
        end
    end
    for (genvar i = 0; i < BLOCK_SIZE; ++i) begin
        assign commit_in_ready[i] = commit_out_ready[commit_in_isw[i]];
    end
    
    for (genvar i = 0; i < `ISSUE_WIDTH; ++i) begin
        VX_commit_if #(
            .NUM_LANES (NUM_LANES)
        ) commit_tmp_if();

        `RESET_RELAY(commit_out_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      (commit_out_reset),
            .valid_in   (commit_out_valid[i]),
            .ready_in   (commit_out_ready[i]),
            .data_in    (commit_out_data[i]),            
            .data_out   (commit_tmp_if.data),
            .valid_out  (commit_tmp_if.valid),
            .ready_out  (commit_tmp_if.ready)
        );

        logic [`NUM_THREADS-1:0] commit_tmask_r;
        logic [`NUM_THREADS-1:0][`XLEN-1:0] commit_data_r;
        if (PID_BITS != 0) begin
            always @(*) begin
                commit_tmask_r = '0;
                commit_data_r  = 'x;
                for (integer j = 0; j < NUM_LANES; ++j) begin
                    commit_tmask_r[commit_tmp_if.data.pid * NUM_LANES + j] = commit_tmp_if.data.tmask[j];
                    commit_data_r[commit_tmp_if.data.pid * NUM_LANES + j] = commit_tmp_if.data.data[j];
                end
            end
        end else begin
            assign commit_tmask_r = commit_tmp_if.data.tmask;
            assign commit_data_r = commit_tmp_if.data.data;
        end

        assign commit_out_if[i].valid = commit_tmp_if.valid;
        assign commit_out_if[i].data = {
            commit_tmp_if.data.uuid,
            commit_tmp_if.data.wid,
            commit_tmask_r,
            commit_tmp_if.data.PC,
            commit_tmp_if.data.wb,
            commit_tmp_if.data.rd,
            commit_data_r,
            commit_tmp_if.data.tensor,
            1'b0, // PID
            commit_tmp_if.data.sop,
            commit_tmp_if.data.eop
        };
        assign commit_tmp_if.ready = commit_out_if[i].ready;
    end

endmodule
