CIRCT IR is the central intermediate representation that connects all frontends to all backends. RHDL lowers to it, existing Verilog and VHDL import to it, and multiple execution targets compile from it.
Ruby hardware description language
Central intermediate representation (MLIR-based)
Import existing HDL designs
Verilator simulation
Arcilator cycle-accurate simulation
Native execution via Rust compiler
CIRCT IR is the pivotal intermediate representation in the compilation pipeline. Built on MLIR (Multi-Level Intermediate Representation), it provides a modular, composable framework for representing hardware at multiple levels of abstraction.
All design sources — whether written in RHDL's Ruby DSL or imported from existing Verilog/VHDL — converge into this single canonical form. From there, the IR can be lowered through progressive transformation passes to any supported backend.
This hub-and-spoke model means adding a new frontend or backend requires only implementing a single translation, rather than N×M pairwise conversions.
| Property | Detail |
|---|---|
| Foundation | MLIR (Multi-Level IR) / LLVM |
| Dialects | HW, Comb, Seq, SV, FIRRTL, Arc |
| Inputs | RHDL lowering, Verilog import, VHDL import |
| Outputs | Verilator, Arcilator, Rust native |
| Passes | Canonicalization, CSE, DCE, lowering |
| Verification | Built-in IR validation at every stage |
RHDL provides a high-level Ruby DSL for hardware design. When a design is compiled, the Ruby AST is analyzed and progressively lowered through several stages:
Ruby DSL → RTL model → Gate-level netlist → CIRCT IR
The lowering process preserves the designer's intent while transforming high-level constructs (state machines, memory abstractions, behavioral blocks) into concrete hardware operations that map directly to CIRCT's HW and Comb dialects.
Ruby's metaprogramming makes it possible to express complex parameterized designs concisely, while the lowering pipeline ensures the generated IR is efficient and correct.
# 1. Ruby DSL (source)
class Counter < SequentialComponent
output :count, width: 8
sequential { count <= count + 1 }
end
# 2. RTL model (internal)
# register(count, 8) + adder(count, 1)
# 3. CIRCT IR (output)
# hw.module @Counter
# %c1 = hw.constant 1 : i8
# %add = comb.add %count, %c1
# %reg = seq.compreg %add, %clk
Existing Verilog and VHDL designs can be imported directly into CIRCT IR using CIRCT's parsing infrastructure. This allows legacy or third-party IP blocks to participate in the same compilation pipeline as RHDL designs.
The importer handles SystemVerilog constructs including modules, interfaces, always blocks, and generate statements, mapping them to corresponding CIRCT dialect operations.
This import capability means teams can incrementally adopt RHDL — wrapping existing Verilog IP in Ruby testbenches, or mixing hand-written HDL modules with RHDL-generated components in the same design hierarchy.
| Import Feature | Support |
|---|---|
| Verilog modules | Full structural and behavioral |
| SystemVerilog | Subset (interfaces, always_ff/comb) |
| VHDL entities | Entity/architecture mapping |
| Parameterized modules | Via CIRCT's type system |
| IP integration | Black-box and wrapper support |
| Testbench interop | Mixed RHDL + HDL simulation |
The Verilator backend lowers CIRCT IR to normalized, Verilator-compatible Verilog. CIRCT's ExportVerilog pass generates clean SystemVerilog with specific workarounds for Verilator compatibility:
The resulting Verilog is then compiled by Verilator into a C++ simulation model, providing fast cycle-accurate simulation with VCD waveform output.
// Normalized Verilog for Verilator
module Counter (
input clk,
input rst,
output [7:0] count
);
reg [7:0] _count_reg;
wire [7:0] _count_next;
assign _count_next = _count_reg + 8'h1;
assign count = _count_reg;
always @(posedge clk) begin
if (rst)
_count_reg <= 8'h0;
else
_count_reg <= _count_next;
end
endmodule
Arcilator is CIRCT's native cycle-accurate hardware simulator. Instead of generating Verilog as an intermediate step, it transforms CIRCT IR into register-to-register transfer arcs and compiles them directly to machine code via LLVM.
This approach eliminates the overhead of Verilog generation and parsing, producing simulation performance comparable to or better than Verilator for many designs.
| Property | Detail |
|---|---|
| IR dialect | Arc (register-transfer arcs) |
| Compilation | CIRCT IR → Arc → LLVM IR → native |
| Simulation model | Cycle-accurate, event-driven |
| Performance | Comparable to Verilator |
| Waveform output | VCD trace support |
| Verification | Lockstep mode with Verilator |
The Rust compiler backend takes CIRCT IR and generates Rust code that can be compiled natively by rustc. This provides several unique advantages:
This backend powers RHDL's fast simulation engine and enables the browser-based demos in the showcase. Designs compiled this way run 1–2 orders of magnitude faster than interpreted Ruby simulation.
// Generated Rust simulation model
pub struct Counter {
count: u8,
}
impl Counter {
pub fn tick(
&mut self,
rst: bool,
) -> u8 {
if rst {
self.count = 0;
} else {
self.count =
self.count.wrapping_add(1);
}
self.count
}
}
| Stage | Input | Output | Purpose |
|---|---|---|---|
| RHDL Lowering | Ruby DSL source | CIRCT IR (HW/Comb/Seq) | Frontend compilation from Ruby |
| HDL Import | Verilog / VHDL files | CIRCT IR (HW/Comb/Seq) | Ingestion of existing designs |
| IR Optimization | CIRCT IR | Optimized CIRCT IR | Canonicalization, CSE, DCE |
| Verilog Export | CIRCT IR | Normalized Verilog | Verilator-compatible output |
| Arc Lowering | CIRCT IR | Arc dialect → LLVM IR | Arcilator simulation |
| Rust Codegen | CIRCT IR | Rust source → native/WASM | Rust compiler execution |