diff --git a/src/V3AssertPre.cpp b/src/V3AssertPre.cpp index 19492cbe8..567581eef 100644 --- a/src/V3AssertPre.cpp +++ b/src/V3AssertPre.cpp @@ -57,6 +57,7 @@ private: bool m_inAssign = false; // True if in an AssignNode bool m_inAssignDlyLhs = false; // True if in AssignDly's LHS bool m_inSynchDrive = false; // True if in synchronous drive + std::vector m_xrefsp; // list of xrefs that need name fixup // METHODS @@ -156,6 +157,8 @@ private: m_clockingp = nodep; UINFO(8, " CLOCKING" << nodep << endl); iterateChildren(nodep); + if (nodep->eventp()) nodep->addNextHere(nodep->eventp()->unlinkFrBack()); + VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); } void visit(AstClockingItem* const nodep) override { // Get a ref to the sampled/driven variable @@ -175,7 +178,8 @@ private: AstConst* const skewp = VN_AS(nodep->skewp(), Const); if (skewp->num().isNegative()) skewp->v3error("Skew cannot be negative"); AstNodeExpr* const exprp = nodep->exprp(); - m_clockingp->addVarsp(varp->unlinkFrBack()); + varp->name(m_clockingp->name() + "__DOT__" + varp->name()); + m_clockingp->addNextHere(varp->unlinkFrBack()); varp->user1p(nodep); if (nodep->direction() == VDirection::OUTPUT) { AstVarRef* const skewedRefp = new AstVarRef{flp, varp, VAccess::READ}; @@ -226,9 +230,8 @@ private: AstSampleQueueDType* const queueDtp = new AstSampleQueueDType{flp, exprp->dtypep()}; m_netlistp->typeTablep()->addTypesp(queueDtp); - AstVar* const queueVarp = new AstVar{ - flp, VVarType::MODULETEMP, - "__Vqueue__" + m_clockingp->name() + "__DOT__" + varp->name(), queueDtp}; + AstVar* const queueVarp + = new AstVar{flp, VVarType::MODULETEMP, "__Vqueue__" + varp->name(), queueDtp}; queueVarp->lifetime(VLifetime::STATIC); m_clockingp->addNextHere(queueVarp); // Create a process like this: @@ -241,7 +244,7 @@ private: m_clockingp->addNextHere( new AstAlways{flp, VAlwaysKwd::ALWAYS, nullptr, pushp->makeStmt()}); // Create a process like this: - // always @ queue.pop(, /*out*/}); + // always @ queue.pop(, /*out*/); AstCMethodHard* const popp = new AstCMethodHard{ flp, new AstVarRef{flp, queueVarp, VAccess::READWRITE}, "pop", new AstTime{nodep->fileline(), m_modp->timeunit()}}; @@ -265,6 +268,7 @@ private: nodep->v3error("Only cycle delays can be used in synchronous drives" " (IEEE 1800-2023 14.16)"); } + iterateChildren(nodep); return; } if (m_inAssign && !m_inSynchDrive) { @@ -317,9 +321,26 @@ private: } } void visit(AstNodeVarRef* nodep) override { - if (AstClockingItem* const itemp = VN_CAST(nodep->varp()->user1p(), ClockingItem)) { - if (nodep->access().isReadOrRW() && !nodep->user1() - && itemp->direction() == VDirection::OUTPUT) { + UINFO(8, " -varref: " << nodep << endl); + UINFO(8, " -varref-var-back: " << nodep->varp()->backp() << endl); + UINFO(8, " -varref-var-user1: " << nodep->varp()->user1p() << endl); + if (AstClockingItem* const itemp = VN_CAST( + nodep->varp()->user1p() ? nodep->varp()->user1p() : nodep->varp()->firstAbovep(), + ClockingItem)) { + if (nodep->user1()) return; + + // ensure linking still works, this has to be done only once + if (AstVarXRef* xrefp = VN_CAST(nodep, VarXRef)) { + UINFO(8, " -clockvarxref-in: " << xrefp << endl); + string dotted = xrefp->dotted(); + const size_t dotPos = dotted.rfind('.'); + dotted.erase(dotPos, string::npos); + xrefp->dotted(dotted); + UINFO(8, " -clockvarxref-out: " << xrefp << endl); + m_xrefsp.emplace_back(xrefp); + } + + if (nodep->access().isReadOrRW() && itemp->direction() == VDirection::OUTPUT) { nodep->v3error("Cannot read from output clockvar (IEEE 1800-2023 14.3)"); } if (nodep->access().isWriteOrRW()) { @@ -329,10 +350,11 @@ private: "to clockvars (IEEE 1800-2023 14.16)"); } if (m_inAssign) m_inSynchDrive = true; - } else if (!nodep->user1() && itemp->direction() == VDirection::INPUT) { + } else if (itemp->direction() == VDirection::INPUT) { nodep->v3error("Cannot write to input clockvar (IEEE 1800-2023 14.3)"); } } + nodep->user1(true); } } void visit(AstNodeAssign* nodep) override { @@ -496,6 +518,8 @@ public: clearAssertInfo(); // Process iterate(nodep); + // Fix up varref names + for (AstVarXRef* xrefp : m_xrefsp) xrefp->name(xrefp->varp()->name()); } ~AssertPreVisitor() override = default; }; diff --git a/src/V3AstNodeExpr.h b/src/V3AstNodeExpr.h index 576065cd9..6ca000b7f 100644 --- a/src/V3AstNodeExpr.h +++ b/src/V3AstNodeExpr.h @@ -5645,6 +5645,7 @@ public: inline AstVarXRef(FileLine* fl, AstVar* varp, const string& dotted, const VAccess& access); ASTGEN_MEMBERS_AstVarXRef; string name() const override VL_MT_STABLE { return m_name; } // * = Var name + void name(const std::string& name) override { m_name = name; } // * = Var name void dump(std::ostream& str) const override; void dumpJson(std::ostream& str) const override; string dotted() const { return m_dotted; } diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index b9e141a6b..7142c789d 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -926,8 +926,7 @@ class AstClocking final : public AstNode { // Children: SENITEM, CLOCKING ITEMs, VARs // @astgen op1 := sensesp : AstSenItem // @astgen op2 := itemsp : List[AstClockingItem] - // @astgen op3 := varsp : List[AstVar] - // @astgen op4 := eventp : Optional[AstVar] + // @astgen op3 := eventp : Optional[AstVar] std::string m_name; // Clocking block name const bool m_isDefault = false; // True if default clocking const bool m_isGlobal = false; // True if global clocking diff --git a/src/V3Clock.cpp b/src/V3Clock.cpp index 4175a36c0..6b2c5004f 100644 --- a/src/V3Clock.cpp +++ b/src/V3Clock.cpp @@ -95,7 +95,6 @@ class ClockVisitor final : public VNVisitor { = string{"__Vsampled__"} + vscp->scopep()->nameDotless() + "__" + varp->name(); FileLine* const flp = vscp->fileline(); AstVar* const newvarp = new AstVar{flp, VVarType::MODULETEMP, newvarname, varp->dtypep()}; - newvarp->noReset(true); // Reset by below assign m_scopep->modp()->addStmtsp(newvarp); AstVarScope* const newvscp = new AstVarScope{flp, m_scopep, newvarp}; vscp->user1p(newvscp); diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index d0edca27d..b3d2f1302 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -2875,6 +2875,7 @@ class LinkDotResolveVisitor final : public VNVisitor { } } else if (VN_IS(foundp->nodep(), Clocking)) { m_ds.m_dotSymp = foundp; + if (m_ds.m_dotText != "") m_ds.m_dotText += "." + nodep->name(); ok = m_ds.m_dotPos == DP_SCOPE; } else if (const AstNodeFTask* const ftaskp = VN_CAST(foundp->nodep(), NodeFTask)) { diff --git a/src/V3Scope.cpp b/src/V3Scope.cpp index fcaaa2eb2..81621ca3e 100644 --- a/src/V3Scope.cpp +++ b/src/V3Scope.cpp @@ -51,7 +51,6 @@ class ScopeVisitor final : public VNVisitor { // STATE, for passing down one level of hierarchy (may need save/restore) AstCell* m_aboveCellp = nullptr; // Cell that instantiates this module AstScope* m_aboveScopep = nullptr; // Scope that instantiates this scope - AstClocking* m_clockingp = nullptr; // Current clocking block std::unordered_map m_packageScopes; // Scopes for each package VarScopeMap m_varScopes; // Varscopes created for each scope and var @@ -243,15 +242,6 @@ class ScopeVisitor final : public VNVisitor { // We iterate under the *clone* iterateChildren(clonep); } - void visit(AstClocking* nodep) override { - VL_RESTORER(m_clockingp); - m_clockingp = nodep; - UINFO(4, " CLOCKING " << nodep << endl); - iterateChildren(nodep); - if (nodep->varsp()) m_scopep->modp()->addStmtsp(nodep->varsp()->unlinkFrBackWithNext()); - if (nodep->eventp()) m_scopep->modp()->addStmtsp(nodep->eventp()->unlinkFrBack()); - VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); - } void visit(AstNodeFTask* nodep) override { // Add to list of blocks under this scope UINFO(4, " FTASK " << nodep << endl); @@ -270,9 +260,7 @@ class ScopeVisitor final : public VNVisitor { } void visit(AstVar* nodep) override { // Make new scope variable - if (m_clockingp) { - nodep->name(VString::dot(m_clockingp->name(), "__DOT__", nodep->name())); - } else if (!nodep->user1p()) { + if (!nodep->user1p()) { AstScope* scopep = m_scopep; if (AstIfaceRefDType* const ifacerefp = VN_CAST(nodep->dtypep(), IfaceRefDType)) { // Attach every non-virtual interface variable its inner scope diff --git a/test_regress/t/t_clocking_xref.pl b/test_regress/t/t_clocking_xref.pl new file mode 100755 index 000000000..9d615c1c3 --- /dev/null +++ b/test_regress/t/t_clocking_xref.pl @@ -0,0 +1,22 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 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 + +scenarios(simulator => 1); + +compile( + verilator_flags2 => ["--exe --main --timing"], + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_clocking_xref.v b/test_regress/t/t_clocking_xref.v new file mode 100644 index 000000000..b5531d901 --- /dev/null +++ b/test_regress/t/t_clocking_xref.v @@ -0,0 +1,46 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2024 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module mod; + logic clk = 1'b0; + logic inp = 1'b0; + clocking cb @(posedge clk); + input #3 inp; + endclocking + always @(posedge clk) inp <= 1'b1; + always #1 clk = ~clk; +endmodule + +module main; + logic clk = 1'b0; + logic inp = 1'b0; + always begin + #2 + if (t.mod1.cb.inp != 1'b0) $stop; + if (t.main1.cbb.inp != 1'b0) $stop; + if (t.main2.cbb.inp != 1'b0) $stop; + #4; + if (t.mod1.cb.inp != 1'b1) $stop; + if (t.main1.cbb.inp != 1'b1) $stop; + if (t.main2.cbb.inp != 1'b1) $stop; + end + clocking cbb @(posedge clk); + input #3 inp; + endclocking + always @(posedge clk) inp <= 1'b1; + always #1 clk = ~clk; +endmodule + +module t; + main main1(); + mod mod1(); + main main2(); + initial begin + #7; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule