Merge 24e61af2c0
into 9f04ee68c8
This commit is contained in:
commit
0f21baa478
|
@ -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; }
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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); }
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -11,7 +11,7 @@ module t (/*AUTOARG*/
|
|||
|
||||
input clk;
|
||||
|
||||
logic unu3 = 0;
|
||||
bit unu3 = 0;
|
||||
|
||||
logic isusd = 0;
|
||||
cover property (@(posedge clk) isusd == 0);
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue