Trigger virtual interfaces in proper place (#6844)

This commit is contained in:
Igor Zaworski 2026-01-06 13:15:33 +01:00 committed by GitHub
parent c62ed27e0b
commit d5784b8cf2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 280 additions and 165 deletions

View File

@ -41,7 +41,7 @@ namespace {
class VirtIfaceVisitor final : public VNVisitor {
private:
// NODE STATE
// AstIface::user1() -> AstVarScope*. Trigger var for this interface
// AstVarRef::user1() -> bool. Whether it has been visited
const VNUser1InUse m_user1InUse;
// TYPES
@ -51,47 +51,12 @@ private:
// STATE
AstNetlist* const m_netlistp; // Root node
AstAssign* m_trigAssignp = nullptr; // Previous/current trigger assignment
AstIface* m_trigAssignIfacep = nullptr; // Interface type whose trigger is assigned
// by m_trigAssignp
AstVar* m_trigAssignMemberVarp = nullptr; // Member pointer whose trigger is assigned
V3UniqueNames m_vifTriggerNames{"__VvifTrigger"}; // Unique names for virt iface
// triggers
VirtIfaceTriggers m_triggers; // Interfaces and corresponding trigger vars
// METHODS
// For each write across a virtual interface boundary
static void foreachWrittenVirtIface(AstNode* const nodep, const OnWriteToVirtIface& onWrite) {
nodep->foreach([&](AstVarRef* const refp) {
if (refp->access().isReadOnly()) return;
if (AstIfaceRefDType* const dtypep = VN_CAST(refp->varp()->dtypep(), IfaceRefDType)) {
if (dtypep->isVirtual() && VN_IS(refp->firstAbovep(), MemberSel)) {
onWrite(refp, dtypep->ifacep());
}
} else if (AstIface* const ifacep = refp->varp()->sensIfacep()) {
onWrite(refp, ifacep);
}
});
}
// For each write across a virtual interface boundary (member-level tracking)
static void foreachWrittenVirtIfaceMember(
AstNode* const nodep, const std::function<void(AstVarRef*, AstIface*, AstVar*)>& onWrite) {
nodep->foreach([&](AstVarRef* const refp) {
if (refp->access().isReadOnly()) return;
if (AstIfaceRefDType* const dtypep = VN_CAST(refp->varp()->dtypep(), IfaceRefDType)) {
if (dtypep->isVirtual()) {
if (AstMemberSel* const memberSelp = VN_CAST(refp->firstAbovep(), MemberSel)) {
// Extract the member varp from the MemberSel node
AstVar* memberVarp = memberSelp->varp();
onWrite(refp, dtypep->ifacep(), memberVarp);
}
}
} else if (AstIface* const ifacep = refp->varp()->sensIfacep()) {
AstVar* memberVarp = refp->varp();
onWrite(refp, ifacep, memberVarp);
}
});
}
// Returns true if there is a write across a virtual interface boundary
static bool writesToVirtIface(const AstNode* const nodep) {
return nodep->exists([](const AstVarRef* const refp) {
@ -103,25 +68,6 @@ private:
return writesToVirtIfaceMember || writesToIfaceSensVar;
});
}
// Error on write across a virtual interface boundary
static void unsupportedWriteToVirtIfaceMember(AstNode* nodep, const char* locationp) {
if (!nodep) return;
foreachWrittenVirtIfaceMember(
nodep, [locationp](AstVarRef* const selp, AstIface*, AstVar* varp) {
selp->v3warn(E_UNSUPPORTED,
"Unsupported: Write to virtual interface in " << locationp);
});
}
// Create trigger var for the given interface if it doesn't exist; return a write ref to it
AstVarRef* createVirtIfaceTriggerRefp(FileLine* const flp, AstIface* ifacep) {
if (!ifacep->user1()) {
AstScope* const scopeTopp = m_netlistp->topScopep()->scopep();
AstVarScope* const vscp = scopeTopp->createTemp(m_vifTriggerNames.get(ifacep), 1);
ifacep->user1p(vscp);
m_triggers.addIfaceTrigger(ifacep, vscp);
}
return new AstVarRef{flp, VN_AS(ifacep->user1p(), VarScope), VAccess::WRITE};
}
// Create trigger reference for a specific interface member
AstVarRef* createVirtIfaceMemberTriggerRefp(FileLine* const flp, AstIface* ifacep,
@ -142,12 +88,6 @@ private:
// VISITORS
void visit(AstNodeProcedure* nodep) override {
VL_RESTORER(m_trigAssignp);
m_trigAssignp = nullptr;
VL_RESTORER(m_trigAssignIfacep);
m_trigAssignIfacep = nullptr;
VL_RESTORER(m_trigAssignMemberVarp);
m_trigAssignMemberVarp = nullptr;
// Not sure if needed, but be paranoid to match previous behavior as didn't optimize
// before ..
if (VN_IS(nodep, AlwaysPost) && writesToVirtIface(nodep)) {
@ -155,111 +95,34 @@ private:
}
iterateChildren(nodep);
}
void visit(AstCFunc* nodep) override {
VL_RESTORER(m_trigAssignp);
m_trigAssignp = nullptr;
VL_RESTORER(m_trigAssignIfacep);
m_trigAssignIfacep = nullptr;
VL_RESTORER(m_trigAssignMemberVarp);
m_trigAssignMemberVarp = nullptr;
iterateChildren(nodep);
}
void visit(AstNodeIf* nodep) override {
unsupportedWriteToVirtIfaceMember(nodep->condp(), "if condition");
{
VL_RESTORER(m_trigAssignp);
VL_RESTORER(m_trigAssignIfacep);
VL_RESTORER(m_trigAssignMemberVarp);
iterateAndNextNull(nodep->thensp());
void visit(AstVarRef* const nodep) override {
if (nodep->access().isReadOnly()) return;
if (nodep->user1SetOnce()) return;
AstIface* ifacep = nullptr;
AstVar* memberVarp = nullptr;
if (AstIfaceRefDType* const dtypep = VN_CAST(nodep->varp()->dtypep(), IfaceRefDType)) {
if (dtypep->isVirtual()) {
if (AstMemberSel* const memberSelp = VN_CAST(nodep->firstAbovep(), MemberSel)) {
// Extract the member varp from the MemberSel node
memberVarp = memberSelp->varp();
ifacep = dtypep->ifacep();
}
}
} else if ((ifacep = nodep->varp()->sensIfacep())) {
memberVarp = nodep->varp();
}
{
VL_RESTORER(m_trigAssignp);
VL_RESTORER(m_trigAssignIfacep);
VL_RESTORER(m_trigAssignMemberVarp);
iterateAndNextNull(nodep->elsesp());
}
if (v3Global.usesTiming()) {
// Clear the trigger assignment, as there could have been timing controls in either
// branch
m_trigAssignp = nullptr;
m_trigAssignIfacep = nullptr;
m_trigAssignMemberVarp = nullptr;
}
}
void visit(AstLoop* nodep) override {
UASSERT_OBJ(!nodep->contsp(), nodep, "'contsp' only used before LinkJump");
{
VL_RESTORER(m_trigAssignp);
VL_RESTORER(m_trigAssignIfacep);
VL_RESTORER(m_trigAssignMemberVarp);
iterateAndNextNull(nodep->stmtsp());
}
if (v3Global.usesTiming()) {
// Clear the trigger assignment, as there could have been timing controls in the loop
m_trigAssignp = nullptr;
m_trigAssignIfacep = nullptr;
m_trigAssignMemberVarp = nullptr;
}
}
void visit(AstLoopTest* nodep) override {
unsupportedWriteToVirtIfaceMember(nodep->condp(), "loop condition");
}
void visit(AstJumpBlock* nodep) override {
{
VL_RESTORER(m_trigAssignp);
VL_RESTORER(m_trigAssignIfacep);
VL_RESTORER(m_trigAssignMemberVarp);
iterateChildren(nodep);
}
if (v3Global.usesTiming()) {
// Clear the trigger assignment, as there could have been timing controls in the jump
// block
m_trigAssignp = nullptr;
m_trigAssignIfacep = nullptr;
m_trigAssignMemberVarp = nullptr;
}
}
void visit(AstNodeStmt* nodep) override {
if (v3Global.usesTiming()
&& nodep->exists([](AstNode* nodep) { return nodep->isTimingControl(); })) {
m_trigAssignp = nullptr;
m_trigAssignIfacep = nullptr;
m_trigAssignMemberVarp = nullptr;
}
FileLine* const flp = nodep->fileline();
foreachWrittenVirtIfaceMember(nodep, [&](AstVarRef*, AstIface* ifacep,
AstVar* memberVarp) {
if (ifacep != m_trigAssignIfacep || memberVarp != m_trigAssignMemberVarp) {
// Write to different interface member than before - need new trigger assignment
m_trigAssignIfacep = ifacep;
m_trigAssignMemberVarp = memberVarp;
m_trigAssignp = nullptr;
}
if (!m_trigAssignp) {
m_trigAssignp
= new AstAssign{flp, createVirtIfaceMemberTriggerRefp(flp, ifacep, memberVarp),
new AstConst{flp, AstConst::BitTrue{}}};
nodep->addNextHere(m_trigAssignp);
}
});
// Fallback to whole-interface tracking if no member-specific assignments found
if (!m_trigAssignp) {
foreachWrittenVirtIface(nodep, [&](AstVarRef*, AstIface* ifacep) {
if (ifacep != m_trigAssignIfacep) {
m_trigAssignIfacep = ifacep;
m_trigAssignMemberVarp = nullptr;
m_trigAssignp = nullptr;
}
if (!m_trigAssignp) {
m_trigAssignp = new AstAssign{flp, createVirtIfaceTriggerRefp(flp, ifacep),
new AstConst{flp, AstConst::BitTrue{}}};
nodep->addNextHere(m_trigAssignp);
}
});
if (ifacep && memberVarp) {
FileLine* const flp = nodep->fileline();
VNRelinker relinker;
nodep->unlinkFrBack(&relinker);
relinker.relink(new AstExprStmt{
flp,
new AstAssign{flp, createVirtIfaceMemberTriggerRefp(flp, ifacep, memberVarp),
new AstConst{flp, AstConst::BitTrue{}}},
nodep});
}
}
void visit(AstNodeExpr*) override {} // Accelerate
void visit(AstNode* nodep) override { iterateChildren(nodep); }
public:

View File

@ -11,9 +11,8 @@ import vltest_bootstrap
test.scenarios('simulator')
test.compile(fails=test.vlt_all, expect_filename=test.golden_filename)
test.compile(verilator_flags2=["--binary", "--unroll-stmts", "0"])
if not test.vlt_all:
test.execute()
test.execute()
test.passes()

View File

@ -0,0 +1,36 @@
// DESCRIPTION: Verilator: Verilog Test module for SystemVerilog 'alias'
//
// Alias type check error test.
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by Antmicro.
// SPDX-License-Identifier: CC0-1.0
interface Bus;
bit data;
endinterface
module t;
Bus intf();
virtual Bus vif = intf;
bit ok = 0;
function logic write_data(output bit data);
data = ~data;
return data;
endfunction
initial @(posedge vif.data) ok = 1;
initial begin
bit first = 1;
#1;
do begin
if (!first) $stop;
first = 0;
end while(!write_data(vif.data));
#1 if (ok != 1) $stop;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,18 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2024 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('simulator')
test.compile(verilator_flags2=["--binary", "--unroll-stmts", "0"])
test.execute()
test.passes()

View File

@ -0,0 +1,32 @@
// DESCRIPTION: Verilator: Verilog Test module for SystemVerilog 'alias'
//
// Alias type check error test.
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by Antmicro.
// SPDX-License-Identifier: CC0-1.0
interface Bus;
bit data;
endinterface
module t;
Bus intf();
virtual Bus vif = intf;
bit ok = 0;
function logic write_data(output bit data);
data = ~data;
return data;
endfunction
initial @(posedge vif.data) ok = 1;
initial begin
#1;
for (int i = 0; !write_data(vif.data); i++) $stop;
#1 if (ok != 1) $stop;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,18 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2024 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('simulator')
test.compile(verilator_flags2=["--binary"])
test.execute()
test.passes()

View File

@ -0,0 +1,32 @@
// DESCRIPTION: Verilator: Verilog Test module for SystemVerilog 'alias'
//
// Alias type check error test.
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by Antmicro.
// SPDX-License-Identifier: CC0-1.0
interface Bus;
bit data;
endinterface
module t;
Bus intf();
virtual Bus vif = intf;
bit ok = 0;
function logic write_data(output bit data);
data = ~data;
return data;
endfunction
initial @(posedge vif.data) ok = 1;
initial begin
#1;
if (!write_data(vif.data)) $stop;
#1 if (ok != 1) $stop;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,18 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2024 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('simulator')
test.compile(verilator_flags2=["--binary", "--unroll-stmts", "0"])
test.execute()
test.passes()

View File

@ -0,0 +1,32 @@
// DESCRIPTION: Verilator: Verilog Test module for SystemVerilog 'alias'
//
// Alias type check error test.
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by Antmicro.
// SPDX-License-Identifier: CC0-1.0
interface Bus;
bit data;
endinterface
module t;
Bus intf();
virtual Bus vif = intf;
bit ok = 0;
function logic write_data(output bit data);
data = ~data;
return data;
endfunction
initial @(posedge vif.data) ok = 1;
initial begin
#1;
while (!write_data(vif.data)) $stop;
#1 if (ok != 1) $stop;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,18 @@
#!/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('simulator')
test.compile(verilator_flags2=['--binary'])
test.execute()
test.passes()

View File

@ -0,0 +1,49 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by Antmicro.
// SPDX-License-Identifier: CC0-1.0
interface clk_if;
bit a;
bit clk;
endinterface
interface inf;
bit clk;
bit v;
clocking cb @(posedge clk);
inout v;
endclocking
endinterface
class Clocker;
virtual clk_if clk;
task clock();
fork forever #1 clk.clk = ~clk.clk;
join_none
endtask
endclass
module t;
clk_if c();
inf i();
assign i.clk = c.clk;
Clocker clocker;
initial begin
i.clk = 0;
i.v = 0;
clocker = new;
clocker.clk = c;
clocker.clock();
i.cb.v <= 1;
#5;
$stop;
end
initial @(posedge i.cb.v) begin
$write("*-* All Finished *-*\n");
$finish;
end
endmodule