From ffceba108b8cf5c1530f992ac17849854df7eaa9 Mon Sep 17 00:00:00 2001 From: Ethan Sifferman Date: Mon, 2 Feb 2026 12:35:50 -0800 Subject: [PATCH] fixed nba --- src/V3Delayed.cpp | 82 ++++++++++++++++++++++---------- test_regress/t/t_timing_nba_3.py | 18 +++++++ test_regress/t/t_timing_nba_3.v | 44 +++++++++++++++++ 3 files changed, 118 insertions(+), 26 deletions(-) create mode 100644 test_regress/t/t_timing_nba_3.py create mode 100644 test_regress/t/t_timing_nba_3.v diff --git a/src/V3Delayed.cpp b/src/V3Delayed.cpp index 209218b8e..b4a47179b 100644 --- a/src/V3Delayed.cpp +++ b/src/V3Delayed.cpp @@ -177,6 +177,8 @@ class DelayedVisitor final : public VNVisitor { } m_flagSharedKit; struct { // Stuff needed for Scheme::FlagUnique AstAlwaysPost* postp; // The post block for commiting results + AstVarScope* commitFlagp; // The commit flag variable + AstVarScope* commitValp; // The commit value variable } m_flagUniqueKit; struct { // Stuff needed for Scheme::ValueQueueWhole/Scheme::ValueQueuePartial AstVarScope* vscp; // The commit queue variable @@ -791,42 +793,70 @@ class DelayedVisitor final : public VNVisitor { AstActive* const activep = new AstActive{flp, "nba-flag-unique", vscpInfo.senTreep()}; activep->senTreeStorep(vscpInfo.senTreep()); scopep->addBlocksp(activep); - // Add 'Post' scheduled process to be populated later + // Add 'Post' scheduled process AstAlwaysPost* const postp = new AstAlwaysPost{flp}; activep->addStmtsp(postp); vscpInfo.flagUniqueKit().postp = postp; + + // Create a flag variable to track whether NBA occurred + const std::string flagName = "__VdlySet__" + vscp->varp()->shortName(); + AstVarScope* const commitFlagp = createTemp(flp, scopep, flagName, 1); + commitFlagp->varp()->setIgnorePostWrite(); + vscpInfo.flagUniqueKit().commitFlagp = commitFlagp; + // Create a value variable to track the final value after a NBA + const std::string valName = "__VdlyVal__" + vscp->varp()->shortName(); + AstVarScope* const commitValp = createTemp(flp, scopep, valName, vscp->dtypep()); + vscpInfo.flagUniqueKit().commitValp = commitValp; + + // NBA 'Post' block: if (__VdlySet) { __VdlySet = 0; var = __VdlyVal; } + // This runs after all NBAs in the time step, applying the last captured value + AstIf* const ifp = new AstIf{flp, new AstVarRef{flp, commitFlagp, VAccess::READ}}; + postp->addStmtsp(ifp); + ifp->addThensp(new AstAssign{flp, new AstVarRef{flp, commitFlagp, VAccess::WRITE}, + new AstConst{flp, AstConst::BitFalse{}}}); + ifp->addThensp(new AstAssign{flp, new AstVarRef{flp, vscp, VAccess::WRITE}, + new AstVarRef{flp, commitValp, VAccess::READ}}); } void convertSchemeFlagUnique(AstAssignDly* nodep, AstVarScope* vscp, VarScopeInfo& vscpInfo) { UASSERT_OBJ(vscpInfo.m_scheme == Scheme::FlagUnique, vscp, "Inconsistent NBA scheme"); - FileLine* const flp = vscp->fileline(); - AstScope* const scopep = VN_AS(nodep->user2p(), Scope); + FileLine* const flp = nodep->fileline(); - // Base name suffix for signals constructed below - const std::string baseName = uniqueTmpName(scopep, vscp, vscpInfo); + AstVarScope* const commitFlagp = vscpInfo.flagUniqueKit().commitFlagp; + AstVarScope* const commitValp = vscpInfo.flagUniqueKit().commitValp; - // Unlink and capture the RHS value - AstNodeExpr* const capturedRhsp - = captureVal(scopep, nodep, nodep->rhsp()->unlinkFrBack(), "__VdlyVal" + baseName); + // Whole-variable update: use shared flag/value so last NBA wins + if (VN_IS(nodep->lhsp(), VarRef)) { + // Capture the RHS value to be applied at 'Post' time + nodep->addHereThisAsNext(new AstAssign{flp, + new AstVarRef{flp, commitValp, VAccess::WRITE}, + nodep->rhsp()->unlinkFrBack()}); + // Set the flag to indicate this NBA needs to be applied + nodep->addHereThisAsNext(new AstAssign{flp, + new AstVarRef{flp, commitFlagp, VAccess::WRITE}, + new AstConst{flp, AstConst::BitTrue{}}}); + } else { + // Partial update: different bits/indices may be written, so each needs its own flag + AstScope* const scopep = VN_AS(nodep->user2p(), Scope); + const std::string baseName = uniqueTmpName(scopep, vscp, vscpInfo); + AstNodeExpr* const capturedRhsp + = captureVal(scopep, nodep, nodep->rhsp()->unlinkFrBack(), "__VdlyVal" + baseName); + AstNodeExpr* const capturedLhsp + = captureLhs(scopep, nodep, nodep->lhsp()->unlinkFrBack(), baseName); + AstVarScope* const uniqueFlagVscp = createTemp(flp, scopep, "__VdlySet" + baseName, 1); + uniqueFlagVscp->varp()->setIgnorePostWrite(); - // Unlink and capture the LHS reference - AstNodeExpr* const capturedLhsp - = captureLhs(scopep, nodep, nodep->lhsp()->unlinkFrBack(), baseName); + // Set the flag to indicate this partial NBA needs to be applied + nodep->addHereThisAsNext( + new AstAssign{flp, new AstVarRef{flp, uniqueFlagVscp, VAccess::WRITE}, + new AstConst{flp, AstConst::BitTrue{}}}); - // Create new flag - AstVarScope* const flagVscp = createTemp(flp, scopep, "__VdlySet" + baseName, 1); - flagVscp->varp()->setIgnorePostWrite(); - // Set the flag at the original NBA - nodep->addHereThisAsNext( // - new AstAssign{flp, new AstVarRef{flp, flagVscp, VAccess::WRITE}, - new AstConst{flp, AstConst::BitTrue{}}}); - // Add the 'Post' scheduled commit - AstIf* const ifp = new AstIf{flp, new AstVarRef{flp, flagVscp, VAccess::READ}}; - vscpInfo.flagUniqueKit().postp->addStmtsp(ifp); - // Immediately clear the flag - ifp->addThensp(new AstAssign{flp, new AstVarRef{flp, flagVscp, VAccess::WRITE}, - new AstConst{flp, AstConst::BitFalse{}}}); - // Commit the value - ifp->addThensp(new AstAssign{flp, capturedLhsp, capturedRhsp}); + // NBA 'Post' block: if (__VdlySet) { __VdlySet = 0; var[idx] = __VdlyVal; } + AstIf* const ifp = new AstIf{flp, new AstVarRef{flp, uniqueFlagVscp, VAccess::READ}}; + vscpInfo.flagUniqueKit().postp->addStmtsp(ifp); + ifp->addThensp(new AstAssign{flp, new AstVarRef{flp, uniqueFlagVscp, VAccess::WRITE}, + new AstConst{flp, AstConst::BitFalse{}}}); + ifp->addThensp(new AstAssign{flp, capturedLhsp, capturedRhsp}); + } // Delete original NBA pushDeletep(nodep->unlinkFrBack()); diff --git a/test_regress/t/t_timing_nba_3.py b/test_regress/t/t_timing_nba_3.py new file mode 100644 index 000000000..7da1fcea5 --- /dev/null +++ b/test_regress/t/t_timing_nba_3.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: 2024 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=["--binary", "--assert"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_timing_nba_3.v b/test_regress/t/t_timing_nba_3.v new file mode 100644 index 000000000..259d6737b --- /dev/null +++ b/test_regress/t/t_timing_nba_3.v @@ -0,0 +1,44 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Ethan Sifferman +// SPDX-License-Identifier: CC0-1.0 + +module t; + +int delay = 0; always #1 delay = int'($time) / 4; +task automatic do_delay; + if (delay > 0) #(delay); +endtask + +// `a` should match `b` +logic a = 0; +always begin + a <= 1; + do_delay(); + a <= 0; + + #1; +end + +logic b = 0; +always begin + b <= 1; + do_delay(); + b <= 0; + + #1; + b <= 1; // this line should do nothing +end + +always #1 assert (a == b); + +initial begin + $dumpfile("dump.vcd"); + $dumpvars; + #20; + $write("*-* All Finished *-*\n"); + $finish; +end + +endmodule