mirror of
https://github.com/verilator/verilator.git
synced 2026-10-06 01:54:01 +02:00
The constant pool is now an ordinary package, created with the netlist and instantiated under $root like any other package, and most special handling has been removed, including AstConstPool. Lookups go through V3ConstPool, which has a singleton instance owned by V3Global. Static methods on V3Common form the public interfce to add/find constant pool entries. With that, the constant pool is usable at any stage during compilation, so enum and dimension tables created by V3Width, and enum value tables created by V3Randomize now also live in the constant pool instead of $unit, so identical tables are shared. Associative array constants are handled separately from unpacked tables, which used to be broken but unused. Emitted constant pool variables use direct initialization, and `constinit` with C++20 where the type allows it. This ensures we don't change a run-time in a way that would result in unintended code size increase. -fno-merge-const-pool, which was introduced years ago but never prompted a bug report is deprecated and has no effect. This is also prep for future work.
609 lines
27 KiB
C++
609 lines
27 KiB
C++
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
//*************************************************************************
|
|
// DESCRIPTION: Verilator: Commit expression widths
|
|
//
|
|
// Code available from: https://verilator.org
|
|
//
|
|
//*************************************************************************
|
|
//
|
|
// 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-FileCopyrightText: 2003-2026 Wilson Snyder
|
|
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
|
//
|
|
//*************************************************************************
|
|
//
|
|
// Remove all $signed, $unsigned, we're done with them.
|
|
//
|
|
// This step is only called on real V3Width, not intermediate e.g. widthParams
|
|
//
|
|
//*************************************************************************
|
|
|
|
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
|
|
|
|
#include "config_build.h"
|
|
#include "verilatedos.h"
|
|
|
|
#include "V3WidthCommit.h"
|
|
|
|
#include "V3MemberMap.h"
|
|
|
|
VL_DEFINE_DEBUG_FUNCTIONS;
|
|
|
|
//######################################################################
|
|
|
|
// Hash a std::pair automatically
|
|
struct PairHash final {
|
|
template <typename T, typename U>
|
|
std::size_t operator()(const std::pair<T, U>& x) const {
|
|
return std::hash<T>()(x.first) + std::hash<U>()(x.second);
|
|
}
|
|
};
|
|
|
|
//######################################################################
|
|
// Now that all widthing is complete,
|
|
// Copy all width() to widthMin(). V3Const expects this
|
|
|
|
class WidthCommitVisitor final : public VNVisitor {
|
|
// NODE STATE
|
|
// AstVar::user1p -> bool. Processed
|
|
// AstNodeFTask::user2() -> uint64_t. Non-zero if ever referenced (called)
|
|
// AstNew::user2() -> uint64_t. Count of number of references, minus references in
|
|
// functions never called
|
|
const VNUser1InUse m_inuser1;
|
|
const VNUser2InUse m_inuser2;
|
|
|
|
// STATE
|
|
AstNodeFTask* m_ftaskp = nullptr; // Current function/task
|
|
AstNodeModule* m_modp = nullptr; // Current module
|
|
const char* m_contNbap = nullptr; // In continuous- or non-blocking assignment
|
|
bool m_contReads = false; // Check read continuous automatic variables
|
|
bool m_dynsizedelem = false; // Writing dynamically-sized array element, not the array itself
|
|
VMemberMap m_memberMap; // Member names cached for fast lookup
|
|
bool m_taskRefWarn = true; // Allow task reference warnings
|
|
bool m_underSel = false; // Under AstMemberSel or AstSel
|
|
bool m_underAlwaysEdged = false; // Under always with sequential SenTree
|
|
std::vector<AstNew*> m_virtualNewsp; // Instantiations of virtual classes
|
|
std::vector<AstNodeFTask*> m_tasksp; // All the tasks, we will check if they are ever called
|
|
|
|
public:
|
|
// METHODS
|
|
VL_DEFINE_DEBUG_FUNCTIONS;
|
|
|
|
private:
|
|
// METHODS
|
|
void editDType(AstNode* nodep) {
|
|
// Called by every visitor. Edit dtypes for this node, also check for some warnings
|
|
nodep->dtypep(editOneDType(nodep->dtypep()));
|
|
if (m_ftaskp && m_ftaskp->verilogFunction() && m_taskRefWarn && nodep->isTimingControl())
|
|
nodep->v3warn(
|
|
FUNCTIMECTL,
|
|
"Functions cannot contain time-controlling statements (IEEE 1800-2023 13.4)\n"
|
|
<< nodep->warnContextPrimary() << "\n"
|
|
<< nodep->warnMore() << "... Suggest make caller 'function "
|
|
<< m_ftaskp->prettyName() << "' a task\n"
|
|
<< m_ftaskp->warnContextSecondary());
|
|
}
|
|
AstNodeDType* editOneDType(AstNodeDType* nodep) {
|
|
// See if the dtype/refDType can be converted to a standard one
|
|
// This reduces the number of dtypes in the system, and since
|
|
// dtypep() figures into sameTree() results in better optimizations
|
|
if (!nodep) return nullptr;
|
|
// Recurse to handle the data type, as may change the size etc of this type
|
|
if (!nodep->user1()) iterate(nodep);
|
|
// Look for duplicate
|
|
if (AstBasicDType* const bdtypep = VN_CAST(nodep, BasicDType)) {
|
|
AstBasicDType* const newp = nodep->findInsertSameDType(bdtypep);
|
|
if (newp != bdtypep && debug() >= 9) {
|
|
UINFO_PREFIX("dtype replacement ");
|
|
nodep->dumpSmall(std::cout);
|
|
std::cout << " ----> ";
|
|
newp->dumpSmall(std::cout);
|
|
std::cout << '\n';
|
|
}
|
|
return newp;
|
|
}
|
|
return nodep;
|
|
}
|
|
void classEncapCheck(AstNode* nodep, AstNode* defp, AstClass* defClassp) {
|
|
// Check local/protected status and complain
|
|
bool local = false;
|
|
bool prot = false;
|
|
if (!defp) {
|
|
// rand_mode() / constraint_mode() handled in V3Randomize
|
|
UASSERT_OBJ(nodep->name() == "rand_mode" || nodep->name() == "constraint_mode", nodep,
|
|
"Only rand_mode() and constraint_mode() can have no def");
|
|
return;
|
|
}
|
|
if (const auto anodep = VN_CAST(defp, Var)) {
|
|
local = anodep->isHideLocal();
|
|
prot = anodep->isHideProtected();
|
|
} else if (const auto anodep = VN_CAST(defp, NodeFTask)) {
|
|
local = anodep->isHideLocal();
|
|
prot = anodep->isHideProtected();
|
|
} else if (const auto anodep = VN_CAST(defp, Typedef)) {
|
|
local = anodep->isHideLocal();
|
|
prot = anodep->isHideProtected();
|
|
} else {
|
|
nodep->v3fatalSrc("ref to unhandled definition type " << defp->prettyTypeName());
|
|
}
|
|
if (local || prot) {
|
|
// In case of covergroup, the reference is to the enclosing class, not the covergroup
|
|
// itself
|
|
const AstClass* refClassp = VN_CAST(m_modp, Class);
|
|
if (refClassp && refClassp->isCovergroup())
|
|
refClassp = refClassp->covergroupEnclosingClassp();
|
|
const char* how = nullptr;
|
|
// Inner nested classes can access `local` or `protected` members of their outer class
|
|
const auto nestedAccess = [refClassp](const AstClass*, const AstNode* memberp) {
|
|
return memberp == refClassp;
|
|
};
|
|
if (local && defClassp
|
|
&& ((refClassp != defClassp) && !(defClassp->existsMember(nestedAccess)))) {
|
|
how = "'local'";
|
|
} else if (prot && defClassp && !AstClass::isClassExtendedFrom(refClassp, defClassp)
|
|
&& !(defClassp->existsMember(nestedAccess))) {
|
|
how = "'protected'";
|
|
}
|
|
if (how) {
|
|
UINFO(9, "refclass " << refClassp);
|
|
UINFO(9, "defclass " << defClassp);
|
|
nodep->v3warn(ENCAPSULATED, nodep->prettyNameQ()
|
|
<< " is hidden as " << how
|
|
<< " within this context (IEEE 1800-2023 8.18)\n"
|
|
<< nodep->warnContextPrimary() << "\n"
|
|
<< nodep->warnOther()
|
|
<< "... Location of definition\n"
|
|
<< defp->warnContextSecondary());
|
|
}
|
|
}
|
|
}
|
|
void varLifetimeCheck(AstNode* nodep, AstVar* varp) {
|
|
// Skip if we are under a member select (lhs of a dot)
|
|
// We don't care about lifetime of anything else than rhs of a dot
|
|
if (!m_underSel && m_contNbap) {
|
|
std::string varType;
|
|
const AstNodeDType* const varDtp = varp->dtypep()->skipRefp();
|
|
if (varp->lifetime().isAutomatic() && !VN_IS(varDtp, IfaceRefDType)
|
|
&& !(varp->isFuncLocal() && varp->isIO()))
|
|
varType = "Automatic lifetime";
|
|
else if (varp->isClassMember() && !varp->lifetime().isStatic()
|
|
&& !VN_IS(varDtp, IfaceRefDType))
|
|
varType = "Class non-static";
|
|
else if (varDtp->isDynamicallySized() && m_dynsizedelem)
|
|
varType = "Dynamically-sized";
|
|
if (!varType.empty()) {
|
|
UINFO(1, " Related var dtype: " << varDtp);
|
|
nodep->v3error(varType
|
|
<< " variable not allowed in " << m_contNbap
|
|
<< " assignment (IEEE 1800-2023 6.21): " << varp->prettyNameQ());
|
|
}
|
|
}
|
|
}
|
|
|
|
void deadCheckTasks() {
|
|
for (AstNodeFTask* taskp : m_tasksp) {
|
|
if (!taskp->user2()) {
|
|
taskp->foreach([](AstNew* newp) { newp->user2Inc(-1); });
|
|
}
|
|
}
|
|
for (AstNew* newp : m_virtualNewsp) {
|
|
if (newp->user2() > 0)
|
|
newp->v3error("Illegal to call 'new' using an abstract virtual class "
|
|
+ AstNode::prettyNameQ(newp->classOrPackagep()->origName())
|
|
+ " (IEEE 1800-2023 8.21)");
|
|
}
|
|
}
|
|
|
|
void virtualRecurse(AstNodeFTask* nodep, const AstClass* classp, bool& isVirtualr) {
|
|
static std::unordered_map<std::pair<const AstClass*, const AstNodeFTask*>, bool, PairHash>
|
|
s_classFuncsChecked; // Track what was recursed to avoid O(class*funcs^2)
|
|
// IEEE doesn't require virtual marking at derived classes' functions.
|
|
// Propagate virtual marking from base class function upwards.
|
|
auto pair = s_classFuncsChecked.emplace(std::make_pair(classp, nodep), isVirtualr);
|
|
if (!pair.second) {
|
|
if (pair.first->second) isVirtualr = true;
|
|
return;
|
|
}
|
|
//
|
|
if (nodep->isVirtual()) isVirtualr = true;
|
|
// Propagate value from extends/implements
|
|
for (AstClassExtends* extendsp = classp->extendsp(); extendsp;
|
|
extendsp = VN_AS(extendsp->nextp(), ClassExtends)) {
|
|
const AstClass* const eclassp = extendsp->classp();
|
|
if (AstNodeFTask* const fbasep
|
|
= VN_CAST(m_memberMap.findMember(eclassp, nodep->name()), NodeFTask)) {
|
|
if (fbasep != nodep) {
|
|
virtualRecurse(fbasep, eclassp, isVirtualr);
|
|
continue;
|
|
}
|
|
}
|
|
virtualRecurse(nodep, eclassp, isVirtualr);
|
|
}
|
|
if (isVirtualr) {
|
|
nodep->isVirtual(true);
|
|
// Update memoize map in case had a false when first created
|
|
s_classFuncsChecked[std::make_pair(classp, nodep)] = isVirtualr;
|
|
}
|
|
}
|
|
|
|
// VISITORS
|
|
void visit(AstNodeModule* nodep) override {
|
|
VL_RESTORER(m_modp);
|
|
m_modp = nodep;
|
|
iterateChildren(nodep);
|
|
editDType(nodep);
|
|
if (AstClass* const classp = VN_CAST(nodep, Class)) {
|
|
for (AstClassExtends* extendsp = classp->extendsp(); extendsp;
|
|
extendsp = extendsp->classp()->extendsp()) {
|
|
const AstClass* const ebasep = extendsp->classp();
|
|
if (ebasep->baseOverride().isFinal()) {
|
|
extendsp->v3error("Class " << nodep->prettyNameQ()
|
|
<< " is being extended from class marked ':final'"
|
|
" (IEEE 1800-2023 8.20)\n"
|
|
<< extendsp->warnContextPrimary() << "\n"
|
|
<< ebasep->warnOther()
|
|
<< "... Location of ':final' class being extended\n"
|
|
<< ebasep->warnContextSecondary());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
void visit(AstAlways* nodep) override {
|
|
// As have not optimized SenTrees yet, an 'always .*' will be on first and only SenItem
|
|
if (nodep->sentreep() && nodep->sentreep()->sensesp()
|
|
&& nodep->sentreep()->sensesp()->isComboStar()) {
|
|
const bool noReads = nodep->forall(
|
|
[&](const AstNodeVarRef* refp) { return !refp->access().isReadOrRW(); });
|
|
if (noReads) {
|
|
nodep->v3warn(ALWNEVER, "'always @*' will never execute as expression list is "
|
|
"empty (no variables read)\n"
|
|
<< nodep->warnMore()
|
|
<< "... Suggest use 'always_comb'");
|
|
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
|
|
return;
|
|
}
|
|
}
|
|
VL_RESTORER(m_underAlwaysEdged);
|
|
m_underAlwaysEdged
|
|
= nodep->sentreep() && nodep->sentreep()->sensesp() && nodep->sentreep()->hasEdge();
|
|
// Iterate will delete ComboStar sentrees, so after above
|
|
iterateChildren(nodep);
|
|
editDType(nodep);
|
|
}
|
|
void visit(AstFork* nodep) override {
|
|
VL_RESTORER(m_taskRefWarn);
|
|
// fork..join_any is allowed to call tasks, and UVM does this
|
|
if (!nodep->isTimingControl()) m_taskRefWarn = false;
|
|
iterateChildren(nodep);
|
|
editDType(nodep);
|
|
}
|
|
void visit(AstSenTree* nodep) override {
|
|
if (nodep->sensesp() && nodep->sensesp()->isComboStar()) {
|
|
UASSERT_OBJ(!nodep->sensesp()->nextp(), nodep, "Shouldn't be senitems after .*");
|
|
// Make look like standalone always
|
|
// (Rest of code assumed this before .* existed)
|
|
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
|
|
return;
|
|
}
|
|
iterateChildren(nodep);
|
|
editDType(nodep);
|
|
}
|
|
void visit(AstAttrOf* nodep) override {
|
|
switch (nodep->attrType()) {
|
|
case VAttrType::FUNC_ARG_PROTO: // FALLTHRU
|
|
case VAttrType::FUNC_RETURN_PROTO:
|
|
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
|
|
return;
|
|
default:;
|
|
}
|
|
iterateChildren(nodep);
|
|
editDType(nodep);
|
|
}
|
|
void visit(AstClassExtends* nodep) override {
|
|
if (nodep->user1SetOnce()) return; // Process once
|
|
// Extend arguments were converted to super.new arguments in V3LinkDot
|
|
if (nodep->argsp()) pushDeletep(nodep->argsp()->unlinkFrBackWithNext());
|
|
iterateChildren(nodep);
|
|
}
|
|
void visit(AstConst* nodep) override {
|
|
if (nodep->user1SetOnce()) return; // Process once
|
|
UASSERT_OBJ(nodep->dtypep(), nodep, "No dtype");
|
|
iterate(nodep->dtypep()); // Do datatype first
|
|
if (AstConst* const newp = V3WidthCommit::newIfConstCommitSize(nodep)) {
|
|
nodep->replaceWith(newp);
|
|
AstNode* const oldp = nodep;
|
|
nodep = newp;
|
|
// UINFOTREE(5, oldp, "", "fixConstSize_old");
|
|
// UINFOTREE(5, newp, "", "_new");
|
|
VL_DO_DANGLING(pushDeletep(oldp), oldp);
|
|
}
|
|
editDType(nodep);
|
|
}
|
|
void visit(AstCastWrap* nodep) override {
|
|
iterateChildren(nodep);
|
|
editDType(nodep);
|
|
UINFO(6, " Replace " << nodep << " w/ " << nodep->lhsp());
|
|
nodep->replaceWith(nodep->lhsp()->unlinkFrBack());
|
|
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
|
}
|
|
void visit(AstConstraint* nodep) override {
|
|
iterateChildren(nodep);
|
|
editDType(nodep);
|
|
{
|
|
const AstClass* const classp = VN_CAST(m_modp, Class);
|
|
if (nodep->isKwdPure()
|
|
&& (!classp || (!classp->isInterfaceClass() && !classp->isVirtual()))) {
|
|
nodep->v3error("Illegal to have 'pure constraint' in non-abstract class"
|
|
" (IEEE 1800-2023 18.5.2)");
|
|
}
|
|
}
|
|
}
|
|
void visit(AstNodeDType* nodep) override {
|
|
// Note some specific dtypes have unique visitors
|
|
visitIterateNodeDType(nodep);
|
|
}
|
|
void visit(AstNodeUOrStructDType* nodep) override {
|
|
if (nodep->user1SetOnce()) return; // Process once
|
|
visitIterateNodeDType(nodep);
|
|
}
|
|
void visit(AstParamTypeDType* nodep) override {
|
|
if (nodep->user1SetOnce()) return; // Process once
|
|
visitIterateNodeDType(nodep);
|
|
// Move to type table as all dtype pointers must resolve there
|
|
nodep->unlinkFrBack(); // Make non-child
|
|
v3Global.rootp()->typeTablep()->addTypesp(nodep);
|
|
}
|
|
void visit(AstRefDType* nodep) override {
|
|
visitIterateNodeDType(nodep);
|
|
if (!nodep->typedefp()) return; // Already checked and cleared
|
|
classEncapCheck(nodep, nodep->typedefp(), VN_CAST(nodep->classOrPackagep(), Class));
|
|
nodep->typedefp(nullptr); // No longer needed
|
|
}
|
|
void visitIterateNodeDType(AstNodeDType* nodep) {
|
|
// Rather than use dtypeChg which may make new nodes, we edit in place,
|
|
// as we don't need to preserve any widthMin's, and every dtype with the same width
|
|
// gets an identical edit.
|
|
if (nodep->user1SetOnce()) return; // Process once
|
|
nodep->widthMinFromWidth();
|
|
// Too late to any unspecified sign to be anything but unsigned
|
|
if (nodep->numeric().isNosign()) nodep->numeric(VSigning::UNSIGNED);
|
|
iterateChildren(nodep);
|
|
nodep->virtRefDTypep(editOneDType(nodep->virtRefDTypep()));
|
|
nodep->virtRefDType2p(editOneDType(nodep->virtRefDType2p()));
|
|
}
|
|
void visit(AstNodeFTask* nodep) override {
|
|
if (!nodep->taskPublic() && !nodep->dpiExport() && !nodep->dpiImport())
|
|
m_tasksp.push_back(nodep);
|
|
VL_RESTORER(m_ftaskp);
|
|
m_ftaskp = nodep;
|
|
iterateChildren(nodep);
|
|
editDType(nodep);
|
|
bool isVirtual = false;
|
|
if (const AstClass* const classp = VN_CAST(m_modp, Class)) {
|
|
virtualRecurse(nodep, classp, isVirtual /*ref*/);
|
|
}
|
|
if (nodep->isStatic() && nodep->isVirtual()) // After propagated isVirtual
|
|
nodep->v3error("Static methods cannot be virtual");
|
|
{
|
|
const AstClass* const classp = VN_CAST(m_modp, Class);
|
|
if (nodep->classMethod() && nodep->pureVirtual() && classp
|
|
&& !classp->isInterfaceClass() && !classp->isVirtual()) {
|
|
nodep->v3error(
|
|
"Illegal to have 'pure virtual' in non-virtual class (IEEE 1800-2023 8.21)");
|
|
}
|
|
}
|
|
bool extended = false;
|
|
if (const AstClass* const classp = VN_CAST(m_modp, Class)) {
|
|
// Walk down inheritance
|
|
for (AstClassExtends* extendsp = classp->extendsp(); extendsp;
|
|
extendsp = extendsp->classp()->extendsp()) {
|
|
const AstClass* const eclassp = extendsp->classp();
|
|
if (AstNodeFTask* const fbasep
|
|
= VN_CAST(m_memberMap.findMember(eclassp, nodep->name()), NodeFTask)) {
|
|
if (fbasep != nodep) {
|
|
extended = true;
|
|
if (nodep->baseOverride().isInitial()) {
|
|
nodep->v3error("Member "
|
|
<< nodep->prettyNameQ()
|
|
<< " is marked ':initial' but is being extended"
|
|
" (IEEE 1800-2023 8.20)\n"
|
|
<< nodep->warnContextPrimary() << "\n"
|
|
<< fbasep->warnOther()
|
|
<< "... Location of declaration being extended\n"
|
|
<< fbasep->warnContextSecondary());
|
|
}
|
|
if (fbasep->baseOverride().isFinal()) {
|
|
nodep->v3error(
|
|
"Member "
|
|
<< nodep->prettyNameQ()
|
|
<< " is being extended from member marked ':final'"
|
|
" (IEEE 1800-2023 8.20)\n"
|
|
<< nodep->warnContextPrimary() << "\n"
|
|
<< fbasep->warnOther()
|
|
<< "... Location of ':final' declaration being extended\n"
|
|
<< fbasep->warnContextSecondary());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!extended && nodep->baseOverride().isExtends()) {
|
|
nodep->v3error("Member " << nodep->prettyNameQ()
|
|
<< " marked ':extends' but no base class function is"
|
|
" being extend (IEEE 1800-2023 8.20)");
|
|
}
|
|
}
|
|
void visit(AstNodeVarRef* nodep) override {
|
|
iterateChildren(nodep);
|
|
editDType(nodep);
|
|
classEncapCheck(nodep, nodep->varp(), VN_CAST(nodep->classOrPackagep(), Class));
|
|
if (nodep->access().isWriteOrRW() || m_contReads) varLifetimeCheck(nodep, nodep->varp());
|
|
if (VN_IS(nodep, VarRef))
|
|
nodep->name(""); // Clear to save memory; nodep->name() will work via nodep->varp()
|
|
}
|
|
void visit(AstAssign* nodep) override {
|
|
iterateChildren(nodep);
|
|
editDType(nodep);
|
|
// Lint
|
|
if (m_underAlwaysEdged) {
|
|
const bool ignore = nodep->lhsp()->forall([&](const AstVarRef* refp) {
|
|
// Ignore reads (e.g.: index expressions)
|
|
if (refp->access().isReadOnly()) return true;
|
|
const AstVar* const varp = refp->varp();
|
|
// Ignore ...
|
|
return varp->isUsedLoopIdx() // ... loop indices
|
|
|| varp->isTemp() // ... temporaries
|
|
|| varp->fileline()->warnIsOff(V3ErrorCode::BLKSEQ); // ... user said so
|
|
});
|
|
if (!ignore) {
|
|
nodep->v3warn(BLKSEQ,
|
|
"Blocking assignment '=' in sequential logic process\n"
|
|
<< nodep->warnMore() //
|
|
<< "... Suggest using delayed assignment '<='");
|
|
}
|
|
}
|
|
}
|
|
void visit(AstAssignCont* nodep) override {
|
|
iterateAndNextNull(nodep->timingControlp());
|
|
{
|
|
VL_RESTORER(m_contNbap);
|
|
VL_RESTORER(m_contReads);
|
|
m_contNbap = "continuous";
|
|
m_contReads = true;
|
|
iterateAndNextNull(nodep->lhsp());
|
|
iterateAndNextNull(nodep->rhsp());
|
|
}
|
|
editDType(nodep);
|
|
}
|
|
void visit(AstAssignDly* nodep) override {
|
|
iterateAndNextNull(nodep->timingControlp());
|
|
iterateAndNextNull(nodep->rhsp());
|
|
{
|
|
VL_RESTORER(m_contNbap);
|
|
VL_RESTORER(m_contReads);
|
|
m_contNbap = "nonblocking";
|
|
m_contReads = false;
|
|
iterateAndNextNull(nodep->lhsp());
|
|
}
|
|
editDType(nodep);
|
|
}
|
|
void visit(AstAssignW* nodep) override {
|
|
iterateAndNextNull(nodep->timingControlp());
|
|
iterateAndNextNull(nodep->rhsp());
|
|
{
|
|
VL_RESTORER(m_contNbap);
|
|
VL_RESTORER(m_contReads);
|
|
m_contNbap = "continuous";
|
|
m_contReads = false;
|
|
iterateAndNextNull(nodep->lhsp());
|
|
}
|
|
editDType(nodep);
|
|
}
|
|
void visit(AstNodeFTaskRef* nodep) override {
|
|
iterateChildren(nodep);
|
|
editDType(nodep);
|
|
classEncapCheck(nodep, nodep->taskp(), VN_CAST(nodep->classOrPackagep(), Class));
|
|
if (nodep->taskp() && nodep->taskp()->verilogTask() && m_ftaskp
|
|
&& m_ftaskp->verilogFunction() && m_taskRefWarn) {
|
|
nodep->v3warn(FUNCTIMECTL,
|
|
"Functions cannot invoke tasks (IEEE 1800-2023 13.4)\n"
|
|
<< nodep->warnContextPrimary() << "\n"
|
|
<< nodep->warnMore() << "... Suggest make caller 'function "
|
|
<< m_ftaskp->prettyName() << "' a task\n"
|
|
<< m_ftaskp->warnContextSecondary() << "\n"
|
|
<< nodep->warnMore() << "... Or, suggest make called 'task "
|
|
<< nodep->taskp()->prettyName() << "' a function void\n"
|
|
<< nodep->taskp()->warnContextSecondary());
|
|
}
|
|
if (nodep->taskp()) nodep->taskp()->user2(1);
|
|
if (AstNew* const newp = VN_CAST(nodep, New)) {
|
|
if (!VN_IS(newp->backp(), Assign)) return;
|
|
if (AstClass* const classp = VN_CAST(newp->classOrPackagep(), Class)) {
|
|
if (classp->isVirtual() || classp->isInterfaceClass()) {
|
|
m_virtualNewsp.push_back(newp);
|
|
newp->user2Inc();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
void visit(AstMemberSel* nodep) override {
|
|
{
|
|
VL_RESTORER(m_underSel);
|
|
m_underSel = true;
|
|
iterateChildren(nodep);
|
|
}
|
|
editDType(nodep);
|
|
if (AstClassRefDType* const classrefp = VN_CAST(nodep->fromp()->dtypep(), ClassRefDType)) {
|
|
classEncapCheck(nodep, nodep->varp(), classrefp->classp());
|
|
} // else might be struct, etc
|
|
varLifetimeCheck(nodep, nodep->varp());
|
|
}
|
|
void visit(AstVar* nodep) override {
|
|
iterateChildren(nodep);
|
|
editDType(nodep);
|
|
if (nodep->isFuncLocal() && nodep->direction() == VDirection::INPUT && nodep->valuep()) {
|
|
// It's the default value of optional argument.
|
|
// Such values are added to function calls in V3Width so can be removed now
|
|
pushDeletep(nodep->valuep()->unlinkFrBack());
|
|
}
|
|
}
|
|
void visit(AstNodePreSel* nodep) override { // LCOV_EXCL_LINE
|
|
// This check could go anywhere after V3Param
|
|
nodep->v3fatalSrc("Presels should have been removed before this point");
|
|
}
|
|
void visit(AstCMethodHard* nodep) override {
|
|
VL_RESTORER(m_dynsizedelem);
|
|
if (nodep->method() == VCMethod::ARRAY_AT || nodep->method() == VCMethod::ARRAY_AT_WRITE
|
|
|| nodep->method() == VCMethod::DYN_AT_WRITE_APPEND)
|
|
m_dynsizedelem = true;
|
|
iterateChildren(nodep);
|
|
editDType(nodep);
|
|
}
|
|
void visit(AstAssocSel* nodep) override {
|
|
VL_RESTORER(m_dynsizedelem);
|
|
m_dynsizedelem = true;
|
|
iterateChildren(nodep);
|
|
editDType(nodep);
|
|
}
|
|
void visit(AstSel* nodep) override {
|
|
{
|
|
VL_RESTORER(m_underSel);
|
|
m_underSel = true;
|
|
iterateChildren(nodep);
|
|
}
|
|
editDType(nodep);
|
|
}
|
|
void visit(AstClassOrPackageRef* nodep) override {
|
|
// Reference must have been resolved, can delete these
|
|
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
|
|
}
|
|
void visit(AstNode* nodep) override {
|
|
iterateChildren(nodep);
|
|
editDType(nodep);
|
|
}
|
|
|
|
public:
|
|
// CONSTRUCTORS
|
|
explicit WidthCommitVisitor(AstNetlist* nodep) {
|
|
// Were changing widthMin's, so the table is now somewhat trashed
|
|
nodep->typeTablep()->clearCache();
|
|
iterate(nodep);
|
|
deadCheckTasks();
|
|
// Don't want to AstTypeTable::repairCache, as all needed nodes
|
|
// have been added back in; a repair would prevent dead nodes from
|
|
// being detected
|
|
}
|
|
~WidthCommitVisitor() override = default;
|
|
};
|
|
|
|
//######################################################################
|
|
// V3WidthCommit class functions
|
|
|
|
void V3WidthCommit::widthCommit(AstNetlist* nodep) {
|
|
UINFO(2, __FUNCTION__ << ":");
|
|
{ WidthCommitVisitor{nodep}; } // Destruct before checking
|
|
V3Global::dumpCheckGlobalTree("widthcommit", 0, dumpTreeEitherLevel() >= 6);
|
|
}
|