Skip to main content

SpireHDL is a hardware description language for digital circuits.

Project description

SpireHDL

A modern Python HDL that compiles concise, composable hardware descriptions to synthesizable Verilog and AIG/AAG netlists — with synthesis optimization and a cycle-accurate simulator built in.

  • Designed for humans and agents to be effective: a small surface that reads well to engineers and LLMs alike
  • Reduces area and delay vs. a traditional Verilog flow: optimization is part of the compile, not an afterthought
  • Integrated with ABC and mockturtle: modern synthesis optimization wired directly into the compilation pipeline
  • Arithmetic library with automated replacement: swap adders, multipliers, and FP cores driven by an objective
  • Cycle-accurate Python simulator" drive inputs, tick clocks, inspect expressions/outputs without leaving Python
  • Content-addressed optimization cache: instant re-runs via @flowy_optimized / @abc_optimized decorators

Optimizations built in ⚡

SpireHDL supports source-embedded optimization intent: the designer marks what to optimize (e.g. a module, FSM, or arithmetic block) directly in the HDL source, and the compiler realizes it through synthesis-aware passes.

These optimization layers run inside the compile pipeline, so emitted Verilog is already small and fast before external tools see it. The numbers below are measured against a plain Yosys flow on the same RTL.

🔢 Arithmetic auto-replacement: replace_arithmetic_ops

Drops in topology-tuned adders, multipliers, and MAC fusions against an area / delay / adp objective. On an 8-bit ALU (add + sub + mul):

  • −51% transistors with the area objective
  • 4.2× shorter critical path with the delay objective
  • balanced adp gets near-minimal area and near-minimal depth at once

MAC patterns (a*b + c) are fused into single column-reduction units, eliminating a full adder stage. See README_arithmetic_optimization.md.

🧠 ABC + mockturtle decorators: @abc_optimized / @flowy_optimized

One decorator stacks modern AIG synthesis (resyn2, &deepsyn, mockturtle) onto any Module or Component, with a content-addressed cache for instant re-runs:

  • −69% AIG gates on an 8-bit multiplier (resyn2)
  • −83% on a 16-bit multiplier
  • stack with @arithmetic_optimized for compounding wins — ABC cleans up after the arithmetic rewriter

See README_optimization_decorators.md.

🎯 FSM + encoding search: optimized_fsm / optimized_encoding

Hopcroft state minimisation and bit-assignment search as two composable context managers. On the canonical 7-state case10 Moore FSM:

  • −19% cells with optimized_encoding alone
  • −44% cells with optimized_fsm alone
  • −69.5% cells when both are nested — a ~3× reduction with two with blocks, no hand-tuned encoding tables

An 8-opcode CPU decoder sees −66.7% cells from a single optimized_encoding, because the search discovers an opcode layout where each wide OR collapses to one bit-test. See README_fsm_optimization.md.

🛠️ Fine-grained architecture selection: arithmetic_generator

Beyond the automatic passes above, the unified arithmetic generator lets you hand-pick the exact micro-architecture of an adder, multiplier, MAC, or matmul (partial-product generation, compression-tree topology, and final-stage adder), then emit Verilog/AAG, simulate, and collect Yosys metrics for direct comparison. Use it to explore the design space at full granularity when you want to drive the architecture choice yourself rather than leaving it to the objective-driven replacer. See README_arithmetic_generator.md.

Overview

🪶 Minimal core

In its simplest form, SpireHDL only needs these core files. This is intentional — the HDL is kept to a minimal, self-contained core, and higher-level features are layered on top:

  • spirehdl/spirehdl.py – the expression DSL. It provides bit-precise types such as Bool, UInt, and SInt, shared-expression caching, and the overloaded arithmetic / bitwise operators that make the Python syntax feel like an HDL.
  • spirehdl/spirehdl_module.py – structural modeling helpers. The Module class constructs ports, wires, and registers, produces Verilog, and exposes analysis utilities. The Component base class lets you package reusable sub-designs and convert them to or from SpireHDL modules. IOCollector can rebuild packed ports from bit-level signals when importing external netlists.
  • spirehdl/spirehdl_simulator.py – a lightweight simulator that can drive inputs, tick clocks, inspect outputs or internal expressions, and capture probes for debugging—all without leaving Python.

📚 Further reading

Other markdown documents in this repository:

Installation

git clone https://github.com/huawei-csl/spire-hdl.git
cd spire-hdl
pip install -e .

The library relies on the packages listed in requirements.txt. Optional regression tests require Yosys/Pyosys and aigverse if you plan to exercise the external tooling integration flows.

Quick start

1. Describe a module

from spirehdl.spirehdl_module import Module
from spirehdl.spirehdl import Bool, UInt, mux, cat

m = Module("LogicDemo", with_clock=False, with_reset=False)
a = m.input(UInt(8), "a")
b = m.input(UInt(8), "b")
sel = m.input(Bool(), "sel")
sum_ = m.output(UInt(9), "sum")
mask = m.output(UInt(4), "mask")
out = m.output(UInt(8), "out")

