From 8c11d0d0bd8114ca6ba17d98a487c6e710e91e8d Mon Sep 17 00:00:00 2001 From: Artur Bieniek Date: Tue, 7 Apr 2026 12:44:52 +0200 Subject: [PATCH 1/4] 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 From 2736262b982e0f4f86607682b8bfb39e862b8f63 Mon Sep 17 00:00:00 2001 From: em2machine <92717390+em2machine@users.noreply.github.com> Date: Tue, 7 Apr 2026 17:58:36 -0400 Subject: [PATCH 2/4] Fix resolving default/nondefault type-of-type parameters (#7380) (#7385) --- src/V3Param.cpp | 33 +++++----- test_regress/t/t_uvm_typeof_type.py | 18 ++++++ test_regress/t/t_uvm_typeof_type.v | 94 +++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+), 17 deletions(-) create mode 100755 test_regress/t/t_uvm_typeof_type.py create mode 100644 test_regress/t/t_uvm_typeof_type.v diff --git a/src/V3Param.cpp b/src/V3Param.cpp index 13e31e2e3..0223f6bd6 100644 --- a/src/V3Param.cpp +++ b/src/V3Param.cpp @@ -1235,30 +1235,29 @@ class ParamProcessor final { } } - // Check if exprp is a ClassRefDType whose class is the default-parameter clone - // of origp's template class. This catches the case where an explicit type parameter - // like Holder#(W#(int)) resolves to the same specialization as the implicit default - // Holder#(), because W#(int) deparameterizes to W_ (the all-default clone of W). - // Uses the user4p link set at line ~1658 when defaultsResolved is true. - static bool classTypeMatchesDefaultClone(const AstNodeDType* exprp, - const AstNodeDType* origp) { + // Check if exprp's class matches origp's class after deparameterization. + // Handles both the simple case (user4p link from defaultsResolved) and the + // nested case where the default's inner class has non-default sub-parameters + // (e.g., uvm_sequence#(uvm_reg_item) where uvm_reg_item != default uvm_sequence_item). + bool classTypeMatchesDefaultClone(const AstNodeDType* exprp, const AstNodeDType* origp) { exprp = exprp->skipRefp(); origp = origp->skipRefp(); const auto* const exprClassRefp = VN_CAST(exprp, ClassRefDType); const auto* const origClassRefp = VN_CAST(origp, ClassRefDType); - UINFO(9, "classTypeMatchesDefaultClone: exprClassRef=" - << exprClassRefp << " origClassRef=" << origClassRefp); if (!exprClassRefp || !origClassRefp) return false; + // Fast path: check user4p link (set when template was deparameterized with defaults) const AstNodeModule* const defaultClonep = VN_CAST(origClassRefp->classp()->user4p(), Class); - const bool result = defaultClonep && defaultClonep == exprClassRefp->classp(); - UINFO(9, " origClass=" << origClassRefp->classp()->prettyNameQ() - << " origClassp=" << cvtToHex(origClassRefp->classp()) - << " user4p=" << (defaultClonep ? cvtToHex(defaultClonep) : "null") - << " exprClass=" << exprClassRefp->classp()->prettyNameQ() - << " exprClassp=" << cvtToHex(exprClassRefp->classp()) - << " result=" << result); - return result; + if (defaultClonep && defaultClonep == exprClassRefp->classp()) return true; + // Slow path: deparameterize the default type and compare the result. + if (!origClassRefp->classp()->hasGParam()) return false; + // const_cast safe: cloneTree doesn't modify the source + AstClassRefDType* const origClonep = static_cast( + const_cast(origClassRefp)->cloneTree(false)); + AstNodeModule* const resolvedModp = classRefDeparam(origClonep, origClassRefp->classp()); + const bool match = resolvedModp && VN_CAST(resolvedModp, Class) == exprClassRefp->classp(); + VL_DO_DANGLING(origClonep->deleteTree(), origClonep); + return match; } static bool paramConstsEqualAtMaxWidth(AstConst* exprp, AstConst* origp) { diff --git a/test_regress/t/t_uvm_typeof_type.py b/test_regress/t/t_uvm_typeof_type.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_uvm_typeof_type.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"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_uvm_typeof_type.v b/test_regress/t/t_uvm_typeof_type.v new file mode 100644 index 000000000..076275d2d --- /dev/null +++ b/test_regress/t/t_uvm_typeof_type.v @@ -0,0 +1,94 @@ +// DESCRIPTION: Verilator: Verify that nested class type parameters with +// explicit-equivalent defaults resolve to the same specialization. +// +// When uvm_reg_sequence#(type BASE = uvm_sequence#(uvm_reg_item)) is +// extended with the explicit equivalent default, both must produce the +// same specialization so that $cast succeeds between them. +// +// 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 + +virtual class uvm_object; +endclass + +class uvm_factory; + uvm_object m_type_names[string]; + + function uvm_object create_object_by_name(string name); + uvm_object wrapper; + if (m_type_names.exists(name)) begin + wrapper = m_type_names[name]; + end + else begin + $display("%%Error: object not found '%s'", name); + $stop; + end + return wrapper; + endfunction + + function void register(uvm_object obj, string name); + m_type_names[name] = obj; + endfunction +endclass + +uvm_factory factory; + +class uvm_sequence_item extends uvm_object; +endclass + +class uvm_reg_item extends uvm_sequence_item; +endclass + +virtual class uvm_sequence #( + type REQ = uvm_sequence_item, + type RSP = REQ +) extends uvm_object; +endclass + +class uvm_reg_sequence #( + type BASE = uvm_sequence#(uvm_reg_item) +) extends BASE; + function new; + factory.register(this, "uvm_reg_sequence"); + endfunction +endclass + +class uvm_reg_hw_reset_seq extends uvm_reg_sequence #(uvm_sequence #(uvm_reg_item)); + function new; + factory.register(this, "uvm_reg_hw_reset_seq"); + endfunction +endclass + +module t; + initial begin + uvm_reg_hw_reset_seq rsq; + + uvm_reg_sequence seq; + uvm_object obj; + int cst; + string seq_name; + + factory = new; + rsq = new; + + seq_name = "uvm_reg_hw_reset_seq"; + + obj = factory.create_object_by_name(seq_name); + if (obj == null) $stop; + + cst = $cast(seq, obj); + /* verilator lint_off WIDTHTRUNC */ + if (!cst || seq == null) begin + $display("%%Error: cast failed"); + $stop; + end + /* verilator lint_on WIDTHTRUNC */ + if (seq != rsq) $stop; + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule From 141fe8bdadcc274e9d92e487efcd5c6322f11e62 Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Wed, 8 Apr 2026 09:31:54 +0200 Subject: [PATCH 3/4] Support sequence intersect operator (#7374) --- src/V3AssertPre.cpp | 3 +- src/V3AssertProp.cpp | 80 +++++++++++++++++- src/V3AstNodeExpr.h | 24 ++++++ src/V3Dfg.cpp | 8 +- src/V3DfgCse.cpp | 2 + src/V3Width.cpp | 3 +- src/verilog.y | 2 +- test_regress/t/t_sequence_intersect.py | 18 ++++ test_regress/t/t_sequence_intersect.v | 82 +++++++++++++++++++ .../t/t_sequence_intersect_len_warn.out | 13 +++ .../t/t_sequence_intersect_len_warn.py | 18 ++++ .../t/t_sequence_intersect_len_warn.v | 22 +++++ .../t/t_sequence_intersect_range_unsup.out | 6 ++ .../t/t_sequence_intersect_range_unsup.py | 18 ++++ .../t/t_sequence_intersect_range_unsup.v | 14 ++++ .../t/t_sequence_nonconst_delay_unsup.out | 2 +- test_regress/t/t_sequence_sexpr_unsup.out | 3 - 17 files changed, 306 insertions(+), 12 deletions(-) create mode 100755 test_regress/t/t_sequence_intersect.py create mode 100644 test_regress/t/t_sequence_intersect.v create mode 100644 test_regress/t/t_sequence_intersect_len_warn.out create mode 100755 test_regress/t/t_sequence_intersect_len_warn.py create mode 100644 test_regress/t/t_sequence_intersect_len_warn.v create mode 100644 test_regress/t/t_sequence_intersect_range_unsup.out create mode 100755 test_regress/t/t_sequence_intersect_range_unsup.py create mode 100644 test_regress/t/t_sequence_intersect_range_unsup.v diff --git a/src/V3AssertPre.cpp b/src/V3AssertPre.cpp index 6bed69be2..79769bd07 100644 --- a/src/V3AssertPre.cpp +++ b/src/V3AssertPre.cpp @@ -745,7 +745,7 @@ private: AstSenTree* const senTreep = newSenTree(nodep); AstAlways* const alwaysp = new AstAlways{flp, VAlwaysKwd::ALWAYS, senTreep, ifp}; cntVarp->addNextHere(alwaysp); - // Match when N-1 previous cycles were true and current cycle is true + // Match: cnt >= N-1 (previous cycles via NBA) && expr (current cycle) AstNodeExpr* const cntCheckp = new AstGte{flp, new AstVarRef{flp, cntVarp, VAccess::READ}, new AstConst{flp, static_cast(n - 1)}}; cntCheckp->dtypeSetBit(); @@ -852,6 +852,7 @@ private: AstVar* const cntVarp = new AstVar{flp, VVarType::BLOCKTEMP, name + "__counter", exprp->findBasicDType(VBasicDTypeKwd::UINT32)}; cntVarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); + cntVarp->noSample(true); AstBegin* const beginp = new AstBegin{flp, name + "__block", cntVarp, true}; beginp->addStmtsp( diff --git a/src/V3AssertProp.cpp b/src/V3AssertProp.cpp index 183afdfc4..a36cc8004 100644 --- a/src/V3AssertProp.cpp +++ b/src/V3AssertProp.cpp @@ -136,8 +136,9 @@ static std::vector extractTimeline(AstNodeExpr* nodep) { if (AstConst* const constp = VN_CAST(dlyp->lhsp(), Const)) { cycles = constp->toSInt(); } else { - dlyp->lhsp()->v3warn(E_UNSUPPORTED, - "Unsupported: non-constant cycle delay in sequence and/or"); + dlyp->lhsp()->v3warn( + E_UNSUPPORTED, + "Unsupported: non-constant cycle delay in sequence and/or/intersect"); } } // The expression after the delay @@ -158,6 +159,18 @@ static std::vector extractTimeline(AstNodeExpr* nodep) { return timeline; } +// True if any AstSExpr in the subtree has a range delay (##[m:n]). +// Uses forall(): predicate returns false when a range delay is found, +// so !forall(...) means "at least one range delay exists". +static bool subtreeHasRangeDelay(const AstNode* nodep) { + return !nodep->forall([](const AstSExpr* sexprp) { + if (const AstDelay* const dlyp = VN_CAST(sexprp->delayp(), Delay)) { + if (dlyp->isRangeDelay()) return false; + } + return true; + }); +} + // Lower sequence and/or to AST class AssertPropLowerVisitor final : public VNVisitor { // STATE @@ -396,6 +409,47 @@ class AssertPropLowerVisitor final : public VNVisitor { VL_DO_DANGLING(nodep->deleteTree(), nodep); } + // Lower a multi-cycle sequence 'intersect' (IEEE 1800-2023 16.9.6). + // intersect = and + equal-length constraint: both operands must match with the same duration. + // When total delays are equal constants, this is identical to 'and'; otherwise constant-false. + void lowerSeqIntersect(AstNodeBiop* nodep) { + const std::vector lhsTimeline = extractTimeline(nodep->lhsp()); + const std::vector rhsTimeline = extractTimeline(nodep->rhsp()); + int lhsTotal = 0; + for (const auto& step : lhsTimeline) lhsTotal += step.delayCycles; + int rhsTotal = 0; + for (const auto& step : rhsTimeline) rhsTotal += step.delayCycles; + if (lhsTotal != rhsTotal) { + // Lengths differ: per IEEE 16.9.6, the match set is empty -- constant-false. + // Warn the user; mismatched lengths are almost always a mistake. + // Skip when either operand had a range delay: RangeDelayExpander already + // replaced it with an FSM expression (containsSExpr returns false) and + // issued UNSUPPORTED, so no second diagnostic is needed. + if (containsSExpr(nodep->lhsp()) && containsSExpr(nodep->rhsp())) { + if (lhsTotal > rhsTotal) { + nodep->v3warn(WIDTHEXPAND, "Intersect sequence length mismatch" + " (left " + << lhsTotal << " cycles, right " << rhsTotal + << " cycles) -- intersection is always empty"); + } else { + nodep->v3warn(WIDTHTRUNC, "Intersect sequence length mismatch" + " (left " + << lhsTotal << " cycles, right " << rhsTotal + << " cycles) -- intersection is always empty"); + } + } + FileLine* const flp = nodep->fileline(); + AstBegin* const bodyp = new AstBegin{flp, "", nullptr, true}; + bodyp->addStmtsp(new AstPExprClause{flp, false}); + AstPExpr* const pexprp = new AstPExpr{flp, bodyp, nodep->dtypep()}; + nodep->replaceWith(pexprp); + VL_DO_DANGLING(nodep->deleteTree(), nodep); + return; + } + // Same length: length restriction is trivially satisfied, lower as 'and'. + lowerSeqAnd(nodep); + } + void visit(AstSAnd* nodep) override { iterateChildren(nodep); if (containsSExpr(nodep->lhsp()) || containsSExpr(nodep->rhsp())) { @@ -409,6 +463,19 @@ class AssertPropLowerVisitor final : public VNVisitor { VL_DO_DANGLING(nodep->deleteTree(), nodep); } } + void visit(AstSIntersect* nodep) override { + iterateChildren(nodep); + if (containsSExpr(nodep->lhsp()) || containsSExpr(nodep->rhsp())) { + lowerSeqIntersect(nodep); + } else { + // Pure boolean operands: length is always 0, lower to LogAnd + AstLogAnd* const newp = new AstLogAnd{nodep->fileline(), nodep->lhsp()->unlinkFrBack(), + nodep->rhsp()->unlinkFrBack()}; + newp->dtypeFrom(nodep); + nodep->replaceWith(newp); + VL_DO_DANGLING(nodep->deleteTree(), nodep); + } + } void visit(AstSOr* nodep) override { iterateChildren(nodep); if (containsSExpr(nodep->lhsp()) || containsSExpr(nodep->rhsp())) { @@ -1099,6 +1166,15 @@ class RangeDelayExpander final : public VNVisitor { } } + void visit(AstSIntersect* nodep) override { + // intersect with a range-delay operand cannot be lowered: the length-pairing + // logic requires knowing each operand's concrete length, which is dynamic. + if (subtreeHasRangeDelay(nodep->lhsp()) || subtreeHasRangeDelay(nodep->rhsp())) { + nodep->v3warn(E_UNSUPPORTED, "Unsupported: intersect with ranged cycle-delay operand"); + } + iterateChildren(nodep); + } + void visit(AstSThroughout* nodep) override { // Reject throughout with range-delay sequences before FSM expansion // would silently lose per-tick enforcement (IEEE 1800-2023 16.9.9) diff --git a/src/V3AstNodeExpr.h b/src/V3AstNodeExpr.h index 7949ebf6e..3a4ebf035 100644 --- a/src/V3AstNodeExpr.h +++ b/src/V3AstNodeExpr.h @@ -3670,6 +3670,30 @@ public: bool sizeMattersRhs() const override { return false; } int instrCount() const override { return widthInstrs() + INSTR_COUNT_BRANCH; } }; +class AstSIntersect final : public AstNodeBiop { + // Sequence 'intersect' (IEEE 1800-2023 16.9.6): both operands match with equal length. + // AND with a length restriction. For boolean operands, lowered to AstLogAnd. +public: + AstSIntersect(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) + : ASTGEN_SUPER_SIntersect(fl, lhsp, rhsp) { + dtypeSetBit(); + } + ASTGEN_MEMBERS_AstSIntersect; + // LCOV_EXCL_START // Lowered before these are ever called + void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs) override { + out.opLogAnd(lhs, rhs); + } + string emitVerilog() override { return "%k(%l %fintersect %r)"; } + string emitC() override { V3ERROR_NA_RETURN(""); } + string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); } + bool cleanOut() const override { return true; } + bool cleanLhs() const override { return true; } + bool cleanRhs() const override { return true; } + bool sizeMattersLhs() const override { return false; } + bool sizeMattersRhs() const override { return false; } + int instrCount() const override { return widthInstrs() + INSTR_COUNT_BRANCH; } + // LCOV_EXCL_STOP +}; class AstSOr final : public AstNodeBiop { // Sequence 'or' (IEEE 1800-2023 16.9.7): at least one operand sequence must match. // Operates on match sets, not values. For boolean operands, lowered to AstLogOr. diff --git a/src/V3Dfg.cpp b/src/V3Dfg.cpp index 36e24baf3..1aadcb37a 100644 --- a/src/V3Dfg.cpp +++ b/src/V3Dfg.cpp @@ -781,11 +781,13 @@ void DfgVertex::typeCheck(const DfgGraph& dfg) const { } case VDfgType::SAnd: + case VDfgType::SIntersect: case VDfgType::SOr: case VDfgType::SThroughout: { - UASSERT_OBJ(false, this, - "SAnd/SOr/SThroughout should be removed before DFG"); // LCOV_EXCL_LINE - return; // LCOV_EXCL_LINE + // LCOV_EXCL_START // Lowered before DFG + UASSERT_OBJ(false, this, "SAnd/SIntersect/SOr/SThroughout should be removed before DFG"); + return; + // LCOV_EXCL_STOP } case VDfgType::LogAnd: diff --git a/src/V3DfgCse.cpp b/src/V3DfgCse.cpp index 71fef6625..7f8d5898f 100644 --- a/src/V3DfgCse.cpp +++ b/src/V3DfgCse.cpp @@ -126,6 +126,7 @@ class V3DfgCse final { case VDfgType::StreamL: case VDfgType::StreamR: case VDfgType::SAnd: + case VDfgType::SIntersect: case VDfgType::SOr: case VDfgType::SThroughout: case VDfgType::Sub: @@ -251,6 +252,7 @@ class V3DfgCse final { case VDfgType::ShiftRS: case VDfgType::StreamL: case VDfgType::SAnd: + case VDfgType::SIntersect: case VDfgType::SOr: case VDfgType::SThroughout: case VDfgType::StreamR: diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 5fb16555a..35f2e2b6c 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -294,8 +294,9 @@ class WidthVisitor final : public VNVisitor { // Widths: 1 bit out, lhs 1 bit, rhs 1 bit; Real: converts via compare with 0 void visit(AstLogAnd* nodep) override { visit_log_and_or(nodep); } void visit(AstLogOr* nodep) override { visit_log_and_or(nodep); } - // Sequence and/or (IEEE 1800-2023 16.9.2), same width rules as LogAnd/LogOr + // Sequence and/or/intersect (IEEE 1800-2023 16.9.2), same width rules as LogAnd/LogOr void visit(AstSAnd* nodep) override { visit_log_and_or(nodep); } + void visit(AstSIntersect* nodep) override { visit_log_and_or(nodep); } void visit(AstSOr* nodep) override { visit_log_and_or(nodep); } void visit(AstLogEq* nodep) override { // Conversion from real not in IEEE, but a fallout diff --git a/src/verilog.y b/src/verilog.y index 8641c3d62..6b6a453e6 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -6849,7 +6849,7 @@ sexpr: // ==IEEE: sequence_expr (The name sexpr is important as reg { $$ = new AstSOr{$2, $1, $3}; } // // Intersect always has an sexpr rhs | ~p~sexpr yINTERSECT sexpr - { $$ = $1; BBUNSUP($2, "Unsupported: intersect (in sequence expression)"); DEL($3); } + { $$ = new AstSIntersect{$2, $1, $3}; } // | yFIRST_MATCH '(' sexpr ')' { $$ = $3; BBUNSUP($1, "Unsupported: first_match (in sequence expression)"); } diff --git a/test_regress/t/t_sequence_intersect.py b/test_regress/t/t_sequence_intersect.py new file mode 100755 index 000000000..ddef50cab --- /dev/null +++ b/test_regress/t/t_sequence_intersect.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=['--assert --timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_sequence_intersect.v b/test_regress/t/t_sequence_intersect.v new file mode 100644 index 000000000..b9347bf16 --- /dev/null +++ b/test_regress/t/t_sequence_intersect.v @@ -0,0 +1,82 @@ +// 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 checkh(gotv, expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t ( + input clk +); + integer cyc = 0; + reg [63:0] crc = '0; + reg [63:0] sum = '0; + + // Derive test signals from CRC + wire a = crc[0]; + wire b = crc[1]; + wire c = crc[2]; + wire d = crc[3]; + + wire [63:0] result = {60'h0, d, c, b, a}; + + always_ff @(posedge clk) begin +`ifdef TEST_VERBOSE + $write("[%0t] cyc==%0d crc=%x a=%b b=%b c=%b d=%b\n", + $time, cyc, crc, a, b, c, d); +`endif + cyc <= cyc + 1; + crc <= {crc[62:0], crc[63] ^ crc[2] ^ crc[0]}; + sum <= result ^ {sum[62:0], sum[63] ^ sum[2] ^ sum[0]}; + + if (cyc == 0) begin + crc <= 64'h5aef0c8d_d70a4497; + sum <= '0; + end else if (cyc < 10) begin + sum <= '0; + end else if (cyc == 99) begin + `checkh(crc, 64'hc77bb9b3784ea091); + `checkh(sum, 64'hdb7bc8bfe61f987e); + $write("*-* All Finished *-*\n"); + $finish; + end + end + + // ========================================================================= + // Boolean intersect (length-0): equivalent to boolean AND (IEEE 16.9.6) + // ========================================================================= + + // Boolean intersect: when a & b, intersect succeeds (equivalent to AND) + assert property (@(posedge clk) disable iff (cyc < 2) + (a & b) |-> (a intersect b)); + + // Boolean intersect with constant true -- reduces to just 'a' + assert property (@(posedge clk) disable iff (cyc < 2) + a |-> (a intersect 1'b1)); + + // ========================================================================= + // Multi-cycle sequence intersect (IEEE 1800-2023 16.9.6) + // Same-length sequences: intersect succeeds when both arms complete + // ========================================================================= + + // Both arms have length 1; 1'b1 guarantees completion on both sides + assert property (@(posedge clk) + (a & b) |-> (a ##1 1'b1) intersect (b ##1 1'b1)); + + // Both arms have length 2 + assert property (@(posedge clk) + (a & b) |-> (a ##2 1'b1) intersect (b ##2 1'b1)); + + // Different internal structure, same total length (2 cycles each) + assert property (@(posedge clk) + (a & b) |-> (a ##1 1'b1 ##1 1'b1) intersect (b ##2 1'b1)); + + // Standalone constant intersect (always passes) + assert property (@(posedge clk) + (1'b1 ##1 1'b1) intersect (1'b1 ##1 1'b1)); + +endmodule diff --git a/test_regress/t/t_sequence_intersect_len_warn.out b/test_regress/t/t_sequence_intersect_len_warn.out new file mode 100644 index 000000000..18b0f88fe --- /dev/null +++ b/test_regress/t/t_sequence_intersect_len_warn.out @@ -0,0 +1,13 @@ +%Warning-WIDTHTRUNC: t/t_sequence_intersect_len_warn.v:16:17: Intersect sequence length mismatch (left 1 cycles, right 3 cycles) -- intersection is always empty + : ... note: In instance 't' + 16 | (a ##1 b) intersect (c ##3 d)); + | ^~~~~~~~~ + ... For warning description see https://verilator.org/warn/WIDTHTRUNC?v=latest + ... Use "/* verilator lint_off WIDTHTRUNC */" and lint_on around source to disable this message. +%Warning-WIDTHEXPAND: t/t_sequence_intersect_len_warn.v:20:17: Intersect sequence length mismatch (left 3 cycles, right 1 cycles) -- intersection is always empty + : ... note: In instance 't' + 20 | (a ##3 b) intersect (c ##1 d)); + | ^~~~~~~~~ + ... For warning description see https://verilator.org/warn/WIDTHEXPAND?v=latest + ... Use "/* verilator lint_off WIDTHEXPAND */" and lint_on around source to disable this message. +%Error: Exiting due to diff --git a/test_regress/t/t_sequence_intersect_len_warn.py b/test_regress/t/t_sequence_intersect_len_warn.py new file mode 100755 index 000000000..58fc7aeba --- /dev/null +++ b/test_regress/t/t_sequence_intersect_len_warn.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('vlt_all') + +test.compile(verilator_flags2=['--assert', '--timing', '--lint-only'], + fails=True, + expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_sequence_intersect_len_warn.v b/test_regress/t/t_sequence_intersect_len_warn.v new file mode 100644 index 000000000..e544c8926 --- /dev/null +++ b/test_regress/t/t_sequence_intersect_len_warn.v @@ -0,0 +1,22 @@ +// 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 +// verilog_lint: off +// verilog_format: on + +module t (input clk); + logic a, b, c, d; + + // LHS length 2, RHS length 4 -- WIDTHTRUNC (left < right) + assert property (@(posedge clk) + (a ##1 b) intersect (c ##3 d)); + + // LHS length 4, RHS length 2 -- WIDTHEXPAND (left > right) + assert property (@(posedge clk) + (a ##3 b) intersect (c ##1 d)); + +endmodule diff --git a/test_regress/t/t_sequence_intersect_range_unsup.out b/test_regress/t/t_sequence_intersect_range_unsup.out new file mode 100644 index 000000000..5e4af72b9 --- /dev/null +++ b/test_regress/t/t_sequence_intersect_range_unsup.out @@ -0,0 +1,6 @@ +%Error-UNSUPPORTED: t/t_sequence_intersect_range_unsup.v:12:21: Unsupported: intersect with ranged cycle-delay operand + : ... note: In instance 't' + 12 | (a ##[1:5] b) intersect (c ##2 d)); + | ^~~~~~~~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_sequence_intersect_range_unsup.py b/test_regress/t/t_sequence_intersect_range_unsup.py new file mode 100755 index 000000000..f83bbb887 --- /dev/null +++ b/test_regress/t/t_sequence_intersect_range_unsup.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('linter') + +test.compile(verilator_flags2=['--assert --timing'], + fails=True, + expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_sequence_intersect_range_unsup.v b/test_regress/t/t_sequence_intersect_range_unsup.v new file mode 100644 index 000000000..837f9b146 --- /dev/null +++ b/test_regress/t/t_sequence_intersect_range_unsup.v @@ -0,0 +1,14 @@ +// 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 + +module t (input clk); + logic a, b, c, d; + + // Range delay in intersect operand is unsupported + assert property (@(posedge clk) + (a ##[1:5] b) intersect (c ##2 d)); + +endmodule diff --git a/test_regress/t/t_sequence_nonconst_delay_unsup.out b/test_regress/t/t_sequence_nonconst_delay_unsup.out index 8d1555846..9f10d0e7b 100644 --- a/test_regress/t/t_sequence_nonconst_delay_unsup.out +++ b/test_regress/t/t_sequence_nonconst_delay_unsup.out @@ -1,4 +1,4 @@ -%Error-UNSUPPORTED: t/t_sequence_nonconst_delay_unsup.v:14:40: Unsupported: non-constant cycle delay in sequence and/or +%Error-UNSUPPORTED: t/t_sequence_nonconst_delay_unsup.v:14:40: Unsupported: non-constant cycle delay in sequence and/or/intersect : ... note: In instance 't' 14 | assert property (@(posedge clk) (a ##delay b) and (c ##1 d)); | ^~~~~ diff --git a/test_regress/t/t_sequence_sexpr_unsup.out b/test_regress/t/t_sequence_sexpr_unsup.out index 351fbc36c..8c43cda59 100644 --- a/test_regress/t/t_sequence_sexpr_unsup.out +++ b/test_regress/t/t_sequence_sexpr_unsup.out @@ -2,9 +2,6 @@ 34 | a within(b); | ^~~~~~ ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_sequence_sexpr_unsup.v:50:7: Unsupported: intersect (in sequence expression) - 50 | a intersect b; - | ^~~~~~~~~ %Error-UNSUPPORTED: t/t_sequence_sexpr_unsup.v:89:7: Unsupported: [*] boolean abbrev expression 89 | a [*]; | ^~ From 7dcf58680700616ea705fe12d1f5df020635d518 Mon Sep 17 00:00:00 2001 From: Ryszard Rozak Date: Wed, 8 Apr 2026 13:09:25 +0200 Subject: [PATCH 4/4] Fix sampling of hierarchical references (#7386) --- src/V3Assert.cpp | 5 ++--- test_regress/t/t_property_disable_iff_midreset.v | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/V3Assert.cpp b/src/V3Assert.cpp index 87cd14f5b..2adda9d22 100644 --- a/src/V3Assert.cpp +++ b/src/V3Assert.cpp @@ -709,9 +709,8 @@ class AssertVisitor final : public VNVisitor { } VL_DO_DANGLING(pushDeletep(nodep), nodep); } - void visit(AstVarRef* nodep) override { - iterateChildren(nodep); - if (m_inSampled && !(nodep->varp() && nodep->varp()->noSample())) { + void visit(AstNodeVarRef* nodep) override { + if (m_inSampled && !nodep->varp()->noSample()) { if (!nodep->access().isReadOnly()) { nodep->v3warn(E_UNSUPPORTED, "Unsupported: Write to variable in sampled expression"); diff --git a/test_regress/t/t_property_disable_iff_midreset.v b/test_regress/t/t_property_disable_iff_midreset.v index e82e9ea46..c7972ee88 100644 --- a/test_regress/t/t_property_disable_iff_midreset.v +++ b/test_regress/t/t_property_disable_iff_midreset.v @@ -24,7 +24,7 @@ module t ( int fails_b = 0; // First launch at cyc==2 should be canceled by reset pulse in the middle. - assert property (@(posedge clk) disable iff (rst) (cyc == 2) |-> ##2 done) + assert property (@(posedge clk) disable iff (rst) (t.cyc == 2) |-> ##2 done) else fails_a++; // Second launch at cyc==8 has no reset pulse in flight and should fail once.