File size: 6,979 Bytes
2166d7e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
/**
 * 8x8 Systolic Array for Matrix Multiplication
 * 
 * Computes matrix product: C = A @ B^T
 * - A: 8×8 matrix (Q tile, broadcast row-wise from left)
 * - B: 8×8 matrix (K^T tile, broadcast column-wise from top)
 * - C: 8×8 output matrix (partial products accumulated bottom-wise)
 * 
 * Dataflow (Systolic):
 * - Input A enters from left, shifts right across rows
 * - Input B enters from top, shifts down across columns
 * - Partial sums propagate downward
 * 
 * Pipeline Stages:
 * - Each PE has 3-stage internal pipeline
 * - Systolic latency: 3 + 8 = 11 cycles (first output valid after 11 cycles)
 * 
 * Data Format:
 * - All data: 8-bit signed fixed-point (Q4.4)
 * - Accumulators: 16-bit signed (to handle multiplication overflow)
 * 
 * Example data flow (first output):
 * Cycle 1:   A[0,0], B[0,0] enter top-left PE
 * Cycles 2-3: Pipelined processing in PE
 * Cycle 4-10: Data propagates to neighbors
 * Cycle 11:   First valid output at C[0,0]
 */

module systolic_array #(
    parameter DATA_WIDTH = 8,      // Input/output data width (Q4.4)
    parameter ACC_WIDTH = 16,      // Accumulator width
    parameter PE_ROWS = 8,         // Number of rows
    parameter PE_COLS = 8          // Number of columns
) (
    input  logic                                        clk,
    input  logic                                        rst_n,
    
    // Control
    input  logic                                        valid_in,
    output logic                                        valid_out,
    
    // Input: A matrix data (from left side, feeds all rows)
    // Array shape: [PE_ROWS][1] - one column (left edge)
    input  logic [PE_ROWS-1:0][DATA_WIDTH-1:0]         matrix_A_in,
    
    // Input: B matrix data (from top side, feeds all columns)
    // Array shape: [1][PE_COLS] - one row (top edge)
    input  logic [PE_COLS-1:0][DATA_WIDTH-1:0]         matrix_B_in,
    
    // Output: C matrix data (from bottom side, all accumulators)
    // Array shape: [PE_ROWS][PE_COLS]
    output logic [PE_ROWS-1:0][PE_COLS-1:0][ACC_WIDTH-1:0] matrix_C_out
);

    // =====================================================================
    // Internal Signals: Systolic Mesh Connections
    // =====================================================================
    
    // A dataflow: row-wise propagation (left to right)
    // shape: [PE_ROWS][PE_COLS+1] - extra column for input
    logic [PE_ROWS-1:0][PE_COLS:0][DATA_WIDTH-1:0] A_mesh;
    
    // B dataflow: column-wise propagation (top to bottom)
    // shape: [PE_ROWS+1][PE_COLS] - extra row for input
    logic [PE_ROWS:0][PE_COLS-1:0][DATA_WIDTH-1:0] B_mesh;
    
    // C dataflow: column-wise propagation (top to bottom)
    // shape: [PE_ROWS+1][PE_COLS]
    logic [PE_ROWS:0][PE_COLS-1:0][ACC_WIDTH-1:0] C_mesh;
    
    // Valid signals: one per PE (for pipeline control)
    logic [PE_ROWS-1:0][PE_COLS-1:0] pe_valid_in;
    logic [PE_ROWS-1:0][PE_COLS-1:0] pe_valid_out;
    
    // =====================================================================
    // Input Boundary Conditions
    // =====================================================================
    
    // A enters from left: A_mesh[:][0] = matrix_A_in[:]
    always_comb begin
        for (int i = 0; i < PE_ROWS; i++) begin
            A_mesh[i][0] = matrix_A_in[i];
        end
    end
    
    // B enters from top: B_mesh[0][:] = matrix_B_in[:]
    always_comb begin
        for (int j = 0; j < PE_COLS; j++) begin
            B_mesh[0][j] = matrix_B_in[j];
        end
    end
    
    // C enters as zeros (partial sums start from zero)
    always_comb begin
        for (int j = 0; j < PE_COLS; j++) begin
            C_mesh[0][j] = {ACC_WIDTH{1'b0}};
        end
    end
    
    // Valid signal broadcast to top-left corner
    always_comb begin
        pe_valid_in[0][0] = valid_in;
        for (int i = 1; i < PE_ROWS; i++) begin
            for (int j = 0; j < PE_COLS; j++) begin
                pe_valid_in[i][j] = 1'b0;  // Only top-left gets valid signal
            end
        end
    end
    
    // =====================================================================
    // Systolic Mesh: Generate and Connect PE Array
    // =====================================================================
    
    generate
        for (genvar i = 0; i < PE_ROWS; i++) begin : gen_rows
            for (genvar j = 0; j < PE_COLS; j++) begin : gen_cols
                
                processing_element #(
                    .DATA_WIDTH(DATA_WIDTH),
                    .ACC_WIDTH(ACC_WIDTH),
                    .INIT_VALUE({ACC_WIDTH{1'b0}})
                ) pe_inst (
                    .clk(clk),
                    .rst_n(rst_n),
                    
                    // Input
                    .valid_in(pe_valid_in[i][j]),
                    .matrix_A_in(A_mesh[i][j]),      // From left
                    .matrix_B_in(B_mesh[i][j]),      // From top
                    .matrix_C_in(C_mesh[i][j]),      // From top
                    
                    // Output
                    .valid_out(pe_valid_out[i][j]),
                    .matrix_A_out(A_mesh[i][j+1]),   // To right
                    .matrix_C_out(C_mesh[i+1][j])    // To bottom
                );
                
                // Extract output: C_out[i][j]
                assign matrix_C_out[i][j] = C_mesh[i+1][j];
            end
        end
    endgenerate
    
    // =====================================================================
    // Output Valid Signal
    // =====================================================================
    // The output is valid when data exits bottom-right corner PE
    // Due to 3-stage PE pipeline, this occurs 3 cycles after bottom-right PE
    // processes data. For an 8x8 array, diagonal propagation takes:
    // 3 (PE internal) + 7 (diagonal to bottom-right) + 3 (output from PE) = 13 cycles
    
    // Simpler approach: use a valid propagation counter
    // valid_out pulses when first block of data reaches output
    
    logic [3:0] valid_counter;  // Count cycles (0-15 sufficient for 13-cycle latency)
    logic       output_valid_q;
    
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            valid_counter <= 4'b0;
            output_valid_q <= 1'b0;
        end else begin
            // Count up to 13 (11 for propagation + 2 for pipeline sync)
            if (valid_in && valid_counter == 4'b0) begin
                valid_counter <= 4'd1;
            end else if (valid_counter > 4'b0 && valid_counter < 4'd13) begin
                valid_counter <= valid_counter + 1'b1;
            end else if (valid_counter == 4'd13) begin
                output_valid_q <= 1'b1;
                valid_counter <= 4'b0;  // Reset for next block
            end else begin
                output_valid_q <= 1'b0;
            end
        end
    end
    
    assign valid_out = output_valid_q;

endmodule : systolic_array