Timing Models
Every HwComponent carries two models. The functional model says what it computes — the
math from inputs to outputs. The timing model says how long that takes and what event it
pends on to complete — when, in simulated time, the work finishes and the output becomes
available. The functional model is documented with each component; this section is about the
timing model.
The name timing model may not be final — it is the forward model that produces a timeline (as opposed to the Timing Analysis Tools that measure one). It builds directly on the Simulation timing model page (
Clock,self.timeout, charging compute latency) and goes deeper: how the shape of a component’s load/compute/store loop changes the timeline.
Three flows, one continuum
Many of the accelerators in Waveflow follow a three-step process: read input data, perform some computation on it, and write the output data. This load / compute / store sequence arises often in accelerators, and designers typically use one of three models to realize it. All three models have the same functional behavior but differ in their timing behavior — specifically, in the extent to which the steps overlap. That overlap in turn affects which computations can be supported, the resource usage, and the latency.
-
block: load, compute, and store run sequentially with no overlap — a serial barrier: load the whole array, then compute, then store. Simplest to implement, but has the highest latency.
-
double-buffered: overlap at block granularity (load(n) ∥ process(n-1) ∥ store(n-2)). Reduces latency, but requires additional memory for buffering.
-
streaming: the limit as block size → 1 beat — per-element overlap. Needs minimal buffering and achieves the lowest latency, but is only possible when the computation can be performed as data arrives.
The pages below take them in order of modeling difficulty — block first (a single serial coroutine), then streaming (per-element timestamps in one coroutine), then double-buffered (which needs actual concurrency).
A note on naming
This section prefers double-buffered (or “ping-pong”) over a bare “buffered.” Block processing also uses a buffer — it loads a whole array into one before computing — so “buffered” alone is ambiguous. “Double-buffered” names the thing that matters: two buffers, so the next block can load while the current one is still being processed.
In this section
- LT vs CT models — loosely-timed vs cycle-timed simulation, and why Waveflow is LT.
- Block processing — the serial barrier: load → compute → store. The simplest to model, and the closed-form compute latency
latency + II·(m − 1). - Streaming processing — per-element overlap: the first output appears
latencycycles after the first input, and the rest are gated by whichever is slower, input arrival or compute rate. - Double-buffered processing — block-granularity overlap (
load(n) ∥ process(n-1) ∥ store(n-2)), modeled with three concurrent SimPy processes through depth-2 buffers. The matrix-LT FIR (rowwise_fir) is the worked, cosim-calibrated example, synthesized via a DATAFLOW custom hook. - Fitting a timing model — recovering a timing model’s parameters from measured
(size, cycles)data points; see also the Calibration package.
See also
- Simulation timing model — the
Clock,self.timeout, and where transfer vs. compute latency is charged. This section assumes that page. - Timing Analysis Tools — the reverse direction: measuring throughput / latency / overlap from a produced timeline (VCD, cosim, AXI parsing).
- Interfaces —
read_array/write_array(memory-mapped) andget_pipelined/write_pipelined(streams), the transfer calls these flows are built from.
Table of contents
- LT vs CT models - Loosely-timed (LT) vs cycle-timed (CT) simulation. Waveflow models timing loosely — one timed event per transaction, not per clock edge — which is what makes a whole-system Python simulation fast, and is the same choice established computer-architecture simulators make.
- Block processing - The block-processing timing model: load a whole array, compute, store. A single serial coroutine — no overlap — so it is the simplest to model. The compute wait is the closed form latency + II·(m − 1), with the (m − 1) convention spelled out.
- Streaming processing - The streaming timing model: compute overlaps the load, so the first output appears `latency` cycles after the first input and later outputs are gated by whichever is slower — input arrival or compute rate. The per-element max() collapses to a first/last form; only first and last arrival times are known.
- Double-buffered processing - The double-buffered (ping-pong) timing model: load(n) ∥ process(n-1) ∥ store(n-2). Block-granularity overlap that cannot be expressed in one serial coroutine — it needs three concurrent SimPy processes handing off through depth-2 buffers, from which the steady-state max(load, compute, store) emerges.
- Fitting a timing model - Recovering the timing-model parameters — latency, ii, unroll_factor — from a few measured (size, cycles) data points, by linear regression against the effective trip count. The cosim cycle-model work is building the infrastructure to do this against RTL ground truth.