This commit is contained in:
Todd Strader 2026-07-14 19:58:35 -04:00 committed by GitHub
commit dfac5bf414
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
59 changed files with 1567 additions and 0 deletions

View File

@ -266,10 +266,16 @@ The grammar of control commands is as follows:
.. option:: public [-module "<modulename>"] [-task/-function "<taskname>"] [-var "<signame>"]
.. option:: public_flat -path "<hier.path.signame>"
.. option:: public_flat [-module "<modulename>"] [-task/-function "<taskname>"] [(-param | -port | -var) "<signame>"]
.. option:: public_flat_rd -path "<hier.path.signame>"
.. option:: public_flat_rd [-module "<modulename>"] [-task/-function "<taskname>"] [(-param | -port | -var) "<signame>"]
.. option:: public_flat_rw -path "<hier.path.signame>"
.. option:: public_flat_rw [-module "<modulename>"] [-task/-function "<taskname>"] [(-param | -port | -var) "<signame>"] ["@(edge)"]
Sets the specified signal to be public. Same as
@ -283,6 +289,23 @@ The grammar of control commands is as follows:
following ``<signame>`` can contain ``*`` and ``?`` wildcard
characters that match any substring or any single character respectively.
For ``public_flat``, ``public_flat_rd``, ``public_flat_rw``, and
``public``, ``-path`` may instead be given a fully qualified
hierarchical path (e.g. ``-path "top.i_sub.signame"``).
Verilator then walks the design hierarchy to find the named signal.
Wildcards are not supported in hierarchical paths. A ``\`` introducing a
Verilog escaped identifier must be written in the quoted string as ``\\``
and have trailing whitespace, for example
``-path "top.\\inst.with.dot .\\sig.with.dot "``.
When a hierarchical ``-path`` traverses an arrayed instance (e.g.
``sub i_sub [3:0] ();``), per-iteration selectivity is not currently
supported. Naming a single iteration such as
``-path "top.i_sub[2].signame"`` marks the signal as public in **all**
iterations of that cell array. Non-arrayed sibling instantiations
(e.g. ``sub i_a(); sub i_b();``) and generate-for iterations do support
per-instance selectivity.
.. option:: sc_biguint -module "<modulename>" -var "<signame>"
Sets the input/output signal to be of ``sc_biguint<{width}>`` type.

View File

