diff --git a/src/V3AstNodeStmt.h b/src/V3AstNodeStmt.h index 9d4d21c37..a4ddea7e4 100644 --- a/src/V3AstNodeStmt.h +++ b/src/V3AstNodeStmt.h @@ -1332,6 +1332,20 @@ public: return new AstAssign{fileline(), lhsp, rhsp, controlp}; } }; +class AstAssignCont final : public AstNodeAssign { + // Continuous procedural 'assign'. See AstAssignW for non-procedural version. +public: + AstAssignCont(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp, + AstNode* timingControlp = nullptr) + : ASTGEN_SUPER_AssignCont(fl, lhsp, rhsp, timingControlp) { + dtypeFrom(lhsp); + } + ASTGEN_MEMBERS_AstAssignCont; + AstNodeAssign* cloneType(AstNodeExpr* lhsp, AstNodeExpr* rhsp) override { + AstNode* const controlp = timingControlp() ? timingControlp()->cloneTree(false) : nullptr; + return new AstAssignCont{fileline(), lhsp, rhsp, controlp}; + } +}; class AstAssignDly final : public AstNodeAssign { public: AstAssignDly(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp, diff --git a/src/V3WidthCommit.cpp b/src/V3WidthCommit.cpp index 11f765cd8..1e48ac87d 100644 --- a/src/V3WidthCommit.cpp +++ b/src/V3WidthCommit.cpp @@ -48,8 +48,8 @@ class WidthCommitVisitor final : public VNVisitor { AstNodeFTask* m_ftaskp = nullptr; // Current function/task AstNodeModule* m_modp = nullptr; // Current module std::string m_contNba; // In continuous- or non-blocking assignment - bool m_dynsizedelem - = false; // Writing a dynamically-sized array element, not the array itself + bool m_contReads = false; // Check read continuous automatic variables + bool m_dynsizedelem = false; // Writing dynamically-sized array element, not the array itself VMemberMap m_memberMap; // Member names cached for fast lookup bool m_taskRefWarn = true; // Allow task reference warnings bool m_underSel = false; // Under AstMemberSel or AstSel @@ -390,7 +390,7 @@ private: iterateChildren(nodep); editDType(nodep); classEncapCheck(nodep, nodep->varp(), VN_CAST(nodep->classOrPackagep(), Class)); - if (nodep->access().isWriteOrRW()) varLifetimeCheck(nodep, nodep->varp()); + if (nodep->access().isWriteOrRW() || m_contReads) varLifetimeCheck(nodep, nodep->varp()); if (VN_IS(nodep, VarRef)) nodep->name(""); // Clear to save memory; nodep->name() will work via nodep->varp() } @@ -416,12 +416,31 @@ private: } } } + void visit(AstAssignCont* nodep) override { + iterateAndNextNull(nodep->timingControlp()); + { + VL_RESTORER(m_contNba); + VL_RESTORER(m_contReads); + m_contNba = "continuous"; + m_contReads = true; + iterateAndNextNull(nodep->lhsp()); + iterateAndNextNull(nodep->rhsp()); + } + editDType(nodep); + AstNode* const controlp + = nodep->timingControlp() ? nodep->timingControlp()->unlinkFrBack() : nullptr; + nodep->replaceWith(new AstAssign{nodep->fileline(), nodep->lhsp()->unlinkFrBack(), + nodep->rhsp()->unlinkFrBack(), controlp}); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + } void visit(AstAssignDly* nodep) override { iterateAndNextNull(nodep->timingControlp()); iterateAndNextNull(nodep->rhsp()); { VL_RESTORER(m_contNba); + VL_RESTORER(m_contReads); m_contNba = "nonblocking"; + m_contReads = false; iterateAndNextNull(nodep->lhsp()); } editDType(nodep); @@ -431,7 +450,9 @@ private: iterateAndNextNull(nodep->rhsp()); { VL_RESTORER(m_contNba); + VL_RESTORER(m_contReads); m_contNba = "continuous"; + m_contReads = false; iterateAndNextNull(nodep->lhsp()); } editDType(nodep); @@ -470,7 +491,7 @@ private: iterateChildren(nodep); } editDType(nodep); - if (auto* const classrefp = VN_CAST(nodep->fromp()->dtypep(), ClassRefDType)) { + if (AstClassRefDType* const classrefp = VN_CAST(nodep->fromp()->dtypep(), ClassRefDType)) { classEncapCheck(nodep, nodep->varp(), classrefp->classp()); } // else might be struct, etc varLifetimeCheck(nodep, nodep->varp()); diff --git a/src/verilog.y b/src/verilog.y index 2c343f406..b3708f0c1 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -3527,7 +3527,7 @@ statement_item: // IEEE: statement_item { $$ = new AstAssignDly{$2, $1, $4, $3}; } //UNSUP cycle_delay fexprLvalue yP_LTE ';' { UNSUP } | yASSIGN idClassSel '=' delay_or_event_controlE expr ';' - { $$ = new AstAssign{$1, $2, $5, $4}; } + { $$ = new AstAssignCont{$1, $2, $5, $4}; } | yDEASSIGN variable_lvalue ';' { $$ = nullptr; BBUNSUP($1, "Unsupported: Verilog 1995 deassign"); DEL($2); } | yFORCE variable_lvalue '=' expr ';' diff --git a/test_regress/t/t_assign_cont_automatic_bad.out b/test_regress/t/t_assign_cont_automatic_bad.out new file mode 100644 index 000000000..0d32e5fca --- /dev/null +++ b/test_regress/t/t_assign_cont_automatic_bad.out @@ -0,0 +1,6 @@ +%Error: t/t_assign_cont_automatic_bad.v:14:26: Automatic lifetime variable not allowed in continuous assignment (IEEE 1800-2023 6.21): 'l' + : ... note: In instance 't' + 14 | assign g = signed'(l); + | ^ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: Exiting due to diff --git a/test_regress/t/t_assign_cont_automatic_bad.py b/test_regress/t/t_assign_cont_automatic_bad.py new file mode 100755 index 000000000..55203b6c9 --- /dev/null +++ b/test_regress/t/t_assign_cont_automatic_bad.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2025 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('linter') + +test.lint(fails=True, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_assign_cont_automatic_bad.v b/test_regress/t/t_assign_cont_automatic_bad.v new file mode 100644 index 000000000..88911ad28 --- /dev/null +++ b/test_regress/t/t_assign_cont_automatic_bad.v @@ -0,0 +1,20 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2026 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t; + + reg g; + + task automatic tsk; + reg l; + begin: cont_block + assign g = signed'(l); // <--- BAD: using automatic in cont assignment + end + endtask + + initial $stop; + +endmodule