Internals: Use a V3Graph for the hierarchical plan (#6545)

I stared this because the emitted makefiles for hierarchical verilation
were non-deterministic (iterating unordered_map indexed by pointers).
Then I realized that the V3HierPlan is just a dependency graph encoded
in a slightly idiosyncratic way. We do have a data structure to use for
that instead.

With that the output should always be deterministic + have nicer dumps.
This commit is contained in:
Geza Lore 2025-10-09 21:41:23 +02:00 committed by GitHub
parent e63d486422
commit 9dc0cf5a7c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 258 additions and 330 deletions

View File

@ -179,30 +179,30 @@ class CMakeEmitter final {
*of << "# User .cpp files (from .cpp's on Verilator command line)\n";
cmake_set_raw(*of, name + "_USER_CLASSES", cmake_list(v3Global.opt.cppFiles()));
if (const V3HierBlockPlan* const planp = v3Global.hierPlanp()) {
if (const V3HierGraph* const graphp = v3Global.hierGraphp()) {
*of << "# Verilate hierarchical blocks\n";
// Sorted hierarchical blocks in order of leaf-first.
const V3HierBlockPlan::HierVector& hierBlocks = planp->hierBlocksSorted();
*of << "get_target_property(TOP_TARGET_NAME \"${TARGET}\" NAME)\n";
for (V3HierBlockPlan::HierVector::const_iterator it = hierBlocks.begin();
it != hierBlocks.end(); ++it) {
const V3HierBlock* hblockp = *it;
const V3HierBlock::HierBlockSet& children = hblockp->children();
for (const V3GraphVertex& vtx : vlstd::reverse_view(graphp->vertices())) {
const V3HierBlock* const hblockp = vtx.as<V3HierBlock>();
const string prefix = hblockp->hierPrefix();
*of << "add_library(" << prefix << " STATIC)\n";
*of << "target_link_libraries(${TOP_TARGET_NAME} PRIVATE " << prefix << ")\n";
if (!children.empty()) {
if (!hblockp->outEmpty()) {
*of << "target_link_libraries(" << prefix << " INTERFACE";
for (const V3HierBlock* const childp : children) {
*of << " " << childp->hierPrefix();
for (const V3GraphEdge& edge : hblockp->outEdges()) {
const V3HierBlock* const dependencyp = edge.top()->as<V3HierBlock>();
*of << " " << dependencyp->hierPrefix();
}
*of << ")\n";
}
*of << "verilate(" << prefix << " PREFIX " << prefix << " TOP_MODULE "
<< hblockp->modp()->name() << " DIRECTORY "
<< v3Global.opt.makeDir() + "/" + prefix << " SOURCES ";
for (const V3HierBlock* const childp : children) {
*of << " " << v3Global.opt.makeDir() + "/" + childp->hierWrapperFilename(true);
for (const V3GraphEdge& edge : hblockp->outEdges()) {
const V3HierBlock* const dependencyp = edge.top()->as<V3HierBlock>();
*of << " "
<< v3Global.opt.makeDir() + "/" + dependencyp->hierWrapperFilename(true);
}
*of << " ";
const string vFile = hblockp->vFileIfNecessary();
@ -219,12 +219,13 @@ class CMakeEmitter final {
*of << "verilate(${TOP_TARGET_NAME} PREFIX " << v3Global.opt.prefix() << " TOP_MODULE "
<< v3Global.rootp()->topModulep()->name() << " DIRECTORY "
<< v3Global.opt.makeDir() << " SOURCES ";
for (const auto& itr : *planp) {
*of << " " << v3Global.opt.makeDir() + "/" + itr.second.hierWrapperFilename(true);
for (const V3GraphVertex& vtx : graphp->vertices()) {
const V3HierBlock* const hblockp = vtx.as<V3HierBlock>();
*of << " " << v3Global.opt.makeDir() + "/" + hblockp->hierWrapperFilename(true);
}
*of << " " << cmake_list(v3Global.opt.vFiles());
*of << " VERILATOR_ARGS ";
*of << "-f " << planp->topCommandArgsFilename(true);
*of << "-f " << graphp->topCommandArgsFilename(true);
*of << ")\n";
}
}

View File

@ -803,7 +803,7 @@ public:
//######################################################################
class EmitMkHierVerilation final {
const V3HierBlockPlan* const m_planp;
const V3HierGraph* const m_graphp;
const string m_makefile; // path of this makefile
void emitCommonOpts(V3OutMkFile& of) const {
const string cwd = V3Os::filenameRealPath(".");
@ -843,11 +843,9 @@ class EmitMkHierVerilation final {
of.puts("# Libraries of hierarchical blocks\n");
of.puts("VM_HIER_LIBS := \\\n");
const V3HierBlockPlan::HierVector blocks
= m_planp->hierBlocksSorted(); // leaf comes first
// List in order of leaf-last order so that linker can resolve dependency
for (const auto& block : vlstd::reverse_view(blocks)) {
of.puts(" " + block->hierLibFilename(true) + " \\\n");
for (const V3GraphVertex& vtx : m_graphp->vertices()) {
const V3HierBlock* const blockp = vtx.as<V3HierBlock>();
of.puts(" " + blockp->hierLibFilename(true) + " \\\n");
}
of.puts("\n");
@ -866,28 +864,31 @@ class EmitMkHierVerilation final {
// Top level module
{
const string argsFile = v3Global.hierPlanp()->topCommandArgsFilename(false);
const string argsFile = v3Global.hierGraphp()->topCommandArgsFilename(false);
of.puts("\n# Verilate the top module\n");
of.puts(v3Global.opt.prefix()
+ ".mk: $(VM_HIER_INPUT_FILES) $(VM_HIER_VERILOG_LIBS) ");
of.puts(V3Os::filenameNonDir(argsFile) + " ");
for (const auto& itr : *m_planp) of.puts(itr.second.hierWrapperFilename(true) + " ");
for (const V3GraphVertex& vtx : m_graphp->vertices()) {
const V3HierBlock* const blockp = vtx.as<V3HierBlock>();
of.puts(blockp->hierWrapperFilename(true) + " ");
}
of.puts("\n");
emitLaunchVerilator(of, argsFile);
}
// Rules to process hierarchical blocks
of.puts("\n# Verilate hierarchical blocks\n");
for (const V3HierBlock* const blockp : m_planp->hierBlocksSorted()) {
for (const V3GraphVertex& vtx : m_graphp->vertices()) {
const V3HierBlock* const blockp = vtx.as<V3HierBlock>();
const string prefix = blockp->hierPrefix();
const string argsFilename = blockp->commandArgsFilename(false);
of.puts(blockp->hierGeneratedFilenames(true));
of.puts(": $(VM_HIER_INPUT_FILES) $(VM_HIER_VERILOG_LIBS) ");
of.puts(V3Os::filenameNonDir(argsFilename) + " ");
const V3HierBlock::HierBlockSet& children = blockp->children();
for (V3HierBlock::HierBlockSet::const_iterator child = children.begin();
child != children.end(); ++child) {
of.puts((*child)->hierWrapperFilename(true) + " ");
for (const V3GraphEdge& edge : blockp->outEdges()) {
const V3HierBlock* const dependencyp = edge.top()->as<V3HierBlock>();
of.puts(dependencyp->hierWrapperFilename(true) + " ");
}
of.puts("\n");
emitLaunchVerilator(of, argsFilename);
@ -897,9 +898,9 @@ class EmitMkHierVerilation final {
of.puts(": ");
of.puts(blockp->hierMkFilename(true));
of.puts(" ");
for (V3HierBlock::HierBlockSet::const_iterator child = children.begin();
child != children.end(); ++child) {
of.puts((*child)->hierLibFilename(true));
for (const V3GraphEdge& edge : blockp->outEdges()) {
const V3HierBlock* const dependencyp = edge.top()->as<V3HierBlock>();
of.puts(dependencyp->hierLibFilename(true));
of.puts(" ");
}
of.puts("\n\t$(MAKE) -f " + blockp->hierMkFilename(false) + " -C " + prefix);
@ -910,8 +911,8 @@ class EmitMkHierVerilation final {
}
public:
explicit EmitMkHierVerilation(const V3HierBlockPlan* planp)
: m_planp{planp}
explicit EmitMkHierVerilation(const V3HierGraph* graphp)
: m_graphp{graphp}
, m_makefile{v3Global.opt.makeDir() + "/" + v3Global.opt.prefix() + "_hier.mk"} {
V3OutMkFile of{m_makefile};
emit(of);
@ -926,7 +927,7 @@ void V3EmitMk::emitmk() {
const EmitMk emitter;
}
void V3EmitMk::emitHierVerilation(const V3HierBlockPlan* planp) {
void V3EmitMk::emitHierVerilation(const V3HierGraph* graphp) {
UINFO(2, __FUNCTION__ << ":");
EmitMkHierVerilation{planp};
EmitMkHierVerilation{graphp};
}

View File

@ -20,7 +20,7 @@
#include "config_build.h"
#include "verilatedos.h"
class V3HierBlockPlan;
class V3HierGraph;
//============================================================================
@ -30,7 +30,7 @@ public:
static const size_t PARALLEL_FILE_CNT_THRESHOLD = 128;
static void emitmk() VL_MT_DISABLED;
static void emitHierVerilation(const V3HierBlockPlan* planp) VL_MT_DISABLED;
static void emitHierVerilation(const V3HierGraph* planp) VL_MT_DISABLED;
};
#endif // Guard

View File

@ -111,64 +111,69 @@ class V3EmitMkJsonEmitter final {
.putList("user_classes", cppFiles)
.end();
if (const V3HierBlockPlan* const planp = v3Global.hierPlanp()) {
// Sorted hierarchical blocks in order of leaf-first.
const V3HierBlockPlan::HierVector& hierBlocks = planp->hierBlocksSorted();
if (const V3HierGraph* const graphp = v3Global.hierGraphp()) {
of.begin("submodules", '[');
for (V3HierBlockPlan::HierVector::const_iterator it = hierBlocks.begin();
it != hierBlocks.end(); ++it) {
const V3HierBlock* hblockp = *it;
const V3HierBlock::HierBlockSet& children = hblockp->children();
std::vector<std::string> childDeps;
// Enumerate in dependency order, leaves first. TODO: Shouldn't
// really have to, but verilator-config.cmake.in depends on order.
for (const V3GraphVertex& vtx : vlstd::reverse_view(graphp->vertices())) {
const V3HierBlock* const hblockp = vtx.as<V3HierBlock>();
std::vector<std::string> deps;
std::vector<std::string> sources;
for (const V3HierBlock* const childp : children) {
childDeps.emplace_back(childp->hierPrefix());
sources.emplace_back(makeDir + "/" + childp->hierWrapperFilename(true));
for (const V3GraphEdge& edge : hblockp->outEdges()) {
const V3HierBlock* const dependencyp = edge.top()->as<V3HierBlock>();
deps.emplace_back(dependencyp->hierPrefix());
sources.emplace_back(makeDir + "/" + dependencyp->hierWrapperFilename(true));
}
const string vFile = hblockp->vFileIfNecessary();
const std::string vFile = hblockp->vFileIfNecessary();
if (!vFile.empty()) sources.emplace_back(vFile);
for (const auto& i : v3Global.opt.vFiles())
sources.emplace_back(
V3Os::filenameSlashPath(V3Os::filenameRealPath(i.filename())));
std::vector<std::string> cflags;
cflags.emplace_back("-fPIC");
for (const VFileLibName& i : v3Global.opt.vFiles()) {
const std::string fname = i.filename();
sources.emplace_back(V3Os::filenameSlashPath(V3Os::filenameRealPath(fname)));
}
of.begin()
.put("prefix", hblockp->hierPrefix())
.put("top", hblockp->modp()->name())
.putList("deps", childDeps)
.putList("deps", deps)
.put("directory", makeDir + "/" + hblockp->hierPrefix())
.putList("sources", sources)
.putList("cflags", cflags)
.putList("cflags", {"-fPIC"})
.put("verilator_args", V3Os::filenameSlashPath(V3Os::filenameRealPath(
hblockp->commandArgsFilename(true))))
.end();
}
std::vector<std::string> sources;
for (const auto& itr : *planp)
sources.emplace_back(makeDir + "/" + itr.second.hierWrapperFilename(true));
// Top level reintegration
{
// TODO: When this reintegration "submodule" is built with the bundled
// CMake script, it will overwrite the original json manifest containing the
// "subodule" list we are creating here with one that doesn't have "submodule".
// Good luck debugging, suggest 'message(${MANIFEST})' in verilated-config.cmake.
std::vector<std::string> sources;
for (const V3GraphVertex& vtx : graphp->vertices()) {
const V3HierBlock* const blockp = vtx.as<V3HierBlock>();
sources.emplace_back(makeDir + "/" + blockp->hierWrapperFilename(true));
}
for (const auto& itr : v3Global.opt.vFiles())
sources.emplace_back(
V3Os::filenameSlashPath(V3Os::filenameRealPath(itr.filename())));
for (const VFileLibName& i : v3Global.opt.vFiles()) {
const std::string fname = i.filename();
sources.emplace_back(V3Os::filenameSlashPath(V3Os::filenameRealPath(fname)));
}
of.begin()
.put("prefix", v3Global.opt.prefix())
.put("top", v3Global.rootp()->topModulep()->name())
.put("directory", makeDir)
.putList("sources", sources)
.put("verilator_args", V3Os::filenameSlashPath(V3Os::filenameRealPath(
planp->topCommandArgsFilename(true))))
.end()
.end();
of.begin()
.put("prefix", v3Global.opt.prefix())
.put("top", v3Global.rootp()->topModulep()->name())
.put("directory", makeDir)
.putList("sources", sources)
.put("verilator_args", V3Os::filenameSlashPath(V3Os::filenameRealPath(
graphp->topCommandArgsFilename(true))))
.end();
}
of.end(); // submodules
}
}

View File

@ -339,11 +339,10 @@ public:
V3OutJsonFile& put(bool value) { return putNamed("", value ? "true" : "false", false); }
V3OutJsonFile& put(int value) { return putNamed("", std::to_string(value), false); }
template <typename T>
V3OutJsonFile& putList(const std::string& name, const T& list) {
V3OutJsonFile& putList(const std::string& name, const std::vector<std::string>& list) {
if (list.empty()) return *this;
begin(name, '[');
for (auto it = list.begin(); it != list.end(); ++it) put(*it);
for (const std::string& str : list) put(str);
return end();
}

View File

@ -55,7 +55,7 @@ void V3Global::boot() {
void V3Global::shutdown() {
V3PreShell::shutdown();
VL_DO_CLEAR(delete m_hierPlanp, m_hierPlanp = nullptr); // delete nullptr is safe
VL_DO_CLEAR(delete m_hierGraphp, m_hierGraphp = nullptr); // delete nullptr is safe
VL_DO_CLEAR(delete m_threadPoolp, m_threadPoolp = nullptr); // delete nullptr is safe
#ifdef VL_LEAK_CHECKS
if (m_rootp) VL_DO_CLEAR(m_rootp->deleteTree(), m_rootp = nullptr);

View File

@ -37,7 +37,7 @@
#include <unordered_set>
class AstNetlist;
class V3HierBlockPlan;
class V3HierGraph;
class V3ThreadPool;
//======================================================================
@ -97,12 +97,12 @@ constexpr bool operator==(VWidthMinUsage::en lhs, const VWidthMinUsage& rhs) {
class V3Global final {
// Globals
AstNetlist* m_rootp = nullptr; // Root of entire netlist,
// created by makeInitNetlist(} so static constructors run first
V3HierBlockPlan* m_hierPlanp = nullptr; // Hierarchical Verilation plan,
// nullptr unless hier_block, set via hierPlanp(V3HierBlockPlan*}
V3ThreadPool* m_threadPoolp = nullptr; // Thread Pool,
// nullptr unless 'verilatedJobs' is known, set via threadPoolp(V3ThreadPool*)
// Root of entire netlist, created by makeInitNetlist(} so static constructors run first
AstNetlist* m_rootp = nullptr;
// Hierarchical block graph (plan) iff hierarchical verilation is performed
V3HierGraph* m_hierGraphp = nullptr;
// Thread Pool, nullptr unless 'verilatedJobs' is known, set via threadPoolp(V3ThreadPool*)
V3ThreadPool* m_threadPoolp = nullptr;
VWidthMinUsage m_widthMinUsage
= VWidthMinUsage::LINT_WIDTH; // What AstNode::widthMin() is used for
@ -199,11 +199,8 @@ public:
void setHasForceableSignals() { m_hasForceableSignals = true; }
bool hasSCTextSections() const VL_MT_SAFE { return m_hasSCTextSections; }
void setHasSCTextSections() { m_hasSCTextSections = true; }
V3HierBlockPlan* hierPlanp() const { return m_hierPlanp; }
void hierPlanp(V3HierBlockPlan* plan) {
UASSERT(!m_hierPlanp, "call once");
m_hierPlanp = plan;
}
V3HierGraph* hierGraphp() const { return m_hierGraphp; }
void hierGraphp(V3HierGraph* graphp) { m_hierGraphp = graphp; }
bool useParallelBuild() const { return m_useParallelBuild; }
void useParallelBuild(bool flag) { m_useParallelBuild = flag; }
bool useRandomizeMethods() const { return m_useRandomizeMethods; }

View File

@ -126,7 +126,7 @@ static void V3HierWriteCommonInputs(const V3HierBlock* hblockp, std::ostream* of
//######################################################################
V3HierBlock::StrGParams V3HierBlock::stringifyParams(const V3HierBlockParams::GParams& gparams,
V3HierBlock::StrGParams V3HierBlock::stringifyParams(const std::vector<AstVar*>& gparams,
bool forGOption) {
StrGParams strParams;
for (const AstVar* const gparam : gparams) {
@ -171,18 +171,19 @@ VStringList V3HierBlock::commandArgs(bool forMkJson) const {
opts.push_back(" --protect-key " + v3Global.opt.protectKeyDefaulted());
opts.push_back(" --hierarchical-child " + cvtToStr(v3Global.opt.threads()));
const StrGParams gparamsStr = stringifyParams(params().gparams(), true);
const StrGParams gparamsStr = stringifyParams(m_params, true);
for (const StrGParam& param : gparamsStr) {
const string name = param.first;
const string value = param.second;
opts.push_back("-G" + name + "=" + value + "");
}
if (!params().gTypeParams().empty())
if (!m_typeParams.empty()) {
opts.push_back(" --hierarchical-params-file " + typeParametersFilename());
}
const int blockThreads = V3Control::getHierWorkers(m_modp->origName());
if (blockThreads > 1) {
if (hasParent()) {
if (!inEmpty()) {
V3Control::getHierWorkersFileLine(m_modp->origName())
->v3warn(E_UNSUPPORTED, "Specifying workers for nested hierarchical blocks");
} else {
@ -202,14 +203,13 @@ VStringList V3HierBlock::commandArgs(bool forMkJson) const {
VStringList V3HierBlock::hierBlockArgs() const {
VStringList opts;
const StrGParams gparamsStr = stringifyParams(params().gparams(), false);
const StrGParams gparamsStr = stringifyParams(m_params, false);
opts.push_back("--hierarchical-block ");
string s = modp()->origName(); // origName
s += "," + modp()->name(); // mangledName
for (StrGParams::const_iterator paramIt = gparamsStr.begin(); paramIt != gparamsStr.end();
++paramIt) {
s += "," + paramIt->first;
s += "," + paramIt->second;
for (const StrGParam& pair : gparamsStr) {
s += "," + pair.first;
s += "," + pair.second;
}
opts.back() += s;
return opts;
@ -254,8 +254,9 @@ void V3HierBlock::writeCommandArgsFile(bool forMkJson) const {
*of << "--cc\n";
if (!forMkJson) {
for (const V3HierBlock* const hierblockp : m_children) {
*of << v3Global.opt.makeDir() << "/" << hierblockp->hierWrapperFilename(true) << "\n";
for (const V3GraphEdge& edge : outEdges()) {
const V3HierBlock* const dependencyp = edge.top()->as<V3HierBlock>();
*of << v3Global.opt.makeDir() << "/" << dependencyp->hierWrapperFilename(true) << "\n";
}
*of << "-Mdir " << v3Global.opt.makeDir() << "/" << hierPrefix() << " \n";
}
@ -263,8 +264,9 @@ void V3HierBlock::writeCommandArgsFile(bool forMkJson) const {
const VStringList& commandOpts = commandArgs(false);
for (const string& opt : commandOpts) *of << opt << "\n";
*of << hierBlockArgs().front() << "\n";
for (const V3HierBlock* const hierblockp : m_children) {
*of << hierblockp->hierBlockArgs().front() << "\n";
for (const V3GraphEdge& edge : outEdges()) {
const V3HierBlock* const dependencyp = edge.top()->as<V3HierBlock>();
*of << dependencyp->hierBlockArgs().front() << "\n";
}
*of << v3Global.opt.allArgsStringForHierBlock(false) << "\n";
}
@ -278,13 +280,13 @@ string V3HierBlock::typeParametersFilename() const {
}
void V3HierBlock::writeParametersFile() const {
if (m_params.gTypeParams().empty()) return;
if (m_typeParams.empty()) return;
VHashSha256 hash{"type params"};
const string moduleName = "Vhsh" + hash.digestSymbol();
const std::unique_ptr<std::ofstream> of{V3File::new_ofstream(typeParametersFilename())};
*of << "module " << moduleName << ";\n";
for (AstParamTypeDType* const gparam : m_params.gTypeParams()) {
for (AstParamTypeDType* const gparam : m_typeParams) {
AstTypedef* tdefp
= new AstTypedef{new FileLine{FileLine::builtInFilename()}, gparam->name(), nullptr,
VFlagChildDType{}, gparam->skipRefp()->cloneTreePure(true)};
@ -297,176 +299,113 @@ void V3HierBlock::writeParametersFile() const {
}
//######################################################################
// Collect how hierarchical blocks are used
// Construct graph of hierarchical blocks
class HierBlockUsageCollectVisitor final : public VNVisitorConst {
// NODE STATE
// AstNode::user1() -> bool. Processed
// AstNode::user1() -> bool. Already visited
const VNUser1InUse m_inuser1;
// STATE
using ModuleSet = std::unordered_set<const AstModule*>;
V3HierBlockPlan* const m_planp;
V3HierGraph* const m_graphp = new V3HierGraph{}; // The graph of hierarchical blocks
// Map from hier blocks to the corresponding V3HierBlock graph vertex
std::unordered_map<const AstModule*, V3HierBlock*> m_mod2vtx;
AstModule* m_modp = nullptr; // The current module
AstModule* m_hierBlockp = nullptr; // The nearest parent module that is a hierarchical block
ModuleSet m_referred; // Modules that have hier_block pragma
V3HierBlockParams m_params;
std::vector<AstVar*> m_params; // Overridden value parameters of current module
std::vector<AstParamTypeDType*> m_typeParams; // Type parameters of current module
// Hierarchical blocks instanciated (possibly indirectly) by current hierarchical block
std::vector<V3HierBlock*> m_childrenp;
// VISITORSs
void visit(AstNodeModule*) override {} // Ignore all non AstModule
void visit(AstModule* nodep) override {
// Don't visit twice
// Visit each module once
if (nodep->user1SetOnce()) return;
UINFO(5, "Checking " << nodep->prettyNameQ() << " from "
<< (m_hierBlockp ? m_hierBlockp->prettyNameQ() : "null"s));
UINFO(5, "Visiting " << nodep->prettyNameQ());
VL_RESTORER(m_modp);
AstModule* const prevHierBlockp = m_hierBlockp;
ModuleSet prevReferred;
V3HierBlockParams previousParams;
m_modp = nodep;
if (nodep->hierBlock()) {
m_hierBlockp = nodep;
prevReferred.swap(m_referred);
}
previousParams.swap(m_params);
// If not a hierarchical block, just iterate and return
if (!nodep->hierBlock()) {
iterateChildrenConst(nodep);
return;
}
// This is a hierarchical block, gather parts
VL_RESTORER(m_params);
VL_RESTORER(m_typeParams);
VL_RESTORER(m_childrenp);
m_params.clear();
m_typeParams.clear();
m_childrenp.clear();
iterateChildrenConst(nodep);
if (nodep->hierBlock()) {
m_planp->add(nodep, m_params);
for (const AstModule* modp : m_referred) m_planp->registerUsage(nodep, modp);
m_hierBlockp = prevHierBlockp;
m_referred = prevReferred;
}
m_params.swap(previousParams);
// Create the graph vertex for this hier block
V3HierBlock* const blockp = new V3HierBlock{m_graphp, nodep, m_params, m_typeParams};
// Record it
m_mod2vtx[nodep] = blockp;
// Add an edge to each child block
for (V3HierBlock* const childp : m_childrenp) new V3GraphEdge{m_graphp, blockp, childp, 1};
}
void visit(AstCell* nodep) override {
// Visit used module here to know that the module is hier_block or not.
// This visitor behaves almost depth first search
if (AstModule* const modp = VN_CAST(nodep->modp(), Module)) {
iterateConst(modp);
m_referred.insert(modp);
}
// Nothing to do for interface because hierarchical block does not exist
// beyond interface.
// Nothing to do for non AstModules because hierarchical block cannot exist under them.
AstModule* const modp = VN_CAST(nodep->modp(), Module);
if (!modp) return;
// Depth-first traversal of module hierechy
iterateConst(modp);
// If this is an instance of a hierarchical block, add to child array to link parent
if (modp->hierBlock()) m_childrenp.emplace_back(m_mod2vtx.at(modp));
}
void visit(AstVar* nodep) override {
if (m_modp && m_modp->hierBlock() && nodep->isIfaceRef() && !nodep->isIfaceParent()) {
nodep->v3error("Modport cannot be used at the hierarchical block boundary");
}
if (nodep->isGParam() && nodep->overriddenParam()) m_params.add(nodep);
// Record overridden value parameter
if (nodep->isGParam() && nodep->overriddenParam()) {
UASSERT_OBJ(m_modp, nodep, "Value parameter not under module");
m_params.push_back(nodep);
}
}
void visit(AstParamTypeDType* nodep) override {
// Record type parameter
UASSERT_OBJ(m_modp, nodep, "Type parameter not under module");
m_typeParams.push_back(nodep);
}
void visit(AstParamTypeDType* nodep) override { m_params.add(nodep); }
void visit(AstNodeStmt*) override {} // Accelerate
void visit(AstNodeExpr*) override {} // Accelerate
void visit(AstConstPool*) override {} // Accelerate
void visit(AstTypeTable*) override {} // Accelerate
void visit(AstNode* nodep) override { iterateChildrenConst(nodep); }
// CONSTRUCTOR
HierBlockUsageCollectVisitor(AstNetlist* netlistp) {
iterateChildrenConst(netlistp);
if (dumpGraphLevel() >= 3) m_graphp->dumpDotFilePrefixed("hierblocks_initial");
// Simplify dependencies
m_graphp->removeRedundantEdgesSum(&V3GraphEdge::followAlwaysTrue);
// Topologically sorder the graph
m_graphp->order(); // This is a bit heavy weight, but does produce a topological ordering
if (dumpGraphLevel() >= 3) m_graphp->dumpDotFilePrefixed("hierblocks");
}
public:
HierBlockUsageCollectVisitor(V3HierBlockPlan* planp, AstNetlist* netlist)
: m_planp{planp} {
iterateChildrenConst(netlist);
static V3HierGraph* apply(AstNetlist* netlistp) {
return HierBlockUsageCollectVisitor{netlistp}.m_graphp;
}
};
//######################################################################
void V3HierGraph::writeCommandArgsFiles(bool forMkJson) const {
void V3HierBlockPlan::add(const AstNodeModule* modp, const V3HierBlockParams& params) {
const bool newEntry = m_blocks
.emplace(std::piecewise_construct, std::forward_as_tuple(modp),
std::forward_as_tuple(modp, params))
.second;
if (newEntry) {
UINFO(3, "Add " << modp->prettyNameQ() << " with " << params.gparams().size()
<< " parameters and " << params.gTypeParams().size()
<< " type parameters");
}
}
void V3HierBlockPlan::registerUsage(const AstNodeModule* parentp, const AstNodeModule* childp) {
const iterator parent = m_blocks.find(parentp);
UASSERT_OBJ(parent != m_blocks.end(), parentp, "must be added");
const iterator child = m_blocks.find(childp);
if (child != m_blocks.end()) {
UINFO(3, "Found usage relation " << parentp->prettyNameQ() << " uses "
<< childp->prettyNameQ());
parent->second.addChild(&child->second);
child->second.addParent(&parent->second);
}
}
void V3HierBlockPlan::createPlan(AstNetlist* nodep) {
// When processing a hierarchical block, no need to create a plan anymore.
if (v3Global.opt.hierChild()) return;
AstNodeModule* const modp = nodep->topModulep();
if (modp->hierBlock()) {
modp->v3warn(HIERBLOCK,
"Top module illegally marked hierarchical block, ignoring marking\n"
+ modp->warnMore()
+ "... Suggest remove verilator hier_block on this module");
modp->hierBlock(false);
}
std::unique_ptr<V3HierBlockPlan> planp(new V3HierBlockPlan);
{ HierBlockUsageCollectVisitor{planp.get(), nodep}; }
V3Stats::addStat("HierBlock, Hierarchical blocks", planp->m_blocks.size());
// No hierarchical block is found, nothing to do.
if (planp->empty()) return;
v3Global.hierPlanp(planp.release());
}
V3HierBlockPlan::HierVector V3HierBlockPlan::hierBlocksSorted() const {
using ChildrenMap
= std::unordered_map<const V3HierBlock*, std::unordered_set<const V3HierBlock*>>;
ChildrenMap childrenOfHierBlock;
HierVector sorted;
for (const_iterator it = begin(); it != end(); ++it) {
if (!it->second.hasChild()) { // No children, already leaf
sorted.push_back(&it->second);
} else {
ChildrenMap::value_type::second_type& childrenSet
= childrenOfHierBlock[&it->second]; // insert
const V3HierBlock::HierBlockSet& c = it->second.children();
childrenSet.insert(c.begin(), c.end());
}
}
// Use index instead of iterator because new elements will be added in this loop
for (size_t i = 0; i < sorted.size(); ++i) {
// This hblockp is already leaf.
const V3HierBlock* hblockp = sorted[i];
const V3HierBlock::HierBlockSet& p = hblockp->parents();
for (V3HierBlock::HierBlockSet::const_iterator it = p.begin(); it != p.end(); ++it) {
// Delete hblockp from parents. If a parent does not have a child anymore, then it is
// a leaf too.
const auto parentIt = childrenOfHierBlock.find(*it);
UASSERT_OBJ(parentIt != childrenOfHierBlock.end(), (*it)->modp(), "must be included");
const V3HierBlock::HierBlockSet::size_type erased = parentIt->second.erase(hblockp);
UASSERT_OBJ(erased == 1, hblockp->modp(),
" must be a child of " << parentIt->first->modp());
if (parentIt->second.empty()) { // Now parentIt is leaf
sorted.push_back(parentIt->first);
childrenOfHierBlock.erase(parentIt);
}
}
}
return sorted;
}
void V3HierBlockPlan::writeCommandArgsFiles(bool forMkJson) const {
for (const_iterator it = begin(); it != end(); ++it) {
it->second.writeCommandArgsFile(forMkJson);
for (const V3GraphVertex& vtx : vertices()) {
vtx.as<V3HierBlock>()->writeCommandArgsFile(forMkJson);
}
// For the top module
const std::unique_ptr<std::ofstream> of{
V3File::new_ofstream(topCommandArgsFilename(forMkJson))};
if (!forMkJson) {
// Load wrappers first not to be overwritten by the original HDL
for (const_iterator it = begin(); it != end(); ++it) {
*of << it->second.hierWrapperFilename(true) << "\n";
for (const V3GraphVertex& vtx : vertices()) {
*of << vtx.as<V3HierBlock>()->hierWrapperFilename(true) << "\n";
}
}
V3HierWriteCommonInputs(nullptr, of.get(), forMkJson);
@ -478,8 +417,8 @@ void V3HierBlockPlan::writeCommandArgsFiles(bool forMkJson) const {
*of << "-Mdir " << v3Global.opt.makeDir() << "\n";
*of << "--mod-prefix " << v3Global.opt.modPrefix() << "\n";
}
for (const_iterator it = begin(); it != end(); ++it) {
*of << it->second.hierBlockArgs().front() << "\n";
for (const V3GraphVertex& vtx : vertices()) {
*of << vtx.as<V3HierBlock>()->hierBlockArgs().front() << "\n";
}
if (!v3Global.opt.libCreate().empty()) {
@ -493,10 +432,34 @@ void V3HierBlockPlan::writeCommandArgsFiles(bool forMkJson) const {
*of << v3Global.opt.allArgsStringForHierBlock(true) << "\n";
}
string V3HierBlockPlan::topCommandArgsFilename(bool forMkJson) {
string V3HierGraph::topCommandArgsFilename(bool forMkJson) {
return V3HierCommandArgsFilename(v3Global.opt.prefix(), forMkJson);
}
void V3HierBlockPlan::writeParametersFiles() const {
for (const auto& block : *this) block.second.writeParametersFile();
void V3HierGraph::writeParametersFiles() const {
for (const V3GraphVertex& vtx : vertices()) { vtx.as<V3HierBlock>()->writeParametersFile(); }
}
//######################################################################
void V3Hierarchical::createGraph(AstNetlist* netlistp) {
UASSERT(!v3Global.hierGraphp(), "Should only be called once");
AstNodeModule* const modp = netlistp->topModulep();
if (modp->hierBlock()) {
modp->v3warn(HIERBLOCK, "Top module marked as hierarchical block, ignoring\n"
+ modp->warnMore()
+ "... Suggest remove verilator hier_block on this module");
modp->hierBlock(false);
}
V3HierGraph* const graphp = HierBlockUsageCollectVisitor::apply(netlistp);
V3Stats::addStat("HierBlock, Hierarchical blocks", graphp->vertices().size());
// No hierarchical block is found, nothing to do.
if (graphp->empty()) {
VL_DO_DANGLING(delete graphp, graphp);
return;
}
// Hold on to the graph
v3Global.hierGraphp(graphp);
}

View File

@ -19,7 +19,8 @@
#include "verilatedos.h"
#include "V3Options.h"
#include "V3Ast.h"
#include "V3Graph.h"
#include <map>
#include <set>
@ -36,68 +37,51 @@ class AstVar;
//######################################################################
class V3HierBlockParams final {
class V3HierGraph final : public V3Graph {
public:
using GParams = std::vector<AstVar*>;
using GTypeParams = std::vector<AstParamTypeDType*>;
V3HierGraph() = default;
~V3HierGraph() = default;
VL_UNCOPYABLE(V3HierGraph);
VL_UNMOVABLE(V3HierGraph);
private:
GParams m_params;
GTypeParams m_typeParams;
public:
void add(AstVar* param) { m_params.push_back(param); }
void add(AstParamTypeDType* param) { m_typeParams.push_back(param); }
const GParams& gparams() const { return m_params; };
const GTypeParams& gTypeParams() const { return m_typeParams; };
void swap(V3HierBlockParams& other) {
m_params.swap(other.m_params);
m_typeParams.swap(other.m_typeParams);
}
// Write command line arguments to .f files for child Verilation run
void writeCommandArgsFiles(bool forMkJson) const VL_MT_DISABLED;
void writeParametersFiles() const VL_MT_DISABLED;
static string topCommandArgsFilename(bool forMkJson) VL_MT_DISABLED;
};
class V3HierBlock final {
public:
using HierBlockSet = std::unordered_set<V3HierBlock*>;
class V3HierBlock final : public V3GraphVertex {
VL_RTTI_IMPL(V3HierBlock, V3GraphVertex)
private:
// TYPES
// Parameter name, stringified value
using StrGParam = std::pair<string, string>;
using StrGParams = std::vector<StrGParam>;
// MEMBERS
const AstNodeModule* const m_modp; // Hierarchical block module
// Hierarchical blocks that directly or indirectly instantiate this block
HierBlockSet m_parents;
// Hierarchical blocks that this block directly or indirectly instantiates
HierBlockSet m_children;
// Parameters that are overridden by #(.param(value)) syntax.
const V3HierBlockParams m_params;
const AstModule* const m_modp; // Hierarchical block module
// Value parameters that are overridden by #(.param(value)) syntax.
const std::vector<AstVar*> m_params;
// Types parameters that are overridden by #(.param(value)) syntax.
const std::vector<AstParamTypeDType*> m_typeParams;
// METHODS
VL_UNCOPYABLE(V3HierBlock);
VL_UNMOVABLE(V3HierBlock);
static StrGParams stringifyParams(const V3HierBlockParams::GParams& params,
static StrGParams stringifyParams(const std::vector<AstVar*>& params,
bool forGOption) VL_MT_DISABLED;
public:
V3HierBlock(const AstNodeModule* modp, const V3HierBlockParams& params)
: m_modp{modp}
, m_params{params} {}
// CONSTRUCTORs
V3HierBlock(V3HierGraph* graphp, const AstModule* modp, const std::vector<AstVar*>& params,
const std::vector<AstParamTypeDType*>& typeParams)
: V3GraphVertex{graphp}
, m_modp{modp}
, m_params{params}
, m_typeParams{typeParams} {}
~V3HierBlock() VL_MT_DISABLED = default;
VL_UNCOPYABLE(V3HierBlock);
VL_UNMOVABLE(V3HierBlock);
void addParent(V3HierBlock* parentp) { m_parents.insert(parentp); }
bool hasParent() const { return !m_parents.empty(); }
void addChild(V3HierBlock* childp) { m_children.insert(childp); }
bool hasChild() const { return !m_children.empty(); }
const HierBlockSet& parents() const { return m_parents; }
const HierBlockSet& children() const { return m_children; }
const V3HierBlockParams& params() const { return m_params; }
const AstNodeModule* modp() const { return m_modp; }
const AstModule* modp() const { return m_modp; }
// For emitting Makefile and build definition JSON
VStringList commandArgs(bool forMkJson) const VL_MT_DISABLED;
@ -116,41 +100,18 @@ public:
void writeParametersFile() const VL_MT_DISABLED;
string commandArgsFilename(bool forMkJson) const VL_MT_DISABLED;
string typeParametersFilename() const VL_MT_DISABLED;
// For Graphviz dumps only
std::string name() const override { return m_modp->prettyNameQ(); }
std::string dotShape() const override { return "box"; }
};
//######################################################################
// Pass to create hierarcical block graph
// Holds relationship between AstNodeModule and V3HierBlock
class V3HierBlockPlan final {
// TODO: this map is non-deterministic
using HierMap = std::unordered_map<const AstNodeModule*, V3HierBlock>;
HierMap m_blocks;
V3HierBlockPlan() = default;
VL_UNCOPYABLE(V3HierBlockPlan);
class V3Hierarchical final {
public:
using iterator = HierMap::iterator;
using const_iterator = HierMap::const_iterator;
using HierVector = std::vector<const V3HierBlock*>;
void add(const AstNodeModule* modp, const V3HierBlockParams& params) VL_MT_DISABLED;
void registerUsage(const AstNodeModule* parentp, const AstNodeModule* childp) VL_MT_DISABLED;
const_iterator begin() const { return m_blocks.begin(); }
const_iterator end() const { return m_blocks.end(); }
bool empty() const { return m_blocks.empty(); }
// Returns all hierarchical blocks that sorted in leaf-first order.
// Latter block refers only already appeared hierarchical blocks.
HierVector hierBlocksSorted() const VL_MT_DISABLED;
// Write command line arguments to .f files for child Verilation run
void writeCommandArgsFiles(bool forMkJson) const VL_MT_DISABLED;
void writeParametersFiles() const VL_MT_DISABLED;
static string topCommandArgsFilename(bool forMkJson) VL_MT_DISABLED;
static void createPlan(AstNetlist* nodep) VL_MT_DISABLED;
static void createGraph(AstNetlist* nodep) VL_MT_DISABLED;
};
#endif // guard

View File

@ -193,11 +193,11 @@ static void process() {
// Create a hierarchical Verilation plan
if (!v3Global.opt.lintOnly() && !v3Global.opt.serializeOnly()
&& v3Global.opt.hierarchical()) {
V3HierBlockPlan::createPlan(v3Global.rootp());
&& v3Global.opt.hierarchical() && !v3Global.opt.hierChild()) {
V3Hierarchical::createGraph(v3Global.rootp());
// If a plan is created, further analysis is not necessary.
// The actual Verilation will be done based on this plan.
if (v3Global.hierPlanp()) {
if (v3Global.hierGraphp()) {
reportStatsIfEnabled();
return;
}
@ -744,23 +744,24 @@ static bool verilate(const string& argString) {
V3Error::abortIfWarnings();
if (v3Global.hierPlanp()) { // This run is for just write a makefile
if (V3HierGraph* const hierGraphp
= v3Global.hierGraphp()) { // This run is for just write a makefile
UASSERT(v3Global.opt.hierarchical(), "hierarchical must be set");
UASSERT(!v3Global.opt.hierChild(), "This must not be a hierarchical-child run");
UASSERT(v3Global.opt.hierBlocks().empty(), "hierarchical-block must not be set");
if (v3Global.opt.gmake()) {
v3Global.hierPlanp()->writeCommandArgsFiles(false);
V3EmitMk::emitHierVerilation(v3Global.hierPlanp());
hierGraphp->writeCommandArgsFiles(false);
V3EmitMk::emitHierVerilation(hierGraphp);
}
if (v3Global.opt.cmake()) {
v3Global.hierPlanp()->writeCommandArgsFiles(true);
hierGraphp->writeCommandArgsFiles(true);
V3EmitCMake::emit();
}
if (v3Global.opt.makeJson()) {
v3Global.hierPlanp()->writeCommandArgsFiles(true);
hierGraphp->writeCommandArgsFiles(true);
V3EmitMkJson::emit();
}
v3Global.hierPlanp()->writeParametersFiles();
hierGraphp->writeParametersFiles();
}
if (v3Global.opt.makeDepend().isTrue()) {
string filename = v3Global.opt.makeDir() + "/" + v3Global.opt.prefix();
@ -835,7 +836,7 @@ static void execBuildJob() {
}
static void execHierVerilation() {
UASSERT(v3Global.hierPlanp(), "must be called only when plan exists");
UASSERT(v3Global.hierGraphp(), "must be called only when plan exists");
const string makefile = v3Global.opt.prefix() + "_hier.mk ";
const string target = v3Global.opt.build() ? " hier_build" : " hier_verilation";
const string cmdStr = buildMakeCmd(makefile, target);
@ -885,7 +886,7 @@ int main(int argc, char** argv) {
UINFO(1, "Option --no-verilate: Skip Verilation");
}
if (v3Global.hierPlanp() && v3Global.opt.gmake()) {
if (v3Global.hierGraphp() && v3Global.opt.gmake()) {
execHierVerilation(); // execHierVerilation() takes care of --build too
} else if (v3Global.opt.build()) {
execBuildJob();

View File

@ -1,4 +1,4 @@
%Warning-HIERBLOCK: t/t_hier_block1_bad.v:16:8: Top module illegally marked hierarchical block, ignoring marking
%Warning-HIERBLOCK: t/t_hier_block1_bad.v:16:8: Top module marked as hierarchical block, ignoring
: ... note: In instance 't'
: ... Suggest remove verilator hier_block on this module
16 | module t (