Reset & Defaults
Two fallback mechanisms give signals a well-defined value when nothing else is writing them:
reg.reset(value)— a clocked reset value for a register, bound at the maximum write priority so it dominates every other write.wire.default(value)— a combinational fallback for a wire, bound at an internal low priority so any real assignment overrides it.
Both accept either a signal or a plain Python int (of any width — the int is wrapped into a constant sized to the destination), and both return the signal so they chain with declaration.
Register reset: reg.reset(value)
Section titled “Register reset: reg.reset(value)”self.x = reg(8, "x")self.x.reset(7) # x resets to 7reset records a reset value for the register; the actual reset hardware is
built during the model build. In the emitted Verilog, the reset appears as a
write inside the register’s clocked always block, guarded by the module’s
master reset input mrst and placed last in the block:
always @(posedge WIRE_clk_1547) begin if (WIRE_mrst_1548) begin REG_x_1540[7:0] <= VAL_val0_1541[7:0]; // VAL_val0 = 8'h7 endendWhile mrst is asserted the register loads its reset value; registers with
no reset(...) simply start uninitialized (X in simulation) until their
first real write.
reset(...) is only valid on a reg — calling it on anything else raises a
TypeError.
Reset dominates user writes
Section titled “Reset dominates user writes”The reset write is bound at the maximum priority (DEFAULT_UE_PRI_RST), so
when a user assignment and the reset both apply in the same cycle, the reset
wins. Concretely, in Verilog terms: within one always block, the last
non-blocking write to a register wins, and the reset write is always emitted
last.
class worker(Module): @flow def f(self): r = reg(8) d = wire(8) r.reset(0) with seq(): r |= d # ordinary user writeemits (schematically):
always @(posedge clk) begin if (<step enable>) begin REG_r <= WIRE_d; // user write — emitted first (lower priority) end if (mrst) begin REG_r <= VAL_0; // reset — emitted last (max priority, dominates) endendThe priority spectrum these two fallbacks sit at, relative to user writes:
flowchart LR
low["wire.default(value) - minimum priority - emitted first"] --> mid["user writes ( |= / *= )"] --> high["reg.reset(value) - maximum priority - emitted last, dominates"]
This ordering is one instance of the general write-priority system.
Wide reset values
Section titled “Wide reset values”Reset values are ordinary Python ints of any magnitude — they cross into the Rust core as arbitrary-precision values, so no manual wrapping is needed beyond 64 bits:
self.big = reg(128, "big")self.big.reset((1 << 100) | 0xABCDEF) # emitted as 128'h10000000000abcdefNegative values wrap two’s-complement at the register width, exactly like integer literals in expressions.
Wire defaults: wire.default(value)
Section titled “Wire defaults: wire.default(value)”self.w = wire(8, "w")self.w.default(self.src) # w falls back to src when nothing drives itdefault gives a wire a combinational fallback: the wire takes this value
whenever no real assignment drives it. Because the default is bound at an
internal low priority, any actual *= assignment overrides it. The
source can be a signal or a raw int (self.w.default(5) works and emits an
8-bit 8'h5 constant for an 8-bit wire).
default(...) is only valid on a wire; anything else raises TypeError.
Worked example: tc15
Section titled “Worked example: tc15”The repository test case tc15_reset_default exercises all of the above in
one module — an 8-bit reset, a 72-bit reset (crossing the 64-bit boundary),
and a wire default sourced from a reset register:
from kathryn import *
WIDE_VAL = (1 << 71) | (1 << 64) | 0xABCDEF
class tc15_reset_default(Module): @init def com_declare(self): # 8-bit reg whose reset value is the literal 7. self.x = reg(8, "x") self.x.reset(7) self.x.mark_output("my_x")
# 72-bit reg reset from a >64-bit literal. self.big = reg(72, "big") self.big.reset(WIDE_VAL) self.big.mark_output("my_big")
# A clocked source (reset to 9), and a wire defaulting to it. self.src = reg(8, "src") self.src.reset(9)
self.w = wire(8, "w") self.w.default(self.src) self.w.mark_output("my_w")
def build(output_folder: str) -> None: reset() module = tc15_reset_default() build_model(module) emit_verilog(output_folder)Note there is no @flow method at all — resets and defaults alone are enough
to give this module behavior. The emitted top.v (abbreviated):
// ---- REG declarations ----reg [7:0] REG_x_1540;reg [71:0] REG_big_1542;reg [7:0] REG_src_1544; // ---- WIRE declarations ----reg [7:0] WIRE_w_1546; // ---- VAL declarations ----wire [7:0] VAL_val0_1541 = 8'h7;wire [71:0] VAL_val1_1543 = 72'h810000000000abcdef;wire [7:0] VAL_val2_1545 = 8'h9;
always @(posedge WIRE_clk_1547) begin if (WIRE_mrst_1548) begin REG_x_1540[7:0] <= VAL_val0_1541[7:0]; // x <= 7 under reset endend
always @(posedge WIRE_clk_1547) begin if (WIRE_mrst_1548) begin REG_big_1542[71:0] <= VAL_val1_1543[71:0]; // big <= wide literal endend
always @(*) begin WIRE_w_1546[7:0] <= REG_src_1544[7:0]; // wire default: w follows srcendWhat to notice:
- Each
reset(int)produced a width-sized constant:8'h7for the 8-bit register, and the full 72-bit pattern72'h810000000000abcdef— bit 71, bit 64, and the low0xABCDEFall intact across the 64-bit boundary. - Each reset is a clocked write guarded by
mrst, in its register’s own always block. - The wire default is a plain combinational block: since nothing else drives
w, the default is its only driver, andmy_wtrackssrc(which itself resets to 9).
In the repository, the accompanying cocotb testbench asserts mrst, checks
my_x == 7 and my_big == WIDE_VAL after the first clock edge, then releases
mrst and confirms the values hold.
Where next
Section titled “Where next”- Write Priority — the general mechanism behind “reset last, default first”.
- Assignment — the ordinary writes these fallbacks interact with.
- Signals — declaring the registers and wires themselves.