// 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
