Back to overview

Architecture Overview

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.

CIRCT IR MLIR Verilator Arcilator Rust Backend

Compilation Pipeline

RHDL

Ruby hardware description language

CIRCT IR

Central intermediate representation (MLIR-based)

Verilog / VHDL

Import existing HDL designs

↓ ↓ ↓

Normalized Verilog

Verilator simulation

Arc

Arcilator cycle-accurate simulation

Rust Compilation

Native execution via Rust compiler

CIRCT IR — The Central Hinge

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.

PropertyDetail
FoundationMLIR (Multi-Level IR) / LLVM
DialectsHW, Comb, Seq, SV, FIRRTL, Arc
InputsRHDL lowering, Verilog import, VHDL import
OutputsVerilator, Arcilator, Rust native
PassesCanonicalization, CSE, DCE, lowering
VerificationBuilt-in IR validation at every stage

Frontend: RHDL Lowering

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 DSLRTL modelGate-level netlistCIRCT 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.

lowering pipeline Conceptual
# 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

Frontend: Verilog / VHDL Import

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 FeatureSupport
Verilog modulesFull structural and behavioral
SystemVerilogSubset (interfaces, always_ff/comb)
VHDL entitiesEntity/architecture mapping
Parameterized modulesVia CIRCT's type system
IP integrationBlack-box and wrapper support
Testbench interopMixed RHDL + HDL simulation

Backend: Verilator (Normalized Verilog)

The Verilator backend lowers CIRCT IR to normalized, Verilator-compatible Verilog. CIRCT's ExportVerilog pass generates clean SystemVerilog with specific workarounds for Verilator compatibility:

  • Local variables are avoided (disallowLocalVariables option)
  • Location info is formatted for Verilator's parser
  • Wire declarations are hoisted to module scope
  • Expressions are simplified to avoid Verilator limitations

The resulting Verilog is then compiled by Verilator into a C++ simulation model, providing fast cycle-accurate simulation with VCD waveform output.

verilator output Generated
// 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

Backend: Arcilator (Arc Simulation)

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.

  • Direct CIRCT IR to LLVM lowering via the Arc dialect
  • Cycle-accurate simulation without Verilog intermediary
  • Supports lockstep comparison with Verilator for verification
  • Works with all frontends that fully lower to CIRCT core dialects
PropertyDetail
IR dialectArc (register-transfer arcs)
CompilationCIRCT IR → Arc → LLVM IR → native
Simulation modelCycle-accurate, event-driven
PerformanceComparable to Verilator
Waveform outputVCD trace support
VerificationLockstep mode with Verilator

Backend: Rust Compiler (Native Execution)

The Rust compiler backend takes CIRCT IR and generates Rust code that can be compiled natively by rustc. This provides several unique advantages:

  • Native Rust integration — simulated hardware can be called directly from Rust test harnesses
  • WebAssembly target — compile to WASM for in-browser simulation and testing
  • Memory safety guarantees from the Rust compiler
  • Access to Rust's ecosystem for I/O, networking, and visualization

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.

rust output Generated
// 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
    }
}

Pipeline Summary

StageInputOutputPurpose
RHDL LoweringRuby DSL sourceCIRCT IR (HW/Comb/Seq)Frontend compilation from Ruby
HDL ImportVerilog / VHDL filesCIRCT IR (HW/Comb/Seq)Ingestion of existing designs
IR OptimizationCIRCT IROptimized CIRCT IRCanonicalization, CSE, DCE
Verilog ExportCIRCT IRNormalized VerilogVerilator-compatible output
Arc LoweringCIRCT IRArc dialect → LLVM IRArcilator simulation
Rust CodegenCIRCT IRRust source → native/WASMRust compiler execution