Arithmetic and the DSP slice

FPGA design · Building blocks · Jul 2026

Arithmetic is where an FPGA either shines or struggles, and the difference is whether the design uses the hardened blocks the fabric provides. Addition rides a dedicated carry path and is cheap. Multiplication is expensive in general logic but nearly free in the hardened multiply-accumulate blocks, and those blocks are the reason an FPGA can hold its own against a processor on signal processing and machine-learning inference. This page walks from the adder up to that block and to the fixed-point number formats that feed it.

Adders and the carry chain

Adding two $n$-bit numbers produces a carry at each bit that the next bit needs, so the obvious ripple-carry adder has a path that runs the full width of the operands. Textbooks answer this with the carry-lookahead adder, which computes the carries in parallel from generate and propagate signals and so trades more logic for a shorter path. On an FPGA you usually write neither structure by hand. The fabric has a dedicated carry chain running up each column of slices, far faster than routing a carry through general lookup tables, and when you write a plus sign the tools place the per-bit sums in the lookup tables and the carry on that chain. The practical result is that a wide adder is fast and compact without any cleverness on your part, which is why the adder on the combinational page is a single line.

Fixed-point, the number format of hardware

Floating point exists on FPGAs but costs real resources, and most signal processing and a great deal of inference run instead in fixed-point, which is just an integer with an agreed position for the binary point. A number in $Q_{m.f}$ format has $m$ integer bits and $f$ fractional bits and represents the integer value divided by $2^{f}$. Addition of two fixed-point numbers is plain integer addition as long as the points line up. Multiplication of a $Q_{m.f}$ value by another multiplies the integers and adds the fractional widths, giving a $Q_{2m.2f}$ result that is then rounded or truncated back to the working width. Keeping track of where the point sits, and of how many bits the values need so they never overflow silently, is most of the real work in a fixed-point datapath.

The multiply-accumulate and the DSP slice

The single operation that dominates filters, transforms, and neural networks is the multiply-accumulate, the running sum $\mathrm{acc} \leftarrow \mathrm{acc} + a \cdot b$. It is so common that the fabric hardens it into columns of digital signal processing (DSP) slices, each a fixed circuit that multiplies two moderately wide signed numbers and adds the product into a wide accumulator every clock. A typical slice handles a 27 by 18 bit multiply feeding a 48 bit accumulator, and the wide accumulator is deliberate, since summing many products needs headroom to avoid overflow. Writing the operation plainly, with registers around it, is what lets the tools map it onto a slice and run it at full speed.

mac.svmodule mac #(
    parameter int A_W   = 18,
    parameter int B_W   = 18,
    parameter int ACC_W = 48
)(
    input  logic                     clk,
    input  logic                     rst,
    input  logic                     en,
    input  logic                     clear,   // start a fresh accumulation
    input  logic signed [A_W-1:0]    a,
    input  logic signed [B_W-1:0]    b,
    output logic signed [ACC_W-1:0]  acc
);
    always_ff @(posedge clk) begin
        if (rst)        acc <= '0;
        else if (en) begin
            if (clear)  acc <= a * b;              // first term of a new sum
            else        acc <= acc + (a * b);      // accumulate
        end
    end
endmodule

The clear input starts a new accumulation with the first product rather than adding to whatever was there, which is how you reuse one slice across many separate dot products back to back. The accumulator is 48 bits wide so that a long sum of 36-bit products cannot overflow for any realistic length. The testbench in the repository accumulates dozens of random signed products and checks the running total against a reference on every cycle.

Where this is heading

One multiply-accumulate per cycle is the seed. Put several side by side and you compute a dot product every cycle, and a grid of them streaming matrices past each other is a matrix-multiply engine, which is exactly the capstone of this series. Before that the design needs to run fast, which means pipelining and paying attention to timing, and it needs somewhere to hold the data, which means on-chip memory. Those are the next pages.

Next, on-chip memory and FIFOs.