Fix tracing virtual interface member written from classes (#5044) (#7465)

Track AstMemberSel writes through virtual interface refs and connect them to matching interface-member VarScopes, so class-driven interface clocks get proper VCD activity updates.

Fixes #5044.
This commit is contained in:
Nikolay Puzanov 2026-04-22 19:09:30 +03:00 committed by GitHub
parent 496665800d
commit 5cddbd7fda
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 174 additions and 0 deletions

View File

@ -47,6 +47,7 @@
#include "V3UniqueNames.h"
#include <limits>
#include <map>
#include <set>
#include <unordered_map>
@ -216,6 +217,9 @@ class TraceVisitor final : public VNVisitor {
using ActCodeSet = std::set<uint32_t>;
// For activity set, what traces apply
using TraceVec = std::multimap<ActCodeSet, TraceTraceVertex*>;
// Candidate interface-member VarScopes keyed by (interface type, member name)
std::map<std::pair<const AstIface*, std::string>, std::vector<AstVarScope*>>
m_ifaceMemberVscps;
// METHODS
@ -1125,6 +1129,13 @@ class TraceVisitor final : public VNVisitor {
if (nodep->isTop()) m_topModp = nodep;
iterateChildren(nodep);
}
void visit(AstVarScope* nodep) override {
if (!m_finding) {
if (const AstIface* const ifacep = nodep->varp()->sensIfacep()) {
m_ifaceMemberVscps[{ifacep, nodep->varp()->name()}].push_back(nodep);
}
}
}
void visit(AstStmtExpr* nodep) override {
if (!m_finding && !nodep->user2()) {
if (AstCCall* const callp = VN_CAST(nodep->exprp(), CCall)) {
@ -1214,6 +1225,27 @@ class TraceVisitor final : public VNVisitor {
}
}
}
void visit(AstMemberSel* nodep) override {
if (m_cfuncp && m_finding && nodep->access().isWriteOrRW()) {
AstIfaceRefDType* const dtypep
= VN_CAST(nodep->fromp()->dtypep()->skipRefp(), IfaceRefDType);
if (dtypep && dtypep->isVirtual()) {
const auto it = m_ifaceMemberVscps.find({dtypep->ifacep(), nodep->varp()->name()});
if (it != m_ifaceMemberVscps.end()) {
V3GraphVertex* const funcVtxp = getCFuncVertexp(m_cfuncp);
for (AstVarScope* const vscp : it->second) {
V3GraphVertex* varVtxp = vscp->user1u().toGraphVertex();
if (!varVtxp) {
varVtxp = new TraceVarVertex{&m_graph, vscp};
vscp->user1p(varVtxp);
}
new V3GraphEdge{&m_graph, funcVtxp, varVtxp, 1};
}
}
}
}
iterateChildren(nodep);
}
//--------------------
void visit(AstNode* nodep) override { iterateChildren(nodep); }

View File

@ -0,0 +1,22 @@
#!/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('simulator')
test.compile(verilator_flags2=['--binary --trace-vcd --timing'])
test.execute()
# Expect 5 posedges and 5 low samples in VCD for the class-driven interface clock.
test.file_grep_count(test.trace_filename, r'(?m)^1[!-~]$', 5)
test.file_grep_count(test.trace_filename, r'(?m)^0[!-~]$', 5)
test.passes()

View File

@ -0,0 +1,45 @@
// 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
`timescale 1ns/1ns
`define STRINGIFY(x) `"x`"
interface clk_iface;
bit clk;
endinterface
class clk_driver;
virtual clk_iface vif;
function new(virtual clk_iface vif);
this.vif = vif;
endfunction
task run();
vif.clk = 1'b0;
forever #5 vif.clk = ~vif.clk;
endtask
endclass
module t;
clk_iface ci();
clk_driver drv;
int x = 0;
always @(posedge ci.clk) x = x + 1;
initial begin
drv = new(ci);
drv.run();
end
initial begin
$dumpfile(`STRINGIFY(`TEST_DUMPFILE));
$dumpvars(0, t);
repeat (5) @(posedge ci.clk);
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,22 @@
#!/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('simulator')
test.compile(verilator_flags2=['--binary --trace-vcd --timing'])
test.execute()
# Two class-driven clocks toggle in lockstep: 10 highs and 10 lows total.
test.file_grep_count(test.trace_filename, r'(?m)^1[!-~]$', 10)
test.file_grep_count(test.trace_filename, r'(?m)^0[!-~]$', 10)
test.passes()

View File

@ -0,0 +1,53 @@
// 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
`timescale 1ns/1ns
`define STRINGIFY(x) `"x`"
interface clk_iface;
bit clk;
endinterface
class clk_driver;
virtual clk_iface vif;
function new(virtual clk_iface vif);
this.vif = vif;
endfunction
task run();
vif.clk = 1'b0;
forever #5 vif.clk = ~vif.clk;
endtask
endclass
module t;
clk_iface ci0();
clk_iface ci1();
clk_driver drv0;
clk_driver drv1;
int x0 = 0;
int x1 = 0;
always @(posedge ci0.clk) x0 = x0 + 1;
always @(posedge ci1.clk) x1 = x1 + 1;
initial begin
drv0 = new(ci0);
drv1 = new(ci1);
fork
drv0.run();
drv1.run();
join_none
end
initial begin
$dumpfile(`STRINGIFY(`TEST_DUMPFILE));
$dumpvars(0, t);
repeat (5) @(posedge ci0.clk);
$write("*-* All Finished *-*\n");
$finish;
end
endmodule