Support `##0` cycle delays (#4263) (#7298)

This commit is contained in:
Yilou Wang 2026-03-20 23:29:20 +01:00 committed by GitHub
parent c3fc0d9f0f
commit 9ea7abd1c7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 219 additions and 10 deletions

View File

@ -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 {

View File

@ -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

View File

@ -11,6 +11,6 @@ import vltest_bootstrap
test.scenarios('linter')
test.lint(fails=True, expect_filename=test.golden_filename)
test.lint()
test.passes()

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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