Support member-level triggers for virtual interfaces (#5166) (#6148)

This commit is contained in:
Yilou Wang 2025-07-12 03:04:51 +02:00 committed by GitHub
parent 77180c4020
commit 1044398f95
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 374 additions and 260 deletions

View File

@ -1189,6 +1189,18 @@ VirtIfaceTriggers::makeIfaceToSensMap(AstNetlist* const netlistp, size_t vifTrig
return ifaceToSensMap;
}
VirtIfaceTriggers::IfaceMemberSensMap
VirtIfaceTriggers::makeMemberToSensMap(AstNetlist* const netlistp, size_t vifTriggerIndex,
AstVarScope* trigVscp) const {
IfaceMemberSensMap memberToSensMap;
for (const auto& p : m_memberTriggers) {
memberToSensMap.emplace(
std::make_pair(p.first, createTriggerSenTree(netlistp, trigVscp, vifTriggerIndex)));
++vifTriggerIndex;
}
return memberToSensMap;
}
//============================================================================
// Top level entry-point to scheduling

View File

@ -153,17 +153,55 @@ public:
};
class VirtIfaceTriggers final {
// Represents a specific member in a virtual interface
struct IfaceMember final {
const AstIface* m_ifacep; // Interface type
const AstVar* m_memberVarp; // pointer to member field
IfaceMember(const AstIface* ifacep, const AstVar* memberVarp)
: m_ifacep(ifacep)
, m_memberVarp(memberVarp) {}
bool operator<(const IfaceMember& other) const {
if (m_ifacep != other.m_ifacep) return m_ifacep < other.m_ifacep;
return m_memberVarp < other.m_memberVarp;
}
};
using IfaceMemberTrigger = std::pair<IfaceMember, AstVarScope*>;
using IfaceMemberTriggerVec = std::vector<IfaceMemberTrigger>;
using IfaceMemberSensMap = std::map<IfaceMember, AstSenTree*>;
using IfaceTrigger = std::pair<const AstIface*, AstVarScope*>;
using IfaceTriggerVec = std::vector<IfaceTrigger>;
using IfaceSensMap = std::map<const AstIface*, AstSenTree*>;
IfaceTriggerVec m_triggers;
IfaceMemberTriggerVec m_memberTriggers;
IfaceTriggerVec m_ifaceTriggers;
public:
void emplace_back(IfaceTrigger&& p) { m_triggers.emplace_back(std::move(p)); }
IfaceTriggerVec::const_iterator begin() const { return m_triggers.begin(); }
IfaceTriggerVec::const_iterator end() const { return m_triggers.end(); }
void addMemberTrigger(const AstIface* ifacep, const AstVar* memberVarp,
AstVarScope* triggerVscp) {
m_memberTriggers.emplace_back(IfaceMember(ifacep, memberVarp), triggerVscp);
}
AstVarScope* findMemberTrigger(const AstIface* ifacep, const AstVar* memberVarp) const {
IfaceMember target{ifacep, memberVarp};
for (const auto& pair : m_memberTriggers) {
if (!(pair.first < target) && !(target < pair.first)) return pair.second;
}
return nullptr;
}
IfaceMemberSensMap makeMemberToSensMap(AstNetlist* netlistp, size_t vifTriggerIndex,
AstVarScope* trigVscp) const;
void emplace_back(IfaceTrigger&& p) { m_ifaceTriggers.emplace_back(std::move(p)); }
IfaceTriggerVec::const_iterator begin() const { return m_ifaceTriggers.begin(); }
IfaceTriggerVec::const_iterator end() const { return m_ifaceTriggers.end(); }
IfaceSensMap makeIfaceToSensMap(AstNetlist* netlistp, size_t vifTriggerIndex,
AstVarScope* trigVscp) const;
VL_UNCOPYABLE(VirtIfaceTriggers);
VirtIfaceTriggers() = default;
VirtIfaceTriggers(VirtIfaceTriggers&&) = default;

View File

@ -49,12 +49,15 @@ private:
// TYPES
using OnWriteToVirtIface = std::function<void(AstVarRef*, AstIface*)>;
using OnWriteToVirtIfaceMember
= std::function<void(AstVarRef*, AstIface*, const std::string&)>;
// 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; // Member pointer whose trigger is assigned
V3UniqueNames m_vifTriggerNames{"__VvifTrigger"}; // Unique names for virt iface
// triggers
VirtIfaceTriggers m_triggers; // Interfaces and corresponding trigger vars
@ -73,6 +76,25 @@ private:
}
});
}
// 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,12 +125,31 @@ private:
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,
const AstVar* memberVarp) {
// Check if we already have a trigger for this specific member
AstVarScope* existingTrigger = m_triggers.findMemberTrigger(ifacep, memberVarp);
if (!existingTrigger) {
AstScope* const scopeTopp = m_netlistp->topScopep()->scopep();
// Create a unique name for this member trigger
const std::string triggerName
= m_vifTriggerNames.get(ifacep) + "_Vtrigm_" + memberVarp->name();
AstVarScope* const vscp = scopeTopp->createTemp(triggerName, 1);
m_triggers.addMemberTrigger(ifacep, memberVarp, vscp);
existingTrigger = vscp;
}
return new AstVarRef{flp, existingTrigger, VAccess::WRITE};
}
// 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;
iterateChildren(nodep);
}
void visit(AstCFunc* nodep) override {
@ -116,6 +157,8 @@ private:
m_trigAssignp = nullptr;
VL_RESTORER(m_trigAssignIfacep);
m_trigAssignIfacep = nullptr;
VL_RESTORER(m_trigAssignMemberVarp);
m_trigAssignMemberVarp = nullptr;
iterateChildren(nodep);
}
void visit(AstAssignW* nodep) override {
@ -140,11 +183,13 @@ private:
{
VL_RESTORER(m_trigAssignp);
VL_RESTORER(m_trigAssignIfacep);
VL_RESTORER(m_trigAssignMemberVarp);
iterateAndNextNull(nodep->thensp());
}
{
VL_RESTORER(m_trigAssignp);
VL_RESTORER(m_trigAssignIfacep);
VL_RESTORER(m_trigAssignMemberVarp);
iterateAndNextNull(nodep->elsesp());
}
if (v3Global.usesTiming()) {
@ -152,6 +197,7 @@ private:
// branch
m_trigAssignp = nullptr;
m_trigAssignIfacep = nullptr;
m_trigAssignMemberVarp = nullptr;
}
}
void visit(AstWhile* nodep) override {
@ -161,18 +207,21 @@ private:
{
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(AstJumpBlock* nodep) override {
{
VL_RESTORER(m_trigAssignp);
VL_RESTORER(m_trigAssignIfacep);
VL_RESTORER(m_trigAssignMemberVarp);
iterateChildren(nodep);
}
if (v3Global.usesTiming()) {
@ -180,29 +229,48 @@ private:
// 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; // Could be after a delay - need new trigger assignment
m_trigAssignp = nullptr;
m_trigAssignIfacep = nullptr;
// No restorer, as following statements should not reuse the old assignment
m_trigAssignMemberVarp = nullptr;
}
FileLine* const flp = nodep->fileline();
foreachWrittenVirtIface(nodep, [&](AstVarRef*, AstIface* ifacep) {
if (ifacep != m_trigAssignIfacep) {
// Write to different interface type than before - need new trigger assignment
// No restorer, as following statements should not reuse the old assignment
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, createVirtIfaceTriggerRefp(flp, ifacep),
new AstConst{flp, AstConst::BitTrue{}}};
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);
}
});
}
}
void visit(AstNodeExpr*) override {} // Accelerate
void visit(AstNode* nodep) override { iterateChildren(nodep); }

