Files
verilator/src/V3LinkLevel.cpp
T
Geza Lore 5ab2bf1ec4 Optimize input combinational logic by change detection (#7784)
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.)
2026-06-15 05:42:00 +01:00

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);
}
}
}
}
}