Simulation
A Waveflow design is, first, a Python program you can run. Before any C++ is generated, you assemble your components, wire them together with interfaces, and simulate the whole system — checking that the data flows, the protocol handshakes, and the results are correct. That is the milestone: simulate a whole system in Python.
Discrete-event simulation (PySim)
The simulation is discrete-event (DES): nothing advances on a fixed clock tick — instead the runtime jumps from one scheduled event to the next, so time only moves when something happens. This is what makes a transaction-level model fast: a 1024-word burst is one timed event, not 1024 clock steps.
Waveflow builds this on SimPy. The
Simulation object owns a single simpy.Environment
and the list of participating objects; every simulation entity is a
SimObj that registers itself with that Simulation and
borrows its environment. You rarely touch SimPy directly — SimObj wraps the primitives you need
(self.timeout(...), self.process(...), self.event()).
The three-phase lifecycle
Simulation.run_sim() drives every registered SimObj through pre_sim → run_proc → post_sim,
in registration order (error_cleanup() runs on every object if the run raises, so files and loggers
close). That per-object lifecycle — and the on_start / @sim_only component specifics — is on the
SimObj page; this is the system view: how the hooks are driven across
all objects at once.
In this section
- SimObj — the base object every simulation entity is, its three-phase lifecycle, and a toy two-
SimObjproducer/consumer simulation. - Process generators — SimPy’s
yield-based concurrency for newcomers: events, pausing, parallel spawn, return values, andProcessGen[T]hints. - Running a simulation — instantiate components, wire them with
Interfaceobjects, build aSimulation, and callrun_sim(). - Timing model —
Clockand how components / interfaces express cycle latency (the forward model that produces the timeline). - Logging — the
LoggerSimObj: recording timestamped events to a CSV for inspection and timing analysis.
See also
- Interfaces — the transactional connections components are wired through.
- Hardware Components — declaring the components (ports,
HwParam) that a simulation runs. - Timing Analysis Tools — analyzing the timeline a simulation produces (this section is the model that produces it).
- Build System — running a simulation as a step in a
BuildDag.
The simulation models a design in Python. Turning that design into synthesizable C++ — the generated kernel structure and the hand-written kernel bodies — is the codegen arc: Component Code Generation and Custom Hooks.
Table of contents
- SimObj - The SimObj base: everything in a simulation is a SimObj that registers with the owning Simulation, borrows its single simpy.Environment, and uses the wrapped primitives (self.timeout, self.process, self.event). A runnable toy two-SimObj producer/consumer shows a bare simulation before any interface or HwComponent.
- Process generators - A quick guide to SimPy's generator-based concurrency for readers new to it: what an event and a process generator are, how yield pauses a process, when behavior must be a generator, how to spawn a non-blocking parallel process, how to return a value (proc.value vs yield from), and how to type-hint with ProcessGen[T].
- Running a simulation - Assemble and run a system in Python: instantiate components against one Simulation, wire their ports with Interface objects (.bind), and call run_sim() to drive pre_sim / run_proc / post_sim. Results are read off the objects afterward.
- Timing model - The forward timing model that produces the simulated timeline — Clock (frequency / period), how interfaces charge transfer latency in cycles, and how a component models compute latency with self.timeout / self.action. (Analyzing the result is Timing Analysis.)
- Logging - Record timestamped events during a simulation: Logger is a SimObj that writes a flushed CSV (a leading time column from env.now plus your declared fields); log(**fields) records a row, get_tv(field) reads back (times, values). NullLogger is the no-op default.