137 lines
3.7 KiB
Systemverilog
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
|