123  CIRCT firrtl Dialect

The CIRCT firrtl dialect models FIRRTL, the intermediate representation used by Chisel-based hardware flows before Verilog emission. FIRRTL stands for Flexible Intermediate Representation for RTL. In practical terms, this dialect is where CIRCT represents Chisel-style modules, wires, registers, memories, aggregate types, annotations, reset inference, width inference, last-connect semantics, layers, probes, and many compatibility details from the original Scala FIRRTL Compiler.

For a beginner, the useful mental model is this: firrtl is the hardware-design language level between a generator such as Chisel and lower CIRCT hardware dialects such as hw, comb, seq, sv, verif, ltl, and sim. It is high enough to preserve FIRRTL source semantics and annotations, but structured enough for MLIR passes to infer missing information, resolve connections, legalize memories, and lower the design toward SystemVerilog.

The local checkout defines 173 firrtl operations and 64 named FIRRTL-related transformation or conversion passes.

123.1 When FIRRTL Is Important

firrtl is important when a flow starts from Chisel, .fir input, or FIRRTL-shaped compiler IR. You will see it when using firtool, when debugging Chisel-to-Verilog lowering, or when studying how CIRCT replaces the old Scala FIRRTL Compiler for common FIRRTL use cases.

Use this dialect when you need to answer questions like:

  • What did Chisel generate before final Verilog emission?
  • Which values still have uninferred widths or reset kinds?
  • Has FIRRTL last-connect semantics been expanded yet?
  • Are aggregate bundles and vectors still present, or have they been split into ground types?
  • Are annotations, probes, layers, options, or classes still influencing lowering?
  • Are memories still FIRRTL memories, CHIRRTL memories, or already lowered modules?
  • Has the design crossed into the lower CIRCT hardware dialects?

You usually do not write full FIRRTL dialect IR by hand. You inspect it to understand hardware generated by a frontend, and you transform it through compiler pipelines.

123.2 Why It Is Needed

FIRRTL carries semantics that are not obvious in plain RTL. A FIRRTL design can contain unresolved widths, abstract reset types, aggregate bundle directions, last-connect rules, annotations from Chisel or tools, memory declarations, source names, and target strings used by downstream tooling. Lowering straight to hw.module and comb operations would erase or prematurely commit many of those details.

The firrtl dialect gives CIRCT a place to preserve those details while passes make them explicit. Width inference fills in integer sizes. Reset inference chooses synchronous or asynchronous reset behavior. Type lowering splits aggregate ports, wires, and registers. when expansion resolves conditional last-connect behavior. Memory lowering chooses module forms. Annotation lowering translates structured metadata into operations, symbols, attributes, or emitted metadata.

The implication is that FIRRTL IR is both a source-language preservation layer and a staging area. Seeing firrtl means the design may still rely on FIRRTL rules rather than fully explicit RTL structure.

123.3 Types And Attributes

The FIRRTL type system is richer than plain MLIR integer types. Hardware ground types include unsigned integers, signed integers, clocks, resets, asynchronous resets, and analog values. In assembly, common mnemonics include firrtl.uint, firrtl.sint, firrtl.clock, firrtl.reset, firrtl.asyncreset, and firrtl.analog.

Aggregate hardware types include vectors, open vectors, bundles, open bundles, and enums, with mnemonics such as firrtl.vector, firrtl.openvector, firrtl.bundle, firrtl.openbundle, and firrtl.enum. These are central to Chisel-style hardware because many module ports and wires are structured rather than flat.

FIRRTL also has reference and probe-related types such as firrtl.ref, firrtl.lhs, and firrtl.rwprobe. These support reference ports, writable probes, cross-module references, force/release behavior, and related verification or instrumentation features.

The dialect includes property and object-oriented types too: firrtl.class, firrtl.anyref, firrtl.string, firrtl.integer, firrtl.list, firrtl.path, firrtl.bool, firrtl.double, firrtl.fstring, and firrtl.domain. These are used by classes, object values, formal properties, strings, lists, paths, and domain analysis.

