Task: Q2_GEMM


---

# Human-Readable I/O-Only Spec — `systolic`

## 1) Module

* **Name**: `TOP`
* **Clock**: `clk`, rising-edge.
* **Reset**: `rst`, **Synchronous, active-high**.

  * While `rst=1`: internal state is cleared; `o_valid=0`;
  * On `rst` deassert (1→0): module is ready to accept a new tile per the rules below.

> All ports are **1-D flattened** (no multi-dimensional arrays on the port list).

## 2) Ports (I/O only)

### 2.1 Control & Clock

* `input  clk`
* `input  rst`
* `input  tile_en` — **load enable**; must be **1 for four consecutive cycles** to provide **one tile**.

### 2.2 Data Inputs (sampled only when `tile_en=1`)

* `input  [15:0] tileA_data_0`
* `input  [15:0] tileA_data_1`
* `input  [15:0] tileA_data_2`
* `input  [15:0] tileA_data_3`
* `input  [15:0] tileB_data_0`
* `input  [15:0] tileB_data_1`
* `input  [15:0] tileB_data_2`
* `input  [15:0] tileB_data_3`

**Sampling rule:** On each **posedge** with `tile_en=1`, sample all eight inputs above. **Exactly four consecutive** such cycles constitute one complete tile load (32 total 16-bit values).

### 2.3 Data Outputs (valid only when `o_valid=1`)

* `output reg [15:0] tileO_data_0`  … `tileO_data_15`

**Mapping:** `tileO_data_(4*r + c)` corresponds to output element at row `r`, column `c`, with `r,c ∈ {0..3}` (row-major).

### 2.4 Completion Pulse

* `output reg o_valid` — **one-cycle pulse**; when `o_valid=1`, **all** `tileO_data_*` are valid and stable in that same cycle.

## 3) Timing Contract (externally observable)

1. **Load phase**

   * One tile **must** be provided by **four consecutive** cycles with `tile_en=1`.
   * Inputs are sampled on each **posedge** of those four cycles.

2. **Output validity**
   * `o_valid` is **one clock** wide; all `tileO_data_*` are valid and stable in that cycle only.
   * `tileO_data_*` are considered valid **only** in the cycle where `o_valid=1`.
   * Outside that cycle, `tileO_data_*` may be don’t-care.

## 4) **Critical rule after completion**

* The **cycle immediately after** `o_valid` assert `tile_en=1`.
* **If** `tile_en=1` in that cycle, it **must be accepted** as the **first load cycle of the next tile**.

## 5) Interface assumptions

* No other ready/valid or back-pressure signals exist.
* Inputs during `rst=1` are ignored; outputs/`o_valid` have no meaning during `rst=1`.
* Internal implementation is unconstrained; only this **I/O and timing** contract will be verified.

---


## Reference RTL Implementation

