Skip to main content

An agent that synthesizes formal properties for RTL, runs a model checker, and iterates on counterexamples.

Project description

Toroid

AI that writes and proves correctness properties for chips.

Toroid reads a spec and a small RTL module, synthesizes formal properties with an LLM, runs an open-source model checker (Yosys + SymbiYosys), and iterates on counterexamples — automating the hardware-verification engineer's inner loop. The LLM proposes; the solver disposes, so hallucination cannot pass verification. Every reported result is a bounded proof to depth k, an unbounded proof, or a concrete counterexample waveform — never the model's unverified say-so.

The demo: point it at a FIFO with an injected off-by-one in its full flag. Toroid checks the flag/occupancy invariant full ⟺ count==DEPTH, proves it on the correct FIFO and falsifies it on the buggy one with a solver counterexample — a state where the flag disagrees with the true occupancy at the DEPTH boundary — and narrates the failing cycle deterministically.

Status: M1–M5 built — verifies real RTL end-to-end, catches an injected FIFO bug, and checks RTL-to-RTL equivalence, all on a real solver. The LLM synthesis path ran live against claude-opus-4-8 and is replayed from a recorded fixture in CI. See ROADMAP.md for the plan and PROGRESS.md for what's shipped.

The whole thing in one screen — the injected FIFO bug, caught by a real solver, with the failing cycle narrated (make demo; regenerate with make screenshot, which paints the command's actual stdout — nothing here is typed by hand):

toroid verify on the buggy FIFO: F1 falsified with a counterexample, F2 proven


See it work

1 — The AI writes the properties; the solver proves them (recorded live, claude-opus-4-8). Claude reads the spec + the Yosys-extracted interface and synthesizes properties; they pass a Yosys compile gate, then the model checker discharges each one. This run produced 9 properties — 5 safety asserts + 4 reachability covers — which compiled on the first attempt; every assert proves:

# recorded synthesis: 9 properties (5 asserts + 4 covers), compiled in 1 attempt
| Property                                       | Verdict   | Engine              |
|------------------------------------------------|-----------|---------------------|
| P1  After synchronous reset, count is 0        | ✅ PROVEN | yosys-sat (minisat) |
| P2  Enabled below max increments by 1          | ✅ PROVEN | yosys-sat (minisat) |
| P3  Enabled at max saturates at 15 (no wrap)   | ✅ PROVEN | yosys-sat (minisat) |
| P4  Disabled out of reset holds value          | ✅ PROVEN | yosys-sat (minisat) |
| P5  count never exceeds 15                      | ✅ PROVEN | yosys-sat (minisat) |

Recorded to tests/fixtures/counter.synth.json and replayed offline with no API keypytest -m integration -k recorded_synthesis reproduces the table above (the 4 covers are anti-vacuity witnesses; yosys-sat reports unknown for cover mode, so only the 5 asserts carry a verdict). A fresh live run (needs ANTHROPIC_API_KEY, and is non-deterministic run to run): toroid verify designs/counter.v --spec designs/counter.md --top counter.

2 — It catches a real bug, with a solver counterexample. The buggy FIFO computes its full flag one slot early — assign full = (cnt == 3'd3) instead of == 4. The structural invariant full ⟺ count==DEPTH(4) proves on the correct FIFO and is falsified on the buggy one; the solver returns a state where count == 4 yet full == 0, exactly the dropped boundary:

$ toroid verify designs/fifo_buggy.v --top fifo --no-llm --props designs/fifo.props.json
| F1  full is asserted exactly when count == DEPTH (4) | ❌ FALSIFIED | yosys-sat | depth 20 |
| F2  empty is asserted exactly when count == 0        | ✅ PROVEN    | yosys-sat |    —     |

step |  rst  wr_en  rd_en  full  empty  count
   0 |   1     0      1      0     0      4    ← count==4 but full==0: the off-by-one boundary
   1 |   1     0      0      0     1      0
   ⋮   (steps 2–18 identical: rst=1, count=0, full=0, empty=1)
  19 |   0     0      0      0     1      0    ← counterexample ends

On the lightweight yosys-sat backend the counterexample lands at the unconstrained initial state, because that backend ignores assume cells (ADR-0004). The sby + Bitwuzla backend lifts that limit and is live and green in CI (--backend sby): it honors assumptions and discharges covers, so assume-dependent properties prove and a pass whose witness is unreachable is caught as VACUOUS — see designs/counter_assume.props.json and designs/counter_vacuous.props.json, which the formal-sby CI job runs on every push.

3 — It proves (or disproves) RTL-to-RTL equivalence. A miter with shared symbolic inputs — the hardware sibling of Congruent:

$ toroid equiv designs/max2.v designs/max2_alt.v     --top-a max2 --top-b max2_alt      → ✅ PROVEN
$ toroid equiv designs/max2.v designs/max2_min_bug.v --top-a max2 --top-b max2_min_bug  → ❌ FALSIFIED
    distinguishing input at step 0:  a=4  b=2  →  max2 y=4   impostor y=2

Every verdict is tied to a real solver run — never the model's say-so. A pass with an unreachable witness is reported VACUOUS, and PROVEN is only awarded by an unbounded engine (k-induction / PDR); BMC alone is an honest BOUNDED-PASS.

bug-catch benchmark: proven vs falsified per design

The solver runs (2, 3, and the bug-catch chart) need nothing but pip install (Yosys via yowasp-yosys) and run on Windows; the live-LLM synthesis in (1) needs an ANTHROPIC_API_KEY, and its output is recorded as a fixture so CI replays the AI path with no key.


What it can't do

The honest limits, so the claims above aren't read wider than they are:

  • No full concurrent SVA. Properties use the open-Yosys subset (immediate assertions
    • $past/$rose/$stable/$anyseq/$anyconst). Sequences and the full temporal layer need the commercial Verific frontend — a deliberate non-goal (ADR-0002).
  • Small, single-clock modules. Counter/FIFO/arbiter scale (≲500 lines). No multi-clock or CDC, no full-chip, no industrial IP.
  • Safety properties only — no liveness or fairness.
  • The lightweight backend ignores assumptions. yosys-sat (the pip-only default) ignores assume cells (ADR-0004), so assume-dependent properties can't be proven and covers aren't discharged — use --backend sby for those. Both backends narrate counterexamples cycle-by-cycle.
  • It never edits your RTL. An rtl_bug verdict is terminal: Toroid diagnoses and pinpoints, it does not rewrite the design under test.
  • LLM synthesis is non-deterministic. A live re-run may propose a different (still gated) property set; the committed fixture is the pinned, reproducible artifact.
  • The benchmarks are illustrative, not a benchmark claim. Three tiny designs, no confidence intervals and no named external baseline — they show the flow works and the bug is caught, nothing more.
  • No floating-point, analog, gate-level, or timing verification.

Run it

Prerequisites: Python ≥ 3.11 (check: python --version). For the actual verification you need a model checker — pick one:

  • Lightweight (works on Windows, no admin): pip install yowasp-yosys — Yosys as WebAssembly. Drives the built-in sat backend (internal minisat, no external solver). This is what the dev install (.[dev]) pulls in.
  • Full stack: the OSS CAD Suite (Yosys + SymbiYosys + Bitwuzla) on PATH — https://github.com/YosysHQ/oss-cad-suite-build — for the sby backend, which honors assume cells and discharges covers (see ADR-0004). Linux is the supported path (it's what CI runs; use WSL2 on Windows); the native Windows build also works — see the "H2" entry in PROGRESS.md for a packaging caveat.

The offline demo and the unit tests need neither.

python -m venv .venv && source .venv/Scripts/activate   # Windows Git Bash
                                                        # (Linux/macOS: .venv/bin/activate)
pip install -e ".[dev]"     # once (includes yowasp-yosys)

make demo                   # ONE COMMAND: prove real RTL, catch a real bug with a
                            # counterexample, and check equivalence. No key, no OSS CAD
                            # Suite. (`make check` = lint + typecheck + tests.)

toroid demo               # offline showcase: render a wrapper + a sample report
# Verify real RTL (run from the repo root; yowasp-yosys is sandboxed to the CWD):
toroid verify designs/counter.v --spec designs/counter.md --top counter --no-llm
# → P1, P2 ✅ PROVEN.  Try the over-strong property to see a counterexample:
toroid verify designs/counter.v --top counter --no-llm --props designs/counter_bad.props.json
# → PB ❌ FALSIFIED, with a waveform written under designs/_build/.

pytest                      # unit tests (fast, offline)
pytest -m integration       # runs a real proof via Yosys (needs a Yosys on PATH)

The demo that lands

# Clean FIFO: the flag/occupancy invariants hold.
toroid verify designs/fifo.v --top fifo --no-llm --props designs/fifo.props.json
#   → F1 (full ⟺ count==4), F2 (empty ⟺ count==0)  ✅ PROVEN

# Buggy FIFO (off-by-one `full`): the bug is caught with a counterexample.
toroid verify designs/fifo_buggy.v --top fifo --no-llm --props designs/fifo.props.json
#   → F1 ❌ FALSIFIED (waveform shows full=0 at count==4); F2 ✅ PROVEN

# Benchmarks (charts need the `bench` extra: pip install -e ".[bench]"):
python -m benchmarks.bugcatch          # per-design verdicts + bug-catch chart
python -m benchmarks.depth_vs_time     # bounded-proof time vs BMC depth

# RTL-to-RTL equivalence:
toroid equiv designs/max2.v designs/max2_alt.v    --top-a max2 --top-b max2_alt     # ✅ PROVEN
toroid equiv designs/max2.v designs/max2_min_bug.v --top-a max2 --top-b max2_min_bug # ❌ distinguishing input

Commands

Command What it does
toroid demo Offline: render the formal wrapper + a sample verdict report.
toroid verify <rtl…> --top <name> --no-llm Discharge a hand-written property file against the RTL.
toroid verify … --backend {auto,sby,yosys-sat} Pick the engine (auto: sby if present, else yosys-sat).
toroid equiv <a.v> <b.v> --top-a A --top-b B Prove two designs equivalent, or find a distinguishing input.
toroid extract <rtl…> --top <name> Print the extracted interface model (debug).
pytest / pytest -m integration Unit tests / live end-to-end tests.
ruff check . && mypy Lint + typecheck.

How to give feedback

You mainly test and report. Every milestone in ROADMAP.md ends with explicit Test steps — run them, then:

  • Describe what happened in plain language.
  • Paste any errors verbatim (the single most useful thing).
  • For verification runs, the report (--report out.md) is the artifact to share.

Project docs

Doc What's in it
DESIGN.md The full design and rationale — the single source of truth.
ROADMAP.md The milestone checklist (the plan + what's done).
PROGRESS.md Build log: what shipped each milestone and why.
docs/ Architecture decision records (ADRs) and long-form notes.
Toroid-Foundational-Doc.md The origin thesis and positioning.

Tech stack

Python 3.11 · Yosys + SymbiYosys + Bitwuzla (open formal stack, ISC/MIT) · Anthropic SDK (claude-opus-4-8) · pytest / ruff / mypy. Pairs with Congruent (software equivalence) as the formal-methods-meets-AI portfolio spine.

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

toroid-0.0.1.tar.gz (43.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

toroid-0.0.1-py3-none-any.whl (49.8 kB view details)

Uploaded Python 3

File details

Details for the file toroid-0.0.1.tar.gz.

File metadata

  • Download URL: toroid-0.0.1.tar.gz
  • Upload date:
  • Size: 43.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for toroid-0.0.1.tar.gz
Algorithm Hash digest
SHA256 c0074bceed9e7842ff31a425dd55446462fdd0adb963dae4a7c1ba3c0b11a0cd
MD5 a6f24d1a10b15d40e76474d8c66af5f9
BLAKE2b-256 a12285c2756e8957aceaf2c384e328f2fbcfc1e5b595920218b10d3bf9cc206f

See more details on using hashes here.

Provenance

The following attestation bundles were made for toroid-0.0.1.tar.gz:

Publisher: publish.yml on satchmakua/toroid

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file toroid-0.0.1-py3-none-any.whl.

File metadata

  • Download URL: toroid-0.0.1-py3-none-any.whl
  • Upload date:
  • Size: 49.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for toroid-0.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 e1711365a520b4cf77a73ef7453106b5e357d5a6135d68939089686ab956ea10
MD5 91dde36edccc7a37e5d8edf0e6f5c424
BLAKE2b-256 676eb0468486ee0095b42e7280514246e5015cd3c9d048afca766bfb4837ff90

See more details on using hashes here.

Provenance

The following attestation bundles were made for toroid-0.0.1-py3-none-any.whl:

Publisher: publish.yml on satchmakua/toroid

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page