View File

@ -1,19 +1,12 @@
[0] vif1.data==0000
[0] intf2.data==0000
[0] vif4.data==0000
[10] intf2.data==beef
[20] vif1.data==dead
[20] vif4.data==face
[30] intf2.data==beef
[40] vif1.data==dead
[40] vif4.data==face
[50] intf2.data==beef
[60] vif1.data==dead
[60] vif4.data==face
[70] intf2.data==beef
[80] intf2.data==beef
[80] vif4.data==cafe
[90] intf2.data==beef
[100] intf2.data==beef
[100] vif4.data==deaf
[0] vif3.data==0000
[0] intf4.data==0000
[5000] intf2.data==beef
[15000] vif1.data==dead
[15000] vif3.data==fafa
[15000] intf4.data==face
[75000] intf4.data==cafe
[95000] vif3.data==cafe
[95000] intf4.data==deaf
*-* All Finished *-*

View File

@ -11,7 +11,7 @@ import vltest_bootstrap
test.scenarios('simulator')
test.compile(verilator_flags2=["-fno-reorder"])
test.compile(verilator_flags2=["--exe --main --timing -fno-reorder"])
test.execute(expect_filename=test.golden_filename)

View File

@ -5,63 +5,69 @@
// SPDX-License-Identifier: CC0-1.0
interface Bus1;
logic [15:0] data;
logic [15:0] data;
endinterface
interface Bus2;
logic [15:0] data;
logic [15:0] data;
endinterface
interface Bus3;
logic [15:0] data;
logic [15:0] data;
endinterface
module t (
clk
);
input clk;
integer cyc = 0;
Bus1 intf1();
Bus2 intf2();
Bus3 intf3(), intf4();
virtual Bus1 vif1 = intf1;
virtual Bus2 vif2 = intf2;
virtual Bus3 vif3 = intf3, vif4 = intf4;
module t_controlflow;
logic clk = 0;
integer cyc = 0;
Bus1 intf1();
Bus2 intf2();
Bus3 intf3(), intf4();
virtual Bus1 vif1 = intf1;
virtual Bus2 vif2 = intf2;
virtual Bus3 vif3 = intf3, vif4 = intf4;
// Finish on negedge so that $finish is last
always @(negedge clk)
if (cyc >= 10) begin
$write("*-* All Finished *-*\n");
$finish;
// Finish on negedge so that $finish is last
always @(negedge clk) begin
if (cyc >= 10) begin
$write("*-* All Finished *-*\n");
$finish;
end
end
function void assign_to_intf3();
if ($c("1")) return;
intf3.data = 'hcafe;
endfunction
function void assign_to_intf3();
intf3.data = 'hcafe;
endfunction
always @(posedge clk) begin
logic foo = 1;
cyc <= cyc + 1;
if (cyc == 1 || cyc == 3 || cyc == 5) intf1.data = 'hdead;
else vif2.data = 'hbeef;
if (cyc == 1 || cyc == 3 || cyc == 5) begin
if (cyc >= 3) $c("// dummy statement");
else intf3.data = 'hfafa;
intf4.data = 'hface;
always @(posedge clk) begin
cyc <= cyc + 1;
if (cyc == 1 || cyc == 3 || cyc == 5) intf1.data = 'hdead;
else vif2.data = 'hbeef;
if (cyc == 1 || cyc == 3 || cyc == 5) begin
if (cyc < 3) intf3.data = 'hfafa;
intf4.data = 'hface;
end
if (cyc == 7) begin
intf4.data = 'hcafe;
end
if (cyc == 9) begin
assign_to_intf3;
intf4.data = 'hdeaf;
end
end
always @(vif1.data) begin
$write("[%0t] vif1.data==%h\n", $time, vif1.data);
end
always @(intf2.data) begin
$write("[%0t] intf2.data==%h\n", $time, intf2.data);
end
always @(vif3.data) begin
$write("[%0t] vif3.data==%h\n", $time, vif3.data);
end
always @(intf4.data) begin
$write("[%0t] intf4.data==%h\n", $time, intf4.data);
end
initial begin
repeat (20) #5ns clk = ~clk;
end
if (cyc == 7) begin
while ($c("0")) begin
foo = 0;
intf3.data = 'hbebe;
end
intf4.data = 'hcafe;
end
if (cyc == 9) begin
assign_to_intf3;
intf4.data = 'hdeaf;
end
end
always_comb $write("[%0t] vif1.data==%h\n", $time, vif1.data);
always_comb $write("[%0t] intf2.data==%h\n", $time, intf2.data);
always_comb $write("[%0t] vif4.data==%h\n", $time, vif4.data);
endmodule

