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

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

class map_storage_backdoor extends uvm_reg_backdoor;
  virtual map_storage_if vif;
  function new(string name = "bd"); super.new(name); endfunction
  virtual task write(uvm_reg_item rw);
    do_pre_write(rw);
    vif.value = rw.value[0];
    rw.status = UVM_IS_OK;
    do_post_write(rw);
  endtask
  virtual function void read_func(uvm_reg_item rw);
    rw.value[0] = vif.value;
    rw.status = UVM_IS_OK;
  endfunction
endclass

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

class map_storage_block extends uvm_reg_block;
  map_storage_reg rg;
  function new(string name = "model"); super.new(name, UVM_NO_COVERAGE); endfunction
  function void build(bit mapped = 1);
    rg = new(); rg.configure(this); rg.build();
    if (mapped) begin
      default_map = create_map("map", 0, 4, UVM_LITTLE_ENDIAN, 1);
      default_map.add_reg(rg, 0, "RW");
    end
    lock_model();
  endfunction
endclass

class backdoor_map_observer extends uvm_report_catcher;
  int map_warnings;
  function new(string name = "observer"); super.new(name); endfunction
  virtual function action_e catch();
    if (get_severity() == UVM_WARNING && get_id() == "RegModel" &&
        uvm_is_match("*is not contained within map 'Backdoor'*", get_message()))
      map_warnings++;
    return THROW;
  endfunction
endclass

module backdoor_map_probe;
  map_storage_if hw();
  initial begin
    map_storage_block model = new();
    map_storage_block unmapped_model = new("unmapped_model");
    map_storage_backdoor bd = new();
    backdoor_map_observer observer = new();
    uvm_status_e status;
    uvm_reg_data_t value, full_value, mask, expected;
    string scenario_names[8] = '{"reg_null", "reg_pseudo", "field_null", "field_real",
                                "reg_unmapped", "reg_real", "mirror_pseudo", "field_via_reg"};
    model.build();
    unmapped_model.build(0);
    bd.vif = hw;
    model.rg.set_backdoor(bd);
    unmapped_model.rg.set_backdoor(bd);
    uvm_report_cb::add(null, observer);
    mask = '1;
    mask >>= $bits(uvm_reg_data_t) - model.rg.value.get_n_bits();

    for (int scenario = 0; scenario < 8; scenario++) begin
      hw.value = 'h12345678;
      void'(model.rg.predict(hw.value));
      void'(unmapped_model.rg.predict(hw.value));
      observer.map_warnings = 0;
      $display("PROBE|begin|%s", scenario_names[scenario]);
      case (scenario)
        0: model.rg.read(status, value, UVM_BACKDOOR);
        1: model.rg.read(status, value, UVM_BACKDOOR, uvm_reg_map::backdoor());
        2: model.rg.value.read(status, value, UVM_BACKDOOR);
        3: model.rg.value.read(status, value, UVM_BACKDOOR, model.default_map);
        4: unmapped_model.rg.read(status, value, UVM_BACKDOOR);
        5: model.rg.read(status, value, UVM_BACKDOOR, model.default_map);
        6: begin
          model.rg.mirror(status, UVM_CHECK, UVM_BACKDOOR, uvm_reg_map::backdoor());
          value = model.rg.get_mirrored_value();
        end
        7: begin
          model.rg.read(status, full_value, UVM_BACKDOOR);
          value = (full_value >> model.rg.value.get_lsb_pos()) & mask;
        end
      endcase
      expected = (scenario inside {2, 3, 7}) ? 'h56 : 'h12345678;
      if (status != UVM_IS_OK || value != expected)
        `uvm_fatal("BACKDOOR_MAP", "Read status/data mismatch")

      // This fixture has RW fields and no concurrent hardware writer.
      case (scenario)
        0, 6: model.rg.write(status, 'h89abcdef, UVM_BACKDOOR);
        1: model.rg.write(status, 'h89abcdef, UVM_BACKDOOR, uvm_reg_map::backdoor());
        2: model.rg.value.write(status, 'hab, UVM_BACKDOOR);
        3: model.rg.value.write(status, 'hab, UVM_BACKDOOR, model.default_map);
        4: unmapped_model.rg.write(status, 'h89abcdef, UVM_BACKDOOR);
        5: model.rg.write(status, 'h89abcdef, UVM_BACKDOOR, model.default_map);
        7: begin
          model.rg.read(status, full_value, UVM_BACKDOOR);
          if (status != UVM_IS_OK) `uvm_fatal("BACKDOOR_MAP", "RMW read failed")
          full_value = (full_value & ~(mask << model.rg.value.get_lsb_pos())) |
                       ((uvm_reg_data_t'('hab) & mask) << model.rg.value.get_lsb_pos());
          model.rg.write(status, full_value, UVM_BACKDOOR);
        end
      endcase
      expected = (scenario inside {2, 3, 7}) ? 'h1234ab78 : 'h89abcdef;
      if (status != UVM_IS_OK || hw.value != expected)
        `uvm_fatal("BACKDOOR_MAP", "Write status/hardware mismatch")
      if (scenario == 6) begin
        model.rg.mirror(status, UVM_CHECK, UVM_BACKDOOR, uvm_reg_map::backdoor());
        if (status != UVM_IS_OK) `uvm_fatal("BACKDOOR_MAP", "Mirror failed")
      end
      if (((scenario == 4) ? unmapped_model.rg.get_mirrored_value() :
                            model.rg.get_mirrored_value()) != expected)
        `uvm_fatal("BACKDOOR_MAP", "Mirror value mismatch")
      $display("REVIEW|%s_warnings|%0d", scenario_names[scenario], observer.map_warnings);
      $display("REVIEW|%s_data_ok|1", scenario_names[scenario]);
    end
    $display("REVIEW|done|1");
    $finish;
  end
endmodule