CHIRRTL, the higher-level memory subset inside the same dialect area, adds behavioral memory types such as !chirrtl.cmemory<element-type, count> and !chirrtl.cmemoryport.

Important attributes include annotation arrays, port annotations, domain information, Grand Central augmented bundle/vector/ground attributes, parameter declarations, memory initialization metadata, layer arrays, domain fields, and several enum attributes. Those enums cover name preservation, calling conventions, layer conventions, memory read-under-write behavior, memory port direction, event controls, and target kinds.

For a beginner, the key lesson is that FIRRTL types and attributes often encode information that will disappear later. If you are debugging lost names, split ports, reset behavior, or annotation behavior, inspect FIRRTL before the relevant lowering pass erases that structure.

123.4 Operation Inventory

This checkout defines 173 firrtl operations. The list is large because FIRRTL covers whole-circuit structure, declarations, statements, expressions, memory forms, verification, object/class support, probes, layers, domains, and intrinsics.

123.4.1 Structure And Top-Level Design

These operations describe circuits, modules, classes, layers, options, and major compilation regions:

firrtl.circuit
firrtl.module
firrtl.extmodule
firrtl.intmodule
firrtl.memmodule
firrtl.class
firrtl.extclass
firrtl.layer
firrtl.layerblock
firrtl.option
firrtl.option_case
firrtl.formal
firrtl.simulation
firrtl.domain

Read firrtl.circuit as the top-level FIRRTL design. firrtl.module is a normal FIRRTL module, while firrtl.extmodule represents an external Verilog or blackbox module. firrtl.intmodule and firrtl.memmodule are intermediate generated module forms. Class and object features use firrtl.class, firrtl.extclass, and object operations. Layers and options support conditional or specialized design structure.

123.4.2 Declarations, Instances, State, And Memories

These operations introduce named hardware, instances, state, memories, and related handles:

firrtl.instance
firrtl.instance_choice
firrtl.mem
firrtl.node
firrtl.reg
firrtl.regreset
firrtl.wire
firrtl.contract
firrtl.object
firrtl.domain.create
firrtl.combmem
firrtl.seqmem
firrtl.memoryport
firrtl.memoryport.access
firrtl.debugport

firrtl.instance instantiates another module-like operation. firrtl.instance_choice supports option-based specialization. firrtl.wire, firrtl.node, firrtl.reg, and firrtl.regreset are common declarations for combinational names and sequential state. firrtl.mem is a lower-level FIRRTL memory, while firrtl.combmem and firrtl.seqmem are CHIRRTL-style memories whose ports are inferred and later made explicit.

123.4.3 Statements, Connections, And Control

These operations describe imperative FIRRTL behavior, conditions, connections, simulation effects, and verification statements:

firrtl.attach
firrtl.bind
firrtl.connect
firrtl.matchingconnect
firrtl.propassign
firrtl.ref.define
firrtl.skip
firrtl.stop
firrtl.printf
firrtl.fprintf
firrtl.fflush
firrtl.assert
firrtl.assume
firrtl.cover
firrtl.property_assert
firrtl.require
firrtl.ensure
firrtl.force
firrtl.ref.force
firrtl.ref.force_initial
firrtl.ref.release
firrtl.ref.release_initial
firrtl.when
firrtl.match

firrtl.connect is the ordinary connection operation. firrtl.matchingconnect handles connection by matching aggregate structure. firrtl.bind attaches companion behavior into a target module. firrtl.when and firrtl.match model conditional FIRRTL behavior before lower passes resolve it. firrtl.assert, firrtl.assume, firrtl.cover, and firrtl.property_assert preserve verification intent. Print, stop, force, release, require, and ensure operations carry simulation and verification side effects.

123.4.4 Constants, Aggregates, Selects, And Casts

These operations construct values, select fields or elements, manipulate aggregate structure, and convert between related FIRRTL types:

