diff --git a/Changes b/Changes index c649104cb..9374ec69c 100644 --- a/Changes +++ b/Changes @@ -41,6 +41,7 @@ Verilator 5.043 devel * Optimize $past delayed variable reuse (#6689). [Geza Lore, Fractile Ltd.] * Optimize combinational loops through sign extension (#6724). [Geza Lore] * Optimize trace initialization code size (#6749). [Geza Lore] +* Fix extern function that returns parametrized class (#4924). * Fix expression short circuiting (#6483). [Todd Strader] * Fix expression coverage of system calls (#6592). [Todd Strader] * Fix `--timing` with `--x-initial-edge` (#6603) (#6631). [Krzysztof Bieganski, Antmicro Ltd.] diff --git a/include/verilatedos.h b/include/verilatedos.h index 3d762e167..b8c500824 100644 --- a/include/verilatedos.h +++ b/include/verilatedos.h @@ -261,6 +261,16 @@ VL_DANGLING(var); \ } while (false) +/// As with VL_DO_DANGLING, but two variables dangle. +#define VL_DO_DANGLING2(stmt, var, var2) \ + do { \ + do { \ + stmt; \ + } while (false); \ + VL_DANGLING(var); \ + VL_DANGLING(var2); \ + } while (false) + /// Perform an e.g. delete, then set variable to nullptr as a requirement #define VL_DO_CLEAR(stmt, stmt2) \ do { \ diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index 9542e7c2f..9f5be7aee 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -966,18 +966,17 @@ class LinkDotFindVisitor final : public VNVisitor { void moveExternFuncDecl(AstNodeFTask* nodep, AstClass* classp) { // Move an 'extern function|task' to inside a class or, from an outer to inner-class - if (!nodep->isExternDef()) { - // Move it to proper spot under the target class - nodep->unlinkFrBack(); - classp->addStmtsp(nodep); - nodep->isExternDef(true); // So we check there's a matching extern - nodep->classOrPackagep()->unlinkFrBack()->deleteTree(); - // Any "Type::" reference in the function's IO are really "MovedToClass::" references - if (nodep->fvarp()) moveExternFuncDeclRefs(nodep->fvarp(), classp); - for (AstNode* stmtp = nodep->stmtsp(); stmtp; stmtp = stmtp->nextp()) { - if (AstVar* const portp = VN_CAST(stmtp, Var)) { - if (portp->isIO()) moveExternFuncDeclRefs(portp, classp); - } + if (nodep->isExternDef()) return; + // Move it to proper spot under the target class + nodep->unlinkFrBack(); + classp->addStmtsp(nodep); + nodep->isExternDef(true); // So we check there's a matching extern + nodep->classOrPackagep()->unlinkFrBack()->deleteTree(); + // Any "Type::" reference in the function's IO are really "MovedToClass::" references + if (nodep->fvarp()) moveExternFuncDeclRefs(nodep->fvarp(), classp); + for (AstNode* stmtp = nodep->stmtsp(); stmtp; stmtp = stmtp->nextp()) { + if (AstVar* const portp = VN_CAST(stmtp, Var)) { + if (portp->isIO()) moveExternFuncDeclRefs(portp, classp); } } } @@ -985,7 +984,14 @@ class LinkDotFindVisitor final : public VNVisitor { nodep->foreach([this, classp](AstClassOrPackageRef* refp) { // if (refp->name() == classp->name() && !refp->paramsp()) { UINFO(9, "Cleaning up external function type for class " << refp); - pushDeletep(refp->unlinkFrBack()); + AstDot* const dotp = VN_CAST(refp->backp(), Dot); + if (dotp && dotp->lhsp() == refp) { + // If had DOT(CLASSREF this, ...) the DOT can go away + dotp->replaceWith(dotp->rhsp()->unlinkFrBack()); + VL_DO_DANGLING2(pushDeletep(dotp), dotp, refp); + } else { + VL_DO_DANGLING(pushDeletep(refp->unlinkFrBack()), refp); + } } }); } diff --git a/test_regress/t/t_class_extern2.py b/test_regress/t/t_class_extern2.py new file mode 100755 index 000000000..d4f986441 --- /dev/null +++ b/test_regress/t/t_class_extern2.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2024 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_class_extern2.v b/test_regress/t/t_class_extern2.v new file mode 100644 index 000000000..10f46a997 --- /dev/null +++ b/test_regress/t/t_class_extern2.v @@ -0,0 +1,54 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2025 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +class One #( + type VALUE_T = int unsigned +); + typedef One#(VALUE_T) self_t; + VALUE_T value; + extern static function self_t create(VALUE_T value); +endclass + +class Two #( + type VALUE_T = int unsigned +); + VALUE_T value; + extern static function Two#(VALUE_T) create(VALUE_T value); +`ifdef NEVER + // works ok if put function directly here + static function Two#(VALUE_T) create(VALUE_T value); + Two #(VALUE_T) obj = new(); + obj.value = value; + return obj; + endfunction +`endif +endclass + +function One::self_t One::create(VALUE_T value); + One #(VALUE_T) obj = new(); + obj.value = value; + return obj; +endfunction + +function Two#(Two::VALUE_T) Two::create(VALUE_T value); + Two #(VALUE_T) obj = new(); + obj.value = value; + return obj; +endfunction + +module t; + initial begin + One #(string) one; + Two #(string) two; + one = One#(string)::create("one"); + two = Two#(string)::create("two"); + if (one.value !== "one") $stop; + if (two.value !== "two") $stop; + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule