From 9b99d9697fd631d46b7bdaf0713a4b9d82348138 Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Fri, 18 Jul 2025 15:07:31 +0200 Subject: [PATCH] Fix virtual interface member propagation (#6175) (#6184) --- src/V3Sched.cpp | 34 ++++- src/V3Sched.h | 2 +- .../t/t_virtual_interface_member_trigger.v | 58 ++++++-- ...interface_member_trigger_realistic_case.py | 18 +++ ..._interface_member_trigger_realistic_case.v | 138 ++++++++++++++++++ 5 files changed, 230 insertions(+), 20 deletions(-) create mode 100755 test_regress/t/t_virtual_interface_member_trigger_realistic_case.py create mode 100755 test_regress/t/t_virtual_interface_member_trigger_realistic_case.v diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp index 633d8fbf3..8e16c3479 100644 --- a/src/V3Sched.cpp +++ b/src/V3Sched.cpp @@ -104,6 +104,17 @@ void invertAndMergeSenTreeMap( for (const auto& pair : senTreeMap) result.emplace(pair.second, pair.first); } +AstSenTree* findTriggeredIface(const AstVarScope* vscp, + const VirtIfaceTriggers::IfaceSensMap& vifTrigged, + const VirtIfaceTriggers::IfaceMemberSensMap& vifMemberTriggered) { + const auto ifaceIt = vifTrigged.find(vscp->varp()->sensIfacep()); + if (ifaceIt != vifTrigged.end()) return ifaceIt->second; + for (const auto& memberIt : vifMemberTriggered) { + if (memberIt.first.m_ifacep == vscp->varp()->sensIfacep()) { return memberIt.second; } + } + return nullptr; +} + //============================================================================ // Code generation utility functions @@ -955,8 +966,10 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp, AstSenTree* const dpiExportTriggered = dpiExportTriggerVscp ? createTriggerSenTree(netlistp, trig.m_vscp, dpiExportTriggerIndex) : nullptr; - const auto& vifTriggered + const auto& vifTriggeredIco = virtIfaceTriggers.makeIfaceToSensMap(netlistp, firstVifTriggerIndex, trig.m_vscp); + const auto& vifMemberTriggeredIco + = virtIfaceTriggers.makeMemberToSensMap(netlistp, firstVifTriggerIndex, trig.m_vscp); // Create and Order the body function AstCFunc* const icoFuncp @@ -968,8 +981,9 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp, } if (varp->isWrittenByDpi()) out.push_back(dpiExportTriggered); if (vscp->varp()->sensIfacep()) { - const auto it = vifTriggered.find(vscp->varp()->sensIfacep()); - if (it != vifTriggered.end()) out.push_back(it->second); + AstSenTree* ifaceTriggered = findTriggeredIface( + vscp, vifTriggeredIco, vifMemberTriggeredIco); + out.push_back(ifaceTriggered); } }); splitCheck(icoFuncp); @@ -1371,6 +1385,8 @@ void schedule(AstNetlist* netlistp) { const auto& vifTriggeredAct = virtIfaceTriggers.makeIfaceToSensMap(netlistp, firstVifTriggerIndex, actTrig.m_vscp); + const auto& vifMemberTriggeredAct + = virtIfaceTriggers.makeMemberToSensMap(netlistp, firstVifTriggerIndex, actTrig.m_vscp); AstCFunc* const actFuncp = V3Order::order( netlistp, {&logicRegions.m_pre, &logicRegions.m_act, &logicReplicas.m_act}, trigToSenAct, @@ -1379,8 +1395,9 @@ void schedule(AstNetlist* netlistp) { if (it != actTimingDomains.end()) out = it->second; if (vscp->varp()->isWrittenByDpi()) out.push_back(dpiExportTriggeredAct); if (vscp->varp()->sensIfacep()) { - const auto sit = vifTriggeredAct.find(vscp->varp()->sensIfacep()); - if (sit != vifTriggeredAct.end()) out.push_back(sit->second); + AstSenTree* ifaceTriggered + = findTriggeredIface(vscp, vifTriggeredAct, vifMemberTriggeredAct); + out.push_back(ifaceTriggered); } }); splitCheck(actFuncp); @@ -1408,6 +1425,8 @@ void schedule(AstNetlist* netlistp) { : nullptr; const auto& vifTriggered = virtIfaceTriggers.makeIfaceToSensMap(netlistp, firstVifTriggerIndex, trigVscp); + const auto& vifMemberTriggered + = virtIfaceTriggers.makeMemberToSensMap(netlistp, firstVifTriggerIndex, trigVscp); const auto& timingDomains = timingKit.remapDomains(trigMap); AstCFunc* const funcp = V3Order::order( @@ -1417,8 +1436,9 @@ void schedule(AstNetlist* netlistp) { if (it != timingDomains.end()) out = it->second; if (vscp->varp()->isWrittenByDpi()) out.push_back(dpiExportTriggered); if (vscp->varp()->sensIfacep()) { - const auto sit = vifTriggered.find(vscp->varp()->sensIfacep()); - if (sit != vifTriggered.end()) out.push_back(sit->second); + AstSenTree* ifaceTriggered + = findTriggeredIface(vscp, vifTriggered, vifMemberTriggered); + out.push_back(ifaceTriggered); } }); diff --git a/src/V3Sched.h b/src/V3Sched.h index 5d197e5ca..03ff99a18 100644 --- a/src/V3Sched.h +++ b/src/V3Sched.h @@ -168,6 +168,7 @@ class VirtIfaceTriggers final { } }; +public: using IfaceMemberTrigger = std::pair; using IfaceMemberTriggerVec = std::vector; using IfaceMemberSensMap = std::map; @@ -179,7 +180,6 @@ class VirtIfaceTriggers final { IfaceMemberTriggerVec m_memberTriggers; IfaceTriggerVec m_ifaceTriggers; -public: void addMemberTrigger(const AstIface* ifacep, const AstVar* memberVarp, AstVarScope* triggerVscp) { m_memberTriggers.emplace_back(IfaceMember(ifacep, memberVarp), triggerVscp); diff --git a/test_regress/t/t_virtual_interface_member_trigger.v b/test_regress/t/t_virtual_interface_member_trigger.v index a6bf92d7f..593e78baa 100755 --- a/test_regress/t/t_virtual_interface_member_trigger.v +++ b/test_regress/t/t_virtual_interface_member_trigger.v @@ -10,6 +10,7 @@ interface INTF; logic x; logic y; logic z; + logic [7:0] data; endinterface class Dummy; @@ -17,28 +18,61 @@ class Dummy; function new(virtual INTF vif); this.vif = vif; endfunction + task write_data(logic [7:0] d); + vif.data = d; + endtask 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; + // === Part 1: logic trigger false loop test === + logic s1, s2, src_val; + INTF intf_loop(); + virtual INTF vif_loop; + assign intf_loop.x = s1; + assign intf_loop.y = src_val; + assign intf_loop.z = !intf_loop.y; + assign s2 = intf_loop.z; assign s1 = s2; - Dummy d; + // === Part 2: data transfer chain test === + logic [7:0] data; + INTF intf_read(); + INTF intf_write(); + assign intf_read.data = data; + assign data = intf_write.data; + virtual INTF vif_read, vif_write; + + Dummy cl_1, cl_2; initial begin - d = new(vintf); + + // Test 1: no false loop with member-level trigger + #1ns; + vif_loop = intf_loop; + cl_1 = new(vif_loop); #1ns; src_val = 0; #1ns; - if (!(d.vif.x == 1 && d.vif.y == 0 && d.vif.z == 1 && s1 == 1 && s2 == 1)) $stop; + if (!(cl_1.vif.x == 1 && cl_1.vif.y == 0 && cl_1.vif.z == 1 && s1 == 1 && s2 == 1)) $stop; + + // Test 2: write from module + #1ns; + vif_read = intf_read; + vif_write = intf_write; + #1ns; + vif_write.data = 8'hA5; + #1ns; + if (vif_read.data !== 8'hA5) $stop; + + // Test 3: write from class + #1ns; + cl_2 = new(vif_write); + #1ns; + cl_2.write_data(8'hB7); + #1ns; + if (vif_read.data !== 8'hB7) $stop; + + #5ns; $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_virtual_interface_member_trigger_realistic_case.py b/test_regress/t/t_virtual_interface_member_trigger_realistic_case.py new file mode 100755 index 000000000..bd059b0f2 --- /dev/null +++ b/test_regress/t/t_virtual_interface_member_trigger_realistic_case.py @@ -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() diff --git a/test_regress/t/t_virtual_interface_member_trigger_realistic_case.v b/test_regress/t/t_virtual_interface_member_trigger_realistic_case.v new file mode 100755 index 000000000..2674d7a0a --- /dev/null +++ b/test_regress/t/t_virtual_interface_member_trigger_realistic_case.v @@ -0,0 +1,138 @@ +// 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 clk; + logic [7:0] data; + logic valid; + logic ready; +endinterface + +time TA = 5ns; + +class intf_driver; + virtual INTF intf; + function new(virtual INTF intf); + this.intf = intf; + endfunction + + task cycle_start(); + #TA; + endtask + + task cycle_end(); + @(posedge intf.clk); + endtask + + task init_master(); + intf.data = '0; + intf.valid = 0; + endtask + + task init_slave(); + intf.ready = 0; + endtask + + task recv_data(output logic [7:0] data); + intf.ready <= #TA 1; + cycle_start(); + while (!(intf.valid && intf.ready)) begin cycle_end(); cycle_start(); end + cycle_end(); + data = intf.data; + intf.ready <= #TA 0; + endtask + + task send_data(input logic [7:0] data); + intf.data <= #TA data; + intf.valid <= #TA 1; + cycle_start(); + while (!(intf.valid && intf.ready)) begin cycle_end(); cycle_start(); end + cycle_end(); + intf.valid <= #TA 0; + endtask +endclass + +module t_virtual_interface_member_trigger_realistic_case(); + logic clk; + logic [7:0] data; + logic valid; + logic ready; + logic [7:0] recv_data; + + INTF read_intf(); + assign read_intf.clk = clk; + assign read_intf.data = data; + assign read_intf.valid = valid; + assign ready = read_intf.ready; + + INTF write_intf(); + assign write_intf.clk = clk; + assign data = write_intf.data; + assign valid = write_intf.valid; + assign write_intf.ready = ready; + + intf_driver driver_master; + intf_driver driver_slave; + + virtual INTF vif_read = read_intf; + virtual INTF vif_write = write_intf; + + initial begin + repeat(1000) begin + clk = '1; + #10ns; + clk = '0; + #10ns; + end + end + + initial begin + driver_master = new(vif_write); + driver_slave = new(vif_read); + + driver_master.init_master(); + driver_slave.init_slave(); + fork + begin + #35ns; + driver_master.send_data(8'h42); + // $display("[%0d]: Write data: %02x", $time, write_intf.data); + #10ns; + driver_master.send_data(8'h43); + // $display("[%0d]: Write data: %02x", $time, write_intf.data); + #10ns; + driver_master.send_data(8'h44); + // $display("[%0d]: Write data: %02x", $time, write_intf.data); + end + begin + #10ns; + driver_slave.recv_data(recv_data); + // $display("[%0d]: Got data: %02x", $time, recv_data); + if (recv_data !== 8'h42) $stop; + #5ns; + driver_slave.recv_data(recv_data); + // $display("[%0d]: Got data: %02x", $time, recv_data); + if (recv_data !== 8'h43) $stop; + #15ns; + driver_slave.recv_data(recv_data); + // $display("[%0d]: Got data: %02x", $time, recv_data); + if (recv_data !== 8'h44) $stop; + end + join + + $write("*-* All Finished *-*\n"); + $finish; + end + + // Dump waveforms + // initial begin + // $dumpfile("t_virtual_interface_member_trigger.vcd"); + // $dumpvars(0, t_virtual_interface_member_trigger); + // end + +endmodule