// no multi-dimensional arrays
module TOP (
    input clk, 
    input rst, 
    input tile_en, 
    input [15:0] tileA_data_0,
    input [15:0] tileA_data_1,
    input [15:0] tileA_data_2,
    input [15:0] tileA_data_3,
    input [15:0] tileB_data_0,
    input [15:0] tileB_data_1,
    input [15:0] tileB_data_2,
    input [15:0] tileB_data_3,
    output reg   o_valid,
    // output logic [15:0] tileO_data [0:3][0:3]
    // output reg [15:0] tileO_data [0:15]
    output reg [15:0] tileO_data_0,
    output reg [15:0] tileO_data_1,
    output reg [15:0] tileO_data_2,
    output reg [15:0] tileO_data_3,
    output reg [15:0] tileO_data_4,
    output reg [15:0] tileO_data_5,
    output reg [15:0] tileO_data_6,
    output reg [15:0] tileO_data_7,
    output reg [15:0] tileO_data_8,
    output reg [15:0] tileO_data_9,
    output reg [15:0] tileO_data_10,
    output reg [15:0] tileO_data_11,
    output reg [15:0] tileO_data_12,
    output reg [15:0] tileO_data_13,
    output reg [15:0] tileO_data_14,
    output reg [15:0] tileO_data_15
);
    // input buffers
    reg [15:0] tileA_w [0:27], tileA_r [0:27];
    reg [15:0] tileB_w [0:27], tileB_r [0:27];
    
    // state machine 
    // typedef enum logic [1:0] {
    //     LOADING, 
    //     COMPUTE,
    //     DONE
    // } state_t;
    // state_t state_w, state_r;
    localparam LOADING = 2'b00, COMPUTE = 2'b01, DONE = 2'b10;
    reg [1:0] state_w, state_r;

    reg [1:0] load_num_w, load_num_r;
    reg [4:0] propagate_num_w, propagate_num_r;

    always@(*) begin
        state_w = state_r;
        case (state_r)
            LOADING: begin
                if (tile_en && load_num_r == 3) begin
                    state_w = COMPUTE;
                end
            end
            COMPUTE: begin
                // Assuming some condition to go to DONE state
                if(propagate_num_r == 11) begin
                    state_w = DONE;
                end
            end
            DONE: begin
                state_w = LOADING; // Loop back to LOADING for next tile
            end
        endcase
    end

    // logic [15:0] next_a [0:3][0:3], next_b [0:3][0:3];
    reg [15:0] next_a [0:15], next_b [0:15];
    reg [15:0] tileA_in [0:3], tileB_in [0:3];
    reg PE_clear, PE_en, PE_out;
    wire  [15:0] o [0:15];
    wire PE_input_gate;
    assign PE_input_gate = (state_r == LOADING || state_r == COMPUTE) && PE_en;

    always@(*) begin
        tileO_data_0 = o[0];
        tileO_data_1 = o[1];
        tileO_data_2 = o[2];
        tileO_data_3 = o[3];
        tileO_data_4 = o[4];
        tileO_data_5 = o[5];
        tileO_data_6 = o[6];
        tileO_data_7 = o[7];
        tileO_data_8 = o[8];
        tileO_data_9 = o[9];
        tileO_data_10 = o[10];
        tileO_data_11 = o[11];
        tileO_data_12 = o[12];
        tileO_data_13 = o[13];
        tileO_data_14 = o[14];
        tileO_data_15 = o[15];
    end    

    // systolic array 

    //                 tileB_in[0]  tileB_in[1] tileB_in[2] tileB_in[3]
    //  tileA_in[0]    PE00         PE01        PE02        PE03
    //  tileA_in[1]    PE10         PE11        PE12        PE13
    //  tileA_in[2]    PE20         PE21        PE22        PE23
    //  tileA_in[3]    PE30         PE31        PE32        PE33

    PE pe00 (
        .*,
        .a(tileA_in[0]), 
        .b(tileB_in[0]),
        .next_a(next_a[0]),
        .next_b(next_b[0]),
        .o(o[0])
    );
    PE pe01 (
        .*,
        .a(next_a[0]),
        .b(tileB_in[1]),
        .next_a(next_a[1]), 
        .next_b(next_b[1]),
        .o(o[1])
    );
    PE pe02 (
        .*,
        .a(next_a[1]),
        .b(tileB_in[2]),
        .next_a(next_a[2]),
        .next_b(next_b[2]),
        .o(o[2])
    );
    PE pe03 (
        .*,
        .a(next_a[2]),
        .b(tileB_in[3]),
        .next_a(),
        .next_b(next_b[3]),
        .o(o[3])
    );
    PE pe10 (
        .*,
        .a(tileA_in[1]), 
        .b(next_b[0]),
        .next_a(next_a[4]), 
        .next_b(next_b[4]),
        .o(o[4])
    );
    PE pe11 (
        .*,
        .a(next_a[4]),
        .b(next_b[1]),
        .next_a(next_a[5]),
        .next_b(next_b[5]),
        .o(o[5])
    );
    PE pe12 (
        .*,
        .a(next_a[5]),
        .b(next_b[2]),
        .next_a(next_a[6]),
        .next_b(next_b[6]),
        .o(o[6])
    );
    PE pe13 (
        .*,
        .a(next_a[6]), 
        .b(next_b[3]),
        .next_a(),
        .next_b(next_b[7]),
        .o(o[7])
    );
    PE pe20 (
        .*,
        .a(tileA_in[2]), 
        .b(next_b[4]),
        .next_a(next_a[8]),
        .next_b(next_b[8]),
        .o(o[8])
    );
    PE pe21 (
        .*,
        .a(next_a[8]),
        .b(next_b[5]),
        .next_a(next_a[9]),
        .next_b(next_b[9]),
        .o(o[9])
    );
    PE pe22 (
        .*,
        .a(next_a[9]), 
        .b(next_b[6]),
        .next_a(next_a[10]),
        .next_b(next_b[10]),
        .o(o[10])
    );
    PE pe23 (
        .*,
        .a(next_a[10]),
        .b(next_b[7]),
        .next_a(),
        .next_b(next_b[11]),
        .o(o[11])
    );
    PE pe30 (
        .*,
        .a(tileA_in[3]), 
        .b(next_b[8]),
        .next_a(next_a[12]),
        .next_b(),
        .o(o[12])
    );
    PE pe31 (
        .*,
        .a(next_a[12]), 
        .b(next_b[9]),
        .next_a(next_a[13]),
        .next_b(),
        .o(o[13])
    );
    PE pe32 (
        .*,
        .a(next_a[13]), 
        .b(next_b[10]),
        .next_a(next_a[14]),
        .next_b(),
        .o(o[14])
    );
    PE pe33 (
        .*,
        .a(next_a[14]), 
        .b(next_b[11]),
        .next_a(),
        .next_b(),
        .o(o[15])
    );



    always@(*) begin
        load_num_w = load_num_r;
        propagate_num_w = propagate_num_r;
        for(int i=0; i<28; i++) begin
            tileA_w[i] = tileA_r[i];
            tileB_w[i] = tileB_r[i];
        end
        PE_clear = 0;
        PE_en = 0;
        PE_out = 0;
        o_valid = 0;
        for(int i=0; i<4; i++) begin
            tileA_in[i] = 0;
            tileB_in[i] = 0;
        end
        case(state_r)
            LOADING: begin
                if(tile_en) begin
                    // tile
                    // 05 04 03 02 01 00
                    // 15 14 13 12 11 10
                    // 25 24 23 22 21 20
                    // 35 34 33 32 31 30
                    load_num_w = load_num_r + 1;
                    case(load_num_r)
                    0: begin
                        tileA_w[0] = tileA_data_0;
                        tileA_w[1] = tileA_data_1;
                        tileA_w[2] = tileA_data_2;
                        tileA_w[3] = tileA_data_3;
                        tileB_w[0] = tileB_data_0;
                        tileB_w[8] = tileB_data_1;
                        tileB_w[16] = tileB_data_2;
                        tileB_w[24] = tileB_data_3;
                    end
                    1: begin
                        tileA_w[8] = tileA_data_0;
                        tileA_w[9] = tileA_data_1;
                        tileA_w[10] = tileA_data_2;
                        tileA_w[11] = tileA_data_3;
                        tileB_w[1] = tileB_data_0;
                        tileB_w[9] = tileB_data_1;
                        tileB_w[17] = tileB_data_2;
                        tileB_w[25] = tileB_data_3;
                    end
                    2: begin
                        tileA_w[16] = tileA_data_0;
                        tileA_w[17] = tileA_data_1;
                        tileA_w[18] = tileA_data_2;
                        tileA_w[19] = tileA_data_3;
                        tileB_w[2] = tileB_data_0;
                        tileB_w[10] = tileB_data_1; 
                        tileB_w[18] = tileB_data_2;
                        tileB_w[26] = tileB_data_3;
                    end
                    3: begin
                        load_num_w = 0;
                        tileA_w[24] = tileA_data_0;
                        tileA_w[25] = tileA_data_1;
                        tileA_w[26] = tileA_data_2;
                        tileA_w[27] = tileA_data_3;
                        tileB_w[3] = tileB_data_0;
                        tileB_w[11] = tileB_data_1; 
                        tileB_w[19] = tileB_data_2;
                        tileB_w[27] = tileB_data_3;
                    end
                    endcase
                end
            end
            COMPUTE: begin
                propagate_num_w = propagate_num_r + 1;
                case(propagate_num_r)
                0,1,2,3,4,5,6: begin
                    for(int i=0; i<4; i++) begin
                        tileA_in[i] = tileA_r[i * 7 + propagate_num_r];
                        tileB_in[i] = tileB_r[i * 7 + propagate_num_r];
                    end
                    PE_en = 1;
                end
                11: begin // last cycle
                    PE_en = 1;
                    propagate_num_w = 0; // Reset for next tile
                end                    
                default: begin
                    PE_en = 1;
                end
                endcase
            end
            DONE: begin
                PE_out  = 1;
                o_valid = 1;
                PE_clear = 1;
            end
        endcase
    end

    // systolic array 

    //                 tileB_in[0]  tileB_in[1] tileB_in[2] tileB_in[3]
    //  tileA_in[0]    PE00         PE01        PE02        PE03
    //  tileA_in[1]    PE10         PE11        PE12        PE13
    //  tileA_in[2]    PE20         PE21        PE22        PE23
    //  tileA_in[3]    PE30         PE31        PE32        PE33

    always @(posedge clk or posedge rst) begin
        if (rst) begin
            for(int i = 0; i < 28; i++) begin
                tileA_r[i] <= 0;
                tileB_r[i] <= 0;
            end
        end
        else if(state_r == LOADING) begin
            // Load the tileA and tileB data
            for(int i = 0; i < 28; i++) begin
                tileA_r[i] <= tileA_w[i];
                tileB_r[i] <= tileB_w[i];
            end
        end
    end
    always @(posedge clk or posedge rst) begin
        if (rst) begin
            // Reset logic
            state_r <= LOADING;
            load_num_r <= 0;
            propagate_num_r <= 0;
        end else begin 
            // Write logic
            state_r <= state_w;
            load_num_r <= load_num_w;
            propagate_num_r <= propagate_num_w;
        end
    end


