verilator/test_regress/t/t_class_trigger_null.v

137 lines
3.7 KiB
Systemverilog

// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Cameron Waite
// SPDX-License-Identifier: CC0-1.0
// Test for null pointer dereference when sensitivity expressions reference
// class members. Verilator evaluates trigger expressions at simulation start,
// even for tasks that haven't been called yet. This caused null pointer
// dereference when accessing class_handle.event or class_handle.vif.signal
// because the task parameter didn't have a valid value yet.
//
// Also tests "instant fall-through" scenario: events triggered at time 0
// (same timestep as class construction) should still be detected.
interface my_if;
logic sig;
endinterface
package my_pkg;
class my_driver;
virtual my_if vif;
event my_event;
function new(virtual my_if vif_in);
vif = vif_in;
endfunction
endclass
// Task with sensitivity expressions on class member event and signals
task automatic my_task(my_driver drv, output int ev_cnt, pos_cnt, neg_cnt);
my_driver h = drv;
if (h == null) begin
$display("Error: drv is NULL!");
$finish;
end
fork
begin
// Wait on class member event - previously caused null deref
@(h.my_event);
ev_cnt++;
end
begin
// Wait on posedge through virtual interface - previously caused null deref
@(posedge h.vif.sig);
pos_cnt++;
end
begin
// Wait on negedge through virtual interface - previously caused null deref
@(negedge h.vif.sig);
neg_cnt++;
end
begin
#10;
->h.my_event;
#10;
h.vif.sig = 1;
#10;
h.vif.sig = 0;
end
join
endtask
endpackage
module t;
my_if intf ();
my_pkg::my_driver drv;
virtual my_if vif;
int event_count;
int posedge_count;
int negedge_count;
// Counter for instant (time 0) event detection
int instant_event_count = 0;
// Test "instant fall-through": sensitivity on class member event at module level
// This always block is evaluated at elaboration when drv is still null.
// After construction, events triggered at time 0 should still be detected.
always @(drv.my_event) begin
instant_event_count++;
end
// Construct the class at time 0
initial begin
vif = intf;
drv = new(vif);
intf.sig = 0;
end
// Trigger event at time 0 (same timestep as construction) - "instant fall-through"
// Use #0 to ensure construction happens first in delta cycle ordering
initial begin
#0;
->drv.my_event;
end
// Call the task later - trigger expressions were evaluated before this
initial begin
event_count = 0;
posedge_count = 0;
negedge_count = 0;
#20;
my_pkg::my_task(drv, event_count, posedge_count, negedge_count);
// Verify all triggers occurred
if (event_count != 1) begin
$display("%%Error: event_count = %0d, expected 1", event_count);
$stop;
end
if (posedge_count != 1) begin
$display("%%Error: posedge_count = %0d, expected 1", posedge_count);
$stop;
end
if (negedge_count != 1) begin
$display("%%Error: negedge_count = %0d, expected 1", negedge_count);
$stop;
end
// Verify instant fall-through worked (time 0 event was detected)
// We expect 2: one from time 0, one from the task at time 30
if (instant_event_count != 2) begin
$display("%%Error: instant_event_count = %0d, expected 2", instant_event_count);
$display("%%Error: Instant fall-through failed - events at time 0 were missed!");
$stop;
end
$write("*-* All Finished *-*\n");
$finish;
end
endmodule