Skip to content

Examples Gallery

Kathryn ships 39 worked examples under test/model/, numbered tc1 … tc39 with no gaps. Every file is self-contained: it describes the model, provides a build() function that emits Verilog, and carries its own cocotb simulation that asserts the intended behaviour end-to-end through a real simulator. They are the ground truth for how each feature behaves — when a tutorial page and your intuition disagree, run the example.

Each entry links to the tutorial page that covers its feature.

#ExampleWhat it showsTutorial
1tc1_seq_simpleA single sequential block: x <= simple_val, then y <= x.Seq & Par
2tc2_parParallel auto-sync: x and y assigned in two branches, both settle the same cycle.Seq & Par
17tc17_reset_defaultReg reset values and wire defaults, fed as direct int literals (one wider than 64 bits).Reset & Defaults
18tc18_int_operand_autowrapInt literals as operands/sources auto-wrap into width-matched vals; every overloaded operator exercised.Expressions
19tc19_asm_resizeAssignment-source auto-resize: narrower sources zero-extend, wider sources drop MSBs (with warnings).Conversion & Resize
28tc28_mem_blkmem_blk + mem_ele: gated clocked write port, same-cycle combinational read, and hierarchical preload of the memory array from the testbench.Signals
#ExampleWhat it showsTutorial
3tc3_sifSequential if: x <= 42 only when cond_in is high (condition sampled sequentially).Conditionals
4tc4_cifCombinational if: same as tc3 but the condition costs zero extra cycles, so x latches earlier.Conditionals
5tc5_zifZero-cycle if: wires driven combinationally; outputs reflect sources the very cycle conditions go high.Conditionals
6tc6_cloopCounter loop: two explicit resets then x incremented 4 times via cloop(3).Loops
7tc7_cwhileCombinational while: x incremented each iteration while x < 3, zero-cycle condition checks.Loops
8tc8_swhileSequential while: like tc7 but the condition costs one extra clock per iteration.Loops
9tc9_cdowhileDo-while: the body runs at least once, then repeats while x < 3.Loops
10tc10_zswitchZero-cycle switch: an out wire driven combinationally from sel across three cases.State Machines
11tc11_waitWait blocks: sywait (fixed cycles) and scwait (condition) delaying the next assignment.Waits
12tc12_pickPick block: a multi-way select that is NOT mutex-chained — every matching pif fires; pidef is the default.Pick
13tc13_forever_scwaitThe “processor loop” pattern: an endless cwhile handshaking with the outside world through scwait, plus always-on bare comb logic beside the loop.Waits
16tc16_zif_chain_same_regzif/zelif/zelse chain writing one reg three values — lowers to a single clocked priority mux.Conditionals
#ExampleWhat it showsTutorial
14tc14_par_same_priorityThree parallel writes to the same reg at the SAME priority: stable sort keeps program order, the last wins.Write Priority
15tc15_par_diff_prioritySame three writes at three DIFFERENT priorities: the highest-priority write wins even when declared first.Write Priority
#ExampleWhat it showsTutorial
20tc20_pip_zync_baseline3-stage pip/zync pipeline chained through shared arbiters, free-running with no stall, flush, or guard.Pipeline Basics
21tc21_pip_zync_cond_stallA stage-2 conditional one-shot stall: a cif guard fires sywait(5) once, stalling the pipe 5 cycles.Stalls & Bubbles
22tc22_pip_zync_stall_bubbleA one-cycle stall bubble: arb.stall() pulsed once punches a single bubble into the pipeline.Stalls & Bubbles
23tc23_pip_zync_flush_deadlockA one-shot arb.flush() holds the arbiter’s reset high, jamming the pipe permanently at (5, 4, 4).Flush & Hazards
24tc24_pip_zync_multi_assign_orderThe same register assigned twice in one clocked block — probes which write wins (last-write override).Assignment Ordering
25tc25_pip_zync_multi_assign_prioritytc24’s double write wrapped in priority(...) — the higher-priority (first-declared) write wins instead.Assignment Ordering
26tc26_zync_fanoutOne producer fires TWO consumer pipelines in lockstep via a multi-arb zync with mode="all".Fanout
27tc27_zync_parity_fanoutConditional fan-out: per-bind conditions route the producer to a different consumer by parity (mode="any").Fanout

