From 9ea7abd1c7e20976172f1e9144bd9afa90cd5497 Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Fri, 20 Mar 2026 23:29:20 +0100 Subject: [PATCH] Support `##0` cycle delays (#4263) (#7298) --- src/V3AssertPre.cpp | 44 +++++++- test_regress/t/t_clocking_unsup2.out | 6 - test_regress/t/t_clocking_unsup2.py | 2 +- test_regress/t/t_clocking_zero_delay.py | 18 +++ test_regress/t/t_clocking_zero_delay.v | 112 +++++++++++++++++++ test_regress/t/t_clocking_zero_delay_bad.out | 10 ++ test_regress/t/t_clocking_zero_delay_bad.py | 16 +++ test_regress/t/t_clocking_zero_delay_bad.v | 21 ++++ 8 files changed, 219 insertions(+), 10 deletions(-) delete mode 100644 test_regress/t/t_clocking_unsup2.out create mode 100755 test_regress/t/t_clocking_zero_delay.py create mode 100644 test_regress/t/t_clocking_zero_delay.v create mode 100644 test_regress/t/t_clocking_zero_delay_bad.out create mode 100755 test_regress/t/t_clocking_zero_delay_bad.py create mode 100644 test_regress/t/t_clocking_zero_delay_bad.v diff --git a/src/V3AssertPre.cpp b/src/V3AssertPre.cpp index 441447b72..eb9acb885 100644 --- a/src/V3AssertPre.cpp +++ b/src/V3AssertPre.cpp @@ -47,6 +47,7 @@ private: AstClocking* m_clockingp = nullptr; // Current clocking block // Reset each module: AstClocking* m_defaultClockingp = nullptr; // Default clocking for current module + AstVar* m_defaultClkEvtVarp = nullptr; // Event var for default clocking (for ##0) AstDefaultDisable* m_defaultDisablep = nullptr; // Default disable for current module // Reset each assertion: AstSenItem* m_senip = nullptr; // Last sensitivity @@ -376,9 +377,39 @@ private: nodep->v3error( "Delay value is not an elaboration-time constant (IEEE 1800-2023 16.7)"); } else if (constp->isZero()) { - nodep->v3warn(E_UNSUPPORTED, "Unsupported: ##0 delays"); - VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); - VL_DO_DANGLING(valuep->deleteTree(), valuep); + VL_DO_DANGLING(pushDeletep(valuep), valuep); + if (m_inSynchDrive) { + // ##0 has no effect in synchronous drives (IEEE 1800-2023 14.11) + VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); + return; + } + if (m_inPExpr) { + // ##0 in sequence context = zero delay = same clock tick + VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); + return; + } + // Procedural ##0: synchronize with default clocking event (IEEE 1800-2023 14.11) + // If the clocking event has not yet occurred this timestep, wait for it; + // otherwise continue without suspension. + if (!m_defaultClockingp) { + nodep->v3error("Usage of cycle delays requires default clocking" + " (IEEE 1800-2023 14.11)"); + VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); + return; + } + AstVar* const evtVarp = m_defaultClkEvtVarp; + UASSERT_OBJ(evtVarp, nodep, "Default clocking event var not pre-created"); + AstCMethodHard* const isTriggeredp = new AstCMethodHard{ + flp, new AstVarRef{flp, evtVarp, VAccess::READ}, VCMethod::EVENT_IS_TRIGGERED}; + isTriggeredp->dtypeSetBit(); + AstEventControl* const waitp = new AstEventControl{ + flp, + new AstSenTree{flp, new AstSenItem{flp, VEdgeType::ET_EVENT, + new AstVarRef{flp, evtVarp, VAccess::READ}}}, + nullptr}; + AstIf* const ifp = new AstIf{flp, new AstNot{flp, isTriggeredp}, waitp}; + nodep->replaceWith(ifp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); return; } AstSenItem* sensesp = nullptr; @@ -806,9 +837,11 @@ private: } void visit(AstNodeModule* nodep) override { VL_RESTORER(m_defaultClockingp); + VL_RESTORER(m_defaultClkEvtVarp); VL_RESTORER(m_defaultDisablep); VL_RESTORER(m_modp); m_defaultClockingp = nullptr; + m_defaultClkEvtVarp = nullptr; nodep->foreach([&](AstClocking* const clockingp) { if (clockingp->isDefault()) { if (m_defaultClockingp) { @@ -827,6 +860,11 @@ private: m_defaultDisablep = disablep; }); m_modp = nodep; + // Pre-create and cache the clocking event var before iterating children. + // visit(AstClocking) will unlink the event from the clocking node and place it + // in the module tree, then delete the clocking. After that, ensureEventp() would + // create an orphaned var. Caching here avoids this. + m_defaultClkEvtVarp = m_defaultClockingp ? m_defaultClockingp->ensureEventp() : nullptr; iterateChildren(nodep); } void visit(AstProperty* nodep) override { diff --git a/test_regress/t/t_clocking_unsup2.out b/test_regress/t/t_clocking_unsup2.out deleted file mode 100644 index acefbfd3b..000000000 --- a/test_regress/t/t_clocking_unsup2.out +++ /dev/null @@ -1,6 +0,0 @@ -%Error-UNSUPPORTED: t/t_clocking_unsup2.v:14:10: Unsupported: ##0 delays - : ... note: In instance 't' - 14 | always ##0; - | ^~ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error: Exiting due to diff --git a/test_regress/t/t_clocking_unsup2.py b/test_regress/t/t_clocking_unsup2.py index c7d9b21a5..67b896515 100755 --- a/test_regress/t/t_clocking_unsup2.py +++ b/test_regress/t/t_clocking_unsup2.py @@ -11,6 +11,6 @@ import vltest_bootstrap test.scenarios('linter') -test.lint(fails=True, expect_filename=test.golden_filename) +test.lint() test.passes() diff --git a/test_regress/t/t_clocking_zero_delay.py b/test_regress/t/t_clocking_zero_delay.py new file mode 100755 index 000000000..f2d7f08fa --- /dev/null +++ b/test_regress/t/t_clocking_zero_delay.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', '--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_clocking_zero_delay.v b/test_regress/t/t_clocking_zero_delay.v new file mode 100644 index 000000000..3061deaf4 --- /dev/null +++ b/test_regress/t/t_clocking_zero_delay.v @@ -0,0 +1,112 @@ +// 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 + +module t; + logic clk = 0; + always #5 clk = ~clk; + + logic [7:0] data_ref; + logic [7:0] data_test; + + default clocking cb @(posedge clk); + output #0 data_ref; + output #0 data_test; + endclocking + + // ========================================================= + // Branch 1: Procedural ##0 (IEEE 1800-2023 14.11) + // - Before clocking event: suspend until event fires + // - After clocking event: continue without suspension + // ========================================================= + + // 1a: ##0 at time 0 -- event has not yet fired, must wait + initial begin + ##0; + `checkd(int'($time), 5) + end + + // 1b: ##0 after @(posedge clk) -- event already fired, no-op + initial begin + @(posedge clk); + ##0; + `checkd(int'($time), 5) + end + + // 1c: Multiple consecutive ##0 all resolve at same time + initial begin + ##0; + ##0; + ##0; + `checkd(int'($time), 5) + end + + // 1d: ##0 followed by ##1 -- verify cycle counting still works + initial begin + ##0; + `checkd(int'($time), 5) + ##1; + `checkd(int'($time), 15) + end + + // ========================================================= + // Branch 2: ##0 in synchronous drive (IEEE 1800-2023 14.11) + // "shall have no effect, as if it were not present" + // cb.data <= ##0 value behaves same as cb.data <= value + // ========================================================= + + // Drive both at the same posedge: one with ##0, one without. + // If ##0 is truly "no effect", both signals update in the same cycle. + always begin + @(posedge clk); + cb.data_ref <= 8'hAB; // no ##0 -- baseline + cb.data_test <= ##0 8'hAB; // with ##0 -- should behave identically + @(posedge clk); + `checkd(data_test, data_ref) + `checkd(data_test, 8'hAB) + wait(0); + end + + // ========================================================= + // Branch 3: ##0 in property/sequence context + // a ##0 b = both a and b hold at the same clock tick + // + // ========================================================= + + logic p = 0, q = 0; + int cycle = 0; + int assert_pass_count = 0; + + always @(posedge clk) begin + cycle <= cycle + 1; + p <= (cycle == 3); // p=1 only at cycle 4 + q <= (cycle == 3); // q=1 only at cycle 4 + end + + // Pass action block: only incremented on nonvacuous success + assert property (@(posedge clk) p |-> (p ##0 q)) + assert_pass_count++; + else + $error("Branch 3: assertion (p |-> p ##0 q) failed"); + + // ========================================================= + // Completion + // ========================================================= + + initial begin + #200; + if (assert_pass_count == 0) begin + $write("%%Error: assertion (p |-> p ##0 q) never passed non-vacuously\n"); + `stop; + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_clocking_zero_delay_bad.out b/test_regress/t/t_clocking_zero_delay_bad.out new file mode 100644 index 000000000..dc0f229e0 --- /dev/null +++ b/test_regress/t/t_clocking_zero_delay_bad.out @@ -0,0 +1,10 @@ +%Error: t/t_clocking_zero_delay_bad.v:15:17: Cycle delays not allowed as intra-assignment delays (IEEE 1800-2023 14.11) + : ... note: In instance 'has_clocking' + 15 | always out <= ##0 1; + | ^~ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: t/t_clocking_zero_delay_bad.v:20:11: Usage of cycle delays requires default clocking (IEEE 1800-2023 14.11) + : ... note: In instance 'no_clocking' + 20 | initial ##0; + | ^~ +%Error: Exiting due to diff --git a/test_regress/t/t_clocking_zero_delay_bad.py b/test_regress/t/t_clocking_zero_delay_bad.py new file mode 100755 index 000000000..38cf36b43 --- /dev/null +++ b/test_regress/t/t_clocking_zero_delay_bad.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_zero_delay_bad.v b/test_regress/t/t_clocking_zero_delay_bad.v new file mode 100644 index 000000000..a28cfa849 --- /dev/null +++ b/test_regress/t/t_clocking_zero_delay_bad.v @@ -0,0 +1,21 @@ +// 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 + +/* verilator lint_off MULTITOP */ + +// Bad: ##0 as intra-assignment delay (IEEE 1800-2023 14.11) +module has_clocking; + logic clk = 0; + logic out; + default clocking cb @(posedge clk); + endclocking + always out <= ##0 1; +endmodule + +// Bad: ##0 without default clocking (IEEE 1800-2023 14.11) +module no_clocking; + initial ##0; +endmodule