Assignment
Kathryn has two assignment operators, and the choice between them is the choice between a flip-flop update and same-cycle logic:
dest |= src— clocked assignment. On the clock edge (when the enclosing flow step is active),destlatchessrc. This is Verilog’s non-blocking<=inside analways @(posedge clk)block.dest *= src— combinational assignment.destfollowssrcwithin the same cycle. This is driven from analways @(*)block.
class example(Module): @flow def f(self): a, b, c = reg(8), wire(8), reg(8) with seq(): c |= a + b # reg <- expr (clocked: c latches on the edge) b *= a # wire <- reg (combinational: b follows a)Assignments are made inside flow blocks (here with seq():) in @flow
methods; each one becomes a node of the enclosing block, which supplies the
enable condition — in a seq, “this step is active”. See
Seq & Par.
Which operator for which signal
Section titled “Which operator for which signal”The operator encodes intent, and Kathryn checks it against the destination’s kind:
| Destination | |= (clocked) | *= (combinational) |
|---|---|---|
reg | yes | TypeError |
mem_ele (write element) | yes | TypeError |
wire | TypeError | yes |
val | TypeError | TypeError |
expr (operator result) | TypeError | TypeError |
How the destination’s kind picks the operator and the emitted timing:
flowchart TB
D["destination kind?"]
D --> R["reg / mem_ele write element"] --> CLK["|= clocked - non-blocking <= in always @(posedge clk)"]
D --> W["wire"] --> COMB["*= combinational - driven from always @(*)"]
D --> RO["val / expr"] --> ERR["not an assignment destination - TypeError"]
Using the wrong operator raises a TypeError in Python, before anything
touches the model:
r, w = reg(8), wire(8)
w |= r # TypeError: `|=` (clocked assign) requires a reg / mem_blk / mem_ele destinationr *= w # TypeError: `*=` (combinational assign) requires a wire destinationThis guard is derived from the destination component itself (registers and memory are clocked; wires are combinational; constants and expression results are not assignment destinations at all), so the check can never drift out of sync with the hardware kind.
Integer sources
Section titled “Integer sources”The right-hand side can be a plain Python int; it is auto-wrapped into a constant sized to the destination:
r = reg(12)with seq(): r |= 5 # emitted constant is 12 bits wide: 12'h5 r |= -1 # two's-complement wrap: 12'hfffDetails (including >64-bit literals) are in Expressions.
Sliced assignment
Section titled “Sliced assignment”Both operators work on inclusive slices of the destination, the source, or both:
a, b = reg(16), wire(16)with seq(): a[7, 0] |= b[7, 0] # clocked write to the low byte of a b[15, 8] *= a[15, 8] # combinational drive of the high byte of bA slice inherits the clocked/combinational nature of the signal it comes
from, so the same operator rules apply: slices of a reg take |=, slices
of a wire take *=.
An explicit = on a sliced destination is also an assignment (the
direction is resolved from the destination’s kind):
a[3, 0] = b[3, 0] # sliced write; clocked because a is a regWhat = on a whole signal means (caution)
Section titled “What = on a whole signal means (caution)”a = reg(8)a = b # NOT a hardware assignment!Plain = on a whole signal is ordinary Python name binding: it makes the
name a refer to the object b, and builds no hardware. Only |=, *=,
and the sliced dest[hi, lo] = src form create assignments. This is the one
place Python syntax cannot be overloaded, so keep an eye on it.
Width mismatches
Section titled “Width mismatches”If the source region is narrower than the destination region, it is
zero-extended (unsigned) to fit; if it is wider, the high bits are dropped
and the low bits are kept. To be explicit, slice the source or use
.extend(width) yourself.
Multiple writes to one destination
Section titled “Multiple writes to one destination”Several assignments may target the same register — from different steps, branches, or parallel blocks. When more than one can fire in the same cycle, their ordering is governed by the write-priority system: higher-priority writes are emitted later in the register’s always block and therefore win. See Write Priority.
Two built-in fallbacks ride on this same mechanism: register reset values (maximum priority) and wire defaults (minimum priority) — covered next in Reset & Defaults.
The emitted Verilog
Section titled “The emitted Verilog”From the Quickstart example, one
clocked assignment self.x |= self.simple_val inside a seq becomes:
always @(posedge WIRE_clk_12) begin if (SR_ST_seq_state_4_0_ST_36) begin REG_x_1[7:0] <= VAL_simple_val_3[7:0]; endend— a non-blocking write, guarded by the enable of the flow step that contains
it. A combinational *= emits the same shape under always @(*) with no
clock or guard state.