mirror of
https://github.com/verilator/verilator.git
synced 2026-09-07 18:06:07 +02:00
@@ -64,6 +64,7 @@ class LinkParseVisitor final : public VNVisitor {
|
||||
AstNodeExpr* m_defaultInSkewp = nullptr; // Current default input skew
|
||||
AstNodeExpr* m_defaultOutSkewp = nullptr; // Current default output skew
|
||||
int m_anonUdpId = 0; // Counter for anonymous UDP instances
|
||||
int m_coverpointNum = 0; // Counter for unnamed coverpoints within current covergroup
|
||||
int m_genblkAbove = 0; // Begin block number of if/case/for above
|
||||
int m_genblkNum = 0; // Begin block number, 0=none seen
|
||||
int m_beginDepth = 0; // How many begin blocks above current node within current AstNodeModule
|
||||
@@ -1149,6 +1150,268 @@ class LinkParseVisitor final : public VNVisitor {
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
|
||||
// Append, for each arg in argsp, an INPUT parameter plus a "this.<member> = <param>"
|
||||
// assignment to funcp. The parameter is a clone of the covergroup member and so shares its
|
||||
// name; 'this.' on the LHS targets the member, otherwise the same-named local parameter
|
||||
// shadows it and the assignment self-assigns the parameter, leaving the member unwritten.
|
||||
// argsp may be null (no args appended).
|
||||
static void addArgMemberCopies(AstFunc* funcp, AstNode* argsp) {
|
||||
for (AstNode* argp = argsp; argp; argp = argp->nextp()) {
|
||||
AstVar* const origVarp = VN_AS(argp, Var);
|
||||
AstVar* const paramp = origVarp->cloneTree(false);
|
||||
paramp->funcLocal(true);
|
||||
paramp->direction(VDirection::INPUT);
|
||||
funcp->addStmtsp(paramp);
|
||||
AstNodeExpr* const lhsp = new AstDot{
|
||||
origVarp->fileline(), false, new AstParseRef{origVarp->fileline(), "this"},
|
||||
new AstParseRef{origVarp->fileline(), origVarp->name()}};
|
||||
AstNodeExpr* const rhsp = new AstParseRef{paramp->fileline(), paramp->name()};
|
||||
funcp->addStmtsp(new AstAssign{origVarp->fileline(), lhsp, rhsp});
|
||||
}
|
||||
}
|
||||
|
||||
// Create boilerplate covergroup methods on the given AstClass.
|
||||
// argsp/sampleArgsp are the raw arg lists still owned by the caller; they are iterated
|
||||
// (cloned) but not deleted here.
|
||||
static void createCovergroupMethods(AstClass* nodep, AstFunc* newFuncp, AstNode* argsp,
|
||||
AstNode* sampleArgsp) {
|
||||
// Hidden static to take unspecified reference argument results
|
||||
AstVar* const defaultVarp
|
||||
= new AstVar{nodep->fileline(), VVarType::MEMBER, "__Vint", nodep->findIntDType()};
|
||||
defaultVarp->lifetime(VLifetime::STATIC_EXPLICIT);
|
||||
nodep->addStmtsp(defaultVarp);
|
||||
|
||||
// Handle constructor arguments - add function parameters and assignments
|
||||
if (argsp) {
|
||||
UASSERT_OBJ(newFuncp, nodep,
|
||||
"Covergroup class must have a 'new' constructor function");
|
||||
// Save the existing body statements and unlink them, so the arg assignments run
|
||||
// before the coverage body, then re-append the body.
|
||||
AstNode* const existingBodyp = newFuncp->stmtsp();
|
||||
if (existingBodyp) existingBodyp->unlinkFrBackWithNext();
|
||||
addArgMemberCopies(newFuncp, argsp);
|
||||
if (existingBodyp) newFuncp->addStmtsp(existingBodyp);
|
||||
}
|
||||
|
||||
// IEEE: option / type_option members allow external access (cg_inst.option.X)
|
||||
// and require std:: types; std:: is kept because setUsesStdPackage() is called
|
||||
// at parse time for every covergroup declaration.
|
||||
{
|
||||
AstVar* const varp
|
||||
= new AstVar{nodep->fileline(), VVarType::MEMBER, "option", VFlagChildDType{},
|
||||
new AstRefDType{nodep->fileline(), "vl_covergroup_options_t",
|
||||
new AstClassOrPackageRef{nodep->fileline(), "std",
|
||||
nullptr, nullptr},
|
||||
nullptr}};
|
||||
nodep->addMembersp(varp);
|
||||
}
|
||||
{
|
||||
AstVar* const varp
|
||||
= new AstVar{nodep->fileline(), VVarType::MEMBER, "type_option", VFlagChildDType{},
|
||||
new AstRefDType{nodep->fileline(), "vl_covergroup_type_options_t",
|
||||
new AstClassOrPackageRef{nodep->fileline(), "std",
|
||||
nullptr, nullptr},
|
||||
nullptr}};
|
||||
nodep->addMembersp(varp);
|
||||
}
|
||||
|
||||
// IEEE: function void sample([arguments])
|
||||
{
|
||||
AstFunc* const funcp = new AstFunc{nodep->fileline(), "sample", nullptr, nullptr};
|
||||
addArgMemberCopies(funcp, sampleArgsp);
|
||||
funcp->classMethod(true);
|
||||
funcp->dtypep(funcp->findVoidDType());
|
||||
nodep->addMembersp(funcp);
|
||||
}
|
||||
|
||||
// IEEE: function void start(), void stop()
|
||||
for (const string& name : {"start"s, "stop"s}) {
|
||||
AstFunc* const funcp = new AstFunc{nodep->fileline(), name, nullptr, nullptr};
|
||||
funcp->classMethod(true);
|
||||
funcp->dtypep(funcp->findVoidDType());
|
||||
nodep->addMembersp(funcp);
|
||||
}
|
||||
|
||||
// IEEE: static function real get_coverage(optional ref int, optional ref int)
|
||||
// IEEE: function real get_inst_coverage(optional ref int, optional ref int)
|
||||
for (const string& name : {"get_coverage"s, "get_inst_coverage"s}) {
|
||||
AstFunc* const funcp = new AstFunc{nodep->fileline(), name, nullptr, nullptr};
|
||||
funcp->fileline()->warnOff(V3ErrorCode::NORETURN, true);
|
||||
funcp->isStatic(name == "get_coverage");
|
||||
funcp->classMethod(true);
|
||||
funcp->dtypep(funcp->findVoidDType());
|
||||
nodep->addMembersp(funcp);
|
||||
{
|
||||
AstVar* const varp = new AstVar{nodep->fileline(), VVarType::MEMBER, name,
|
||||
nodep->findDoubleDType()};
|
||||
varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
|
||||
varp->funcLocal(true);
|
||||
varp->direction(VDirection::OUTPUT);
|
||||
varp->funcReturn(true);
|
||||
funcp->fvarp(varp);
|
||||
}
|
||||
for (const string& varname : {"covered_bins"s, "total_bins"s}) {
|
||||
AstVar* const varp = new AstVar{nodep->fileline(), VVarType::MEMBER, varname,
|
||||
nodep->findStringDType()};
|
||||
varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
|
||||
varp->funcLocal(true);
|
||||
varp->direction(VDirection::INPUT);
|
||||
varp->valuep(new AstVarRef{nodep->fileline(), defaultVarp, VAccess::READ});
|
||||
funcp->addStmtsp(varp);
|
||||
}
|
||||
}
|
||||
|
||||
// IEEE: function void set_inst_name(string)
|
||||
{
|
||||
AstFunc* const funcp
|
||||
= new AstFunc{nodep->fileline(), "set_inst_name", nullptr, nullptr};
|
||||
funcp->classMethod(true);
|
||||
funcp->dtypep(funcp->findVoidDType());
|
||||
nodep->addMembersp(funcp);
|
||||
AstVar* const varp = new AstVar{nodep->fileline(), VVarType::MEMBER, "name",
|
||||
nodep->findStringDType()};
|
||||
varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
|
||||
varp->funcLocal(true);
|
||||
varp->direction(VDirection::INPUT);
|
||||
funcp->addStmtsp(varp);
|
||||
}
|
||||
}
|
||||
|
||||
void visit(AstCovergroup* nodep) override {
|
||||
// AstCovergroup can only appear inside a module/class/package; never at root level.
|
||||
UASSERT_OBJ(m_modp, nodep, "AstCovergroup not under module");
|
||||
// If we're already inside a covergroup class, this is the sentinel AstCovergroup
|
||||
// node carrying the clocking event for V3Covergroup - don't re-transform it.
|
||||
if (VN_IS(m_modp, Class) && VN_AS(m_modp, Class)->isCovergroup()) return;
|
||||
|
||||
// Transform raw parse-time AstCovergroup into a fully-formed AstClass
|
||||
cleanFileline(nodep);
|
||||
|
||||
const string libname = m_modp->libname();
|
||||
AstClass* const cgClassp = new AstClass{nodep->fileline(), nodep->name(), libname};
|
||||
cgClassp->isCovergroup(true);
|
||||
v3Global.useCovergroup(true);
|
||||
|
||||
// Clocking event: unlink before deleteTree, attach as AstCovergroup child on class
|
||||
if (AstSenTree* const eventp = nodep->eventp()) {
|
||||
eventp->unlinkFrBack();
|
||||
AstCovergroup* const cgNodep = new AstCovergroup{
|
||||
nodep->fileline(), nodep->name(), nullptr, nullptr, nullptr, eventp};
|
||||
cgClassp->addMembersp(cgNodep);
|
||||
}
|
||||
|
||||
// Convert constructor args to member variables
|
||||
for (AstNode* argp = nodep->argsp(); argp; argp = argp->nextp()) {
|
||||
AstVar* const origVarp = VN_AS(argp, Var);
|
||||
AstVar* const memberp = origVarp->cloneTree(false);
|
||||
memberp->varType(VVarType::MEMBER);
|
||||
memberp->funcLocal(false);
|
||||
memberp->direction(VDirection::NONE);
|
||||
cgClassp->addMembersp(memberp);
|
||||
}
|
||||
|
||||
// Convert sample args to member variables
|
||||
for (AstNode* argp = nodep->sampleArgsp(); argp; argp = argp->nextp()) {
|
||||
AstVar* const origVarp = VN_AS(argp, Var);
|
||||
AstVar* const memberp = origVarp->cloneTree(false);
|
||||
memberp->varType(VVarType::MEMBER);
|
||||
memberp->funcLocal(false);
|
||||
memberp->direction(VDirection::NONE);
|
||||
cgClassp->addMembersp(memberp);
|
||||
}
|
||||
|
||||
// Create the constructor; detach membersp (coverage body) and use as its body
|
||||
AstFunc* const newFuncp = new AstFunc{nodep->fileline(), "new", nullptr, nullptr};
|
||||
newFuncp->fileline()->warnOff(V3ErrorCode::NORETURN, true);
|
||||
newFuncp->classMethod(true);
|
||||
newFuncp->isConstructor(true);
|
||||
newFuncp->dtypep(cgClassp->dtypep());
|
||||
if (AstNode* const bodyp = nodep->membersp()) {
|
||||
bodyp->unlinkFrBackWithNext();
|
||||
newFuncp->addStmtsp(bodyp);
|
||||
}
|
||||
cgClassp->addMembersp(newFuncp);
|
||||
|
||||
// Add all boilerplate covergroup methods (reads argsp/sampleArgsp from nodep)
|
||||
createCovergroupMethods(cgClassp, newFuncp, nodep->argsp(), nodep->sampleArgsp());
|
||||
|
||||
// Replace AstCovergroup with AstClass and process the new class normally.
|
||||
// Reset the unnamed-coverpoint counter so synthesized names are stable and
|
||||
// independent of unrelated covergroups elsewhere in the file.
|
||||
VL_RESTORER(m_coverpointNum);
|
||||
m_coverpointNum = 0;
|
||||
nodep->replaceWith(cgClassp);
|
||||
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
||||
iterate(cgClassp);
|
||||
}
|
||||
|
||||
void visit(AstCoverpoint* nodep) override {
|
||||
cleanFileline(nodep);
|
||||
// Give every coverpoint a guaranteed-unique, deterministic name so all downstream
|
||||
// consumers (generated bin-variable names, the cross coverpoint map, hierarchical
|
||||
// report names) see a consistent identifier. Unlabeled coverpoints arrive from the
|
||||
// grammar with an empty name; left empty, two of them in one covergroup collide on
|
||||
// the generated "__Vcov__<bin>" variable name (e.g. duplicate "__Vcov__auto_0").
|
||||
if (nodep->name().empty()) {
|
||||
// A single-identifier coverpoint expression is the only form that parses to an
|
||||
// AstParseRef with a usable name here; a dotted/scoped/select/concatenation/call
|
||||
// expression is either a different node (so the cast yields null) or a name-less
|
||||
// AstParseRef (e.g. a member-select). Either of those gets a synthesized name.
|
||||
const AstParseRef* const refp = VN_CAST(nodep->exprp(), ParseRef);
|
||||
if (refp && !refp->name().empty()) {
|
||||
// Single-identifier coverpoint: take the variable's name (IEEE 1800-2023
|
||||
// 19.5 - an unlabeled coverpoint of a single variable is named for it).
|
||||
nodep->name(refp->name());
|
||||
} else {
|
||||
// Compound expression (member/part select, concatenation, ...): synthesize
|
||||
// a unique name. Leading "__V" keeps it out of the user namespace.
|
||||
nodep->name("__Vcoverpoint" + cvtToStr(m_coverpointNum++));
|
||||
}
|
||||
}
|
||||
// Re-sort the parse-time mixed bins list (AstCoverBin + AstCgOptionAssign)
|
||||
// into the typed binsp and optionsp slots. The grammar attaches both node types
|
||||
// to binsp (op2) as a raw List[AstNode]; now that they are properly parented we
|
||||
// can iterate and split them without any temporary-parent tricks.
|
||||
for (AstNode *itemp = nodep->binsp(), *nextp; itemp; itemp = nextp) {
|
||||
nextp = itemp->nextp();
|
||||
if (AstCgOptionAssign* const optp = VN_CAST(itemp, CgOptionAssign)) {
|
||||
optp->unlinkFrBack();
|
||||
if (optp->optionType() == VCoverOptionType::AT_LEAST
|
||||
|| optp->optionType() == VCoverOptionType::AUTO_BIN_MAX) {
|
||||
nodep->addOptionsp(new AstCoverOption{optp->fileline(), optp->optionType(),
|
||||
optp->valuep()->cloneTree(false)});
|
||||
} else {
|
||||
optp->v3warn(COVERIGN,
|
||||
"Ignoring unsupported coverage option: " + optp->prettyNameQ());
|
||||
}
|
||||
VL_DO_DANGLING(optp->deleteTree(), optp);
|
||||
}
|
||||
}
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
|
||||
void visit(AstCoverCross* nodep) override {
|
||||
cleanFileline(nodep);
|
||||
// Distribute the parse-time raw cross_body list (rawBodyp, op3) into the
|
||||
// typed optionsp slot. The grammar produces AstCgOptionAssign nodes for
|
||||
// option.* items; convert them to AstCoverOption exactly as visit(AstCoverpoint*)
|
||||
// does. Other items (functions, unsupported bin selectors) are discarded.
|
||||
for (AstNode *itemp = nodep->rawBodyp(), *nextp; itemp; itemp = nextp) {
|
||||
nextp = itemp->nextp();
|
||||
itemp->unlinkFrBack();
|
||||
AstCgOptionAssign* const optp = VN_AS(itemp, CgOptionAssign);
|
||||
const VCoverOptionType optType = optp->optionType();
|
||||
optp->v3warn(COVERIGN,
|
||||
"Ignoring unsupported coverage cross option: " + optp->prettyNameQ());
|
||||
// Always preserve the option node so V3Coverage can track its source line
|
||||
// for coverage annotation, even when the option itself is unsupported.
|
||||
nodep->addOptionsp(
|
||||
new AstCoverOption{optp->fileline(), optType, optp->valuep()->cloneTree(false)});
|
||||
VL_DO_DANGLING(optp->deleteTree(), optp);
|
||||
}
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
|
||||
void visit(AstNode* nodep) override {
|
||||
// Default: Just iterate
|
||||
cleanFileline(nodep);
|
||||
|
||||
Reference in New Issue
Block a user