struct access

This commit is contained in:
Edmund Lam 2026-06-09 13:24:39 -04:00
parent dce1c8d165
commit af121fcb3e
2 changed files with 74 additions and 37 deletions

View File

@ -1302,8 +1302,50 @@ class ParamProcessor final {
V3Const::constifyParamsEdit(varp);
}
if (AstConst* const constp = VN_CAST(varp->valuep(), Const)) {
dotp->replaceWith(constp->cloneTree(false));
VL_DO_DANGLING(dotp->deleteTree(), dotp);
// Walk up any outer `.fieldN[.fieldM...]` chain (struct lparam
// member access like `CFG::cfg.jt.cam_type`) and accumulate the
// packed-struct bit offset. Replace the topmost Dot with a
// Sel(constp, lsb, width) that constify can fold.
int totalLsb = 0;
int sliceWidth = varp->width();
AstNodeDType* curDTypep = varp->dtypep();
AstNode* topp = dotp;
AstDot* outerDot = VN_CAST(dotp->backp(), Dot);
while (outerDot && outerDot->lhsp() == topp) {
const AstParseRef* const fieldRef = VN_CAST(outerDot->rhsp(), ParseRef);
if (!fieldRef) break;
AstNodeDType* const skippedp = curDTypep ? curDTypep->skipRefp() : nullptr;
AstNodeUOrStructDType* const structp
= VN_CAST(skippedp, NodeUOrStructDType);
if (!structp) break;
AstMemberDType* foundMemp = nullptr;
for (AstMemberDType* memp = structp->membersp(); memp;
memp = VN_AS(memp->nextp(), MemberDType)) {
if (memp->name() == fieldRef->name()) {
foundMemp = memp;
break;
}
}
if (!foundMemp) break;
totalLsb += foundMemp->lsb();
sliceWidth = foundMemp->width();
curDTypep = foundMemp->subDTypep();
topp = outerDot;
outerDot = VN_CAST(outerDot->backp(), Dot);
}
if (topp == dotp) {
dotp->replaceWith(constp->cloneTree(false));
VL_DO_DANGLING(dotp->deleteTree(), dotp);
} else {
AstConst* const clonep = static_cast<AstConst*>(constp->cloneTree(false));
AstSel* const selp
= new AstSel{topp->fileline(), clonep, totalLsb, sliceWidth};
// Preserve the field's actual dtype (e.g. enum) on the Sel so
// assignments to enum-typed pins don't trip ENUMVALUE.
if (curDTypep) selp->dtypep(curDTypep);
topp->replaceWith(selp);
VL_DO_DANGLING(topp->deleteTree(), topp);
}
}
}
}
@ -2190,8 +2232,8 @@ public:
});
}
// Walk every subtree reachable from `rootp` for three kinds of deferred work
// V3LinkDot left behind when `class#(...)::member` couldn't yet be resolved:
// Walk every subtree reachable from `rootp` for deferred work V3LinkDot left behind
// when `class#(...)::member` couldn't yet be resolved:
// (1) `AstDot` with a ClassOrPackageRef lhs — a `class::lparam` value Dot to
// constify (e.g. `c8::b` in a pin expression).
// (2) `AstRefDType` with a typedefp — descend into the typedef's subDType

View File

@ -2,13 +2,14 @@
//
// Typedefs and class type parameters that resolve through a
// class-scope-resolved localparam (typedef alias of a parameterized
// class, e.g. inst::b). Exercises four sub-cases:
// class, e.g. inst::b). Exercises five sub-cases:
// - Deferred lparam used as a packed range bound in a typedef
// - Deferred lparam used as a value argument to a parameterized class
// - Class-scope-resolved typedef that itself depends on the param
// - Class-scope Dot used DIRECTLY (no intermediate lparam) in a typedef
// range inside a parameterized module, consumed by `$bits` on a child
// cell pin
// range, consumed by `$bits` on a child cell pin
// - Struct lparam field access via class-scope Dot
// (`CFG::struct_lparam.field`) inside a parameterized module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
@ -19,35 +20,21 @@
`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0)
// verilog_format: on
// Sink for sub-case (4): the BITS pin consumes $bits of a typedef whose
// packed range uses class-scope Dots. V3Param must descend into the typedef's
// subDType and resolve those Dots, otherwise V3Width emits "dotted expressions
// in parameters".
module Sink #(
parameter int BITS = 1
) ();
logic [BITS-1:0] data;
endmodule
module Sub #(parameter int W = 8) (); endmodule
// Parameterized wrapper so the class specialization is deferred until V3Param
// processes the cell instance.
module Sub #(
parameter int W = 8
) ();
// Inner class — same shape as `t`'s C, repeated here to keep sub-case (4)
// self-contained inside Sub.
virtual class CInner #(
parameter int a
);
localparam int b = a;
endclass
// Sub-case (5): struct lparam field access via class-scope Dot. Needs a
// parameterized-module wrapper so the class specialization is deferred until
// V3Param processes the cell instance.
typedef struct packed { logic [7:0] hi; logic [7:0] lo; } pair_t;
typedef CInner#(W) CFG;
class P #(parameter pair_t p);
localparam pair_t pp = p;
endclass
// (4) typedef packed range uses class-scope Dots directly
typedef logic [(CFG::b + CFG::b - 1):0] dot_range_t;
Sink #(.BITS($bits(dot_range_t))) u_sink ();
module PairHolder #(parameter pair_t cfg = '{hi:'d7, lo:'d11}) ();
typedef P#(cfg) PALIAS;
localparam logic [7:0] hi_val = PALIAS::pp.hi;
localparam logic [7:0] lo_val = PALIAS::pp.lo;
endmodule
module t;
@ -80,8 +67,13 @@ module t;
// Wire whose range comes from a deferred lparam
logic [b8-1:0] wide_bus;
// (4) parameterized wrapper with a typedef range using class Dots directly
Sub #(.W(8)) u_sub ();
// (4) Cell pin $bits consumes a typedef whose packed range is built from
// class-scope Dots directly (no intermediate deferred lparam).
typedef logic [(c8::b + c8::b - 1):0] direct_use_t;
Sub #(.W($bits(direct_use_t))) u_sub ();
// (5) struct lparam field access via class-scope Dot
PairHolder #(.cfg('{hi:'d7, lo:'d11})) u_ph ();
initial begin
`checkh(b8, 32'd8);
@ -91,8 +83,11 @@ module t;
`checkh(data_value, 8'hff);
wide_bus = '1;
`checkh(wide_bus, 8'hff);
// sub-case (4): $bits(dot_range_t) = CFG::b + CFG::b = 8 + 8 = 16
`checkh($bits(u_sub.u_sink.data), 32'd16);
// sub-case (4): $bits(direct_use_t) = c8::b + c8::b = 8 + 8 = 16
`checkh($bits(direct_use_t), 32'd16);
// sub-case (5): struct lparam field access folds to the right field
`checkh(u_ph.hi_val, 8'd7);
`checkh(u_ph.lo_val, 8'd11);
$write("*-* All Finished *-*\n");
$finish;
end