mirror of
https://github.com/verilator/verilator.git
synced 2026-10-06 18:13:50 +02:00
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)
1202 lines
58 KiB
C++
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);
|
|
}
|