Hardware Interfaces

Waveflow models hardware communication channels as interfaces — transactional connections between SimObj instances in a SimPy discrete-event simulation. Interfaces decouple the timing model of a bus from the functional logic of the modules connected to it.

This section is the Python transactional model: the interface classes, their master/slave endpoints, the write / read / get calls, binding, and the cycle-based latency model. It is Python-only by design — an interface is not a standalone synthesizable artifact but a port of a module, so its synthesizable side is documented where the kernel is, in Module Code Generation and Custom Hooks.

Start with Overview for the core model — Interface versus endpoint, the Words type, the latency model, and the SimPy lifecycle — then pick an interface from the map below.

Interfaces compose, and that is the map

A key property of Waveflow interfaces is that they are composable: an interface endpoint may use the methods of other endpoints to build a richer transactional behaviour on top of them. An AckedStreamIF is not a new kind of wire — it is two ordinary streams that a module wants to talk about as one thing, and it says so:

from waveflow.hw.reverse_stream import AckedStreamIF
print(AckedStreamIF.physical_interfaces.__doc__.splitlines()[0])
Two ordinary streams.  In hardware there is no acked stream — there are two FIFOs.

So the guide is in two parts, and the test is one question — does this interface build on another one?

     
Primitive builds on no other interface — it is the module’s direct connection to the outside StreamIF · MMIF / DirectMMIF · BramIF · RegMapMMIFSlave · StreamOfBlocksIF · CrossBarIF · RFSampIF
Derived built from one or more primitives, whose endpoints it owns and drives CreditStreamIF · AckedStreamIF · AXIMMQueue

Primitives come first, because everything in the second row is written in terms of the first.

Two of them are worth pointing at directly. RFSampIF is primitive — it composes nothing — but it is domain-specific, so it lives with its domain rather than here. StreamOfBlocksIF is primitive too, though it only ever appears inside a module rather than on its boundary.

A forward reference, not a definition. When a module is lowered to an HLS or XSI target, a primitive endpoint typically becomes a port on the generated kernel, and a derived one becomes whatever its underlying primitives become — nothing new appears in the hardware. How that happens, and what the machinery is called, is Endpoint kinds. You do not need any of it to use an interface, which is why it is not here.


Table of contents

  • Overview - The transactional interface model — Interface vs master/slave endpoint, the Words type, the cycle-based latency model, and the SimPy write/read/bind lifecycle, plus a runnable two-SimObj StreamIF toy.
  • Primitive interfaces - The interfaces that have a real HLS lowering — a stream, a memory-mapped port, a BRAM port, an AXI-Lite register map, a stream-of-blocks, a crossbar. Split by position: a boundary primitive becomes a port on the generated kernel and has a kind_of_endpoint kind; an internal primitive lowers to an HLS construct that only exists inside the kernel.
  • Derived interfaces - The interfaces that are transaction patterns over a primitive rather than a construct of their own — the two reverse channels (credit and ack), a schema transfer, an array transfer, and the AXI-MM command queue. Each is built from primitives; what differs is how it hands you the primitive underneath — declared and wired automatically, or owned and bound by you.