124 CIRCT chirrtl Dialect
124.1 Beginner Summary
The CIRCT chirrtl dialect is a small companion dialect to FIRRTL for high-level behavioral memories.
It models memories before the compiler has decided exactly what FIRRTL memory ports they need. A CHIRRTL memory starts as a behavioral object, and separate operations declare and access ports. Later, the firrtl-lower-chirrtl pass infers the concrete port kinds and rewrites the CHIRRTL memory into standard FIRRTL memory operations.
Think of chirrtl as the “memory inference” layer for FIRRTL. It lets frontend or imported FIRRTL-style IR say “there is a memory, and here are its port uses,” then lets the compiler determine whether each port is read-only, write-only, read-write, or debug.
124.2 Why This Dialect Exists
Hardware memories are not just arrays. Their observable behavior depends on ports, clocks, enable signals, read latency, write latency, masks, write modes, read-under-write behavior, and whether a port is used for reads, writes, or both.
In source-level FIRRTL and Chisel-style designs, memory intent can be more behavioral than structural. A designer may describe a memory and use a port, while the compiler is still expected to infer the exact FIRRTL memory port shape.
The chirrtl dialect exists to hold that pre-inference representation.
It fills the gap between:
- high-level memory declarations that are easy for a frontend to emit;
- standard FIRRTL
firrtl.memoperations with fully determined port bundles; - later CIRCT lowering to HW, SV, or generated memory modules.
Without CHIRRTL, a frontend would need to eagerly know whether each memory port is read, write, read-write, or debug. CHIRRTL lets CIRCT derive that from actual uses.
124.3 When It Matters
You will see chirrtl early in CIRCT FIRRTL pipelines, before memory lowering has completed.
A typical path looks like this:
CHIRRTL memory ops
-> firrtl-lower-chirrtl
-> standard FIRRTL memories and ports
-> firrtl-flatten-memory / firrtl-lower-memory / memory cleanup
-> FIRRTLToHW
-> HW/SV
The dialect matters most when debugging memory behavior. If a memory port is inferred incorrectly, or if a port is never enabled, the issue is usually visible around chirrtl.memoryport and chirrtl.memoryport.access before firrtl-lower-chirrtl rewrites them.
124.4 When To Use It
Use chirrtl when representing FIRRTL/Chisel-style behavioral memories whose ports still need inference.
Use it for:
- combinational memories with read latency
0and write latency1; - sequential memories with read and write latency
1; - memory ports whose final kind can be inferred from reads and writes;
- debug ports that observe memory contents;
- FIRRTL import paths that preserve high-level memory intent.
Do not use chirrtl once the memory interface is already structural. At that point, use regular FIRRTL memory operations.
Do not use it as a generic memory abstraction for software-like arrays. It is hardware-memory specific and depends on FIRRTL types, clocks, directions, and memory semantics.
124.5 Core Concepts
124.5.1 Behavioral Memories
The main CHIRRTL type is !chirrtl.cmemory<element-type, element-count>.
It represents a behavioral memory with an element type and depth, but without fixed ports. For example:
!chirrtl.cmemory<uint<32>, 16>
!chirrtl.cmemory<bundle<a : uint<1>>, 16>
The element type is a FIRRTL base type. The element count is the memory depth.
124.5.2 Memory Ports
The second CHIRRTL type is !chirrtl.cmemoryport.
It represents a declared port on a CHIRRTL memory. A port has a data result and a port-token result. The data result is used by ordinary FIRRTL operations. The token result is passed to chirrtl.memoryport.access to define address, clock, and enable behavior.
124.5.3 Port Kind Inference
chirrtl.memoryport has a declared direction attribute, but the lowering pass also inspects uses of the port data.
The port can become:
Read, if the data is only read;Write, if the data is only written;ReadWrite, if the data is both read and written;- deleted, if the port is unused and has no meaningful inferred direction.
This is the main service CHIRRTL provides.
124.5.4 Combinational And Sequential Memories
CHIRRTL distinguishes:
- combinational memory, created by
chirrtl.combmem; - sequential memory, created by
chirrtl.seqmem.
Combinational memories have read latency 0 and write latency 1. Sequential memories have both read latency and write latency 1.
Sequential memories also carry FIRRTL read-under-write behavior, represented by the RUWBehavior attribute.
124.5.5 Enable Inference
The lowering pass wires memory enables when it creates FIRRTL memory ports. Most port kinds are enabled at the port access point.
Sequential read ports are trickier. The pass may infer enable points from the operation that defines the address:
- if the address is a wire or register, enable is associated with valid drives to that address;
- if the address is a node, enable is placed at the node;
- otherwise, the pass may fail to infer an enable and warn that the port is never enabled.
This behavior is one reason CHIRRTL is a temporary dialect. It captures source intent until the compiler can make these port and enable decisions.
124.6 Types
| Type | Meaning |
|---|---|
!chirrtl.cmemory<element, depth> |
Behavioral memory with FIRRTL element type and fixed depth. |
!chirrtl.cmemoryport |
Token-like handle for a declared memory port. |
124.7 Operations
The CHIRRTL dialect currently defines five operations:
| Operation | Purpose |
|---|---|
chirrtl.combmem |
Define a behavioral combinational memory. |
chirrtl.seqmem |
Define a behavioral sequential memory. |
chirrtl.memoryport |
Declare a port on a CHIRRTL memory. |
chirrtl.memoryport.access |
Provide address and clock for a memory port access. |
chirrtl.debugport |
Declare a debug observation port on a CHIRRTL memory. |
124.7.1 chirrtl.combmem
chirrtl.combmem defines a combinational behavioral memory.
It produces a !chirrtl.cmemory value. A combinational memory has read latency 0 and write latency 1. It may carry a name, name-kind, annotations, an inner symbol, an initialization attribute, and a prefix.
Example shape:
%mem = chirrtl.combmem : !chirrtl.cmemory<uint<8>, 256>
Use this operation when reads are intended to be combinational before memory lowering determines concrete ports.
124.7.2 chirrtl.seqmem
chirrtl.seqmem defines a sequential behavioral memory.
It also produces a !chirrtl.cmemory, but it models a memory whose reads and writes both have latency 1. It carries read-under-write behavior, because sequential memory semantics depend on what happens when the same address is read and written in the same cycle.
Example shape:
%mem = chirrtl.seqmem Undefined : !chirrtl.cmemory<uint<8>, 256>
Use it when a memory should be treated as a clocked memory rather than a combinational lookup.
124.7.3 chirrtl.memoryport
chirrtl.memoryport declares a port on a chirrtl.combmem or chirrtl.seqmem.
It takes a !chirrtl.cmemory and returns two values:
- the port data value, whose type is the memory element type;
- a
!chirrtl.cmemoryporthandle used bychirrtl.memoryport.access.
The operation also has a direction such as Read, Write, ReadWrite, or Infer. If the direction is Infer, the lowering pass decides the final kind from uses of the data value.
Example shape:
%data, %port = chirrtl.memoryport Infer %mem {name = "p0"}
: (!chirrtl.cmemory<uint<8>, 256>)
-> (!firrtl.uint<8>, !chirrtl.cmemoryport)
124.7.4 chirrtl.memoryport.access
chirrtl.memoryport.access enables a declared memory port at a particular address and clock.
It takes:
- the
!chirrtl.cmemoryporthandle; - a FIRRTL integer index;
- a FIRRTL clock.
Example shape:
chirrtl.memoryport.access %port[%addr], %clock
: !chirrtl.cmemoryport, !firrtl.uint<8>, !firrtl.clock
The operation does not itself read or write data. Instead, it supplies the access metadata that the lowering pass uses to wire the generated FIRRTL memory port’s addr, en, and clk fields.
124.7.5 chirrtl.debugport
chirrtl.debugport declares a debug memory port.
It takes a CHIRRTL memory and returns a FIRRTL reference type. In lowering, it becomes a debug port on the generated FIRRTL memory. Use it for observation and debugging paths rather than normal read/write hardware behavior.
124.8 Transformations And Conversions
124.8.1 firrtl-lower-chirrtl
This is the key CHIRRTL pass.
It runs on firrtl.module operations. It finds chirrtl.combmem and chirrtl.seqmem operations, collects their chirrtl.memoryport and chirrtl.debugport users, infers port kinds, and creates standard FIRRTL memory operations.
Important rewrite behavior:
- portless memories are deleted;
- unused ports are deleted;
- CHIRRTL memory ports are sorted by name before the FIRRTL memory is created;
- combinational memories become FIRRTL memories with read latency
0and write latency1; - sequential memories become FIRRTL memories with read latency
1and write latency1; - read ports produce FIRRTL
datafields; - write ports produce FIRRTL
dataandmaskfields; - read-write ports produce FIRRTL
rdata,wmode,wdata, andwmaskfields; - debug ports become debug memory ports;
- annotations, inner symbols, prefixes, names, and memory initialization attributes are preserved where applicable.
This pass is a conversion from CHIRRTL to FIRRTL. After it succeeds, the CHIRRTL memory operations should be gone.
124.8.2 Port Use Rewriting
While lowering, the pass tracks how port data values are used.
If a use writes to the port data, it rewrites the destination to the generated FIRRTL write-data field and drives the appropriate mask. If the port is read-write, it also drives write mode.
If a use reads from the port data, it rewrites the source to the generated FIRRTL read-data field.
For aggregate element access, such as firrtl.subfield, firrtl.subindex, or firrtl.subaccess, the pass may clone the access operation so reads go through read-data paths and writes go through write-data/mask paths.
124.9 What It Means In A Pipeline
Seeing chirrtl in IR means the compiler is still preserving behavioral memory intent.
The IR is saying:
- this is a memory with a type and depth;
- these operations declare possible ports;
- these uses imply whether a port is read, write, or read-write;
- the compiler still needs to create the exact FIRRTL memory port bundle.
Once firrtl-lower-chirrtl runs, the IR becomes less behavioral and more structural. The memory has explicit FIRRTL ports, fields, masks, enables, and latencies.
124.10 Common Mistakes
Do not confuse chirrtl.combmem with a final hardware memory. It is a behavioral memory declaration waiting for lowering.
Do not expect chirrtl.memoryport.access to be the actual read or write. The read or write is inferred from how the port data value is used elsewhere.
Do not assume an inferred port will always become read-only. If the same port data is both read and written, lowering creates a read-write port.
Do not leave CHIRRTL around late in the pipeline. Most later FIRRTL and HW passes expect standard FIRRTL memory operations, not CHIRRTL memory inference ops.
124.11 Source Map
| Topic | Source path |
|---|---|
| Dialect definition | circt/include/circt/Dialect/FIRRTL/CHIRRTLDialect.td |
| CHIRRTL types | circt/include/circt/Dialect/FIRRTL/CHIRRTLTypes.td |
| CHIRRTL operations | circt/include/circt/Dialect/FIRRTL/CHIRRTLOps.td |
| Dialect implementation | circt/lib/Dialect/FIRRTL/CHIRRTLDialect.cpp |
| Type implementation | circt/lib/Dialect/FIRRTL/CHIRRTLTypes.cpp |
| Lowering pass declaration | circt/include/circt/Dialect/FIRRTL/Passes.td |
| CHIRRTL lowering implementation | circt/lib/Dialect/FIRRTL/Transforms/LowerCHIRRTL.cpp |
| Related FIRRTL memory lowering | circt/lib/Dialect/FIRRTL/Transforms/LowerMemory.cpp |
| FIRRTL to HW conversion | circt/lib/Conversion/FIRRTLToHW/LowerToHW.cpp |
| Tests | circt/test/Dialect/FIRRTL/lower-chirrtl.mlir and circt/test/Dialect/FIRRTL/lower-chirrtl-errors.mlir |