Skip to content

[debug][firrtl] Carry Chisel source-level types through the pipeline#10410

Open
fkhaidari wants to merge 5 commits into
llvm:mainfrom
fkhaidari:fk-sc/debug-info
Open

[debug][firrtl] Carry Chisel source-level types through the pipeline#10410
fkhaidari wants to merge 5 commits into
llvm:mainfrom
fkhaidari:fk-sc/debug-info

Conversation

@fkhaidari
Copy link
Copy Markdown

@fkhaidari fkhaidari commented May 7, 2026

Hi! This commit carry Chisel source-level type information (type names, enum definitions, constructor parameters) through the FIRRTL -> MLIR pipeline so downstream consumers (Tywaves, HGDB) can recover the original Chisel types

Based on @rameloni's prior Tywaves/CIRCT debug-info work (#6983, #7246, chipsalliance/chisel#4224, chipsalliance/chisel#4015), but uses intrinsics instead of annotations. This PR pairs with the frontend intrinsics from chipsalliance/chisel#5276

The dbg dialect extensions (dbg.enumdef, dbg.subfield, dbg.moduleinfo) are language-agnostic and reusable by any frontend; the FIRRTL side adds handlers in firrtl-lower-intrinsics for the new circt_debug_{var,subfield,enumdef,moduleinfo} intrinsics that feed them

