Generator for configurable systolic arrays. Supports configurable dimensions, precision, and input and weight stationary dataflows. Integrates with Rocket as an RoCC accelerator.
tileRows,tileColumns- A Tile is a fully combinational 2D array of PEs with dimension (
tileRowsxtileColumns)
- A Tile is a fully combinational 2D array of PEs with dimension (
meshRows,meshColumns- A Mesh is a pipelined 2D array of Tiles with dimension (
meshRowsxmeshColumns) - A Mesh is the top-level 'core' systolic array structure
- It can natively perform a
matmulof square and equal dimension matrices- e.g. C = A (m x n)
matmulB (n x k) wherem = n = k = tileRows x meshRows = tileColumns x meshColumns
- e.g. C = A (m x n)
- To perform matmul with non-square matrices, arbitrary inner dimension
n, or matrices larger than the Mesh, requires software to break the computation down into the Mesh's primitivematmul
- A Mesh is a pipelined 2D array of Tiles with dimension (
dataWidth=dataBytesx 8- The native elaboration-time data width (in bits) of the PEs MAC unit and input/output wires
- Nominally
dataWidth = 8, dataBytes = 1as we are designing an INT8 accelerator
- For simplicity, we assume
meshRows = meshColumnsandtileRows = tileColumns
Format: mvin rs1, rs2
rs1= virtual DRAM address (byte addressed) to load into scratchpadrs2= local scratchpad address (systolic array single-axis addressed; i.e.tileColumns x meshColumns x dataBytesbytes of data are captured in 1 address)- the highest bits of
rs2determine the bank number and the lowest bits determine the entry in the scratchpad
- the highest bits of
funct= 2
Action: Scratchpad[rs2] <= DRAM[Translate[rs1]]
- Loads a fixed amount of data (
tileColumns x meshColumns x tileRows x meshRows x dataBytesbytes) into the scratchpad - Load is sequential from the rs1/rs2 base addresses. Stride must be set by the
config_mvincommand
Format: mvout rs1, rs2
rs1= virtual DRAM address (byte addressed) to write to from scratchpadrs2= local scratchpad address (systolic array single-axis addressed; i.e.tileColumns x meshColumns x dataBytesbytes of data are captured in 1 address)- the highest bits of
rs2determine the bank number and the lowests bits determine the entry in the scratchpad - if the 32nd bit is 1,
rs2refers the accumulator memory space. In this case, the bitwidth of the elements is the accumulated result bitwidth
- the highest bits of
funct= 3
Action: DRAM[Translate[rs2]] <= Scratchpad[rs1]
- Stores a fixed amount of data (
tileColumns x meshColumns x tileRows x meshRows x dataBytesbytes) from the scratchpad to L2/DRAM - Store is sequential from the rs1/rs2 base addresses. Stride must be set by the
config_mvoutcommand
Format: config_ex rs1 rs2
rs1=rs1[0:1]must be00.rs1[2]will determine if output (0) or weight (1) stationary.rs1[4:3]will determine the activation function: either relu (1), relu6 (2), or no activation function (0).rs[63:32]is the number of bits by which the accumulated result of a matmul is right-shifted when leaving the accumulatorrs2[31:0]= the number of bits by which the accumulated result of a matmul is right-shifted when leaving the systolic arrayrs2[63:32]= the number of bits by which 6 should be left-shifted before applying relu6funct= 0
Action: mode <= rs1(2); shift <= rs2
Format: config_mvin rs1 rs2
rs1=rs1[0:1]must be01rs2= the stride in bytesfunct= 0
Action: stride <= rs2
Format: config_mvout rs1 rs2
rs1=rs1[0:1]must be10rs2= the stride in bytesfunct= 0
Action: stride <= rs2
Format: flush rs1
rs1= Ifrs1[0]is 1, then the current TLB request is skipped (if its waiting for an interrupt). Otherwise, the current TLB request is repeated.
Notes:
- This instruction executes as soon as it is received without waiting for other instructions which may be queued up. It is the programmer's responsibility to insert fences if necessary.
- Dependency bits cannot be appended to this instruction.
Every single matrix multiply operation is a combination of matmul.preload and matmul.compute (due to the length of a single instruction it was split into two instructions). matmul.preload should precede the matmul.compute.
Example:
//// first matmul ////
// rs1 = InputD
// rs2 = OutputC
// rs3 = InputA
// rs4 = InputB
//matmul InputA InputB OutputC InputD
1. matmul.preload $rs1 $rs2
2. matmul.compute $rs3 $rs4
Action: Scratchpad[rs2] <= Scratchpad[rs3] * Scratchpad[rs4] + Scratchpad[rs1]
Notes on addressing:
- For B or D, the address can be replaced with all high bits to input a 0 matrix instead.
- If the 32nd bit of any address is high, it will point to the accumulator's memory space.
Format: matmul.preload rs1, rs2
rs1= local scratchpad address (systolic array single-axis addressed) of D matrix (when output-stationary), or B matrix (when weight-stationary)rs2= local scratchpad address (systolic array single-axis addressed) of C matrix. If this is set to all high bits, then C will not be written to the scratchpad. If the 32nd and 31st bits are high, the result will be accumulated on top of the previous result pointed to by this address in the accumulatorfunct= 6
Commit Behavior: This instruction commits on the cycle after the systolic array receives it. The systolic array remains idle until the subsequent OS/WS specific instructions are seen.
Format: matmul.compute.preloaded rs1, rs2
rs1= local scratchpad address (systolic array single-axis addressed) of A matrixrs2= local scratchpad address (systolic array single-axis addressed) of B matrix (when output-stationary), or D matrix (when weight-stationary)funct= 4- This instruction will compute on the value preloaded (D if output-stationary, or B if weight-stationary)
Format: matmul.compute.accumulated rs1, rs2
funct= 5rs1andrs2have the same encoding as thematmul.compute.preloadedencoding- If output-stationary, this instruction will compute on the previously computed result (C) in the systolic array
- If weight-stationary, this instruction will compute on the previously preloaded weights (B) in the systolic array
- mvin -> multseq (handled by SW and instruction stream ordering)
- multseq -> multseq (handled by HW, SW doesn't have to worry about polling for completion if there exists a mult -> mult RAW dependency)
- multseq -> mvout (handled by HW, blocking on all previous mults completing)
- mvout -> multseq (WAR dependency is handled by instruction ordering, mvout is blocking)
We want to calculate C = A x B + D.
- Dimensions: A (m x n), B (n x k), D (m x k), C (m x k)
- For simplicity assume m = n = k =
tileRowsxmeshRows=tileColsxmeshCols- In our typical systolic array parameterization,
tileRows = tileCols = 2, andmeshRows = meshCols = 8
- In our typical systolic array parameterization,
- Assume A.T (transposed A), B, D are stored in DRAM row-major
- A needs to be fed into the systolic array column-wise (so it is stored transposed), while B needs to be fed in row-wise
- D is fed in row-wise from the top of the systolic array (this is done before A and B are fed simultaneously)
This sequence of instructions performs the matmul:
- Assume A, B, D are stored at addresses DaddrA, DaddrB, DaddrD in DRAM
- Assume there's space in DRAM at DaddrC for the C result matrix
- Assume the SW has reserved scratchpad space for A, B, C, D at addresses SaddrA, SaddrB, SaddrC, SaddrD
for (i = 0; i < n; ++i) {
mvin (DaddrA + i*m*dataBytes) (SaddrA + i*m*dataBytes)
}
for (i = 0; i < n; ++i) {
mvin (DaddrB + i*k*dataBytes) (SaddrB + i*k*dataBytes)
}
for (i = 0; i < m; ++i) {
mvin (DaddrD + i*k*dataBytes) (SaddrD + i*k*dataBytes)
}
matmul.preload SaddrD
matmul.os1 SaddrA, SaddrB
matmul.os2 SaddrC
for (i = 0; i < m; ++i) {
mvout (DaddrC + i*k*dataBytes) (SaddrD + i*k*dataBytes)
}