mirror of
https://github.com/verilator/verilator.git
synced 2026-10-06 01:54:01 +02:00
When a lot of combinational logic is driven from top level inputs, work can be wasted evaluating that logic if the top level inputs don't change. This change adds an optimization by performing a change detect on the top level inputs, and evaluate 'ico' logic only if the top level input actually changed. This especially helps with --hierarchical/--lib-create which runs the 'ico' of each sub-model in the eval settle loop. This was observed to yield 40%+ run-time speedup on some partitioned designs. The added change detection is cheap, so it is emitted even if the 'ico' region is small, and is on by default. The optimization is only sound if the model itself does not write to the top level inputs (otherwise the 'previous value' variables would be out of sync, which are not updated by internal writes.). If we can detect a top level input is written within the design, then for that input, we fall back on always running the relevant logic. With --vpi we cannot prove safety statically, so --vpi will disable this optimisation unless explicitly enabled. (In which case it's the user's responsibility to not write to top level inputs via the VPI.)
454 lines
21 KiB
C++
454 lines
21 KiB
C++
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
//*************************************************************************
|
|
// DESCRIPTION: Verilator: Resolve module/signal name references
|
|
//
|
|
// Code available from: https://verilator.org
|
|
//
|
|
//*************************************************************************
|
|
//
|
|
// This program is free software; you can redistribute it and/or modify it
|
|
// under the terms of either the GNU Lesser General Public License Version 3
|
|
// or the Perl Artistic License Version 2.0.
|
|
// SPDX-FileCopyrightText: 2003-2026 Wilson Snyder
|
|
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
|
//
|
|
//*************************************************************************
|
|
// LINKTOP TRANSFORMATIONS:
|
|
// Utility functions
|
|
// Sort cells by depth
|
|
// Create new MODULE TOP with connections to below signals
|
|
//*************************************************************************
|
|
|
|
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
|
|
|
|
#include "V3LinkLevel.h"
|
|
|
|
#include <vector>
|
|
|
|
VL_DEFINE_DEBUG_FUNCTIONS;
|
|
|
|
//######################################################################
|
|
// Levelizing class functions
|
|
|
|
struct CmpLevel final {
|
|
bool operator()(const AstNodeModule* lhsp, const AstNodeModule* rhsp) const {
|
|
return lhsp->level() < rhsp->level();
|
|
}
|
|
};
|
|
|
|
void V3LinkLevel::modSortByLevel() {
|
|
// Sort modules by levels, root down to lowest children
|
|
// Calculate levels again in case we added modules
|
|
UINFO(2, "modSortByLevel()");
|
|
|
|
// level() was computed for us in V3LinkCells
|
|
|
|
ModVec mods; // Modules
|
|
ModVec tops; // Top level modules
|
|
for (AstNodeModule* nodep = v3Global.rootp()->modulesp(); nodep;
|
|
nodep = VN_AS(nodep->nextp(), NodeModule)) {
|
|
if (nodep->isTop() && !VN_IS(nodep, NotFoundModule)) {
|
|
UINFO(9, "top candidate " << nodep);
|
|
tops.push_back(nodep);
|
|
}
|
|
mods.push_back(nodep);
|
|
}
|
|
if (tops.size() >= 2) {
|
|
const AstNode* const secp = tops[1]; // Complain about second one, as first often intended
|
|
if (!secp->fileline()->warnIsOff(V3ErrorCode::MULTITOP)) {
|
|
auto warnTopModules = [](const AstNode* const secp, ModVec tops)
|
|
VL_REQUIRES(V3Error::s().m_mutex) -> std::string {
|
|
std::stringstream ss;
|
|
for (AstNode* alsop : tops) {
|
|
ss << secp->warnMore() << "... Top module " << alsop->prettyNameQ() << '\n'
|
|
<< alsop->warnContextSecondary();
|
|
}
|
|
return ss.str();
|
|
};
|
|
|
|
secp->v3warn(MULTITOP, "Multiple top level modules\n"
|
|
<< secp->warnMore()
|
|
<< "... Suggest see manual; fix the duplicates, or use "
|
|
"--top-module to select top."
|
|
<< V3Error::s().warnContextNone()
|
|
<< V3Error::warnAdditionalInfo()
|
|
<< warnTopModules(secp, tops));
|
|
}
|
|
}
|
|
|
|
timescaling(mods);
|
|
|
|
// Reorder the netlist's modules to have modules in level sorted order
|
|
stable_sort(mods.begin(), mods.end(), CmpLevel()); // Sort the vector
|
|
UINFO(9, "modSortByLevel() sorted"); // Comment required for gcc4.6.3 / bug666
|
|
for (AstNodeModule* nodep : mods) nodep->unlinkFrBack();
|
|
UASSERT_OBJ(!v3Global.rootp()->modulesp(), v3Global.rootp(), "Unlink didn't work");
|
|
for (AstNodeModule* nodep : mods) v3Global.rootp()->addModulesp(nodep);
|
|
UINFO(9, "modSortByLevel() done"); // Comment required for gcc4.6.3 / bug666
|
|
V3Global::dumpCheckGlobalTree("cellsort", false, dumpTreeEitherLevel() >= 3);
|
|
}
|
|
|
|
void V3LinkLevel::timescaling(const ModVec& mods) {
|
|
// Timescale determination
|
|
const AstNodeModule* modTimedp = nullptr;
|
|
VTimescale unit{VTimescale::NONE};
|
|
|
|
// Move timeunit attributes from parse to module unit
|
|
// Grammar only allows timeunit as module_item, so no need to recurse full tree
|
|
for (AstNodeModule* modp : mods) {
|
|
for (AstNode *nextp, *childp = modp->stmtsp(); childp; childp = nextp) {
|
|
nextp = childp->nextp();
|
|
// cppcheck-suppress constVariablePointer
|
|
if (AstPragma* pragp = VN_CAST(childp, Pragma)) {
|
|
if (pragp->pragType() == VPragmaType::TIMEUNIT_SET) {
|
|
modp->timeunit(pragp->timescale());
|
|
VL_DO_DANGLING(pragp->unlinkFrBack()->deleteTree(), pragp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Use highest level module as default unit - already sorted in proper order
|
|
// Combine timing into later modules
|
|
for (AstNodeModule* modp : mods) {
|
|
if (!modTimedp && !modp->timeunit().isNone()) {
|
|
modTimedp = modp;
|
|
unit = modTimedp->timeunit();
|
|
break;
|
|
}
|
|
}
|
|
unit = v3Global.opt.timeComputeUnit(unit); // Apply override
|
|
if (unit.isNone()) unit = VTimescale{VTimescale::TS_DEFAULT};
|
|
v3Global.rootp()->timeunit(unit);
|
|
|
|
bool dunitTimed = false; // $unit had a timeunit
|
|
if (const AstPackage* const upkgp = v3Global.rootp()->dollarUnitPkgp()) {
|
|
if (!upkgp->timeunit().isNone()) dunitTimed = true;
|
|
}
|
|
|
|
for (AstNodeModule* modp : mods) {
|
|
if (!v3Global.opt.timeOverrideUnit().isNone()) modp->timeunit(unit);
|
|
if (modp->timeunit().isNone()) {
|
|
if (modTimedp // Got previous
|
|
&& !dunitTimed
|
|
&& ( // unit doesn't already include an override
|
|
v3Global.opt.timeOverrideUnit().isNone()
|
|
&& v3Global.opt.timeDefaultUnit().isNone())
|
|
&& modp->timescaleMatters()) {
|
|
modp->v3warn(TIMESCALEMOD,
|
|
"Timescale missing on this module as other modules have "
|
|
"it (IEEE 1800-2023 3.14.2.3)\n"
|
|
<< modp->warnContextPrimary() << '\n'
|
|
<< modTimedp->warnOther()
|
|
<< "... Location of module with timescale\n"
|
|
<< modTimedp->warnContextSecondary());
|
|
}
|
|
modp->timeunit(unit);
|
|
}
|
|
}
|
|
|
|
v3Global.rootp()->timescaleSpecified(modTimedp); // true if some module specifies timescale
|
|
|
|
if (v3Global.rootp()->timeprecision().isNone()) {
|
|
v3Global.rootp()->timeprecisionMerge(v3Global.rootp()->fileline(),
|
|
VTimescale{VTimescale::TS_DEFAULT});
|
|
}
|
|
|
|
// Classes under package have timescale propagated in V3LinkParse
|
|
}
|
|
|
|
//######################################################################
|
|
// Wrapping
|
|
|
|
void V3LinkLevel::wrapTop(AstNetlist* rootp) {
|
|
UINFO(2, __FUNCTION__ << ":");
|
|
// We do ONLY the top module
|
|
AstNodeModule* const oldmodp = rootp->modulesp();
|
|
if (!oldmodp) { // Later V3LinkDot will warn
|
|
UINFO(1, "No module found to wrap");
|
|
return;
|
|
}
|
|
rootp->resolvedTopModuleName(oldmodp->name());
|
|
|
|
AstNodeModule* const newmodp = new AstModule{oldmodp->fileline(), "$root", oldmodp->libname()};
|
|
newmodp->name(AstNode::encodeName(newmodp->name())); // so origName is nice
|
|
// Make the new module first in the list
|
|
oldmodp->unlinkFrBackWithNext();
|
|
newmodp->addNext(oldmodp);
|
|
newmodp->depth(1);
|
|
newmodp->level(1);
|
|
newmodp->modPublic(true);
|
|
newmodp->protect(false);
|
|
newmodp->timeunit(oldmodp->timeunit());
|
|
rootp->addModulesp(newmodp);
|
|
|
|
// TODO the module creation above could be done after linkcells, but
|
|
// the rest must be done after data type resolution
|
|
wrapTopCell(rootp);
|
|
|
|
// Instantiate all packages under the top wrapper
|
|
// This way all later SCOPE based optimizations can ignore packages
|
|
for (AstNodeModule* modp = rootp->modulesp(); modp; modp = VN_AS(modp->nextp(), NodeModule)) {
|
|
if (VN_IS(modp, Package)) {
|
|
AstCell* const cellp
|
|
= new AstCell{modp->fileline(), modp->fileline(),
|
|
// Could add __03a__03a="::" to prevent conflict
|
|
// with module names/"v"
|
|
modp->name(), modp->name(), nullptr, nullptr, nullptr};
|
|
cellp->modp(modp);
|
|
newmodp->addStmtsp(cellp);
|
|
}
|
|
}
|
|
|
|
// All modules and hier-classes except one we created are now a level deeper
|
|
rootp->foreach([&](AstNodeModule* const modp) {
|
|
if (modp != newmodp && modp->level()) modp->level(1 + modp->level());
|
|
});
|
|
|
|
V3Global::dumpCheckGlobalTree("wraptop", 0, dumpTreeEitherLevel() >= 6);
|
|
}
|
|
|
|
void V3LinkLevel::wrapTopCell(AstNetlist* rootp) {
|
|
AstNodeModule* const newmodp = rootp->modulesp();
|
|
UASSERT_OBJ(newmodp && newmodp->isTop(), rootp, "No TOP module found to insert under");
|
|
|
|
// Find all duplicate signal names (if multitop)
|
|
using NameSet = std::unordered_set<std::string>;
|
|
NameSet ioNames;
|
|
NameSet dupNames;
|
|
// For all modules, skipping over new top
|
|
// cppcheck-suppress constVariablePointer
|
|
for (AstNodeModule* oldmodp = VN_AS(rootp->modulesp()->nextp(), NodeModule);
|
|
oldmodp && oldmodp->isTop(); oldmodp = VN_AS(oldmodp->nextp(), NodeModule)) {
|
|
for (AstNode* subnodep = oldmodp->stmtsp(); subnodep; subnodep = subnodep->nextp()) {
|
|
if (AstVar* const oldvarp = VN_CAST(subnodep, Var)) {
|
|
if (oldvarp->isIO()) {
|
|
if (!ioNames.insert(oldvarp->name()).second) {
|
|
// UINFO(8, "Multitop dup I/O found: " << oldvarp);
|
|
dupNames.insert(oldvarp->name());
|
|
}
|
|
} else if (v3Global.opt.topIfacesSupported() && oldvarp->isIfaceRef()) {
|
|
const AstNodeDType* const subtypep = oldvarp->subDTypep();
|
|
if (VN_IS(subtypep, IfaceRefDType)) {
|
|
const AstIfaceRefDType* const ifacerefp = VN_AS(subtypep, IfaceRefDType);
|
|
if (!ifacerefp->cellp()) {
|
|
if (!ioNames.insert(oldvarp->name()).second) {
|
|
// UINFO(8, "Multitop dup interface found: " << oldvarp);
|
|
dupNames.insert(oldvarp->name());
|
|
}
|
|
}
|
|
}
|
|
if (VN_IS(subtypep, UnpackArrayDType)) {
|
|
const AstUnpackArrayDType* const arrp = VN_AS(subtypep, UnpackArrayDType);
|
|
const AstNodeDType* const arrsubtypep = arrp->subDTypep();
|
|
if (VN_IS(arrsubtypep, IfaceRefDType)) {
|
|
const AstIfaceRefDType* const ifacerefp
|
|
= VN_AS(arrsubtypep, IfaceRefDType);
|
|
if (!ifacerefp->cellp()) {
|
|
if (!ioNames.insert(oldvarp->name()).second) {
|
|
// UINFO(8, "Multitop dup interface array found: " << oldvarp);
|
|
dupNames.insert(oldvarp->name());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// For all modules, skipping over new top
|
|
for (AstNodeModule* oldmodp = VN_AS(rootp->modulesp()->nextp(), NodeModule);
|
|
oldmodp && oldmodp->isTop(); oldmodp = VN_AS(oldmodp->nextp(), NodeModule)) {
|
|
if (VN_IS(oldmodp, Package)) continue;
|
|
// Add instance
|
|
UINFO(5, "LOOP " << oldmodp);
|
|
AstCell* const cellp = new AstCell{
|
|
newmodp->fileline(),
|
|
newmodp->fileline(),
|
|
(!v3Global.opt.l2Name().empty() ? v3Global.opt.l2Name() : oldmodp->name()),
|
|
oldmodp->name(),
|
|
nullptr,
|
|
nullptr,
|
|
nullptr};
|
|
cellp->modp(oldmodp);
|
|
newmodp->addStmtsp(cellp);
|
|
|
|
// Add pins
|
|
for (AstNode* subnodep = oldmodp->stmtsp(); subnodep; subnodep = subnodep->nextp()) {
|
|
if (AstVar* const oldvarp = VN_CAST(subnodep, Var)) {
|
|
UINFO(8, "VARWRAP " << oldvarp);
|
|
if (oldvarp->isIO()) {
|
|
string name = oldvarp->name();
|
|
if (dupNames.find(name) != dupNames.end()) {
|
|
// __02E=. while __DOT__ looks nicer but will break V3LinkDot
|
|
name = oldmodp->name() + "__02E" + name;
|
|
}
|
|
|
|
AstVar* const varp = oldvarp->cloneTree(false);
|
|
varp->name(name);
|
|
varp->protect(false);
|
|
newmodp->addStmtsp(varp);
|
|
varp->sigPublic(true); // User needs to be able to get to it...
|
|
oldvarp->primaryIO(false);
|
|
varp->primaryIO(true);
|
|
varp->icoMaybeWritten(oldvarp->icoMaybeWritten());
|
|
if (varp->isRef() || varp->isConstRef()) {
|
|
varp->v3warn(E_UNSUPPORTED,
|
|
"Unsupported: ref/const ref as primary input/output: "
|
|
<< varp->prettyNameQ());
|
|
}
|
|
if (varp->isIO() && v3Global.opt.systemC()) {
|
|
varp->sc(true);
|
|
// User can see trace one level down from the wrapper
|
|
// Avoids packing & unpacking SC signals a second time
|
|
varp->trace(false);
|
|
}
|
|
|
|
if (v3Global.opt.noTraceTop() && varp->isIO()) varp->trace(false);
|
|
|
|
AstPin* const pinp = new AstPin{
|
|
oldvarp->fileline(), 0, varp->name(),
|
|
new AstVarRef{varp->fileline(), varp,
|
|
oldvarp->isWritable() ? VAccess::WRITE : VAccess::READ}};
|
|
// Skip length and width comp; we know it's a direct assignment
|
|
pinp->modVarp(oldvarp);
|
|
cellp->addPinsp(pinp);
|
|
} else if (v3Global.opt.topIfacesSupported() && oldvarp->isIfaceRef()) {
|
|
// for each interface port on oldmodp instantiate a corresponding interface
|
|
// cell in $root
|
|
const AstNodeDType* const subtypep = oldvarp->subDTypep();
|
|
if (VN_IS(subtypep, IfaceRefDType)) {
|
|
const AstIfaceRefDType* const ifacerefp = VN_AS(subtypep, IfaceRefDType);
|
|
if (!ifacerefp->cellp()) {
|
|
string name = oldvarp->name();
|
|
if (dupNames.find(name) != dupNames.end()) {
|
|
// __02E=. while __DOT__ looks nicer but will break V3LinkDot
|
|
name = oldmodp->name() + "__02E" + name;
|
|
}
|
|
|
|
AstCell* ifacecellp = new AstCell{newmodp->fileline(),
|
|
newmodp->fileline(),
|
|
name,
|
|
ifacerefp->ifaceName(),
|
|
nullptr,
|
|
nullptr,
|
|
nullptr};
|
|
ifacecellp->modp(ifacerefp->ifacep());
|
|
newmodp->addStmtsp(ifacecellp);
|
|
|
|
AstIfaceRefDType* const idtypep = new AstIfaceRefDType{
|
|
newmodp->fileline(), name, ifacerefp->ifaceName()};
|
|
idtypep->ifacep(nullptr);
|
|
idtypep->dtypep(idtypep);
|
|
idtypep->cellp(ifacecellp);
|
|
rootp->typeTablep()->addTypesp(idtypep);
|
|
|
|
AstVar* varp = new AstVar{newmodp->fileline(), VVarType::IFACEREF,
|
|
name + "__Viftop", idtypep};
|
|
varp->isIfaceParent(true);
|
|
ifacecellp->addNextHere(varp);
|
|
ifacecellp->hasIfaceVar(true);
|
|
|
|
AstPin* const pinp
|
|
= new AstPin{oldvarp->fileline(), 0, varp->name(),
|
|
new AstVarRef{varp->fileline(), varp,
|
|
oldvarp->isWritable() ? VAccess::WRITE
|
|
: VAccess::READ}};
|
|
pinp->modVarp(oldvarp);
|
|
cellp->addPinsp(pinp);
|
|
}
|
|
} else if (VN_IS(subtypep, UnpackArrayDType)) {
|
|
const AstUnpackArrayDType* const oldarrp
|
|
= VN_AS(subtypep, UnpackArrayDType);
|
|
const AstNodeDType* const arrsubtypep = oldarrp->subDTypep();
|
|
if (VN_IS(arrsubtypep, IfaceRefDType)) {
|
|
const AstIfaceRefDType* const ifacerefp
|
|
= VN_AS(arrsubtypep, IfaceRefDType);
|
|
if (!ifacerefp->cellp()) {
|
|
string name = oldvarp->name();
|
|
if (dupNames.find(name) != dupNames.end()) {
|
|
// __02E=. while __DOT__ looks nicer but will break V3LinkDot
|
|
name = oldmodp->name() + "__02E" + name;
|
|
}
|
|
|
|
AstUnpackArrayDType* arraydtypep
|
|
= VN_AS(oldvarp->dtypep(), UnpackArrayDType);
|
|
AstCell* ifacearraycellp
|
|
= new AstCell{newmodp->fileline(),
|
|
newmodp->fileline(),
|
|
name,
|
|
ifacerefp->ifaceName(),
|
|
nullptr,
|
|
nullptr,
|
|
arraydtypep->rangep()->cloneTree(true)};
|
|
ifacearraycellp->modp(ifacerefp->ifacep());
|
|
newmodp->addStmtsp(ifacearraycellp);
|
|
|
|
AstIfaceRefDType* const idtypep = new AstIfaceRefDType{
|
|
newmodp->fileline(), name, ifacerefp->ifaceName()};
|
|
idtypep->ifacep(nullptr);
|
|
idtypep->dtypep(idtypep);
|
|
idtypep->cellp(ifacearraycellp);
|
|
rootp->typeTablep()->addTypesp(idtypep);
|
|
|
|
AstNodeArrayDType* const arrp = new AstUnpackArrayDType{
|
|
newmodp->fileline(), idtypep,
|
|
arraydtypep->rangep()->cloneTree(true)};
|
|
AstVar* varp = new AstVar{newmodp->fileline(), VVarType::IFACEREF,
|
|
name + "__Viftop", arrp};
|
|
varp->isIfaceParent(true);
|
|
ifacearraycellp->addNextHere(varp);
|
|
ifacearraycellp->hasIfaceVar(true);
|
|
rootp->typeTablep()->addTypesp(arrp);
|
|
|
|
AstPin* const pinp = new AstPin{
|
|
oldvarp->fileline(), 0, varp->name(),
|
|
new AstVarRef{varp->fileline(), varp,
|
|
oldvarp->isWritable() ? VAccess::WRITE
|
|
: VAccess::READ}};
|
|
pinp->modVarp(oldvarp);
|
|
cellp->addPinsp(pinp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void V3LinkLevel::nonWrapTop(AstNetlist* rootp) {
|
|
// Perform variable setting step for top modules to prevent
|
|
// optimizing away primary ports when only serializing input
|
|
if (!rootp->modulesp()) { // Later V3LinkDot will warn
|
|
UINFO(1, "No module found");
|
|
return;
|
|
}
|
|
|
|
// For all top modules
|
|
for (AstNodeModule* modp = rootp->modulesp(); modp && modp->isTop();
|
|
modp = VN_AS(modp->nextp(), NodeModule)) {
|
|
if (VN_IS(modp, Package)) continue;
|
|
|
|
UINFO(5, "LOOP " << modp);
|
|
|
|
for (AstNode* subnodep = modp->stmtsp(); subnodep; subnodep = subnodep->nextp()) {
|
|
if (AstVar* const varp = VN_CAST(subnodep, Var)) {
|
|
UINFO(8, "VARWRAP " << varp);
|
|
if (varp->isIO()) {
|
|
varp->protect(false);
|
|
varp->sigPublic(true);
|
|
varp->primaryIO(true);
|
|
if (varp->isRef() || varp->isConstRef()) {
|
|
varp->v3warn(E_UNSUPPORTED,
|
|
"Unsupported: ref/const ref as primary input/output: "
|
|
<< varp->prettyNameQ());
|
|
}
|
|
if (v3Global.opt.systemC()) varp->sc(true);
|
|
if (v3Global.opt.noTraceTop()) varp->trace(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|