Support rise/fall delays (#7368)
Signed-off-by: Artur Bieniek <abieniek@antmicro.com>
This commit is contained in:
parent
dfb7b034a5
commit
8c11d0d0bd
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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}); }
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue