verilator/src/V3LinkParse.cpp

970 lines
44 KiB
C++

// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Parse module/signal name references
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// Copyright 2003-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
//
//*************************************************************************
// LinkParse TRANSFORMATIONS:
// Top-down traversal
// Move some attributes around
//*************************************************************************
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
#include "V3LinkParse.h"
#include "V3Control.h"
#include "V3Stats.h"
#include <set>
#include <vector>
VL_DEFINE_DEBUG_FUNCTIONS;
//######################################################################
// Link state, as a visitor of each AstNode
class LinkParseVisitor final : public VNVisitor {
// NODE STATE
// Cleared on netlist
// AstNode::user1() -> bool. True if processed
// AstNode::user2() -> bool. True if fileline recomputed
const VNUser1InUse m_inuser1;
const VNUser2InUse m_inuser2;
// TYPES
using ImplTypedefMap = std::map<std::string, AstTypedef*>;
// STATE - across all visitors
std::unordered_set<FileLine*> m_filelines; // Filelines that have been seen
// STATE - for current visit position (use VL_RESTORER)
// If set, move AstVar->valuep() initial values to this module
ImplTypedefMap m_implTypedef; // Created typedefs for each <container,name>
AstVar* m_varp = nullptr; // Variable we're under
AstNodeModule* m_valueModp = nullptr;
AstNodeModule* m_modp = nullptr; // Current module
AstNodeProcedure* m_procedurep = nullptr; // Current procedure
AstNodeFTask* m_ftaskp = nullptr; // Current task
AstNodeBlock* m_blockp = nullptr; // Current AstNodeBlock
AstNodeDType* m_dtypep = nullptr; // Current data type
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_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
VLifetime m_lifetime = VLifetime::STATIC_IMPLICIT; // Propagating lifetime
bool m_insideLoop = false; // True if the node is inside a loop
bool m_lifetimeAllowed = false; // True to allow lifetime settings
bool m_moduleWithGenericIface = false; // If current module contains generic interface
// STATE - Statistic tracking
VDouble0 m_statModules; // Number of modules seen
// METHODS
void cleanFileline(AstNode* nodep) {
if (nodep->user2SetOnce()) return; // Process once
// We make all filelines unique per AstNode. This allows us to
// later turn off messages on a fileline when an issue is found
// so that messages on replicated blocks occur only once,
// without suppressing other token's messages as a side effect.
// We could have verilog.l create a new one on every token,
// but that's a lot more structures than only doing AST nodes.
// TODO: Many places copy the filename when suppressing warnings,
// perhaps audit to make consistent and this is no longer needed
if (m_filelines.find(nodep->fileline()) != m_filelines.end()) {
nodep->fileline(new FileLine{nodep->fileline()});
}
m_filelines.insert(nodep->fileline());
}
string nameFromTypedef(AstNode* nodep) {
// Try to find a name for a typedef'ed enum/struct
if (const AstTypedef* const typedefp = VN_CAST(nodep->backp(), Typedef)) {
// Create a name for the enum, to aid debug and tracing
// This name is not guaranteed to be globally unique (due to later parameterization)
string above;
if (m_modp && VN_IS(m_modp, Package)) {
above = m_modp->name() + "::";
} else if (m_modp) {
above = m_modp->name() + ".";
}
return above + typedefp->name();
}
return "";
}
void visitIterateNodeDType(AstNodeDType* nodep) {
if (nodep->user1SetOnce()) return; // Process only once.
cleanFileline(nodep);
VL_RESTORER(m_dtypep);
m_dtypep = nodep;
iterateChildren(nodep);
}
bool nestedIfBegin(AstGenBlock* nodep) { // Point at begin inside the GenIf
// IEEE says directly nested item is not a new block
// The genblk name will get attached to the if true/false LOWER begin block(s)
// 1: GENIF
// -> 1:3: GENBLOCK [IMPLIED] // nodep passed to this function
// 1:3:1: GENIF
// 1:3:1:2: GENBLOCK genblk1 [IMPLIED]
const AstNode* const backp = nodep->backp();
return (nodep->implied() // User didn't provide begin/end
&& VN_IS(backp, GenIf) && VN_CAST(backp, GenIf)->elsesp() == nodep
&& !nodep->nextp() // No other statements under upper genif else
&& (VN_IS(nodep->itemsp(), GenIf)) // Begin has if underneath
&& !nodep->itemsp()->nextp()); // Has only one item
}
void checkIndent(AstNode* nodep, AstNode* childp) {
// Try very hard to avoid false positives
AstNode* nextp = nodep->nextp();
if (!childp) return;
if (!nextp && VN_IS(nodep, Loop) && VN_IS(nodep->backp(), Begin))
nextp = nodep->backp()->nextp();
if (!nextp) return;
if (VN_IS(childp, Begin) || VN_IS(childp, GenBlock)) return;
FileLine* const nodeFlp = nodep->fileline();
FileLine* const childFlp = childp->fileline();
FileLine* const nextFlp = nextp->fileline();
// UINFO(0, "checkInd " << nodeFlp->firstColumn() << " " << nodep);
// UINFO(0, " child " << childFlp->firstColumn() << " " << childp);
// UINFO(0, " next " << nextFlp->firstColumn() << " " << nextp);
// Same filename, later line numbers (no macro magic going on)
if (nodeFlp->filenameno() != childFlp->filenameno()) return;
if (nodeFlp->filenameno() != nextFlp->filenameno()) return;
if (nodeFlp->lastLineno() >= childFlp->firstLineno()) return;
if (childFlp->lastLineno() >= nextFlp->firstLineno()) return;
// This block has indent 'a'
// Child block has indent 'b' where indent('b') > indent('a')
// Next block has indent 'b'
// (note similar code below)
if (!(nodeFlp->firstColumn() < childFlp->firstColumn()
&& nextFlp->firstColumn() >= childFlp->firstColumn()))
return;
// Might be a tab difference in spaces up to the node prefix, if so
// just ignore this warning
// Note it's correct we look at nodep's column in all of these
const std::string nodePrefix = nodeFlp->sourcePrefix(nodeFlp->firstColumn());
const std::string childPrefix = childFlp->sourcePrefix(nodeFlp->firstColumn());
const std::string nextPrefix = nextFlp->sourcePrefix(nodeFlp->firstColumn());
if (childPrefix != nodePrefix) return;
if (nextPrefix != childPrefix) return;
// Some file lines start after the indentation, so make another check
// using actual file contents
const std::string nodeSource = nodeFlp->source();
const std::string childSource = childFlp->source();
const std::string nextSource = nextFlp->source();
if (!(VString::leadingWhitespaceCount(nodeSource)
< VString::leadingWhitespaceCount(childSource)
&& VString::leadingWhitespaceCount(nextSource)
>= VString::leadingWhitespaceCount(childSource)))
return;
nextp->v3warn(MISINDENT,
"Misleading indentation\n"
<< nextp->warnContextPrimary() << '\n'
<< nodep->warnOther()
<< "... Expected indentation matching this earlier statement's line:\n"
<< nodep->warnContextSecondary());
}
// VISITORS
void visit(AstNodeFTask* nodep) override {
if (nodep->user1SetOnce()) return; // Process only once.
// Mark class methods
if (VN_IS(m_modp, Class)) nodep->classMethod(true);
V3Control::applyFTask(m_modp, nodep);
cleanFileline(nodep);
VL_RESTORER(m_ftaskp);
m_ftaskp = nodep;
VL_RESTORER(m_lifetime);
VL_RESTORER(m_lifetimeAllowed);
m_lifetimeAllowed = true;
if (!nodep->lifetime().isNone()) {
m_lifetime = nodep->lifetime().makeImplicit();
} else {
if (nodep->classMethod()) {
// Class methods are automatic by default
m_lifetime = VLifetime::AUTOMATIC_IMPLICIT;
} else if (nodep->dpiImport() || VN_IS(nodep, Property)) {
// DPI-imported functions and properties don't have lifetime specifiers
m_lifetime = VLifetime::NONE;
}
nodep->lifetime(m_lifetime);
}
if (nodep->classMethod() && nodep->lifetime().isStatic()) {
nodep->v3error("Class function/task cannot be static lifetime ('"
<< nodep->verilogKwd() << " static') (IEEE 1800-2023 6.21)\n"
<< nodep->warnMore() << "... May have intended 'static "
<< nodep->verilogKwd() << "'");
}
iterateChildren(nodep);
}
void visit(AstNodeDType* nodep) override { visitIterateNodeDType(nodep); }
void visit(AstConstraint* nodep) override {
v3Global.useRandomizeMethods(true);
iterateChildren(nodep);
}
void visit(AstEnumDType* nodep) override {
if (nodep->name() == "") {
nodep->name(nameFromTypedef(nodep)); // Might still remain ""
}
visitIterateNodeDType(nodep);
}
void visit(AstNodeUOrStructDType* nodep) override {
if (nodep->name() == "") {
nodep->name(nameFromTypedef(nodep)); // Might still remain ""
}
visitIterateNodeDType(nodep);
}
void visit(AstEnumItem* nodep) override {
// Expand ranges
cleanFileline(nodep);
iterateChildren(nodep);
if (nodep->rangep()) {
if (VL_UNCOVERABLE(!VN_IS(nodep->rangep()->leftp(), Const) // LCOV_EXCL_START
|| !VN_IS(nodep->rangep()->rightp(), Const))) {
// We check this rule in the parser, so shouldn't fire
nodep->v3error("Enum ranges must be integral, per spec");
} // LCOV_EXCL_STOP
const int left = nodep->rangep()->leftConst();
const int right = nodep->rangep()->rightConst();
const int increment = (left > right) ? -1 : 1;
uint32_t offset_from_init = 0;
AstEnumItem* addp = nullptr;
FileLine* const flp = nodep->fileline();
for (int i = left; i != (right + increment); i += increment, ++offset_from_init) {
const string name = nodep->name() + cvtToStr(i);
AstNodeExpr* valuep = nullptr;
if (nodep->valuep()) {
// V3Width looks for Adds with same fileline as the EnumItem
valuep
= new AstAdd{flp, nodep->valuep()->cloneTree(true),
new AstConst{flp, AstConst::Unsized32{}, offset_from_init}};
}
addp = AstNode::addNext(addp, new AstEnumItem{flp, name, nullptr, valuep});
}
nodep->replaceWith(addp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
}
}
void visit(AstVar* nodep) override {
cleanFileline(nodep);
if (nodep->lifetime().isStatic() && m_insideLoop && nodep->valuep()) {
nodep->lifetime(VLifetime::AUTOMATIC_IMPLICIT);
nodep->v3warn(STATICVAR, "Static variable with assignment declaration declared in a "
"loop converted to automatic");
} else if (nodep->valuep() && nodep->lifetime().isNone() && m_lifetime.isStatic()
&& !nodep->isIO()
// In task, or a procedure but not Initial/Final as executed only once
&& ((m_ftaskp && !m_ftaskp->lifetime().isStaticExplicit())
|| (m_procedurep && !VN_IS(m_procedurep, Initial)
&& !VN_IS(m_procedurep, Final)))) {
if (VN_IS(m_modp, Module) && m_ftaskp) {
m_ftaskp->v3warn(
IMPLICITSTATIC,
"Function/task's lifetime implicitly set to static\n"
<< m_ftaskp->warnMore() << "... Suggest use '" << m_ftaskp->verilogKwd()
<< " automatic' or '" << m_ftaskp->verilogKwd() << " static'\n"
<< m_ftaskp->warnContextPrimary() << '\n'
<< nodep->warnOther() << "... Location of implicit static variable\n"
<< nodep->warnMore() << "... The initializer value will only be set once\n"
<< nodep->warnContextSecondary());
} else {
nodep->v3warn(IMPLICITSTATIC,
"Variable's lifetime implicitly set to static\n"
<< nodep->warnMore()
<< "... The initializer value will only be set once\n"
<< nodep->warnMore()
<< "... Suggest use 'static' before variable declaration'");
}
}
if (!m_lifetimeAllowed && nodep->lifetime().isAutomatic()) {
nodep->v3error(
"Module variables cannot have automatic lifetime (IEEE 1800-2023 6.21): "
<< nodep->prettyNameQ());
nodep->lifetime(VLifetime::STATIC_IMPLICIT);
}
if (!nodep->direction().isAny()) { // Not a port
if (nodep->lifetime().isNone()) {
if (m_lifetimeAllowed) {
nodep->lifetime(m_lifetime);
} else { // Module's always static per IEEE 1800-2023 6.21
nodep->lifetime(VLifetime::STATIC_IMPLICIT);
}
}
} else if (m_ftaskp) {
if (!nodep->lifetime().isAutomatic()) nodep->lifetime(VLifetime::AUTOMATIC_IMPLICIT);
} else if (nodep->lifetime().isNone()) {
// lifetime shouldn't be unknown, set static if none
nodep->lifetime(VLifetime::STATIC_IMPLICIT);
}
if (nodep->isGParam() && !nodep->isAnsi()) { // shadow some parameters into localparams
if (m_beginDepth > 0
|| (m_beginDepth == 0
&& (m_modp->hasParameterList() || VN_IS(m_modp, Class)
|| VN_IS(m_modp, Package)))) {
nodep->varType(VVarType::LPARAM);
}
}
if (nodep->isGParam() && m_modp) m_modp->hasGParam(true);
if (nodep->isParam() && !nodep->valuep()
&& nodep->fileline()->language() < V3LangCode::L1800_2009) {
nodep->v3warn(NEWERSTD,
"Parameter requires default value, or use IEEE 1800-2009 or later.");
}
if (AstParseTypeDType* const ptypep = VN_CAST(nodep->subDTypep(), ParseTypeDType)) {
// It's a parameter type. Use a different node type for this.
AstNode* dtypep = nodep->valuep();
if (dtypep) {
dtypep->unlinkFrBack();
} else {
dtypep = new AstVoidDType{nodep->fileline()};
}
AstNode* const newp = new AstParamTypeDType{
nodep->fileline(), nodep->varType(),
ptypep->fwdType(), nodep->name(),
VFlagChildDType{}, new AstRequireDType{nodep->fileline(), dtypep}};
nodep->replaceWith(newp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
return;
}
m_moduleWithGenericIface |= VN_IS(nodep->childDTypep(), IfaceGenericDType);
// Maybe this variable has a signal attribute
V3Control::applyVarAttr(m_modp, m_ftaskp, nodep);
if (v3Global.opt.anyPublicFlat() && nodep->varType().isVPIAccessible()) {
if (v3Global.opt.publicFlatRW()) {
nodep->sigUserRWPublic(true);
} else if (v3Global.opt.publicParams() && nodep->isParam()) {
nodep->sigUserRWPublic(true);
} else if (m_modp && v3Global.opt.publicDepth()) {
if ((m_modp->depth() - 1) <= v3Global.opt.publicDepth()) {
nodep->sigUserRWPublic(true);
} else if (VN_IS(m_modp, Package) && nodep->isParam()) {
nodep->sigUserRWPublic(true);
}
}
}
// We used modTrace before leveling, and we may now
// want to turn it off now that we know the levelizations
if (v3Global.opt.traceDepth() && m_modp
&& (m_modp->depth() - 1) > v3Global.opt.traceDepth()) {
m_modp->modTrace(false);
nodep->trace(false);
}
m_varp = nodep;
iterateChildren(nodep);
m_varp = nullptr;
// temporaries under an always aren't expected to be blocking
if (m_procedurep && VN_IS(m_procedurep, Always))
nodep->fileline()->modifyWarnOff(V3ErrorCode::BLKSEQ, true);
if (nodep->valuep()) {
FileLine* const fl = nodep->valuep()->fileline();
// A variable with an = value can be 4 things:
if (nodep->isParam() || (m_ftaskp && nodep->isNonOutput())) {
// 1. Parameters and function inputs: It's a default to use if not overridden
} else if (!m_ftaskp && !VN_IS(m_modp, Class) && nodep->isNonOutput()
&& !nodep->isInput()) {
// 2. Module inout/ref/constref: const default to use
nodep->v3warn(E_UNSUPPORTED,
"Unsupported: Default value on module inout/ref/constref: "
<< nodep->prettyNameQ());
nodep->valuep()->unlinkFrBack()->deleteTree();
} else if (m_blockp) {
// 3. Under blocks, it's an initial value to be under an assign
// TODO: This is wrong if it's a static variable right?
FileLine* const newfl = new FileLine{fl};
newfl->warnOff(V3ErrorCode::E_CONSTWRITTEN, true);
m_blockp->addStmtsp(
new AstAssign{newfl, new AstVarRef{newfl, nodep, VAccess::WRITE},
VN_AS(nodep->valuep()->unlinkFrBack(), NodeExpr)});
} else if (m_valueModp) {
// 4. Under modules/class, it's the time 0 initialziation value
// Making an AstAssign (vs AstAssignW) to a wire is an error, suppress it
FileLine* const newfl = new FileLine{fl};
newfl->warnOff(V3ErrorCode::PROCASSWIRE, true);
newfl->warnOff(V3ErrorCode::E_CONSTWRITTEN, true);
// Create a ParseRef to the wire. We cannot use the var as it may be deleted if
// it's a port (see t_var_set_link.v)
AstAssign* const assp
= new AstAssign{newfl, new AstParseRef{newfl, nodep->name()},
VN_AS(nodep->valuep()->unlinkFrBack(), NodeExpr)};
if (nodep->lifetime().isAutomatic()) {
nodep->addNextHere(new AstInitialAutomatic{newfl, assp});
} else {
nodep->addNextHere(new AstInitialStatic{newfl, assp});
}
} else {
nodep->v3fatalSrc("Variable with initializer in unexpected position");
}
}
}
void visit(AstConst* nodep) override {
if (nodep->num().autoExtend() && nodep->fileline()->language() < V3LangCode::L1800_2005) {
nodep->v3warn(NEWERSTD, "Unbased unsized literals require IEEE 1800-2005 or later.");
}
}
void visit(AstAttrOf* nodep) override {
cleanFileline(nodep);
iterateChildren(nodep);
if (nodep->attrType() == VAttrType::DT_PUBLIC) {
AstTypedef* const typep = VN_AS(nodep->backp(), Typedef);
UASSERT_OBJ(typep, nodep, "Attribute not attached to typedef");
typep->attrPublic(true);
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
} else if (nodep->attrType() == VAttrType::VAR_FORCEABLE) {
UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable");
m_varp->setForceable();
v3Global.setHasForceableSignals();
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
} else if (nodep->attrType() == VAttrType::VAR_PUBLIC) {
UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable");
m_varp->sigUserRWPublic(true);
m_varp->sigModPublic(true);
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
} else if (nodep->attrType() == VAttrType::VAR_PUBLIC_FLAT) {
UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable");
m_varp->sigUserRWPublic(true);
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
} else if (nodep->attrType() == VAttrType::VAR_PUBLIC_FLAT_RD) {
UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable");
m_varp->sigUserRdPublic(true);
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
} else if (nodep->attrType() == VAttrType::VAR_PUBLIC_FLAT_RW) {
UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable");
m_varp->sigUserRWPublic(true);
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
} else if (nodep->attrType() == VAttrType::VAR_ISOLATE_ASSIGNMENTS) {
UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable");
m_varp->attrIsolateAssign(true);
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
} else if (nodep->attrType() == VAttrType::VAR_SFORMAT) {
UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable");
m_varp->attrSFormat(true);
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
} else if (nodep->attrType() == VAttrType::VAR_SPLIT_VAR) {
UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable");
if (!VN_IS(m_modp, Module)) {
m_varp->v3warn(
SPLITVAR,
m_varp->prettyNameQ()
<< " has split_var metacomment, "
"but will not be split because it is not declared in a module.");
} else {
m_varp->attrSplitVar(true);
}
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
} else if (nodep->attrType() == VAttrType::VAR_SC_BV) {
UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable");
m_varp->attrScBv(true);
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
}
}
void visit(AstDefImplicitDType* nodep) override {
cleanFileline(nodep);
UINFO(8, " DEFIMPLICIT " << nodep);
// Must remember what names we've already created, and combine duplicates
// so that for "var enum {...} a,b" a & b will share a common typedef.
// Change to unique name space per module so that an addition of
// a new type won't change every verilated module.
AstTypedef* defp = nullptr;
const ImplTypedefMap::iterator it = m_implTypedef.find(nodep->name());
if (it != m_implTypedef.end()) {
defp = it->second;
UINFO(9, "Reused impltypedef " << nodep << " --> " << defp);
} else {
// Definition must be inserted right after the variable (etc) that needed it
// AstVar, AstTypedef, AstNodeFTask are common containers
AstNode* backp = nodep->backp();
for (; backp; backp = backp->backp()) {
if (VN_IS(backp, Var) || VN_IS(backp, Typedef) || VN_IS(backp, NodeFTask)) break;
}
UASSERT_OBJ(backp, nodep,
"Implicit enum/struct type created under unexpected node type");
AstNodeDType* const dtypep = nodep->childDTypep();
dtypep->unlinkFrBack();
if (VN_IS(backp, Typedef)) {
// A typedef doesn't need us to make yet another level of typedefing
// For typedefs just remove the AstRefDType level of abstraction
nodep->replaceWith(dtypep);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
return;
} else {
defp = new AstTypedef{nodep->fileline(), nodep->name(), nullptr, VFlagChildDType{},
dtypep};
m_implTypedef.emplace(defp->name(), defp);
// Rename so that name doesn't change if a type is added/removed elsewhere
// But the m_implTypedef is stil by old name so we can find it for next new lookups
defp->name("__typeimpmod" + cvtToStr(m_implTypedef.size()));
UINFO(9, "New impltypedef " << defp);
backp->addNextHere(defp);
}
}
nodep->replaceWith(new AstRefDType{nodep->fileline(), defp->name()});
VL_DO_DANGLING(nodep->deleteTree(), nodep);
}
void visit(AstDelay* nodep) override {
cleanFileline(nodep);
UASSERT_OBJ(m_modp, nodep, "Delay not under module");
nodep->timeunit(m_modp->timeunit());
iterateChildren(nodep);
}
void visit(AstNodeForeach* nodep) override {
// FOREACH(array, loopvars, body)
UINFO(9, "FOREACH " << nodep);
cleanFileline(nodep);
// Separate iteration vars from base from variable
// Input:
// v--- arrayp
// 1. DOT(DOT(first, second), ASTSELLOOPVARS(third, var0..var1))
// Separated:
// bracketp = ASTSELLOOPVARS(...)
// arrayp = DOT(DOT(first, second), third)
// firstVarp = var0..var1
// Other examples
// 2. ASTSELBIT(first, var0))
// 3. ASTSELLOOPVARS(first, var0..var1))
// 4. DOT(DOT(first, second), ASTSELBIT(third, var0))
VL_RESTORER(m_insideLoop);
m_insideLoop = true;
AstNode* bracketp = nodep->arrayp();
while (AstDot* dotp = VN_CAST(bracketp, Dot)) bracketp = dotp->rhsp();
if (AstSelBit* const selp = VN_CAST(bracketp, SelBit)) {
// Convert to AstSelLoopVars so V3LinkDot knows what's being defined
AstNode* const newp
= new AstSelLoopVars{selp->fileline(), selp->fromp()->unlinkFrBack(),
selp->bitp()->unlinkFrBackWithNext()};
selp->replaceWith(newp);
VL_DO_DANGLING(selp->deleteTree(), selp);
} else if (VN_IS(bracketp, SelLoopVars)) {
// Ok
} else {
nodep->v3error("Foreach missing bracketed loop variable is no-operation"
" (IEEE 1800-2023 12.7.3)");
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
return;
}
iterateChildren(nodep);
}
void visit(AstRepeat* nodep) override {
cleanFileline(nodep);
VL_RESTORER(m_insideLoop);
m_insideLoop = true;
checkIndent(nodep, nodep->stmtsp());
iterateChildren(nodep);
}
void visit(AstLoop* nodep) override {
cleanFileline(nodep);
VL_RESTORER(m_insideLoop);
m_insideLoop = true;
if (VN_IS(nodep->stmtsp(), LoopTest)) {
checkIndent(nodep, nodep->stmtsp()->nextp());
} else {
checkIndent(nodep, nodep->stmtsp());
}
iterateChildren(nodep);
}
void visit(AstRandSequence* nodep) override {
cleanFileline(nodep);
nodep->v3warn(E_UNSUPPORTED, "Unsupported: randsequence");
iterateChildren(nodep);
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
}
void visit(AstRSCase* nodep) override {
cleanFileline(nodep);
nodep->v3warn(E_UNSUPPORTED, "Unsupported: randsequence case");
iterateChildren(nodep);
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
}
void visit(AstRSIf* nodep) override {
cleanFileline(nodep);
nodep->v3warn(E_UNSUPPORTED, "Unsupported: randsequence if");
iterateChildren(nodep);
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
}
void visit(AstRSRepeat* nodep) override {
cleanFileline(nodep);
nodep->v3warn(E_UNSUPPORTED, "Unsupported: randsequence repeat");
iterateChildren(nodep);
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
}
void visit(AstWait* nodep) override {
cleanFileline(nodep);
iterateChildren(nodep);
if (nodep->condp()->isZero()) {
// Special case "wait(0)" we won't throw WAITCONST as user wrote
// it that way with presumed intent - UVM does this.
FileLine* const newfl = nodep->fileline();
newfl->warnOff(V3ErrorCode::WAITCONST, true);
nodep->fileline(newfl);
}
}
void visit(AstNodeModule* nodep) override {
V3Control::applyModule(nodep);
++m_statModules;
if (VN_IS(nodep, Class) && VN_CAST(nodep, Class)->isInterfaceClass()
&& VN_IS(m_modp, Class) && VN_CAST(m_modp, Class)->isInterfaceClass()) {
nodep->v3error("Interface class shall not be nested within another interface class."
" (IEEE 1800-2023 8.26)");
}
VL_RESTORER(m_modp);
VL_RESTORER(m_anonUdpId);
VL_RESTORER(m_genblkAbove);
VL_RESTORER(m_genblkNum);
VL_RESTORER(m_beginDepth);
VL_RESTORER(m_implTypedef);
VL_RESTORER(m_lifetime);
VL_RESTORER(m_lifetimeAllowed);
VL_RESTORER(m_moduleWithGenericIface);
VL_RESTORER(m_valueModp);
// Module: Create sim table for entire module and iterate
cleanFileline(nodep);
// Classes inherit from upper package
if (m_modp && nodep->timeunit().isNone()) nodep->timeunit(m_modp->timeunit());
m_modp = nodep;
m_anonUdpId = 0;
m_genblkAbove = 0;
m_genblkNum = 0;
m_beginDepth = 0;
m_implTypedef.clear();
m_valueModp = nodep;
m_lifetime = nodep->lifetime().makeImplicit();
m_lifetimeAllowed = VN_IS(nodep, Class);
m_moduleWithGenericIface = false;
if (m_lifetime.isNone()) {
m_lifetime
= VN_IS(nodep, Class) ? VLifetime::AUTOMATIC_IMPLICIT : VLifetime::STATIC_IMPLICIT;
}
if (nodep->name() == "TOP") {
// May mess up scope resolution and cause infinite loop
nodep->v3warn(E_UNSUPPORTED, "Module cannot be named 'TOP' as conflicts with "
"Verilator top-level internals");
}
iterateChildren(nodep);
if (AstModule* const modp = VN_CAST(nodep, Module)) {
modp->hasGenericIface(m_moduleWithGenericIface);
}
}
void visitIterateNoValueMod(AstNode* nodep) {
// Iterate a node which any Var within shouldn't create an InitialAutomatic procedure
cleanFileline(nodep);
VL_RESTORER(m_valueModp);
m_valueModp = nullptr;
iterateChildren(nodep);
}
void visit(AstNodeProcedure* nodep) override {
VL_RESTORER(m_lifetimeAllowed);
m_lifetimeAllowed = true;
VL_RESTORER(m_procedurep);
m_procedurep = nodep;
visitIterateNoValueMod(nodep);
}
void visit(AstCover* nodep) override { visitIterateNoValueMod(nodep); }
void visit(AstRestrict* nodep) override { visitIterateNoValueMod(nodep); }
void visit(AstGenBlock* nodep) override {
V3Control::applyCoverageBlock(m_modp, nodep);
cleanFileline(nodep);
VL_RESTORER(m_beginDepth);
++m_beginDepth;
const AstNode* const backp = nodep->backp();
// IEEE says directly nested item is not a new block
// The genblk name will get attached to the if true/false LOWER begin block(s)
const bool nestedIf = nestedIfBegin(nodep);
// It's not FOR(BEGIN(...)) but we earlier changed it to BEGIN(FOR(...))
int assignGenBlkNum = -1;
if (nodep->genforp()) {
++m_genblkNum;
if (nodep->name() == "") assignGenBlkNum = m_genblkNum;
} else if (nodep->name() == "" && (VN_IS(backp, GenCaseItem) || VN_IS(backp, GenIf))
&& !nestedIf) {
assignGenBlkNum = m_genblkAbove;
}
if (assignGenBlkNum != -1) {
nodep->name("genblk" + cvtToStr(assignGenBlkNum));
if (nodep->itemsp()) {
nodep->v3warn(GENUNNAMED,
"Unnamed generate block "
<< nodep->prettyNameQ() << " (IEEE 1800-2023 27.6)\n"
<< nodep->warnMore()
<< "... Suggest assign a label with 'begin : gen_<label_name>'");
}
}
if (nodep->name() != "") {
VL_RESTORER(m_genblkAbove);
VL_RESTORER(m_genblkNum);
m_genblkAbove = 0;
m_genblkNum = 0;
iterateChildren(nodep);
} else {
iterateChildren(nodep);
}
}
void visit(AstGenCase* nodep) override {
++m_genblkNum;
cleanFileline(nodep);
VL_RESTORER(m_genblkAbove);
VL_RESTORER(m_genblkNum);
m_genblkAbove = m_genblkNum;
m_genblkNum = 0;
iterateChildren(nodep);
}
void visit(AstGenIf* nodep) override {
cleanFileline(nodep);
checkIndent(nodep, nodep->elsesp() ? nodep->elsesp() : nodep->thensp());
const bool nestedIf = (VN_IS(nodep->backp(), GenBlock)
&& nestedIfBegin(VN_CAST(nodep->backp(), GenBlock)));
if (nestedIf) {
iterateChildren(nodep);
} else {
++m_genblkNum;
VL_RESTORER(m_genblkAbove);
VL_RESTORER(m_genblkNum);
m_genblkAbove = m_genblkNum;
m_genblkNum = 0;
iterateChildren(nodep);
}
}
void visit(AstCell* nodep) override {
if (nodep->origName().empty()) {
if (!VN_IS(nodep->modp(), Primitive)) { // Module/Program/Iface
nodep->modNameFileline()->v3error("Instance of " << nodep->modp()->verilogKwd()
<< " must be named");
}
// UDPs can have empty instance names. Assigning unique names for them to prevent any
// conflicts
const string newName = "$unnamedudp" + cvtToStr(++m_anonUdpId);
nodep->name(newName);
nodep->origName(newName);
}
iterateChildren(nodep);
}
void visit(AstNodeBlock* nodep) override {
{
VL_RESTORER(m_blockp);
m_blockp = nodep;
// Temporarily unlink the statements so variable initializers can be inserted in order
AstNode* const stmtsp = nodep->stmtsp();
if (stmtsp) stmtsp->unlinkFrBackWithNext();
iterateAndNextNull(nodep->declsp());
nodep->addStmtsp(stmtsp);
}
if (AstBegin* const beginp = VN_CAST(nodep, Begin)) {
V3Control::applyCoverageBlock(m_modp, beginp);
}
cleanFileline(nodep);
iterateAndNextNull(nodep->stmtsp());
if (AstFork* const forkp = VN_CAST(nodep, Fork)) iterateAndNextNull(forkp->forksp());
}
void visit(AstCase* nodep) override {
V3Control::applyCase(nodep);
cleanFileline(nodep);
iterateChildren(nodep);
}
void visit(AstDot* nodep) override {
cleanFileline(nodep);
iterateChildren(nodep);
if (VN_IS(nodep->lhsp(), ParseRef) && nodep->lhsp()->name() == "super"
&& VN_IS(nodep->rhsp(), New)) {
// Look for other statements until hit function start
AstNode* scanp = nodep;
// Skip over the New's statement
for (; scanp && !VN_IS(scanp, StmtExpr); scanp = scanp->backp()) {}
if (VN_IS(scanp, StmtExpr)) { // Ignore warning if something not understood
scanp = scanp->backp();
for (; scanp; scanp = scanp->backp()) {
if (VN_IS(scanp, NodeStmt) || VN_IS(scanp, NodeModule)
|| VN_IS(scanp, NodeFTask))
break;
}
if (!VN_IS(scanp, NodeFTask)) {
nodep->rhsp()->v3error(
"'super.new' not first statement in new function (IEEE 1800-2023 8.15)\n"
<< nodep->rhsp()->warnContextPrimary() << scanp->warnOther()
<< "... Location of earlier statement\n"
<< scanp->warnContextSecondary());
}
}
}
}
void visit(AstIf* nodep) override {
cleanFileline(nodep);
checkIndent(nodep, nodep->elsesp() ? nodep->elsesp() : nodep->thensp());
iterateChildren(nodep);
}
void visit(AstPrintTimeScale* nodep) override {
// Inlining may change hierarchy, so just save timescale where needed
cleanFileline(nodep);
iterateChildren(nodep);
nodep->name(m_modp->name());
nodep->timeunit(m_modp->timeunit());
}
void visit(AstSFormatF* nodep) override {
cleanFileline(nodep);
iterateChildren(nodep);
nodep->timeunit(m_modp->timeunit());
}
void visit(AstSScanF* nodep) override {
cleanFileline(nodep);
iterateChildren(nodep);
nodep->timeunit(m_modp->timeunit());
}
void visit(AstTime* nodep) override {
cleanFileline(nodep);
iterateChildren(nodep);
nodep->timeunit(m_modp->timeunit());
}
void visit(AstTimeD* nodep) override {
cleanFileline(nodep);
iterateChildren(nodep);
nodep->timeunit(m_modp->timeunit());
}
void visit(AstTimeImport* nodep) override {
cleanFileline(nodep);
iterateChildren(nodep);
nodep->timeunit(m_modp->timeunit());
}
void visit(AstTimeUnit* nodep) override {
cleanFileline(nodep);
iterateChildren(nodep);
nodep->timeunit(m_modp->timeunit());
}
void visit(AstEventControl* nodep) override {
cleanFileline(nodep);
iterateChildren(nodep);
AstAlways* const alwaysp = VN_CAST(nodep->backp(), Always);
if (alwaysp && alwaysp->keyword() == VAlwaysKwd::ALWAYS_COMB) {
alwaysp->v3error("Event control statements not legal under always_comb "
"(IEEE 1800-2023 9.2.2.2.2)\n"
<< alwaysp->warnMore() << "... Suggest use a normal 'always'");
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
} else if (alwaysp && !alwaysp->sentreep()) {
// If the event control is at the top, move the sentree to the always
if (AstSenTree* const sentreep = nodep->sentreep()) {
sentreep->unlinkFrBackWithNext();
alwaysp->sentreep(sentreep);
}
if (nodep->stmtsp()) alwaysp->addStmtsp(nodep->stmtsp()->unlinkFrBackWithNext());
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
}
}
void visit(AstClocking* nodep) override {
cleanFileline(nodep);
VL_RESTORER(m_defaultInSkewp);
VL_RESTORER(m_defaultOutSkewp);
// Find default input and output skews
AstClockingItem* nextItemp = nodep->itemsp();
for (AstClockingItem* itemp = nextItemp; itemp; itemp = nextItemp) {
nextItemp = VN_AS(itemp->nextp(), ClockingItem);
if (itemp->exprp() || itemp->assignp()) continue;
if (itemp->skewp()) {
if (itemp->direction() == VDirection::INPUT) {
// Disallow default redefinition; note some simulators allow this
if (m_defaultInSkewp) {
itemp->skewp()->v3error("Multiple default input skews not allowed");
}
m_defaultInSkewp = itemp->skewp();
} else if (itemp->direction() == VDirection::OUTPUT) {
if (AstConst* const constp = VN_CAST(itemp->skewp(), Const)) {
if (constp->num().is1Step()) {
itemp->skewp()->v3error("1step not allowed as output skew");
}
}
// Disallow default redefinition; note some simulators allow this
if (m_defaultOutSkewp) {
itemp->skewp()->v3error("Multiple default output skews not allowed");
}
m_defaultOutSkewp = itemp->skewp();
} else {
itemp->v3fatalSrc("Incorrect direction");
}
}
VL_DO_DANGLING(pushDeletep(itemp->unlinkFrBack()), itemp);
}
iterateChildren(nodep);
}
void visit(AstClockingItem* nodep) override {
cleanFileline(nodep);
if (nodep->direction() == VDirection::OUTPUT) {
if (!nodep->skewp()) {
if (m_defaultOutSkewp) {
nodep->skewp(m_defaultOutSkewp->cloneTree(false));
} else {
// Default is 0 (IEEE 1800-2023 14.3)
nodep->skewp(new AstConst{nodep->fileline(), 0});
}
} else if (AstConst* const constp = VN_CAST(nodep->skewp(), Const)) {
if (constp->num().is1Step()) {
nodep->skewp()->v3error("1step not allowed as output skew");
}
}
} else if (nodep->direction() == VDirection::INPUT) {
if (!nodep->skewp()) {
if (m_defaultInSkewp) {
nodep->skewp(m_defaultInSkewp->cloneTree(false));
} else {
// Default is 1step (IEEE 1800-2023 14.3)
nodep->skewp(new AstConst{nodep->fileline(), AstConst::OneStep{}});
}
}
}
iterateChildren(nodep);
}
void visit(AstPackageImport* nodep) override {
cleanFileline(nodep);
if (m_modp && !m_ftaskp && VN_IS(m_modp, Class)) {
nodep->v3error("Import statement directly within a class scope is illegal");
}
iterateChildren(nodep);
}
void visit(AstNode* nodep) override {
// Default: Just iterate
cleanFileline(nodep);
iterateChildren(nodep);
}
public:
// CONSTRUCTORS
explicit LinkParseVisitor(AstNetlist* rootp) { iterate(rootp); }
~LinkParseVisitor() override {
V3Stats::addStatSum(V3Stats::STAT_SOURCE_MODULES, m_statModules);
}
};
//######################################################################
// Link class functions
void V3LinkParse::linkParse(AstNetlist* rootp) {
UINFO(4, __FUNCTION__ << ": ");
{ LinkParseVisitor{rootp}; } // Destruct before checking
V3Global::dumpCheckGlobalTree("linkparse", 0, dumpTreeEitherLevel() >= 6);
}