The tc20 baseline: three stages chained through shared arbiters, free-running:

flowchart LR
    S1["stage 1"] --> A1["arbiter"]
    A1 --> S2["stage 2"]
    S2 --> A2["arbiter"]
    A2 --> S3["stage 3"]

And the tc26 fanout: one producer driving two consumer pipelines in lockstep via a multi-arb zync with mode="all":

flowchart LR
    P["producer"] --> Z["zync<br/>(mode=all)"]
    Z --> C1["consumer pipeline 1"]
    Z --> C2["consumer pipeline 2"]
#ExampleWhat it showsTutorial
29tc29_karray_regfileA 4-entry Karray as a tiny register file: field-wise writes and whole-element {field: source} writes.Conversion & Resize
30tc30_karray_dynamic_indexDynamic element reads: binary addresses at all four indices, plus a read-side custom-fn (reduce) index.Indexing
31tc31_karray_dynamic_assignDynamic element writes: binary address, one-hot custom fn, and a whole-element map — each landing on exactly one element.Dynamic Writes
32tc32_karray_cus_indexThe custom-fn index in depth: per-element compare enables, element map writes, an int-literal source, and a reduce read on the same array.Dynamic Writes
33tc33_karray_reduce_readReduce reads: a max-by-data fold, an extras fold that carries a running sum, and a 2-D pin-and-fold.Reduce
34tc34_karray_to_karrayKarray-to-karray element copy: fields paired by exact name+width; non-matching fields skipped with a warning.Conversion & Resize
35tc35_karray_mixed_k2kAll three index kinds (static / dynamic / custom fn) in ONE k2k statement, on BOTH sides, on 4-D and 3-D arrays.Conversion & Resize
36tc36_karray_bundleNested kaf(KBundle) records end to end: nested-dict writes, bundle-field maps, attribute-chain leaf writes, and structural k2k pairing.Element Records

The tc29 register file: a 4-entry Karray (Hardware Aggregator, Table and Slot) addressed by index, each entry holding named fields:

flowchart TB
    K["Karray regfile<br/>(4 entries)"] --> E0["entry 0"]
    K --> E1["entry 1"]
    K --> E2["entry 2"]
    K --> E3["entry 3"]
    E0 --> F["fields<br/>(per-field or whole-element write)"]
#ExampleWhat it showsTutorial
39tc39_dyn_counterThe DynCounter CCP: two chained conditional adds committed once per cycle, a .now probe read before the commit, and a second enable-less free-running counter.Counter
#ExampleWhat it showsTutorial
37tc37_hier_basicTop + one child: cross-module routing both ways, plus implicit clk/mrst forwarding into the child’s clocked flow.Modules
38tc38_hier_deep_siblingDeep hierarchy Top { ChildA { GrandChild }, ChildB }: 2-level input/output chains, sibling routing through the LCA, and IoWire reuse.Modules

Each file registers itself into a shared cocotb pool at import time, and one entry point discovers and runs them all — there is no per-test Makefile:

Terminal window
PYTHONPATH=py python test/run_cocotb.py # all cases, icarus
PYTHONPATH=py python test/run_cocotb.py verilator # all cases, verilator
PYTHONPATH=py python test/run_cocotb.py icarus tc2_par # one case

The simulator argument defaults to icarus (iverilog); verilator is also supported and needs verilator ≥ 5.036. Each build() follows the standard pipeline — reset(), construct the module, build_model(...), emit_verilog(...) — described in Building & Emitting, and each case’s emitted Verilog plus one VCD per testbench coroutine lands in test/.model_output/<tcname>/.

The Python DSL also has its own simulator-free suite, py/tests/test_smoke.py (~70 tests), which pins the API surface itself: operator guards, priority scoping, the full Karray index/record/bundle behaviour, and end-to-end build+emit runs.