LOOM uses a small dataflow dialect to represent loop-derived stream and
state-machine behavior in DFG form.
Current core operations are:
dataflow.streamdataflow.gatedataflow.carrydataflow.invariant
These are software-graph operations. When they are implemented in hardware, LOOM treats each of them as one dedicated fixed-behavior state machine.
Related documents:
- spec-compilation.md
- spec-dataflow-compilation.md
- spec-dataflow-memory.md
- spec-fabric-function_unit-ops.md
- spec-fabric-function_unit.md
The dataflow dialect itself does not define a dedicated tagged carrier type.
Instead:
- software-side
dataflowoperations use native semantic values such asindex,i1, or other scalar element types - when a mapped hardware path carries those values across ADG boundaries, LOOM
uses Fabric transport types such as
!fabric.bits<...>or!fabric.tagged<...>
Therefore the separation is:
dataflow.*defines the software-visible stream semanticsfabric.bitsandfabric.taggeddefine the hardware transport container
All four current dataflow operations are fixed state-machine behaviors.
Normative LOOM rule for hardware mapping:
- a
function_unitthat implements any one of the fourdataflowoperations must contain exactly that one non-terminatordataflowoperation - such a
function_unitmust uselatency = -1andinterval = -1
LOOM does not currently model any of the four dataflow operations as ordinary
single-fire datapath FUs with meaningful scalar latency or interval.
The authoritative FU-body legality, timing, and exclusivity rules are defined in spec-fabric-function_unit-ops.md.
dataflow.stream is a configurable index-stream generator.
%idx, %cont = dataflow.stream %start, %step, %bound
{step_op = "+=", cont_cond = "<"}
: (index, index, index) -> (index, i1)%start:index%step:index%bound:index
%idx: generated index stream%cont: boolean will-continue stream, typei1
step_op: update operatorcont_cond: loop-continuation comparison
Current valid step_op values are:
+=-=*=/=<<=>>=
Current valid cont_cond values are:
<<=>>=!=
dataflow.stream operates as a two-phase generator.
Phase A: activation
- wait until
%start,%step, and%boundare all available - latch these three values internally
- mark the stream generator active
Phase B: emit one (idx, cont) pair per firing
- evaluate
cont = (next_idx cont_cond bound) - emit the current
next_idxon%idx - emit the boolean result on
%cont - if
cont = true, updatenext_idx = next_idx step_op step - if
cont = false, terminate the current activation and return to Phase A
For a loop with N body iterations:
%idxhasN + 1elements%conthasNtrue values followed by one false value
This is the canonical LOOM representation of the one-step-ahead loop-control
stream used by later dataflow.gate, dataflow.carry, and
dataflow.invariant.
Example:
start = 0step = 1bound = 4step_op = +=cont_cond = <
Then:
%idx = [0, 1, 2, 3, 4]%cont = [true, true, true, true, false]
The common dedicated-dataflow FU rule above applies.
Additional mapping note:
cont_condcontributes runtime configuration bitsstep_opis structural, not runtime-configurable
dataflow.gate shifts a one-step-ahead before-region stream into a body-region
stream.
%after_value, %after_cond = dataflow.gate %before_value, %before_cond
: T, i1 -> T, i1%before_value: before-region value stream, typeT%before_cond: before-region loop-condition stream, typei1
%after_value: body-region value stream, typeT%after_cond: body-region loop-condition stream, typei1
dataflow.gate is the alignment adapter between:
- the before-region stream shape produced by
dataflow.streamordataflow.carry - the body-region stream shape consumed by loop-body operations
Its behavior is a fixed two-phase state machine.
In this phase, dataflow.gate consumes one (before_value, before_cond) pair
from its inputs.
If before_cond = true:
- consume both inputs
- emit only
after_value = before_value - transition to Phase 2
If before_cond = false:
- consume both inputs
- emit nothing
- remain in Phase 1
This phase discards the final tail element associated with the terminating
false condition when no new loop body should start.
In this phase, dataflow.gate again consumes one
(before_value, before_cond) pair.
If before_cond = true:
- consume both inputs
- emit
after_value = before_value - emit
after_cond = true - remain in Phase 2
If before_cond = false:
- consume both inputs
- emit only
after_cond = false - transition back to Phase 1
dataflow.gate transforms an N + 1 before-region stream pair into an N
body-region stream pair:
- one initial
truelaunches the body stream - all interior
truevalues produce(after_value, true) - the terminating
falseproduces onlyafter_cond = false
This is why dataflow.gate is not equivalent to dataflow.invariant and not
equivalent to a simple combinational filter.
Example:
%before_value = [0, 1, 2, 3, 4]%before_cond = [true, true, true, true, false]
Then:
%after_value = [0, 1, 2, 3]%after_cond = [true, true, true, false]
The common dedicated-dataflow FU rule above applies.
dataflow.carry models one loop-carried dependency state machine.
%o = dataflow.carry %d, %a, %b : i1, T, T -> T%d: loop-condition stream, typei1%a: initial value stream, typeT%b: loop-carried value stream, typeT
%o: carried output stream, typeT
dataflow.carry alternates between an initial stage and a loop stage.
- wait for one token on
%a - consume
%a - emit that value on
%o - transition to Phase 2
- wait for one token on
%d - consume
%d
If %d = true:
- transition to Phase 3
If %d = false:
- transition back to Phase 1
- wait for one token on
%b - consume
%b - emit that value on
%o - transition back to Phase 2
This models the classic loop-carried recurrence:
- emit one initial value
- then emit one carried value for each continuing iteration
- reset when the loop terminates
The common dedicated-dataflow FU rule above applies.
dataflow.invariant models one loop-invariant value repeater.
%o = dataflow.invariant %d, %a : i1, T -> T%d: loop-condition stream, typei1%a: invariant seed stream, typeT
%o: invariant replay stream, typeT
dataflow.invariant stores one invariant value and replays it while the loop
condition remains true.
- wait for one token on
%a - consume
%a - store it internally
- emit it once on
%o - transition to Phase 2
- wait for one token on
%d - consume
%d
If %d = true:
- emit the stored invariant value on
%o - remain in Phase 2
If %d = false:
- emit nothing
- transition back to Phase 1
This means:
- the initial invariant value is consumed once per iteration burst
- that value is replayed for the active iterations of that burst
- a terminating
falseresets the machine for the next burst
The common dedicated-dataflow FU rule above applies.
For LOOM mapping:
- each of the four
dataflowoperations is a distinct compatibility class - matching is by exact operation identity
- no implicit equivalence exists between
stream,gate,carry, andinvariant - a hardware FU that implements one of the four
dataflowoperations must not be reused as a generic compound FU for other non-dataflow operations
In particular:
dataflow.invariantmust not be matched throughdataflow.gatedataflow.gatemust not be matched throughdataflow.invariant
LOOM's SCF-to-DFG lowering uses the four operations in a specific pattern:
dataflow.streamgenerates one-step-ahead loop index and continue streamsdataflow.gateconverts before-region shape into body-region shapedataflow.carrymodels loop-carried valuesdataflow.invariantmodels values reused across loop iterations
This is why dataflow.gate is timing-critical in the model: it is the
alignment boundary between the before-loop stream domain and the loop-body
stream domain.
Focused tests should exist for:
- standalone
dataflow.stream - standalone
dataflow.gate - standalone
dataflow.carry - standalone
dataflow.invariant - at least one representative
stream -> gate -> carrychain
These tests should validate:
- token ordering
- stage transitions
- final reset behavior after one terminating
false - mapping correctness when one dataflow op is implemented by one dedicated FU