View File

@ -1,12 +1,5 @@
[0] data==0000
[0] data==0000
[10] data==0000
[20] data==dead
[20] data==beef
[20] data==beef
[30] data==beef
[40] data==face
[40] data==cafe
[40] data==cafe
[50] data==cafe
*-* All Finished *-*
[20000] data==dead
[30000] data==beef
[40000] data==face
[50000] data==cafe

View File

@ -11,7 +11,7 @@ import vltest_bootstrap
test.scenarios('simulator')
test.compile()
test.compile(verilator_flags2=["--binary"])
test.execute(expect_filename=test.golden_filename)

View File

@ -8,10 +8,8 @@ interface Bus;
logic [15:0] data;
endinterface
module t (
clk
);
input clk;
module t_sched_act;
logic clk = 0;
integer cyc = 0;
Bus intf();
virtual Bus vif = intf;
@ -23,10 +21,10 @@ module t (
// Finish on negedge so that $finish is last
always @(negedge clk)
if (cyc >= 5) begin
$write("*-* All Finished *-*\n");
$finish;
end
if (cyc >= 6) begin
$write("*-* All Finished *-*\n");
$finish;
end
always @(posedge clk or data) begin
if (cyc == 1) intf.data <= 'hdead;
@ -35,6 +33,15 @@ module t (
else if (cyc == 4) intf.data <= 'hcafe;
end
assign data = vif.data;
always_comb $write("[%0t] data==%h\n", $time, data);
always @(negedge clk) begin
data <= vif.data;
end
always @(data) begin
$write("[%0t] data==%h\n", $time, data);
end
initial begin
repeat (10) #5ns clk = ~clk;
end
endmodule

View File

@ -1,51 +0,0 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
//
// Copyright 2023 by Geza Lore. 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
//
//*************************************************************************
#include "verilated.h"
#include "Vt_interface_virtual_sched_ico.h"
#include "Vt_interface_virtual_sched_ico__Syms.h"
#include <memory>
int main(int argc, char** argv) {
const std::unique_ptr<VerilatedContext> contextp{new VerilatedContext};
contextp->debug(0);
contextp->commandArgs(argc, argv);
srand48(5);
const std::unique_ptr<VM_PREFIX> topp{new VM_PREFIX};
topp->clk = false;
topp->inc1 = 1;
topp->eval();
topp->inc2 = 1;
topp->eval();
bool flop = true;
while (!contextp->gotFinish() && contextp->time() < 100000) {
contextp->timeInc(5);
if (topp->clk) {
if (flop) {
topp->inc1 += 1;
} else {
topp->inc2 += 1;
}
flop = !flop;
}
topp->clk = !topp->clk;
topp->eval();
}
if (!contextp->gotFinish()) {
vl_fatal(__FILE__, __LINE__, "main", "%Error: Timeout; never got a $finish");
}
return 0;
}

View File

@ -1,43 +1,20 @@
[0] intf1.inc==0
[0] vif2.inc==0
[0] intf1.inc==1
[0] vif2.inc==0
[0] intf1.inc==1
[0] vif2.inc==0
[0] intf1.inc==1
[0] vif2.inc==1
[5] intf1.inc==1
[5] vif2.inc==1
[10] intf1.inc==2
[10] vif2.inc==1
[15] intf1.inc==2
[15] vif2.inc==1
[20] intf1.inc==2
[20] vif2.inc==2
[25] intf1.inc==2
[25] vif2.inc==2
[30] intf1.inc==3
[30] vif2.inc==2
[35] intf1.inc==3
[35] vif2.inc==2
[40] intf1.inc==3
[40] vif2.inc==3
[45] intf1.inc==3
[45] vif2.inc==3
[50] intf1.inc==4
[50] vif2.inc==3
[55] intf1.inc==4
[55] vif2.inc==3
[60] intf1.inc==4
[60] vif2.inc==4
[65] intf1.inc==4
[65] vif2.inc==4
[70] intf1.inc==5
[70] vif2.inc==4
[75] intf1.inc==5
[75] vif2.inc==4
[80] intf1.inc==5
[80] vif2.inc==5
[85] intf1.inc==5
[85] vif2.inc==5
[0] intf1.inc==00000000
[0] vif2.inc==00000001
[5000] intf1.inc==00000001
[10000] vif2.inc==00000002
[15000] intf1.inc==00000002
[20000] vif2.inc==00000003
[25000] intf1.inc==00000003
[30000] vif2.inc==00000004
[35000] intf1.inc==00000004
[40000] vif2.inc==00000005
[45000] intf1.inc==00000005
[50000] vif2.inc==00000006
[55000] intf1.inc==00000006
[60000] vif2.inc==00000007
[65000] intf1.inc==00000007
[70000] vif2.inc==00000008
[75000] intf1.inc==00000008
[80000] vif2.inc==00000009
[85000] intf1.inc==00000009
*-* All Finished *-*

View File

@ -11,7 +11,7 @@ import vltest_bootstrap
test.scenarios('vlt_all')
test.compile(make_main=False, v_flags2=["--exe", test.pli_filename])
test.compile(verilator_flags2=["--exe --main --timing"])
test.execute(expect_filename=test.golden_filename)

View File

@ -8,15 +8,11 @@ interface If;
logic [31:0] inc;
endinterface
module top (
clk,
inc1,
inc2
);
module top;
input clk;
input [31:0] inc1;
input [31:0] inc2;
logic clk = 0;
logic [31:0] inc1 = 0;
logic [31:0] inc2 = 0;
int cyc = 0;
If intf1();
@ -24,18 +20,41 @@ module top (
virtual If vif1 = intf1;
virtual If vif2 = intf2;
assign vif1.inc = inc1;
// assign vif1.inc = inc1;
always @(posedge clk) begin
vif1.inc <= inc1;
end
assign intf2.inc = inc2;
always @(posedge clk) begin
cyc <= cyc + 1;
if (cyc >= 8) begin
if (cyc >= 10) begin
$write("*-* All Finished *-*\n");
$finish;
end
end
always_comb $write("[%0t] intf1.inc==%0h\n", $time, intf1.inc);
always_comb $write("[%0t] vif2.inc==%0h\n", $time, vif2.inc);
always @(intf1.inc) begin
$write("[%0t] intf1.inc==%h\n", $time, intf1.inc);
end
always @(vif2.inc) begin
$write("[%0t] vif2.inc==%h\n", $time, vif2.inc);
end
initial begin
repeat (30) #5ns clk = ~clk;
end
initial begin
inc1 = 1;
inc2 = 1;
repeat (8) begin
#10ns;
inc1 = inc1 + 1;
inc2 = inc2 + 1;
end
end
endmodule

View File

@ -1,21 +1,10 @@
[0] intf1.data==0000
[0] intf2.data==0000
[0] vif3.data==0000
[0] intf2.data==0000
[10] intf2.data==0000
[10] vif3.data==0000
[20] intf1.data==dead
[20] intf2.data==0000
[20] vif3.data==0000
[30] intf2.data==dead
[30] vif3.data==0000
[40] intf1.data==beef
[40] intf2.data==dead
[40] vif3.data==0000
[50] intf2.data==beef
[50] vif3.data==0000
[60] intf2.data==beef
[60] vif3.data==face
[70] intf2.data==beef
[70] vif3.data==cafe
[15000] intf1.data==dead
[30000] intf2.data==dead
[35000] intf1.data==beef
[50000] intf2.data==beef
[55000] vif3.data==face
[65000] vif3.data==cafe
*-* All Finished *-*

View File

@ -11,7 +11,7 @@ import vltest_bootstrap
test.scenarios('simulator')
test.compile()
test.compile(verilator_flags2=["--exe --main --timing"])
test.execute(expect_filename=test.golden_filename)

View File

@ -16,10 +16,9 @@ interface Bus3;
logic [15:0] data;
endinterface
module t (
clk
);
input clk;
module t;
logic clk = 0;
integer cyc = 0;
Bus1 intf1();
Bus2 intf2();
@ -29,7 +28,10 @@ module t (
virtual Bus3 vif3 = intf3;
logic [15:0] data;
assign vif2.data = data;
// assign vif2.data = data;
always @(negedge clk) begin
vif2.data <= data;
end
always @(posedge clk) begin
cyc <= cyc + 1;
@ -54,7 +56,18 @@ module t (
$finish;
end
always_comb $write("[%0t] intf1.data==%h\n", $time, intf1.data);
always_comb $write("[%0t] intf2.data==%h\n", $time, intf2.data);
always_comb $write("[%0t] vif3.data==%h\n", $time, vif3.data);
always @(intf1.data) begin
$write("[%0t] intf1.data==%h\n", $time, intf1.data);
end
always @(intf2.data) begin
$write("[%0t] intf2.data==%h\n", $time, intf2.data);
end
always @(vif3.data) begin
$write("[%0t] vif3.data==%h\n", $time, vif3.data);
end
initial begin
repeat (20) #5ns clk = ~clk;
end
endmodule

View File

@ -1,29 +1,11 @@
vif1.data==dead
intf2.data==0000
vif1.data==dead
intf2.data==0000
intf2.data==beef
vif1.data==dead
intf2.data==beef
intf2.data==beef
vif1.data==cafe
intf2.data==beef
intf2.data==face
vif1.data==cafe
intf2.data==face
intf2.data==face
vif1.data==feed
intf2.data==face
intf2.data==deed
vif1.data==feed
intf2.data==deed
intf2.data==deed
vif1.data==deaf
intf2.data==deed
intf2.data==fafa
vif1.data==deaf
intf2.data==fafa
intf2.data==fafa
vif1.data==bebe
intf2.data==fafa
[0] vif1.data==dead
[0] intf2.data==0000
[1] intf2.data==beef
[2] vif1.data==cafe
[3] intf2.data==face
[4] vif1.data==feed
[5] intf2.data==deed
[6] vif1.data==deaf
[7] intf2.data==fafa
[8] vif1.data==bebe
*-* All Finished *-*

View File

@ -37,6 +37,11 @@ module t;
$finish;
end
always_comb if ($time < 9) $write("vif1.data==%h\n", vif1.data);
always_comb if ($time < 9) $write("intf2.data==%h\n", intf2.data);
always @(vif1.data) begin
if ($time < 9) $write("[%0t] vif1.data==%h\n", $time, vif1.data);
end
always @(intf2.data) begin
if ($time < 9) $write("[%0t] intf2.data==%h\n", $time, intf2.data);
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,45 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by PlanV GmbH.
// SPDX-License-Identifier: CC0-1.0
`timescale 1ns/1ps
interface INTF;
logic x;
logic y;
logic z;
endinterface
class Dummy;
virtual INTF vif;
function new(virtual INTF vif);
this.vif = vif;
endfunction
endclass
module t_virtual_interface_member_trigger();
logic s1, src_val;
logic s2;
INTF vintf();
assign vintf.x = s1;
assign vintf.y = src_val;
assign vintf.z = !vintf.y;
assign s2 = vintf.z;
assign s1 = s2;
Dummy d;
initial begin
d = new(vintf);
#1ns;
src_val = 0;
#1ns;
if (!(d.vif.x == 1 && d.vif.y == 0 && d.vif.z == 1 && s1 == 1 && s2 == 1)) $stop;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule