Spire HDL is a hardware description language for digital circuits.
Project description
Spire — a modern Python HDL that compiles concise, composable hardware descriptions to synthesizable Verilog and AIG netlists, with synthesis optimization and a cycle-accurate simulator built in.
- Built for humans and agents alike: a small surface that stays readable as a design grows
- Reduces area and delay vs. a traditional Verilog flow: optimization is part of the compile
- 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
@abc_optimized/@flowy_optimizeddecorators
Optimizations built in 💡
Spire supports source-level 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.
Because these passes run as part of the compile, the 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: @arithmetic_optimized
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
areaobjective - 4.2× shorter critical path with the
delayobjective - balanced
adpgets 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 Component or Netlist, 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_optimizedfor 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 a 7-state case10 Moore FSM:
- −19% cells with
optimized_encodingalone - −44% cells with
optimized_fsmalone - −69.5% cells when both are nested — a ~3× reduction with two
withblocks, 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/AIG, simulate, and collect Yosys metrics for direct comparison. See README_arithmetic_generator.md.
Overview
🪶 Minimal core
In its simplest form, Spire 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:
spire/expr.py– the expression DSL. It provides bit-precise types such asBool,UInt, andSInt, shared-expression caching, and the overloaded arithmetic / bitwise operators that make the Python syntax feel like an HDL.spire/component.py– theComponentbase class: author reusable designs, declare IO withIORecord/Input/Output, emit Verilog/AIG, analyze, and import/embed sub-designs. The flat netlist IR it lowers to lives inspire/ir.py(Netlist).spire/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
Deeper guides for specific features:
- Type system — values, the
BitSerializable/Assignabletraits, theExprvsHDLCompositehierarchy, and the…Likecoercion aliases (class diagram + philosophy) - Composite data types — structured bit-packable values (
Array,CompositeRecord,FixedPoint,FloatingPoint,CompositeRegister) withto_bits/<<=/@= - Interfaces — reusable IO bundles (
Stream,Flow,MemPort) withFlipped/connect/view_as_flippedand on-interface behaviour - State machines — declaration with the
State/EncodingAPI andswitch_/case_bodies - Control structures —
if_/elif_/else_andswitch_/case_/defaultcontext managers - Memories — RAM / ROM / FIFO primitives, port wiring with
<<=, simulation, and reading state - Arithmetic optimization — automatic replacement with optimized versions (adders, multipliers, MAC, etc)
- Optimization decorators —
@abc_optimized/@flowy_optimizedcircuit optimization - FSM optimization —
optimized_fsmandoptimized_encoding(state minimisation + encoding search) - Arithmetic generators — evaluation scripts and extra tooling notes
- Tiled matmul accelerator — parametrizable signed output-stationary matmul over a
T×T×Tcore, with input/output RAMs and amode+ RAM-access interface - Custom Verilog — emit a raw Verilog block from a
CustomVerilogComponent, with or without a Python sim model (blackbox) - AIG / AAG export & import — lower a
Componentto an AIGER netlist and read AIG/AAG back in as aComponent - Verilog testbench — turn a
Simulatorrun into a self-checking, synthesizable Verilog testbench - Examples — example designs exercising Spire features
Installation
Install the latest release from PyPI:
pip install spire-hdl
For development, install from source in editable mode:
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.
Quick start
1. Describe a component
A Component is the one abstraction you author. Declare its IO once with IORecord
(field names become signal names; Input/Output set direction), put the logic in
elaborate(), and emit Verilog directly — without ever touching the IR:
from spire import Component, IORecord, Input, Output, Bool, UInt
from spire.expr import mux, cat
class LogicDemo(Component):
def __init__(self):
self.io = IORecord(
a=Input(UInt(8)),
b=Input(UInt(8)),
sel=Input(Bool()),
sum=Output(UInt(9)),
mask=Output(UInt(4)),
out=Output(UInt(8)),
)
self.elaborate()
def elaborate(self):
io = self.io
io.sum <<= io.a + io.b # automatic width growth
io.mask <<= cat(io.a[7], io.b[7]) # concatenate slices
io.out <<= mux(io.sel, io.a & io.b, io.a | io.b)
demo = LogicDemo()
print(demo.to_verilog(name="LogicDemo"))
to_verilog() checks that every output has a driver (and every register a next-state assignment) before emitting Verilog (see component.py).
Registers are created via the standalone Register class, which takes 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 <<=, and pass with_clock/with_reset when emitting a sequential component:
from spire import Component, IORecord, Output
from spire.expr import Register, UInt
class Counter(Component):
def __init__(self):
self.io = IORecord(q=Output(UInt(8)))
self.elaborate()
def elaborate(self):
cnt = Register(UInt(8), init=0) # reset value via init=
cnt <<= cnt + 1 # next-state = cnt + 1
self.io.q <<= cnt
print(Counter().to_verilog(name="Counter", with_clock=True, with_reset=True))
2. Simulate the design
from spire import Simulator
sim = Simulator(demo) # Simulator lowers the Component internally
sim.set("a", 0xC3)
sim.set("b", 0x99)
sim.set("sel", 1)
sim.eval() # recompute combinational logic
print(sim.peek_outputs()) # {'sum': 0x15c, 'mask': 0x3, 'out': 0x81}
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 (simulator.py).
3. Export, import & equivalence-check
A Component converts to Verilog, AIGER, or the flat netlist IR — and re-imports the same formats — so you can hand a design to synthesis, optimize it externally, and check the result is still equivalent:
from spire import Component
from spire.aig.aig_aigerverse import conv_aag_into_aig
from aigverse import Aig, equivalence_checking
verilog = demo.to_verilog(name="LogicDemo") # synthesizable Verilog (str)
aag = demo.to_aag(name="LogicDemo") # AIGER ASCII lines (list[str])
net = demo.to_netlist(name="LogicDemo") # flat netlist IR (Netlist)
demo2 = Component.from_netlist(net) # re-import the IR (also: .from_verilog / .from_aag_lines)
# equivalence-check the original against the re-imported design (aigverse, on their AIGs)
a1 = conv_aag_into_aig(demo.to_aag(name="LogicDemo"), Aig())
a2 = conv_aag_into_aig(demo2.to_aag(name="LogicDemo"), Aig())
assert equivalence_checking(a1, a2)
Components and the netlist IR
Componentis the one abstraction you author. It builds Verilog/AIG directly (to_verilog,to_aag), analyzes timing (analyze), imports designs from Verilog or AIG formats (from_verilog,from_aag_lines,from_netlist), and embeds reusable sub-designs into the surrounding logic automatically when their IO is wired. Components exposeget_ios()/get_spec()as the single IO normalization point — also what drives port regrouping when you import flattened designs (seecomponent.py).Netlist(inspire.ir) is the flat, lowered netlist that every backend consumes — Spire's internal IR. Power users can build one directly: it offers constructors for inputs, outputs, wires, and registers; signal enumeration; Verilog emission with automatic width fitting; and ananalyze()routine reporting combinational depth and node counts. The quick start never needs it.- Minimal end-to-end component example:
testing/examples/simple_component.py.
Short component + hierarchy usage example:
from spire import Component, IORecord, Input, Output, UInt
class SimpleAdder(Component):
def __init__(self, width=8):
self.width = width
self.io = IORecord(
a=Input(UInt(width)),
b=Input(UInt(width)),
sum=Output(UInt(width + 1)),
)
self.elaborate()
def elaborate(self):
self.io.sum <<= self.io.a + self.io.b
class Sum3Hierarchical(Component):
def __init__(self):
self.io = IORecord(
a=Input(UInt(8)),
b=Input(UInt(8)),
c=Input(UInt(8)),
sum=Output(UInt(10)),
)
self.elaborate()
def elaborate(self):
add_ab = SimpleAdder(width=8) # first sub-component
add_abc = SimpleAdder(width=9) # 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
print(Sum3Hierarchical().to_verilog(name="Sum3Hier")) # one flat module, built from embedded components
Hierarchical design with components
Components are how to build hierarchy: instantiate one inside another, adapt its IO, or drop in a pre-synthesized netlist — all without leaving Python. A common pattern wraps a reusable Spire block (see multipliers_ext.py). It is also possible to import an external AIG module, turn it into a Component, and use it directly as a native Spire block inside a larger generator (see multipliers_ext_optimized.py). The same approach covers Verilog imports, so Spire code and external IP mix freely.
Composite data types
Spire includes structured, bit-packable composites for cleaner interfaces and bulk assignments (composite/). See README_composite_types.md for the full reference with an example for every type:
HDLCompositedefines the base "pack to bits" API that powers all composites (base.py).Arrayoffers N-dimensional indexing, packed assignment (<<=), and element-wise assignment (@=) for nested vectors or composites (array.py).CompositeRecordis the bundle of named fields that stays packable to a flat bitvector — build it inline (CompositeRecord(a=Input(...), ...)), via a subclass__init__callingsuper().__init__(...), or as a@dataclass; declare fields as annotations for IDE autocomplete (record.py).FixedPointwraps aWireor view with explicit total/frac widths and quantization helpers, keeping arithmetic readable while staying hardware-friendly (fixed_point.py).FloatingPointprovides an IEEE-style view withadd/mulhelpers parameterized by exponent / fraction widths (floating_point.py).CompositeRegisterstores any composite in a single register while preserving a structured view via.value/.Q(register.py).
Example:
from spire.composite.array import Array
from spire.composite.record import CompositeRecord
from spire.composite.fixed_point import FixedPoint, FixedPointType
from spire.composite.register import CompositeRegister
from spire.expr import UInt, Wire
class Bus(CompositeRecord):
data = Wire(UInt(8))
valid = Wire(UInt(1))
payload = Array([Bus(), Bus()])
acc = FixedPoint(FixedPointType(width_total=16, width_frac=8))
acc_reg = CompositeRegister(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()andget()let you drive or inspect signals by name.step()advances the clock, committing register next-state expressions while honoring asynchronous resets.watch()andpeek_next()provide scope-style visibility for debugging complex pipelines.
For waveforms, set sim.trace_enabled = True before driving the design, then dump the captured trace to a VCD file:
from spire.various.vcd_writer import write_vcd
sim.trace_enabled = True
sim.eval()
for _ in range(5):
sim.step()
write_vcd(trace_by_names=sim.get_trace_by_names(), filename="run.vcd", top_module=m.name, timescale="1ns")
Slices
Spire signals follow Python's indexing convention. For example, sig[4:7] creates a new expression made up of bits 4, 5, and 6 (counted from the LSB) of the original expression sig.
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 Verilogcomponent_example.py– Comprehensive examples including hierarchical design and simulationcomposing_components.py– Shows how to compose a largerComponentfrom smaller automatically embedded componentsdirect_expression_basics.py– Minimal direct expression examples (y = a + b) plus+,-, unary-,Const(..., Int(...)), typed/plainFalse, and a recursive Horner polynomial buildertesting/riscv/rv32i.py– Minimal RV32I core example; seetesting/riscv/test_rv32i.pyfor simulation-based checks.
See the examples README for detailed documentation and key concepts.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file spire_hdl-0.2.1.tar.gz.
File metadata
- Download URL: spire_hdl-0.2.1.tar.gz
- Upload date:
- Size: 340.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
64a0664cbcf702e62a723bc693ec08b0c393ffe4e4306c28047266a1e4dfe824
|
|
| MD5 |
f61c5ecc1b060cb3c99a3d0b3adb66d5
|
|
| BLAKE2b-256 |
5a9b258566c45e04089013eb21a895fee9c32609ccd68bfa0e0353a4c968c3cb
|
Provenance
The following attestation bundles were made for spire_hdl-0.2.1.tar.gz:
Publisher:
ci.yml on huawei-csl/spire-hdl
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
spire_hdl-0.2.1.tar.gz -
Subject digest:
64a0664cbcf702e62a723bc693ec08b0c393ffe4e4306c28047266a1e4dfe824 - Sigstore transparency entry: 2047496029
- Sigstore integration time:
-
Permalink:
huawei-csl/spire-hdl@30cdb2acff67fe0a4a7baef572b8ef89b6ecaf3e -
Branch / Tag:
refs/heads/main - Owner: https://github.com/huawei-csl
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@30cdb2acff67fe0a4a7baef572b8ef89b6ecaf3e -
Trigger Event:
push
-
Statement type:
File details
Details for the file spire_hdl-0.2.1-py3-none-any.whl.
File metadata
- Download URL: spire_hdl-0.2.1-py3-none-any.whl
- Upload date:
- Size: 420.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
abf9eff99ff0a45f2c51c99487ff16c16eddd81047d7927218f25983a7d5abe9
|
|
| MD5 |
9fb3f392b049c593aaf4dade8a83826c
|
|
| BLAKE2b-256 |
a1cdcdf773d74ff10ef5c9d8dd0a4ef7939ce76d81e0418473894a28dbd61b7e
|
Provenance
The following attestation bundles were made for spire_hdl-0.2.1-py3-none-any.whl:
Publisher:
ci.yml on huawei-csl/spire-hdl
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
spire_hdl-0.2.1-py3-none-any.whl -
Subject digest:
abf9eff99ff0a45f2c51c99487ff16c16eddd81047d7927218f25983a7d5abe9 - Sigstore transparency entry: 2047496046
- Sigstore integration time:
-
Permalink:
huawei-csl/spire-hdl@30cdb2acff67fe0a4a7baef572b8ef89b6ecaf3e -
Branch / Tag:
refs/heads/main - Owner: https://github.com/huawei-csl
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@30cdb2acff67fe0a4a7baef572b8ef89b6ecaf3e -
Trigger Event:
push
-
Statement type: