Component Model
Primitives, composites, and the full primitive reference
Try a Primitive
Search for any primitive, see its ports and parameters, and interact with a live demo:
The Core Invariant
Circuits are either structural (nodes + connections) or behavioral (eval/onTick functions). Structural circuits have zero runtime overhead: they're elaborated away before simulation.
When you write a composite like HalfAdder using nodes and connect, you're defining structure. The simulator expands it to primitives before running. This is called elaboration. All runtime behavior ultimately comes from circuits with eval or onTick functions.
You can also write your own behavioral circuits using eval (combinational) or state + onTick (sequential). See the API Reference for details.
Component Resolution
When the system encounters a node:
- Check for
eval/onTickon the circuit; if found, treat as a behavioral leaf - Check for
nodes/connect; if found, elaborate (expand recursively) - Error: circuit has neither
Primitive Reference
Logic Gates
| Primitive | Inputs | Outputs | Description |
|---|---|---|---|
And | a: Bit, b: Bit | out: Bit | AND: true when both inputs are true |
Or | a: Bit, b: Bit | out: Bit | OR: true when at least one input is true |
Not | in: Bit | out: Bit | NOT: inverts the input |
Nand | a: Bit, b: Bit | out: Bit | NAND: false only when both inputs are true |
Nor | a: Bit, b: Bit | out: Bit | NOR: true only when both inputs are false |
Xor | a: Bit, b: Bit | out: Bit | XOR: true when inputs differ |
Xnor | a: Bit, b: Bit | out: Bit | XNOR: true when inputs match |
Buffer | in: Bit | out: Bit | Passes input through unchanged |
I/O
| Primitive | Inputs | Outputs | Parameters | Description |
|---|---|---|---|---|
Switch | — | out: Bit | value (default: 0) | Clickable 1-bit toggle |
Button | — | out: Bit | — | Momentary push button |
Input | — | out: Bus[N] | value (default: 0), width (default: 8) | Editable numeric input |
Led | in: Bit | — | — | Visual LED indicator |
Output | in: Bus[8] | — | — | Multi-bit output sink |
Constant | — | out: Bit | value (default: 0) | Fixed value source |
Probe | in: Bit | out: Bit | — | Debug passthrough |
Arithmetic
All arithmetic primitives support a width parameter (default: 8, options: 4, 8, 16, 32).
| Primitive | Inputs | Outputs | Description |
|---|---|---|---|
Adder | a, b, carry_in | sum, carry_out | N-bit adder with carry |
Subtractor | a, b, borrow_in | difference, borrow_out | N-bit subtractor with borrow |
Multiplier | a, b | product: Bus[2N] | N×N → 2N-bit multiplier |
Comparator | a, b | eq, lt, gt | N-bit comparator |
Incrementer | in | out | Adds 1 (wraps at max) |
LeftShifter | value, shift | result | Logical left shift |
RightShifter | value, shift | result | Logical right shift |
SignedAdder | a, b, carry_in | sum, overflow, carry_out | Signed add with overflow |
SignedComparator | a, b | eq, lt, gt, lte, gte | Signed compare |
SignedMultiplier | a, b | product: Bus[2N] | Signed N×N multiplier |
Bus Operations
All support width parameter (default: 8).
| Primitive | Inputs | Outputs | Description |
|---|---|---|---|
BusAnd | a, b | out | Bitwise AND |
BusOr | a, b | out | Bitwise OR |
BusNot | in | out | Bitwise NOT |
BusXor | a, b | out | Bitwise XOR |
Routing
| Primitive | Inputs | Outputs | Parameters | Description |
|---|---|---|---|---|
Mux | in0, in1, sel: Bit | out | width (default: 1) | 2-to-1 multiplexer |
Decoder | in: Bus[2] | out0–out3: Bit | — | 2-to-4 decoder |
Splitter | in: Bus[8] | out0, out1: Bus[4] | — | Split bus in half |
Splitter8to8 | in: Bus[8] | bit0–bit7: Bit | — | Split bus to individual bits |
Combiner8to8 | bit0–bit7: Bit | out: Bus[8] | — | Combine bits to bus |
BitSlice | in: Bus[8] | out: Bus[N] | low (0), high (7) | Extract bit range [low..high] |
Concat | high, low | out | lowWidth (4) | Concatenate buses |
AddressCombiner | lo: Bus[8], hi: Bus[8] | out: Bus[16] | — | Combine to 16-bit address |
Sequential
These components have clocked state that updates on the rising edge of clk.
"Sequential" isn't a flag you set: a primitive is sequential exactly when it declares state (these two do), and a composite is sequential when it contains one. That same state declaration is what gives the circuit its single implicit clock, which is why you never wire clk yourself. See How it works.
| Primitive | Inputs | Outputs | Parameters | Description |
|---|---|---|---|---|
DFlipFlop | d: Bit | q: Bit, q_bar: Bit | — | 1-bit register, captures d on clock edge |
Register | data: Bus[N], we: Bit | q: Bus[N] | width (8), value (0) | N-bit register, writes when we is high |
Key difference: DFlipFlop always captures its input on every clock edge. Register only writes when we (write enable) is high, which lets you hold a value across multiple cycles.
Reset behavior (Verilog export): when the exporter's auto-plumbed rst_n is low, both DFlipFlop and Register snap back to their value arg. In simulation, sim.reset() produces the same effect. Memory primitives (RAM, DualPortRAM) preserve their contents during reset; only writes are suppressed.
Memory
| Primitive | Inputs | Outputs | Parameters | Description |
|---|---|---|---|---|
ROM | addr: Bus[16] | data_out: Bus[8] | baseAddress | Read-only, combinational read |
RAM | addr, data_in, we: Bit | data_out | addressWidth (8), dataWidth (8) | Single-port, combinational read, clocked write |
DualPortRAM | addrA, dataA, weA: Bit, addrB | outA, outB | addressWidth (8), dataWidth (8) | Port A reads/writes, port B read-only |
DualPortROM | addrA: Bus[32], addrB: Bus[32] | dataA: Bus[32], dataB: Bus[32] | — | Two independent read ports, one shared memory. Byte-addressable, returns 32-bit little-endian words. Used for architectures that need simultaneous instruction fetch and data read (e.g. rv32i-board.circuit.ts). |
Memory is initialized by calling the component factory with the memory parameter:
nodes: {
ram: DualPortRAM({ memory: {
0: 42, // address 0 = 42
1: 100, // address 1 = 100
64: 33, // address 64 = 33
} }),
},Reads are combinational (address in → data out immediately). Writes happen on the rising clock edge when we is high.
Display
| Primitive | Inputs | Outputs | Description |
|---|---|---|---|
SevenSegment | in: Bus[4] | — | Hex digit display (0–F) |
HexDisplay | in: Bus[N] | — | Multi-digit hex display |
Screen | dataIn: Bus[8] | addrB: Bus[8] | 8×8 pixel framebuffer display |
RasterDisplay | dataIn: Bus[8] | addrB, scanX, scanY, hblank, vblank | Hardware-accurate raster with scan counters |
The Screen primitive works with DualPortRAM: connect screen.addrB → ram.addrB and ram.outB → screen.dataIn. The screen scans through addresses 0–63 to read pixel data.
I/O Devices
| Primitive | Inputs | Outputs | Description |
|---|---|---|---|
Console | data: Bus[8], we: Bit | — | Memory-mapped text output |
UART_TX | addr, data_in, we, re | data_out | Memory-mapped serial transmit |
NIC_FIFO | addr, data_in, we, re | data_out, tx_data, tx_valid, etc. | Network interface with TX/RX FIFOs |