ExSystolic.Array (ex_systolic v0.2.0)

Copy Markdown View Source

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

space_spec()

@type space_spec() :: {module(), keyword()}

t()

@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

connect(array, direction)

@spec connect(t(), atom()) :: t()

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

fill(array, pe_module)

@spec fill(t(), module()) :: t()

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

fill(array, pe_module, pe_opts)

@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

input(array, port, stream_specs)

@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

materialize_links(array)

@spec materialize_links(t()) :: t()

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

new(opts)

@spec new(keyword()) :: t()

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

result_map(map)

@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}

result_matrix(map)

@spec result_matrix(t()) :: [[term()]]

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]]

trace(array, enabled)

@spec trace(t(), boolean()) :: t()

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