A systolic array: a grid of processing elements connected by links.
The Array module provides the user-facing API for building and running systolic computations. It composes Space, Link, PE, and Clock into a single coherent data structure.
Construction
array =
Array.new(rows: 3, cols: 3)
|> Array.fill(MyPE)
|> Array.connect(:west_to_east)
|> Array.connect(:north_to_south)
|> Array.input(:west, [{{0,0}, [1,2,3]}, {{1,0}, [4,5,6]}])
|> Array.input(:north, [{{0,0}, [7,8]}, {{0,1}, [9,10]}])Execution
result = Clock.run(array, ticks: 10)Pluggable space
By default, arrays use ExSystolic.Space.Grid2D as their spatial
model. You can supply a custom space module:
Array.new(space: {MySpace, key: value})The space module must implement ExSystolic.Space.
## Data structure
The array is an %ExSystolic.Array{} struct with these fields:
:space--{module, opts}tuple defining the spatial model:grid-- the coordinate grid (backward compat, derived from space):pes-- map of coord => {module, state}:links-- list of Link structs:tick-- current tick counter:trace-- trace data:trace_enabled-- whether tracing is active:input_streams-- pending external input streams
Summary
Functions
Connects PEs along the specified axis.
Fills every grid position with the given PE module.
Fills every grid position with the given PE module and per-PE options.
Attaches external input streams to the array.
Materializes all links for the array using its Space module.
Creates a new empty array.
Returns a map of coord => state for every PE.
Extracts the result matrix from the array's PE states.
Enables trace recording on the array.
Types
@type t() :: %ExSystolic.Array{ grid: ExSystolic.Grid.t(), input_streams: %{required({ExSystolic.Grid.coord(), atom()}) => [term()]}, links: [ExSystolic.Link.t()], pes: %{required(ExSystolic.Grid.coord()) => {module(), ExSystolic.PE.state()}}, space: space_spec(), tick: non_neg_integer(), trace: ExSystolic.Trace.t(), trace_enabled: boolean() }
Functions
Connects PEs along the specified axis.
Delegates to the array's Space module to produce both internal
(PE-to-PE) and boundary (external-input-to-PE) links for the given
direction. The set of valid directions is space-specific; Grid2D
supports :west_to_east and :north_to_south.
Examples
iex> array = ExSystolic.Array.new(rows: 2, cols: 2) |> ExSystolic.Array.fill(ExSystolic.PE.MAC)
iex> array = ExSystolic.Array.connect(array, :west_to_east)
iex> length(array.links)
4
Fills every grid position with the given PE module.
Each PE is initialized via its init/1 callback with an empty option
list. The default accumulator for MAC is 0.
Examples
iex> array = ExSystolic.Array.new(rows: 2, cols: 2) |> ExSystolic.Array.fill(ExSystolic.PE.MAC)
iex> map_size(array.pes)
4
iex> elem(array.pes[{0,0}], 0)
ExSystolic.PE.MAC
@spec fill(t(), module(), %{required(ExSystolic.Grid.coord()) => keyword()}) :: t()
Fills every grid position with the given PE module and per-PE options.
pe_opts is a map of coord => keyword(). PEs not in the map get
default init options [].
Examples
iex> array = ExSystolic.Array.new(rows: 1, cols: 1)
iex> array = ExSystolic.Array.fill(array, ExSystolic.PE.MAC, %{{0,0} => [acc: 100]})
iex> elem(array.pes[{0,0}], 1)
100
@spec input(t(), atom(), [{ExSystolic.Space.coord(), [term()]}]) :: t()
Attaches external input streams to the array.
port is an atom naming the PE port the stream enters through.
Common values are :west and :north for Grid2D arrays, but any
atom is accepted to support custom spaces with arbitrary port names.
stream_specs is a list of {coord, values} where values is
a list of items to inject one per tick into the PE at coord
through port.
Calling input/3 for the same {coord, port} more than once raises
ArgumentError -- duplicates are rejected to prevent silent data loss.
Examples
iex> array = ExSystolic.Array.new(rows: 2, cols: 2) |> ExSystolic.Array.fill(ExSystolic.PE.MAC)
iex> array = ExSystolic.Array.connect(array, :west_to_east)
iex> array = ExSystolic.Array.connect(array, :north_to_south)
iex> array = ExSystolic.Array.input(array, :west, [{{0,0}, [1,2,3]}])
iex> map_size(array.input_streams)
1
Materializes all links for the array using its Space module.
Creates links for every neighbour relationship returned by the space.
This is an alternative to calling connect/2 for each direction
individually -- useful for custom spaces where directions are not
known in advance.
Examples
iex> array = ExSystolic.Array.new(rows: 2, cols: 2) |> ExSystolic.Array.fill(ExSystolic.PE.MAC)
iex> array = ExSystolic.Array.materialize_links(array)
iex> length(array.links)
8
Creates a new empty array.
Accepts either rows: / cols: (backward compatible) or space:
for a custom spatial model.
Examples
iex> array = ExSystolic.Array.new(rows: 2, cols: 3)
iex> array.grid.rows
2
iex> array.tick
0
iex> elem(array.space, 0)
ExSystolic.Space.Grid2D
iex> array = ExSystolic.Array.new(space: {ExSystolic.Space.Grid2D, rows: 2, cols: 3})
iex> array.grid.rows
2
@spec result_map(t()) :: %{required(ExSystolic.Space.coord()) => term()}
Returns a map of coord => state for every PE.
Works for any Space implementation, not just Grid2D.
Examples
iex> array = ExSystolic.Array.new(rows: 2, cols: 2) |> ExSystolic.Array.fill(ExSystolic.PE.MAC)
iex> ExSystolic.Array.result_map(array)
%{{0, 0} => 0, {0, 1} => 0, {1, 0} => 0, {1, 1} => 0}
Extracts the result matrix from the array's PE states.
Returns a list of lists (row-major) suitable for matrix operations. Each entry is the PE state at that coordinate.
This function is Grid2D-specific: it relies on the array having
rectangular {row, col} coordinates. For arrays built on a custom
ExSystolic.Space, use result_map/1 instead.
Examples
iex> array = ExSystolic.Array.new(rows: 2, cols: 2) |> ExSystolic.Array.fill(ExSystolic.PE.MAC)
iex> ExSystolic.Array.result_matrix(array)
[[0, 0], [0, 0]]
Enables trace recording on the array.
Examples
iex> array = ExSystolic.Array.new(rows: 1, cols: 1) |> ExSystolic.Array.trace(true)
iex> array.trace_enabled
true