Fix scheduling of virtual interface method writes (#7641)
Fix scheduling of writes in virtual interfaces, there were missing triggers (see added test). Make V3SchedVirtIface handle writes done inside methods called through a virtual interface. The pass first records direct vif.member writes, VIF method calls, and candidate interface member VarScopes. It then walks the methods reachable from those VIF calls, writes to persistent interface variables in those method bodies are treated as VIF writes, and nested calls are followed with the same interface context. Function locals, temps, and events are ignored because they are not persistent interface storage observable through a later VIF read. Triggers are still created only from the intersection of (interface type, member name) writes and matching VarScopes, so unrelated interface variables and interfaces with no virtual access do not get extra triggers.
This commit is contained in:
parent
84cc08b756
commit
350158c857
|
|
@ -39,11 +39,18 @@ namespace {
|
|||
class VirtIfaceVisitor final : public VNVisitor {
|
||||
private:
|
||||
// STATE
|
||||
using IfaceMember = std::pair<const AstIface*, std::string>;
|
||||
using IfaceCallable = std::pair<AstIface*, AstCFunc*>;
|
||||
|
||||
// Set of (iface, member) pairs written through VIF -- defines which members need triggers
|
||||
std::set<std::pair<const AstIface*, const std::string>> m_vifWrittenMembers;
|
||||
std::set<IfaceMember> m_vifWrittenMembers;
|
||||
// All candidate VarScopes of interface members (keyed by interface type + member name)
|
||||
std::map<std::pair<const AstIface*, const std::string>, std::vector<AstVarScope*>>
|
||||
m_candidateVscps;
|
||||
std::map<IfaceMember, std::vector<AstVarScope*>> m_candidateVscps;
|
||||
std::set<std::pair<IfaceMember, AstVarScope*>> m_seenCandidateVscps;
|
||||
// VarScope index and callable worklist for VIF method-body writes.
|
||||
std::map<const AstVar*, std::vector<AstVarScope*>> m_vscpsByVar;
|
||||
std::vector<IfaceCallable> m_reachableIfaceCallables;
|
||||
std::set<IfaceCallable> m_seenReachableIfaceCallables;
|
||||
VirtIfaceTriggers m_triggers;
|
||||
|
||||
// METHODS
|
||||
|
|
@ -70,12 +77,24 @@ private:
|
|||
}
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
void visit(AstCMethodCall* nodep) override {
|
||||
if (const AstIfaceRefDType* const dtypep
|
||||
= VN_CAST(nodep->fromp()->dtypep()->skipRefp(), IfaceRefDType)) {
|
||||
if (VL_UNCOVERABLE(!dtypep->isVirtual())) {
|
||||
// Concrete interface method calls are lowered before this pass.
|
||||
} else {
|
||||
addReachableIfaceCallable(dtypep->ifaceViaCellp(), nodep->funcp());
|
||||
}
|
||||
}
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
void visit(AstVarScope* nodep) override {
|
||||
// Collect candidate VarScopes. sensIfacep() is set on interface members
|
||||
// accessed via any MemberSel (virtual or non-virtual).
|
||||
if (const AstIface* const ifacep = nodep->varp()->sensIfacep()) {
|
||||
m_candidateVscps[{ifacep, nodep->varp()->name()}].push_back(nodep);
|
||||
}
|
||||
AstVar* const varp = nodep->varp();
|
||||
if (varp->isTemp()) return;
|
||||
m_vscpsByVar[varp].push_back(nodep);
|
||||
if (const AstIface* const ifacep = varp->sensIfacep()) addCandidateVscp(ifacep, nodep);
|
||||
}
|
||||
void visit(AstNodeProcedure* nodep) override {
|
||||
// Disable lifetime optimization for variables in AlwaysPost blocks
|
||||
|
|
@ -87,6 +106,19 @@ private:
|
|||
}
|
||||
void visit(AstNode* nodep) override { iterateChildren(nodep); }
|
||||
|
||||
void addCandidateVscp(const AstIface* const ifacep, AstVarScope* const vscp) {
|
||||
const IfaceMember member{ifacep, vscp->varp()->name()};
|
||||
if (m_seenCandidateVscps.emplace(member, vscp).second)
|
||||
m_candidateVscps[member].push_back(vscp);
|
||||
}
|
||||
|
||||
void addReachableIfaceCallable(AstIface* const ifacep, AstCFunc* const funcp) {
|
||||
const IfaceCallable callable{ifacep, funcp};
|
||||
if (m_seenReachableIfaceCallables.emplace(callable).second) {
|
||||
m_reachableIfaceCallables.push_back(callable);
|
||||
}
|
||||
}
|
||||
|
||||
// Build final trigger list by intersecting VIF writes with candidate VarScopes
|
||||
void buildTriggers() {
|
||||
for (const auto& written : m_vifWrittenMembers) {
|
||||
|
|
@ -103,6 +135,29 @@ public:
|
|||
// CONSTRUCTORS
|
||||
explicit VirtIfaceVisitor(AstNetlist* nodep) {
|
||||
iterate(nodep);
|
||||
for (size_t i = 0; i < m_reachableIfaceCallables.size(); ++i) {
|
||||
const IfaceCallable callable = m_reachableIfaceCallables[i];
|
||||
callable.second->foreach([this, callable](AstNodeExpr* const nodep) {
|
||||
if (AstVarRef* const refp = VN_CAST(nodep, VarRef)) {
|
||||
// Only persistent interface storage is observable through a VIF read.
|
||||
UASSERT_OBJ(refp->varScopep(), refp, "No var scope");
|
||||
AstVar* const varp = refp->varp();
|
||||
if (!refp->access().isWriteOrRW() || varp->isFuncLocal() || varp->isTemp()
|
||||
|| varp->isEvent() || !VN_IS(refp->varScopep()->scopep()->modp(), Iface)) {
|
||||
return;
|
||||
}
|
||||
varp->sensIfacep(callable.first);
|
||||
m_vifWrittenMembers.emplace(callable.first, varp->name());
|
||||
const auto it = m_vscpsByVar.find(varp);
|
||||
UASSERT_OBJ(it != m_vscpsByVar.end(), varp,
|
||||
"No VarScope for interface member");
|
||||
for (AstVarScope* const vscp : it->second)
|
||||
addCandidateVscp(callable.first, vscp);
|
||||
} else if (AstNodeCCall* const callp = VN_CAST(nodep, NodeCCall)) {
|
||||
addReachableIfaceCallable(callable.first, callp->funcp());
|
||||
}
|
||||
});
|
||||
}
|
||||
buildTriggers();
|
||||
}
|
||||
~VirtIfaceVisitor() override = default;
|
||||
|
|
|
|||
|
|
@ -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('simulator')
|
||||
|
||||
test.compile(verilator_flags2=["--timing"])
|
||||
|
||||
test.execute()
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Antmicro
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
class msg;
|
||||
string context_name;
|
||||
|
||||
function void set_context(string name);
|
||||
context_name = name;
|
||||
endfunction
|
||||
endclass
|
||||
|
||||
package helper_pkg;
|
||||
int package_counter;
|
||||
|
||||
function void bump();
|
||||
package_counter++;
|
||||
endfunction
|
||||
endpackage
|
||||
|
||||
interface intf(output logic a, output logic b);
|
||||
msg m;
|
||||
event e;
|
||||
|
||||
task go();
|
||||
helper_pkg::package_counter++;
|
||||
go_helper();
|
||||
go_helper();
|
||||
endtask
|
||||
|
||||
task go_helper();
|
||||
// verilator no_inline_task
|
||||
m.set_context("go_helper");
|
||||
helper_pkg::bump();
|
||||
-> e;
|
||||
a <= 1;
|
||||
endtask
|
||||
endinterface
|
||||
|
||||
class driver;
|
||||
virtual intf vif;
|
||||
|
||||
function new(virtual intf vif);
|
||||
this.vif = vif;
|
||||
endfunction
|
||||
|
||||
task go();
|
||||
vif.go();
|
||||
endtask
|
||||
endclass
|
||||
|
||||
module t;
|
||||
wire a;
|
||||
wire b;
|
||||
virtual intf vif;
|
||||
|
||||
intf i(a, b);
|
||||
|
||||
initial begin
|
||||
driver d;
|
||||
vif = t.i;
|
||||
t.i.m = new;
|
||||
d = new(t.i);
|
||||
d.go();
|
||||
end
|
||||
|
||||
always @(posedge a) begin
|
||||
vif.b <= 1;
|
||||
end
|
||||
|
||||
always @(*) begin
|
||||
if (a && b) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
Loading…
Reference in New Issue