@ -2146,6 +2146,7 @@ class AstVar final : public AstNode {
bool m_sigModPublic : 1; // User C code accesses this signal and module
bool m_sigUserRdPublic : 1; // User C code accesses this signal, read only
bool m_sigUserRWPublic : 1; // User C code accesses this signal, read-write
bool m_mayBecomePublic : 1; // Pre-V3Begin: candidate for hier-var-driven public flag
bool m_usedParam : 1; // Parameter is referenced (on link; later signals not setup)
bool m_usedLoopIdx : 1; // Variable subject of for unrolling
bool m_funcLocal : 1; // Local variable for a function
@ -2212,6 +2213,7 @@ class AstVar final : public AstNode {
m_sigModPublic = false;
m_sigUserRdPublic = false;
m_sigUserRWPublic = false;
m_mayBecomePublic = false;
m_funcLocal = false;
m_funcLocalSticky = false;
m_funcReturn = false;
@ -2388,6 +2390,8 @@ public:
m_sigUserRWPublic = flag;
if (flag) sigUserRdPublic(true);
}
bool mayBecomePublic() const { return m_mayBecomePublic; }
void mayBecomePublic(bool flag) { m_mayBecomePublic = flag; }
void sc(bool flag) { m_sc = flag; }
void scSensitive(bool flag) { m_scSensitive = flag; }
void primaryIO(bool flag) { m_primaryIO = flag; }

View File

@ -18,12 +18,18 @@
#include "V3Control.h"
#include "V3Hash.h"
#include "V3InstrCount.h"
#include "V3String.h"
#include <cerrno>
#include <cstdlib>
#include <limits>
#include <map>
#include <memory>
#include <set>
#include <unordered_map>
#include <vector>
VL_DEFINE_DEBUG_FUNCTIONS;
@ -791,11 +797,54 @@ public:
FileLine* flp() const { return m_flp; }
};
// Deferred application of a public attribute given by hierarchical -path
enum class V3ControlHierKind : uint8_t {
UNKNOWN, // Not yet classified
CELL,
BLOCK
};
struct V3ControlHierSegment final {
std::string m_name; // Identifier without trailing array index(es)
std::vector<int> m_indices; // Trailing array indices
V3ControlHierKind m_kind = V3ControlHierKind::UNKNOWN;
};
struct V3ControlHierVarEntry final {
FileLine* m_fl;
std::string m_path; // Full path
VAttrType m_attr;
std::string m_topName; // First segment: top module name
std::vector<V3ControlHierSegment> m_middle; // Intermediate cells/blocks
std::string m_leafName; // Final segment: variable name
V3ControlHierVarEntry(FileLine* fl, const std::string& path, VAttrType attr)
: m_fl{fl}
, m_path{path}
, m_attr{attr} {}
};
// Prefix tree over all -path entries
struct V3ControlHierTreeNode final {
V3ControlHierKind m_kind = V3ControlHierKind::UNKNOWN; // Kind of this node's segment
// Public'ed vars directly in this scope: (leaf var pretty name, attribute).
std::vector<std::pair<std::string, VAttrType>> m_vars;
// Child scopes keyed by pretty segment text.
std::map<std::string, std::unique_ptr<V3ControlHierTreeNode>> m_children;
V3ControlHierTreeNode* getOrAddChild(const std::string& key) {
std::unique_ptr<V3ControlHierTreeNode>& slot = m_children[key];
if (!slot) slot = std::make_unique<V3ControlHierTreeNode>();
return slot.get();
}
};
class V3ControlResolver final {
enum ProfileDataMode : uint8_t { NONE = 0, MTASK = 1, HIER_DPI = 2 };
V3ControlModuleResolver m_modules; // Access to module names (with wildcards)
V3ControlFileResolver m_files; // Access to file names (with wildcards)
V3ControlScopeTraceResolver m_scopeTraces; // Regexp to trace enables
std::vector<V3ControlHierVarEntry> m_hierVarAttrs; // -path public attrs
V3ControlHierTreeNode m_hierVarTreeRoot; // Prefix tree over m_hierVarAttrs
std::unordered_map<std::string, V3ControlHierTreeNode*> m_hierVarCellPaths;
std::map<V3Hash, int> m_hierVarVariants; // Interned hier variant hashes
std::unordered_map<string, std::unordered_map<string, uint64_t>>
m_profileData; // Access to profile_data records
uint8_t m_mode = NONE;
@ -813,6 +862,12 @@ public:
V3ControlModuleResolver& modules() { return m_modules; }
V3ControlFileResolver& files() { return m_files; }
V3ControlScopeTraceResolver& scopeTraces() { return m_scopeTraces; }
std::vector<V3ControlHierVarEntry>& hierVarAttrs() { return m_hierVarAttrs; }
V3ControlHierTreeNode& hierVarTreeRoot() { return m_hierVarTreeRoot; }
std::unordered_map<std::string, V3ControlHierTreeNode*>& hierVarCellPaths() {
return m_hierVarCellPaths;
}
std::map<V3Hash, int>& hierVarVariants() { return m_hierVarVariants; }
void addProfileData(FileLine* fl, const string& hierDpi, uint64_t cost) {
// Empty key for hierarchical DPI wrapper costs.
@ -1015,6 +1070,20 @@ void V3Control::addVarAttr(FileLine* fl, const string& module, const string& fta
}
}
// For -path'ed vars
void V3Control::addHierVarAttr(FileLine* fl, const string& path, VAttrType attr) {
switch (attr) {
case VAttrType::VAR_PUBLIC:
case VAttrType::VAR_PUBLIC_FLAT:
case VAttrType::VAR_PUBLIC_FLAT_RD:
case VAttrType::VAR_PUBLIC_FLAT_RW:
V3ControlResolver::s().hierVarAttrs().emplace_back(fl, path, attr);
v3Global.dpi(true);
return;
default: fl->v3error("'"s + attr.ascii() + "' attribute does not support -path"); return;
}
}
void V3Control::applyCase(AstCase* nodep) {
const string& filename = nodep->fileline()->filename();
V3ControlFile* const filep = V3ControlResolver::s().files().resolve(filename);
@ -1084,6 +1153,506 @@ void V3Control::applyVarAttr(const AstNodeModule* modulep, const AstNodeFTask* f
}
}
// Parse one decimal array index from 'indexText' and append it to seg.m_indices.
static bool parseIndexDigits(const std::string& indexText, V3ControlHierSegment& seg) {
const size_t firstDigit = (!indexText.empty() && indexText[0] == '-') ? 1 : 0;
if (indexText.size() <= firstDigit) return false; // Empty, or just "-"
for (size_t i = firstDigit; i < indexText.size(); ++i) {
if (indexText[i] < '0' || indexText[i] > '9') return false;
}
errno = 0;
char* endp = nullptr;
const long value = std::strtol(indexText.c_str(), &endp, 10);
if (errno != 0 || *endp != '\0') return false; // Overflow of long, or trailing junk
if (value < std::numeric_limits<int>::min() || value > std::numeric_limits<int>::max())
return false; // Overflow of int
seg.m_indices.push_back(static_cast<int>(value));
return true;
}
enum class IndexRunResult : uint8_t {
OK, // Parsed a (possibly empty) run of "[N]" groups, pos past last ']'
UNTERMINATED, // A '[' has no matching ']'; pos left at the offending '['
BAD_DIGITS // A "[...]" body is not a valid index; pos left at the offending '['
};
static IndexRunResult parseIndexRun(const std::string& str, std::string::size_type& pos,
V3ControlHierSegment& seg) {
while (pos < str.size() && str[pos] == '[') {
const std::string::size_type closep = str.find(']', pos);
if (closep == std::string::npos) return IndexRunResult::UNTERMINATED;
if (!parseIndexDigits(str.substr(pos + 1, closep - pos - 1), seg)) {
return IndexRunResult::BAD_DIGITS;
}
pos = closep + 1;
}
return IndexRunResult::OK;
}
static bool parseEscapedIndex(FileLine* fl, const std::string& path, std::string::size_type& pos,
V3ControlHierSegment& seg) {
const IndexRunResult result = parseIndexRun(path, pos, seg);
if (result == IndexRunResult::UNTERMINATED) {
fl->v3error("-path '" << path << "': unterminated '[' in escaped-name array index");
return false;
}
if (result == IndexRunResult::BAD_DIGITS) {
const std::string::size_type closep = path.find(']', pos);
fl->v3error("-path '" << path << "': malformed array index '"
<< path.substr(pos, closep - pos + 1) << "'");
return false;
}
return true;
}
static bool parsePlainSegment(FileLine* fl, const std::string& path, const std::string& raw,
V3ControlHierSegment& seg) {
const std::string::size_type firstOpen = raw.find('[');
if (firstOpen == std::string::npos) {
if (raw.find(']') != std::string::npos) {
fl->v3error("-path '" << path << "': malformed array index in segment '" << raw
<< "'");
return false;
}
seg.m_name = raw;
return true;
}
seg.m_name = raw.substr(0, firstOpen);
std::string::size_type pos = firstOpen;
const bool ok = firstOpen != 0 && parseIndexRun(raw, pos, seg) == IndexRunResult::OK;
if (!ok || pos != raw.size() || seg.m_name.find(']') != std::string::npos) {
fl->v3error("-path '" << path << "': malformed array index in segment '" << raw << "'");
return false;
}
return true;
}
static std::vector<V3ControlHierSegment> splitHierPath(FileLine* fl, const std::string& path,
bool& errOut) {
static const std::string WHITESPACE = " \t\n\v\f\r";
errOut = false;
std::vector<V3ControlHierSegment> segs;
std::string::size_type i = 0;
const std::string::size_type len = path.size();
while (i < len) {
V3ControlHierSegment seg;
if (path[i] == '\\') {
++i; // skip '\'
const std::string::size_type start = i;
i = path.find_first_of(WHITESPACE, i);
if (i == std::string::npos) i = len;
seg.m_name = path.substr(start, i - start);
if (seg.m_name.empty()) {
fl->v3error("-path '" << path << "': empty escaped identifier");
errOut = true;
return {};
}
// Skip the terminating whitespace of the escaped identifier
i = path.find_first_not_of(WHITESPACE, i);
if (i == std::string::npos) i = len;
if (i < len && path[i] == '[') {
if (!parseEscapedIndex(fl, path, i, seg)) {
errOut = true;
return {};
}
}
} else {
const std::string::size_type start = i;
while (i < len && path[i] != '.') ++i;
const std::string raw = path.substr(start, i - start);
if (raw.empty()) {
fl->v3error("-path '" << path << "': empty identifier in path");
errOut = true;
return {};
}
if (!parsePlainSegment(fl, path, raw, seg)) {
errOut = true;
return {};
}
}
segs.push_back(seg);
if (i < len) {
if (path[i] != '.') {
fl->v3error("-path '" << path << "': expected '.' separator between identifiers");
errOut = true;
return {};
}
++i; // skip '.'
if (i >= len) {
fl->v3error("-path '" << path << "': trailing '.' with no identifier");
errOut = true;
return {};
}
}
}
return segs;
}
static std::string segmentPrettyText(const V3ControlHierSegment& seg) {
std::string out = seg.m_name;
for (const int index : seg.m_indices) out += "[" + std::to_string(index) + "]";
return out;
}
static void applyHierVarAttrToVar(AstVar* varp, VAttrType attr) {
switch (attr) {
case VAttrType::VAR_PUBLIC:
varp->sigUserRWPublic(true);
varp->sigModPublic(true);
break;
case VAttrType::VAR_PUBLIC_FLAT:
case VAttrType::VAR_PUBLIC_FLAT_RW: varp->sigUserRWPublic(true); break;
case VAttrType::VAR_PUBLIC_FLAT_RD: varp->sigUserRdPublic(true); break;
default: break;
}
}
static std::string scopeName(const AstNode* scopep) {
if (const AstNodeModule* m = VN_CAST(scopep, NodeModule)) return m->prettyDehashOrigOrName();
if (const AstBegin* b = VN_CAST(scopep, Begin)) return b->prettyDehashOrigOrName();
if (const AstGenBlock* g = VN_CAST(scopep, GenBlock)) return g->prettyDehashOrigOrName();
return scopep ? scopep->prettyName() : std::string{"<null>"};
}
static void collectScopeMatches(AstNode* firstp, const std::string& name,
const std::string& stripped, std::vector<AstNode*>& out) {
for (AstNode* p = firstp; p; p = p->nextp()) {
if (AstCell* const cellp = VN_CAST(p, Cell)) {
if (cellp->prettyDehashOrigOrName() == name || cellp->origName() == name
|| cellp->name() == name) {
out.push_back(cellp);
continue;
}
// Currently touching one element in a cell array touches them all
if (cellp->rangep()
&& (cellp->prettyDehashOrigOrName() == stripped || cellp->origName() == stripped
|| cellp->name() == stripped))
out.push_back(cellp);
} else if (AstBegin* const beginp = VN_CAST(p, Begin)) {
if (beginp->prettyDehashOrigOrName() == name || beginp->name() == name
|| beginp->prettyDehashOrigOrName() == stripped || beginp->name() == stripped)
out.push_back(beginp);
} else if (AstGenBlock* const genp = VN_CAST(p, GenBlock)) {
if (genp->prettyDehashOrigOrName() == name || genp->name() == name
|| genp->prettyDehashOrigOrName() == stripped || genp->name() == stripped) {
out.push_back(genp);
continue;
}
if (genp->implied() || genp->unnamed())
collectScopeMatches(genp->itemsp(), name, stripped, out);
} else if (AstGenIf* const ifp = VN_CAST(p, GenIf)) {
collectScopeMatches(ifp->thensp(), name, stripped, out);
collectScopeMatches(ifp->elsesp(), name, stripped, out);
} else if (AstGenCase* const casep = VN_CAST(p, GenCase)) {
for (AstGenCaseItem* itemp = casep->itemsp(); itemp;
itemp = VN_AS(itemp->nextp(), GenCaseItem)) {
collectScopeMatches(itemp->itemsp(), name, stripped, out);
}
}
}
}
using ScopeChildrenCache = std::map<std::pair<const AstNode*, std::string>, std::vector<AstNode*>>;
static const std::vector<AstNode*>&
findScopeChildren(AstNode* scopep, const V3ControlHierSegment& seg, ScopeChildrenCache& cache) {
const std::string name = segmentPrettyText(seg);
const auto key = std::make_pair(static_cast<const AstNode*>(scopep), name);
const auto pair = cache.emplace(key, std::vector<AstNode*>{});
std::vector<AstNode*>& out = pair.first->second;
if (!pair.second) return out; // Cache hit
const std::string& stripped = seg.m_name;
if (const AstNodeModule* m = VN_CAST(scopep, NodeModule))
collectScopeMatches(m->stmtsp(), name, stripped, out);
else if (const AstBegin* b = VN_CAST(scopep, Begin))
collectScopeMatches(b->stmtsp(), name, stripped, out);
else if (const AstGenBlock* g = VN_CAST(scopep, GenBlock)) {
collectScopeMatches(g->itemsp(), name, stripped, out);
if (const AstGenFor* const forp = VN_CAST(g->genforp(), GenFor))
collectScopeMatches(forp->itemsp(), name, stripped, out);
}
return out;
}
static AstVar* matchVarList(AstNode* firstp, const std::string& name) {
for (AstNode* p = firstp; p; p = p->nextp()) {
if (AstVar* const varp = VN_CAST(p, Var)) {
if (varp->prettyDehashOrigOrName() == name || varp->name() == name) return varp;
}
}
return nullptr;
}
static AstVar* findVarInScope(AstNode* scopep, const std::string& name) {
if (const AstNodeModule* m = VN_CAST(scopep, NodeModule))
return matchVarList(m->stmtsp(), name);
if (const AstBegin* b = VN_CAST(scopep, Begin)) return matchVarList(b->stmtsp(), name);
if (const AstGenBlock* g = VN_CAST(scopep, GenBlock)) {
if (AstVar* const varp = matchVarList(g->itemsp(), name)) return varp;
if (const AstGenFor* const forp = VN_CAST(g->genforp(), GenFor))
return matchVarList(forp->itemsp(), name);
}
return nullptr;
}
struct PathWalkResult final {
bool anyApplied = false;
size_t deepestIdx = 0;
AstNode* deepestScopep = nullptr;
};
static void walkAndApply(AstNode* scopep, const std::vector<V3ControlHierSegment>& segs,
size_t idx, V3ControlHierVarEntry& entry, PathWalkResult& result,
ScopeChildrenCache& cache) {
if (idx > result.deepestIdx) {
result.deepestIdx = idx;
result.deepestScopep = scopep;
}
if (idx == segs.size() - 1) {
AstVar* const varp = findVarInScope(scopep, segmentPrettyText(segs[idx]));
if (varp) {
varp->mayBecomePublic(true);
result.anyApplied = true;
}
return;
}
const std::vector<AstNode*>& children = findScopeChildren(scopep, segs[idx], cache);
for (AstNode* const childp : children) {
AstNode* nextScopep = nullptr;
V3ControlHierKind kindSeen = V3ControlHierKind::UNKNOWN;
if (AstCell* const cellp = VN_CAST(childp, Cell)) {
if (!cellp->modp()) continue; // Unresolved
nextScopep = cellp->modp();
kindSeen = V3ControlHierKind::CELL;
} else {
nextScopep = childp;
kindSeen = V3ControlHierKind::BLOCK;
}
// idx-1 indexes into entry.m_middle (m_topName is segs[0]).
if (idx >= 1 && (idx - 1) < entry.m_middle.size()
&& entry.m_middle[idx - 1].m_kind == V3ControlHierKind::UNKNOWN) {
entry.m_middle[idx - 1].m_kind = kindSeen;
}
walkAndApply(nextScopep, segs, idx + 1, entry, result, cache);
}
}
static void insertEntryIntoHierTree(const V3ControlHierVarEntry& entry) {
V3ControlHierTreeNode& root = V3ControlResolver::s().hierVarTreeRoot();
auto& cellPaths = V3ControlResolver::s().hierVarCellPaths();
// Top module is the first tree level
V3ControlHierTreeNode* nodep = root.getOrAddChild(entry.m_topName);
nodep->m_kind = V3ControlHierKind::CELL;
const bool registerCells
= !entry.m_middle.empty() && entry.m_middle.back().m_kind == V3ControlHierKind::CELL;
std::string instPath = entry.m_topName;
for (const V3ControlHierSegment& seg : entry.m_middle) {
const std::string key = segmentPrettyText(seg);
instPath += ".";
instPath += key;
nodep = nodep->getOrAddChild(key);
nodep->m_kind = seg.m_kind;
if (registerCells && seg.m_kind == V3ControlHierKind::CELL) cellPaths[instPath] = nodep;
}
nodep->m_vars.emplace_back(entry.m_leafName, entry.m_attr);
}
// Mark all possibly -path'ed vars as mayBecomePublic
void V3Control::applyHierVarAttrs(AstNetlist* netlistp) {
auto& entries = V3ControlResolver::s().hierVarAttrs();
if (entries.empty()) return;
// Reduce scanning for similar paths
ScopeChildrenCache scopeChildrenCache;
std::unordered_map<std::string, AstNodeModule*> topModCache;
for (V3ControlHierVarEntry& entry : entries) {
bool splitErr = false;
const std::vector<V3ControlHierSegment> segs
= splitHierPath(entry.m_fl, entry.m_path, splitErr);
if (splitErr) continue;
if (segs.size() < 2) {
entry.m_fl->v3error("-path '" << entry.m_path
<< "': must name at least a scope and a variable");
continue;
}
const std::string topName = segmentPrettyText(segs[0]);
entry.m_topName = topName;
entry.m_leafName = segmentPrettyText(segs.back());
entry.m_middle.assign(segs.begin() + 1, segs.end() - 1);
const auto topPair = topModCache.emplace(topName, nullptr);
AstNodeModule*& topModp = topPair.first->second;
if (topPair.second) { // Cache miss: scan the netlist once for this name
for (AstNodeModule* modp = netlistp->modulesp(); modp;
modp = VN_AS(modp->nextp(), NodeModule)) {
if (!modp->isTop()) continue;
if (modp->prettyDehashOrigOrName() == topName || modp->origName() == topName
|| modp->name() == topName) {
topModp = modp;
break;
}
}
}
if (!topModp) {
entry.m_fl->v3error("-path '" << entry.m_path << "': cannot find top scope '"
<< topName << "'");
continue;
}
// Walk cells / begins / genblocks to var
PathWalkResult result;
walkAndApply(topModp, segs, 1, entry, result, scopeChildrenCache);
if (result.anyApplied) {
insertEntryIntoHierTree(entry);
continue;
}
// No path resolved - report at the deepest level reached for a useful error.
const size_t failIdx = result.deepestIdx;
AstNode* const failScopep = result.deepestScopep;
const std::string failName = segmentPrettyText(segs[failIdx]);
if (failIdx == segs.size() - 1) {
entry.m_fl->v3error("-path '" << entry.m_path << "': cannot find variable '"
<< failName << "' in scope '" << scopeName(failScopep)
<< "'");
} else {
entry.m_fl->v3error("-path '" << entry.m_path << "': cannot find scope '" << failName
<< "' in scope '" << scopeName(failScopep) << "'");
}
}
// Note: entries are NOT cleared
}
//----------------------------------------------------------------------
// V3Param time per-cell -path unique cloning
static V3Hash hashNodeMarkings(const V3ControlHierTreeNode* nodep) {
uint32_t varsXor = 0; // Order-independent accumulator over the leaf var set
for (const auto& var : nodep->m_vars) {
varsXor ^= (V3Hash{var.first} + V3Hash{static_cast<int32_t>(var.second)}).value();
}
V3Hash hash{varsXor};
for (const auto& child : nodep->m_children) {
hash += V3Hash{child.first} + V3Hash{static_cast<int32_t>(child.second->m_kind)}
+ hashNodeMarkings(child.second.get());
}
return hash;
}
// Variant ID if -path'ed, else -1
int V3Control::cellPathPublicVariant(const std::string& cellInstancePath) {
const auto& cellPaths = V3ControlResolver::s().hierVarCellPaths();
const auto it = cellPaths.find(cellInstancePath);
if (it == cellPaths.end()) return -1; // Not a -path'ed cell; no clone needed
auto& variants = V3ControlResolver::s().hierVarVariants();
const V3Hash hash = hashNodeMarkings(it->second);
const auto pair = variants.emplace(hash, static_cast<int>(variants.size()));
return pair.first->second;
}
//----------------------------------------------------------------------
// Post-V3Begin, pre-V3Inline cell walk and public stamp
static std::string mangleHierSegment(const V3ControlHierSegment& seg) {
std::string out = seg.m_name;
for (const int index : seg.m_indices) {
out += "__BRA__" + AstNode::encodeNumber(index) + "__KET__";
}
return out;
}
// Join two mangled name parts with V3Begin's "__DOT__" separator.
static std::string mangledJoin(const std::string& a, const std::string& b) {
if (a.empty()) return b;
if (b.empty()) return a;
return a + "__DOT__" + b;
}
struct StageDCache final {
std::unordered_map<std::string, AstNodeModule*> m_topMods;
std::map<std::pair<const AstNodeModule*, std::string>, AstCell*> m_cells;
};
static AstNodeModule* findModuleByOrigName(AstNetlist* netlistp, const std::string& name,
StageDCache& cache) {
const auto pair = cache.m_topMods.emplace(name, nullptr);
AstNodeModule*& slot = pair.first->second;
if (!pair.second) return slot; // Cache hit
for (AstNodeModule* modp = netlistp->modulesp(); modp;
modp = VN_AS(modp->nextp(), NodeModule)) {
if (modp->prettyDehashOrigOrName() == name || modp->origName() == name
|| modp->name() == name) {
slot = modp;
break;
}
}
return slot;
}
static AstCell* findCellInModule(AstNodeModule* modp, const std::string& mangledName,
StageDCache& cache) {
const auto key = std::make_pair(static_cast<const AstNodeModule*>(modp), mangledName);
const auto pair = cache.m_cells.emplace(key, nullptr);
AstCell*& slot = pair.first->second;
if (!pair.second) return slot; // Cache hit
const std::string prettyLookup = AstNode::prettyName(mangledName);
for (AstNode* p = modp->stmtsp(); p; p = p->nextp()) {
AstCell* const cellp = VN_CAST(p, Cell);
if (!cellp) continue;
if (cellp->name() == mangledName || cellp->origName() == mangledName
|| cellp->prettyDehashOrigOrName() == mangledName
|| cellp->prettyDehashOrigOrName() == prettyLookup) {
slot = cellp;
break;
}
}
return slot;
}
static AstVar* findVarInModuleByMangledName(AstNodeModule* modp, const std::string& mangledName) {
const std::string prettyLookup = AstNode::prettyName(mangledName);
for (AstNode* p = modp->stmtsp(); p; p = p->nextp()) {
AstVar* const varp = VN_CAST(p, Var);
if (!varp) continue;
if (varp->name() == mangledName || varp->origName() == mangledName
|| varp->prettyDehashOrigOrName() == mangledName
|| varp->prettyDehashOrigOrName() == prettyLookup)
return varp;
}
return nullptr;
}
static void applyOneHierVarMarker(AstNetlist* netlistp, V3ControlHierVarEntry& entry,
StageDCache& cache) {
AstNodeModule* currentModp = findModuleByOrigName(netlistp, entry.m_topName, cache);
if (!currentModp) return; // Error already issued at V3LinkParse time.
std::string mangledPrefix; // Flattened BLOCK segments accumulate here.
for (const V3ControlHierSegment& seg : entry.m_middle) {
const std::string segMangled = mangleHierSegment(seg);
const std::string lookup = mangledJoin(mangledPrefix, segMangled);
if (seg.m_kind == V3ControlHierKind::CELL) {
AstCell* const cellp = findCellInModule(currentModp, lookup, cache);
if (!cellp || !cellp->modp()) return; // Pruned generate iteration, etc.
currentModp = cellp->modp();
mangledPrefix.clear();
} else {
mangledPrefix = lookup;
}
}
const std::string leafLookup = mangledJoin(mangledPrefix, entry.m_leafName);
AstVar* const varp = findVarInModuleByMangledName(currentModp, leafLookup);
if (varp) applyHierVarAttrToVar(varp, entry.m_attr);
}
// Finialize public flags
void V3Control::applyHierVarScopes(AstNetlist* netlistp) {
auto& entries = V3ControlResolver::s().hierVarAttrs();
if (entries.empty()) return;
StageDCache cache;
for (V3ControlHierVarEntry& entry : entries) applyOneHierVarMarker(netlistp, entry, cache);
netlistp->foreach([](AstVar* varp) {
if (varp->mayBecomePublic() && !varp->isSigPublic()) varp->mayBecomePublic(false);
});
entries.clear();
}
int V3Control::getHierWorkers(const string& model) {
return V3ControlResolver::s().getHierWorkers(model);
}

View File

@ -65,6 +65,11 @@ public:
VarSpecKind kind, const string& pattern, VAttrType type,
AstSenTree* nodep);
static void addHierVarAttr(FileLine* fl, const string& path, VAttrType type);
static void applyHierVarAttrs(AstNetlist* netlistp);
static int cellPathPublicVariant(const std::string& cellInstancePath);
static void applyHierVarScopes(AstNetlist* netlistp);
static void applyCase(AstCase* nodep);
static void applyCoverageBlock(AstNodeModule* modulep, AstBegin* nodep);
static void applyCoverageBlock(AstNodeModule* modulep, AstGenBlock* nodep);