sum_ <<= a + b              # automatic width growth
top_bits = cat(a[7], b[7])
mask <<= top_bits           # concatenate slices
a_and_b = a & b
b_or_a = a | b
out <<= mux(sel, a_and_b, b_or_a)

print(m.to_verilog())

The Module API checks that every output has a driver and every register has a next-state assignment before emitting Verilog (see spirehdl_module.py).

Registers are created either via the standalone Register class or Module.reg(...). Both take a typ and an optional reset value via the init= keyword (note: the keyword is init, not reset_value / reset). Assign the next-state expression with <<=:

from spirehdl.spirehdl import Register, UInt

m = Module("Counter", with_clock=True, with_reset=True)
cnt = Register(UInt(8), init=0, name="cnt")       # or: cnt = m.reg(UInt(8), "cnt", init=0)
cnt <<= cnt + 1                                   # next-state = cnt + 1
m.output(UInt(8), "q") <<= cnt

2. Simulate the design

from spirehdl.spirehdl_simulator import Simulator

sim = Simulator(m)
sim.set("a", 0x55).set("b", 0x0F).set("sel", 1)
sim.eval()                 # recompute combinational logic
print(sim.peek_outputs())   # {'sum': 0x64, 'mask': 0x9, 'out': 0x05}

The simulator keeps track of inputs, wires, outputs, and registers, supports eval() for combinational updates, step() for clocked designs, and exposes helpers such as peek, peek_next, and signal watching for deeper inspection (spirehdl_simulator.py).

3. Integrate with external tooling

Modules can be exported to Verilog, AIG, or AAG for downstream synthesis, equivalence checking, or integration into larger verification environments. Import helpers then let you bring optimized or third-party netlists back into SpireHDL for continued composition and simulation (see spirehdl_module.py and multipliers_ext_optimized.py).

Modules and components in detail

  • Component subclasses package reusable structures. They can materialize new modules (to_module), import designs from Verilog or AIG formats (from_verilog, from_aag_lines), and retag ports as internals (make_internal). Components also expose get_spec() to drive IOCollector regrouping when you import flattened designs (see spirehdl_module.py).
  • Module is typically used at the top level or as an intermediate representation while you are still wiring a design. It offers constructors for inputs, outputs, wires, and registers; utilities for enumerating signals; Verilog emission with automatic width fitting; and a module_analyze() routine that reports combinational depth and node counts for timing exploration (spirehdl_module.py).
  • IOCollector helps rebuild packed buses (e.g., a[0] … a[N-1]a[N-1:0]) after reading back designs from AIG/AAG files or external synthesizers (spirehdl_module.py).
  • Minimal end-to-end component example: testing/examples/simple_component.py.

Short component + hierarchy usage example:

from dataclasses import dataclass
from spirehdl.spirehdl import UInt, Signal
from spirehdl.spirehdl_module import Component

class SimpleAdder(Component):
    def __init__(self, width=8):
        self.width = width
        @dataclass
        class IO:
            a: Signal
            b: Signal
            sum: Signal
        self.io = IO(
            a=Signal(name="a", typ=UInt(width), kind="input"),
            b=Signal(name="b", typ=UInt(width), kind="input"),
            sum=Signal(name="sum", typ=UInt(width + 1), kind="output"),
        )
        self.elaborate()

    def elaborate(self):
        self.io.sum <<= self.io.a + self.io.b

class Sum3Hierarchical(Component):
    def __init__(self):
        @dataclass
        class IO:
            a: Signal
            b: Signal
            c: Signal
            sum: Signal
        self.io = IO(
            a=Signal(name="a", typ=UInt(8), kind="input"),
            b=Signal(name="b", typ=UInt(8), kind="input"),
            c=Signal(name="c", typ=UInt(8), kind="input"),
            sum=Signal(name="sum", typ=UInt(10), kind="output"),
        )
        self.elaborate()

    def elaborate(self):
        add_ab = SimpleAdder(width=8).make_internal()     # first sub-component
        add_abc = SimpleAdder(width=9).make_internal()    # second sub-component
        add_ab.io.a <<= self.io.a
        add_ab.io.b <<= self.io.b
        add_abc.io.a <<= add_ab.io.sum
        add_abc.io.b <<= self.io.c
        self.io.sum <<= add_abc.io.sum

module = Sum3Hierarchical().to_module(name="Sum3Hier")
print(module.to_verilog())  # one top module, built from internal components

Hierarchical design with components

Components are ideal for assembling hierarchical designs: they let you instantiate another component, adapt its IO, and even swap in a pre-synthesized netlist without leaving Python. One common pattern wraps a reusable building block with make_internal() so that auxiliary logic can surround the core implementation while exposing a compact public interface (see mutipliers_ext.py). A related flow imports an external AIG module, converts it into a Component, and calls from_module(..., make_internal=True) so the imported logic behaves like a native SpireHDL block inside a larger generator (multipliers_ext_optimized.py). These techniques extend to Verilog importers and make it straightforward to mix SpireHDL-authored code with IP produced by external flows.

Aggregate data types

