ex_systolic: Elixir as a Deterministic Chip Simulator

A systolic-array library that turns the BEAM into a clocked hardware lab, with strict tick semantics, pluggable space models, and a backend split that keeps simulation honest.

8 min read • View on GitHub • More from thanos

A clockwork grid of tiny processing cells receives two synchronized data streams from opposite sides while a clock pulse sweeps across the top. The scene explains that this project treats timing as the main subject, not just matrix multiplication speed.
The point is not raw throughput. It is the discipline of a clocked dataflow system made visible.
Key Takeaways

The Clock Is the Product

`ex_systolic` is not trying to be a faster matrix library. It is trying to make a clocked spatial machine legible. That is the twist: Elixir, a language usually praised for distributed systems, is being used here as a way to model hardware timing with discipline.

The repository’s own thesis is simple enough to miss at first glance. The system is only interesting if the timing is correct. Once you accept that, the rest of the design reads like a set of guardrails around determinism.

I will rewrite this later.

thanos, Project Author · thanos/ex_systolic GitHub README

Why Elixir Is a Strange But Smart Fit

Elixir is an odd place to build a chip simulator, and that is exactly why it is interesting. The BEAM gives you concurrency primitives, but `ex_systolic` uses them under a hard synchronization rule instead of for free-form messaging.

That makes the runtime feel less like an actor playground and more like a controlled lab. Every process can be parallel, but nothing is allowed to leak across a tick boundary. The result is a simulation model that stays predictable even when the work is distributed.

# Conceptual shape of the PE contract
{new_state, outputs} = step(state, inputs)

# No side effects in the middle of the tick.
# No same-tick reads of freshly written data.
# Determinism comes from the order, not from luck.

Inside One Tick

The heart of the system is a six-step BSP loop: Inject, Read, Execute, Collect, Write, Record. That order is the whole game. It makes the simulation behave like a real clocked device, where new values are not visible until the next cycle.

The six-stage tick loop is the determinism contract. Data moves in phases, not in a blur.

That sequencing matters more than parallelism. Injecting a value does not make it instantly visible. Reading happens before execution, execution before collection, and writeback only lands for the following tick. The model forbids the kind of immediate feedback that would make timing ambiguous.


Space and Execution Are Split on Purpose

The codebase separates topology from runtime. `ExSystolic.Space` defines how the grid is shaped and connected. `ExSystolic.Array` and the backend handle the act of stepping through time. That split is the reason the project can talk about future topologies without rewriting the simulation engine.

In practice, that means the array is not married to one geometry. A rectangular grid is the default, but the architecture leaves room for toruses, hex grids, or other spatial layouts if the space module changes. The execution contract stays stable.

LayerJobWhy it matters
SpaceDefines coordinates and linksTopology can change without changing the clock
ArrayHolds PE and link stateKeeps simulation state separate from layout
BackendExecutes each tickDifferent execution strategies can preserve identical semantics

Processing Elements Stay Pure

The processing element contract is deliberately austere. A PE is a state transformer. It receives a state and inputs, then returns a new state and outputs. Nothing inside that callback should depend on the outside world.

That purity is what makes skewed streams manageable. The `:empty` sentinel exists because the machine has to represent absence as data. In a systolic array, blank slots are not a bug. They are part of the timing choreography.

# Conceptual PE step
{state, inputs} -> {new_state, outputs}

# :empty means "nothing arrived this tick"
# not nil, not failure, just a deliberate timing placeholder
A close-up of two parallel lanes feeding a single processing tile. One lane carries padded blank tokens, the other carries delayed tokens, and both arrive one tick apart before combining in a multiply-accumulate cell. The image explains why timing gaps and :empty placeholders are core to the model.
Skewing is not a workaround. It is the mechanism that lets the array line up on the right tick.

Two Backends, One Contract

The backend split is a practical promise. One path interprets the machine directly. The other partitions work across a pool so larger grids can be simulated in parallel. The important part is that both must agree on the same tick semantics.

ApproachStrengthWeaknessBest forHow ex_systolic differs
Verilog or VHDLHardware fidelitySlower to iterate and harder to inspectASIC or FPGA design`ex_systolic` models the same timing rules in a high-level language
C or C++ with GPU librariesRaw performanceThe hardware intent is often hidden behind performance abstractionsThroughput-heavy math`ex_systolic` prioritizes inspectable timing over speed
Typical Elixir concurrencyElegant process orchestrationUsually not built for synchronous chip-like clocksDistributed services`ex_systolic` uses BEAM concurrency inside a strict deterministic boundary

That is why the partitioned backend is not a different story. It is the same story with more workers. Parallelism is allowed, but only if the tick barrier still enforces the same observable behavior.

Why the Same Grid Can Solve Different Problems

The most interesting part of the design is that the machine is not tied to one algebra. The repo points toward semi-rings, which means the same grid can express different problem classes by changing what counts as add and multiply.

That is the GraphBLAS-style insight. The machine stays the same. The meaning of the computation changes. A systolic array built for GEMM can become a graph algorithm engine if the algebra changes under it.

What This Is and Is Not

`ex_systolic` is not competing with BLAS on throughput, and it is not trying to replace hardware description languages. It is a research-friendly simulator that makes the timing rules explicit, testable, and extensible.

Compared with Verilog or VHDL, it is faster to explore and easier to instrument. Compared with C++ or GPU libraries, it is more faithful to the idea of a clocked array than to raw performance. Compared with ordinary Elixir concurrency, it is far stricter about ordering.

ApproachPrimary goalTiming modelBest use case
Verilog or VHDLHardware implementationPhysical clock semanticsDesigning real circuits
C or C++ plus GPU libsSpeedPerformance-firstProduction math kernels
Elixir concurrencyService orchestrationAsynchronous messagingDistributed systems
ex_systolicInspectable simulationStrict BSP ticksResearch, teaching, and modeling

The Bottom Line

`ex_systolic` makes a strong, unusual claim: timing can be part of the API. That is what gives the project its character. It uses Elixir not to imitate a web app, but to stage a clocked hardware world with enough discipline to study it.

For anyone who wants to understand systolic arrays without dropping straight into HDL or low-level performance code, that is a real contribution. The library is small, but the idea is clean.