Support rise/fall delays (#7368)

Signed-off-by: Artur Bieniek <abieniek@antmicro.com>
This commit is contained in:
Artur Bieniek 2026-04-07 12:44:52 +02:00 committed by GitHub
parent dfb7b034a5
commit 8c11d0d0bd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 189 additions and 23 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<delayp>: //== IEEE: delay_control
| '#' '(' minTypMax ')'
{ $$ = new AstDelay{$<fl>1, $3, false}; }
| '#' '(' minTypMax ',' minTypMax ')'
{ $$ = new AstDelay{$<fl>1, $3, false}; RISEFALLDLYUNSUP($3); DEL($5); }
{ $$ = new AstDelay{$<fl>1, $3, false};
$$->fallDelay($5); }
| '#' '(' minTypMax ',' minTypMax ',' minTypMax ')'
{ $$ = new AstDelay{$<fl>1, $5, false}; RISEFALLDLYUNSUP($5); DEL($3); DEL($7); }
{ $$ = new AstDelay{$<fl>1, $3, false}; $$->fallDelay($5); RISEFALLDLYUNSUP($7); DEL($7); }
;
delay_value<nodeExprp>: // ==IEEE:delay_value
@ -6883,7 +6883,7 @@ cycle_delay_range<delayp>: // 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}); }

View File

@ -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]);

View File

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

View File

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

View File

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

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(timing_loop=True, verilator_flags2=["--timing"])
test.execute()
test.passes()

View File

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