From 3c027baa4449064eb7e7ea6cdfc7dc76ba7f0f90 Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Mon, 24 Aug 2026 17:09:02 +0200 Subject: [PATCH] Fix $past in final blocks on a sampling-tick finish (#8153) --- src/V3Assert.cpp | 56 +++++++++++++++++-- test_regress/t/t_case_call_count.py | 4 +- test_regress/t/t_case_call_count.v | 18 ++++++ test_regress/t/t_property_nfa_final_past.py | 2 +- test_regress/t/t_property_nfa_final_past.v | 34 ++++++++--- .../t/t_property_nfa_stop_error_limit.v | 2 +- 6 files changed, 99 insertions(+), 17 deletions(-) diff --git a/src/V3Assert.cpp b/src/V3Assert.cpp index 0edf79650..d441a8f28 100644 --- a/src/V3Assert.cpp +++ b/src/V3Assert.cpp @@ -292,6 +292,8 @@ class AssertVisitor final : public VNVisitor { // Map from (expression, senTree) to AstAlways that computes delayed values of the expression std::unordered_map, std::unordered_map, AstAlways*>> m_modExpr2Sen2DelayedAlwaysp; + // Map from delayed-value AstAlways to its last-sampling-tick time variable + std::unordered_map m_delayedAlways2TickTimep; // METHODS static string assertCtlGetCall(const char* query, VAssertType type, @@ -537,23 +539,43 @@ class AssertVisitor final : public VNVisitor { AstNodeExpr* getPastValue(AstNodeExpr* exprp, AstSenTree* senTreep, uint32_t ticks) { UASSERT_OBJ(ticks > 0, exprp, "Delay must be > 0"); - AstAlways* const alwaysp = getDelayedAlways(exprp, senTreep); + FileLine* const flp = exprp->fileline(); + return pastValueRef(getDelayedAlways(exprp, senTreep), flp, ticks); + } + + AstNodeExpr* pastValueRef(AstAlways* alwaysp, FileLine* flp, uint32_t ticks) { std::vector& delayedr = m_delayed(alwaysp); // Ensure the required delay exists while (delayedr.size() < ticks) { AstVar* const firstp = delayedr.front(); - FileLine* const flp = firstp->fileline(); + FileLine* const varFlp = firstp->fileline(); // Create once more delayed value std::string name = firstp->name(); name.resize(name.size() - 1); name += std::to_string(delayedr.size() + 1); - AstNodeExpr* const prevp = new AstVarRef{flp, delayedr.back(), VAccess::READ}; + AstNodeExpr* const prevp = new AstVarRef{varFlp, delayedr.back(), VAccess::READ}; AstVar* const varp = createDelayedVar(name, alwaysp, prevp); // Add it to delayed variable vector delayedr.emplace_back(varp); } // Return a reference to the appropriately delayed variable - return new AstVarRef{exprp->fileline(), delayedr.at(ticks - 1), VAccess::READ}; + return new AstVarRef{flp, delayedr.at(ticks - 1), VAccess::READ}; + } + + // Time of the pipeline's last sampling tick, for end-of-simulation readers + AstVar* getPastTickTimeVar(AstAlways* alwaysp) { + AstVar*& varpr = m_delayedAlways2TickTimep[alwaysp]; + if (!varpr) { + FileLine* const flp = alwaysp->fileline(); + varpr = new AstVar{flp, VVarType::MODULETEMP, + "_Vpast_" + cvtToStr(m_modPastNum++) + "_t", + m_modp->findUInt64DType()}; + m_modp->addStmtsp(varpr); + AstNodeExpr* const timep = new AstCExpr{flp, "vlSymsp->_vm_contextp__->time()", 64}; + alwaysp->addStmtsp( + new AstAssign{flp, new AstVarRef{flp, varpr, VAccess::WRITE}, timep}); + } + return varpr; } void visitAssertionIterate(AstNodeCoverOrAssert* nodep, AstNode* failsp) { @@ -975,7 +997,25 @@ class AssertVisitor final : public VNVisitor { } UASSERT_OBJ(ticks >= 1, nodep, "0 tick should have been checked in V3Width"); AstNodeExpr* const exprp = newSampledExpr(nodep->exprp()->unlinkFrBack()); - AstNodeExpr* inp = getPastValue(exprp, nodep->sentreep()->unlinkFrBack(), ticks); + AstSenTree* const senTreep = nodep->sentreep()->unlinkFrBack(); + AstNodeExpr* inp = nullptr; + // Deliberately emitted for every ftask $past; calls outside final take the normal stage + if (VN_IS(m_procedurep, Final) || m_ftaskp) { + // A sampling-tick finish reads one stage deeper (IEEE 1800-2023 16.9.3). + FileLine* const flp = nodep->fileline(); + AstAlways* const alwaysp = getDelayedAlways(exprp, senTreep); + AstNodeExpr* const betweenTicksp = pastValueRef(alwaysp, flp, ticks); + AstNodeExpr* const onTickp = pastValueRef(alwaysp, flp, ticks + 1); + AstNodeExpr* const onTickCondp = new AstLogAnd{ + flp, new AstCExpr{flp, "vlSymsp->_vm_contextp__->executingFinal()", 1}, + new AstEq{flp, new AstVarRef{flp, getPastTickTimeVar(alwaysp), VAccess::READ}, + new AstCExpr{flp, "vlSymsp->_vm_contextp__->finishPendingTime()", 64}}}; + AstCond* const condp = new AstCond{flp, onTickCondp, onTickp, betweenTicksp}; + condp->dtypeFrom(onTickp); + inp = condp; + } else { + inp = getPastValue(exprp, senTreep, ticks); + } nodep->replaceWith(inp); VL_DO_DANGLING(pushDeletep(nodep), nodep); } @@ -1213,6 +1253,7 @@ class AssertVisitor final : public VNVisitor { VL_RESTORER(m_modStrobeNum); VL_RESTORER(m_finalp); VL_RESTORER_CLEAR(m_modExpr2Sen2DelayedAlwaysp); + VL_RESTORER_CLEAR(m_delayedAlways2TickTimep); m_modp = nodep; m_modPastNum = 0; m_modStrobeNum = 0; @@ -1224,6 +1265,11 @@ class AssertVisitor final : public VNVisitor { m_procedurep = nodep; iterateChildren(nodep); } + void visit(AstNodeFTask* nodep) override { + VL_RESTORER(m_ftaskp); + m_ftaskp = nodep; + iterateChildren(nodep); + } void visit(AstGenBlock* nodep) override { // This code is needed rather than a visitor in V3Begin, // because V3Assert is called before V3Begin diff --git a/test_regress/t/t_case_call_count.py b/test_regress/t/t_case_call_count.py index bf14a54dd..bbb8b6ce4 100755 --- a/test_regress/t/t_case_call_count.py +++ b/test_regress/t/t_case_call_count.py @@ -15,7 +15,7 @@ test.compile(verilator_flags2=['--stats']) test.execute() -test.file_grep(test.stats, r'LiftExpr, lifted calls\s+(\d+)', 2) -test.file_grep(test.stats, r'Assertions, lifted impure case expressions\s+(\d+)', 2) +test.file_grep(test.stats, r'LiftExpr, lifted calls\s+(\d+)', 5) +test.file_grep(test.stats, r'Assertions, lifted impure case expressions\s+(\d+)', 4) test.passes() diff --git a/test_regress/t/t_case_call_count.v b/test_regress/t/t_case_call_count.v index dc6a896da..dcff576bf 100644 --- a/test_regress/t/t_case_call_count.v +++ b/test_regress/t/t_case_call_count.v @@ -20,10 +20,24 @@ class Cls; function int getPure(); return callCount2; endfunction + function int sel(); + case (get()) + 5: return 15; + 6: return 16; + default: return 17; + endcase + endfunction endclass module t; Cls c; + function automatic int sel2(); + case (c.get2()) + 5: return 25; + 6: return 26; + default: return 27; + endcase + endfunction initial begin bit called; c = new; @@ -50,6 +64,10 @@ module t; endcase if (!called) $stop; if (c.callCount2 != 1) $stop; + if (c.sel() != 16) $stop; + if (c.callCount != 2) $stop; + if (sel2() != 26) $stop; + if (c.callCount2 != 2) $stop; $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_property_nfa_final_past.py b/test_regress/t/t_property_nfa_final_past.py index 65f1233a6..865ca4e7a 100755 --- a/test_regress/t/t_property_nfa_final_past.py +++ b/test_regress/t/t_property_nfa_final_past.py @@ -13,7 +13,7 @@ test.scenarios('vlt_all') test.compile(timing_loop=True, verilator_flags2=['--assert', '--timing']) -test.execute(all_run_flags=['+expect_past=0']) +test.execute(all_run_flags=['+expect_past=1']) test.execute(all_run_flags=['+offedge', '+expect_past=0']) diff --git a/test_regress/t/t_property_nfa_final_past.v b/test_regress/t/t_property_nfa_final_past.v index 82061dec8..e4c848501 100644 --- a/test_regress/t/t_property_nfa_final_past.v +++ b/test_regress/t/t_property_nfa_final_past.v @@ -6,6 +6,11 @@ // $past in 'final' for on-tick and between-tick simulation ends +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + module t; bit clk = 0; @@ -16,18 +21,31 @@ module t; always #1 clk = ~clk; + default clocking cb @(posedge clk); + endclocking + + function automatic bit fpast(); + return $past(data); + endfunction + + function automatic bit fpast_wrapper(); + return fpast(); + endfunction + + task automatic tpast(output bit result); + result = $past(data); + endtask + always @(posedge clk) begin cyc <= cyc + 1; data <= ~data; if (!offedge && cyc == 2) begin + `checkd(fpast(), 1'b1); $write("*-* All Finished *-*\n"); $finish; end end - default clocking cb @(posedge clk); - endclocking - initial begin offedge = $test$plusargs("offedge") != 0; void'($value$plusargs("expect_past=%b", exp_past)); @@ -39,11 +57,11 @@ module t; end final begin - if ($past(data) !== exp_past) begin - $display("%%Error: wrong $past in final: got=%0b exp=%0b offedge=%0b", $past(data), exp_past, - offedge); - $stop; - end + bit tres; + `checkd($past(data), exp_past); + `checkd(fpast_wrapper(), exp_past); + tpast(tres); + `checkd(tres, exp_past); end endmodule diff --git a/test_regress/t/t_property_nfa_stop_error_limit.v b/test_regress/t/t_property_nfa_stop_error_limit.v index 70dbfa3be..5cac68fc0 100644 --- a/test_regress/t/t_property_nfa_stop_error_limit.v +++ b/test_regress/t/t_property_nfa_stop_error_limit.v @@ -39,7 +39,7 @@ module t ( final begin $stop; - `checkd($past(cyc), 10); + `checkd($past(cyc), 9); end endmodule