mirror of
https://github.com/verilator/verilator.git
synced 2026-09-01 10:27:04 +02:00
Fix $past in final blocks on a sampling-tick finish (#8153)
This commit is contained in:
+51
-5
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'])
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user