diff --git a/src/V3Number.h b/src/V3Number.h index 21a917bea..c5d1a57bf 100644 --- a/src/V3Number.h +++ b/src/V3Number.h @@ -665,6 +665,7 @@ public: void isSigned(bool ssigned) { m_data.m_signed = ssigned; } bool isDouble() const VL_MT_SAFE { return dataType() == V3NumberDataType::DOUBLE; } bool isString() const VL_MT_SAFE { return dataType() == V3NumberDataType::STRING; } + bool isOpaque() const VL_MT_SAFE { return isDouble() || isString(); } bool isNumber() const VL_MT_SAFE { return m_data.type() == V3NumberDataType::LOGIC || m_data.type() == V3NumberDataType::DOUBLE; diff --git a/src/V3Param.cpp b/src/V3Param.cpp index 8d25b0c6f..13e31e2e3 100644 --- a/src/V3Param.cpp +++ b/src/V3Param.cpp @@ -1261,6 +1261,31 @@ class ParamProcessor final { return result; } + static bool paramConstsEqualAtMaxWidth(AstConst* exprp, AstConst* origp) { + // Return true if two integer constants are equal after sign-extending + // both to max(width). A typed parameter default (e.g. byte) is + // narrower than a 32-bit pin expression, so sameTree/areSame fail. + if (exprp->num().width() == origp->num().width()) return false; + if (exprp->num().isOpaque()) return false; + if (origp->num().isOpaque()) return false; + const int maxWidth = std::max(exprp->num().width(), origp->num().width()); + V3Number exprNum{exprp, maxWidth}; + if (exprp->num().isSigned()) { + exprNum.opExtendS(exprp->num(), exprp->num().width()); + } else { + exprNum.opAssign(exprp->num()); + } + V3Number origNum{origp, maxWidth}; + if (origp->num().isSigned()) { + origNum.opExtendS(origp->num(), origp->num().width()); + } else { + origNum.opAssign(origp->num()); + } + V3Number isEq{exprp, 1}; + isEq.opEq(exprNum, origNum); + return isEq.isNeqZero(); + } + void cellPinCleanup(AstNode* nodep, AstPin* pinp, AstNodeModule* srcModp, string& longnamer, bool& any_overridesr) { if (!pinp->exprp()) return; // No-connect @@ -1284,6 +1309,13 @@ class ParamProcessor final { } else { UINFO(9, "cellPinCleanup: before constify " << pinp << " " << modvarp); V3Const::constifyParamsEdit(pinp->exprp()); + // Cast/CastSize default values are not yet folded by V3Width. + // Constify here so the comparison below sees a Const node. + // Other node kinds are handled in the branches above. + if (modvarp->valuep() + && (VN_IS(modvarp->valuep(), Cast) || VN_IS(modvarp->valuep(), CastSize))) { + V3Const::constifyParamsEdit(modvarp->valuep()); + } UINFO(9, "cellPinCleanup: after constify " << pinp); // String constants are parsed as logic arrays and converted to strings in V3Const. // At this moment, some constants may have been already converted. @@ -1309,7 +1341,8 @@ class ParamProcessor final { } else if (origp && (exprp->sameTree(origp) || (exprp->num().width() == origp->num().width() - && ParameterizedHierBlocks::areSame(exprp, origp)))) { + && ParameterizedHierBlocks::areSame(exprp, origp)) + || paramConstsEqualAtMaxWidth(exprp, origp))) { // Setting parameter to its default value. Just ignore it. // This prevents making additional modules, and makes coverage more // obvious as it won't show up under a unique module page name. diff --git a/test_regress/t/t_param_cast_default.py b/test_regress/t/t_param_cast_default.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_param_cast_default.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_param_cast_default.v b/test_regress/t/t_param_cast_default.v new file mode 100644 index 000000000..526d743fd --- /dev/null +++ b/test_regress/t/t_param_cast_default.v @@ -0,0 +1,106 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +// Original #6281 reproducer: parameter passed via localparam variable +// vs. literal constant should resolve to the same specialization. +// Fixed by ParameterizedHierBlocks::areSame fallback (landed earlier). +class ClsIntDefault #(parameter int P = 32); + function int get_p; + return P; + endfunction +endclass + +// Parameter with byte cast default value +class ClsByteCast #(parameter byte P = byte'(8)); + function byte get_p; + return P; + endfunction +endclass + +// Parameter with int cast default value +class ClsIntCast #(parameter int P = int'(42)); + function int get_p; + return P; + endfunction +endclass + +// Parameter with signed cast default value +class ClsSignedCast #(parameter int P = int'(-5)); + function int get_p; + return P; + endfunction +endclass + +// Module with cast default (cell array test) +module sub #(parameter byte P = byte'(8)); + initial begin + `checkd(P, 8); + end +endmodule + +module t; + // Original #6281 case: localparam variable vs. literal constant + localparam int WIDTH = 32; + ClsIntDefault #(32) orig_a; + ClsIntDefault #(WIDTH) orig_b; + + // Byte cast default: #() and #(8) should be same type + ClsByteCast #() byte_a; + ClsByteCast #(8) byte_b; + + // Int cast default: #() and #(42) should be same type + ClsIntCast #() int_a; + ClsIntCast #(42) int_b; + + // Signed cast default: #() and #(-5) should be same type + ClsSignedCast #() signed_a; + ClsSignedCast #(-5) signed_b; + + // Multiple instances (template mutation safety) + ClsByteCast #() multi_a; + ClsByteCast #(8) multi_b; + ClsByteCast #() multi_c; + ClsByteCast #(8) multi_d; + + // Module with cast default + sub #() sub_default (); + sub #(8) sub_explicit (); + + initial begin + orig_a = new; + orig_b = orig_a; + `checkd(orig_b.get_p(), 32); + + byte_a = new; + byte_b = byte_a; + `checkd(byte_a.get_p(), 8); + `checkd(byte_b.get_p(), 8); + + int_a = new; + int_b = int_a; + `checkd(int_a.get_p(), 42); + `checkd(int_b.get_p(), 42); + + signed_a = new; + signed_b = signed_a; + `checkd(signed_a.get_p(), -5); + `checkd(signed_b.get_p(), -5); + + multi_a = new; + multi_b = multi_a; + multi_c = multi_a; + multi_d = multi_a; + `checkd(multi_d.get_p(), 8); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_param_width_loc_bad.out b/test_regress/t/t_param_width_loc_bad.out index 36795729c..ed7b2e12c 100644 --- a/test_regress/t/t_param_width_loc_bad.out +++ b/test_regress/t/t_param_width_loc_bad.out @@ -1,4 +1,4 @@ -%Warning-WIDTHTRUNC: t/t_param_width_loc_bad.v:19:21: Operator VAR 'PARAM' expects 1 bits on the Initial value, but Initial value's CONST '32'h0' generates 32 bits. +%Warning-WIDTHTRUNC: t/t_param_width_loc_bad.v:19:21: Operator VAR 'PARAM' expects 1 bits on the Initial value, but Initial value's CONST '32'h1' generates 32 bits. : ... note: In instance 't.test_i' 19 | parameter logic PARAM = 1'b0 | ^~~~~ diff --git a/test_regress/t/t_param_width_loc_bad.v b/test_regress/t/t_param_width_loc_bad.v index 9ea9a5b99..c1bfa49a0 100644 --- a/test_regress/t/t_param_width_loc_bad.v +++ b/test_regress/t/t_param_width_loc_bad.v @@ -7,7 +7,7 @@ module t; // bug1624 - test #(.PARAM(32'd0)) test_i (); + test #(.PARAM(32'd1)) test_i (); initial begin $write("*-* All Finished *-*\n");