From 8c11d0d0bd8114ca6ba17d98a487c6e710e91e8d Mon Sep 17 00:00:00 2001 From: Artur Bieniek Date: Tue, 7 Apr 2026 12:44:52 +0200 Subject: [PATCH] Support rise/fall delays (#7368) Signed-off-by: Artur Bieniek --- docs/guide/warnings.rst | 7 +- src/V3AstNodeStmt.h | 9 +- src/V3EmitV.cpp | 10 +- src/V3Timing.cpp | 26 +++++ src/V3Width.cpp | 5 + src/verilog.y | 10 +- test_regress/t/t_gate_basic.v | 2 +- test_regress/t/t_gate_basic_specify_bad.out | 6 +- test_regress/t/t_gate_delay_unsup.out | 6 +- test_regress/t/t_gate_delay_unsup.py | 9 +- test_regress/t/t_risefall_delay.py | 18 ++++ test_regress/t/t_risefall_delay.v | 104 ++++++++++++++++++++ 12 files changed, 189 insertions(+), 23 deletions(-) create mode 100755 test_regress/t/t_risefall_delay.py create mode 100644 test_regress/t/t_risefall_delay.v diff --git a/docs/guide/warnings.rst b/docs/guide/warnings.rst index 84845a67a..9a85efd3b 100644 --- a/docs/guide/warnings.rst +++ b/docs/guide/warnings.rst @@ -1855,8 +1855,11 @@ List Of Warnings and #(1,2,3) AND (out, a, b); - Warns that rising, falling, and turn-off delays are currently unsupported. - The first (rising) delay is used for all cases. + Warns that the third (turn-off) delay is currently unsupported and is + ignored. Rising and falling delays are supported. + + In versions before 5.048, warned that rising, falling, and turn-off delays were +unsupported. The first (rising) delay was used for all cases. .. option:: SELRANGE diff --git a/src/V3AstNodeStmt.h b/src/V3AstNodeStmt.h index dbf8ed151..05a419f5f 100644 --- a/src/V3AstNodeStmt.h +++ b/src/V3AstNodeStmt.h @@ -543,11 +543,10 @@ class AstDelay final : public AstNodeStmt { // Delay statement // @astgen op1 := lhsp : AstNodeExpr // Delay value (or min for range) // @astgen op2 := stmtsp : List[AstNode] // Statements under delay - // @astgen op3 := rhsp : Optional[AstNodeExpr] // Max delay value (range delay only) + // @astgen op3 := rhsp : Optional[AstNodeExpr] // Max bound for cycle range or fall delay // @astgen op4 := throughoutp : Optional[AstNodeExpr] // Throughout condition (IEEE 16.9.9) VTimescale m_timeunit; // Delay's time unit const bool m_isCycle; // True if it is a cycle delay - public: AstDelay(FileLine* fl, AstNodeExpr* lhsp, bool isCycle) : ASTGEN_SUPER_Delay(fl) @@ -562,8 +561,10 @@ public: void timeunit(const VTimescale& flag) { m_timeunit = flag; } VTimescale timeunit() const { return m_timeunit; } bool isCycleDelay() const { return m_isCycle; } - bool isRangeDelay() const { return rhsp() != nullptr; } - bool isUnbounded() const { return rhsp() && VN_IS(rhsp(), Unbounded); } + bool isRangeDelay() const { return m_isCycle && rhsp() != nullptr; } + bool isUnbounded() const { return isRangeDelay() && VN_IS(rhsp(), Unbounded); } + void fallDelay(AstNodeExpr* const fallDelayp) { rhsp(fallDelayp); } + AstNodeExpr* fallDelay() const { return m_isCycle ? nullptr : rhsp(); } }; class AstDisable final : public AstNodeStmt { // @astgen op1 := targetRefp : Optional[AstNodeExpr] // Reference to link in V3LinkDot diff --git a/src/V3EmitV.cpp b/src/V3EmitV.cpp index 96724db78..ea82d070c 100644 --- a/src/V3EmitV.cpp +++ b/src/V3EmitV.cpp @@ -1127,7 +1127,15 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst { } VL_RESTORER(m_prefixed); m_prefixed = false; - iterateConst(nodep->lhsp()); + if (AstNodeExpr* const fallDelayp = nodep->fallDelay()) { + puts("("); + iterateConst(nodep->lhsp()); + puts(", "); + iterateConst(fallDelayp); + puts(")"); + } else { + iterateConst(nodep->lhsp()); + } if (!m_suppressSemi) { puts(";\n"); } else { diff --git a/src/V3Timing.cpp b/src/V3Timing.cpp index 25b19e788..ac48b4032 100644 --- a/src/V3Timing.cpp +++ b/src/V3Timing.cpp @@ -542,6 +542,20 @@ class TimingControlVisitor final : public VNVisitor { = timeunit.powerOfTen() - m_netlistp->timeprecision().powerOfTen(); return std::pow(10.0, scalePowerOfTen); } + static bool staticallyNonZeroDelay(const AstNodeExpr* valuep) { + if (const AstConst* const constp = VN_CAST(valuep, Const)) return !constp->isZero(); + if (const AstCond* const condp = VN_CAST(valuep, Cond)) { + return staticallyNonZeroDelay(condp->thenp()) + && staticallyNonZeroDelay(condp->elsep()); + } + if (const AstMul* const mulp = VN_CAST(valuep, Mul)) { + const AstConst* const lhsConstp = VN_CAST(mulp->lhsp(), Const); + const AstConst* const rhsConstp = VN_CAST(mulp->rhsp(), Const); + if (lhsConstp && !lhsConstp->isZero()) return staticallyNonZeroDelay(mulp->rhsp()); + if (rhsConstp && !rhsConstp->isZero()) return staticallyNonZeroDelay(mulp->lhsp()); + } + return false; + } // Creates the global delay scheduler variable AstVarScope* getCreateDelayScheduler() { if (m_delaySchedp) return m_delaySchedp; @@ -1022,6 +1036,9 @@ class TimingControlVisitor final : public VNVisitor { m_hasStaticZeroDelay = true; // Don't warn on variable delays, as no point m_unknownDelayFlps.clear(); + } else if (staticallyNonZeroDelay(valuep)) { + // Delay is dynamic, but every statically known outcome is non-zero. + // So we don't need #0 delay support and there should be no warning. } else if (!VN_IS(valuep, Const)) { // Delay is not known at compiile time. Conservatively schedule for #0 support, // but warn if no static #0 delays used as performance might be improved @@ -1250,6 +1267,15 @@ class TimingControlVisitor final : public VNVisitor { AstNodeExpr* const lhs1p = nodep->lhsp()->unlinkFrBack(); AstNodeExpr* const rhs1p = nodep->rhsp()->unlinkFrBack(); AstNode* const controlp = nodep->timingControlp()->unlinkFrBack(); + if (AstDelay* const delayp = VN_CAST(controlp, Delay)) { + if (AstNodeExpr* fallDelayp = delayp->fallDelay()) { + fallDelayp = fallDelayp->unlinkFrBack(); + // Use fall only for an all-zero value, rise otherwise. + delayp->lhsp( + new AstCond{flp, new AstEq{flp, rhs1p->cloneTree(false), new AstConst{flp, 0}}, + fallDelayp, delayp->lhsp()->unlinkFrBack()}); + } + } AstAssign* const assignp = new AstAssign{nodep->fileline(), lhs1p, rhs1p, controlp}; // Put the assignment in a fork..join_none. AstFork* const forkp = new AstFork{flp, VJoinType::JOIN_NONE}; diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 00f7c8269..5fb16555a 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -731,6 +731,11 @@ class WidthVisitor final : public VNVisitor { iterateCheckBool(nodep, "default disable iff condition", nodep->condp(), BOTH); } void visit(AstDelay* nodep) override { + if (AstNodeExpr* const fallDelayp = nodep->fallDelay()) { + iterateCheckDelay(nodep, "delay", nodep->lhsp(), BOTH); + iterateCheckDelay(nodep, "delay", fallDelayp, BOTH); + return; + } if (VN_IS(m_procedurep, Final)) { nodep->v3error("Delays are not legal in final blocks (IEEE 1800-2023 9.2.3)"); VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); diff --git a/src/verilog.y b/src/verilog.y index 116f0fcad..8641c3d62 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -38,8 +38,7 @@ { BBUNSUP((fl), "Unsupported: Verilog 1995 gate primitive: " << (tok)); } #define RISEFALLDLYUNSUP(nodep) \ if (nodep->fileline()->timingOn() && v3Global.opt.timing().isSetTrue()) { \ - nodep->v3warn(RISEFALLDLY, \ - "Unsupported: rising/falling/turn-off delays. Using the first delay"); \ + nodep->v3warn(RISEFALLDLY, "Unsupported: turn-off delays. Ignoring the third delay"); \ } #define MINTYPMAXDLYUNSUP(nodep) \ if (nodep->fileline()->timingOn() && v3Global.opt.timing().isSetTrue()) { \ @@ -3043,9 +3042,10 @@ delay_control: //== IEEE: delay_control | '#' '(' minTypMax ')' { $$ = new AstDelay{$1, $3, false}; } | '#' '(' minTypMax ',' minTypMax ')' - { $$ = new AstDelay{$1, $3, false}; RISEFALLDLYUNSUP($3); DEL($5); } + { $$ = new AstDelay{$1, $3, false}; + $$->fallDelay($5); } | '#' '(' minTypMax ',' minTypMax ',' minTypMax ')' - { $$ = new AstDelay{$1, $5, false}; RISEFALLDLYUNSUP($5); DEL($3); DEL($7); } + { $$ = new AstDelay{$1, $3, false}; $$->fallDelay($5); RISEFALLDLYUNSUP($7); DEL($7); } ; delay_value: // ==IEEE:delay_value @@ -6883,7 +6883,7 @@ cycle_delay_range: // IEEE: ==cycle_delay_range // // the sv-ac committee has been asked to clarify (Mantis 1901) | yP_POUNDPOUND '[' constExpr ':' constExpr ']' { $$ = new AstDelay{$1, $3, true}; - $$->rhsp($5); } + $$->rhsp($5); } | yP_POUNDPOUND yP_BRASTAR ']' { $$ = new AstDelay{$1, new AstConst{$1, 0}, true}; $$->rhsp(new AstUnbounded{$1}); } diff --git a/test_regress/t/t_gate_basic.v b/test_regress/t/t_gate_basic.v index 9641ae318..eda9c1b74 100644 --- a/test_regress/t/t_gate_basic.v +++ b/test_regress/t/t_gate_basic.v @@ -22,7 +22,7 @@ module t ( // verilator lint_off IMPLICIT not #(0.108) NT0 (nt0, a[0]); and #1 AN0 (an0, a[0], b[0]); - nand #(2,3) ND0 (nd0, a[0], b[0], b[1]); + nand #(2,3,4) ND0 (nd0, a[0], b[0], b[1]); or OR0 (or0, a[0], b[0]); nor NR0 (nr0, a[0], b[0], b[2]); xor (xo0, a[0], b[0]); diff --git a/test_regress/t/t_gate_basic_specify_bad.out b/test_regress/t/t_gate_basic_specify_bad.out index 3b16e0887..7e222e977 100644 --- a/test_regress/t/t_gate_basic_specify_bad.out +++ b/test_regress/t/t_gate_basic_specify_bad.out @@ -1,6 +1,6 @@ -%Warning-RISEFALLDLY: t/t_gate_basic.v:25:11: Unsupported: rising/falling/turn-off delays. Using the first delay - 25 | nand #(2,3) ND0 (nd0, a[0], b[0], b[1]); - | ^ +%Warning-RISEFALLDLY: t/t_gate_basic.v:25:15: Unsupported: turn-off delays. Ignoring the third delay + 25 | nand #(2,3,4) ND0 (nd0, a[0], b[0], b[1]); + | ^ ... For warning description see https://verilator.org/warn/RISEFALLDLY?v=latest ... Use "/* verilator lint_off RISEFALLDLY */" and lint_on around source to disable this message. %Warning-SPECIFYIGN: t/t_gate_basic.v:49:25: Ignoring unsupported: specify block construct diff --git a/test_regress/t/t_gate_delay_unsup.out b/test_regress/t/t_gate_delay_unsup.out index b28579928..1123acb04 100644 --- a/test_regress/t/t_gate_delay_unsup.out +++ b/test_regress/t/t_gate_delay_unsup.out @@ -1,6 +1,6 @@ -%Warning-RISEFALLDLY: t/t_gate_basic.v:25:11: Unsupported: rising/falling/turn-off delays. Using the first delay - 25 | nand #(2,3) ND0 (nd0, a[0], b[0], b[1]); - | ^ +%Warning-RISEFALLDLY: t/t_gate_basic.v:25:15: Unsupported: turn-off delays. Ignoring the third delay + 25 | nand #(2,3,4) ND0 (nd0, a[0], b[0], b[1]); + | ^ ... For warning description see https://verilator.org/warn/RISEFALLDLY?v=latest ... Use "/* verilator lint_off RISEFALLDLY */" and lint_on around source to disable this message. %Error: Exiting due to diff --git a/test_regress/t/t_gate_delay_unsup.py b/test_regress/t/t_gate_delay_unsup.py index ddce87727..f56f1b87d 100755 --- a/test_regress/t/t_gate_delay_unsup.py +++ b/test_regress/t/t_gate_delay_unsup.py @@ -9,11 +9,12 @@ import vltest_bootstrap -test.scenarios('linter') +test.scenarios('vlt') test.top_filename = "t/t_gate_basic.v" -test.lint(verilator_flags2=["-Wall", "-Wno-DECLFILENAME -Wno-SPECIFYIGN -Wno-UNUSED"], - fails=True, - expect_filename=test.golden_filename) +test.compile( + verilator_flags2=["--timing", "-Wall", "-Wno-DECLFILENAME -Wno-SPECIFYIGN -Wno-UNUSED"], + fails=True, + expect_filename=test.golden_filename) test.passes() diff --git a/test_regress/t/t_risefall_delay.py b/test_regress/t/t_risefall_delay.py new file mode 100755 index 000000000..390bba1b8 --- /dev/null +++ b/test_regress/t/t_risefall_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(timing_loop=True, verilator_flags2=["--timing"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_risefall_delay.v b/test_regress/t/t_risefall_delay.v new file mode 100644 index 000000000..496149a61 --- /dev/null +++ b/test_regress/t/t_risefall_delay.v @@ -0,0 +1,104 @@ +// DESCRIPTION: Verilator: Rise/fall delays on continuous assigns and gates +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__, `__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0) +`define check_scalar(exp) do begin `checkh(out_assign, exp); `checkh(out_buf, exp); `checkh(out_net, exp); end while(0) + +module t; + logic in = 0; + logic [3:0] in_vec = 4'h0; + wire out_assign; + wire out_buf; + wire #(5,3) out_net; + wire [3:0] out_vec_assign; + + assign #(5,3) out_assign = in; + buf #(5,3) u_buf (out_buf, in); + assign out_net = in; + assign #(5,3) out_vec_assign = in_vec; + + initial begin + #4; + `check_scalar(1'b0); + `checkh(out_vec_assign, 4'h0); + + // Rise canceled by a fall before the rise delay expires. + in = 1'b1; + #2; + `check_scalar(1'b0); + + in = 1'b0; + #4; + `check_scalar(1'b0); + + // A committed rise. + in = 1'b1; + #4; + `check_scalar(1'b0); + #1; + `check_scalar(1'b1); + + // Fall canceled by a new rise before the fall delay expires. + in = 1'b0; + #2; + `check_scalar(1'b1); + in = 1'b1; + #4; + `check_scalar(1'b1); + #1; + `check_scalar(1'b1); + + // A committed fall. + in = 1'b0; + #2; + `check_scalar(1'b1); + #1; + `check_scalar(1'b0); + + // Whole-value vector rise canceled by a fall back to zero. + in_vec = 4'h3; + #2; + `checkh(out_vec_assign, 4'h0); + in_vec = 4'h0; + #4; + `checkh(out_vec_assign, 4'h0); + + // Zero to nonzero uses the rise delay. + in_vec = 4'h3; + #4; + `checkh(out_vec_assign, 4'h0); + #1; + `checkh(out_vec_assign, 4'h3); + + // Nonzero to nonzero still uses the rise delay on the whole value. + in_vec = 4'h5; + #4; + `checkh(out_vec_assign, 4'h3); + #1; + `checkh(out_vec_assign, 4'h5); + + // A pending fall back to zero is canceled by a new nonzero value. + in_vec = 4'h0; + #2; + `checkh(out_vec_assign, 4'h5); + in_vec = 4'h6; + #4; + `checkh(out_vec_assign, 4'h5); + #1; + `checkh(out_vec_assign, 4'h6); + + // Nonzero to zero uses the fall delay. + in_vec = 4'h0; + #2; + `checkh(out_vec_assign, 4'h6); + #1; + `checkh(out_vec_assign, 4'h0); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule