hdl-cat
A Rust hardware description library re-architected around
comp-cat-rs — every abstraction
(composition, state, effects, simulation, codegen) is a morphism, a Kleisli
arrow, or a catamorphism over a comp-cat-rs effect type.
Conceptually parallel to RHDL: bit-precise integer types, a typed hardware description IR, a cycle-accurate simulator, and a Verilog backend. Implementation is independent and categorical.
Table of contents
- Quick start
- Architecture
- Core design
- Building circuits
- Stateful machines
- Simulation
- Verilog emission
- VCD traces
- The
#[kernel]macro - Per-crate tour
- Conventions
- License
Quick start
Build, simulate, and emit Verilog for a 4-bit counter:
use *;
Architecture
hdl-cat-error/ Shared Error enum (hand-rolled Display/From)
hdl-cat-bits/ Bits<N>, SignedBits<N> via const generics
hdl-cat-kind/ Hw trait + TypeDesc (runtime type witness)
hdl-cat-signal/ Signal<D, T> over comp_cat_rs::Stream
hdl-cat-ir/ Free-category IR (comp_cat_rs::Graph instance)
hdl-cat-circuit/ Circuit: Category + MonoidalCategory + Symmetric
hdl-cat-sync/ Mealy machines as IR + initial state
hdl-cat-sim/ Stream-based testbench / simulator / VCD tracer
hdl-cat-verilog/ Verilog AST + stateful emitter
hdl-cat-std/ Component library (counter, accumulator, FIFO, ...)
hdl-cat-macros/ #[kernel] proc-macro frontend
hdl-cat/ Umbrella crate + examples
Core design
- Signals are Streams.
Signal<D, T>wrapsStream<Error, T>; clock domains are zero-sized phantom types; domain-mixing is a type error. - Circuits form a symmetric monoidal category. Sequential composition is
Category::comp; parallel composition isMonoidalCategory::tensor_map. - IR is a free category.
HdlGraphimplementscomp_cat_rs::collapse::free_category::Graph. Compiled circuits are sequences of typedInstructions. Simulation and codegen walk the graph. - State is Kleisli.
Sync<S, I, O>holds an IR graph whose inputs and outputs pair(state, data), threaded cycle to cycle. No closures, no interior mutability. runat the boundary. Simulation and codegen buildIo/Streampipelines internally, calling.run()only at public entry points.
Building circuits
hdl-cat-circuit provides primitive gates and a categorical calculus for
composing them. Each gate is a CircuitArrow<A, B> where A and B are
typed objects (single Obj<T> or nested CircuitTensor).
use Category;
use ;
#
Parallel composition places two arrows side-by-side:
use MonoidalCategory;
use ;
#
Wire permutations (braid, associator, unitors) come from the monoidal
coherence arrows in hdl_cat_circuit::coherence.
Stateful machines
A Sync<S, I, O> wraps an IR graph whose inputs are state ⊗ input and
outputs are next_state ⊗ output. The simulator threads next_state into
the following cycle's state.
use Bits;
use ;
use Sync;
#
Sync machines compose via compose_sync (state becomes (S1, S2)),
par_sync (parallel, state (S1, S2)), and feedback_sync (one-cycle
loop-back).
Simulation
use *;
use BitSeq;
#
Testbench::run returns Io<Error, Vec<TimedSample<BitSeq>>>; .run()
collects the samples.
Verilog emission
Two emitters:
emit_graph— flat combinational view; treats every input wire as a module input port and every output wire as an output port. Useful for pure combinational circuits.emit_sync_graph— stateful view; promotes state wires toregdeclarations driven byalways_ff @(posedge clk)blocks with synchronous reset from the machine's initial state.
The counter above emits as:
module counter4 (
input clk,
input rst,
output reg [3:0] w0
);
wire [3:0] w1;
wire [3:0] w2;
assign w1 = 4'd1;
assign w2 = (w0 + w1);
always_ff @(posedge clk) if (rst) w0 <= 4'd0; else w0 <= w2;
endmodule
VCD traces
hdl_cat_sim::trace_to_string runs a simulation and emits a VCD (Value
Change Dump) string. Each graph wire becomes a $var wire declaration;
one timestamp per cycle with per-wire values.
use *;
use BitSeq;
use trace_to_string;
#
Redirect the string to counter.vcd and open in GTKWave or Surfer.
The #[kernel] macro
#[kernel] lifts a small Rust-subset function into a CircuitArrow
builder. v1 supports: bool / Bits<N> / SignedBits<N> parameters,
let bindings, binary arithmetic/bitwise ops, unary !.
use *;
use kernel;
#
After expansion, xor_then_add becomes a nullary function returning
Result<CircuitArrow<CircuitTensor<Obj<Bits<8>>, Obj<Bits<8>>>, Obj<Bits<8>>>, Error>.
Per-crate tour
| Crate | Purpose |
|---|---|
hdl-cat-error |
The workspace-wide Error enum with hand-rolled Display/From |
hdl-cat-bits |
Bits<N>/SignedBits<N> const-generic integers, wrap arithmetic |
hdl-cat-kind |
Hw trait, TypeDesc, BitSeq buffer — the hardware-typable-values layer |
hdl-cat-signal |
Signal<D, T> over comp_cat_rs::Stream, clock-domain phantoms |
hdl-cat-ir |
HdlGraph, Instruction, Op — the IR implementing comp_cat_rs::Graph |
hdl-cat-circuit |
CircuitArrow, Obj, CircuitTensor, gates, Circuit: Category + MonoidalCategory + Braided + Symmetric |
hdl-cat-sync |
Sync<S, I, O> Mealy machines, compose_sync / par_sync / feedback_sync |
hdl-cat-sim |
Testbench, IR interpreter, TimedSample, VCD emission |
hdl-cat-verilog |
Verilog AST, emit_graph, emit_sync_graph, renderer |
hdl-cat-std |
Standard components: counter, accumulator, down_counter, toggle_ff, shift_register_left, half_adder, full_adder |
hdl-cat-macros |
#[kernel] proc-macro |
hdl-cat |
Umbrella crate re-exporting everything; use hdl_cat::prelude::* |
Conventions
Every crate follows the same Rust discipline (functional, type-driven,
domain-driven). See CLAUDE.md at the workspace root for the enforced
rules: newtypes for domain primitives, hand-rolled error handling,
combinators over pattern matching (on both Option and Result),
static dispatch only, no mut/return/loop/for/unwrap/expect.
Verification gate for every change:
RUSTFLAGS="-D warnings"
License
Dual-licensed under MIT OR Apache-2.0. See LICENSE-MIT and
LICENSE-APACHE.