Fix interface exposure with `--public-depth` or `--trace-depth` (#5758).

This commit is contained in:
Wilson Snyder 2025-09-23 22:05:51 -04:00
parent d972b7465a
commit fd12ab3413
17 changed files with 225 additions and 14 deletions

View File

@ -35,6 +35,7 @@ Verilator 5.041 devel
* Optimize constant folding in wide expression expansion (#6381). [Geza Lore]
* Fix missing BLKSEQ when connecting module port to array (#2973).
* Fix false CONSTVAR error on initializers (#4992).
* Fix interface exposure with `--public-depth` or `--trace-depth` (#5758).
* Fix cell scoping performance (#6059). [Jerry Tianchen]
* Fix hierarchical `--prof-pgo` (#6213). [Bartłomiej Chmiel, Antmicro Ltd.]
* Fix while loop hang on timing-delayed assignment (#6343) (#6354). [Krzysztof Bieganski, Antmicro Ltd.]

View File

@ -293,7 +293,8 @@ class AstNodeModule VL_NOT_FINAL : public AstNode {
string m_someInstanceName; // Hierarchical name of some arbitrary instance of this module.
// Used for user messages only.
string m_libname; // Work library
int m_level = 0; // 1=top module, 2=cell off top module, ...
int m_depth = 0; // 1=top module, 2=cell off top, shared things low, for -depth options
int m_level = 0; // 1=top module, 2=cell off top, shared things have high number
VLifetime m_lifetime; // Lifetime
VTimescale m_timeunit; // Global time unit
VOptionBool m_unconnectedDrive; // State of `unconnected_drive
@ -341,7 +342,9 @@ public:
void someInstanceName(const string& name) { m_someInstanceName = name; }
bool inLibrary() const { return m_inLibrary; }
void inLibrary(bool flag) { m_inLibrary = flag; }
void level(int level) { m_level = level; }
void depth(int value) { m_depth = value; }
int depth() const VL_MT_SAFE { return m_depth; }
void level(int value) { m_level = value; }
int level() const VL_MT_SAFE { return m_level; }
string libname() const { return m_libname; }
string prettyLibnameQ() const { return "'" + prettyName(libname()) + "'"; }

View File

@ -2419,6 +2419,7 @@ void AstNetlist::createTopScope(AstScope* scopep) {
void AstNodeModule::dump(std::ostream& str) const {
this->AstNode::dump(str);
str << " L" << level();
str << " D" << depth();
if (modPublic()) str << " [P]";
if (inLibrary()) str << " [LIB]";
if (dead()) str << " [DEAD]";

View File

@ -201,6 +201,7 @@ class V3GraphVertex VL_NOT_FINAL {
friend class V3GraphEdge;
friend class GraphAcyc;
friend class GraphAlgRank;
friend class GraphAlgRankDepth;
V3ListLinks<V3GraphVertex> m_links; // List links to store instances of this class
V3GraphEdge::OList m_outs; // List of outbound edges
V3GraphEdge::IList m_ins; // List of inbound edges
@ -382,11 +383,19 @@ public:
void stronglyConnected(V3EdgeFuncP edgeFuncp) VL_MT_DISABLED;
/// Assign an ordering number to all vertexes in a tree.
/// For multiple usages of a vertex, get the maximum of all inbound ranks + 1
/// All nodes with no inputs will get rank 1
/// Side-effect: changes user()
void rank(V3EdgeFuncP edgeFuncp) VL_MT_DISABLED;
void rank() VL_MT_DISABLED;
/// Assign an ordering number to all vertexes in a tree.
/// For multiple usages of a vertex, get the minimu of all inbound ranks + 1
/// All nodes with no inputs will get rank 1
/// Side-effect: changes user()
void rankMin(V3EdgeFuncP edgeFuncp) VL_MT_DISABLED;
void rankMin() VL_MT_DISABLED;
/// Sort all vertices and edges using the V3GraphVertex::sortCmp() function
void sortVertices() VL_MT_DISABLED;
/// Sort all edges and edges using the V3GraphEdge::sortCmp() function

View File

@ -271,12 +271,9 @@ class GraphAlgRank final : GraphAlg<> {
vertex.user(0);
}
for (V3GraphVertex& vertex : m_graphp->vertices()) {
if (!vertex.user()) { //
vertexIterate(&vertex, 1);
}
if (!vertex.user()) vertexIterate(&vertex, 1);
}
}
void vertexIterate(V3GraphVertex* vertexp, uint32_t currentRank) {
// Assign rank to each unvisited node
// If larger rank is found, assign it and loop back through
@ -302,10 +299,63 @@ public:
~GraphAlgRank() = default;
};
void V3Graph::rank() { GraphAlgRank{this, &V3GraphEdge::followAlwaysTrue}; }
void V3Graph::rank() { rank(&V3GraphEdge::followAlwaysTrue); }
void V3Graph::rank(V3EdgeFuncP edgeFuncp) { GraphAlgRank{this, edgeFuncp}; }
//######################################################################
//######################################################################
// Algorithms - ranking min
// Changes user() and rank()
class GraphAlgRankMin final : GraphAlg<> {
void main() {
// Rank each vertex, ignoring cutable edges
// Vertex::m_user begin: 1 indicates processing, 2 indicates completed
// Clear existing ranks
for (V3GraphVertex& vertex : m_graphp->vertices()) {
vertex.rank(0);
vertex.user(0);
}
for (V3GraphVertex& vertex : m_graphp->vertices()) {
if (!vertex.user()) vertexIterate(&vertex);
}
}
uint32_t vertexIterate(V3GraphVertex* vertexp) {
// Assign rank to each unvisited node
// If we hit a back node make a list of all loops
if (vertexp->user() == 1) {
m_graphp->loopsMessageCb(vertexp, m_edgeFuncp);
return vertexp->rank();
}
if (vertexp->user()) return vertexp->rank(); // Done earlier
vertexp->user(1);
vertexp->rank(1); // In case loop
// If no input edges, then rank 1 (+ adder)
// Otherwise, get minimum from following all inputs.
uint32_t minrank = ~0U;
for (V3GraphEdge& edge : vertexp->inEdges()) {
if (followEdge(&edge)) {
const uint32_t nrank = vertexIterate(edge.fromp());
minrank = std::min(minrank, nrank);
}
}
if (minrank == ~0U) minrank = 0;
vertexp->rank(minrank + vertexp->rankAdder());
vertexp->user(2);
return vertexp->rank();
}
public:
GraphAlgRankMin(V3Graph* graphp, V3EdgeFuncP edgeFuncp)
: GraphAlg<>{graphp, edgeFuncp} {
main();
}
~GraphAlgRankMin() = default;
};
void V3Graph::rankMin() { rankMin(&V3GraphEdge::followAlwaysTrue); }
void V3Graph::rankMin(V3EdgeFuncP edgeFuncp) { GraphAlgRankMin{this, edgeFuncp}; }
//######################################################################
//######################################################################
// Algorithms - report loops

View File

@ -205,6 +205,14 @@ class LinkCellsVisitor final : public VNVisitor {
modp->level(vvertexp->rank() + 1);
}
}
m_graph.rankMin();
for (V3GraphVertex& vtx : m_graph.vertices()) {
if (const LinkCellsVertex* const vvertexp = vtx.cast<LinkCellsVertex>()) {
// +1 so we leave level 1 for the new wrapper we'll make in a moment
AstNodeModule* const modp = vvertexp->modp();
modp->depth(vvertexp->rank() + 1);
}
}
if (v3Global.opt.topModule() != "" && !m_topVertexp) {
v3error("Specified --top-module '" << v3Global.opt.topModule()
<< "' was not found in design.");

View File

@ -173,6 +173,7 @@ void V3LinkLevel::wrapTop(AstNetlist* rootp) {
// Make the new module first in the list
oldmodp->unlinkFrBackWithNext();
newmodp->addNext(oldmodp);
newmodp->depth(1);
newmodp->level(1);
newmodp->modPublic(true);
newmodp->protect(false);

View File

@ -354,7 +354,7 @@ class LinkParseVisitor final : public VNVisitor {
} else if (v3Global.opt.publicParams() && nodep->isParam()) {
nodep->sigUserRWPublic(true);
} else if (m_modp && v3Global.opt.publicDepth()) {
if ((m_modp->level() - 1) <= v3Global.opt.publicDepth()) {
if ((m_modp->depth() - 1) <= v3Global.opt.publicDepth()) {
nodep->sigUserRWPublic(true);
} else if (VN_IS(m_modp, Package) && nodep->isParam()) {
nodep->sigUserRWPublic(true);
@ -365,7 +365,7 @@ class LinkParseVisitor final : public VNVisitor {
// We used modTrace before leveling, and we may now
// want to turn it off now that we know the levelizations
if (v3Global.opt.traceDepth() && m_modp
&& (m_modp->level() - 1) > v3Global.opt.traceDepth()) {
&& (m_modp->depth() - 1) > v3Global.opt.traceDepth()) {
m_modp->modTrace(false);
nodep->trace(false);
}

View File

@ -1761,6 +1761,9 @@ class ParamTop final : VNDeleter {
maxParentLevel = std::max(maxParentLevel, parentp->level());
}
if (modp->level() <= maxParentLevel) modp->level(maxParentLevel + 1);
// Not correct fixup of depth(), as it should be mininum. But depth() is unused
// past V3LinkParse, so just do something sane in case this code doesn't get updated
modp->depth(modp->level());
}
void resortNetlistModules(AstNetlist* netlistp) {

View File

@ -2285,7 +2285,7 @@ class VlTest:
#######################################################################
# File utilities
def files_identical(self, fn1: str, fn2: str, is_logfile=False) -> None:
def files_identical(self, fn1: str, fn2: str, is_logfile=False, strip_hex=False) -> None:
"""Test if two files have identical contents"""
delay = 0.25
for tryn in range(Args.log_retries, -1, -1):
@ -2294,10 +2294,11 @@ class VlTest:
delay = min(1, delay * 2)
moretry = tryn != 0
if not self._files_identical_try(
fn1=fn1, fn2=fn2, is_logfile=is_logfile, moretry=moretry):
fn1=fn1, fn2=fn2, is_logfile=is_logfile, strip_hex=strip_hex, moretry=moretry):
break
def _files_identical_try(self, fn1: str, fn2: str, is_logfile: bool, moretry: bool) -> bool:
def _files_identical_try(self, fn1: str, fn2: str, is_logfile: bool, strip_hex: bool,
moretry: bool) -> bool:
# If moretry, then return true to try again
try:
f1 = open( # pylint: disable=consider-using-with
@ -2321,6 +2322,7 @@ class VlTest:
fn1=fn1,
fn2=fn2,
is_logfile=is_logfile,
strip_hex=strip_hex,
moretry=moretry)
if f1:
f1.close()
@ -2329,7 +2331,7 @@ class VlTest:
return again
def _files_identical_reader(self, f1, f2, fn1: str, fn2: str, is_logfile: bool,
moretry: bool) -> None:
strip_hex: bool, moretry: bool) -> None:
# If moretry, then return true to try again
l1s = f1.readlines()
l2s = f2.readlines() if f2 else []
@ -2377,6 +2379,13 @@ class VlTest:
#
l1s = l1o
if strip_hex:
l1o = []
for line in l1s:
line = re.sub(r'\b0x[0-9a-f]+', '0x#', line)
l1o.append(line)
l1s = l1o
for lineno_m1 in range(0, max(len(l1s), len(l2s))):
l1 = l1s[lineno_m1] if lineno_m1 < len(l1s) else "*EOF*\n"
l2 = l2s[lineno_m1] if lineno_m1 < len(l2s) else "*EOF*\n"

View File

@ -0,0 +1,43 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by Wilson Snyder.
// SPDX-License-Identifier: CC0-1.0
interface AXIS_IF (
input logic aclk
);
logic [127:0] tdata;
logic [ 31:0] tuser;
logic tvalid, tready;
modport master(input aclk, output tdata, tuser, tvalid, input tready);
modport slave(input aclk, input tdata, tuser, tvalid, output tready);
endinterface : AXIS_IF
module sub (
input clk,
AXIS_IF.slave s_axis_if
);
assign s_axis_if.tready = s_axis_if.tdata[0];
endmodule
module dut (
input clk,
AXIS_IF.slave s_axis_if
);
sub u_sub(.*);
endmodule
module t(/*AUTOARG*/
// Inputs
clk
);
input clk;
AXIS_IF s_axis_if (.aclk(clk));
dut u_dut (.clk, .s_axis_if(s_axis_if));
initial begin
$c("Verilated::scopesDump();");
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,5 @@
scopesDump:
SCOPE 0x#: top.TOP
SCOPE 0x#: top.t
*-* All Finished *-*

View File

@ -0,0 +1,21 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2025 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
test.top_filename = "t/t_vpi_public_depthn.v"
test.compile(verilator_flags2=['--public-depth 1'])
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,7 @@
scopesDump:
SCOPE 0x#: top.TOP
SCOPE 0x#: top.t
SCOPE 0x#: top.t.s_axis_if
SCOPE 0x#: top.t.u_dut
*-* All Finished *-*

View File

@ -0,0 +1,21 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2025 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
test.top_filename = "t/t_vpi_public_depthn.v"
test.compile(verilator_flags2=['--public-depth 2'])
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,8 @@
scopesDump:
SCOPE 0x#: top.TOP
SCOPE 0x#: top.t
SCOPE 0x#: top.t.s_axis_if
SCOPE 0x#: top.t.u_dut
SCOPE 0x#: top.t.u_dut.u_sub
*-* All Finished *-*

View File

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