View File

@ -418,6 +418,7 @@ class DeadVisitor final : public VNVisitor {
}
bool mightElimVar(const AstVar* nodep) const {
if (nodep->isSigPublic()) return false; // Can't elim publics!
if (nodep->mayBecomePublic()) return false; // .vlt -path'ed candidate
if (nodep->isIO() || nodep->isClassMember() || nodep->sensIfacep()) return false;
if (nodep->isTemp() && !nodep->isTrace()) return true;
return m_elimUserVars; // Post-Trace can kill most anything

View File

@ -1426,6 +1426,7 @@ public:
void V3LinkParse::linkParse(AstNetlist* rootp) {
UINFO(4, __FUNCTION__ << ": ");
V3Control::applyHierVarAttrs(rootp);
{ LinkParseVisitor{rootp}; } // Destruct before checking
V3Global::dumpCheckGlobalTree("linkparse", 0, dumpTreeEitherLevel() >= 6);
}

View File

@ -51,6 +51,7 @@
#include "V3Case.h"
#include "V3Const.h"
#include "V3Control.h"
#include "V3EmitV.h"
#include "V3Hasher.h"
#include "V3LinkDotIfaceCapture.h"
@ -1960,6 +1961,14 @@ class ParamProcessor final {
// Make sure constification worked
// Must be a separate loop, as constant conversion may have changed some pointers.
string longname = srcModp->name() + "_";
// Clone for .vlt -path'ed variants
if (VN_IS(nodep, Cell)) {
const int pubVariant = V3Control::cellPathPublicVariant(srcModp->someInstanceName());
if (pubVariant >= 0) {
any_overrides = true;
longname += "__Vhier" + cvtToStr(pubVariant);
}
}
if (debug() >= 9 && paramsp) paramsp->dumpTreeAndNext(cout, "- cellparams: ");
if (srcModp->hierBlock()) {

View File

@ -172,6 +172,7 @@ struct SplitVarImpl VL_NOT_FINAL {
return reason;
}
if (varp->isSigPublic()) return "it is public";
if (varp->mayBecomePublic()) return "it may become public from .vlt";
if (varp->isUsedLoopIdx()) return "it is used as a loop variable";
if (varp->isForceable()) return "it is forceable";
return nullptr;

View File

@ -302,6 +302,10 @@ static void process() {
// No more AstGenBlocks after this
V3Begin::debeginAll(v3Global.rootp()); // Flatten cell names, before inliner
// Stamp public attributes from .vlt hier-var paths onto per-instance
// leaf vars
V3Control::applyHierVarScopes(v3Global.rootp());
// Expand inouts, stage 2
// Also simplify pin connections to always be AssignWs in prep for V3Unknown
V3Tristate::tristateAll(v3Global.rootp());

View File

@ -179,6 +179,7 @@ vnum {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5}
-?"-module" { FL; return yVLT_D_MODULE; }
-?"-mtask" { FL; return yVLT_D_MTASK; }
-?"-param" { FL; return yVLT_D_PARAM; }
-?"-path" { FL; return yVLT_D_PATH; }
-?"-port" { FL; return yVLT_D_PORT; }
-?"-rule" { FL; return yVLT_D_RULE; }
-?"-q" { FL; return yVLT_D_Q; }

View File

@ -282,6 +282,7 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"})
%token<fl> yVLT_D_MODULE "--module"
%token<fl> yVLT_D_MTASK "--mtask"
%token<fl> yVLT_D_PARAM "--param"
%token<fl> yVLT_D_PATH "--path"
%token<fl> yVLT_D_PORT "--port"
%token<fl> yVLT_D_RULE "--rule"
%token<fl> yVLT_D_Q "--q"
@ -8453,6 +8454,8 @@ vltItem:
}}
| vltVarAttrFront vltDModuleE vltDFTaskE vltVarAttrSpecE attr_event_controlE
{ V3Control::addVarAttr($<fl>1, *$2, *$3, GRAMMARP->m_vltVarSpecKind, *$4, $1, $5); }
| vltVarAttrFront vltDPath
{ V3Control::addHierVarAttr($<fl>1, *$2, $1); }
| vltVarAttrFrontDeprecated vltDModuleE vltDFTaskE vltVarAttrSpecE
{ /* Historical, now has no effect */ }
| vltInlineFront vltDModuleE vltDFTaskE
@ -8578,6 +8581,10 @@ vltDModuleE<strp>: // [--module <arg>]
{ $$ = $1; }
;
vltDPath<strp>: // --path <arg>
yVLT_D_PATH str { $$ = $2; }
;
vltDScope<strp>: // --scope <arg>
yVLT_D_SCOPE str { $$ = $2; }
;

View File

@ -0,0 +1,35 @@
scopesDump:
SCOPE 0x#: TOP
VAR 0x#: top_port_i
VAR 0x#: top_port_o
SCOPE 0x#: top
VAR 0x#: TOP_LOCALPARAM
VAR 0x#: TOP_PARAM
VAR 0x#: top_port_i
VAR 0x#: top_port_o
SCOPE 0x#: top.i_mid_0
VAR 0x#: MID_LOCALPARAM
VAR 0x#: MID_PARM
VAR 0x#: mid_tmp_b
SCOPE 0x#: top.i_mid_0.i_sub_0
VAR 0x#: f__Vstatic__SUB_F_LOCALPARAM
VAR 0x#: sub_port_i
VAR 0x#: sub_port_o
SCOPE 0x#: top.i_mid_0.i_sub_1
VAR 0x#: f__Vstatic__SUB_F_LOCALPARAM
VAR 0x#: sub_port_i
VAR 0x#: sub_port_o
SCOPE 0x#: top.i_mid_1
VAR 0x#: MID_LOCALPARAM
VAR 0x#: MID_PARM
VAR 0x#: mid_tmp_b
SCOPE 0x#: top.i_mid_1.i_sub_0
VAR 0x#: f__Vstatic__SUB_F_LOCALPARAM
VAR 0x#: sub_port_i
VAR 0x#: sub_port_o
SCOPE 0x#: top.i_mid_1.i_sub_1
VAR 0x#: f__Vstatic__SUB_F_LOCALPARAM
VAR 0x#: sub_port_i
VAR 0x#: sub_port_o
*-* All Finished *-*

View File

@ -0,0 +1,21 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# 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: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
test.top_filename = "t/t_vlt_public_spec.v"
test.compile(verilator_flags2=["--binary", "--vpi", test.name + ".vlt"])
test.execute()
test.files_identical(test.run_log_filename, test.golden_filename, is_logfile=True, strip_hex=True)
test.passes()

View File

@ -0,0 +1,22 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
`verilator_config
// Same effect as t_vlt_public_spec.vlt but using hierarchical -path
// (without -module) where applicable. Demonstrates fine-grained per-signal
// public control by fully qualified instance path.
public_flat_rd -module "top" -param "*"
public_flat_rw -module "top" -port "*"
public_flat_rd -module "mid" -param "*"
public_flat_rw -module "mid" -function "*" -port "top_f_port_o"
public_flat -path "top.i_mid_0.mid_tmp_b"
public_flat -path "top.i_mid_1.mid_tmp_b"
public_flat -module "sub" -port "*"
public_flat_rd -module "sub" -function "*" -param "SUB_F_LOCALPARAM"

View File

@ -0,0 +1,17 @@
%Error: t/t_vlt_public_spec_hier_attr_bad.vlt:10:1: 'VAR_SC_BV' attribute does not support -path
10 | sc_bv -path "top.i_mid_0.mid_tmp_b"
| ^~~~~
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: t/t_vlt_public_spec_hier_attr_bad.vlt:11:1: 'VAR_SC_BIGUINT' attribute does not support -path
11 | sc_biguint -path "top.i_mid_0.mid_tmp_b"
| ^~~~~~~~~~
%Error: t/t_vlt_public_spec_hier_attr_bad.vlt:12:1: 'VAR_SFORMAT' attribute does not support -path
12 | sformat -path "top.i_mid_0.mid_tmp_b"
| ^~~~~~~
%Error: t/t_vlt_public_spec_hier_attr_bad.vlt:13:1: 'VAR_SPLIT_VAR' attribute does not support -path
13 | split_var -path "top.i_mid_0.mid_tmp_b"
| ^~~~~~~~~
%Error: t/t_vlt_public_spec_hier_attr_bad.vlt:14:1: 'VAR_FORCEABLE' attribute does not support -path
14 | forceable -path "top.i_mid_0.mid_tmp_b"
| ^~~~~~~~~
%Error: Exiting due to

View File

@ -0,0 +1,19 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# 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: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
test.top_filename = "t/t_vlt_public_spec.v"
test.lint(fails=True,
expect_filename=test.golden_filename,
verilator_flags2=["--vpi", test.name + ".vlt"])
test.passes()

View File

@ -0,0 +1,14 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
`verilator_config
// Hierarchical -path is only supported with public-family attributes.
sc_bv -path "top.i_mid_0.mid_tmp_b"
sc_biguint -path "top.i_mid_0.mid_tmp_b"
sformat -path "top.i_mid_0.mid_tmp_b"
split_var -path "top.i_mid_0.mid_tmp_b"
forceable -path "top.i_mid_0.mid_tmp_b"

View File

@ -0,0 +1,50 @@
%Error: t/t_vlt_public_spec_hier_bad.vlt:12:1: -path 'no_such_top.no_such_var': cannot find top scope 'no_such_top'
12 | public_flat -path "no_such_top.no_such_var"
| ^~~~~~~~~~~
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: t/t_vlt_public_spec_hier_bad.vlt:15:1: -path 'top.i_mid_bogus.mid_tmp_b': cannot find scope 'i_mid_bogus' in scope 'top'
15 | public_flat -path "top.i_mid_bogus.mid_tmp_b"
| ^~~~~~~~~~~
%Error: t/t_vlt_public_spec_hier_bad.vlt:18:1: -path 'top.i_mid_0.no_such_var': cannot find variable 'no_such_var' in scope 'mid'
18 | public_flat -path "top.i_mid_0.no_such_var"
| ^~~~~~~~~~~
%Error: t/t_vlt_public_spec_hier_bad.vlt:21:1: -path 'top.\ .mid_tmp_b': empty escaped identifier
21 | public_flat -path "top.\\ .mid_tmp_b"
| ^~~~~~~~~~~
%Error: t/t_vlt_public_spec_hier_bad.vlt:24:1: -path 'top..mid_tmp_b': empty identifier in path
24 | public_flat -path "top..mid_tmp_b"
| ^~~~~~~~~~~
%Error: t/t_vlt_public_spec_hier_bad.vlt:27:1: -path 'top.i_mid_0.': trailing '.' with no identifier
27 | public_flat -path "top.i_mid_0."
| ^~~~~~~~~~~
%Error: t/t_vlt_public_spec_hier_bad.vlt:30:1: -path 'top.\i_mid_0 mid_tmp_b': expected '.' separator between identifiers
30 | public_flat -path "top.\\i_mid_0 mid_tmp_b"
| ^~~~~~~~~~~
%Error: t/t_vlt_public_spec_hier_bad.vlt:33:1: -path 'top.\foo] [2.mid_tmp_b': unterminated '[' in escaped-name array index
33 | public_flat -path "top.\\foo] [2.mid_tmp_b"
| ^~~~~~~~~~~
%Error: t/t_vlt_public_spec_hier_bad.vlt:36:1: -path 'top': must name at least a scope and a variable
36 | public_flat -path "top"
| ^~~~~~~~~~~
%Error: t/t_vlt_public_spec_hier_bad.vlt:39:1: -path 'top.i_mid_0[abc].mid_tmp_b': malformed array index in segment 'i_mid_0[abc]'
39 | public_flat -path "top.i_mid_0[abc].mid_tmp_b"
| ^~~~~~~~~~~
%Error: t/t_vlt_public_spec_hier_bad.vlt:42:1: -path 'top.i_mid_0[99999999999].mid_tmp_b': malformed array index in segment 'i_mid_0[99999999999]'
42 | public_flat -path "top.i_mid_0[99999999999].mid_tmp_b"
| ^~~~~~~~~~~
%Error: t/t_vlt_public_spec_hier_bad.vlt:45:1: -path 'top.\foo] [abc].mid_tmp_b': malformed array index '[abc]'
45 | public_flat -path "top.\\foo] [abc].mid_tmp_b"
| ^~~~~~~~~~~
%Error: t/t_vlt_public_spec_hier_bad.vlt:48:1: -path 'top.i_mid_0].mid_tmp_b': malformed array index in segment 'i_mid_0]'
48 | public_flat -path "top.i_mid_0].mid_tmp_b"
| ^~~~~~~~~~~
%Error: t/t_vlt_public_spec_hier_bad.vlt:51:1: -path 'top.[0].mid_tmp_b': malformed array index in segment '[0]'
51 | public_flat -path "top.[0].mid_tmp_b"
| ^~~~~~~~~~~
%Error: t/t_vlt_public_spec_hier_bad.vlt:54:1: -path 'top.i_mid_0[1][abc].mid_tmp_b': malformed array index in segment 'i_mid_0[1][abc]'
54 | public_flat -path "top.i_mid_0[1][abc].mid_tmp_b"
| ^~~~~~~~~~~
%Error: t/t_vlt_public_spec_hier_bad.vlt:57:1: -path 'top.i_mid_0[1]x[0].mid_tmp_b': malformed array index in segment 'i_mid_0[1]x[0]'
57 | public_flat -path "top.i_mid_0[1]x[0].mid_tmp_b"
| ^~~~~~~~~~~
%Error: Exiting due to

View File

@ -0,0 +1,19 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# 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: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
test.top_filename = "t/t_vlt_public_spec.v"
test.lint(fails=True,
expect_filename=test.golden_filename,
verilator_flags2=["--vpi", test.name + ".vlt"])
test.passes()

View File

@ -0,0 +1,57 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
`verilator_config
// Each of the following lines exercises an error case for hierarchical -path.
// Unknown top module name
public_flat -path "no_such_top.no_such_var"
// Unknown cell name in the middle of the hierarchical path
public_flat -path "top.i_mid_bogus.mid_tmp_b"
// Unknown variable name at the leaf of the path
public_flat -path "top.i_mid_0.no_such_var"
// Empty escaped identifier: '\' immediately followed by whitespace
public_flat -path "top.\\ .mid_tmp_b"
// Empty identifier in path: two consecutive dots
public_flat -path "top..mid_tmp_b"
// Trailing '.' with no identifier
public_flat -path "top.i_mid_0."
// Missing '.' separator between escaped identifier and next segment
public_flat -path "top.\\i_mid_0 mid_tmp_b"
// Unterminated '[' in array index after escaped identifier
public_flat -path "top.\\foo] [2.mid_tmp_b"
// Single segment: must name at least a scope and a variable
public_flat -path "top"
// Non-numeric array index on a plain (non-escaped) segment
public_flat -path "top.i_mid_0[abc].mid_tmp_b"
// Array index that overflows int
public_flat -path "top.i_mid_0[99999999999].mid_tmp_b"
// Non-numeric folded array index after an escaped identifier
public_flat -path "top.\\foo] [abc].mid_tmp_b"
// Trailing ']' with no matching '[' in a non-escaped segment
public_flat -path "top.i_mid_0].mid_tmp_b"
// Stray '[' at position 0 of a non-escaped segment
public_flat -path "top.[0].mid_tmp_b"
// Multidimensional index with a non-numeric dimension
public_flat -path "top.i_mid_0[1][abc].mid_tmp_b"
// Text between two index groups in a multidim segment
public_flat -path "top.i_mid_0[1]x[0].mid_tmp_b"

View File

@ -0,0 +1,8 @@
scopesDump:
SCOPE 0x#: top
SCOPE 0x#: top.i_sub[0]
VAR 0x#: leaf
SCOPE 0x#: top.i_sub[1]
VAR 0x#: leaf
*-* All Finished *-*

View File

@ -0,0 +1,20 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# 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: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
test.compile(verilator_flags2=["--binary", "--vpi", test.name + ".vlt"])
test.execute()
test.files_identical(test.run_log_filename, test.golden_filename, is_logfile=True, strip_hex=True)
test.passes()

View File

@ -0,0 +1,23 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
// Demonstrate that per-instance selectivity is NOT yet implemented for
// arrayed cells.
module sub();
int leaf = 0;
endmodule
module top();
sub i_sub [1:0] ();
initial begin
$c("Verilated::scopesDump();");
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,12 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
`verilator_config
// Name a single iteration of a cell array. Per-instance selectivity is not
// yet supported for arrayed cells (see .v for the architectural reason),
// so this currently marks ALL iterations.
public_flat -path "top.i_sub[1].leaf"

View File

@ -0,0 +1,8 @@
scopesDump:
SCOPE 0x#: top
SCOPE 0x#: top.\foo[abc]
VAR 0x#: \var.with.dot
SCOPE 0x#: top.\inst.with.dot
VAR 0x#: \var.with.dot
*-* All Finished *-*

View File

@ -0,0 +1,20 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# 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: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
test.compile(verilator_flags2=["--binary", "--vpi", test.name + ".vlt"])
test.execute()
test.files_identical(test.run_log_filename, test.golden_filename, is_logfile=True, strip_hex=True)
test.passes()

View File

@ -0,0 +1,21 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
module sub_mod();
int \var.with.dot = 0;
endmodule
module top();
sub_mod \inst.with.dot ();
sub_mod \foo[abc] ();
initial begin
$c("Verilated::scopesDump();");
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,11 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
`verilator_config
public_flat -path "top.\\inst.with.dot .\\var.with.dot "
public_flat -path "top.\\foo[abc] .\\var.with.dot "

View File

@ -0,0 +1,12 @@
scopesDump:
SCOPE 0x#: top
SCOPE 0x#: top.\foo] [0]
VAR 0x#: leaf
SCOPE 0x#: top.\foo] [1]
VAR 0x#: leaf
SCOPE 0x#: top.\foo] [2]
VAR 0x#: leaf
SCOPE 0x#: top.\foo] [3]
VAR 0x#: leaf
*-* All Finished *-*

View File

@ -0,0 +1,20 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# 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: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
test.compile(verilator_flags2=["--binary", "--vpi", test.name + ".vlt"])
test.execute()
test.files_identical(test.run_log_filename, test.golden_filename, is_logfile=True, strip_hex=True)
test.passes()

View File

@ -0,0 +1,20 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
module sub();
int leaf = 0;
endmodule
module top();
sub \foo] [3:0] ();
initial begin
$c("Verilated::scopesDump();");
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,9 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
`verilator_config
public_flat -path "top.\\foo] [2].leaf"

View File

@ -0,0 +1,13 @@
scopesDump:
SCOPE 0x#: top
SCOPE 0x#: top.gen_if
VAR 0x#: if_local
SCOPE 0x#: top.gen_if.i_sub
VAR 0x#: sub_tmp
SCOPE 0x#: top.gen_loop[0]
SCOPE 0x#: top.gen_loop[0].i_sub
VAR 0x#: sub_tmp
SCOPE 0x#: top.gen_loop[1]
VAR 0x#: loop_local
*-* All Finished *-*

View File

@ -0,0 +1,21 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# 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: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
test.top_filename = "t/t_vlt_public_spec_hier_genblock.v"
test.compile(verilator_flags2=["--binary", "--vpi", test.name + ".vlt"])
test.execute()
test.files_identical(test.run_log_filename, test.golden_filename, is_logfile=True, strip_hex=True)
test.passes()

View File

@ -0,0 +1,47 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
module top (
input int top_port_i,
output int top_port_o
);
int top_tmp;
generate
for (genvar i = 0; i < 2; ++i) begin : gen_loop
int loop_local;
sub i_sub(top_port_i, loop_local);
end
endgenerate
generate
if (1) begin : gen_if
int if_local;
sub i_sub(top_port_i, if_local);
end
endgenerate
assign top_port_o = top_tmp;
initial begin
top_tmp = 0;
$c("Verilated::scopesDump();");
$write("*-* All Finished *-*\n");
$finish;
end
endmodule
module sub (
input int sub_port_i,
output int sub_port_o
);
int sub_tmp;
assign sub_tmp = sub_port_i + 1;
assign sub_port_o = sub_tmp;
endmodule

View File

@ -0,0 +1,19 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
`verilator_config
// Var inside a cell inside a generate-for iteration:
public_flat -path "top.gen_loop[0].i_sub.sub_tmp"
// Var directly inside a generate-for iteration:
public_flat -path "top.gen_loop[1].loop_local"
// Var inside a cell inside a generate-if block:
public_flat -path "top.gen_if.i_sub.sub_tmp"
// Var directly inside a generate-if block:
public_flat -path "top.gen_if.if_local"

View File

@ -0,0 +1,14 @@
%Error: t/t_vlt_public_spec_hier_genblock_bad.vlt:13:1: -path 'top.no_such_gen.x': cannot find scope 'no_such_gen' in scope 'top'
13 | public_flat -path "top.no_such_gen.x"
| ^~~~~~~~~~~
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: t/t_vlt_public_spec_hier_genblock_bad.vlt:16:1: -path 'top.gen_loop[0].no_such_var': cannot find variable 'no_such_var' in scope 'gen_loop'
16 | public_flat -path "top.gen_loop[0].no_such_var"
| ^~~~~~~~~~~
%Error: t/t_vlt_public_spec_hier_genblock_bad.vlt:19:1: -path 'top.gen_if.no_such_var': cannot find variable 'no_such_var' in scope 'gen_if'
19 | public_flat -path "top.gen_if.no_such_var"
| ^~~~~~~~~~~
%Error: t/t_vlt_public_spec_hier_genblock_bad.vlt:22:1: -path 'top.gen_loop[0].no_such_cell.sub_tmp': cannot find scope 'no_such_cell' in scope 'gen_loop'
22 | public_flat -path "top.gen_loop[0].no_such_cell.sub_tmp"
| ^~~~~~~~~~~
%Error: Exiting due to

View File

@ -0,0 +1,19 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# 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: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
test.top_filename = "t/t_vlt_public_spec_hier_genblock.v"
test.lint(fails=True,
expect_filename=test.golden_filename,
verilator_flags2=["--vpi", test.name + ".vlt"])
test.passes()

View File

@ -0,0 +1,22 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
`verilator_config
// Each line below exercises an error case for hierarchical -path that
// descends through generate blocks.
// Unknown generate block name directly under the top module
public_flat -path "top.no_such_gen.x"
// Unknown variable inside a generate-for iteration
public_flat -path "top.gen_loop[0].no_such_var"
// Unknown variable inside a generate-if block
public_flat -path "top.gen_if.no_such_var"
// Unknown cell name inside a generate-for iteration
public_flat -path "top.gen_loop[0].no_such_cell.sub_tmp"

View File

@ -0,0 +1,20 @@
scopesDump:
SCOPE 0x#: top
SCOPE 0x#: top.i_arr[0]
VAR 0x#: data
SCOPE 0x#: top.i_arr[1]
VAR 0x#: data
SCOPE 0x#: top.i_arr[2]
VAR 0x#: data
SCOPE 0x#: top.i_md[0][0]
VAR 0x#: data
SCOPE 0x#: top.i_md[0][1]
VAR 0x#: data
SCOPE 0x#: top.i_md[1][0]
VAR 0x#: data
SCOPE 0x#: top.i_md[1][1]
VAR 0x#: data
SCOPE 0x#: top.i_single
VAR 0x#: data
*-* All Finished *-*

View File

@ -0,0 +1,20 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# 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: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
test.compile(verilator_flags2=["--binary", "--vpi", test.name + ".vlt"])
test.execute()
test.files_identical(test.run_log_filename, test.golden_filename, is_logfile=True, strip_hex=True)
test.passes()

View File

@ -0,0 +1,22 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
interface ifc();
logic [7:0] data;
endinterface
module top();
ifc i_single ();
ifc i_arr [2:0] ();
ifc i_md [1:0][1:0] ();
initial begin
$c("Verilated::scopesDump();");
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,16 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
`verilator_config
// Single interface instance: per-instance selective.
public_flat -path "top.i_single.data"
// 1D interface array: marks the var in every iteration.
public_flat -path "top.i_arr[1].data"
// Multidimensional interface array: marks the var in every iteration.
public_flat -path "top.i_md[1][0].data"

View File

@ -0,0 +1,12 @@
scopesDump:
SCOPE 0x#: top
SCOPE 0x#: top.i_sub[0][0]
VAR 0x#: leaf
SCOPE 0x#: top.i_sub[0][1]
VAR 0x#: leaf
SCOPE 0x#: top.i_sub[1][0]
VAR 0x#: leaf
SCOPE 0x#: top.i_sub[1][1]
VAR 0x#: leaf
*-* All Finished *-*

View File

@ -0,0 +1,20 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# 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: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
test.compile(verilator_flags2=["--binary", "--vpi", test.name + ".vlt"])
test.execute()
test.files_identical(test.run_log_filename, test.golden_filename, is_logfile=True, strip_hex=True)
test.passes()

View File

@ -0,0 +1,20 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
module sub();
int leaf = 0;
endmodule
module top();
sub i_sub [1:0][1:0] ();
initial begin
$c("Verilated::scopesDump();");
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,9 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
`verilator_config
public_flat -path "top.i_sub[1][0].leaf"

View File

@ -0,0 +1,12 @@
scopesDump:
SCOPE 0x#: top
SCOPE 0x#: top.gen_loop[-1]
VAR 0x#: loop_local
SCOPE 0x#: top.i_sub[-1]
VAR 0x#: sub_leaf
SCOPE 0x#: top.i_sub[-2]
VAR 0x#: sub_leaf
SCOPE 0x#: top.i_sub[0]
VAR 0x#: sub_leaf
*-* All Finished *-*

View File

@ -0,0 +1,20 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# 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: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
test.compile(verilator_flags2=["--binary", "--vpi", test.name + ".vlt"])
test.execute()
test.files_identical(test.run_log_filename, test.golden_filename, is_logfile=True, strip_hex=True)
test.passes()

View File

@ -0,0 +1,26 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
module sub();
int sub_leaf = 0;
endmodule
module top();
generate
for (genvar i = -2; i < 0; ++i) begin : gen_loop
int loop_local;
end
endgenerate
sub i_sub [0:-2] ();
initial begin
$c("Verilated::scopesDump();");
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,11 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
`verilator_config
public_flat -path "top.gen_loop[-1].loop_local"
public_flat -path "top.i_sub[-1].sub_leaf"

View File

@ -0,0 +1,32 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
module sub #(
parameter int SUB_PARAM = 7
) ();
localparam int SUB_LOCALPARAM = 3;
int by_pragma /*verilator public_flat_rd*/;
int by_vlt;
int not_public;
initial begin
by_pragma = SUB_PARAM;
by_vlt = SUB_LOCALPARAM;
not_public = 0;
end
endmodule
module top();
sub i_sub();
initial begin
$c("Verilated::scopesDump();");
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,8 @@
scopesDump:
SCOPE 0x#: top
SCOPE 0x#: top.i_sub
VAR 0x#: SUB_LOCALPARAM
VAR 0x#: SUB_PARAM
VAR 0x#: by_vlt
*-* All Finished *-*

View File

@ -0,0 +1,23 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# 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: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
test.top_filename = "t/t_vlt_public_spec_hier_pub.v"
test.compile(verilator_flags2=[
"--binary", "--vpi", "--public-ignore", "--public-params", test.name + ".vlt"
])
test.execute()
test.files_identical(test.run_log_filename, test.golden_filename, is_logfile=True, strip_hex=True)
test.passes()

View File

@ -0,0 +1,9 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
`verilator_config
public_flat -path "top.i_sub.by_vlt"

View File

@ -0,0 +1,10 @@
scopesDump:
SCOPE 0x#: top
SCOPE 0x#: top.sub_a
VAR 0x#: some_var
SCOPE 0x#: top.sub_b
VAR 0x#: some_var
SCOPE 0x#: top.sub_c
VAR 0x#: some_var
*-* All Finished *-*

View File

@ -0,0 +1,20 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# 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: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
test.compile(verilator_flags2=["--binary", "--vpi", test.name + ".vlt"])
test.execute()
test.files_identical(test.run_log_filename, test.golden_filename, is_logfile=True, strip_hex=True)
test.passes()

View File

@ -0,0 +1,23 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
module sub();
int some_var = 0;
endmodule
module top();
sub sub_a (); // tagged public_flat
sub sub_b (); // tagged public_flat (same marking as sub_a -> shares clone)
sub sub_c (); // tagged public_flat_rd (different marking -> own clone)
sub sub_d (); // not tagged
initial begin
$c("Verilated::scopesDump();");
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,16 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
`verilator_config
// sub_a and sub_b: identical marking -> share one deparam clone.
public_flat -path "top.sub_a.some_var"
public_flat -path "top.sub_b.some_var"
// sub_c: different attribute -> distinct clone, still per-instance public.
public_flat_rd -path "top.sub_c.some_var"
// sub_d is left untagged: its some_var must NOT be public.