Files
verilator/src/V3Param.cpp
T
Geza Lore fd35492226 Split V3Hashed to V3Hasher and V3DupFinder (#2967)
V3Hasher is responsible for computing AstNode hashes, while V3DupFinder
can be used to find duplicate trees based on hashes. Interface of
V3DupFinder simplified somewhat. No functional change intended at this
point, but hash computation might differ in minor details, this however
should have no perceivable effect on output/runtime.

Implements (#2964)
2021-05-21 01:41:46 +01:00

1202 lines
58 KiB
C++

// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Replicate modules for parameterization
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// Copyright 2003-2021 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
//
//*************************************************************************
// PARAM TRANSFORMATIONS:
// Top down traversal:
// For each cell:
// If parameterized,
// Determine all parameter widths, constant values.
// (Interfaces also matter, as if an interface is parameterized
// this effectively changes the width behavior of all that
// reference the iface.)
// Clone module cell calls, renaming with __{par1}_{par2}_...
// Substitute constants for cell's module's parameters.
// Relink pins and cell and ifacerefdtype to point to new module.
//
// For interface Parent's we have the AstIfaceRefDType::cellp()
// pointing to this module. If that parent cell's interface
// module gets parameterized, AstIfaceRefDType::cloneRelink
// will update AstIfaceRefDType::cellp(), and V3LinkDot will
// see the new interface.
//
// However if a submodule's AstIfaceRefDType::ifacep() points
// to the old (unparameterized) interface and needs correction.
// To detect this we must walk all pins looking for interfaces
// that the parent has changed and propagate down.
//
// Then process all modules called by that cell.
// (Cells never referenced after parameters expanded must be ignored.)
//
// After we complete parameters, the varp's will be wrong (point to old module)
// and must be relinked.
//
//*************************************************************************
#include "config_build.h"
#include "verilatedos.h"
#include "V3Global.h"
#include "V3Param.h"
#include "V3Ast.h"
#include "V3Case.h"
#include "V3Const.h"
#include "V3Os.h"
#include "V3Parse.h"
#include "V3Width.h"
#include "V3Unroll.h"
#include "V3Hasher.h"
#include <deque>
#include <map>
#include <memory>
#include <vector>
//######################################################################
// Hierarchical block and parameter db (modules without parameter is also handled)
class ParameterizedHierBlocks final {
using HierBlockOptsByOrigName = std::multimap<std::string, const V3HierarchicalBlockOption*>;
using HierMapIt = HierBlockOptsByOrigName::const_iterator;
using HierBlockModMap = std::map<const std::string, AstNodeModule*>;
using ParamConstMap = std::map<const std::string, std::unique_ptr<AstConst>>;
using GParamsMap = std::map<const std::string, AstVar*>; // key:parameter name value:parameter
// MEMBERS
// key:Original module name, value:HiearchyBlockOption*
// If a module is parameterized, the module is uniquiefied to overridden parameters.
// This is why HierBlockOptsByOrigName is multimap.
HierBlockOptsByOrigName m_hierBlockOptsByOrigName;
// key:mangled module name, value:AstNodeModule*
HierBlockModMap m_hierBlockMod;
// Overridden parameters of the hierarchical block
std::map<const V3HierarchicalBlockOption*, ParamConstMap> m_hierParams;
std::map<const std::string, GParamsMap>
m_modParams; // Parameter variables of hierarchical blocks
// METHODS
VL_DEBUG_FUNC; // Declare debug()
public:
ParameterizedHierBlocks(const V3HierBlockOptSet& hierOpts, AstNetlist* nodep) {
for (const auto& hierOpt : hierOpts) {
m_hierBlockOptsByOrigName.insert(
std::make_pair(hierOpt.second.origName(), &hierOpt.second));
const V3HierarchicalBlockOption::ParamStrMap& params = hierOpt.second.params();
ParamConstMap& consts = m_hierParams[&hierOpt.second];
for (V3HierarchicalBlockOption::ParamStrMap::const_iterator pIt = params.begin();
pIt != params.end(); ++pIt) {
std::unique_ptr<AstConst> constp{AstConst::parseParamLiteral(
new FileLine(FileLine::EmptySecret()), pIt->second)};
UASSERT(constp, pIt->second << " is not a valid parameter literal");
const bool inserted = consts.emplace(pIt->first, std::move(constp)).second;
UASSERT(inserted, pIt->first << " is already added");
}
// origName may be already registered, but it's fine.
m_modParams.insert({hierOpt.second.origName(), {}});
}
for (AstNodeModule* modp = nodep->modulesp(); modp;
modp = VN_CAST(modp->nextp(), NodeModule)) {
if (hierOpts.find(modp->prettyName()) != hierOpts.end()) {
m_hierBlockMod.emplace(modp->name(), modp);
}
const auto defParamIt = m_modParams.find(modp->name());
if (defParamIt != m_modParams.end()) {
// modp is the original of parameterized hierarchical block
for (AstNode* stmtp = modp->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
if (AstVar* varp = VN_CAST(stmtp, Var)) {
if (varp->isGParam()) defParamIt->second.emplace(varp->name(), varp);
}
}
}
}
}
AstNodeModule* findByParams(const string& origName, AstPin* firstPinp,
const AstNodeModule* modp) {
if (m_hierBlockOptsByOrigName.find(origName) == m_hierBlockOptsByOrigName.end()) {
return nullptr;
}
// This module is a hierarchical block. Need to replace it by the protect-lib wrapper.
const std::pair<HierMapIt, HierMapIt> candidates
= m_hierBlockOptsByOrigName.equal_range(origName);
const auto paramsIt = m_modParams.find(origName);
UASSERT_OBJ(paramsIt != m_modParams.end(), modp, origName << " must be registered");
HierMapIt hierIt;
for (hierIt = candidates.first; hierIt != candidates.second; ++hierIt) {
bool found = true;
size_t paramIdx = 0;
const ParamConstMap& params = m_hierParams[hierIt->second];
UASSERT(params.size() == hierIt->second->params().size(), "not match");
for (AstPin* pinp = firstPinp; pinp; pinp = VN_CAST(pinp->nextp(), Pin)) {
if (!pinp->exprp()) continue;
UASSERT_OBJ(!pinp->modPTypep(), pinp,
"module with type parameter must not be a hierarchical block");
if (AstVar* modvarp = pinp->modVarp()) {
AstConst* constp = VN_CAST(pinp->exprp(), Const);
UASSERT_OBJ(constp, pinp,
"parameter for a hierarchical block must have been constified");
const auto paramIt = paramsIt->second.find(modvarp->name());
UASSERT_OBJ(paramIt != paramsIt->second.end(), modvarp, "must be registered");
AstConst* defValuep = VN_CAST(paramIt->second->valuep(), Const);
if (defValuep && areSame(constp, defValuep)) {
UINFO(5, "Setting default value of " << constp << " to " << modvarp
<< std::endl);
continue; // Skip this parameter because setting the same value
}
const auto pIt = vlstd::as_const(params).find(modvarp->name());
UINFO(5, "Comparing " << modvarp->name() << " " << constp << std::endl);
if (pIt == params.end() || paramIdx >= params.size()
|| !areSame(constp, pIt->second.get())) {
found = false;
break;
}
UINFO(5, "Matched " << modvarp->name() << " " << constp << " and "
<< pIt->second.get() << std::endl);
++paramIdx;
}
}
if (found && paramIdx == hierIt->second->params().size()) break;
}
UASSERT_OBJ(hierIt != candidates.second, firstPinp, "No protect-lib wrapper found");
// parameter settings will be removed in the bottom of caller visitCell().
const HierBlockModMap::const_iterator modIt
= m_hierBlockMod.find(hierIt->second->mangledName());
UASSERT_OBJ(modIt != m_hierBlockMod.end(), firstPinp,
hierIt->second->mangledName() << " is not found");
const auto it = vlstd::as_const(m_hierBlockMod).find(hierIt->second->mangledName());
if (it == m_hierBlockMod.end()) return nullptr;
return it->second;
}
static bool areSame(AstConst* pinValuep, AstConst* hierOptParamp) {
if (pinValuep->isString()) {
return pinValuep->num().toString() == hierOptParamp->num().toString();
}
// Bitwidth of hierOptParamp is accurate because V3Width already caluclated in the previous
// run. Bitwidth of pinValuep is before width analysis, so pinValuep is casted to
// hierOptParamp width.
V3Number varNum(pinValuep, hierOptParamp->num().width());
if (hierOptParamp->isDouble()) {
varNum.isDouble(true);
if (pinValuep->isDouble()) {
varNum.opAssign(pinValuep->num());
} else { // Cast from integer to real
varNum.opIToRD(pinValuep->num());
}
return v3EpsilonEqual(varNum.toDouble(), hierOptParamp->num().toDouble());
} else { // Now integer type is assumed
if (pinValuep->isDouble()) { // Need to cast to int
// Parameter is actually an integral type, but passed value is floating point.
// Conversion from real to integer uses rounding in V3Width.cpp
varNum.opRToIRoundS(pinValuep->num());
} else if (pinValuep->isSigned()) {
varNum.opExtendS(pinValuep->num(), pinValuep->num().width());
} else {
varNum.opAssign(pinValuep->num());
}
V3Number isEq(pinValuep, 1);
isEq.opEq(varNum, hierOptParamp->num());
return isEq.isNeqZero();
}
}
};
//######################################################################
// Remove parameters from cells and build new modules
class ParamProcessor final {
// NODE STATE - Local
// AstVar::user4() // int Global parameter number (for naming new module)
// // (0=not processed, 1=iterated, but no number,
// // 65+ parameter numbered)
// NODE STATE - Shared with ParamVisitor
// AstNodeModule::user5() // bool True if processed
// AstGenFor::user5() // bool True if processed
// AstVar::user5() // bool True if constant propagated
// AstCell::user5p() // string* Generate portion of hierarchical name
AstUser4InUse m_inuser4;
AstUser5InUse m_inuser5;
// User1/2/3 used by constant function simulations
// TYPES
// Note may have duplicate entries
using IfaceRefRefs = std::deque<std::pair<AstIfaceRefDType*, AstIfaceRefDType*>>;
// STATE
using CloneMap = std::unordered_map<const AstNode*, AstNode*>;
struct ModInfo {
AstNodeModule* m_modp; // Module with specified name
CloneMap m_cloneMap; // Map of old-varp -> new cloned varp
explicit ModInfo(AstNodeModule* modp)
: m_modp{modp} {}
};
std::map<const std::string, ModInfo> m_modNameMap; // Hash of created module flavors by name
std::map<const std::string, std::string>
m_longMap; // Hash of very long names to unique identity number
int m_longId = 0;
// All module names that are loaded from source code
// Generated modules by this visitor is not included
V3StringSet m_allModuleNames;
using ValueMapValue = std::pair<int, std::string>;
std::map<const V3Hash, ValueMapValue> m_valueMap; // Hash of node hash to (param value, name)
int m_nextValue = 1; // Next value to use in m_valueMap
AstNodeModule* m_modp = nullptr; // Current module being processed
// Database to get protect-lib wrapper that matches parameters in hierarchical Verilation
ParameterizedHierBlocks m_hierBlocks;
// Default parameter values key:parameter name, value:default value (can be nullptr)
using DefaultValueMap = std::map<std::string, AstConst*>;
// Default parameter values of hierarchical blocks
std::map<AstNodeModule*, DefaultValueMap> m_defaultParameterValues;
// METHODS
VL_DEBUG_FUNC; // Declare debug()
void makeSmallNames(AstNodeModule* modp) {
std::vector<int> usedLetter;
usedLetter.resize(256);
// Pass 1, assign first letter to each gparam's name
for (AstNode* stmtp = modp->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
if (AstVar* varp = VN_CAST(stmtp, Var)) {
if (varp->isGParam() || varp->isIfaceRef()) {
char ch = varp->name()[0];
ch = std::toupper(ch);
if (ch < 'A' || ch > 'Z') ch = 'Z';
varp->user4(usedLetter[static_cast<int>(ch)] * 256 + ch);
usedLetter[static_cast<int>(ch)]++;
}
} else if (AstParamTypeDType* typep = VN_CAST(stmtp, ParamTypeDType)) {
char ch = 'T';
typep->user4(usedLetter[static_cast<int>(ch)] * 256 + ch);
usedLetter[static_cast<int>(ch)]++;
}
}
}
string paramSmallName(AstNodeModule* modp, AstNode* varp) {
if (varp->user4() <= 1) makeSmallNames(modp);
int index = varp->user4() / 256;
char ch = varp->user4() & 255;
string st = cvtToStr(ch);
while (index) {
st += cvtToStr(char((index % 25) + 'A'));
index /= 26;
}
return st;
}
string paramValueNumber(AstNode* nodep) {
string key = nodep->name();
if (AstIfaceRefDType* ifrtp = VN_CAST(nodep, IfaceRefDType)) {
if (ifrtp->cellp() && ifrtp->cellp()->modp()) {
key = ifrtp->cellp()->modp()->name();
} else if (ifrtp->ifacep()) {
key = ifrtp->ifacep()->name();
} else {
nodep->v3fatalSrc("Can't parameterize interface without module name");
}
} else if (AstBasicDType* bdtp = VN_CAST(nodep, BasicDType)) {
if (bdtp->isRanged()) {
key += "[" + cvtToStr(bdtp->left()) + ":" + cvtToStr(bdtp->right()) + "]";
}
}
V3Hash hash = V3Hasher::uncachedHash(nodep);
// Force hash collisions -- for testing only
if (VL_UNLIKELY(v3Global.opt.debugCollision())) hash = V3Hash();
int num;
const auto it = m_valueMap.find(hash);
if (it != m_valueMap.end() && it->second.second == key) {
num = it->second.first;
} else {
num = m_nextValue++;
m_valueMap[hash] = std::make_pair(num, key);
}
return string("z") + cvtToStr(num);
}
string moduleCalcName(AstNodeModule* srcModp, const string& longname) {
string newname = longname;
if (longname.length() > 30) {
const auto iter = m_longMap.find(longname);
if (iter != m_longMap.end()) {
newname = iter->second;
} else {
newname = srcModp->name();
// We use all upper case above, so lower here can't conflict
newname += "__pi" + cvtToStr(++m_longId);
m_longMap.emplace(longname, newname);
}
}
UINFO(4, "Name: " << srcModp->name() << "->" << longname << "->" << newname << endl);
return newname;
}
AstNodeDType* arraySubDTypep(AstNodeDType* nodep) {
// If an unpacked array, return the subDTypep under it
if (AstUnpackArrayDType* adtypep = VN_CAST(nodep, UnpackArrayDType)) {
return adtypep->subDTypep();
}
// We have not resolved parameter of the child yet, so still
// have BracketArrayDType's. We'll presume it'll end up as assignment
// compatible (or V3Width will complain).
if (AstBracketArrayDType* adtypep = VN_CAST(nodep, BracketArrayDType)) {
return adtypep->subDTypep();
}
return nullptr;
}
void collectPins(CloneMap* clonemapp, AstNodeModule* modp) {
// Grab all I/O so we can remap our pins later
for (AstNode* stmtp = modp->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
if (AstVar* varp = VN_CAST(stmtp, Var)) {
if (varp->isIO() || varp->isGParam() || varp->isIfaceRef()) {
// Cloning saved a pointer to the new node for us, so just follow that link.
AstVar* oldvarp = varp->clonep();
// UINFO(8,"Clone list 0x"<<hex<<(uint32_t)oldvarp
// <<" -> 0x"<<(uint32_t)varp<<endl);
clonemapp->emplace(oldvarp, varp);
}
} else if (AstParamTypeDType* ptp = VN_CAST(stmtp, ParamTypeDType)) {
if (ptp->isGParam()) {
AstParamTypeDType* oldptp = ptp->clonep();
clonemapp->emplace(oldptp, ptp);
}
}
}
}
void relinkPins(const CloneMap* clonemapp, AstPin* startpinp) {
for (AstPin* pinp = startpinp; pinp; pinp = VN_CAST(pinp->nextp(), Pin)) {
if (pinp->modVarp()) {
// Find it in the clone structure
// UINFO(8,"Clone find 0x"<<hex<<(uint32_t)pinp->modVarp()<<endl);
const auto cloneiter = clonemapp->find(pinp->modVarp());
UASSERT_OBJ(cloneiter != clonemapp->end(), pinp,
"Couldn't find pin in clone list");
pinp->modVarp(VN_CAST(cloneiter->second, Var));
} else if (pinp->modPTypep()) {
const auto cloneiter = clonemapp->find(pinp->modPTypep());
UASSERT_OBJ(cloneiter != clonemapp->end(), pinp,
"Couldn't find pin in clone list");
pinp->modPTypep(VN_CAST(cloneiter->second, ParamTypeDType));
} else {
pinp->v3fatalSrc("Not linked?");
}
}
}
void relinkPinsByName(AstPin* startpinp, AstNodeModule* modp) {
std::map<const string, AstVar*> nameToPin;
for (AstNode* stmtp = modp->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
if (AstVar* varp = VN_CAST(stmtp, Var)) {
if (varp->isIO() || varp->isGParam() || varp->isIfaceRef()) {
nameToPin.emplace(varp->name(), varp);
}
}
}
for (AstPin* pinp = startpinp; pinp; pinp = VN_CAST(pinp->nextp(), Pin)) {
if (AstVar* varp = pinp->modVarp()) {
const auto varIt = vlstd::as_const(nameToPin).find(varp->name());
UASSERT_OBJ(varIt != nameToPin.end(), varp,
"Not found in " << modp->prettyNameQ());
pinp->modVarp(varIt->second);
}
}
}
// Check if parameter setting during instantiation is simple enough for hierarchical verilation
void checkSupportedParam(AstNodeModule* modp, AstPin* pinp) const {
// InitArray and AstParamTypeDType are not supported because that can not be set via -G
// option.
if (pinp->modVarp()) {
bool supported = false;
if (AstConst* constp = VN_CAST(pinp->exprp(), Const)) {
supported = !constp->isOpaque();
}
if (!supported) {
pinp->v3error(AstNode::prettyNameQ(modp->origName())
<< " has hier_block metacomment, hierarchical verilation"
<< " supports only integer/floating point/string parameters");
}
} else if (VN_IS(pinp->modPTypep(), ParamTypeDType)) {
pinp->v3error(AstNode::prettyNameQ(modp->origName())
<< " has hier_block metacomment, but 'parameter type' is not supported");
}
}
bool moduleExists(const string& modName) const {
if (m_allModuleNames.find(modName) != m_allModuleNames.end()) return true;
if (m_modNameMap.find(modName) != m_modNameMap.end()) return true;
return false;
}
string parameterizedHierBlockName(AstNodeModule* modp, AstPin* paramPinsp) {
// Create a unique name in the following steps
// - Make a long name that includes all parameters, that appear
// in the alphabetical order.
// - Hash the long name to get valid Verilog symbol
UASSERT_OBJ(modp->hierBlock(), modp, "should be used for hierarchical block");
std::map<string, AstConst*> pins;
for (AstPin* pinp = paramPinsp; pinp; pinp = VN_CAST(pinp->nextp(), Pin)) {
checkSupportedParam(modp, pinp);
if (AstVar* varp = pinp->modVarp()) {
if (!pinp->exprp()) continue;
if (varp->isGParam()) {
AstConst* constp = VN_CAST(pinp->exprp(), Const);
pins.emplace(varp->name(), constp);
}
}
}
auto paramsIt = m_defaultParameterValues.find(modp);
if (paramsIt == m_defaultParameterValues.end()) { // Not cached yet, so check parameters
// Using map with key=string so that we can scan it in deterministic order
DefaultValueMap params;
for (AstNode* stmtp = modp->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
if (AstVar* varp = VN_CAST(stmtp, Var)) {
if (varp->isGParam()) {
AstConst* constp = VN_CAST(varp->valuep(), Const);
// constp can be nullptr if the parameter is not used to instantiate sub
// module. varp->valuep() is not contified yet in the case.
// nullptr means that the parameter is using some default value.
params.emplace(varp->name(), constp);
}
}
}
paramsIt = m_defaultParameterValues.emplace(modp, std::move(params)).first;
}
if (paramsIt->second.empty()) return modp->name(); // modp has no parameter
string longname = modp->name();
for (auto&& defaultValue : paramsIt->second) {
auto pinIt = pins.find(defaultValue.first);
AstConst* constp = pinIt == pins.end() ? defaultValue.second : pinIt->second;
// This longname is not valid as verilog symbol, but ok, because it will be hashed
longname += "_" + defaultValue.first + "=";
// constp can be nullptr
if (constp) longname += constp->num().ascii(false);
}
auto iter = m_longMap.find(longname);
if (iter != m_longMap.end()) return iter->second; // Already calculated
VHashSha256 hash;
// Calculate hash using longname
// The hash is used as the module suffix to find a module name that is unique in the design
hash.insert(longname);
while (true) {
// Copy VHashSha256 just in case of hash collision
VHashSha256 hashStrGen = hash;
// Hex string must be a safe suffix for any symbol
const string hashStr = hashStrGen.digestHex();
for (string::size_type i = 1; i < hashStr.size(); ++i) {
string newName = modp->name();
// Don't use '__' not to be encoded when this module is loaded later by Verilator
if (newName.at(newName.size() - 1) != '_') newName += '_';
newName += hashStr.substr(0, i);
if (!moduleExists(newName)) {
m_longMap.emplace(longname, newName);
return newName;
}
}
// Hash collision. maybe just v3error is practically enough
hash.insert(V3Os::trueRandom(64));
}
}
void deepCloneModule(AstNodeModule* srcModp, AstNode* cellp, AstPin* paramsp,
const string& newname, const IfaceRefRefs& ifaceRefRefs) {
// Deep clone of new module
// Note all module internal variables will be re-linked to the new modules by clone
// However links outside the module (like on the upper cells) will not.
AstNodeModule* newmodp = srcModp->cloneTree(false);
newmodp->name(newname);
newmodp->user5(false); // We need to re-recurse this module once changed
newmodp->recursive(false);
newmodp->recursiveClone(false);
// Only the first generation of clone holds this property
newmodp->hierBlock(srcModp->hierBlock() && !srcModp->recursiveClone());
// Recursion may need level cleanups
if (newmodp->level() <= m_modp->level()) newmodp->level(m_modp->level() + 1);
if ((newmodp->level() - srcModp->level()) >= (v3Global.opt.moduleRecursionDepth() - 2)) {
cellp->v3error("Exceeded maximum --module-recursion-depth of "
<< v3Global.opt.moduleRecursionDepth());
}
// Keep tree sorted by level
AstNodeModule* insertp = srcModp;
while (VN_IS(insertp->nextp(), NodeModule)
&& VN_CAST(insertp->nextp(), NodeModule)->level() < newmodp->level()) {
insertp = VN_CAST(insertp->nextp(), NodeModule);
}
insertp->addNextHere(newmodp);
m_modNameMap.emplace(newmodp->name(), ModInfo(newmodp));
auto iter = m_modNameMap.find(newname);
CloneMap* clonemapp = &(iter->second.m_cloneMap);
UINFO(4, " De-parameterize to new: " << newmodp << endl);
// Grab all I/O so we can remap our pins later
// Note we allow multiple users of a parameterized model,
// thus we need to stash this info.
collectPins(clonemapp, newmodp);
// Relink parameter vars to the new module
relinkPins(clonemapp, paramsp);
// Fix any interface references
for (auto it = ifaceRefRefs.cbegin(); it != ifaceRefRefs.cend(); ++it) {
AstIfaceRefDType* portIrefp = it->first;
AstIfaceRefDType* pinIrefp = it->second;
AstIfaceRefDType* cloneIrefp = portIrefp->clonep();
UINFO(8, " IfaceOld " << portIrefp << endl);
UINFO(8, " IfaceTo " << pinIrefp << endl);
UASSERT_OBJ(cloneIrefp, portIrefp, "parameter clone didn't hit AstIfaceRefDType");
UINFO(8, " IfaceClo " << cloneIrefp << endl);
cloneIrefp->ifacep(pinIrefp->ifaceViaCellp());
UINFO(8, " IfaceNew " << cloneIrefp << endl);
}
// Assign parameters to the constants specified
// DOES clone() so must be finished with module clonep() before here
for (AstPin* pinp = paramsp; pinp; pinp = VN_CAST(pinp->nextp(), Pin)) {
if (pinp->exprp()) {
if (AstVar* modvarp = pinp->modVarp()) {
AstNode* newp = pinp->exprp(); // Const or InitArray
AstConst* exprp = VN_CAST(newp, Const);
AstConst* origp = VN_CAST(modvarp->valuep(), Const);
const bool overridden
= !(origp && ParameterizedHierBlocks::areSame(exprp, origp));
// Remove any existing parameter
if (modvarp->valuep()) modvarp->valuep()->unlinkFrBack()->deleteTree();
// Set this parameter to value requested by cell
UINFO(9, " set param " << modvarp << " = " << newp << endl);
modvarp->valuep(newp->cloneTree(false));
modvarp->overriddenParam(overridden);
} else if (AstParamTypeDType* modptp = pinp->modPTypep()) {
AstNodeDType* dtypep = VN_CAST(pinp->exprp(), NodeDType);
UASSERT_OBJ(dtypep, pinp, "unlinked param dtype");
if (modptp->childDTypep()) modptp->childDTypep()->unlinkFrBack()->deleteTree();
// Set this parameter to value requested by cell
modptp->childDTypep(dtypep->cloneTree(false));
// Later V3LinkDot will convert the ParamDType to a Typedef
// Not done here as may be localparams, etc, that also need conversion
}
}
}
}
const ModInfo* moduleFindOrClone(AstNodeModule* srcModp, AstNode* cellp, AstPin* paramsp,
const string& newname, const IfaceRefRefs& ifaceRefRefs) {
// Already made this flavor?
auto it = m_modNameMap.find(newname);
if (it != m_modNameMap.end()) {
UINFO(4, " De-parameterize to old: " << it->second.m_modp << endl);
} else {
deepCloneModule(srcModp, cellp, paramsp, newname, ifaceRefRefs);
it = m_modNameMap.find(newname);
UASSERT(it != m_modNameMap.end(), "should find just-made module");
}
const ModInfo* modInfop = &(it->second);
return modInfop;
}
void cellPinCleanup(AstNode* nodep, AstPin* pinp, AstNodeModule* srcModp, string& longnamer,
bool& any_overridesr) {
if (!pinp->exprp()) return; // No-connect
if (AstVar* modvarp = pinp->modVarp()) {
if (!modvarp->isGParam()) {
pinp->v3error("Attempted parameter setting of non-parameter: Param "
<< pinp->prettyNameQ() << " of " << nodep->prettyNameQ());
} else if (VN_IS(pinp->exprp(), InitArray) && arraySubDTypep(modvarp->subDTypep())) {
// Array assigned to array
AstNode* exprp = pinp->exprp();
longnamer += "_" + paramSmallName(srcModp, modvarp) + paramValueNumber(exprp);
any_overridesr = true;
} else {
AstConst* exprp = VN_CAST(pinp->exprp(), Const);
AstConst* origp = VN_CAST(modvarp->valuep(), Const);
if (!exprp) {
// if (debug()) pinp->dumpTree(cout, "error:");
pinp->v3error("Can't convert defparam value to constant: Param "
<< pinp->prettyNameQ() << " of " << nodep->prettyNameQ());
pinp->exprp()->replaceWith(new AstConst(
pinp->fileline(), AstConst::WidthedValue(), modvarp->width(), 0));
} else if (origp && exprp->sameTree(origp)) {
// Setting parameter to its default value. Just ignore it.
// This prevents making additional modules, and makes coverage more
// obvious as it won't show up under a unique module page name.
} else if (exprp->num().isDouble() || exprp->num().isString()
|| exprp->num().isFourState() || exprp->num().width() != 32) {
longnamer
+= ("_" + paramSmallName(srcModp, modvarp) + paramValueNumber(exprp));
any_overridesr = true;
} else {
longnamer
+= ("_" + paramSmallName(srcModp, modvarp) + exprp->num().ascii(false));
any_overridesr = true;
}
}
} else if (AstParamTypeDType* modvarp = pinp->modPTypep()) {
AstNodeDType* exprp = VN_CAST(pinp->exprp(), NodeDType);
AstNodeDType* origp = modvarp->subDTypep();
if (!exprp) {
pinp->v3error("Parameter type pin value isn't a type: Param "
<< pinp->prettyNameQ() << " of " << nodep->prettyNameQ());
} else if (!origp) {
pinp->v3error("Parameter type variable isn't a type: Param "
<< modvarp->prettyNameQ());
} else {
UINFO(9, "Parameter type assignment expr=" << exprp << " to " << origp << endl);
if (exprp->sameTree(origp)) {
// Setting parameter to its default value. Just ignore it.
// This prevents making additional modules, and makes coverage more
// obvious as it won't show up under a unique module page name.
} else {
V3Const::constifyParamsEdit(exprp);
longnamer += "_" + paramSmallName(srcModp, modvarp) + paramValueNumber(exprp);
any_overridesr = true;
}
}
} else {
pinp->v3error("Parameter not found in sub-module: Param "
<< pinp->prettyNameQ() << " of " << nodep->prettyNameQ());
}
}
void cellInterfaceCleanup(AstCell* nodep, AstNodeModule* srcModp, string& longnamer,
bool& any_overridesr, IfaceRefRefs& ifaceRefRefs) {
for (AstPin* pinp = nodep->pinsp(); pinp; pinp = VN_CAST(pinp->nextp(), Pin)) {
AstVar* modvarp = pinp->modVarp();
if (modvarp->isIfaceRef()) {
AstIfaceRefDType* portIrefp = VN_CAST(modvarp->subDTypep(), IfaceRefDType);
if (!portIrefp && arraySubDTypep(modvarp->subDTypep())) {
portIrefp = VN_CAST(arraySubDTypep(modvarp->subDTypep()), IfaceRefDType);
}
AstIfaceRefDType* pinIrefp = nullptr;
AstNode* exprp = pinp->exprp();
AstVar* varp
= (exprp && VN_IS(exprp, VarRef)) ? VN_CAST(exprp, VarRef)->varp() : nullptr;
if (varp && varp->subDTypep() && VN_IS(varp->subDTypep(), IfaceRefDType)) {
pinIrefp = VN_CAST(varp->subDTypep(), IfaceRefDType);
} else if (varp && varp->subDTypep() && arraySubDTypep(varp->subDTypep())
&& VN_CAST(arraySubDTypep(varp->subDTypep()), IfaceRefDType)) {
pinIrefp = VN_CAST(arraySubDTypep(varp->subDTypep()), IfaceRefDType);
} else if (exprp && exprp->op1p() && VN_IS(exprp->op1p(), VarRef)
&& VN_CAST(exprp->op1p(), VarRef)->varp()
&& VN_CAST(exprp->op1p(), VarRef)->varp()->subDTypep()
&& arraySubDTypep(VN_CAST(exprp->op1p(), VarRef)->varp()->subDTypep())
&& VN_CAST(
arraySubDTypep(VN_CAST(exprp->op1p(), VarRef)->varp()->subDTypep()),
IfaceRefDType)) {
pinIrefp = VN_CAST(
arraySubDTypep(VN_CAST(exprp->op1p(), VarRef)->varp()->subDTypep()),
IfaceRefDType);
}
UINFO(9, " portIfaceRef " << portIrefp << endl);
if (!portIrefp) {
pinp->v3error("Interface port " << modvarp->prettyNameQ()
<< " is not an interface " << modvarp);
} else if (!pinIrefp) {
pinp->v3error("Interface port "
<< modvarp->prettyNameQ()
<< " is not connected to interface/modport pin expression");
} else {
UINFO(9, " pinIfaceRef " << pinIrefp << endl);
if (portIrefp->ifaceViaCellp() != pinIrefp->ifaceViaCellp()) {
UINFO(9, " IfaceRefDType needs reconnect " << pinIrefp << endl);
longnamer += ("_" + paramSmallName(srcModp, pinp->modVarp())
+ paramValueNumber(pinIrefp));
any_overridesr = true;
ifaceRefRefs.push_back(std::make_pair(portIrefp, pinIrefp));
if (portIrefp->ifacep() != pinIrefp->ifacep()
// Might be different only due to param cloning, so check names too
&& portIrefp->ifaceName() != pinIrefp->ifaceName()) {
pinp->v3error("Port " << pinp->prettyNameQ() << " expects "
<< AstNode::prettyNameQ(portIrefp->ifaceName())
<< " interface but pin connects "
<< AstNode::prettyNameQ(pinIrefp->ifaceName())
<< " interface");
}
}
}
}
}
}
public:
void cellDeparam(AstCell* nodep, AstNodeModule* modp, const string& hierName) {
m_modp = modp;
// Cell: Check for parameters in the instantiation.
// We always run this, even if no parameters, as need to look for interfaces,
// and remove any recursive references
UINFO(4, "De-parameterize: " << nodep << endl);
// Create new module name with _'s between the constants
if (debug() >= 10) nodep->dumpTree(cout, "-cell: ");
// Evaluate all module constants
V3Const::constifyParamsEdit(nodep);
AstNodeModule* srcModp = nodep->modp();
srcModp->hierName(hierName + "." + nodep->name());
// Make sure constification worked
// Must be a separate loop, as constant conversion may have changed some pointers.
// if (debug()) nodep->dumpTree(cout, "-cel2: ");
string longname = srcModp->name() + "_";
bool any_overrides = false;
// Must always clone __Vrcm (recursive modules)
if (nodep->recursive()) any_overrides = true;
if (debug() > 8) nodep->paramsp()->dumpTreeAndNext(cout, "-cellparams: ");
if (srcModp->hierBlock()) {
longname = parameterizedHierBlockName(srcModp, nodep->paramsp());
any_overrides = longname != srcModp->name();
} else {
for (AstPin* pinp = nodep->paramsp(); pinp; pinp = VN_CAST(pinp->nextp(), Pin)) {
cellPinCleanup(nodep, pinp, srcModp, longname /*ref*/, any_overrides /*ref*/);
}
}
IfaceRefRefs ifaceRefRefs;
cellInterfaceCleanup(nodep, srcModp, longname /*ref*/, any_overrides /*ref*/,
ifaceRefRefs /*ref*/);
if (!any_overrides) {
UINFO(8, "Cell parameters all match original values, skipping expansion.\n");
} else if (AstNodeModule* paramedModp
= m_hierBlocks.findByParams(srcModp->name(), nodep->paramsp(), m_modp)) {
nodep->modp(paramedModp);
nodep->modName(paramedModp->name());
paramedModp->dead(false);
// We need to relink the pins to the new module
relinkPinsByName(nodep->pinsp(), paramedModp);
} else {
string newname = srcModp->hierBlock() ? longname : moduleCalcName(srcModp, longname);
const ModInfo* modInfop
= moduleFindOrClone(srcModp, nodep, nodep->paramsp(), newname, ifaceRefRefs);
// Have child use this module instead.
nodep->modp(modInfop->m_modp);
nodep->modName(newname);
// We need to relink the pins to the new module
relinkPinsByName(nodep->pinsp(), modInfop->m_modp);
UINFO(8, " Done with " << modInfop->m_modp << endl);
}
nodep->recursive(false);
// Delete the parameters from the cell; they're not relevant any longer.
if (nodep->paramsp()) nodep->paramsp()->unlinkFrBackWithNext()->deleteTree();
UINFO(8, " Done with " << nodep << endl);
// if (debug() >= 10)
// v3Global.rootp()->dumpTreeFile(v3Global.debugFilename("param-out.tree"));
}
// CONSTRUCTORS
explicit ParamProcessor(AstNetlist* nodep)
: m_hierBlocks{v3Global.opt.hierBlocks(), nodep} {
for (AstNodeModule* modp = nodep->modulesp(); modp;
modp = VN_CAST(modp->nextp(), NodeModule)) {
m_allModuleNames.insert(modp->name());
}
}
~ParamProcessor() = default;
VL_UNCOPYABLE(ParamProcessor);
};
//######################################################################
// Process parameter visitor
class ParamVisitor final : public AstNVisitor {
// STATE
ParamProcessor m_processor; // De-parameterize a cell, build modules
UnrollStateful m_unroller; // Loop unroller
AstNodeModule* m_modp = nullptr; // Current module being processed
string m_generateHierName; // Generate portion of hierarchy name
string m_unlinkedTxt; // Text for AstUnlinkedRef
std::deque<AstCell*> m_cellps; // Cells left to process (in this module)
std::multimap<int, AstNodeModule*> m_todoModps; // Modules left to process
// METHODS
VL_DEBUG_FUNC; // Declare debug()
void visitCellDeparam(AstCell* nodep, const string& hierName) {
// Cell: Check for parameters in the instantiation.
iterateChildren(nodep);
UASSERT_OBJ(nodep->modp(), nodep, "Not linked?");
m_processor.cellDeparam(nodep, m_modp, hierName);
// Remember to process the child module at the end of the module
m_todoModps.emplace(nodep->modp()->level(), nodep->modp());
}
void visitModules() {
// Loop on all modules left to process
// Hitting a cell adds to the appropriate level of this level-sorted list,
// so since cells originally exist top->bottom we process in top->bottom order too.
while (!m_todoModps.empty()) {
const auto itm = m_todoModps.cbegin();
AstNodeModule* nodep = itm->second;
m_todoModps.erase(itm);
if (!nodep->user5SetOnce()) { // Process once; note clone() must clear so we do it
// again
m_modp = nodep;
UINFO(4, " MOD " << nodep << endl);
if (m_modp->hierName().empty()) m_modp->hierName(m_modp->origName());
iterateChildren(nodep);
// Note above iterate may add to m_todoModps
//
// Process interface cells, then non-interface which may ref an interface cell
for (int nonIf = 0; nonIf < 2; ++nonIf) {
for (AstCell* cellp : m_cellps) {
if ((nonIf == 0 && VN_IS(cellp->modp(), Iface))
|| (nonIf == 1 && !VN_IS(cellp->modp(), Iface))) {
string fullName(m_modp->hierName());
if (const string* genHierNamep = (string*)cellp->user5p()) {
fullName += *genHierNamep;
cellp->user5p(nullptr);
VL_DO_DANGLING(delete genHierNamep, genHierNamep);
}
VL_DO_DANGLING(visitCellDeparam(cellp, fullName), cellp);
}
}
}
m_cellps.clear();
m_modp = nullptr;
UINFO(4, " MOD-done\n");
}
}
}
// VISITORS
virtual void visit(AstNodeModule* nodep) override {
if (nodep->dead()) {
UINFO(4, " MOD-dead. " << nodep << endl); // Marked by LinkDot
return;
} else if (nodep->recursiveClone()) {
// Fake, made for recursive elimination
UINFO(4, " MOD-recursive-dead. " << nodep << endl);
nodep->dead(true); // So Dead checks won't count references to it
return;
}
//
if (!nodep->dead() && VN_IS(nodep, Class)) {
for (AstNode* stmtp = nodep->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
if (AstVar* varp = VN_CAST(stmtp, Var)) {
if (varp->isParam()) {
varp->v3warn(E_UNSUPPORTED, "Unsupported: class parameters");
}
}
}
}
//
if (m_modp) {
UINFO(4, " MOD-under-MOD. " << nodep << endl);
iterateChildren(nodep);
} else if (nodep->level() <= 2 // Haven't added top yet, so level 2 is the top
|| VN_IS(nodep, Class) // Nor moved classes
|| VN_IS(nodep, Package)) { // Likewise haven't done wrapTopPackages yet
// Add request to END of modules left to process
m_todoModps.emplace(nodep->level(), nodep);
m_generateHierName = "";
visitModules();
} else if (nodep->user5()) {
UINFO(4, " MOD-done " << nodep << endl); // Already did it
} else {
// Should have been done by now, if not dead
UINFO(4, " MOD-dead? " << nodep << endl);
}
}
virtual void visit(AstCell* nodep) override {
// Must do ifaces first, so push to list and do in proper order
string* genHierNamep = new string(m_generateHierName);
nodep->user5p(genHierNamep);
m_cellps.push_back(nodep);
}
virtual void visit(AstClassRefDType* nodep) override {
if (nodep->paramsp()) {
nodep->paramsp()->v3warn(E_UNSUPPORTED, "Unsupported: parameterized classes");
pushDeletep(nodep->paramsp()->unlinkFrBackWithNext());
}
iterateChildren(nodep);
}
// Make sure all parameters are constantified
virtual void visit(AstVar* nodep) override {
if (nodep->user5SetOnce()) return; // Process once
iterateChildren(nodep);
if (nodep->isParam()) {
if (!nodep->valuep()) {
nodep->v3error("Parameter without initial value is never given value"
<< " (IEEE 1800-2017 6.20.1): " << nodep->prettyNameQ());
} else {
V3Const::constifyParamsEdit(nodep); // The variable, not just the var->init()
if (!VN_IS(nodep->valuep(), Const)
&& !VN_IS(nodep->valuep(), Unbounded)) { // Complex init, like an array
// Make a new INITIAL to set the value.
// This allows the normal array/struct handling code to properly
// initialize the parameter.
nodep->addNext(new AstInitial(
nodep->fileline(),
new AstAssign(nodep->fileline(),
new AstVarRef(nodep->fileline(), nodep, VAccess::WRITE),
nodep->valuep()->cloneTree(true))));
if (nodep->isFuncLocal()) {
// We put the initial in wrong place under a function. We
// should move the parameter out of the function and to the
// module, with appropriate dotting, but this confuses LinkDot
// (as then name isn't found later), so punt - probably can
// treat as static function variable when that is supported.
nodep->v3warn(E_UNSUPPORTED,
"Unsupported: Parameters in functions with complex assign");
}
}
}
}
}
// Make sure varrefs cause vars to constify before things above
virtual void visit(AstVarRef* nodep) override {
// Might jump across functions, so beware if ever add a m_funcp
if (nodep->varp()) iterate(nodep->varp());
}
bool ifaceParamReplace(AstVarXRef* nodep, AstNode* candp) {
for (; candp; candp = candp->nextp()) {
if (nodep->name() == candp->name()) {
if (AstVar* varp = VN_CAST(candp, Var)) {
UINFO(9, "Found interface parameter: " << varp << endl);
nodep->varp(varp);
return true;
} else if (AstPin* pinp = VN_CAST(candp, Pin)) {
UINFO(9, "Found interface parameter: " << pinp << endl);
UASSERT_OBJ(pinp->exprp(), pinp, "Interface parameter pin missing expression");
VL_DO_DANGLING(nodep->replaceWith(pinp->exprp()->cloneTree(false)), nodep);
return true;
}
}
}
return false;
}
virtual void visit(AstVarXRef* nodep) override {
// Check to see if the scope is just an interface because interfaces are special
string dotted = nodep->dotted();
if (!dotted.empty() && nodep->varp() && nodep->varp()->isParam()) {
AstNode* backp = nodep;
while ((backp = backp->backp())) {
if (VN_IS(backp, NodeModule)) {
UINFO(9, "Hit module boundary, done looking for interface" << endl);
break;
}
if (VN_IS(backp, Var) && VN_CAST(backp, Var)->isIfaceRef()
&& VN_CAST(backp, Var)->childDTypep()
&& (VN_CAST(VN_CAST(backp, Var)->childDTypep(), IfaceRefDType)
|| (VN_CAST(VN_CAST(backp, Var)->childDTypep(), UnpackArrayDType)
&& VN_CAST(VN_CAST(backp, Var)->childDTypep()->getChildDTypep(),
IfaceRefDType)))) {
AstIfaceRefDType* ifacerefp
= VN_CAST(VN_CAST(backp, Var)->childDTypep(), IfaceRefDType);
if (!ifacerefp) {
ifacerefp = VN_CAST(VN_CAST(backp, Var)->childDTypep()->getChildDTypep(),
IfaceRefDType);
}
// Interfaces passed in on the port map have ifaces
if (AstIface* ifacep = ifacerefp->ifacep()) {
if (dotted == backp->name()) {
UINFO(9, "Iface matching scope: " << ifacep << endl);
if (ifaceParamReplace(nodep, ifacep->stmtsp())) { //
return;
}
}
}
// Interfaces declared in this module have cells
else if (AstCell* cellp = ifacerefp->cellp()) {
if (dotted == cellp->name()) {
UINFO(9, "Iface matching scope: " << cellp << endl);
if (ifaceParamReplace(nodep, cellp->paramsp())) { //
return;
}
}
}
}
}
}
nodep->varp(nullptr); // Needs relink, as may remove pointed-to var
}
virtual void visit(AstUnlinkedRef* nodep) override {
AstVarXRef* varxrefp = VN_CAST(nodep->op1p(), VarXRef);
AstNodeFTaskRef* taskrefp = VN_CAST(nodep->op1p(), NodeFTaskRef);
if (varxrefp) {
m_unlinkedTxt = varxrefp->dotted();
} else if (taskrefp) {
m_unlinkedTxt = taskrefp->dotted();
} else {
nodep->v3fatalSrc("Unexpected AstUnlinkedRef node");
return;
}
iterate(nodep->cellrefp());
if (varxrefp) {
varxrefp->dotted(m_unlinkedTxt);
} else {
taskrefp->dotted(m_unlinkedTxt);
}
nodep->replaceWith(nodep->op1p()->unlinkFrBack());
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
virtual void visit(AstCellArrayRef* nodep) override {
V3Const::constifyParamsEdit(nodep->selp());
if (const AstConst* constp = VN_CAST(nodep->selp(), Const)) {
string index = AstNode::encodeNumber(constp->toSInt());
string replacestr = nodep->name() + "__BRA__??__KET__";
size_t pos = m_unlinkedTxt.find(replacestr);
UASSERT_OBJ(pos != string::npos, nodep,
"Could not find array index in unlinked text: '"
<< m_unlinkedTxt << "' for node: " << nodep);
m_unlinkedTxt.replace(pos, replacestr.length(),
nodep->name() + "__BRA__" + index + "__KET__");
} else {
nodep->v3error("Could not expand constant selection inside dotted reference: "
<< nodep->selp()->prettyNameQ());
return;
}
}
// Generate Statements
virtual void visit(AstGenIf* nodep) override {
UINFO(9, " GENIF " << nodep << endl);
iterateAndNextNull(nodep->condp());
// We suppress errors when widthing params since short-circuiting in
// the conditional evaluation may mean these error can never occur. We
// then make sure that short-circuiting is used by constifyParamsEdit.
V3Width::widthGenerateParamsEdit(nodep); // Param typed widthing will
// NOT recurse the body.
V3Const::constifyGenerateParamsEdit(nodep->condp()); // condp may change
if (const AstConst* constp = VN_CAST(nodep->condp(), Const)) {
AstNode* keepp = (constp->isZero() ? nodep->elsesp() : nodep->ifsp());
if (keepp) {
keepp->unlinkFrBackWithNext();
nodep->replaceWith(keepp);
} else {
nodep->unlinkFrBack();
}
VL_DO_DANGLING(nodep->deleteTree(), nodep);
// Normal edit rules will now recurse the replacement
} else {
nodep->condp()->v3error("Generate If condition must evaluate to constant");
}
}
//! Parameter substitution for generated for loops.
//! @todo Unlike generated IF, we don't have to worry about short-circuiting the conditional
//! expression, since this is currently restricted to simple comparisons. If we ever do
//! move to more generic constant expressions, such code will be needed here.
virtual void visit(AstBegin* nodep) override {
if (nodep->genforp()) {
AstGenFor* forp = VN_CAST(nodep->genforp(), GenFor);
UASSERT_OBJ(forp, nodep, "Non-GENFOR under generate-for BEGIN");
// We should have a GENFOR under here. We will be replacing the begin,
// so process here rather than at the generate to avoid iteration problems
UINFO(9, " BEGIN " << nodep << endl);
UINFO(9, " GENFOR " << forp << endl);
V3Width::widthParamsEdit(forp); // Param typed widthing will NOT recurse the body
// Outer wrapper around generate used to hold genvar, and to ensure genvar
// doesn't conflict in V3LinkDot resolution with other genvars
// Now though we need to change BEGIN("zzz", GENFOR(...)) to
// a BEGIN("zzz__BRA__{loop#}__KET__")
string beginName = nodep->name();
// Leave the original Begin, as need a container for the (possible) GENVAR
// Note V3Unroll will replace some AstVarRef's to the loop variable with constants
// Don't remove any deleted nodes in m_unroller until whole process finishes,
// (are held in m_unroller), as some AstXRefs may still point to old nodes.
VL_DO_DANGLING(m_unroller.unrollGen(forp, beginName), forp);
// Blocks were constructed under the special begin, move them up
// Note forp is null, so grab statements again
if (AstNode* stmtsp = nodep->genforp()) {
stmtsp->unlinkFrBackWithNext();
nodep->addNextHere(stmtsp);
// Note this clears nodep->genforp(), so begin is no longer special
}
} else {
VL_RESTORER(m_generateHierName);
m_generateHierName += "." + nodep->prettyName();
iterateChildren(nodep);
}
}
virtual void visit(AstGenFor* nodep) override { // LCOV_EXCL_LINE
nodep->v3fatalSrc("GENFOR should have been wrapped in BEGIN");
}
virtual void visit(AstGenCase* nodep) override {
UINFO(9, " GENCASE " << nodep << endl);
AstNode* keepp = nullptr;
iterateAndNextNull(nodep->exprp());
V3Case::caseLint(nodep);
V3Width::widthParamsEdit(nodep); // Param typed widthing will NOT recurse the body,
// don't trigger errors yet.
V3Const::constifyParamsEdit(nodep->exprp()); // exprp may change
AstConst* exprp = VN_CAST(nodep->exprp(), Const);
// Constify
for (AstCaseItem* itemp = nodep->itemsp(); itemp;
itemp = VN_CAST(itemp->nextp(), CaseItem)) {
for (AstNode* ep = itemp->condsp(); ep;) {
AstNode* nextp = ep->nextp(); // May edit list
iterateAndNextNull(ep);
VL_DO_DANGLING(V3Const::constifyParamsEdit(ep), ep); // ep may change
ep = nextp;
}
}
// Item match
for (AstCaseItem* itemp = nodep->itemsp(); itemp;
itemp = VN_CAST(itemp->nextp(), CaseItem)) {
if (!itemp->isDefault()) {
for (AstNode* ep = itemp->condsp(); ep; ep = ep->nextp()) {
if (const AstConst* ccondp = VN_CAST(ep, Const)) {
V3Number match(nodep, 1);
match.opEq(ccondp->num(), exprp->num());
if (!keepp && match.isNeqZero()) keepp = itemp->bodysp();
} else {
itemp->v3error("Generate Case item does not evaluate to constant");
}
}
}
}
// Else default match
for (AstCaseItem* itemp = nodep->itemsp(); itemp;
itemp = VN_CAST(itemp->nextp(), CaseItem)) {
if (itemp->isDefault()) {
if (!keepp) keepp = itemp->bodysp();
}
}
// Replace
if (keepp) {
keepp->unlinkFrBackWithNext();
nodep->replaceWith(keepp);
} else {
nodep->unlinkFrBack();
}
VL_DO_DANGLING(nodep->deleteTree(), nodep);
}
virtual void visit(AstNode* nodep) override { iterateChildren(nodep); }
public:
// CONSTRUCTORS
explicit ParamVisitor(AstNetlist* nodep)
: m_processor{nodep} {
// Relies on modules already being in top-down-order
iterate(nodep);
}
virtual ~ParamVisitor() override = default;
VL_UNCOPYABLE(ParamVisitor);
};
//######################################################################
// Param class functions
void V3Param::param(AstNetlist* rootp) {
UINFO(2, __FUNCTION__ << ": " << endl);
{ ParamVisitor visitor(rootp); } // Destruct before checking
V3Global::dumpCheckGlobalTree("param", 0, v3Global.opt.dumpTreeLevel(__FILE__) >= 6);
}