Skip to content

Latest commit

 

History

History
226 lines (185 loc) · 11.6 KB

README.md

File metadata and controls

226 lines (185 loc) · 11.6 KB

pyCircuit

pyCircuit

Describe hardware in Python. Compile once. Simulate in C++ or Verilog.

CI Release Latest release BSD 3-Clause license Python 3.11 or later LLVM and MLIR 22.1.8

Get started · Language reference · Examples · Repository map · Contribute

pyCircuit is a hardware programming language and compiler built on Python syntax and MLIR. Typed modules, persistent state and stateless rules describe the design. The compiler checks types, dependencies and effects, then emits C++ simulation models and Verilog from the same verified hardware IR.

Use it to build reusable hardware modules, compose stateful designs, and run closed simulation systems with compiler-generated drivers. Python supplies the authoring syntax; capture parses the source without executing the design.

What you get

  • Hardware semantics in familiar syntax. Fixed-width values, records, Tables, rules and module composition, with explicit admission and diagnostics.
  • Two backends, one verified design. GFSIM C++ models and Verilog consume the same final artifact, with source ownership preserved across compilation units.
  • Executable systems. A closed @system describes the DUT, stimulus and checks. The compiler generates native and Verilator simulation harnesses.
  • Atomic simulation steps. Independent autonomous Rules start from committed state; nested calls share that Rule's sequential candidates. Local Rule declarations are scheduled automatically; independent Rules never forward candidates. Whole-system checks precede commit, so a failed epoch changes no state or clock history and publishes no source observations.
  • A separate Runtime package. Generated C++ consumers use the Runtime CMake component without installing LLVM or the compiler development package.

The checkout identifies itself as pyCircuit 6.1. The language is under active development: general parameter-dependent elaboration, broader clock scheduling, wide/four-state observation transport and some collection forms remain limited. The language reference defines supported constructs; known limitations records the boundaries.

A complete counter

# ruff: noqa: F823 -- the nested Rule updates System-owned state.
import pycircuit as ac


@ac.system
def HelloCounter():
    count: ac.u8 = 0

    @ac.rule
    def increment() -> None:
        ac.log("info", "count", count)
        count = count + 1

count is eight-bit persistent storage. The rule observes its current value and proposes an increment modulo 256. The compiler infers storage, clock/reset connections and write enables, and generates the simulation harness.

From a checkout with an installed toolchain:

pycircuit run examples/hello_counter --target cpp --cycles 5
pycircuit run examples/hello_counter --target verilog --cycles 5

Each cycle contains low and high sampling epochs, so both runs log 0, 0, 1, 1, 2, 2, 3, 3, 4, 4. Generated files and simulator builds stay under .pycircuit_out/run/hello_counter/. Verilog execution requires Verilator. Use --toolchain /absolute/path/to/install when selecting an external toolchain.

See the quickstart for explicit compile/link/ emit commands and the tutorial for inspecting and running the generated artifacts.

A bounded shared execution model can declare calculation, architectural state/effects and finite queue/II timing once. Native link --execution functional|timing derives the selected execution entry; cpp|verilog remains a separate backend choice. The word and state-dependent digest example shows installed input-driven runs and independent transaction/timing checks. Architectural timers, cancellation and unsupported topology are rejected by this slice, rather than erased during functional projection.

For a portable bounded model project, the installed tool also provides pycircuit doctor, init, verify and benchmark. They reuse the same compiler/Runtime and supplied raw/state expectations. See the project workflow and product scope/roadmap. A separate complete-state clocked profile supports pure hierarchy and checked idle-cycle folding. The accumulator example demonstrates installed native/physical-RTL execution without a second authored model. For a model with NDF requirements, pycircuit spec-check checks the versioned Spec and named Python implementation/test bindings using a pinned external NDF CLI. See the specification workflow. Traceability, behavioral validation and specification approval remain distinct.

Build from source