firrtl.constant
firrtl.specialconstant
firrtl.aggregateconstant
firrtl.invalidvalue
firrtl.bundlecreate
firrtl.vectorcreate
firrtl.enumcreate
firrtl.subfield
firrtl.opensubfield
firrtl.subindex
firrtl.opensubindex
firrtl.subaccess
firrtl.istag
firrtl.subtag
firrtl.tagextract
firrtl.multibit_mux
firrtl.bitcast
firrtl.resetCast
firrtl.constCast
firrtl.hwStructCast
firrtl.verbatim.expr
firrtl.verbatim.wire

These operations are easiest to read from left to right in a use-def chain. Constants and aggregate constructors create structured values. Subfield, subindex, and subaccess operations pick pieces out of aggregates. Cast operations represent FIRRTL-level reinterpretation or compatibility steps that should be gone or simplified later.

123.4.5 Numeric And Logical Expressions

These operations are the familiar arithmetic, comparison, bit, reduction, shift, cast, and mux primitives:

firrtl.add
firrtl.sub
firrtl.mul
firrtl.div
firrtl.rem
firrtl.lt
firrtl.leq
firrtl.gt
firrtl.geq
firrtl.eq
firrtl.neq
firrtl.pad
firrtl.asUInt
firrtl.asSInt
firrtl.asClock
firrtl.asAsyncReset
firrtl.asReset
firrtl.shl
firrtl.shr
firrtl.dshl
firrtl.dshlw
firrtl.dshr
firrtl.cvt
firrtl.neg
firrtl.not
firrtl.and
firrtl.or
firrtl.xor
firrtl.elementwise_and
firrtl.elementwise_or
firrtl.elementwise_xor
firrtl.andr
firrtl.orr
firrtl.xorr
firrtl.cat
firrtl.bits
firrtl.head
firrtl.tail
firrtl.mux

These look like ordinary expression operations, but FIRRTL width and signedness rules still matter. For example, an add may have widths inferred by firrtl-infer-widths, and conversions such as firrtl.asUInt or firrtl.asSInt preserve FIRRTL cast semantics before lowering. The elementwise operations apply logical operations across aggregate-shaped values.

123.4.6 Object, Property, Path, Domain, And Reference Operations

FIRRTL also contains operations for classes, objects, properties, references, paths, domains, strings, booleans, integers, lists, and cross-module references:

firrtl.object.subfield
firrtl.object.anyref_cast
firrtl.string
firrtl.integer
firrtl.double
firrtl.bool
firrtl.list.create
firrtl.list.concat
firrtl.string.concat
firrtl.integer.add
firrtl.integer.mul
firrtl.integer.shr
firrtl.integer.shl
firrtl.bool.and
firrtl.bool.or
firrtl.bool.xor
firrtl.prop.eq
firrtl.unknown
firrtl.unresolved_path
firrtl.path
firrtl.ref.cast
firrtl.ref.resolve
firrtl.ref.send
firrtl.ref.sub
firrtl.ref.rwprobe
firrtl.xmr.ref
firrtl.xmr.deref
firrtl.domain.define
firrtl.domain.anon
firrtl.domain.subfield
firrtl.unsafe_domain_cast

Seeing many of these operations usually means the IR is still preserving high-level FIRRTL features beyond simple RTL. Path and XMR operations often relate to annotations, probes, hierarchy references, or debug/instrumentation flows. Domain operations relate to clock-domain modeling and checking.

123.4.7 Temporal, String, And Intrinsic Operations

These operations support temporal properties, formatted strings, clocks, reset or X checks, DPI, plusargs, FPGA probes, and generic intrinsics:

