ExSystolic v0.2.0 Release Notes
Release date: 2025-05-03
ExSystolic is a BEAM-native systolic array simulator -- clocked spatial dataflow with explicit ticks, links, and processing elements. This release adds parallel execution, a pluggable topology abstraction, and proven cross-backend determinism.
Highlights
Parallel Backend
The new ExSystolic.Backend.Partitioned splits arrays into rectangular tiles and dispatches tile computations in parallel. Two dispatch strategies are available:
:tasks(default) -- usesTask.Supervisor.async_stream/4withordered: true. One supervised task per tile per tick.:pool-- uses the:systolic_poolPoolex worker pool. Reuses long-lived GenServer workers, eliminating per-tick task spawn overhead.
# Default (Task.Supervisor)
Clock.run(array, ticks: 10, backend: :partitioned)
# With tile sizing
Clock.run(array, ticks: 10, backend: :partitioned, tile_rows: 4, tile_cols: 4)
# Poolex pool dispatch
Clock.run(array, ticks: 10, backend: :partitioned, dispatch: :pool)Proven Determinism
Both backends follow the same 6-step Bulk Synchronous Parallel (BSP) contract:
- INJECT -- push external inputs into boundary links
- READ -- drain all link buffers
- EXECUTE -- run PE step functions (parallelised across tiles in partitioned backend)
- COLLECT -- gather PE outputs
- WRITE -- write outputs into link buffers
- RECORD -- append trace events (sorted by
{tick, coord})
Conformance tests verify that interpreted and partitioned backends produce identical PE states and trace events for the same inputs. The partitioned backend's parallel execution cannot observe cross-tile writes within the same tick.
Pluggable Space Topology
The ExSystolic.Space behaviour now includes a links/2 callback. Space modules produce both boundary and internal links for a given direction, and Array.connect/2 delegates entirely to the space module. This eliminates the Grid2D special-case that leaked boundary-link behaviour.
Shared Link Operations
ExSystolic.Backend.LinkOps centralises inject_streams/2, drain_links/2, and write_pe_outputs/2 -- previously triple-duplicated across Clock, Interpreted, and Partitioned. One source of truth, one place for bugs.
New Modules
| Module | Purpose |
|---|---|
ExSystolic.Backend.Partitioned |
Tile-based parallel backend |
ExSystolic.Backend.PoolexWorker |
GenServer worker for pool dispatch |
ExSystolic.Backend.LinkOps |
Shared link buffer operations |
ExSystolic.Tile |
Tile data structure |
ExSystolic.TilePartitioner |
Array-to-tile partitioning |
ExSystolic.Application |
Supervision tree (TaskSupervisor + Poolex pool) |
New APIs
Array.result_map/1-- returnscoord => statefor any Space (not just Grid2D)Array.input/3-- accepts any atom port; raises on duplicate{coord, port}keysPE.value(input, default)-- coerces:empty/nilto a default valuePE.present?(input)-- returnsfalsefor:empty/nilSpace.links(opts, direction)-- produces boundary + internal links for a direction
Bug Fixes
- Trace event ordering was non-deterministic in partitioned backend
- Link
write/2andread/1doctests never executed result_matrix/1silently returned nil cells for non-Grid2D spacesArray.input/3silently overwrote duplicate streams- Range syntax incompatible with Elixir 1.20-rc
PoolexWorker.handle_calltimeout risk on large tiles- Dialyzer opaque type violation in
TilePartitioner
Quality Metrics
| Metric | v0.1.0 | v0.2.0 |
|---|---|---|
| Tests | 95 | 185 |
| Doctests | 12 | 34 |
| Coverage | 95.4% | 98.4% |
| Dialyzer errors | 0 | 0 |
| Credo issues | 0 | 0 |
| Doc warnings | N/A | 0 |
Breaking Changes
result_matrix/1now raisesArgumentErrorfor non-Grid2D spaces (previously returned matrix with nil cells). Useresult_map/1instead.Array.input/3now raisesArgumentErroron duplicate{coord, port}keys (previously silently overwrote).Clock.run/2now raisesArgumentErrorfor unknown backends (previouslyCaseClauseError).Interpreted.read_inputs/2has been deleted (was@doc falsedead code).Tile.boundary_inputsfield has been removed from theTilestruct.- The
Spacebehaviour now requires alinks/2callback.
Known Limitations
- Trace.events list grows unbounded (no
max_eventsor sampling) - Link mutation still uses
List.replace_at(O(n)); indexed map storage not yet implemented - Latency > 1 links are not yet implemented
- No sparse data support
- No semi-ring abstraction
Upgrade Guide
- Update dependency:
{:ex_systolic, "~> 0.2.0"} - If you implemented a custom
ExSystolic.Space, add alinks/2callback. For inspiration, seeExSystolic.Space.Grid2D. - If you called
Interpreted.read_inputs/2, switch toLinkOps.drain_links/2. - If you accessed
Tile.boundary_inputs, it has been removed; boundary links are managed globally by the partitioned backend.