`timescale 1ns/1ps
`include "uvm_macros.svh"
import uvm_pkg::*;

interface storage_if;
  bit [31:0] value;
endinterface

class storage_backdoor extends uvm_reg_backdoor;
  virtual storage_if vif;
  function new(string name = "storage_backdoor");
    super.new(name);
  endfunction
  virtual task write(uvm_reg_item rw);
    do_pre_write(rw);
    if (vif == null) rw.status = UVM_NOT_OK;
    else begin
      vif.value = rw.value[0];
      rw.status = UVM_IS_OK;
    end
    do_post_write(rw);
  endtask
  virtual function void read_func(uvm_reg_item rw);
    if (vif == null) rw.status = UVM_NOT_OK;
    else begin
      rw.value[0] = vif.value;
      rw.status = UVM_IS_OK;
    end
  endfunction
endclass

class storage_reg extends uvm_reg;
  uvm_reg_field value;
  function new(string name = "storage_reg");
    super.new(name, 32, UVM_NO_COVERAGE);
  endfunction
  function void build();
    value = new("value");
    value.configure(this, 32, 0, "RW", 0, 0, 1, 0, 1);
  endfunction
endclass

class storage_block extends uvm_reg_block;
  storage_reg rg;
  function new(string name = "storage_block");
    super.new(name, UVM_NO_COVERAGE);
  endfunction
  function void build();
    rg = new(); rg.configure(this); rg.build();
    default_map = create_map("map", 0, 4, UVM_LITTLE_ENDIAN, 1);
    default_map.add_reg(rg, 0, "RW");
    lock_model();
  endfunction
endclass

class backdoor_observer extends uvm_reg_cbs;
  int before_write, after_write, before_read, after_read;
  function new(string name = "observer"); super.new(name); endfunction
  virtual task pre_write(uvm_reg_item rw); before_write++; endtask
  virtual task post_write(uvm_reg_item rw); after_write++; endtask
  virtual task pre_read(uvm_reg_item rw); before_read++; endtask
  virtual task post_read(uvm_reg_item rw); after_read++; endtask
endclass

module backdoor_binding_probe;
  storage_if hw();
  initial begin
    storage_block model = new();
    storage_backdoor bd = new();
    backdoor_observer observer = new();
    uvm_status_e status;
    uvm_reg_data_t value;
    model.build();
    bd.vif = hw;
    model.rg.set_backdoor(bd);
    uvm_callbacks#(uvm_reg_backdoor, uvm_reg_cbs)::add(bd, observer);
    $display("REVIEW|backdoor_identity|%0d", model.rg.get_backdoor() == bd);
    model.rg.write(status, 'h12345678, UVM_BACKDOOR);
    if (status != UVM_IS_OK) `uvm_fatal("BACKDOOR", "Write failed")
    if (hw.value != 'h12345678) `uvm_fatal("BACKDOOR", "Hardware mismatch")
    $display("REVIEW|write_ok|%0d", status == UVM_IS_OK);
    $display("REVIEW|hardware|%08h", hw.value);
    // Discard observer counts for any read performed by the RAL write path.
    observer.before_read = 0; observer.after_read = 0;
    hw.value = 'h89abcdef;
    model.rg.read(status, value, UVM_BACKDOOR);
    if (status != UVM_IS_OK || value != 'h89abcdef)
      `uvm_fatal("BACKDOOR", "Read failed")
    $display("REVIEW|read_ok|%0d", status == UVM_IS_OK);
    $display("REVIEW|read_value|%08h", value[31:0]);
    $display("REVIEW|mirror|%08h", model.rg.get_mirrored_value());
    $display("REVIEW|write_callbacks|%0d:%0d", observer.before_write, observer.after_write);
    $display("REVIEW|read_callbacks|%0d:%0d", observer.before_read, observer.after_read);
    bd.vif = null;
    model.rg.read(status, value, UVM_BACKDOOR);
    $display("REVIEW|missing_interface_rejected|%0d", status == UVM_NOT_OK);
    $display("REVIEW|done|1");
    $finish;
  end
endmodule