SpireHDL includes structured, bit-packable aggregates for cleaner interfaces and bulk assignments (aggregate/). See README_aggregate_types.md for the full reference with an example for every type:

  • HDLAggregate defines the base “pack to bits” API that powers all aggregates (hdl_aggregate.py).
  • Array offers N-dimensional indexing, packed assignment (<<=), and element-wise assignment (@=) for nested vectors or aggregates (aggregate_array.py).
  • AggregateRecord lets you declare bundle-like classes with named fields that remain packable to a flat bitvector (aggregate_record.py).
  • AggregateRecordDynamic is the dataclass-friendly variant whose fields are defined per-instance, ideal for parameterized IO records (aggregate_record_dynamic.py).
  • FixedPoint wraps a Wire or view with explicit total/frac widths and quantization helpers, keeping arithmetic readable while staying hardware-friendly (aggregate_fixed_point.py).
  • FloatingPoint provides an IEEE-style view with add/mul helpers parameterized by exponent / fraction widths (aggregate_floating_point.py).
  • AggregateRegister stores any aggregate in a single register while preserving a structured view via .value/.Q (aggregate_register.py).

Example:

from spirehdl.aggregate.aggregate_array import Array
from spirehdl.aggregate.aggregate_record import AggregateRecord
from spirehdl.aggregate.aggregate_fixed_point import FixedPoint, FixedPointType
from spirehdl.aggregate.aggregate_register import AggregateRegister
from spirehdl.spirehdl import UInt, Wire

class Bus(AggregateRecord):
    data = Wire(UInt(8))
    valid = Wire(UInt(1))

payload = Array([Bus(), Bus()])
acc = FixedPoint(FixedPointType(width_total=16, width_frac=8))
acc_reg = AggregateRegister(FixedPoint, acc.ftype, name="acc_reg")

acc_reg <<= acc            # packed register write
payload[1] @= payload[0]   # element-wise copy between bundles

Simulation notes

The simulator supports both combinational and sequential designs:

  • eval() recomputes combinational logic and captures registered probes.
  • set() and get() let you drive or inspect signals by name.
  • step() advances the clock, committing register next-state expressions while honoring asynchronous resets.
  • watch() and peek_next() provide scope-style visibility for debugging complex pipelines.

These capabilities align with the standard SpireHDL development flow: express a design, validate it in Python, then export it to your synthesis or verification stack.

Slices

We follow the indexing of python also in SpireHDL signals. For example sig[4:7] creates a new expression containing of bits 4 and 5 (counted from lsb) of the original expression sig.

Main development flow

  1. Model logic in Python. Use Module in the the top-level file and DSL expressions to capture datapaths, state machines, and control logic.
  2. Factor reusable pieces. Wrap recurring structures in Component subclasses so they can be instantiated, parameterized, or replaced with imported implementations.
  3. Simulate early and often. Drive stimuli with the simulator, observe register evolution, and iterate on the Python source before handing designs to downstream tools.
  4. Export netlists. Emit Verilog or AIG/AAG when you are ready for synthesis, formal checking, or integration with external flows.

Examples

Check out the testing/examples/ directory for practical examples:

  • simple_component.py – A minimal example showing how to define a Component with IO ports and generate Verilog
  • component_example.py – Comprehensive examples including hierarchical design and simulation
  • module_with_component.py – Shows how to integrate Components within Module-based designs
  • direct_expression_basics.py – Minimal direct expression examples (y = a + b) plus +, -, unary -, Const(..., Int(...)), typed/plain False, and a recursive Horner polynomial builder
  • testing/riscv/rv32i.py – Minimal RV32I core example; see testing/riscv/test_rv32i.py for simulation-based checks.

See the examples README for detailed documentation and key concepts.

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

spire_hdl-0.1.0.tar.gz (327.3 kB view details)

Uploaded Source

Built Distribution

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

spire_hdl-0.1.0-py3-none-any.whl (8.5 kB view details)

Uploaded Python 3

File details

Details for the file spire_hdl-0.1.0.tar.gz.

File metadata

  • Download URL: spire_hdl-0.1.0.tar.gz
  • Upload date:
  • Size: 327.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for spire_hdl-0.1.0.tar.gz
Algorithm Hash digest
SHA256 19ec90dda5ae97052281a97ddc105ffd97478420a40457422f41874c4aee886a
MD5 49c65d2c435404eddecc5e9bfb1134ba
BLAKE2b-256 12e0838a06653ff8262884c7d02fec2a425e7eb63288693cc658475710e707c5

See more details on using hashes here.

Provenance

The following attestation bundles were made for spire_hdl-0.1.0.tar.gz:

Publisher: ci.yml on huawei-csl/spire-hdl

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

File details

Details for the file spire_hdl-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: spire_hdl-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 8.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for spire_hdl-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 89f89526de69906195fcdd4e208836a9ddabf1210d53d6f30244cee4fb6d9637
MD5 4d76d2807273d955e92fc8223dd97e39
BLAKE2b-256 6916f25b9afce9be5db7025c576a6eeed1444f538f5a9a9ec942ee2b5191a038

See more details on using hashes here.

Provenance

The following attestation bundles were made for spire_hdl-0.1.0-py3-none-any.whl:

Publisher: ci.yml on huawei-csl/spire-hdl

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