Backings
A Karray’s first constructor argument is its backing — the kind of hardware each (element, field) pair becomes. There are three:
| Backing | HwComponentType | Hardware per field | Assign with |
|---|---|---|---|
| Register | REG | one clocked reg per (element, field) | |= (clocked) |
| Wire | WIRE | one combinational net per (element, field) | *= (combinational) |
| Memory block | MEM_BLOCK | one addressable memory block per field | |= (clocked) |
Each backing maps to a different hardware and a different assignment operator:
flowchart LR
REG["REG"] --> R1["one clocked reg<br/>per (element, field)"] --> RO["|= (clocked)"]
WIRE["WIRE"] --> W1["one combinational net<br/>per (element, field)"] --> WO["*= (combinational)"]
MEM["MEM_BLOCK"] --> M1["one memory block<br/>per field"] --> MO["|= (clocked)"]
The backing is fixed at construction:
class RobEntry(Karray): valid = kaf(1) reg_idx = kaf(5)
self.rob = RobEntry(HwComponentType.REG, (5, 3), "rob") # registersself.bus = RobEntry(HwComponentType.WIRE, (2, 2), "bus") # wiresself.kmem = RobEntry(HwComponentType.MEM_BLOCK, (5, 3), "kmem") # memory blocksRegister backing (REG)
Section titled “Register backing (REG)”Reg backing materializes one register per (element, field). A 5×3 array of
{valid:1, reg_idx:5} yields 15 × 2 = 30 registers, each written by its own
clocked always-block. Assign with |=, whether field-wise or whole-element:
with seq(): self.rob[2][1] |= {"valid": self.vsrc, "reg_idx": self.isrc} # whole element self.rob[0][0].valid |= self.vbit # single fieldReg backing is the most capable: it is the only backing that supports dynamic writes (non-selected elements need a register to hold their value), and it supports dynamic reads and reduce.
Wire backing (WIRE)
Section titled “Wire backing (WIRE)”Wire backing is combinational — each field is a net with no storage. Assign
with *=:
class BusEntry(Karray): data = kaf(8)
self.bus = BusEntry(HwComponentType.WIRE, (2, 2), "bus")
with seq(): self.bus[1][0] *= {"data": self.s} # combinational driveWire-backed Karrays can be read dynamically and reduced, but they cannot be the target of a dynamic write (a wire cannot hold the non-selected elements).
Memory-block backing (MEM_BLOCK)
Section titled “Memory-block backing (MEM_BLOCK)”MemBlock backing folds the whole array onto one addressable memory block per field, instead of discrete components per element. A static element write becomes a memory write at the constant flattened address:
self.kmem = RobEntry(HwComponentType.MEM_BLOCK, (5, 3), "kmem")
with seq(): # write element (2,1) -> flat address 7, one write per field's block self.kmem[2][1] |= {"valid": self.vsrc, "reg_idx": self.isrc}The emitted Verilog declares one memory per field (kmem_valid_MEM,
kmem_reg_idx_MEM), which maps naturally onto block RAM. Assign with |=
(clocked), like a reg.
Operator enforcement
Section titled “Operator enforcement”Kathryn checks the operator against the backing before mutating the model,
and raises a TypeError from Python on a mismatch:
self.rk = VEntry(HwComponentType.REG, (2,), "rk")self.wk = VEntry(HwComponentType.WIRE, (2,), "wk")
self.wk[0] |= {"v": self.s} # TypeError: |= needs a reg/mem backingself.rk[0] *= {"v": self.s} # TypeError: *= needs a wire backingThe rules match plain signals: |= declares clocked intent and requires a
reg or mem backing; *= declares combinational intent and requires a wire
backing.
A bare = on a Karray element carries no intent of its own — it is resolved
from the destination’s backing (reg/memblock → clocked, wire → combinational).
It works for static writes on any backing, but keep in mind that on a dynamic
write it inherits the backing’s semantics and a wire-backed target is rejected
(see Dynamic Writes).
Choosing a backing
Section titled “Choosing a backing”REG— the default choice. Random access, per-element writes, dynamic reads/writes, reduce. Costs one register per (element, field), so it scales to small/medium tables (register files, ROB-style structures).WIRE— combinational interconnect shaped like an array; useful for fan-in/fan-out buses where values are recomputed every cycle.MEM_BLOCK— large storage where one address is accessed at a time and a block RAM is the right physical target.