From af121fcb3e000fe743f930956675ed78db011236 Mon Sep 17 00:00:00 2001 From: Edmund Lam Date: Tue, 9 Jun 2026 13:24:39 -0400 Subject: [PATCH] struct access --- src/V3Param.cpp | 50 +++++++++++++-- test_regress/t/t_class_lparam_typedef_chain.v | 61 +++++++++---------- 2 files changed, 74 insertions(+), 37 deletions(-) diff --git a/src/V3Param.cpp b/src/V3Param.cpp index f91dacfca..e536de15e 100644 --- a/src/V3Param.cpp +++ b/src/V3Param.cpp @@ -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(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 diff --git a/test_regress/t/t_class_lparam_typedef_chain.v b/test_regress/t/t_class_lparam_typedef_chain.v index aa4039514..090a33eb2 100644 --- a/test_regress/t/t_class_lparam_typedef_chain.v +++ b/test_regress/t/t_class_lparam_typedef_chain.v @@ -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