Task Requirements
Build the compiler Python 3.11+, CMake 3.25+, Ninja, a C++20 compiler, LLVM and MLIR 22.1.8
Run generated C++ C++20 toolchain, CMake/Ninja and pyCircuit Runtime
Run generated Verilog systems Installed pyCircuit compiler, Verilator, CMake/Ninja and a C++20 toolchain

Install LLVM/MLIR first. The build helper discovers llvm-config-22 or a matching llvm-config; alternatively set LLVM_CONFIG, or both LLVM_DIR and MLIR_DIR, to that installation. Installing the Python package alone does not install the native compiler.

git clone https://github.com/PTO-ISA/pyCircuit.git
cd pyCircuit
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -e .
PYC_BUILD_TESTING=OFF bash tools/pyc build

export PYC_TOOLCHAIN_ROOT="$PWD/.pycircuit_out/toolchain/install"
export PATH="$PYC_TOOLCHAIN_ROOT/bin:$PATH"
pycircuit run examples/hello_counter --target cpp --cycles 5

These commands use a POSIX shell. The installation guide covers CMake build options, Runtime-only installations and package consumers. For prebuilt wheels, check the assets and platform support of the specific release.

One compiler flow

Python modules and systems
          │ syntax capture
          ▼
MLIR type, dependency and effect analysis
          │ hardware lowering and verification
          ▼
Independent source units ── explicit link closure ── verified final IR
                                                        │
                                               ┌────────┴────────┐
                                               ▼                 ▼
                                          GFSIM C++           Verilog
Command Purpose
pycircuit compile Compile one source and publish its body, interface and dependencies.
pycircuit link Check the complete explicit source-unit closure and select the design root.
pycircuit emit Emit C++ or Verilog from the verified final artifact.
pycircuit run Build and execute a closed system through those same public stages.

Reusable functional numeric source units provide binary32/binary64 arithmetic examples, direct and integral rounding, configurable FP4/FP8 value codecs, word arithmetic and bit transformations through this public flow. Their inspectable providers, build driver and independent native/RTL oracles live in examples/functional_float; they are bounded example algorithms, rather than a complete floating library or ISA. The same reference separately records actual latest-selected Bench802/ASL validation and the SSM-owned matrix-default consumer fix; it does not replace older frozen or newcc799 measurements with new-asset results. The registered word-state System example now demonstrates public numeric composition, commit state and sticky logical effects with native workers1/2 and RTL, independently of SSM.

@module defines reusable hardware, @rule defines an atomic stateless transition, and @system defines a closed simulation composition. The current system path owns its generated clock/reset scaffolding; ordinary module drivers can provide explicit physical inputs. See system execution for sampling, reset, observation and failure semantics.

Explore and contribute

Start with For
Repository map Folder responsibilities, source boundaries and the right place for a change.
Recommended examples The 21 current teaching and design examples, ordered by purpose.
Design gallery Self-contained projects, including the RV32I core and an NDF project starter.
Example catalog Generated navigation and exact verification owners for those examples.
Python language Types, source constructs, composition and rejection boundaries.
Compiler architecture Capture, MLIR analyses, source publication and code generation.
Build and test workflow Bounded gates, native tests and nightly coverage.
Contribution guide Scope, contracts, review and reproducible changes.

For compiler development, enable PYC_BUILD_TESTING=ON and install the native test dependencies, including GoogleTest and lit. With that testing build installed, run the bounded checks from the repository root:

bash tools/run_api_tests.sh --tier gate
bash tools/run_examples.sh --tier gate

For a nondefault build, set PYC_BUILD_DIR and PYC_TOOLCHAIN_ROOT as described in the testing guide. PR CI checks Python, publication, repository hygiene and documentation; native, example and release evidence have their own documented entrypoints. A green CI badge does not imply full backend or platform coverage.

The active catalog contains 21 current examples. Fifty-four superseded wrappers and their original oracles remain in the historical fixture archive; an archived fixture is not a passing result. Retired frontends, Signal APIs and compiler aliases are not compatibility paths.

License

pyCircuit is licensed under BSD-3-Clause.