firrtl.intersect
firrtl.delay
firrtl.concat
firrtl.repeat
firrtl.goto_repeat
firrtl.non_consecutive_repeat
firrtl.implication
firrtl.until
firrtl.eventually
firrtl.past
firrtl.clock
firrtl.fstring.time
firrtl.fstring.hierarchicalmodulename
firrtl.int.generic
firrtl.int.plusargs.test
firrtl.int.plusargs.value
firrtl.int.fpga_probe
firrtl.int.clock_gate
firrtl.int.clock_inv
firrtl.int.clock_div
firrtl.int.unclocked_assume
firrtl.int.isX
firrtl.int.has_been_reset
firrtl.int.dpi.call
firrtl.int.sizeof
firrtl.int.mux2cell
firrtl.int.mux4cell
firrtl.view

These operations show that FIRRTL is not only about datapath RTL. It also carries simulation hooks, formatted output, temporal verification, target-specific intrinsics, and compiler-recognized implementation requests.

123.4.8 Complete Alphabetical Op List

For reference, the exact operation names found in this checkout are:

firrtl.add, firrtl.aggregateconstant, firrtl.and, firrtl.andr, firrtl.asAsyncReset, firrtl.asClock, firrtl.asReset, firrtl.asSInt, firrtl.asUInt, firrtl.assert, firrtl.assume, firrtl.attach, firrtl.bind, firrtl.bitcast, firrtl.bits, firrtl.bool, firrtl.bool.and, firrtl.bool.or, firrtl.bool.xor, firrtl.bundlecreate, firrtl.cat, firrtl.circuit, firrtl.class, firrtl.clock, firrtl.combmem, firrtl.concat, firrtl.connect, firrtl.constCast, firrtl.constant, firrtl.contract, firrtl.cover, firrtl.cvt, firrtl.debugport, firrtl.delay, firrtl.div, firrtl.domain, firrtl.domain.anon, firrtl.domain.create, firrtl.domain.define, firrtl.domain.subfield, firrtl.double, firrtl.dshl, firrtl.dshlw, firrtl.dshr, firrtl.elementwise_and, firrtl.elementwise_or, firrtl.elementwise_xor, firrtl.ensure, firrtl.enumcreate, firrtl.eq, firrtl.eventually, firrtl.extclass, firrtl.extmodule, firrtl.fflush, firrtl.force, firrtl.formal, firrtl.fprintf, firrtl.fstring.hierarchicalmodulename, firrtl.fstring.time, firrtl.geq, firrtl.goto_repeat, firrtl.gt, firrtl.head, firrtl.hwStructCast, firrtl.implication, firrtl.instance, firrtl.instance_choice, firrtl.int.clock_div, firrtl.int.clock_gate, firrtl.int.clock_inv, firrtl.int.dpi.call, firrtl.int.fpga_probe, firrtl.int.generic, firrtl.int.has_been_reset, firrtl.int.isX, firrtl.int.mux2cell, firrtl.int.mux4cell, firrtl.int.plusargs.test, firrtl.int.plusargs.value, firrtl.int.sizeof, firrtl.int.unclocked_assume, firrtl.integer, firrtl.integer.add, firrtl.integer.mul, firrtl.integer.shl, firrtl.integer.shr, firrtl.intersect, firrtl.intmodule, firrtl.invalidvalue, firrtl.istag, firrtl.layer, firrtl.layerblock, firrtl.leq, firrtl.list.concat, firrtl.list.create, firrtl.lt, firrtl.match, firrtl.matchingconnect, firrtl.mem, firrtl.memmodule, firrtl.memoryport, firrtl.memoryport.access, firrtl.module, firrtl.mul, firrtl.multibit_mux, firrtl.mux, firrtl.neg, firrtl.neq, firrtl.node, firrtl.non_consecutive_repeat, firrtl.not, firrtl.object, firrtl.object.anyref_cast, firrtl.object.subfield, firrtl.opensubfield, firrtl.opensubindex, firrtl.option, firrtl.option_case, firrtl.or, firrtl.orr, firrtl.pad, firrtl.past, firrtl.path, firrtl.printf, firrtl.prop.eq, firrtl.propassign, firrtl.property_assert, firrtl.ref.cast, firrtl.ref.define, firrtl.ref.force, firrtl.ref.force_initial, firrtl.ref.release, firrtl.ref.release_initial, firrtl.ref.resolve, firrtl.ref.rwprobe, firrtl.ref.send, firrtl.ref.sub, firrtl.reg, firrtl.regreset, firrtl.rem, firrtl.repeat, firrtl.require, firrtl.resetCast, firrtl.seqmem, firrtl.shl, firrtl.shr, firrtl.simulation, firrtl.skip, firrtl.specialconstant, firrtl.stop, firrtl.string, firrtl.string.concat, firrtl.sub, firrtl.subaccess, firrtl.subfield, firrtl.subindex, firrtl.subtag, firrtl.tagextract, firrtl.tail, firrtl.unknown, firrtl.unresolved_path, firrtl.unsafe_domain_cast, firrtl.until, firrtl.vectorcreate, firrtl.verbatim.expr, firrtl.verbatim.wire, firrtl.view, firrtl.when, firrtl.wire, firrtl.xmr.deref, firrtl.xmr.ref, firrtl.xor, firrtl.xorr

