Everything in this series has been building toward one thing, an engine that multiplies matrices, because matrix multiplication is the operation that dominates signal processing and machine-learning inference, and doing it fast in dedicated hardware is exactly what an FPGA is for. This capstone assembles the pieces from the earlier pages. The multiply-accumulate from the arithmetic page sits on a DSP slice, the pipelining from its own page keeps the clock fast, block RAM holds the operands, and an AXI-Stream handshake feeds the data in. The result is the same structure that sits underneath inference on a real accelerator.
The dot product is the unit of work
Multiplying two matrices means computing, for each output element, the dot product of a row of the first with a column of the second,
$$C_{ij} = \sum_{k} A_{ik} \, B_{kj}.$$
So the tile to build well is a dot product. Lay several multipliers side by side, one per term, feed their products into an adder tree, and register every level so the whole thing is pipelined. The result is a unit that takes one pair of vectors per cycle and produces one dot product a few cycles later, holding full throughput. Here it is for four elements, four signed multiplies into a two-level tree, three pipeline stages deep, with a valid bit riding along.
dot4.svmodule dot4 #(
parameter int W = 16
)(
input logic clk,
input logic rst,
input logic in_valid,
input logic [4*W-1:0] a_flat, // four W-bit signed elements
input logic [4*W-1:0] b_flat,
output logic signed [2*W+1:0] dot,
output logic out_valid
);
logic signed [W-1:0] a0, a1, a2, a3, b0, b1, b2, b3;
assign a0 = a_flat[0*W +: W]; assign a1 = a_flat[1*W +: W];
assign a2 = a_flat[2*W +: W]; assign a3 = a_flat[3*W +: W];
assign b0 = b_flat[0*W +: W]; assign b1 = b_flat[1*W +: W];
assign b2 = b_flat[2*W +: W]; assign b3 = b_flat[3*W +: W];
logic signed [2*W-1:0] p0, p1, p2, p3; // stage 1, products
logic signed [2*W:0] s0, s1; // stage 2, partial sums
logic v1, v2, v3;
always_ff @(posedge clk) begin
if (rst) begin
v1 <= 0; v2 <= 0; v3 <= 0;
end else begin
p0 <= a0 * b0; p1 <= a1 * b1; p2 <= a2 * b2; p3 <= a3 * b3; v1 <= in_valid;
s0 <= p0 + p1; s1 <= p2 + p3; v2 <= v1;
dot <= s0 + s1; v3 <= v2;
end
end
assign out_valid = v3;
endmoduleThe widths grow exactly enough at each stage so nothing overflows, a product is twice the input width and each addition adds one bit. The testbench in the repository streams five hundred random four-element vector pairs through it and checks each dot product against a reference, confirming both the arithmetic and the three-cycle timing.
From a tile to a systolic array
One dot-product tile computes one output element's worth of work per cycle once the pipeline is full. To multiply whole matrices you replicate the tile and arrange for data to flow through the copies so each operand is read once and reused across many multiplies, because fetching operands, not multiplying them, is what limits a real design. The arrangement that does this best is the systolic array, a grid of small processing elements where each element holds a running sum, takes an activation from its left neighbor and a weight from the one above, multiplies and accumulates them, and passes both operands along to the next element. Data marches through the grid in a wavefront, and after it has swept across, each element holds one output of the matrix product. Because every operand entering the edge of the grid is reused down a whole row or column, the array does an enormous number of multiplies for very little data movement. This is precisely the structure at the heart of the tensor units in modern machine-learning accelerators, and on an FPGA the processing elements map straight onto the DSP slices.
Why this belongs on an FPGA
A processor runs matrix multiplication well, so the question is what the FPGA adds. The answer is a datapath shaped exactly to the problem and to the numbers it uses. When a model runs in eight-bit or even smaller fixed-point, an FPGA packs many more multiply-accumulates into the same silicon than a general unit spends on wide floating point, and streams them at a steady rate with predictable, low latency and modest power. That combination, custom precision and a dataflow with no instruction overhead, is why FPGAs sit in inference appliances and in the datacenter beside processors. The path from the lookup table and flip-flop at the start of this series to this array is the whole argument for the technology, that when the shape of the computation is fixed and known, building the exact circuit for it wins.
One thing remains, the discipline that makes any of this trustworthy. Every module here came with a testbench that checked it. The final page is about that discipline, verification.