I am also working on a new debug output format (UHDI) -- it is intended to avoid changes to the current HGLDD format and to let existing HGL debug tools (Tywaves, HGDB, ChiselTrace, etc.) work without maintaining outdated forks. Work is still in progress (demo repo: https://github.com/fkhaidari/uhdi, CIRCT UHDI PR: fkhaidari#2).

@fkhaidari
Copy link
Copy Markdown
Author

Split the original commit into 5 focused commits for easier review. @darthscsi, @seldridge, @fabianschuiki, could you take a look?

@circt-bot
Copy link
Copy Markdown

circt-bot Bot commented May 20, 2026

Results of circt-tests run for b62473b compared to results for 2f8e9c7:

sv-tests

Changes in emitted diagnostics:

  • +62 total change
  • -16 error: top-level module 'dut' has unconnected interface port 'in'
  • +16 error: unsupported construct in ClassType members: ConstraintBlock
  • +14 error: unsupported system call `$sdf_annotate`
  • +12 error: unsupported statement: ProceduralAssign
  • -10 error: unsupported module member: SpecifyBlock
  • -9 error: top-level module 'memMod' has unconnected interface port 'a'
  • +9 error: unsupported system call `$printtimescale`
  • +7 error: unsupported port
  • +6 error: could not resolve hierarchical path name 'med_const'
  • +6 error: could not resolve hierarchical path name 'sml_const'
  • -5 error: unsupported primitive `bufif1`
  • +5 error: unsupported module member: InstanceArray
  • -3 error: 'top_timescale2' is a module definition but is used as a value
  • +3 error: could not resolve hierarchical path name 'med_var'
  • +3 error: could not resolve hierarchical path name 'sml_var'
  • +3 error: unsupported system call `$onehot0`
  • -2 error: 'top_resetall' is a module definition but is used as a value
  • -2 error: 'top_timescale' is a module definition but is used as a value
  • -2 error: 'top_timescale3' is a module definition but is used as a value
  • -2 error: identifier 'in' used before its declaration
  • -2 error: unsupported statement: Invalid
  • +2 error: could not resolve hierarchical path name 'lrg_const'
  • +2 error: could not resolve hierarchical path name 'max'
  • +2 error: could not resolve hierarchical path name 'same'
  • +2 error: endianness of selection must match declared range (type is 'logic[21:0]')
  • +2 error: failed to legalize operation 'moore.conversion'
  • +2 error: implicit named port 'clk' of type 'logic' connects to value of inequivalent type 'bit' [-Wimplicit-port-type-mismatch]
  • +2 error: unsupported expression: SequenceWithMatch
  • +2 error: unsupported system call `$dumpfile`
  • -1 error: 'M' is not a valid top-level module
  • -1 error: 'M1' is not a valid top-level module
  • -1 error: could not resolve hierarchical path name 'Test'
  • -1 error: duplicate definition of 'a' [-Wduplicate-definition]
  • -1 error: duplicate definition of 'm' [-Wduplicate-definition]
  • -1 error: top-level module 'M' has unconnected interface port 'i'
  • -1 error: top-level module 'dev1' has unconnected interface port 'b'
  • -1 error: top-level module 'devA' has unconnected interface port 's'
  • -1 error: unsupported construct: Primitive
  • -1 error: unsupported port: does not map to an internal symbol or expression ``
  • -1 error: unsupported system call `$timeformat`
  • +1 error: 'moore.blocking_assign' op using value defined outside the region
  • +1 error: 'moore.output' op has 0 operands, but enclosing module @m1 has 1 outputs
  • +1 error: 'moore.output' op has 0 operands, but enclosing module @m1_0 has 1 outputs
  • +1 error: 'moore.output' op has 0 operands, but enclosing module @m1_1 has 1 outputs
  • +1 error: 'moore.output' op has 0 operands, but enclosing module @m1_2 has 1 outputs
  • +1 error: 'moore.output' op has 0 operands, but enclosing module @m1_3 has 1 outputs
  • +1 error: 'moore.output' op has 0 operands, but enclosing module @m1_4 has 1 outputs
  • +1 error: cannot read from output clocking signal 'req'
  • +1 error: failed to legalize operation 'moore.concat_ref'
  • +1 error: failed to resolve captured variable `CLK` at call site
  • +1 error: failed to resolve captured variable `pass` at call site
  • +1 error: no implicit conversion from 'sub.t_5' to 'sub.t_5'; explicit conversion exists, are you missing a cast?
  • +1 error: redefinition of 'm'
  • +1 error: too many errors emitted, stopping now [--error-limit=]
  • +1 error: top-level module 'mh20' has unconnected 'ref' port 'x'
  • +1 error: top-level module 'mh20' has unconnected 'ref' port 'y'
  • +1 error: top-level module 'mh21' has unconnected 'ref' port 'x'
  • +1 error: top-level module 'mh21' has unconnected 'ref' port 'y'
  • +1 error: unknown hierarchical name `value1`
  • +1 error: unknown name `nq1`
  • +1 error: unknown name `x`
  • +1 error: unsupported expression: DisableIff
  • +1 error: unsupported system call `$bits`
  • +1 error: unsupported system call `$coverage_control`
  • +1 error: unsupported system call `$fopen`
  • +1 error: unsupported system call `$readmemh`

Introduced 1 segfault:

  • generated/ivtest/regress-vlg_porttest2_iv.sv

fkhaidari added 5 commits May 22, 2026 02:45
EnumDefOp describes the integer->name variant map for an enumeration type.
Its result (EnumDefType) is consumed by dbg.variable and dbg.subfield via an
optional operand, letting waveform viewers render raw integer signals by name.
A canonicalizer deduplicates EnumDefOps with identical (enumTypeName, fqn,
variantsMap, scope) within a block using backward-dominance scan. The op is
intentionally not Pure so DCE does not remove enumdefs whose IDs are referenced
by downstream metadata outside the IR.

SubFieldOp tags a single aggregate leaf for debug info, carrying the same
optional typeName / params / enumDef metadata as dbg.variable. Its result
(SubFieldType) must be consumed by dbg.struct or dbg.array; the verifier
tolerates zero-user ops during incremental construction.

ModuleInfoOp attaches source-language type info (typeName, params) to a module
region. The verifier enforces at most one per region.

VariableOp gains optional typeName and params attributes and an optional enumDef
operand to mirror the per-leaf metadata introduced by SubFieldOp. The existing
backward-compatible build helper retains the old (name, value, scope) signature.
…ugInfo

Adds DIEnumDefId (uint32_t), DIEnumValMap, DIEnumDefMap, and DISourceLang to
DebugInfo.h to carry enum variant tables and source-language type annotations
through the analysis. DIModule gains three new fields: enumDefinitions (the
id->variants table), enumDefIds (op->id map for O(1) lookup from a Value), and
sourceLangType (populated from dbg.moduleinfo). DIVariable gains enumDef (the
raw SSA value), enumDefRef (the module-scoped integer id for serialisation),
and sourceLangType (from dbg.variable typeName/params attributes).

The module walk is changed to PreOrder so every dbg.enumdef is assigned an id
before any dbg.variable that references its result. Duplicate enumdefs (same
enumTypeName + fqn + variantsMap) reuse the first-seen id instead of creating
a second entry. Inline dbg.scope ops now set hasInstances = true so the
fallback HW-instance path is not triggered spuriously.
…metadata

Introduces a two-phase lowering for the debug intrinsics.

Phase 1 -- liftDebugIntrinsics() (new pre-pass, single module walk):
  - circt_debug_enumdef intrinsics are converted to dbg.enumdef ops at module
    scope, deduplicated by fqn. A variant-map mismatch on fqn collision emits a
    warning and keeps the first definition.
  - circt_debug_subfield intrinsics are collected into a DebugLeafList of
    DictionaryAttrs (keys: parent, name, typeName?, params?, enumFqn?) and
    erased. The parent key is the enclosing var's name and is the sole
    var<->leaf linkage; name is the full dotted/indexed display path.
  Both intrinsic kinds are erased after collection.

Phase 2 -- IntrinsicLowerings::lower() (existing path extended):
  CirctDebugVarConverter reads DebugLeafList and enumDefByFqn through the new
  IntrinsicConvertContext, emitting dbg.variable ops with typeName, params, and
  enumDef operands populated from the source intrinsics.

IntrinsicConverter gains a convertWithContext() virtual that defaults to the
existing context-free convert(); checkAndConvert() now accepts
IntrinsicConvertContext and routes through convertWithContext(). A new
addConverter<T>(name, args...) overload supports converters with constructor
arguments. IntrinsicConvertContext is stack-allocated per lower() call and
never stored on the shared IntrinsicLowerings object, keeping parallel pass
invocations race-free.
…intrinsics

When circt_debug_var intrinsics are present, MaterializeDebugInfo must not emit
a second dbg.variable for the same signal; LowerIntrinsics will handle those
variables. Build two skip-sets before the main walk: a DenseSet<Value> for
single-operand vars (keyed by SSA value, not name, to avoid false positives
across scopes) and a DenseSet<StringAttr> for zero-operand vars (memory-type).
Any port, wire, reg, or node whose value or name appears in a skip-set is
silently omitted from materialization.

Adds an ordering guard: if dbg.variable ops already exist at pass entry,
LowerIntrinsics ran before MaterializeDebugInfo. Rather than silently creating
duplicates, emit a per-module warning and return early. The required pass order
(MaterializeDebugInfo -> LowerIntrinsics) is now documented in the file header.

Duplicate circt_debug_var intrinsics (same SSA value or name seen more than
once in a module) emit per-op warnings to surface incorrect frontends.
…pper

LowerCHIRRTL remaps operands that read CHIRRTL memory rdata ports to the
equivalent values after memory lowering. Before this fix, dbg.variable and
dbg.subfield ops referencing memory rdata were silently dropped: the base-class
visitInvalidOp is a no-op, so neither the operands were remapped nor the ops
forwarded to the FIRRTL visitor.

Override visitInvalidOp to call visitUnhandledOp for any op belonging to the
Debug dialect; this triggers the standard operand-remapping path. All other
non-FIRRTL, non-CHIRRTL ops retain the original no-op behaviour.
@fkhaidari fkhaidari force-pushed the fk-sc/debug-info branch 2 times, most recently from 4ba5c85 to befe397 Compare May 22, 2026 01:09
@fkhaidari
Copy link
Copy Markdown
Author

FYI: chipsalliance/chisel#5276 was merged to Chisel

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant