Fix $past in final blocks on a sampling-tick finish (#8153)

This commit is contained in:
Yilou Wang
2026-08-24 11:09:02 -04:00
committed by GitHub
parent 33ad9d530e
commit 3c027baa44
6 changed files with 99 additions and 17 deletions
+51 -5
View File
@@ -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<VNRef<AstNodeExpr>, std::unordered_map<VNRef<AstSenTree>, AstAlways*>>
m_modExpr2Sen2DelayedAlwaysp;
// Map from delayed-value AstAlways to its last-sampling-tick time variable
std::unordered_map<const AstAlways*, AstVar*> 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<AstVar*>& 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
+2 -2
View File
@@ -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()
+18
View File
@@ -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
+1 -1
View File
@@ -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'])
+26 -8
View File
@@ -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
@@ -39,7 +39,7 @@ module t (
final begin
$stop;
`checkd($past(cyc), 10);
`checkd($past(cyc), 9);
end
endmodule