sample code

tb_top.sv

module tb_top;
  logic clk, rstz;
  logic req,gnt,trans,write,ready;
  logic [31:0] addr,wdata,rdata;
  master_bfm master (
    .clk, .rstz, .req, .gnt, .trans, .write,
    .addr, .wdata, .rdata, .ready
  );
  slave_bfm slave (
    .clk, .rstz, .trans, .write,
    .addr, .wdata, .rdata, .ready
  );
  task gen_clk;
    forever begin
      #50 clk <= ~clk;
    end
  endtask
  task gen_gnt;
    forever begin
      while(req!==1'b1) @(posedge clk);
      repeat (2) @(posedge clk);
      gnt <= 1'b1;
      @(posedge clk);
      gnt <= 1'b0;
      @(posedge clk);
    end
  endtask
  initial begin
    bit [31:0] this_rdata;
    $display("run start");
    clk  <= 1'b1;
    rstz <= 1'b1;
    gnt  <= 1'b0;
    #100 fork
           gen_clk;
           gen_gnt;
         join_none
    #20  rstz <= 1'b0;
    #100;
    -> master.go_e;
  end
endmodule

master_bfm.sv

module master_bfm (
  input  logic clk, rstz,
  output logic req,
  input  logic gnt,
  output logic trans,
  output logic write,
  output logic [31:0] addr,
  output logic [31:0] wdata,
  input  logic [31:0] rdata,
  input  logic ready
);
/// clk   ~~|_____|~~~~~|_____|~~~~~|_____|~~~~~|_____|~~~~~|_____|~~~~~
/// req   ________|~~~~~~~~~~~~~~~~~~~~~~~|_____________________________
/// gnt   ____________________|~~~~~~~~~~~|_____________________________
/// trans ________________________________|~~~~~~~~~~~~~~~~~~~~~~~|_____
/// write ________________________________|~~~~~~~~~~~~~~~~~~~~~~~|_____
/// wdata XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX_______________________XXXXXX
/// ready XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX___________|~~~~~~~~~~~XXXXXX
  class transaction;
    bit cmd_type;  /// 1:write, 0:read
    bit [31:0] addr;
    bit [31:0] data;
  endclass
  /// Define for DPI-C ////////////////////////////
  export "DPI-C" task write_issue;
  export "DPI-C" task read_issue;
  export "DPI-C" task wait_cycle;
  import "DPI-C" context task test();
  /////////////////////////////////////////////////
  typedef mailbox #(transaction) t_mbx;
  t_mbx mbx_trans_req;
  t_mbx mbx_trans_dat;
  t_mbx mbx_trans_end;
  event go_e;
  /// for export task (DPI-C)
  task write_issue(int unsigned this_addr, int unsigned this_data);
    transaction this_item;
    this_item = new;
    this_item.cmd_type = 1'b1;
    this_item.addr     = this_addr;
    this_item.data     = this_data;
    $display("[%0d] write cmd. addr=%08xh, data=%08xh",$time,this_addr,this_data);
    mbx_trans_req.put(this_item);
    mbx_trans_end.get(this_item);
  endtask
  task read_issue(int unsigned this_addr);
    transaction this_item;
    bit [31:0] this_data;
    this_item = new;
    this_item.cmd_type = 1'b0;
    this_item.addr     = this_addr;
    $display("[%0d] read  cmd. addr=%08xh",$time,this_addr);
    mbx_trans_req.put(this_item);
    mbx_trans_end.get(this_item);
    this_data = this_item.data;
    $display("[%0d] read data. data=%08xh",$time,this_data);
  endtask
  task wait_cycle(int val);
    repeat (val) @(posedge clk);
  endtask
  /// for internal task (Driver)
  task req_driver;
    transaction this_item;
    forever begin
      mbx_trans_req.get(this_item);
      @(posedge clk);
      req <= 1'b1;
      @(posedge clk);
      while(gnt!==1'b1) @(posedge clk);
      req <= 1'b0;
      mbx_trans_dat.put(this_item);
    end
  endtask
  task dat_driver;
    transaction this_item;
    forever begin
      mbx_trans_dat.get(this_item);
      trans <= 1'b1;
      write <= this_item.cmd_type;
      addr  <= this_item.addr;
      if(this_item.cmd_type==1'b1)begin
        wdata <= this_item.data;
        //$display("[%0d]a write=%b", $time, write);
      end
      while(ready===1'b0) @(posedge clk);
      if(this_item.cmd_type==1'b0)begin
        this_item.data = rdata;
      end
      trans <= 1'b0;
      mbx_trans_end.put(this_item);
    end
  endtask
  task bus_reset;
    req   <= 1'b0;
    trans <= 1'b0;
    write <= 1'b0;
    addr  <= 32'h0000_0000;
    wdata <= 32'h0000_0000;
  endtask
  initial begin
    mbx_trans_req = new;
    mbx_trans_dat = new;
    mbx_trans_end = new;
    wait(rstz===1'b1);
    bus_reset;
    wait(rstz===1'b0);
    @(posedge clk);
    fork
      req_driver;
      dat_driver;
    join_none
  end
  initial begin
    @go_e;
    test();
    $finish;
  end
endmodule

slave_bfm.sv

module slave_bfm (
  input  logic clk,rstz,
  input  logic trans,
  input  logic write,
  input  logic [31:0] addr,
  input  logic [31:0] wdata,
  output logic [31:0] rdata,
  output logic ready
);

byte memory [int unsigned]; /// associative array

  function byte get_mem_data(logic [31:0] this_addr);
    if(memory.exists(this_addr))
      return(memory[this_addr]);
    else begin
      $display("[%0d] no written data. return random value...",$time);
      return($random);
    end
  endfunction
  task mem_access;
    byte wait_val;
    forever begin
      while(trans!==1'b1) @(posedge clk);
      wait_val = $urandom_range(0,3);
      repeat(wait_val) @(posedge clk);
      ready <= 1'b1;
      if(write===1'b1)begin
        memory[{addr[31:2],2'b00}] = wdata[7:0];
        memory[{addr[31:2],2'b01}] = wdata[15:8];
        memory[{addr[31:2],2'b10}] = wdata[23:16];
        memory[{addr[31:2],2'b11}] = wdata[31:24];
      end else begin
        rdata[7:0]   = get_mem_data({addr[31:2],2'b00});
        rdata[15:8]  = get_mem_data({addr[31:2],2'b01});
        rdata[23:16] = get_mem_data({addr[31:2],2'b10});
        rdata[31:24] = get_mem_data({addr[31:2],2'b11});
      end
      @(posedge clk);
      ready <= 1'b0;
      @(posedge clk);
    end
  endtask
  task reset_bus;
    rdata <= 32'h00000000;
    ready <= 1'b0;
  endtask
  initial begin
    reset_bus;
    wait(rstz===1'b1);
    mem_access;
  end
endmodule