diff --git a/src/V3AssertPre.cpp b/src/V3AssertPre.cpp index 8fe9fb5c5..183d087ea 100644 --- a/src/V3AssertPre.cpp +++ b/src/V3AssertPre.cpp @@ -32,6 +32,36 @@ VL_DEFINE_DEBUG_FUNCTIONS; +//###################################################################### +// Clocking blocks of clocking items + +class AssertPreClockingVisitor final : public VNVisitorConst { + // Captures the clocking block of each clocking item before AssertPreVisitor, which needs it + // at synchronous drives, often visited before the clocking block, e.g. in a parent module + // NODE STATE + // AstClockingItem::user3p() // AstClocking*. Clocking block of the item, user3 claimed by + // // AssertPreVisitor + + // VISITORS + void visit(AstNetlist* nodep) override { iterateAndNextConstNull(nodep->modulesp()); } + void visit(AstNodeModule* nodep) override { iterateAndNextConstNull(nodep->stmtsp()); } + void visit(AstGenBlock* nodep) override { iterateAndNextConstNull(nodep->itemsp()); } + void visit(AstClocking* nodep) override { + for (AstNode* itemp = nodep->itemsp(); itemp; itemp = itemp->nextp()) { + if (AstClockingItem* const citemp = VN_CAST(itemp, ClockingItem)) { + citemp->user3p(nodep); + } + } + } + // Clocking blocks are only in modules and their generate blocks + void visit(AstNode*) override {} + +public: + // CONSTRUCTORS + explicit AssertPreClockingVisitor(AstNetlist* nodep) { iterateConst(nodep); } + ~AssertPreClockingVisitor() override = default; +}; + //###################################################################### // Assert class functions @@ -49,9 +79,12 @@ private: // NODE STATE // AstClockingItem::user1p() // AstVar*. varp() of ClockingItem after unlink // AstClockingItem::user2p() // AstVar*. Flag set by drives of output clockvar + // AstClockingItem::user3p() // AstClocking*. Clocking block of the item, captured + // // by AssertPreClockingVisitor // AstPExpr::user1() // bool. Created from AstUntil const VNUser1InUse m_inuser1; const VNUser2InUse m_inuser2; + const VNUser3InUse m_inuser3; // STATE // Current context: AstNetlist* const m_netlistp = nullptr; // Current netlist @@ -83,6 +116,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 + bool m_inConcatAssign = false; // True if in assignment to a concatenation std::vector m_drives; // Clockvars written by the synchronous drive bool m_hasCycleDelay = false; // True if node has cycle delay beneath std::vector m_xrefsp; // list of xrefs that need name fixup @@ -100,26 +134,97 @@ private: } return VN_AS(itemp->user2p(), Var); } - // Assignment setting the drive flag of a driven clockvar, referenced like the clockvar - AstAssign* newDrivenSetp(FileLine* flp, const SynchDrive& drive) { - AstVar* const varp = getCreateDrivenVarp(drive.itemp); - AstNodeExpr* refp; + // Reference to a variable in the scope of a driven clockvar, made like the clockvar's + static AstNodeExpr* newDriveRefp(FileLine* flp, const SynchDrive& drive, AstVar* varp, + const VAccess& access) { if (const AstVarXRef* const xrefp = VN_CAST(drive.refp, VarXRef)) { - refp = new AstVarXRef{flp, varp, xrefp->dotted(), VAccess::WRITE}; + return new AstVarXRef{flp, varp, xrefp->dotted(), access}; } else if (const AstMemberSel* const selp = VN_CAST(drive.refp, MemberSel)) { - // The interface expression is evaluated again for the flag, so it must not have side - // effects; cloneTreePure warns if it has any + // The interface expression is evaluated again, so it must not have side effects; + // cloneTreePure warns if it has any AstMemberSel* const newSelp = new AstMemberSel{flp, selp->fromp()->cloneTreePure(false), varp}; - newSelp->access(VAccess::WRITE); - refp = newSelp; - } else { - refp = new AstVarRef{flp, varp, VAccess::WRITE}; + newSelp->access(access); + return newSelp; } - AstAssign* const setp = new AstAssign{flp, refp, new AstConst{flp, AstConst::BitTrue{}}}; + return new AstVarRef{flp, varp, access}; + } + // Assignment setting the drive flag of a driven clockvar + AstAssign* newDrivenSetp(FileLine* flp, const SynchDrive& drive) { + AstVar* const varp = getCreateDrivenVarp(drive.itemp); + AstAssign* const setp = new AstAssign{flp, newDriveRefp(flp, drive, varp, VAccess::WRITE), + new AstConst{flp, AstConst::BitTrue{}}}; setp->user1(true); return setp; } + // Clocking block of a clocking item + static AstClocking* clockingOf(const AstClockingItem* itemp) { + UASSERT_OBJ(itemp->user3p(), itemp, "Clocking item not captured in a clocking block"); + return VN_AS(itemp->user3p(), Clocking); + } + // Clocking event of a driven clockvar, referenced from the drive like the clockvar, or + // nullptr if unsupported, which is reported on the drive's cycle delay + AstSenItem* newDriveSensesp(AstDelay* delayp, const SynchDrive& drive) { + AstSenItem* const origp = clockingOf(drive.itemp)->sensesp(); + if (VN_IS(drive.refp, VarRef)) return origp->cloneTree(false); + if (origp->exists([](const AstVarXRef*) { return true; })) { + // The reference is relative to the clocking block + delayp->v3warn(E_UNSUPPORTED, "Unsupported: cycle delay in synchronous drive to" + " clockvar in another scope, whose clocking event" + " has a hierarchical reference"); + return nullptr; + } + const AstMemberSel* const selp = VN_CAST(drive.refp, MemberSel); + // Processes outside classes evaluate their events also when not waiting on them, + // possibly before the interface reference is set, so the event is guarded against a + // null reference, which needs an integral value; scheduling does not guard it + const bool guardNull = selp && !VN_IS(m_modp, Class); + if (guardNull && !origp->sensp()->dtypep()->skipRefp()->isIntegralOrPacked()) { + delayp->v3warn(E_UNSUPPORTED, "Unsupported: cycle delay in synchronous drive through" + " an interface reference outside a class, to clocking" + " block with non-integral clocking event"); + return nullptr; + } + // Interface declaring the clocking block, if driven through an interface reference + AstIface* const ifacep + = selp ? VN_AS(selp->fromp()->dtypep()->skipRefp(), IfaceRefDType)->ifaceViaCellp() + : nullptr; + AstSenItem* const sensesp = origp->cloneTree(false); + std::vector refps; + sensesp->foreach([&](AstVarRef* refp) { + if (!refp->classOrPackagep()) refps.push_back(refp); + }); + for (AstVarRef* const refp : refps) { + AstVar* const varp = refp->varp(); + if (ifacep) { + // Mark the variable as read through an interface reference, as V3Width does for + // a member select. The mark is the interface declaring the variable, whose + // instances all share it, so drives through references to any instances agree. + UASSERT_OBJ(!varp->sensIfacep() || varp->sensIfacep() == ifacep, refp, + "Variable read through references to different interfaces"); + varp->sensIfacep(ifacep); + } + refp->replaceWith(newDriveRefp(refp->fileline(), drive, varp, refp->access())); + VL_DO_DANGLING(pushDeletep(refp), refp); + } + if (guardNull) guardNullIface(sensesp, selp->fromp()); + return sensesp; + } + // Make an integral clocking event through an interface reference false while the reference + // is null + static void guardNullIface(AstSenItem* sensesp, AstNodeExpr* fromp) { + FileLine* const flp = sensesp->fileline(); + AstNodeExpr* const sensp = sensesp->sensp()->unlinkFrBack(); + sensesp->sensp(new AstCond{flp, newNotNullp(flp, fromp), sensp, + new AstConst{flp, AstConst::DTyped{}, sensp->dtypep()}}); + if (AstNodeExpr* const condp = sensesp->condp()) { + condp->unlinkFrBack(); + sensesp->condp(new AstLogAnd{flp, newNotNullp(flp, fromp), condp}); + } + } + static AstNodeExpr* newNotNullp(FileLine* flp, AstNodeExpr* fromp) { + return new AstNeq{flp, fromp->cloneTreePure(false), new AstConst{flp, AstConst::Null{}}}; + } static void checkSamplingFuncDType(AstNodeExpr* nodep, const AstNode* exprp) { const AstNodeDType* const dtypep = exprp->dtypep()->skipRefp(); @@ -493,12 +598,12 @@ private: FileLine* const flp = nodep->fileline(); AstNodeExpr* valuep = V3Const::constifyEdit(nodep->lhsp()->unlinkFrBack()); const AstConst* const constp = VN_CAST(valuep, Const); - if (!constp) { + if (!constp && !m_inSynchDrive) { // V3AssertNfa handles non-const delays before this pass and // replaces the property; this branch should never be reached. nodep->v3fatalSrc("Non-constant cycle delay in assertion: " "should have been caught by V3AssertNfa"); - } else if (constp->isZero()) { + } else if (constp && constp->isZero()) { VL_DO_DANGLING(pushDeletep(valuep), valuep); if (m_inSynchDrive) { // ##0 has no effect in synchronous drives (IEEE 1800-2023 14.11) @@ -535,20 +640,36 @@ private: return; } AstSenItem* sensesp = nullptr; - if (!m_defaultClockingp) { + if (!m_drives.empty()) { + if (m_inConcatAssign) { + // Also the clockvars may be of different clocking blocks + nodep->v3error("Cycle delays cannot be used in synchronous drives to a" + " concatenation (IEEE 1800-2023 14.16)"); + VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); + VL_DO_DANGLING(pushDeletep(valuep), valuep); + return; + } + // Count the cycles of the driven clockvar's clocking block (IEEE 1800-2023 14.16) + sensesp = newDriveSensesp(nodep, m_drives.front()); + if (!sensesp) { + VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); + VL_DO_DANGLING(pushDeletep(valuep), valuep); + return; + } + } else if (!m_defaultClockingp) { if (!m_pexprp) { nodep->v3error("Usage of cycle delays requires default clocking" " (IEEE 1800-2023 14.11)"); - VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); - VL_DO_DANGLING(valuep->deleteTree(), valuep); + VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); + VL_DO_DANGLING(pushDeletep(valuep), valuep); return; } - sensesp = m_senip; + sensesp = m_senip->cloneTree(false); } else { - sensesp = m_defaultClockingp->sensesp(); + sensesp = m_defaultClockingp->sensesp()->cloneTree(false); } - AstEventControl* const controlp = new AstEventControl{ - nodep->fileline(), new AstSenTree{flp, sensesp->cloneTree(false)}, nullptr}; + AstEventControl* const controlp + = new AstEventControl{nodep->fileline(), new AstSenTree{flp, sensesp}, nullptr}; const std::string delayName = m_cycleDlyNames.get(nodep); AstNodeExpr* throughoutp = nodep->throughoutp() ? nodep->throughoutp()->unlinkFrBack() : nullptr; @@ -557,6 +678,15 @@ private: nodep->findBasicDType(VBasicDTypeKwd::UINT32)}; cntVarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); AstBegin* const beginp = new AstBegin{flp, delayName + "__block", cntVarp, true}; + // A non-constant delay of a synchronous drive is counted at runtime, waiting for none if + // zero, like a constant '##0' in a synchronous drive. The count is integral of 64 bits, + // as V3Width makes delays, or real, which is rounded (IEEE 1800-2023 6.12.1) + if (valuep->isDouble()) { + valuep = new AstRToIRoundS{flp, valuep}; + valuep->dtypeFrom(cntVarp); + } else { + valuep = new AstSel{flp, valuep, 0, cntVarp->width()}; + } beginp->addStmtsp(new AstAssign{flp, new AstVarRef{flp, cntVarp, VAccess::WRITE}, valuep}); // Throughout: create flag tracking whether condition held every tick @@ -703,9 +833,16 @@ private: if (nodep->user1()) return; VL_RESTORER(m_inAssign); VL_RESTORER(m_inSynchDrive); + VL_RESTORER(m_inConcatAssign); VL_RESTORER_CLEAR(m_drives); m_inAssign = true; m_inSynchDrive = false; + { + // The braces of a concatenation are a replication, by one + const AstNodeExpr* lhsp = nodep->lhsp(); + if (const AstReplicate* const repp = VN_CAST(lhsp, Replicate)) lhsp = repp->srcp(); + m_inConcatAssign = VN_IS(lhsp, Concat); + } { VL_RESTORER(m_inAssignDlyLhs); m_inAssignDlyLhs = VN_IS(nodep, AssignDly); @@ -1686,6 +1823,7 @@ public: // CONSTRUCTORS explicit AssertPreVisitor(AstNetlist* nodep) : m_netlistp{nodep} { + { AssertPreClockingVisitor{nodep}; } // Process iterate(nodep); // Fix up varref names diff --git a/test_regress/t/t_clocking_bad4.out b/test_regress/t/t_clocking_bad4.out index 8107fc352..c184ee2aa 100644 --- a/test_regress/t/t_clocking_bad4.out +++ b/test_regress/t/t_clocking_bad4.out @@ -43,4 +43,8 @@ : ... note: In instance 't' 39 | $display(cb1.out); | ^~~ +%Error: t/t_clocking_bad4.v:46:33: Cycle delays cannot be used in synchronous drives to a concatenation (IEEE 1800-2023 14.16) + : ... note: In instance 't' + 46 | always {cb1.out, cb3.out3} <= ##1 2'b11; + | ^~ %Error: Exiting due to diff --git a/test_regress/t/t_clocking_bad4.v b/test_regress/t/t_clocking_bad4.v index cb959628d..3c7500b72 100644 --- a/test_regress/t/t_clocking_bad4.v +++ b/test_regress/t/t_clocking_bad4.v @@ -38,4 +38,10 @@ module t ( cb1.in = 1; $display(cb1.out); end + + logic out3; + clocking cb3 @(posedge clk); + output out3; + endclocking + always {cb1.out, cb3.out3} <= ##1 2'b11; endmodule diff --git a/test_regress/t/t_clocking_drive_concat.v b/test_regress/t/t_clocking_drive_concat.v index df859429c..4a2ee9489 100644 --- a/test_regress/t/t_clocking_drive_concat.v +++ b/test_regress/t/t_clocking_drive_concat.v @@ -35,12 +35,18 @@ module t; b = 0; @(pe); {pe.a, pe.b} <= 2'b11; + #2; + a = 0; + b = 0; + // '##0' has no effect, so is accepted, unlike other cycle delays (see t_clocking_bad4) + @(pe); + {pe.a, pe.b} <= ##0 2'b11; end initial begin - #30; - `checks(a_log, "1@5 0@7 1@15 ") - `checks(b_log, "1@5 0@7 1@15 ") + #40; + `checks(a_log, "1@5 0@7 1@15 0@17 1@25 ") + `checks(b_log, "1@5 0@7 1@15 0@17 1@25 ") $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_clocking_drive_cycle.py b/test_regress/t/t_clocking_drive_cycle.py new file mode 100755 index 000000000..cd905c04c --- /dev/null +++ b/test_regress/t/t_clocking_drive_cycle.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Cycle delays of synchronous drives count the target clocking block +# +# 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_clocking_drive_cycle.v b/test_regress/t/t_clocking_drive_cycle.v new file mode 100644 index 000000000..e297cff09 --- /dev/null +++ b/test_regress/t/t_clocking_drive_cycle.v @@ -0,0 +1,276 @@ +// DESCRIPTION: Verilator: Cycle delays of synchronous drives count the target clocking block +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checks(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got=\"%s\" exp=\"%s\"\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +package pkg; + bit pclk; +endpackage + +interface bus_if ( + input bit clk +); + bit w; + bit ws; + bit z; + clocking cb @(posedge clk); + output w, z; + output #1 ws; + endclocking + modport tb(clocking cb); +endinterface + +interface en_if ( + input bit clk +); + bit en; + bit y; + clocking icb @(posedge clk iff en); + output y; + endclocking +endinterface + +interface ev_if; + event ev; + bit e; + clocking ecb @(ev); + output e; + endclocking +endinterface + +// A class has no default clocking +class Driver; + virtual bus_if vif; + virtual bus_if.tb tvif; + virtual bus_if vifs[2]; + virtual ev_if vvif; + task run(); + @(vif.cb); + vif.cb.w <= ##2 1; + tvif.cb.w <= ##2 1; + tvif.cb.ws <= ##2 1; + vifs[0].cb.w <= ##2 1; + vvif.ecb.e <= ##2 1; + vif.cb.z <= ##0 1; + endtask +endclass + +// No default clocking is needed for a cycle delay in a drive +module sub ( + input bit clk, + output bit [1:0] q +); + bit [6:0] s; + bit n; + bit p; + clocking cb @(posedge clk); + output q, s; + endclocking + clocking ncb @(negedge clk); + output n; + endclocking + clocking pcb @(posedge pkg::pclk); + output p; + endclocking + for (genvar i = 0; i < 2; ++i) begin : g + bit x; + clocking cb @(posedge clk); + output x; + endclocking + end + initial begin + @(cb); + cb.q <= ##1 2; + end +endmodule + +module port_drv ( + bus_if b +); + initial begin + @(b.cb); + b.cb.w <= ##2 1; + end +endmodule + +module t; + bit clk; + bit tclk; + bit slow_clk; + bit [1:0] v; + bit [1:0] q; + bit c; + bit c0; + bit c7; + bit cr; + bit cr5; + bit d; + int cycles = 2; + int nocycles = 0; + bit [6:0] cycles7 = 2; + real rcycles = 1.6; + string v_log; + string q_log; + string s_log; + string n_log; + string p_log; + string x_log; + string x0_log; + string c_log; + string c0_log; + string c7_log; + string cr_log; + string cr5_log; + string proc_time; + string d_log; + string z_log; + string w_log; + string tw_log; + string tws_log; + string w0_log; + string w1_log; + string mw_log; + string pw_log; + string y_log; + string e_log; + Driver drv = new; + + always #5 clk = ~clk; + always #7 tclk = ~tclk; + always #20 slow_clk = ~slow_clk; + always @(clk) pkg::pclk = clk; + + default clocking slow @(posedge slow_clk); + output d; + endclocking + + clocking fast @(posedge clk); + output v, c, c0, c7, cr, cr5; + endclocking + + sub sub ( + .clk, + .q + ); + bus_if bus (.clk); + // Another instance of the interface, with another clock + bus_if tbus (.clk(tclk)); + bus_if buses[2] (.clk); + bus_if mbus (.clk); + bus_if pbus (.clk); + en_if ebus (.clk); + ev_if vbus (); + port_drv port_drv (.b(pbus)); + + virtual bus_if mvif; + virtual en_if evif; + + initial forever #10->vbus.ev; + + always @(v) if ($time != 0) v_log = {v_log, $sformatf("%0d@%0d ", v, $time)}; + always @(q) if ($time != 0) q_log = {q_log, $sformatf("%0d@%0d ", q, $time)}; + always @(sub.s) if ($time != 0) s_log = {s_log, $sformatf("%0d@%0d ", sub.s, $time)}; + always @(sub.n) if ($time != 0) n_log = {n_log, $sformatf("%0d@%0d ", sub.n, $time)}; + always @(sub.p) if ($time != 0) p_log = {p_log, $sformatf("%0d@%0d ", sub.p, $time)}; + always @(sub.g[1].x) if ($time != 0) x_log = {x_log, $sformatf("%0d@%0d ", sub.g[1].x, $time)}; + always @(sub.g[0].x) if ($time != 0) x0_log = {x0_log, $sformatf("%0d@%0d ", sub.g[0].x, $time)}; + always @(c) if ($time != 0) c_log = {c_log, $sformatf("%0d@%0d ", c, $time)}; + always @(c0) if ($time != 0) c0_log = {c0_log, $sformatf("%0d@%0d ", c0, $time)}; + always @(c7) if ($time != 0) c7_log = {c7_log, $sformatf("%0d@%0d ", c7, $time)}; + always @(cr) if ($time != 0) cr_log = {cr_log, $sformatf("%0d@%0d ", cr, $time)}; + always @(cr5) if ($time != 0) cr5_log = {cr5_log, $sformatf("%0d@%0d ", cr5, $time)}; + always @(d) if ($time != 0) d_log = {d_log, $sformatf("%0d@%0d ", d, $time)}; + always @(bus.z) if ($time != 0) z_log = {z_log, $sformatf("%0d@%0d ", bus.z, $time)}; + always @(bus.w) if ($time != 0) w_log = {w_log, $sformatf("%0d@%0d ", bus.w, $time)}; + always @(tbus.w) if ($time != 0) tw_log = {tw_log, $sformatf("%0d@%0d ", tbus.w, $time)}; + always @(tbus.ws) if ($time != 0) tws_log = {tws_log, $sformatf("%0d@%0d ", tbus.ws, $time)}; + always @(buses[0].w) if ($time != 0) w0_log = {w0_log, $sformatf("%0d@%0d ", buses[0].w, $time)}; + always @(buses[1].w) if ($time != 0) w1_log = {w1_log, $sformatf("%0d@%0d ", buses[1].w, $time)}; + always @(mbus.w) if ($time != 0) mw_log = {mw_log, $sformatf("%0d@%0d ", mbus.w, $time)}; + always @(pbus.w) if ($time != 0) pw_log = {pw_log, $sformatf("%0d@%0d ", pbus.w, $time)}; + always @(ebus.y) if ($time != 0) y_log = {y_log, $sformatf("%0d@%0d ", ebus.y, $time)}; + always @(vbus.e) if ($time != 0) e_log = {e_log, $sformatf("%0d@%0d ", vbus.e, $time)}; + + initial begin + mvif = mbus; + evif = ebus; + drv.vif = bus; + drv.tvif = tbus; + drv.vifs[0] = buses[0]; + drv.vifs[1] = buses[1]; + drv.vvif = vbus; + drv.run(); + end + + // Each drive counts the cycles of the target clockvar's clocking block, not of the default + // clocking (IEEE 1800-2023 14.16) + initial begin + @(fast); + fast.v <= ##2 1; + sub.cb.s <= ##2 7'h55; + sub.ncb.n <= ##1 1; + sub.pcb.p <= ##1 1; + sub.g[1].cb.x <= ##2 1; + // The default clocking counts its cycles as before + slow.d <= ##1 1; + // A non-constant cycle delay is evaluated by the drive, and counted at runtime + fast.c <= ##cycles 1; + fast.c0 <= ##nocycles 1; + fast.c7 <= ##cycles7 1; + // A real cycle count is rounded (IEEE 1800-2023 6.12.1) + fast.cr <= ##rcycles 1; + fast.cr5 <= ##(2.5) 1; + sub.g[0].cb.x <= ##cycles 1; + buses[1].cb.w <= ##2 1; + mvif.cb.w <= ##2 1; + end + + // The clocking event of icb only occurs while en is set + initial begin + #1 evif.icb.y <= ##1 1; + #20 ebus.en = 1; + end + + // Also in a procedural cycle delay, of the default clocking + initial begin + #2; + ##(1.5); + proc_time = $sformatf("%0d", $time); + end + + initial begin + #100; + `checks(v_log, "1@25 ") + `checks(q_log, "2@15 ") + `checks(s_log, "85@25 ") + `checks(n_log, "1@10 ") + `checks(p_log, "1@15 ") + `checks(x_log, "1@25 ") + `checks(x0_log, "1@25 ") + `checks(c_log, "1@25 ") + `checks(c0_log, "1@5 ") + `checks(c7_log, "1@25 ") + `checks(cr_log, "1@25 ") + `checks(cr5_log, "1@35 ") + `checks(d_log, "1@20 ") + `checks(proc_time, "60") + `checks(z_log, "1@5 ") + `checks(w_log, "1@25 ") + `checks(tw_log, "1@21 ") + `checks(tws_log, "1@22 ") + `checks(w0_log, "1@25 ") + `checks(w1_log, "1@25 ") + `checks(mw_log, "1@25 ") + `checks(pw_log, "1@25 ") + `checks(y_log, "1@25 ") + `checks(e_log, "1@20 ") + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_clocking_drive_cycle_unsup.out b/test_regress/t/t_clocking_drive_cycle_unsup.out new file mode 100644 index 000000000..6c6d23e95 --- /dev/null +++ b/test_regress/t/t_clocking_drive_cycle_unsup.out @@ -0,0 +1,10 @@ +%Error-UNSUPPORTED: t/t_clocking_drive_cycle_unsup.v:18:24: Unsupported: cycle delay in synchronous drive to clockvar in another scope, whose clocking event has a hierarchical reference + : ... note: In instance 't' + 18 | initial sub.scb.s <= ##1 1; + | ^~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error-UNSUPPORTED: t/t_clocking_drive_cycle_unsup.v:24:19: Unsupported: cycle delay in synchronous drive through an interface reference outside a class, to clocking block with non-integral clocking event + : ... note: In instance 't' + 24 | vvif.ecb.e <= ##1 1; + | ^~ +%Error: Exiting due to diff --git a/test_regress/t/t_clocking_drive_cycle_unsup.py b/test_regress/t/t_clocking_drive_cycle_unsup.py new file mode 100755 index 000000000..38cf36b43 --- /dev/null +++ b/test_regress/t/t_clocking_drive_cycle_unsup.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_clocking_drive_cycle_unsup.v b/test_regress/t/t_clocking_drive_cycle_unsup.v new file mode 100644 index 000000000..a092b0fb3 --- /dev/null +++ b/test_regress/t/t_clocking_drive_cycle_unsup.v @@ -0,0 +1,34 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +module sub; + bit s; + clocking scb @(posedge t.clk); + output s; + endclocking + initial scb.s <= ##1 1; +endmodule + +module t; + bit clk; + sub sub (); + initial sub.scb.s <= ##1 1; + + ev_if vbus (); + virtual ev_if vvif; + initial begin + vvif = vbus; + vvif.ecb.e <= ##1 1; + end +endmodule + +interface ev_if; + event ev; + bit e; + clocking ecb @(ev); + output e; + endclocking +endinterface