123.5 Transformations And Conversions

The FIRRTL pipeline is large because it must translate a high-level, annotation-heavy design into explicit lower hardware IR. The local checkout has 64 named FIRRTL-related passes.

123.5.1 Input, Annotation, Debug, And Metadata Passes

firrtl-resolve-paths lowers unresolved path strings to path operations with annotation target identifiers. firrtl-lower-annotations normalizes supported semi-structured JSON annotations and paths. firrtl-blackbox-reader reads inline or path-based blackbox Verilog into emitted verbatim files. firrtl-grand-central processes Grand Central annotations, companion modules, views, interfaces, and XMRs. firrtl-resolve-traces updates trace annotations. firrtl-emit-metadata emits metadata such as sequence memory replacement metadata. firrtl-assign-output-dirs assigns output directories using the instance graph. firrtl-materialize-debug-info creates debug operations for FIRRTL ports, nodes, wires, registers, and instances. firrtl-print-field-source, firrtl-print-instance-graph, and firrtl-print-nla-table are analysis and debugging print passes.

123.5.2 Inference And Checking Passes

firrtl-infer-widths infers all FIRRTL widths and errors if a width cannot be inferred. firrtl-infer-resets resolves abstract reset behavior. firrtl-infer-rw merges compatible read and write memory ports into read-write ports. firrtl-infer-domains performs domain inference and checking for clock-domain modeling. firrtl-check-comb-loops detects combinational cycles. firrtl-check-recursive-instantiation rejects recursive module or class instantiation. firrtl-check-layers checks illegal layer nesting. firrtl-lint reports static simulation failures, static false assertions, and problematic XMRs. firrtl-module-summary prints a module summary.

123.5.3 FIRRTL Normalization Passes

firrtl-lower-chirrtl turns CHIRRTL memories into explicit FIRRTL memory operations and infers port directions. firrtl-lower-matches lowers firrtl.match to firrtl.when. firrtl-expand-whens resolves conditional last-connect semantics and removes when structure. firrtl-lower-types splits aggregate FIRRTL types into ground types where required. firrtl-lower-signatures lowers aggregate module signatures. firrtl-lower-open-aggs separates non-hardware open aggregate elements. firrtl-drop-const removes const modifiers and const casts. firrtl-passive-wires removes flips from wire aggregate types. firrtl-merge-connections merges compatible field-level connects. firrtl-vb-to-bv converts vector-of-bundles form to bundle-of-vectors form. firrtl-vectorization rewrites selected primitive operations into vector operations. firrtl-sfc-compat applies compatibility fixes for Scala FIRRTL Compiler behavior. firrtl-finalize-ir performs cleanup after export-oriented lowering.

123.5.4 Optimization Passes

firrtl-imconstprop performs intermodule optimistic constant propagation. firrtl-imdeadcodeelim removes intermodule dead code. firrtl-dedup structurally deduplicates modules and classes. firrtl-eliminate-wires removes wires when writes dominate reads and no preservation rule prevents the rewrite. firrtl-inliner inlines annotated or selected modules. firrtl-drop-names changes preserved names to droppable names according to the chosen preservation mode. firrtl-register-optimizer applies classic FIRRTL register optimizations. firrtl-inner-symbol-dce removes unused inner symbols.

123.5.5 Memory, DUT, And Extraction Passes

firrtl-lower-memory lowers FIRRTL memories to generated modules. firrtl-flatten-memory converts memory data types to flat unsigned integers with bitcasts. firrtl-mem-to-reg-of-vec implements combinational memories with registers. firrtl-add-seqmem-ports adds ports to memory modules from annotations. firrtl-inject-dut-hier adds hierarchy outside a DUT for extracted modules. firrtl-extract-instances moves annotated instances upward in the hierarchy. firrtl-link-circuits combines circuits and links external modules to implementations when available. firrtl-annotate-input-only-modules marks input-only modules for inlining to avoid outputless blackbox problems.

123.5.6 Classes, Layers, Probes, Options, Intrinsics, And Domains

firrtl-lower-classes lowers FIRRTL class and object features to OM classes and objects. firrtl-lower-layers lowers layers and layer blocks to instances. firrtl-layer-merge merges layer blocks that reference the same layer. firrtl-layer-sink sinks operations into layers when possible. firrtl-specialize-layers permanently enables or disables layer specializations. firrtl-specialize-option specializes option groups and instance choices. firrtl-populate-instance-choice-symbols populates globally unique symbols for instance choices before SystemVerilog lowering. firrtl-lower-domains lowers domain information to properties and erases domain-specific structure. firrtl-probes-to-signals replaces probes with explicitly routed signals. firrtl-lower-xmr lowers reference ports to encoded XMR verbatim forms. firrtl-lower-intmodules replaces intmodule instances with generic intrinsic operations. firrtl-lower-intrinsics lowers generic intrinsics to implementation operations. firrtl-lower-dpi lowers DPI intrinsics to simulation DPI operations. firrtl-randomize-register-init creates randomized initialization bit vectors for registers.

123.5.7 Conversion Out Of FIRRTL

lower-firrtl-to-hw is the major conversion pass from FIRRTL to the lower CIRCT hardware family. It targets dialects such as hw, comb, seq, sv, verif, ltl, sim, and emit, depending on the operations present and conversion options. This is the point where FIRRTL-specific structure is expected to become lower-level hardware, verification, simulation, and emission IR.

123.5.8 Complete Pass List

The exact pass names covered in this chapter are:

firrtl-add-seqmem-ports, firrtl-annotate-input-only-modules, firrtl-assign-output-dirs, firrtl-blackbox-reader, firrtl-check-comb-loops, firrtl-check-layers, firrtl-check-recursive-instantiation, firrtl-dedup, firrtl-drop-const, firrtl-drop-names, firrtl-eliminate-wires, firrtl-emit-metadata, firrtl-expand-whens, firrtl-extract-instances, firrtl-finalize-ir, firrtl-flatten-memory, firrtl-grand-central, firrtl-imconstprop, firrtl-imdeadcodeelim, firrtl-infer-domains, firrtl-infer-resets, firrtl-infer-rw, firrtl-infer-widths, firrtl-inject-dut-hier, firrtl-inliner, firrtl-inner-symbol-dce, firrtl-layer-merge, firrtl-layer-sink, firrtl-link-circuits, firrtl-lint, firrtl-lower-annotations, firrtl-lower-chirrtl, firrtl-lower-classes, firrtl-lower-domains, firrtl-lower-dpi, firrtl-lower-intmodules, firrtl-lower-intrinsics, firrtl-lower-layers, firrtl-lower-matches, firrtl-lower-memory, firrtl-lower-open-aggs, firrtl-lower-signatures, firrtl-lower-types, firrtl-lower-xmr, firrtl-materialize-debug-info, firrtl-mem-to-reg-of-vec, firrtl-merge-connections, firrtl-module-summary, firrtl-passive-wires, firrtl-populate-instance-choice-symbols, firrtl-print-field-source, firrtl-print-instance-graph, firrtl-print-nla-table, firrtl-probes-to-signals, firrtl-randomize-register-init, firrtl-register-optimizer, firrtl-resolve-paths, firrtl-resolve-traces, firrtl-sfc-compat, firrtl-specialize-layers, firrtl-specialize-option, firrtl-vb-to-bv, firrtl-vectorization, lower-firrtl-to-hw

