module bit_repr_matrix(input logic   clk,
    input logic  reset,
    input logic start,
    input logic [3:0]  width, // n <= 8
    input logic [3:0]  length, // n <= 8
    input logic signed [31:0] S_ij,
    input logic [7:0] ell, // l <= 32

    output logic [3:0]    output_length, // at most length
    output logic [7:0]    output_width, // at most 256
    output logic signed [31:0]   data_out, // prev memory or S_star_ij
    output logic done);
    
    logic write_enable;
    logic read_enable;
    logic comp_enable;
    logic [3:0] write_index;
    logic [7:0] comp_index;
    logic [7:0] read_index;
    logic [3:0] loaded_row_num;
    logic [3:0] comp_row_num;
    logic [3:0] spitting_row_num;

	integer i;
    // initialize a DMEM to store input vector.
    // maximum number of 8 rows are supported.
    logic signed [31:0] input_mem0 [7:0]; // n <= 8
    logic signed [31:0] input_mem1 [7:0];
    logic signed [31:0] input_mem2 [7:0];
    logic signed [31:0] input_mem3 [7:0];
    logic signed [31:0] input_mem4 [7:0];
    logic signed [31:0] input_mem5 [7:0];
    logic signed [31:0] input_mem6 [7:0];
    logic signed [31:0] input_mem7 [7:0];
    always_ff @(posedge clk) begin
        if (reset) begin
            for(i = 0; i <= 7; i = i+1)begin
                input_mem0[i] <= 0;
                input_mem1[i] <= 0;
                input_mem2[i] <= 0;
                input_mem3[i] <= 0;
                input_mem4[i] <= 0;
                input_mem5[i] <= 0;
                input_mem6[i] <= 0;
                input_mem7[i] <= 0;
            end
            write_enable <= 0;
            write_index <= 0;
            loaded_row_num <= 0;

        end else if (start || write_enable) begin
            if (write_index[3:0] < width) begin
                write_enable <= 1;

                case (loaded_row_num)
                    4'b0000: begin
                        input_mem0[write_index[2:0]] <= S_ij;
                    end
                    4'b0001: begin
                        input_mem1[write_index[2:0]] <= S_ij;
                    end
                    4'b0010: begin
                        input_mem2[write_index[2:0]] <= S_ij;
                    end
                    4'b0011: begin
                        input_mem3[write_index[2:0]] <= S_ij;
                    end
                    4'b0100: begin
                        input_mem4[write_index[2:0]] <= S_ij;
                    end
                    4'b0101: begin
                        input_mem5[write_index[2:0]] <= S_ij;
                    end
                    4'b0110: begin
                        input_mem6[write_index[2:0]] <= S_ij;
                    end
                    4'b0111: begin
                        input_mem7[write_index[2:0]] <= S_ij;
                    end
                    default: begin
                        input_mem0[write_index[2:0]] <= 0;
                    end
                endcase

                write_index <= write_index + 1;
                if (write_index + 1 == width) begin
                    write_index <= 0;
                    loaded_row_num <= loaded_row_num + 1;
                    if (loaded_row_num + 1 == length) begin
                        comp_enable <= 1;
                        write_enable <= 0;
                        loaded_row_num <= 0;
                        write_index <= 0;
                    end
                end
            end
        end else if (read_enable) begin
            // set computational flag to false
            comp_enable <= 0;
        end
    end

    assign output_width = width * ell;
    assign output_length = length;
    
    logic signed [31:0] n; // corresponding to n-th element in the input vector
    logic [7:0] comp_input_index;
    logic [7:0] expo_index;

    // initialize a DMEM to store output vector.
    // maximum number of 8 rows are supported.
    logic signed [31:0] output_mem0 [255:0]; // n <= 256
    logic signed [31:0] output_mem1 [255:0];
    logic signed [31:0] output_mem2 [255:0];
    logic signed [31:0] output_mem3 [255:0];
    logic signed [31:0] output_mem4 [255:0];
    logic signed [31:0] output_mem5 [255:0];
    logic signed [31:0] output_mem6 [255:0];
    logic signed [31:0] output_mem7 [255:0];

    // do computations when computational flag is true
    always_ff @(posedge clk) begin
        if (comp_enable) begin
            if (comp_index < output_width) begin

                case (comp_row_num)
                    4'b0000: begin
                        n = input_mem0[comp_input_index[2:0]];
                        output_mem0[comp_index] <= 2**(ell-expo_index-1) * n;
                    end

                    4'b0001: begin
                        n = input_mem1[comp_input_index[2:0]];
                        output_mem1[comp_index] <= 2**(ell-expo_index-1) * n;
                    end

                    4'b0010: begin
                        n = input_mem2[comp_input_index[2:0]];
                        output_mem2[comp_index] <= 2**(ell-expo_index-1) * n;
                    end

                    4'b0011: begin
                        n = input_mem3[comp_input_index[2:0]];
                        output_mem3[comp_index] <= 2**(ell-expo_index-1) * n;
                    end
                    4'b0100: begin
                        n = input_mem4[comp_input_index[2:0]];
                        output_mem4[comp_index] <= 2**(ell-expo_index-1) * n;
                    end
                    4'b0101: begin
                        n = input_mem5[comp_input_index[2:0]];
                        output_mem5[comp_index] <= 2**(ell-expo_index-1) * n;
                    end
                    4'b0110: begin
                        n = input_mem6[comp_input_index[2:0]];
                        output_mem6[comp_index] <= 2**(ell-expo_index-1) * n;
                    end
                    4'b0111: begin
                        n = input_mem7[comp_input_index[2:0]];
                        output_mem7[comp_index] <= 2**(ell-expo_index-1) * n;
                    end
                    default: begin
                        n = input_mem0[comp_input_index[2:0]];
                        output_mem0[comp_index] <= 2**(ell-expo_index-1) * n;
                    end
                endcase

                comp_index <= comp_index + 1;
                comp_input_index <= (comp_index + 1) / ell;

                if (expo_index == ell - 1) expo_index <= 0;
                else expo_index <= expo_index + 1;
                
                if (comp_index + 1 == output_width) begin
                    comp_index <= 0;
                    comp_input_index <= 0;
                    expo_index <= 0;
                    comp_row_num <= comp_row_num + 1;
                    if (comp_row_num + 1 == length) begin
                        comp_input_index <= 0;
                        expo_index <= 0;
                        comp_index <= 0;
                        comp_row_num <= 0;
                        // once finished, set read_enable to true
                        read_enable <= 1;
                    end
                end
            end
        end else if (done) begin
            read_enable <= 0;
        end
    end

    // read element in each row every clock cycle
    always_ff @(posedge clk) begin
        if (read_enable) begin
            if (read_index < output_width) begin

                // spitting out one element each cycle with appropriate index
                case (spitting_row_num)
                    4'b0000: begin
                        data_out <= output_mem0[read_index];
                    end

                    4'b0001: begin
                        data_out <= output_mem1[read_index];
                    end

                    4'b0010: begin
                        data_out <= output_mem2[read_index];
                    end

                    4'b0011: begin
                        data_out <= output_mem3[read_index];
                    end
                    4'b0100: begin
                        data_out <= output_mem4[read_index];
                    end
                    4'b0101: begin
                        data_out <= output_mem5[read_index];
                    end
                    4'b0110: begin
                        data_out <= output_mem6[read_index];
                    end
                    4'b0111: begin
                        data_out <= output_mem7[read_index];
                    end
                    default: begin
                        data_out <= output_mem0[read_index];
                    end
                endcase
                read_index <= read_index + 1;
                if (read_index + 1 == output_width) begin
                    read_index <= 0;
                    spitting_row_num <= spitting_row_num + 1;
                    if (spitting_row_num + 1 == output_length) begin
                        spitting_row_num <= 0;
                        read_index <= 0;
                        done <= 1;
                    end
                end
            end
        end
    end
     
endmodule