diff --git a/docs/guide/control.rst b/docs/guide/control.rst index 8553de997..f6d73e453 100644 --- a/docs/guide/control.rst +++ b/docs/guide/control.rst @@ -266,10 +266,13 @@ The grammar of control commands is as follows: .. option:: public [-module ""] [-task/-function ""] [-var ""] +.. option:: public_flat -path "" .. option:: public_flat [-module ""] [-task/-function ""] [(-param | -port | -var) ""] +.. option:: public_flat_rd -path "" .. option:: public_flat_rd [-module ""] [-task/-function ""] [(-param | -port | -var) ""] +.. option:: public_flat_rw -path "" .. option:: public_flat_rw [-module ""] [-task/-function ""] [(-param | -port | -var) ""] ["@(edge)"] Sets the specified signal to be public. Same as @@ -283,6 +286,23 @@ The grammar of control commands is as follows: following ```` 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 "" -var "" Sets the input/output signal to be of ``sc_biguint<{width}>`` type. diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index 3c25a9992..9dab7eba6 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -2138,6 +2138,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 @@ -2204,6 +2205,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; @@ -2380,6 +2382,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; } diff --git a/src/V3Control.cpp b/src/V3Control.cpp index 3ac7aa727..7c076379d 100644 --- a/src/V3Control.cpp +++ b/src/V3Control.cpp @@ -21,9 +21,12 @@ #include "V3InstrCount.h" #include "V3String.h" +#include +#include #include #include #include +#include VL_DEFINE_DEBUG_FUNCTIONS; @@ -791,11 +794,53 @@ 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 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 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> m_vars; + // Child scopes keyed by pretty segment text. + std::map> m_children; + + V3ControlHierTreeNode* getOrAddChild(const std::string& key) { + std::unique_ptr& slot = m_children[key]; + if (!slot) slot = std::make_unique(); + 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 m_hierVarAttrs; // -path public attrs + V3ControlHierTreeNode m_hierVarTreeRoot; // Prefix tree over m_hierVarAttrs + std::unordered_map m_hierVarCellPaths; std::unordered_map> m_profileData; // Access to profile_data records uint8_t m_mode = NONE; @@ -813,6 +858,11 @@ public: V3ControlModuleResolver& modules() { return m_modules; } V3ControlFileResolver& files() { return m_files; } V3ControlScopeTraceResolver& scopeTraces() { return m_scopeTraces; } + std::vector& hierVarAttrs() { return m_hierVarAttrs; } + V3ControlHierTreeNode& hierVarTreeRoot() { return m_hierVarTreeRoot; } + std::unordered_map& hierVarCellPaths() { + return m_hierVarCellPaths; + } void addProfileData(FileLine* fl, const string& hierDpi, uint64_t cost) { // Empty key for hierarchical DPI wrapper costs. @@ -1015,6 +1065,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 +1148,516 @@ 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) { + int index = 0; + const char* const first = indexText.data(); + const char* const last = first + indexText.size(); + const auto [ptr, ec] = std::from_chars(first, last, index); + if (ec != std::errc{} || ptr != last) return false; + seg.m_indices.push_back(index); + 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 splitHierPath(FileLine* fl, const std::string& path, + bool& errOut) { + static const std::string WHITESPACE = " \t\n\v\f\r"; + errOut = false; + std::vector 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{""}; +} + +static void collectScopeMatches(AstNode* firstp, const std::string& name, + const std::string& stripped, std::vector& 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::vector>; + +static const std::vector& findScopeChildren(AstNode* scopep, + const V3ControlHierSegment& seg, + ScopeChildrenCache& cache) { + const std::string name = segmentPrettyText(seg); + const auto key = std::make_pair(static_cast(scopep), name); + const auto pair = cache.emplace(key, std::vector{}); + std::vector& 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& 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& 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 topModCache; + for (V3ControlHierVarEntry& entry : entries) { + bool splitErr = false; + const std::vector 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 void appendNodeSignature(const V3ControlHierTreeNode* nodep, std::string& out) { + // Leaf vars: "=;" sorted by (name, attr). + std::vector> vars = nodep->m_vars; + std::sort(vars.begin(), vars.end(), [](const auto& a, const auto& b) { + if (a.first != b.first) return a.first < b.first; + return a.second.ascii() < b.second.ascii(); + }); + // NOCOMMIT -- is this too verbose? + out += "v{"; + for (const auto& var : vars) { + out += var.first; + out += "="; + out += var.second.ascii(); + out += ";"; + } + out += "}"; + // Child scopes: "(:)" in sorted key order (std::map). + out += "c{"; + for (const auto& child : nodep->m_children) { + out += child.first; + out += ":"; + out += std::to_string(static_cast(child.second->m_kind)); + out += "("; + appendNodeSignature(child.second.get(), out); + out += ")"; + } + out += "}"; +} + +// -path'ed public marking as a string for V3Param uniquification and cloning +std::string V3Control::cellPathPublicSignature(const std::string& cellInstancePath) { + const auto& cellPaths = V3ControlResolver::s().hierVarCellPaths(); + const auto it = cellPaths.find(cellInstancePath); + if (it == cellPaths.end()) return ""; + std::string sig; + appendNodeSignature(it->second, sig); + return sig; +} + +//---------------------------------------------------------------------- +// 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 m_topMods; + std::map, 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(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); } diff --git a/src/V3Control.h b/src/V3Control.h index c555ea188..4474c9e2c 100644 --- a/src/V3Control.h +++ b/src/V3Control.h @@ -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 std::string cellPathPublicSignature(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); diff --git a/src/V3Dead.cpp b/src/V3Dead.cpp index 411ec57eb..ecff7f1c7 100644 --- a/src/V3Dead.cpp +++ b/src/V3Dead.cpp @@ -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 diff --git a/src/V3LinkParse.cpp b/src/V3LinkParse.cpp index 5403d77c2..40d6e590b 100644 --- a/src/V3LinkParse.cpp +++ b/src/V3LinkParse.cpp @@ -1427,6 +1427,7 @@ public: void V3LinkParse::linkParse(AstNetlist* rootp) { UINFO(4, __FUNCTION__ << ": "); + V3Control::applyHierVarAttrs(rootp); { LinkParseVisitor{rootp}; } // Destruct before checking V3Global::dumpCheckGlobalTree("linkparse", 0, dumpTreeEitherLevel() >= 6); } diff --git a/src/V3Param.cpp b/src/V3Param.cpp index 0cf109211..62576ba74 100644 --- a/src/V3Param.cpp +++ b/src/V3Param.cpp @@ -50,6 +50,7 @@ #include "V3Param.h" #include "V3Case.h" +#include "V3Control.h" #include "V3Const.h" #include "V3EmitV.h" #include "V3Hasher.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 string pubSig = V3Control::cellPathPublicSignature(srcModp->someInstanceName()); + if (!pubSig.empty()) { + any_overrides = true; + longname += "__Vhier" + V3Hash{pubSig}.toString(); + } + } if (debug() >= 9 && paramsp) paramsp->dumpTreeAndNext(cout, "- cellparams: "); if (srcModp->hierBlock()) { diff --git a/src/V3SplitVar.cpp b/src/V3SplitVar.cpp index 917123be6..dc600617f 100644 --- a/src/V3SplitVar.cpp +++ b/src/V3SplitVar.cpp @@ -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; diff --git a/src/Verilator.cpp b/src/Verilator.cpp index b1f0a9af0..c25c23713 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -301,6 +301,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()); diff --git a/src/verilog.l b/src/verilog.l index 36058b0a6..6c77d13fa 100644 --- a/src/verilog.l +++ b/src/verilog.l @@ -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; } diff --git a/src/verilog.y b/src/verilog.y index db145ea8e..c16331bdb 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -282,6 +282,7 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"}) %token yVLT_D_MODULE "--module" %token yVLT_D_MTASK "--mtask" %token yVLT_D_PARAM "--param" +%token yVLT_D_PATH "--path" %token yVLT_D_PORT "--port" %token yVLT_D_RULE "--rule" %token yVLT_D_Q "--q" @@ -8444,6 +8445,8 @@ vltItem: }} | vltVarAttrFront vltDModuleE vltDFTaskE vltVarAttrSpecE attr_event_controlE { V3Control::addVarAttr($1, *$2, *$3, GRAMMARP->m_vltVarSpecKind, *$4, $1, $5); } + | vltVarAttrFront vltDPath + { V3Control::addHierVarAttr($1, *$2, $1); } | vltVarAttrFrontDeprecated vltDModuleE vltDFTaskE vltVarAttrSpecE { /* Historical, now has no effect */ } | vltInlineFront vltDModuleE vltDFTaskE @@ -8569,6 +8572,10 @@ vltDModuleE: // [--module ] { $$ = $1; } ; +vltDPath: // --path + yVLT_D_PATH str { $$ = $2; } + ; + vltDScope: // --scope yVLT_D_SCOPE str { $$ = $2; } ; diff --git a/test_regress/t/t_vlt_public_spec_hier.out b/test_regress/t/t_vlt_public_spec_hier.out new file mode 100644 index 000000000..f88d2fd36 --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier.out @@ -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 *-* diff --git a/test_regress/t/t_vlt_public_spec_hier.py b/test_regress/t/t_vlt_public_spec_hier.py new file mode 100755 index 000000000..7627834cd --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier.py @@ -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() diff --git a/test_regress/t/t_vlt_public_spec_hier.vlt b/test_regress/t/t_vlt_public_spec_hier.vlt new file mode 100644 index 000000000..c383449d9 --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier.vlt @@ -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" diff --git a/test_regress/t/t_vlt_public_spec_hier_attr_bad.out b/test_regress/t/t_vlt_public_spec_hier_attr_bad.out new file mode 100644 index 000000000..a9e5a2f1c --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_attr_bad.out @@ -0,0 +1,20 @@ +%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_ISOLATE_ASSIGNMENTS' attribute does not support -path + 14 | isolate_assignments -path "top.i_mid_0.mid_tmp_b" + | ^~~~~~~~~~~~~~~~~~~ +%Error: t/t_vlt_public_spec_hier_attr_bad.vlt:15:1: 'VAR_FORCEABLE' attribute does not support -path + 15 | forceable -path "top.i_mid_0.mid_tmp_b" + | ^~~~~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_vlt_public_spec_hier_attr_bad.py b/test_regress/t/t_vlt_public_spec_hier_attr_bad.py new file mode 100755 index 000000000..5bbab92af --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_attr_bad.py @@ -0,0 +1,18 @@ +#!/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() diff --git a/test_regress/t/t_vlt_public_spec_hier_attr_bad.vlt b/test_regress/t/t_vlt_public_spec_hier_attr_bad.vlt new file mode 100644 index 000000000..311ddbc25 --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_attr_bad.vlt @@ -0,0 +1,15 @@ +// 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" +isolate_assignments -path "top.i_mid_0.mid_tmp_b" +forceable -path "top.i_mid_0.mid_tmp_b" diff --git a/test_regress/t/t_vlt_public_spec_hier_bad.out b/test_regress/t/t_vlt_public_spec_hier_bad.out new file mode 100644 index 000000000..e1dc6d967 --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_bad.out @@ -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 diff --git a/test_regress/t/t_vlt_public_spec_hier_bad.py b/test_regress/t/t_vlt_public_spec_hier_bad.py new file mode 100755 index 000000000..5bbab92af --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_bad.py @@ -0,0 +1,18 @@ +#!/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() diff --git a/test_regress/t/t_vlt_public_spec_hier_bad.vlt b/test_regress/t/t_vlt_public_spec_hier_bad.vlt new file mode 100644 index 000000000..bed3f027c --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_bad.vlt @@ -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" diff --git a/test_regress/t/t_vlt_public_spec_hier_cellarr_share.out b/test_regress/t/t_vlt_public_spec_hier_cellarr_share.out new file mode 100644 index 000000000..991332a1f --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_cellarr_share.out @@ -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 *-* diff --git a/test_regress/t/t_vlt_public_spec_hier_cellarr_share.py b/test_regress/t/t_vlt_public_spec_hier_cellarr_share.py new file mode 100644 index 000000000..56480f2ae --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_cellarr_share.py @@ -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() diff --git a/test_regress/t/t_vlt_public_spec_hier_cellarr_share.v b/test_regress/t/t_vlt_public_spec_hier_cellarr_share.v new file mode 100644 index 000000000..3b5f1cf8d --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_cellarr_share.v @@ -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 diff --git a/test_regress/t/t_vlt_public_spec_hier_cellarr_share.vlt b/test_regress/t/t_vlt_public_spec_hier_cellarr_share.vlt new file mode 100644 index 000000000..ec731ff6d --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_cellarr_share.vlt @@ -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" diff --git a/test_regress/t/t_vlt_public_spec_hier_escid.out b/test_regress/t/t_vlt_public_spec_hier_escid.out new file mode 100644 index 000000000..040f54aab --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_escid.out @@ -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 *-* diff --git a/test_regress/t/t_vlt_public_spec_hier_escid.py b/test_regress/t/t_vlt_public_spec_hier_escid.py new file mode 100755 index 000000000..56480f2ae --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_escid.py @@ -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() diff --git a/test_regress/t/t_vlt_public_spec_hier_escid.v b/test_regress/t/t_vlt_public_spec_hier_escid.v new file mode 100644 index 000000000..57f3e168e --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_escid.v @@ -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 diff --git a/test_regress/t/t_vlt_public_spec_hier_escid.vlt b/test_regress/t/t_vlt_public_spec_hier_escid.vlt new file mode 100644 index 000000000..9d4ac89ba --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_escid.vlt @@ -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 " diff --git a/test_regress/t/t_vlt_public_spec_hier_escid_array.out b/test_regress/t/t_vlt_public_spec_hier_escid_array.out new file mode 100644 index 000000000..aaac93b2a --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_escid_array.out @@ -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 *-* diff --git a/test_regress/t/t_vlt_public_spec_hier_escid_array.py b/test_regress/t/t_vlt_public_spec_hier_escid_array.py new file mode 100644 index 000000000..56480f2ae --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_escid_array.py @@ -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() diff --git a/test_regress/t/t_vlt_public_spec_hier_escid_array.v b/test_regress/t/t_vlt_public_spec_hier_escid_array.v new file mode 100644 index 000000000..9b36001b1 --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_escid_array.v @@ -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 diff --git a/test_regress/t/t_vlt_public_spec_hier_escid_array.vlt b/test_regress/t/t_vlt_public_spec_hier_escid_array.vlt new file mode 100644 index 000000000..051f52978 --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_escid_array.vlt @@ -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" diff --git a/test_regress/t/t_vlt_public_spec_hier_genblock.out b/test_regress/t/t_vlt_public_spec_hier_genblock.out new file mode 100644 index 000000000..bb4a06109 --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_genblock.out @@ -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 *-* diff --git a/test_regress/t/t_vlt_public_spec_hier_genblock.py b/test_regress/t/t_vlt_public_spec_hier_genblock.py new file mode 100755 index 000000000..3019eb79c --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_genblock.py @@ -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() diff --git a/test_regress/t/t_vlt_public_spec_hier_genblock.v b/test_regress/t/t_vlt_public_spec_hier_genblock.v new file mode 100644 index 000000000..75db0894c --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_genblock.v @@ -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 diff --git a/test_regress/t/t_vlt_public_spec_hier_genblock.vlt b/test_regress/t/t_vlt_public_spec_hier_genblock.vlt new file mode 100644 index 000000000..66a0a9288 --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_genblock.vlt @@ -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" diff --git a/test_regress/t/t_vlt_public_spec_hier_genblock_bad.out b/test_regress/t/t_vlt_public_spec_hier_genblock_bad.out new file mode 100644 index 000000000..4c6ffe665 --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_genblock_bad.out @@ -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 diff --git a/test_regress/t/t_vlt_public_spec_hier_genblock_bad.py b/test_regress/t/t_vlt_public_spec_hier_genblock_bad.py new file mode 100755 index 000000000..b5d8ba2d4 --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_genblock_bad.py @@ -0,0 +1,18 @@ +#!/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() diff --git a/test_regress/t/t_vlt_public_spec_hier_genblock_bad.vlt b/test_regress/t/t_vlt_public_spec_hier_genblock_bad.vlt new file mode 100644 index 000000000..035605856 --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_genblock_bad.vlt @@ -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" diff --git a/test_regress/t/t_vlt_public_spec_hier_iface.out b/test_regress/t/t_vlt_public_spec_hier_iface.out new file mode 100644 index 000000000..472c5c61b --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_iface.out @@ -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 *-* diff --git a/test_regress/t/t_vlt_public_spec_hier_iface.py b/test_regress/t/t_vlt_public_spec_hier_iface.py new file mode 100644 index 000000000..56480f2ae --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_iface.py @@ -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() diff --git a/test_regress/t/t_vlt_public_spec_hier_iface.v b/test_regress/t/t_vlt_public_spec_hier_iface.v new file mode 100644 index 000000000..311e23a9a --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_iface.v @@ -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 diff --git a/test_regress/t/t_vlt_public_spec_hier_iface.vlt b/test_regress/t/t_vlt_public_spec_hier_iface.vlt new file mode 100644 index 000000000..5447ab9e3 --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_iface.vlt @@ -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" diff --git a/test_regress/t/t_vlt_public_spec_hier_multidim.out b/test_regress/t/t_vlt_public_spec_hier_multidim.out new file mode 100644 index 000000000..b1cc3b88e --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_multidim.out @@ -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 *-* diff --git a/test_regress/t/t_vlt_public_spec_hier_multidim.py b/test_regress/t/t_vlt_public_spec_hier_multidim.py new file mode 100644 index 000000000..56480f2ae --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_multidim.py @@ -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() diff --git a/test_regress/t/t_vlt_public_spec_hier_multidim.v b/test_regress/t/t_vlt_public_spec_hier_multidim.v new file mode 100644 index 000000000..d500d97ad --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_multidim.v @@ -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 diff --git a/test_regress/t/t_vlt_public_spec_hier_multidim.vlt b/test_regress/t/t_vlt_public_spec_hier_multidim.vlt new file mode 100644 index 000000000..51f1d0432 --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_multidim.vlt @@ -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" diff --git a/test_regress/t/t_vlt_public_spec_hier_negidx.out b/test_regress/t/t_vlt_public_spec_hier_negidx.out new file mode 100644 index 000000000..89f1b5fbe --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_negidx.out @@ -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 *-* diff --git a/test_regress/t/t_vlt_public_spec_hier_negidx.py b/test_regress/t/t_vlt_public_spec_hier_negidx.py new file mode 100644 index 000000000..56480f2ae --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_negidx.py @@ -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() diff --git a/test_regress/t/t_vlt_public_spec_hier_negidx.v b/test_regress/t/t_vlt_public_spec_hier_negidx.v new file mode 100644 index 000000000..43cd9306c --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_negidx.v @@ -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 diff --git a/test_regress/t/t_vlt_public_spec_hier_negidx.vlt b/test_regress/t/t_vlt_public_spec_hier_negidx.vlt new file mode 100644 index 000000000..d86009d65 --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_negidx.vlt @@ -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" diff --git a/test_regress/t/t_vlt_public_spec_hier_pub.v b/test_regress/t/t_vlt_public_spec_hier_pub.v new file mode 100644 index 000000000..330c2ac30 --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_pub.v @@ -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 diff --git a/test_regress/t/t_vlt_public_spec_hier_pubflags.out b/test_regress/t/t_vlt_public_spec_hier_pubflags.out new file mode 100644 index 000000000..ef55e8e29 --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_pubflags.out @@ -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 *-* diff --git a/test_regress/t/t_vlt_public_spec_hier_pubflags.py b/test_regress/t/t_vlt_public_spec_hier_pubflags.py new file mode 100755 index 000000000..966a970ba --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_pubflags.py @@ -0,0 +1,24 @@ +#!/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() diff --git a/test_regress/t/t_vlt_public_spec_hier_pubflags.vlt b/test_regress/t/t_vlt_public_spec_hier_pubflags.vlt new file mode 100644 index 000000000..66398d8cc --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_pubflags.vlt @@ -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" diff --git a/test_regress/t/t_vlt_public_spec_hier_share.out b/test_regress/t/t_vlt_public_spec_hier_share.out new file mode 100644 index 000000000..cbbb3f88b --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_share.out @@ -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 *-* diff --git a/test_regress/t/t_vlt_public_spec_hier_share.py b/test_regress/t/t_vlt_public_spec_hier_share.py new file mode 100644 index 000000000..56480f2ae --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_share.py @@ -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() diff --git a/test_regress/t/t_vlt_public_spec_hier_share.v b/test_regress/t/t_vlt_public_spec_hier_share.v new file mode 100644 index 000000000..8d333ad8c --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_share.v @@ -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 diff --git a/test_regress/t/t_vlt_public_spec_hier_share.vlt b/test_regress/t/t_vlt_public_spec_hier_share.vlt new file mode 100644 index 000000000..4d7782dad --- /dev/null +++ b/test_regress/t/t_vlt_public_spec_hier_share.vlt @@ -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.