123.6 What It Implies

Seeing firrtl.circuit, firrtl.module, or firrtl.extmodule means the design is still in FIRRTL module form. Module signatures, annotations, and aggregate types may still carry source-level meaning.

Seeing firrtl.wire, firrtl.node, firrtl.reg, firrtl.regreset, firrtl.mem, firrtl.combmem, or firrtl.seqmem means declarations are still represented with FIRRTL rules. Memory ports may still need inference or lowering.

Seeing firrtl.when means last-connect behavior has not been fully resolved. After firrtl-expand-whens, the design should be closer to explicit muxes, connects, and lowered control.

Seeing aggregate constructors or selects such as firrtl.bundlecreate, firrtl.vectorcreate, firrtl.subfield, and firrtl.subindex means aggregate structure is still visible. After firrtl-lower-types, much of that structure may become split ground signals.

Seeing path, probe, XMR, layer, option, object, or class operations means the flow is still preserving advanced FIRRTL features. These often need specialized lowering before ordinary RTL emission.

Seeing lower-firrtl-to-hw in a pipeline means the compiler is leaving FIRRTL and entering lower CIRCT hardware IR. After that point, debugging FIRRTL-only issues such as width inference, annotations, and last-connect semantics is much harder.

123.7 How To Read FIRRTL IR

Start at the top-level firrtl.circuit, then inspect modules and ports. Check whether port and value types are ground or aggregate. If widths are missing or reset types are abstract, look for whether firrtl-infer-widths and firrtl-infer-resets have run.

Next, look for firrtl.when, firrtl.match, firrtl.connect, and firrtl.matchingconnect. These tell you whether the design still depends on FIRRTL connection semantics. If a signal value is surprising, remember that FIRRTL uses last-connect semantics before expansion.

Then inspect declarations. firrtl.regreset makes reset behavior explicit, while firrtl.reg may depend on separate reset or initialization logic. firrtl.combmem and firrtl.seqmem are still high-level memory forms. firrtl.mem is lower but still not final hardware.

Finally, search for annotations, paths, probes, layers, options, classes, and intrinsics. Those features often explain why a pipeline has many FIRRTL-specific passes before it can lower cleanly to hw, comb, seq, or sv.

123.8 Minimal Example

This simplified fragment shows the kind of structure FIRRTL preserves:

firrtl.circuit "Top" {
  firrtl.module @Top(in %clock: !firrtl.clock, in %reset: !firrtl.uint<1>,
                     in %a: !firrtl.uint<8>, out %y: !firrtl.uint<8>) {
    %zero = firrtl.constant 0 : !firrtl.uint<8>
    %r = firrtl.regreset %clock, %reset, %zero : !firrtl.clock, !firrtl.uint<1>, !firrtl.uint<8>
    %sum = firrtl.add %r, %a : (!firrtl.uint<8>, !firrtl.uint<8>) -> !firrtl.uint<9>
    %trunc = firrtl.bits %sum, 7, 0 : !firrtl.uint<9>
    firrtl.connect %y, %trunc : !firrtl.uint<8>
  }
}

The important reading is not just “this is a register plus an add.” The FIRRTL dialect also preserves the module boundary, FIRRTL clock and reset types, width behavior on the add, bit selection, and connection semantics. Later passes can infer, normalize, optimize, and lower this into ordinary hardware dialect operations.