This commit is contained in:
Ryszard Rozak 2025-07-22 13:18:43 +02:00 committed by GitHub
commit 0f21baa478
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 225 additions and 32 deletions

View File

@ -3057,12 +3057,15 @@ class AstCoverToggle final : public AstNodeStmt {
// @astgen op1 := incp : AstCoverInc
// @astgen op2 := origp : AstNodeExpr
// @astgen op3 := changep : AstNodeExpr
// @astgen op4 := initp : Optional[AstNodeExpr]
public:
AstCoverToggle(FileLine* fl, AstCoverInc* incp, AstNodeExpr* origp, AstNodeExpr* changep)
AstCoverToggle(FileLine* fl, AstCoverInc* incp, AstNodeExpr* origp, AstNodeExpr* changep,
AstNodeExpr* initp)
: ASTGEN_SUPER_CoverToggle(fl) {
this->incp(incp);
this->origp(origp);
this->changep(changep);
this->initp(initp);
}
ASTGEN_MEMBERS_AstCoverToggle;
int instrCount() const override { return 3 + INSTR_COUNT_BRANCH + INSTR_COUNT_LD; }

View File

@ -100,23 +100,44 @@ class ClockVisitor final : public VNVisitor {
// if (debug()) nodep->dumpTree("- ct: ");
// COVERTOGGLE(INC, ORIG, CHANGE) ->
// IF(ORIG ^ CHANGE) { INC; CHANGE = ORIG; }
AstNode* const incp = nodep->incp()->unlinkFrBack();
FileLine* fl = nodep->fileline();
AstNodeStmt* const incp = nodep->incp()->unlinkFrBack();
AstNodeExpr* const origp = nodep->origp()->unlinkFrBack();
AstNodeExpr* const changeWrp = nodep->changep()->unlinkFrBack();
AstNodeExpr* const changeRdp = ConvertWriteRefsToRead::main(changeWrp->cloneTree(false));
AstNodeExpr* comparedp = nullptr;
// Xor will optimize better than Eq, when CoverToggle has bit selects,
// but can only use Xor with non-opaque types
// but can only use Xor with non-opaque types.
// Opaque types aren't accepted by V3Coverage::varIgnoreToggle
if (const AstBasicDType* const bdtypep
= VN_CAST(origp->dtypep()->skipRefp(), BasicDType)) {
if (!bdtypep->isOpaque()) comparedp = new AstXor{nodep->fileline(), origp, changeRdp};
if (!bdtypep->isOpaque()) comparedp = new AstXor{fl, origp, changeRdp};
}
if (!comparedp) comparedp = AstEq::newTyped(nodep->fileline(), origp, changeRdp);
AstIf* const newp = new AstIf{nodep->fileline(), comparedp, incp};
UASSERT_OBJ(comparedp, nodep, "Toggle coverage of non-opaque type variable");
AstNodeStmt* incBodyp;
AstIf* initIfp = nullptr;
if (nodep->initp()) {
if (AstVarRef* const writeRefp = VN_CAST(nodep->initp(), VarRef)) {
AstVarRef* const readRefp = writeRefp->cloneTree(false);
readRefp->access(VAccess::READ);
AstAssign* const initAssignp
= new AstAssign{fl, writeRefp->unlinkFrBack(), new AstConst{fl, 2}};
initIfp = new AstIf{fl, new AstEq{fl, readRefp, new AstConst{fl, 1}}, initAssignp};
incBodyp
= new AstIf{fl, new AstEq{fl, readRefp->cloneTree(false), new AstConst{fl, 2}},
incp, initIfp->cloneTree(false)};
} else {
nodep->initp()->v3fatalSrc("Initp is not a var ref");
incBodyp = nullptr;
}
} else {
incBodyp = incp;
}
AstIf* const newp = new AstIf{fl, comparedp, incBodyp, initIfp};
// We could add another IF to detect posedges, and only increment if so.
// It's another whole branch though versus a potential memory miss.
// We'll go with the miss.
newp->addThensp(new AstAssign{nodep->fileline(), changeWrp, origp->cloneTree(false)});
newp->addThensp(new AstAssign{fl, changeWrp, origp->cloneTree(false)});
nodep->replaceWith(newp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
}

View File

@ -103,14 +103,17 @@ class CoverageVisitor final : public VNVisitor {
const string m_comment; // Comment for coverage dump
AstNodeExpr* m_varRefp; // How to get to this element
AstNodeExpr* m_chgRefp; // How to get to this element
ToggleEnt(const string& comment, AstNodeExpr* vp, AstNodeExpr* cp)
AstNodeExpr* m_initRefp; // Expression that stores initialization status
ToggleEnt(const string& comment, AstNodeExpr* vp, AstNodeExpr* cp, AstNodeExpr* initp)
: m_comment{comment}
, m_varRefp{vp}
, m_chgRefp{cp} {}
, m_chgRefp{cp}
, m_initRefp{initp} {}
~ToggleEnt() = default;
void cleanup() {
VL_DO_CLEAR(m_varRefp->deleteTree(), m_varRefp = nullptr);
VL_DO_CLEAR(m_chgRefp->deleteTree(), m_chgRefp = nullptr);
if (m_initRefp) { VL_DO_CLEAR(m_initRefp->deleteTree(), m_initRefp = nullptr); }
}
};
@ -387,16 +390,55 @@ class CoverageVisitor final : public VNVisitor {
const string newvarname = "__Vtogcov__"s + m_beginHier + nodep->shortName();
FileLine* const fl_nowarn = new FileLine{nodep->fileline()};
fl_nowarn->modifyWarnOff(V3ErrorCode::UNUSEDSIGNAL, true);
fl_nowarn->modifyWarnOff(V3ErrorCode::ALWCOMBORDER, true);
fl_nowarn->modifyWarnOff(V3ErrorCode::MULTIDRIVEN, true);
fl_nowarn->modifyWarnOff(V3ErrorCode::UNOPTFLAT, true);
fl_nowarn->coverageOn(false);
AstVar* const chgVarp
= new AstVar{fl_nowarn, VVarType::MODULETEMP, newvarname, nodep};
m_modp->addStmtsp(chgVarp);
AstVar* initVarp = nullptr;
AstVarRef* initWriteRefp = nullptr;
// We need to exclude top module input ports, because they can't have value x.
// TODO: Exclude also variables driven by top module input ports
const bool isTopModuleInput
= (v3Global.rootp()->topModulep() == m_modp) && nodep->isInput();
const AstBasicDType* const basicDTypep
= VN_CAST(nodep->dtypep()->skipRefp(), BasicDType);
if (basicDTypep && basicDTypep->isFourstate() && basicDTypep->isBitLogic()
&& !basicDTypep->isRanged() && !isTopModuleInput) {
const string initVarName
= "__Vtogcovinit__"s + m_beginHier + nodep->shortName();
AstBasicDType* const initDtypep
= new AstBasicDType{fl_nowarn, VBasicDTypeKwd::INT, VSigning::NOSIGN};
v3Global.rootp()->typeTablep()->addTypesp(initDtypep);
initVarp
= new AstVar{fl_nowarn, VVarType::MODULETEMP, initVarName, initDtypep};
m_modp->addStmtsp(initVarp);
initWriteRefp = new AstVarRef{fl_nowarn, initVarp, VAccess::WRITE};
AstAssign* const assignp
= new AstAssign{fl_nowarn, initWriteRefp->cloneTree(false),
new AstConst{fl_nowarn, AstConst::All1{}}};
AstIf* const ifp = new AstIf{
fl_nowarn,
new AstEq{fl_nowarn, new AstVarRef{fl_nowarn, initVarp, VAccess::READ},
new AstConst{fl_nowarn, AstConst::All0{}}},
assignp};
AstSenTree* const sentreep = new AstSenTree{
fl_nowarn, new AstSenItem{fl_nowarn, VEdgeType::ET_CHANGED,
new AstVarRef{fl_nowarn, nodep, VAccess::READ}}};
AstAlways* const alwaysp
= new AstAlways{fl_nowarn, VAlwaysKwd::ALWAYS, sentreep, ifp};
m_modp->addStmtsp(alwaysp);
}
// Create bucket for each dimension * bit.
// This is necessarily an O(n^2) expansion, which is why
// we limit coverage to signals with < 256 bits.
ToggleEnt newvec{""s, new AstVarRef{fl_nowarn, nodep, VAccess::READ},
new AstVarRef{fl_nowarn, chgVarp, VAccess::WRITE}};
new AstVarRef{fl_nowarn, chgVarp, VAccess::WRITE}, initWriteRefp};
toggleVarRecurse(nodep->dtypeSkipRefp(), 0, newvec, nodep);
newvec.cleanup();
}
@ -410,7 +452,8 @@ class CoverageVisitor final : public VNVisitor {
varp->fileline(),
newCoverInc(varp->fileline(), "", "v_toggle",
hierPrefix + varp->name() + above.m_comment, "", 0, ""),
above.m_varRefp->cloneTree(false), above.m_chgRefp->cloneTree(false)};
above.m_varRefp->cloneTree(false), above.m_chgRefp->cloneTree(false),
above.m_initRefp ? above.m_initRefp->cloneTree(false) : nullptr};
m_modp->addStmtsp(newp);
}
@ -425,7 +468,12 @@ class CoverageVisitor final : public VNVisitor {
new AstSel{varp->fileline(),
above.m_varRefp->cloneTree(false), index_code, 1},
new AstSel{varp->fileline(),
above.m_chgRefp->cloneTree(false), index_code, 1}};
above.m_chgRefp->cloneTree(false), index_code, 1},
above.m_initRefp
? new AstSel{varp->fileline(),
above.m_initRefp->cloneTree(false),
index_code, 1}
: nullptr};
toggleVarBottom(newent, varp);
newent.cleanup();
}
@ -439,7 +487,12 @@ class CoverageVisitor final : public VNVisitor {
new AstArraySel{varp->fileline(),
above.m_varRefp->cloneTree(false), index_code},
new AstArraySel{varp->fileline(),
above.m_chgRefp->cloneTree(false), index_code}};
above.m_chgRefp->cloneTree(false), index_code},
above.m_initRefp
? new AstArraySel{varp->fileline(),
above.m_initRefp->cloneTree(false),
index_code}
: nullptr};
toggleVarRecurse(adtypep->subDTypep()->skipRefp(), depth + 1, newent, varp);
newent.cleanup();
}
@ -447,11 +500,16 @@ class CoverageVisitor final : public VNVisitor {
for (int index_docs = adtypep->lo(); index_docs <= adtypep->hi(); ++index_docs) {
const AstNodeDType* const subtypep = adtypep->subDTypep()->skipRefp();
const int index_code = index_docs - adtypep->lo();
ToggleEnt newent{above.m_comment + "["s + cvtToStr(index_docs) + "]",
new AstSel{varp->fileline(), above.m_varRefp->cloneTree(false),
index_code * subtypep->width(), subtypep->width()},
new AstSel{varp->fileline(), above.m_chgRefp->cloneTree(false),
index_code * subtypep->width(), subtypep->width()}};
ToggleEnt newent{
above.m_comment + "["s + cvtToStr(index_docs) + "]",
new AstSel{varp->fileline(), above.m_varRefp->cloneTree(false),
index_code * subtypep->width(), subtypep->width()},
new AstSel{varp->fileline(), above.m_chgRefp->cloneTree(false),
index_code * subtypep->width(), subtypep->width()},
above.m_initRefp
? new AstSel{varp->fileline(), above.m_initRefp->cloneTree(false),
index_code * subtypep->width(), subtypep->width()}
: nullptr};
toggleVarRecurse(adtypep->subDTypep()->skipRefp(), depth + 1, newent, varp);
newent.cleanup();
}
@ -466,7 +524,11 @@ class CoverageVisitor final : public VNVisitor {
new AstSel{varp->fileline(), above.m_varRefp->cloneTree(false), index_code,
subtypep->width()},
new AstSel{varp->fileline(), above.m_chgRefp->cloneTree(false), index_code,
subtypep->width()}};
subtypep->width()},
above.m_initRefp
? new AstSel{varp->fileline(), above.m_initRefp->cloneTree(false),
index_code, subtypep->width()}
: nullptr};
toggleVarRecurse(subtypep, depth + 1, newent, varp);
newent.cleanup();
}
@ -478,9 +540,16 @@ class CoverageVisitor final : public VNVisitor {
varp->fileline(), above.m_varRefp->cloneTree(false), itemp->name()};
AstNodeExpr* const chgRefp = new AstStructSel{
varp->fileline(), above.m_chgRefp->cloneTree(false), itemp->name()};
AstNodeExpr* const initRefp
= above.m_initRefp
? new AstStructSel{varp->fileline(),
above.m_initRefp->cloneTree(false), itemp->name()}
: nullptr;
varRefp->dtypep(subtypep);
chgRefp->dtypep(subtypep);
ToggleEnt newent{above.m_comment + "."s + itemp->name(), varRefp, chgRefp};
initRefp->dtypep(subtypep);
ToggleEnt newent{above.m_comment + "."s + itemp->name(), varRefp, chgRefp,
initRefp};
toggleVarRecurse(subtypep, depth + 1, newent, varp);
newent.cleanup();
}
@ -490,9 +559,10 @@ class CoverageVisitor final : public VNVisitor {
if (const AstMemberDType* const itemp = adtypep->membersp()) {
AstNodeDType* const subtypep = itemp->subDTypep()->skipRefp();
if (adtypep->packed()) {
ToggleEnt newent{above.m_comment + "."s + itemp->name(),
above.m_varRefp->cloneTree(false),
above.m_chgRefp->cloneTree(false)};
ToggleEnt newent{
above.m_comment + "."s + itemp->name(), above.m_varRefp->cloneTree(false),
above.m_chgRefp->cloneTree(false),
above.m_initRefp ? above.m_initRefp->cloneTree(false) : nullptr};
toggleVarRecurse(subtypep, depth + 1, newent, varp);
newent.cleanup();
} else {
@ -500,9 +570,16 @@ class CoverageVisitor final : public VNVisitor {
varp->fileline(), above.m_varRefp->cloneTree(false), itemp->name()};
AstNodeExpr* const chgRefp = new AstStructSel{
varp->fileline(), above.m_chgRefp->cloneTree(false), itemp->name()};
AstNodeExpr* const initRefp
= above.m_initRefp
? new AstStructSel{varp->fileline(),
above.m_initRefp->cloneTree(false), itemp->name()}
: nullptr;
varRefp->dtypep(subtypep);
chgRefp->dtypep(subtypep);
ToggleEnt newent{above.m_comment + "."s + itemp->name(), varRefp, chgRefp};
initRefp->dtypep(subtypep);
ToggleEnt newent{above.m_comment + "."s + itemp->name(), varRefp, chgRefp,
initRefp};
toggleVarRecurse(subtypep, depth + 1, newent, varp);
newent.cleanup();
}

View File

@ -1265,6 +1265,9 @@ class DelayedVisitor final : public VNVisitor {
void visit(AstAssignPost*) override {}
void visit(AstAlwaysPost*) override {}
// Children of AstCoverToggle aren't part of assignments
void visit(AstCoverToggle* nodep) override {}
//--------------------
void visit(AstNode* nodep) override { iterateChildren(nodep); }

View File

@ -304,8 +304,14 @@ class GateBuildVisitor final : public VNVisitorConst {
// We use weight of one; if we ref the var more than once, when we simplify,
// the weight will increase
if (nodep->access().isWriteOrRW()) m_graphp->addEdge(m_logicVertexp, vVtxp, 1);
if (nodep->access().isReadOrRW()) m_graphp->addEdge(vVtxp, m_logicVertexp, 1);
if (VN_IS(nodep->backp(), CoverToggle)) {
// Temporary workaround. Otherwise assignments to initp() var are removed
m_graphp->addEdge(m_logicVertexp, vVtxp, 1);
m_graphp->addEdge(vVtxp, m_logicVertexp, 1);
} else {
if (nodep->access().isWriteOrRW()) m_graphp->addEdge(m_logicVertexp, vVtxp, 1);
if (nodep->access().isReadOrRW()) m_graphp->addEdge(vVtxp, m_logicVertexp, 1);
}
}
void visit(AstConcat* nodep) override {
UASSERT_OBJ(!(VN_IS(nodep->backp(), NodeAssign)

View File

@ -0,0 +1,20 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2025 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('simulator')
test.compile(verilator_flags2=['--cc --coverage-toggle --stats'])
test.execute()
test.inline_checks()
test.passes()

View File

@ -0,0 +1,63 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by Antmicro.
// SPDX-License-Identifier: CC0-1.0
module t(clk);
// verilator coverage_off
input clk;
integer cyc;
// verilator coverage_on
logic logic_unused;
// CHECK_COVER(-1,"top.t","logic_unused",0)
bit bit_unused;
// CHECK_COVER(-1,"top.t","bit_unused",0)
logic logic_always_0;
// CHECK_COVER(-1,"top.t","logic_always_0",0)
bit bit_always_0;
// CHECK_COVER(-1,"top.t","bit_always_0",0)
logic logic_always_1;
// CHECK_COVER(-1,"top.t","logic_always_1",0)
bit bit_always_1;
// CHECK_COVER(-1,"top.t","bit_always_1",1)
logic logic_initial_0;
// CHECK_COVER(-1,"top.t","logic_initial_0",0)
bit bit_initial_0;
// CHECK_COVER(-1,"top.t","bit_initial_0",0)
logic logic_initial_1;
// CHECK_COVER(-1,"top.t","logic_initial_1",0)
bit bit_initial_1;
// CHECK_COVER(-1,"top.t","bit_initial_1",1)
initial begin
cyc = 1;
logic_initial_0 = 0;
bit_initial_0 = 0;
logic_initial_1 = 1;
bit_initial_1 = 1;
end
always @(posedge clk) begin
logic_always_0 = 0;
bit_always_0 = 0;
logic_always_1 = 1;
bit_always_1 = 1;
if (cyc != 0) cyc <= cyc + 1;
if (cyc == 10) begin
$write("*-* All Finished *-*\n");
$finish;
end
end
endmodule

View File

@ -1,7 +1,7 @@
%Warning-UNUSEDSIGNAL: t/t_cover_unused_bad.v:14:10: Signal is not used: 'unu3'
: ... note: In instance 't'
14 | logic unu3 = 0;
| ^~~~
%Warning-UNUSEDSIGNAL: t/t_cover_unused_bad.v:14:8: Signal is not used: 'unu3'
: ... note: In instance 't'
14 | bit unu3 = 0;
| ^~~~
... For warning description see https://verilator.org/warn/UNUSEDSIGNAL?v=latest
... Use "/* verilator lint_off UNUSEDSIGNAL */" and lint_on around source to disable this message.
%Error: Exiting due to

View File

@ -11,7 +11,7 @@ module t (/*AUTOARG*/
input clk;
logic unu3 = 0;
bit unu3 = 0;
logic isusd = 0;
cover property (@(posedge clk) isusd == 0);

View File

@ -16,6 +16,6 @@ test.compile(verilator_flags2=["-Wno-UNOPTTHREADS", "--stats", "--coverage", "--
test.execute()
if test.vlt:
test.file_grep(test.stats, r'Optimizations, Const bit op reduction\s+(\d+)', 620)
test.file_grep(test.stats, r'Optimizations, Const bit op reduction\s+(\d+)', 223)
test.passes()