Skip to content

rtl/pe.sv — Systolic Processing Element (v2)

The architectural atom of a weight-stationary systolic MAC array: one multiply-accumulate cell with mixed-precision (INT8 or INT16) operands and an INT48 accumulator wide enough to prevent overflow at every supported precision. Not currently instantiatednpu.sv inlines equivalent combinational MAC + adder-tree logic directly for maximum Icarus Verilog simulation throughput rather than chaining 256 pe instances. This file exists as the standalone architectural reference for a future systolic-tiled or FPGA DSP-slice-mapped implementation.

Where it fits

Nowhere in the current build (Makefile's VERILOG_SOURCES does not include it). Conceptually, it's what each of the 256 cells of npu.sv's MAC array would be if built as a systolic chain instead of a fully-parallel combinational array.

Ports

Direction Name Width Description
in clk, rst_n 1, 1 Clock / reset
in clear 1 Synchronous accumulator clear
in weight 16 (signed) Stationary weight (INT16; NPU sign-extends INT8 before driving)
in act_in 16 (signed) Activation flowing in from the left
in sum_in 48 (signed) Partial sum flowing in from the left
out act_out 16 (signed) Activation passthrough to the right (1-cycle registered)
out sum_out 48 (signed) Accumulated sum to the right: sum_in + weight × act_in

Functionality

Pipeline depth: 1 cycle. Every clock edge: act_out <= act_in (pass the activation one cell to the right, unchanged) and sum_out <= sum_in + sign_extend(weight × act_in). The INT16×INT16 product is INT32; it's sign-extended to INT48 before adding to sum_in, matching the accumulator width npu.sv uses for its inlined equivalent.

              weight (stationary)
                   │
act_in ──────────► [×] ──► (+) ──► sum_out
                           ▲
                        sum_in

Chaining 16 of these horizontally (with sum_in of cell c = sum_out of cell c-1, and 0 for the leftmost) computes one row's dot product over 16 cycles of systolic flow-through, versus the fully-parallel adder-tree npu.sv actually uses (1 cycle, 256 simultaneous multiplies). The tradeoff a real systolic array buys back is area/routing — no giant multi-input adder tree — at the cost of the fill/drain latency systolic arrays are known for.

Example

Two cells in a chain, computing sum = w0×a + w1×a for a shared activation a flowing through:

cycle 0: cell0.act_in=a, cell0.sum_in=0        -> cell0.act_out<=a, cell0.sum_out<=0+w0*a
cycle 1: cell1.act_in=cell0.act_out=a
         cell1.sum_in=cell0.sum_out=w0*a       -> cell1.sum_out<=w0*a + w1*a
  • npu.md — where this cell's logic is inlined as a fully-parallel array instead
  • npu.md (docs) — prose writeup of the PE's role in the conceptual architecture