Trigger virtual interfaces in proper place (#6844)
This commit is contained in:
parent
c62ed27e0b
commit
d5784b8cf2
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue