Skip to content

Typescope include guard drops typedefs when several exported classes are compiled together #138

Description

@teqdruid

The following was produced by Claude.

Summary

Since #121 (Emit hw.typealias for named types in CIRCT IR output), the CIRCT typescope is
emitted inline into every generated .sv file behind an include guard named after the
scope:

`ifndef _TYPESCOPE_CoreModuleTypeScope
`define _TYPESCOPE_CoreModuleTypeScope
  typedef logic _hdk_lqd__LqdControlOp;
  typedef struct packed {logic [63:0] address; logic [15:0] valid_bytes; logic [63:0] data; }
    _hdk_esi_lqd__EsiLqdWriteReq___64__;
  ...
`endif // _TYPESCOPE_CoreModuleTypeScope

The guard macro is identical in every file, but the contents differ per file — each file
declares only the named types it uses. When more than one exported class is compiled into the
same design, the first file processed defines the guard and every later file's typedefs are
silently skipped, while its modules still reference them.

The result is unsimulatable/unsynthesizable output from an otherwise successful compile.

Reproducer

Two exports of a template that instantiate a named type with different parameters is enough.
Concretely, with EsiLqdToHost<8> / EsiLqdToHost<64> / … (each producing
EsiLqdWriteReq<MessageBytes*8>):

$ kanagawa exports.k -O3 --backend=sv --target-device=mock --output=r_
$ verilator --lint-only -sv r_*.sv
%Error: r_LqdToHost_8.sv:12344:30:   Can't find typedef/interface: '_hdk_esi_lqd__EsiLqdWriteReq___64__'
%Error: r_LqdToHost_64.sv:12345:30:  Can't find typedef/interface: '_hdk_esi_lqd__EsiLqdWriteReq___512__'
%Error: r_LqdToHost_512.sv:12349:30: Can't find typedef/interface: '_hdk_esi_lqd__EsiLqdWriteReq___4096__'
%Error: r_LqdToHost_8.sv:12332:30:   Can't find typedef/interface: '_hdk_lqd__LqdControlOp'
... (12 errors total)

r_LqdToHost_8.sv wins the guard, so it is the only file whose _TYPESCOPE_CoreModuleTypeScope
body survives. _hdk_esi_lqd__EsiLqdWriteReq___512__ (needed by r_LqdToHost_64.sv) is never
declared anywhere in the compiled sources.

The dangling reference is on the ESI wrapper port itself:

output wire struct packed {_hdk_esi_lqd__EsiLqdWriteReq___64__ req; } WriteMem,

Regression range

Same sources, same flags, same Verilator (5.048):

KanagawaOSS Can't find typedef errors
1.2.0-main.240 0
1.2.0-main.249 12

On main.240 the _TYPESCOPE_CoreModuleTypeScope block is emitted but empty, so the shared
guard was harmless. #121 populated it without making the guard granular.

Impact

Any design that compiles two or more exported classes using named struct/union/enum types on
ESI-visible ports. Because the named types reach the ports via hw.typealias, this is exactly
the case #121 was intended to improve.

Suggested fix

Guard each declaration on its own name rather than the scope, so the blocks compose:

`ifndef _TYPEDECL_CoreModuleTypeScope___hdk_esi_lqd__EsiLqdWriteReq___64__
`define _TYPEDECL_CoreModuleTypeScope___hdk_esi_lqd__EsiLqdWriteReq___64__
  typedef struct packed {...} _hdk_esi_lqd__EsiLqdWriteReq___64__;
`endif

Every declaration then survives and is still defined exactly once (the mangled names already
encode template arguments, so equal names imply equal definitions). We are carrying this rewrite
as a post-processing pass over the generated .sv locally and it resolves all 12 errors.

Note that a consumer which re-emits the imported typescope (e.g. PyCDE emitting its own modules
from the imported IR) must apply the same scheme, otherwise it both suppresses the Kanagawa
blocks and redeclares their contents.

Related, from the same upgrade

Also worth being aware of for hw.typealias consumers: each generated file declares a typescope
under the same symbol with different members, so tools that import several Kanagawa MLIR
files and de-duplicate typescopes by symbol name must merge the hw.typedecl entries rather
than keep the first typescope, or the aliases from later files end up unresolvable.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions