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
|