endmodule

module PE (
    input clk, 
    input rst, 
    input signed [15:0] a, 
    input signed [15:0] b, 
    input PE_clear, 
    input PE_en, 
    input PE_out,
    input PE_input_gate,
    output  signed [15:0] next_a, 
    output  signed [15:0] next_b,
    output reg  signed [15:0] o
);
    reg signed [35:0] o_w, o_r;
    reg signed [15:0] a_r, b_r, a_w, b_w;
    
    assign a_w = a;
    assign b_w = b;
    assign next_a = a_r;
    assign next_b = b_r;
    
    localparam signed [35:0] NEG_MAX = -(1<<30);
    localparam signed [35:0] POS_MAX = (1<<30) - 1;
    
    always@(*) begin
        o = 0;
        if (PE_out) begin
            if(o_r > 0) begin
                if(o_r >= POS_MAX) begin // > 1
                    o = 16'h7FFF; // Saturate to max positive value
                end else begin
                    o = {1'b0, o_r[29-:15]}; // Take the upper 15 bits and sign extend
                end
            end
            else begin
                if(o_r <= NEG_MAX) begin // < -1
                    o = 16'h8000; // Saturate to max negative value
                    
                end else begin
                    o = {1'b1, o_r[29-:15]}; // Take the upper 15 bits and sign extend
                end
            end
        end
    end
    always@(*) begin
        o_w = o_r;
        if (PE_clear) begin
            o_w = 0;
        end else if (PE_en) begin
            o_w = o_r + a * b;
        end
    end
    always @(posedge clk or posedge rst) begin
        if (rst) begin
            o_r <= 0;
        end
        else if(PE_en || PE_clear)begin 
            o_r <= o_w;
        end
    end
    always @(posedge clk or posedge rst) begin
        if (rst) begin
            a_r <= 0;
            b_r <= 0;
        end
        else if(PE_input_gate) begin 
            a_r <= a_w;
            b_r <= b_w;
        end
    end

endmodule

## Testbench

`timescale 1ns/10ps
`define CYCLE 	  4.4
`define MAX_CYCLE 5000
`define RST_DELAY `CYCLE/2
`define Matrix1_A_ref "./00_TB/data/matrix1_A.hex"
`define Matrix2_A_ref "./00_TB/data/matrix2_A.hex"
`define Matrix3_A_ref "./00_TB/data/matrix3_A.hex"
`define Matrix1_B_ref "./00_TB/data/matrix1_B.hex"
`define Matrix2_B_ref "./00_TB/data/matrix2_B.hex"
`define Matrix3_B_ref "./00_TB/data/matrix3_B.hex"
`define Matrix1_O_ref "./00_TB/data/matrix1_O.hex"
`define Matrix2_O_ref "./00_TB/data/matrix2_O.hex"
`define Matrix3_O_ref "./00_TB/data/matrix3_O.hex"
`define INST_ref      "./00_TB/data/inst.hex"

module test;

 // Ports
    wire            clk;
    wire            rst;
    wire            rst_n;
	logic           out_end;
	integer         correct, error, j, total_error;
            
	logic  [15:0]  matrixA1 [0:63], matrixA2 [0:63], matrixA3 [0:63];
    logic  [15:0]  matrixB1 [0:63], matrixB2 [0:63], matrixB3 [0:63];
    logic  [15:0]  matrixO1 [0:255], matrixO2 [0:255], matrixO3 [0:255];
	logic  [5:0]   inst_m  [0:3];

	// signal of top module
	logic [7:0] addr_A, addr_B, addr_I, addr_O;
	logic en_A, en_B, en_I, en_O;
	logic [15:0] data_A, data_B;
	logic [5:0]  data_I;
	logic [15:0] data_O;
    logic out_valid;
	logic ap_start, ap_done;

    logic [1:0] inst_length;

    integer s;

    initial begin
        $readmemh(`Matrix1_A_ref, matrixA1);
        $readmemh(`Matrix2_A_ref, matrixA2);
        $readmemh(`Matrix3_A_ref, matrixA3);
        $readmemh(`Matrix1_B_ref, matrixB1);
        $readmemh(`Matrix2_B_ref, matrixB2);
        $readmemh(`Matrix3_B_ref, matrixB3);
        $readmemh(`Matrix1_O_ref, matrixO1);
        $readmemh(`Matrix2_O_ref, matrixO2);
        $readmemh(`Matrix3_O_ref, matrixO3);
		$readmemh(`INST_ref, inst_m);
    end


    localparam SDFFILE = "../02_SYN/Netlist/top_syn.sdf";
    `ifdef SDF
        initial $sdf_annotate(SDFFILE, TOP);
    `endif


    // Modules
    clk_gen clk_gen_inst (
        .clk   (clk),
        .rst   (rst),
        .rst_n (rst_n)
    );
    TOP_Control TOP_Control (
        .clk 	  (clk),
		.rst 	  (rst),
		.addr_A  (addr_A),
		.en_A 	  (en_A),
		.data_A  (data_A),
		.addr_B  (addr_B),
		.en_B 	  (en_B),
		.data_B  (data_B),
		.addr_I  (addr_I),
		.en_I 	  (en_I),
		.data_I  (data_I),
		.addr_O  (addr_O),
		.data_O  (data_O),
        .en_O 	  (en_O),
        .out_valid(out_valid),
		.ap_start(ap_start),
		.ap_done (ap_done)

    );
    
    // initial begin
    //     $fsdbDumpfile("top.fsdb");
    //     $fsdbDumpvars(0, TOP_Control);
    // end
    initial begin
        $display(" Cycle Period = %0f ns", `CYCLE);
    end

    integer i, inst_i;
    // Input
    initial begin

        i = 0; inst_i = 0;
        addr_A = 0; addr_B = 0; addr_I = 0; addr_O = 0;
        en_A = 0; en_B = 0; en_I = 0;
        data_A = 0; data_B = 0; data_I = 0;
		ap_start = 0;

        out_end = 0;
        correct = 0;
        error   = 0;
        total_error = 0;
        en_O = 0;

        j = 0;

        // Waiting for reset to finish
        wait (rst_n === 1'b0);
        wait (rst_n === 1'b1);
        @(posedge clk);

        while(inst_m[inst_i] !== 0) begin
            $display("----------------------------------------------");
            $display("-             inst = %0d                      -", inst_m[inst_i]);
            @(negedge clk);
            addr_I = inst_i;
            en_I = 1;
            data_I = inst_m[inst_i];

            while(i < 64) begin
                @(negedge clk);
                en_I = 0;
                addr_A = i;             addr_B = i;
                en_A = 1;               en_B = 1;
                case(inst_i)
                0: begin
                    data_A = matrixA1[i]; data_B = matrixB1[i];
                end
                1: begin
                    data_A = matrixA2[i]; data_B = matrixB2[i];
                end
                2: begin
                    data_A = matrixA3[i]; data_B = matrixB3[i];
                end
                endcase

                i = i + 1;
            end
            i = 0;
            @(negedge clk);
            ap_start = 1;
            $display("-             START COMPUTE                  -");

            @(posedge ap_done);
            $display("-             CHECK ANSWER                   -");
            ap_start = 0;
            @(posedge clk);
            
            while (ap_done) begin
                @(negedge clk);
                addr_O = j;
                en_O = 1;
                @(negedge clk);
                en_O = 0;
                if(out_valid) begin
                    case(inst_i)
                    0: begin
                        if (data_O === matrixO1[j]) begin
                            correct = correct + 1;
                        end else begin
                            error = error + 1;
                            $display("Test[%d]: Error!, expected %h, got %h", j, matrixO1[j], data_O);
                        end
                    end
                    1: begin
                        if (data_O === matrixO2[j]) begin
                            correct = correct + 1;
                        end else begin
                            error = error + 1;
                            $display("Test[%d]: Error!, expected %h, got %h", j, matrixO2[j], data_O);
                        end
                    end
                    2: begin
                        if (data_O === matrixO3[j]) begin
                            correct = correct + 1;
                        end else begin
                            error = error + 1;
                            $display("Test[%d]: Error!, expected %h, got %h", j, matrixO3[j], data_O);
                        end
                    end
                    endcase
                end
                j = j + 1;
                if(j==256) begin
                    j = 0;
                    inst_i = inst_i + 1;
                    en_O = 0;
                    if(error == 0) begin
                        $display("----------------------------------------------");
                        $display("-              Test %0d PASS!                  -", inst_i);
                        $display("----------------------------------------------");
                    end
                    total_error = total_error + error;
                    error = 0;
                end
            end
        end
        out_end = 1;
    end

    // execution cycle
    integer exe_cycle;

    initial begin
        exe_cycle = 0;
        while(!out_end) begin
            while(ap_start) begin
                @(posedge clk);
                exe_cycle = exe_cycle + 1;
            end
            @(negedge clk);
        end
        $display("----------------------------------------------");
        $display("------------total time: %0d cycles-----------------", exe_cycle); 
        $display("----------------------------------------------");
        if(total_error == 0) begin
            $display("------------------------------------------------");
            $display("----------------All tests PASS!-----------------");
            $display("------------------------------------------------");
        end else begin
            $display("------------------------------------------------");
            $display("-              Total Error: %0d               -", total_error);
            $display("------------------------------------------------");
        end
        
        $finish;
    end

endmodule

module clk_gen(
    output reg clk,
    output reg rst,
    output reg rst_n
);
    always #(`CYCLE/2.0) clk = ~clk;
    // initial begin
    //     clk = 1'b0;
    //     rst = 1'b0; rst_n = 1'b1; #(              0.25  * `CYCLE);
    //     rst = 1'b1; rst_n = 1'b0; #((`RST_DELAY - 0.25) * `CYCLE);
    //     rst = 1'b0; rst_n = 1'b1; #(         `MAX_CYCLE * `CYCLE);
    //     $display("Error! Time limit exceeded!");
    //     $finish;
    // end

   initial begin
        clk = 1'b1; 
        rst = 1'b0;  rst_n = 1'b1; #(`CYCLE * 0.25);
        rst = 1'b1;  rst_n = 1'b0; #((`RST_DELAY)        * `CYCLE);
        rst = 1'b0; rst_n = 1'b1;
        
        #(`MAX_CYCLE * `CYCLE);
        $display("------------------------");
        $display("Error! Runtime exceeded!");
        $display("------------------------");
        $finish;
    end
endmodule


module TOP_Control (
    input clk,
    input rst, 
    input  [7:0]  addr_A, 
    input         en_A,
    input  [15:0] data_A,
    input  [7:0]  addr_B,
    input         en_B,
    input  [15:0] data_B,
    input  [7:0]  addr_I,
    input         en_I,
    input  [5:0]  data_I,
    input  [7:0]  addr_O,
    output [15:0] data_O,
    input         en_O,
    output        out_valid,
    input         ap_start,
    output        ap_done
);

    reg [15:0] matrixA_w[0:63], matrixA_r[0:63];
    reg [15:0] matrixB_w[0:63], matrixB_r[0:63];
    reg [15:0] matrixO1_w[0:255], matrixO1_r[0:255];
    reg [5:0]  inst_m_w  [0:5], inst_m_r  [0:5];

    reg tile_en;
    reg  [15:0] tileA_data [0:3], tileB_data [0:3];
    wire [15:0] tileO_data [0:15];
    wire o_valid;

    TOP TOP (
        .clk(clk),
        .rst(rst),
        .tile_en(tile_en),
        // .tileA_data(tileA_data),
        // .tileB_data(tileB_data),
        .tileA_data_0(tileA_data[0]),
        .tileA_data_1(tileA_data[1]),
        .tileA_data_2(tileA_data[2]),
        .tileA_data_3(tileA_data[3]),
        .tileB_data_0(tileB_data[0]),
        .tileB_data_1(tileB_data[1]),
        .tileB_data_2(tileB_data[2]),
        .tileB_data_3(tileB_data[3]),
        // .tileO_data(tileO_data),
        .tileO_data_0(tileO_data[0]),
        .tileO_data_1(tileO_data[1]),
        .tileO_data_2(tileO_data[2]),
        .tileO_data_3(tileO_data[3]),
        .tileO_data_4(tileO_data[4]),
        .tileO_data_5(tileO_data[5]),
        .tileO_data_6(tileO_data[6]),
        .tileO_data_7(tileO_data[7]),
        .tileO_data_8(tileO_data[8]),
        .tileO_data_9(tileO_data[9]),
        .tileO_data_10(tileO_data[10]),
        .tileO_data_11(tileO_data[11]),
        .tileO_data_12(tileO_data[12]),
        .tileO_data_13(tileO_data[13]),
        .tileO_data_14(tileO_data[14]),
        .tileO_data_15(tileO_data[15]),
        .o_valid(o_valid)
    );

    typedef enum logic [2:0] {
        IDLE, 
        LOADING, 
        FILLING,
        COMPUTE, 
        DONE, 
        FINISH
    } state_t;
    state_t state_w, state_r;

    reg [1:0] load_num_w, load_num_r;
    reg [2:0] inst_num_w, inst_num_r;
    reg [1:0] tile_row_num_w, tile_row_num_r;
    reg [1:0] tile_col_num_w, tile_col_num_r;

    reg [15:0] odata_w, odata_r;
    reg out_valid_w, out_valid_r;
    wire [2:0] inst_sel;
    

    assign ap_done   = (state_r == DONE);
    assign data_O    = odata_r;
    assign out_valid = out_valid_r;
    assign inst_sel = addr_I[2:0];
    

    always@(*)  begin
        state_w = state_r;
        case (state_r)
        IDLE: begin
            state_w = LOADING;
        end
        LOADING: begin
            if(ap_start) begin
                state_w = FILLING;
            end
            if(en_I && data_I==0) begin
                state_w = FINISH;
            end
        end
        FILLING: begin
            if(load_num_r == 3) begin
                state_w = COMPUTE;
            end
        end
        COMPUTE: begin
            if(o_valid) begin
                case(inst_m_r[inst_num_r])
                4: begin
                    state_w = DONE;
                end
                8: begin
                    if(tile_row_num_r == 1 && tile_col_num_r == 1) begin
                        state_w = DONE; // Move to DONE after filling first tile
                    end else begin
                        state_w = FILLING; // Continue filling next tile
                    end
                end
                16: begin
                    if(tile_row_num_r == 3 && tile_col_num_r == 3) begin
                        // state_w = DONE; // Move to DONE after filling second tile
                        state_w = DONE;
                    end else begin
                        state_w = FILLING; // Continue filling next tile
                    end
                end
                endcase
            end
        end
        DONE: begin
            if(en_O && addr_O == 255) begin
                state_w = LOADING; // Otherwise, go back to LOADING
            end
        end
        FINISH: begin
        end
        endcase
    end
    always @(posedge clk or posedge rst) begin
        if (rst) state_r <= IDLE;
        else state_r <= state_w;
    end

    
    always@(*)  begin
        for(int i=0; i<64; i++) begin
            matrixA_w[i] = matrixA_r[i];
            matrixB_w[i] = matrixB_r[i];
        end
        for(int i=0; i<256; i++) begin
            matrixO1_w[i] = matrixO1_r[i];
        end
        for(int i=0; i<6; i++) begin
            inst_m_w[i] = inst_m_r[i];
        end
        tile_en    = 0;
        load_num_w = load_num_r;
        inst_num_w = inst_num_r;
        tile_row_num_w = tile_row_num_r;
        tile_col_num_w = tile_col_num_r;
        for(int i=0; i<4; i++) begin
            tileA_data[i] = 0;
            tileB_data[i] = 0;
        end
        odata_w     = odata_r;
        out_valid_w = 0;

        case(state_r)
        IDLE: begin end
        LOADING: begin
            if(en_A) begin // and en_B
                matrixA_w[addr_A] = data_A;
                matrixB_w[addr_B] = data_B;
            end
            if(en_I && data_I!=0) begin
                inst_m_w[inst_sel] = data_I;
            end
        end
        FILLING: begin
            load_num_w = load_num_r + 1;
            tile_en = 1; // Enable tile for processing
            if(load_num_r == 3) begin        
                load_num_w = 0;
            end
            // 0000 0001 0010 0011
            // 0100 0101 0110 0111
            // 1000 1001 1010 1011
            // 1100 1101 1110 1111
            case(load_num_r)
            0: begin
                for(int i=0;i<4;i++) begin
                    tileA_data[i] = matrixA_r[4*tile_row_num_r*4 + i];
                    tileB_data[i] = matrixB_r[4*tile_col_num_r + i];
                end
            end
            1: begin
                for(int i=0;i<4;i++) begin
                    tileA_data[i] = matrixA_r[(4*tile_row_num_r + 1)*4 + i];
                    tileB_data[i] = matrixB_r[16 + 4*tile_col_num_r + i];
                end
            end
            2: begin
                for(int i=0;i<4;i++) begin
                    tileA_data[i] = matrixA_r[(4*tile_row_num_r + 2)*4 + i];
                    tileB_data[i] = matrixB_r[32 + 4*tile_col_num_r + i];
                end
            end
            3: begin
                for(int i=0;i<4;i++) begin
                    tileA_data[i] = matrixA_r[(4*tile_row_num_r + 3)*4 + i];
                    tileB_data[i] = matrixB_r[48 + 4*tile_col_num_r + i];
                end
            end
            endcase
        end
            
        COMPUTE: begin
            if(o_valid) begin
                for(int i=0; i<4; i++) begin
                    for(int j=0; j<4; j++) begin
                        matrixO1_w[(4*tile_row_num_r + i)*16 + 4*tile_col_num_r + j] = tileO_data[4*i + j];

                    end
                end
                case(inst_m_r[inst_num_r])
                4: begin
                end
                8: begin
                    tile_col_num_w = tile_col_num_r + 1;
                    if(tile_col_num_r == 1) begin
                        tile_row_num_w = 1;
                        tile_col_num_w = 0; // Reset column for next row
                        if(tile_row_num_r == 1) begin
                            tile_row_num_w = 0;
                        end
                    end
                end
                16: begin
                    tile_col_num_w = tile_col_num_r + 1;
                    if(tile_col_num_r == 3) begin
                        tile_row_num_w = tile_row_num_r + 1;
                        tile_col_num_w = 0; // Reset column for next row
                        if(tile_row_num_r == 3) begin
                            tile_row_num_w = 0;
                        end
                    end
                end
                endcase
            end
        end
        DONE: begin
            if(en_O) begin
                out_valid_w = 1;
                // odata_w = matrixO1_r[addr_O[7:4]][addr_O[3:0]];
                odata_w = matrixO1_r[addr_O]; // Flattened access
                if(addr_O == 255) begin
                    inst_num_w = inst_num_r + 1;
                    for(int i = 0; i < 256; i++) begin
                        matrixO1_w[i] = 0; // Reset output matrix after reading
                    end
                end
            end
            
        end
        endcase
    end
    always @(posedge clk or posedge rst) begin
        if(rst) begin
            for(int i = 0; i < 256; i++) begin
                matrixO1_r[i] <= 0;
            end
        end
        else if(state_r == COMPUTE || state_r == DONE) begin
            for(int i = 0; i < 256; i++) begin
                matrixO1_r[i] <= matrixO1_w[i];
            end
        end
    end
    always @(posedge clk or posedge rst) begin
        if (rst) begin
            for(int i = 0; i < 64; i++) begin
                matrixA_r[i] <= 0;
                matrixB_r[i] <= 0;
            end
            for (int k = 0; k < 6; k++) begin
                inst_m_r[k] <= 0;
            end
        end else if(state_r == LOADING) begin

            for(int i = 0; i < 64; i++) begin
                matrixA_r[i] <= matrixA_w[i];
                matrixB_r[i] <= matrixB_w[i];
            end
            for (int k = 0; k < 6; k++) begin
                inst_m_r[k] <= inst_m_w[k];
            end
        end
    end

    always @(posedge clk or posedge rst) begin
        if(rst) begin
            odata_r <= 0;
        end
        else if(state_r == DONE) begin
            odata_r <= odata_w;
        end
    end

    always @(posedge clk or posedge rst) begin
        if(rst) begin
            out_valid_r    <= 0;
            load_num_r     <= 0;
            inst_num_r     <= 0;
            tile_row_num_r <= 0;
            tile_col_num_r <= 0;
        end else begin
            out_valid_r    <= out_valid_w;
            load_num_r     <= load_num_w;
            inst_num_r     <= inst_num_w;
            tile_row_num_r <= tile_row_num_w;
            tile_col_num_r <= tile_col_num_w;
        end
    end


endmodule