module systolic_array (
    input  wire       clk,
    input  wire       reset,

    input  wire [4:0] in_left_zero,
    input  wire [4:0] in_left_sign,
    input  wire [7:0] in_top,
    input  wire       restart_inputs,
    input  wire       reset_accumulators,
    input  wire       copy_accumulator_values_to_out_queue,
    input  wire       restart_out_queue,
    //input wire      apply_shift_to_accumulators,
    //input wire      apply_relu_to_out,

    output wire [7:0] out
);
    localparam SLICES = 3;
    localparam SLICE_BITS = $clog2(SLICES);
    localparam SLICES_MINUS_1 = SLICES - 1;
    localparam W = 1 * SLICES;
    localparam H = 5 * SLICES;
    localparam ARRAY_SIZE_BITS = $clog2(W*H);

    reg [H  -1:0] arg_left_zero_curr;
    reg [H  -1:0] arg_left_sign_curr;
    reg [W*8-1:0] arg_top_curr;

    reg [H  -1:0] arg_left_zero_next;
    reg [H  -1:0] arg_left_sign_next;
    reg [W*8-1:0] arg_top_next;

    reg  [SLICE_BITS-1:0] slice_counter;
    reg  signed [16:0] accumulators      [W*H-1:0];
    wire signed [16:0] accumulators_next [W*H-1:0];
    reg  signed [16:0] out_queue         [W*H-1:0];
    reg  [ARRAY_SIZE_BITS-1:0] out_queue_counter;

    integer n;
    always @(posedge clk) begin
        if (reset | restart_inputs | slice_counter == SLICES_MINUS_1)
            slice_counter <= 0;
        else
            slice_counter <= slice_counter + 1;

        if (reset | restart_out_queue)
            out_queue_counter <= 0;
        else
            out_queue_counter <= out_queue_counter + 1;

        if (reset) begin
            arg_left_zero_next <= 0;
            arg_left_sign_next <= 0;
            arg_top_next <= 0;
        end else begin
            arg_left_zero_next[slice_counter*5 +: 5] <= in_left_zero;
            arg_left_sign_next[slice_counter*5 +: 5] <= in_left_sign;
            arg_top_next[slice_counter*8 +: 8] <= in_top;
        end

        if (slice_counter == 0) begin
            arg_left_zero_curr <= arg_left_zero_next;
            arg_left_sign_curr <= arg_left_sign_next;
            arg_top_curr <= arg_top_next;
        end
        
        // The following loop must be unrolled, otherwise Verilator
        // will treat <= assignments inside the loop as errors
        // See similar bug report and workaround here:
        //   https://github.com/verilator/verilator/issues/2782
        // Ideally unroll_full Verilator metacommand should be used,
        // however it is supported only from Verilator 5.022 (#3260) [Jiaxun Yang]
        // Instead BLKLOOPINIT errors are suppressed for this loop
        /* verilator lint_off BLKLOOPINIT */
        for (n = 0; n < W*H; n = n + 1) begin
            if (reset | reset_accumulators)
                accumulators[n] <= 0;
            else
                accumulators[n] <= accumulators_next[n];

            if (copy_accumulator_values_to_out_queue)
                out_queue[n]    <= accumulators_next[n];
        end
        /* verilator lint_on BLKLOOPINIT */
    end

    genvar i, j;
    generate
    for (j = 0; j < W; j = j + 1)
        for (i = 0; i < H; i = i + 1) begin : mac
            wire [16:0] value_curr  = accumulators     [i*W+j];
            wire [16:0] value_next  = accumulators_next[i*W+j];
            wire [16:0] value_queue = out_queue        [i*W+j];
            wire skip = (j != slice_counter) | arg_left_zero_curr[i];
            wire sign = arg_left_sign_curr[i];
            wire signed [7:0] addend = $signed(arg_top_curr[j*8 +: 8]);
            assign accumulators_next[i*W+j] =
                 reset  ? 0 :
                 skip   ? accumulators[i*W+j] + 0 :
                (sign   ? accumulators[i*W+j] - addend :
                          accumulators[i*W+j] + addend);
        end
    endgenerate

    assign out = out_queue[out_queue_counter] >> 8;
    // assign out = out_queue[out_queue_counter][7:0];
endmodule