From dd1eb2dfe7a6442d831f6746254c0023eafac858 Mon Sep 17 00:00:00 2001 From: UnsignedByte Date: Thu, 9 Jul 2026 15:10:45 -0400 Subject: [PATCH] more testing --- src/V3Param.cpp | 39 ++++++++++++---- .../t/t_class_lparam_struct_field_chain.v | 11 +++++ .../t_class_lparam_struct_field_chain_bad.out | 14 ++++++ .../t_class_lparam_struct_field_chain_bad.py | 16 +++++++ .../t/t_class_lparam_struct_field_chain_bad.v | 46 +++++++++++++++++++ 5 files changed, 117 insertions(+), 9 deletions(-) create mode 100644 test_regress/t/t_class_lparam_struct_field_chain_bad.out create mode 100644 test_regress/t/t_class_lparam_struct_field_chain_bad.py create mode 100644 test_regress/t/t_class_lparam_struct_field_chain_bad.v diff --git a/src/V3Param.cpp b/src/V3Param.cpp index 4819892a2..e3146dbd7 100644 --- a/src/V3Param.cpp +++ b/src/V3Param.cpp @@ -1311,26 +1311,47 @@ class ParamProcessor final { AstNodeDType* curDTypep = varp->dtypep(); AstNode* topp = dotp; AstDot* outerDotp = VN_CAST(dotp->backp(), Dot); - // On any mismatch (non-ParseRef rhs, non-packed-struct dtype, or - // unknown field name) leave the outer Dots intact so V3Width can - // emit the proper user-facing diagnostic. - while (outerDotp && outerDotp->lhsp() == topp) { + const auto errorRecover = [&](AstDot* const badp, const string& msg) { + badp->v3error(msg); + badp->replaceWith(new AstConst{badp->fileline(), AstConst::Signed32{}, 0}); + VL_DO_DANGLING(badp->deleteTree(), badp); + }; + bool errored = false; + while (outerDotp && outerDotp->lhsp() == topp) { // LCOV_EXCL_BR_LINE const AstParseRef* const fieldRefp = VN_CAST(outerDotp->rhsp(), ParseRef); - if (!fieldRefp) break; - AstNodeDType* const skippedp = curDTypep ? curDTypep->skipRefp() : nullptr; + if (!fieldRefp) { + errorRecover(outerDotp, "Malformed dotted select in parameter value"); + errored = true; + break; + } + UASSERT_OBJ(curDTypep, outerDotp, + "curDTypep null in struct field chain walk"); + AstNodeDType* const skippedp = curDTypep->skipRefp(); AstNodeUOrStructDType* const structp = VN_CAST(skippedp, NodeUOrStructDType); - if (!structp) break; + if (!structp) { + errorRecover(outerDotp, + "Dotted member select on non-struct parameter value"); + errored = true; + break; + } AstMemberDType* const foundMemp = VN_CAST( m_memberMap.findMember(structp, fieldRefp->name()), MemberDType); - UASSERT_OBJ(foundMemp, outerDotp, "member not found in struct/union"); - if (!foundMemp->subDTypep()) break; + if (!foundMemp) { + errorRecover(outerDotp, "Member " + fieldRefp->prettyNameQ() + + " not found in structure"); + errored = true; + break; + } + UASSERT_OBJ(foundMemp->subDTypep(), outerDotp, + "member has null subDTypep"); totalLsb += foundMemp->lsb(); sliceWidth = foundMemp->width(); curDTypep = foundMemp->subDTypep(); topp = outerDotp; outerDotp = VN_CAST(outerDotp->backp(), Dot); } + if (errored) return; if (topp == dotp) { dotp->replaceWith(constp->cloneTree(false)); VL_DO_DANGLING(dotp->deleteTree(), dotp); diff --git a/test_regress/t/t_class_lparam_struct_field_chain.v b/test_regress/t/t_class_lparam_struct_field_chain.v index f78cc96f2..d5b924943 100644 --- a/test_regress/t/t_class_lparam_struct_field_chain.v +++ b/test_regress/t/t_class_lparam_struct_field_chain.v @@ -35,6 +35,12 @@ class C #(parameter int W = 1); tag: W[7:0] + 8'd2}; endclass +// Wrapper class containing the use of another class +class D #(parameter int W = 1); + typedef C#(W) CC; + localparam int q = int'(CC::cfg.tag) + 100; +endclass + module Sub #(parameter int WIDTH = 0) (); endmodule @@ -47,6 +53,9 @@ module Mid #(parameter int W = 8) (); // (2) nested struct field access -> two loop iterations, lsb accumulation Sub #(int'(CFG::cfg.jt.cam_type)) u_cam (); Sub #(int'(CFG::cfg.jt.depth)) u_depth (); + // (3) nested struct-field Dot buried in a wrapper class's lparam value + typedef D#(W) DD; + Sub #(int'(DD::q)) u_q (); endmodule module t; @@ -59,6 +68,8 @@ module t; `checkh(u.u_cam.WIDTH, 32'd8); // cfg.jt.depth = W + 1 = 9 (nested, lsb 8) `checkh(u.u_depth.WIDTH, 32'd9); + // q = cfg.tag + 100 = 10 + 100 = 110 + `checkh(u.u_q.WIDTH, 32'd110); $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_class_lparam_struct_field_chain_bad.out b/test_regress/t/t_class_lparam_struct_field_chain_bad.out new file mode 100644 index 000000000..d84a5df97 --- /dev/null +++ b/test_regress/t/t_class_lparam_struct_field_chain_bad.out @@ -0,0 +1,14 @@ +%Error: t/t_class_lparam_struct_field_chain_bad.v:37:25: Malformed dotted select in parameter value + : ... note: In instance 't.u' + 37 | Sub #(int'(CFG::cfg.jt.cam_type[0])) u_fieldref (); + | ^ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: t/t_class_lparam_struct_field_chain_bad.v:39:26: Dotted member select on non-struct parameter value + : ... note: In instance 't.u' + 39 | Sub #(int'(CFG::cfg.tag.bogus)) u_nonstruct (); + | ^ +%Error: t/t_class_lparam_struct_field_chain_bad.v:41:25: Member 'nosuchfield' not found in structure + : ... note: In instance 't.u' + 41 | Sub #(int'(CFG::cfg.jt.nosuchfield)) u_nomember (); + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_class_lparam_struct_field_chain_bad.py b/test_regress/t/t_class_lparam_struct_field_chain_bad.py new file mode 100644 index 000000000..38cf36b43 --- /dev/null +++ b/test_regress/t/t_class_lparam_struct_field_chain_bad.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('linter') + +test.lint(fails=True, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_class_lparam_struct_field_chain_bad.v b/test_regress/t/t_class_lparam_struct_field_chain_bad.v new file mode 100644 index 000000000..00cf7abfe --- /dev/null +++ b/test_regress/t/t_class_lparam_struct_field_chain_bad.v @@ -0,0 +1,46 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// Negative test for the struct-localparam field-access folding in V3Param +// (resolveDotToTypedef). Malformed outer `.field` chains on a class +// localparam value must produce clean user-facing errors, NOT an internal +// error or a crash in the following width pass. Exercises the three +// mismatch guards in the field-chain walk: +// (1) outer Dot rhs is not a plain field identifier (bit-select) +// (2) `.field` applied to a non-struct value +// (3) field name not a member of the struct +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +typedef struct packed { + logic [7:0] cam_type; + logic [7:0] depth; +} inner_t; + +typedef struct packed { + inner_t jt; + logic [7:0] tag; +} cfg_t; + +class C #(parameter int W = 1); + localparam cfg_t cfg = '{jt: '{cam_type: W[7:0], depth: W[7:0] + 8'd1}, + tag: W[7:0] + 8'd2}; +endclass + +module Sub #(parameter int WIDTH = 0) (); +endmodule + +module Mid #(parameter int W = 8) (); + typedef C#(W) CFG; + // (1) bit-select after the field chain: outer Dot rhs is not a ParseRef + Sub #(int'(CFG::cfg.jt.cam_type[0])) u_fieldref (); + // (2) `.bogus` on the scalar field `tag`: dotting into a non-struct + Sub #(int'(CFG::cfg.tag.bogus)) u_nonstruct (); + // (3) `.nosuchfield` not a member of struct `jt` + Sub #(int'(CFG::cfg.jt.nosuchfield)) u_nomember (); +endmodule + +module t; + Mid #(.W(8)) u (); +endmodule