// Copyright 2003 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
// Create stimulus and Drive the interface
class DriverStim;
  protected virtual example_if v_if;

  task run();
    bit[7:0] x;
    bit[7:0] y;

    v_if.reset();
    forever begin
      x++;
      y++;

      $display("[DriverStim] initiating calculation, x: %8b y: %8b", x, y);
      v_if.initiate_calculation(x, y);
    end
  endtask: run

  function void bind_if(virtual example_if v_if);
    this.v_if = v_if;
  endfunction: bind_if
endclass: DriverStim
// Monitor returns from interface and check them
class MonitorCheck;
  localparam NUM_TXNS = 10;
  protected virtual example_if v_if;

  task run();
    logic[8:0] result;
    int txns_received = 0;

    forever begin
      v_if.wait_for_result(result);
      $display(
        "[MonitorCheck] (%d) result %7b carry_out %1b",
        txns_received, result[7:0], result[8]
        );
      if(++txns_received == NUM_TXNS)  begin
        $write("*-* All Finished *-*\n");
        $finish();
      end
    end
  endtask: run

  function void bind_if(virtual example_if v_if);
    this.v_if = v_if;
  endfunction: bind_if
endclass: MonitorCheck

module example(
  input  logic clk,
  input  logic rstn,
  input  logic[7:0] x,
  input  logic[7:0] y,
  output logic[8:0] z
  );

  // 8 bit full adder
  always_ff @(posedge clk)
    if(!rstn) z <= '0;
    else      z <= x + y;
endmodule: example
// interfaces with the DUT

interface example_if();
  localparam CLK_FREQ_MHz = 400;
  localparam CLK_PERIOD   = 1/((CLK_FREQ_MHz * 1e6) * (1e-12));

  logic      clk;
  logic      rstn;
  logic[7:0] x;
  logic[7:0] y;
  logic[8:0] z;

  initial begin: clk_gen
    forever #(CLK_PERIOD/2) clk = !clk;
  end: clk_gen

  task reset();
    $display("reset called");
    rstn = 0;
    @(posedge clk);
    $display("clock tick");
    rstn = 1;
    @(posedge clk);
  endtask: reset

  event calc_clkd;
  task initiate_calculation(
    input logic[7:0] x_in,
    input logic[7:0] y_in
    );

    x = x_in;
    y = y_in;
    @(posedge clk);
    ->calc_clkd;
  endtask: initiate_calculation

  task wait_for_result(output logic[8:0] result);
    @(calc_clkd);
    result = z;
  endtask: wait_for_result
endinterface: example_if

module t;

  example_if example_if_inst();

  example DUT(
    .clk (example_if_inst.clk),
    .rstn(example_if_inst.rstn),
    .x   (example_if_inst.x),
    .y   (example_if_inst.y),
    .z   (example_if_inst.z)
    );


  initial begin: main
    DriverStim   driverStim = new();
    MonitorCheck monitorCheck = new();

    driverStim.bind_if(example_if_inst);
    monitorCheck.bind_if(example_if_inst);

    fork
      driverStim.run();
      monitorCheck.run();
    join_none
  end: main
endmodule: t
