Skip to content

Backings

A Karray’s first constructor argument is its backing — the kind of hardware each (element, field) pair becomes. There are two:

BackingHwComponentTypeHardware per fieldAssign with
RegisterREGone clocked reg per (element, field)|= (clocked)
WireWIREone combinational net per (element, field)*= (combinational)
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)"]

The backing is fixed at construction:

class RobEntry(Karray):
valid = kaf(1)
reg_idx = kaf(5)
self.rob = RobEntry(HwComponentType.REG, (5, 3), "rob") # registers
self.bus = RobEntry(HwComponentType.WIRE, (2, 2), "bus") # wires

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 field

Reg backing is the more capable of the two: it is the only backing that supports dynamic writes (non-selected elements need a register to hold their value), and it supports whole-array reset, dynamic reads and reduce.

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 drive of element (1,0)

Wire-backed Karrays can be read statically, read dynamically, and reduced — but they cannot be the target of a dynamic or custom-fn write, and they have no reset(). Each element field carries the same implicit zero fallback an ordinary wire does, so an undriven element reads 0.

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: `|=` (clocked assign) requires a reg-backed Karray
self.rk[0] *= {"v": self.s} # TypeError: *= needs a wire backing

The rules match plain signals: |= declares clocked intent and requires the reg backing; *= declares combinational intent and requires the wire backing.

A bare = is rejected outright on a Karray — it carries no clocked/combinational intent of its own, so there is nothing to check against the backing:

self.rf[0] = {"data": self.s} # TypeError: requires `|=` or `*=`, not a bare `=`
self.rf[0].data = self.s # TypeError: same

(This differs from plain signals, where a sliced a[3, 0] = b is a legal assignment resolved from the destination’s kind — see Assignment.)

  • REG — the default choice. Random access, per-element writes, dynamic reads/writes, reduce, whole-array reset. Costs one register per (element, field), so it scales to small and medium tables (register files, ROB-style structures, scoreboards).
  • WIRE — combinational interconnect shaped like an array; useful for fan-in/fan-out buses and decoded views where every value is recomputed each cycle.