Roadmap
This page separates what PySHDL 1.1.0 actually ships from what the project plans to build. It covers the planned debug build and its C functions, the .shdb file, the State Region, the SHDB debugger, the faster code generators, the rest of the planned ecosystem, the order everything is built in, and the tests each planned component must pass before it ships. It closes with what you can do today instead. Everything under planned is a design, not a feature: no flag, function, file format or package in 1.1.0 provides any of it.
Links to the old /docs/debugger/... pages now lead here. Those pages documented the SHDB debugger that shipped with the pre-1.0 PySHDL releases (the 0.x line, up to 0.3.1). PySHDL 1.0 replaced that toolchain with a rewrite: a new flattener, a new C compiler, a new Circuit class and a conformance suite. The rewrite has not rebuilt the debugger yet. Neither 1.0 nor 1.1.0 includes a debugger, a debug build or .shdb files. The planned SHDB is described below, and what you can use today lists the ways to inspect a circuit in 1.1.0.
Shipped in PySHDL 1.1.0
| Component | What you get | Documentation |
|---|---|---|
| Flattener | SHDL source to Base SHDL, as the shdl-flatten command and the flatten_program function | shdl-flatten, the flattening pipeline |
shdlc, release build | Base SHDL (or .shdl source) to C, then to a shared library that exports six functions: reset, poke, peek, step, step_settle, run_batch | shdlc, C ABI |
| PySHDL | The Circuit class: four constructors, poke, peek, step, settle(), step_settle(), run_batch(), reset, introspection and structured errors | Python API, PySHDL internals |
shdl CLI | Projects (shdl.toml, shdl.lock, shdl_modules/), build, test, run, and packages from the Circuit Circus index | shdl CLI, packages |
| Test runner | shdl test, which runs JSON test files of vectors or poke, step and expect sequences | testing |
| Conformance suite | Suite version 1.1.0: 38 cases and 40 frozen cycle-by-cycle traces | conformance |
The repository's test suite collects 1905 tests at 1.1.0: 1644 for the toolchain, 130 for PySHDL (tests/pyshdl/) and 131 for the shdl CLI (tests/shdl_cli/).
The project's own status table is in SHDL_Project.md §7. It marks the flattener, shdlc V1 and the conformance suite as complete, and PySHDL and the shdl CLI with its package registry as built. The standard library and the testbench runner are partial, and everything else is planned.
Planned
Everything in this section is a design target from the specifications. PySHDL 1.1.0 has no debug flag, no debug functions, no .shdb output, no State Region and no alternative code generators. The sections below say, for each item, what 1.1.0 does instead.
The debug build
A debug build would be a second compilation mode of shdlc. It would export the six release functions plus five more for gate-level introspection. The planned signatures, from shdlc_goals.md §3.3:
uint64_t peek_gate (const char *gate_name);
uint64_t peek_gate_prev (const char *gate_name);
uint64_t get_cycle (void);
size_t get_num_gates (void);
int get_gate_info (size_t index,
const char **name,
uint8_t *type,
uint8_t *chunk,
uint8_t *lane);
The planned semantics:
peek_gate(name)would read any gate's current output by its flattened name, such as"fa1_x1".peek_gate_prev(name)would read the same gate's output from the previous cycle. The two-buffer simulation already keeps the previous cycle, so this is expected to be nearly free. It would let a watchpoint ask "did this gate change?" without a shadow copy.get_cycle()would count evaluation cycles since the lastreset().step(0)would not advance it. A lazy tick, caused by peeking an output after a poke, would advance it by exactly 1, because it is a real evaluation cycle.get_num_gates()andget_gate_info(index, ...)would enumerate the gate table in state-layout order: name, type code and position (chunk,lane). The order would stay the same acrossreset()calls, so an index stays valid for the life of the loaded library. The type codes: 0 = XOR, 1 = AND, 2 = OR, 3 = NOT, 4 =__VCC__, 5 =__GND__.- Separate namespaces.
pokeandpeekwould keep addressing ports only, andpeek_gateandpeek_gate_prevwould address gates only. A port and a gate may share a name.
A debug build must give functionally identical results to a release build: the same outputs, cycle for cycle. Gate lookup should be O(1), through a hash table, for circuits beyond about 1000 gates, and step must stay fast. See shdlc_goals.md §3.4 and §6.2.
In 1.1.0, release is the only mode (§6.1). shdlc has no debug option, and the generated library exports only the six release functions:
import ctypes
from SHDL import Circuit
RELEASE = ("reset", "poke", "peek", "step", "step_settle", "run_batch")
DEBUG = ("peek_gate", "peek_gate_prev", "get_cycle", "get_num_gates", "get_gate_info")
with Circuit("examples/adder8.shdl") as c:
(loaded,) = c.build_dir.glob("*.copy*")
lib = ctypes.CDLL(str(loaded))
print("release:", all(hasattr(lib, name) for name in RELEASE))
print("debug: ", [name for name in DEBUG if hasattr(lib, name)])
release: True
debug: []
The .shdb file
Alongside a debug library, shdlc would write a .shdb file: JSON with the circuit's full metadata and the State Region layout (shdlc_goals.md §6.2).
In 1.1.0, no tool writes or reads .shdb files. The metadata a debugger needs is already there, though. The flattener writes all ten metadata blocks into every Base SHDL file, including hierarchy, source_map, constants and monitors, which no 1.1.0 tool consumes:
from flattener.pipeline import flatten_program
from shdlc.baseshdl import parse_base
meta = parse_base(flatten_program("examples/srLatch.shdl").text).meta
print(list(meta))
print(meta["source_map"]["gates"]["i1"])
print(meta["hierarchy"]["SRLatch"]["instances"]["i1"])
print(meta["timing"]["critical_path"])
['version', 'ports', 'hierarchy', 'source_map', 'constants', 'timing', 'monitors', 'stats', 'doc', 'init']
{'file': 'srLatch.shdl', 'line': 22, 'column': 14}
{'type': 'NOT', 'gate': 'i1'}
['i1', 'Q']
shdlc 1.1.0 reads only ports and init. PySHDL reads ports, timing, init, stats and doc.description. The blocks are described in Base SHDL and in base_shdl.md §5.
The State Region
The State Region would hold all simulation state in one contiguous, self-describing block of memory: inputs, outputs, every gate's current and previous output, a cycle counter and status flags. It would start with a versioned header (a magic number, the layout version, the component's identity and section offsets). The layout would be fixed at compile time, the same however the region is allocated, and documented in the .shdb file. That one requirement would enable four things without changing the simulation code:
- Direct access in the same process. A driver resolves a signal's offset once and skips string lookups in hot loops.
- Shared-memory observation. Other processes, such as a waveform viewer, map the region read-only and watch the live simulation at no cost to it.
- Driving from another process. Atomic status flags in the region let another process request steps without sockets.
- Parallel evaluation later. Multi-threaded and SIMD evaluation become internal changes to
tick().
The function-call ABI stays the primary interface. See shdlc_goals.md §4.
In 1.1.0, the generated C keeps its state in separate static arrays (inputs[], the two gate buffers buf_a[] and buf_b[], out_vals[]) plus a dirty flag. There is no header, no cycle counter and no published layout. Everything is private to the library and reachable only through the six functions. See compiler internals.
SHDB, the debugger
SHDB is the planned gate-level debugger, built on PySHDL and the debug build (SHDL_Project.md §4.2). The planned features:
- breakpoints and watchpoints;
- waveform capture;
- hierarchy navigation and scope-aware signal inspection;
- source-level mapping, from a gate back to its line in the
.shdlsource; - display of constants and initial state.
They would draw on the hierarchy, source_map, constants, monitors and init metadata and on peek_gate, peek_gate_prev and get_cycle.
Its acceptance tests are already specified: obligation V.3 of the test plan ports the pre-1.0 debugger's test catalog. That catalog covers:
- loading debug info, with JSON fidelity;
- symbol resolution and completion;
- poke, peek and step through the controller;
- inspecting internal gates mid-propagation;
- breakpoints: value-change, conditional and temporary, with hit counts, enable, disable and clear;
- watchpoints, and running until a breakpoint with a cycle limit;
- bulk inspection and robustness;
- tolerating missing source locations.
In 1.1.0, there is no debugger. See what you can use today for the alternatives.
Faster code generation
The project deliberately leaves compiler optimization for last (SHDL_Project.md Layer 7). The planned tiers, in order:
- type-based bit-packing, many independent gates of one type per machine word;
- PDEP/PEXT-accelerated gathering on hardware that has those instructions;
- SIMD;
- multi-threading.
Every tier has to be bit-exact with the current generator, cycle by cycle, on the full conformance, differential and fuzz suites before it ships. That is obligation V.7 of the test plan. A bit-packed code generator is in development; it is not part of any release.
In 1.1.0, the code generator stores one byte (uint8_t) per gate and evaluates one C statement per gate. Circuits with more than 1024 gates are split into noinline chunk functions of 1024 gates each, which keeps C compile times reasonable. shdlc --dev and shdl build --dev compile with -O0 instead of -O2, trading simulation speed for faster C compiles. See shdlc_goals.md §5.2 and compiler internals.
The rest of the ecosystem
| Item | Plan | In 1.1.0 |
|---|---|---|
| Standard library | Reusable components: derived gates, multiplexers, adders, registers, shifters, ALUs | Partial. No library ships inside PySHDL. examples/stdgates.shdl is the conventional stdgates module, and reusable libraries are published as packages on Circuit Circus. |
| Testbench runner | A .shtb format for input vectors and expected outputs, run in batch | Partial. shdl test runs JSON test files. The .shtb format does not exist. |
| Performance profiler | Measures throughput and finds bottlenecks | Planned |
| Alternative backends | Base SHDL to structural Verilog, BLIF and Yosys JSON | Planned |
| Verification tools | Formal equivalence checking, a combinational-loop lint, coverage, a fuzzer | Planned |
| Visualization | VCD waveform export, schematic viewer, hierarchy explorer, animated propagation view | Planned |
| Web playground | SHDL compiled to WebAssembly, with an in-browser editor, shareable by URL | Planned |
| Hardware bridge | Deploy a circuit to a microcontroller's pins (Arduino, ESP, Raspberry Pi), logic-analyzer capture, hardware-in-the-loop tests, an FPGA bitstream target | Planned |
| Authoring tools | A language server, a formatter (shdlfmt), a documentation generator, a Verilog/BLIF importer, an interactive tutorial | Planned |
| Distribution and glue | The shdl CLI and package manager, shdl deploy, a CI action | The CLI and the Circuit Circus registry are built. shdl deploy and the CI action are planned. |
| SHDL Station | One application combining the editor, simulation, debugging, visualization and testing | Planned; last by design |
Build order
The project builds in dependency layers (SHDL_Project.md §5):
| Layer | Contents | Status |
|---|---|---|
| 0: Core | Flattener, shdlc V1, conformance suite | Complete |
| 1: Consumers of a compiled circuit | PySHDL; standard library; shdlc debug build; SHDB | PySHDL built; standard library partial; debug build and SHDB planned |
| 2: Verification and analysis | Testbench runner; profiler; Verilog, BLIF and Yosys backends; equivalence checker and other verification tools | Testbench runner partial (shdl test); the rest planned |
| 3: Visualization | VCD export, schematic viewer, WebAssembly build and playground | Planned |
| 4: Hardware bridge | Microcontroller pin targets, FPGA bitstream target | Planned |
| 5: Glue and distribution | shdl CLI and package registry; language server, formatter, documentation generator, importer, CI action | CLI and registry built; the rest planned |
| 6: Integration | SHDL Station | Planned |
| 7: Optimization | shdlc performance tiers | Planned, intentionally last |
The critical path is shdlc V1, then the conformance suite, then PySHDL, then the shdlc debug build, then SHDB (critical path). The first three are done, so the debug build is the next step toward a debugger. The standard library and the testbench runner are built alongside, as early as possible, because every other tool tests against them. The backends are a separate track off the flattener that leads to verification (BLIF to equivalence checking) and hardware (Yosys to FPGA).
Test obligations for planned components
The test plan defines each future component's acceptance tests before the component exists, so nothing ships without them. These are Domain V of golden_tests.md:
| ID | Component | What must pass | Status |
|---|---|---|---|
| V.1 | shdlc debug build | Release and debug builds agree on the full fixture and fuzz corpus; peek_gate_prev really returns the previous cycle; get_cycle semantics; gate enumeration (count, names, type codes 0 to 5, chunk and lane) and lookup scaling beyond 1000 gates; .shdb holds the embedded metadata plus a state_region section, with byte-equal JSON in both places; the differential harness compares full gate state in lockstep; the port and gate namespaces stay separate | Not started |
| V.2 | PySHDL | Multi-bit poke and peek; settle() equals step(max_depth) and is refused under feedback; power-on reset honoring init; context manager and dict-style access; boundary cases from the pre-1.0 catalog | Done: 130 tests in 10 modules under tests/pyshdl/ |
| V.3 | SHDB | The pre-1.0 debugger's test catalog, ported wholesale (see SHDB) | Not started |
| V.4 | Testbench runner (.shtb) | Format round-trip; the conformance traces written as .shtb files replay bit-exactly | Not started |
| V.5 | Backends | Verilog output cross-validated against Verilator on the conformance corpus; BLIF output checked for formal equivalence with ABC | Not started |
| V.6 | State Region | Header magic and version; the layout documented in .shdb matches reality (poking at an offset equals poking through the ABI); the layout does not depend on allocation; a shared-memory observer sees live values | Not started |
| V.7 | Optimization tiers | Every tier passes the full conformance, differential and fuzz suites bit-exactly, at parity with V1 | Not started |
The V.2 tests also replay the whole conformance corpus through the public Circuit API. The same corpus is the acceptance gate for every later component.
What you can use today
Without a debugger, you can still see a lot of a running circuit with 1.1.0.
Trace outputs cycle by cycle. Step one cycle at a time and record the outputs. This is what a waveform shows. Here is the carry rippling through an 8-bit adder as it computes 255 + 1:
from SHDL import Circuit
with Circuit("examples/adder8.shdl") as c:
c["A"] = 255
c["B"] = 1
trace = []
for _ in range(17):
c.step(1)
trace.append((c["Sum"], c["Cout"]))
print(trace)
[(0, 0), (254, 0), (252, 0), (252, 0), (248, 0), (248, 0), (240, 0), (240, 0), (224, 0), (224, 0), (192, 0), (192, 0), (128, 0), (128, 0), (0, 0), (0, 1), (0, 1)]
Probe an internal net. Only ports are visible through the ABI, but connecting a gate's output to an extra output port costs no gate and no cycle: an output port is just another name for the net that drives it. Here is the SR latch from examples/srLatch.shdl with the two OR gates inside its NOR pairs exposed, as S goes high:
from SHDL import Circuit
PROBED = """
top component SRProbe(S, R) -> (Q, Qn, NorQ, NorQn) {
o1: OR; i1: NOT;
o2: OR; i2: NOT;
init {
o1.O = 1; i1.O = 0;
o2.O = 0; i2.O = 1;
}
connect {
R -> o1.A; i2.O -> o1.B; o1.O -> i1.A; i1.O -> Q;
S -> o2.A; i1.O -> o2.B; o2.O -> i2.A; i2.O -> Qn;
o1.O -> NorQ; # probe: the OR inside NOR #1
o2.O -> NorQn; # probe: the OR inside NOR #2
}
}
"""
with Circuit.from_source(PROBED, name="srprobe") as c:
print(c.info.stats["total_gates"])
c["S"] = 1
print("cycle o1 Q o2 Qn")
for cycle in range(1, 5):
c.step(1)
print(f"{cycle:>5} {c['NorQ']:>2} {c['Q']} {c['NorQn']:>2} {c['Qn']:>2}")
4
cycle o1 Q o2 Qn
1 1 0 1 1
2 1 0 1 0
3 0 0 1 0
4 0 1 1 0
The change travels one gate per cycle: o2 rises, then Qn falls, then o1 falls, and Q rises on cycle 4. See latches for the full example.
Other tools that work today:
- Read the generated code.
Circuit(..., keep_artifacts=True)keeps the Base SHDL and C it compiled (see artifact directories).shdlc --emit-candshdl build --emit-basewrite them from the command line. - Read the metadata directly. Parse the Base SHDL with
shdlc.baseshdl.parse_base(text).meta, as shown above, to map gate names to source lines and instances. - Drive a circuit by hand.
shdl runopens a poke, peek, step and reset REPL over your project's circuit. See shdl CLI. - Pin down behavior with tests.
shdl testruns vector and sequence tests (testing), andCircuit.run_batchchecks many input frames in one call (Python API). - Check timing.
c.timing.max_depthis the longest input-to-output path in cycles, and the retained Base SHDL'stimingblock also lists the per-output depths and the critical path.
See also
- SHDL_Project.md §4: the whole planned ecosystem, component by component.
- shdlc_goals.md: the debug build ABI and the State Region in full.
- golden_tests.md, Domain V: the test obligations for every planned component.
- Simulation model: the cycle model any future tool has to preserve.