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

class bus_item extends uvm_sequence_item;
  `uvm_object_utils(bus_item)
  bit write;
  int unsigned address;
  int unsigned data;
  function new(string name = "bus_item"); super.new(name); endfunction
endclass

class base_object extends uvm_object;
  `uvm_object_utils(base_object)
  function new(string name = "base_object"); super.new(name); endfunction
endclass

class derived_object extends base_object;
  `uvm_object_utils(derived_object)
  function new(string name = "derived_object"); super.new(name); endfunction
endclass

class probe_callback extends uvm_callback;
  int calls;
  function new(string name = "probe_callback"); super.new(name); endfunction
  virtual function void observed(); calls++; endfunction
endclass

class bus_driver extends uvm_driver#(bus_item);
  `uvm_component_utils(bus_driver)
  `uvm_register_cb(bus_driver, probe_callback)
  int unsigned memory[int unsigned];
  int transfers;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  task run_phase(uvm_phase phase);
    bus_item request;
    forever begin
      seq_item_port.get_next_item(request);
      if (request.write) memory[request.address] = request.data;
      else request.data = memory.exists(request.address) ? memory[request.address] : 0;
      transfers++;
      `uvm_do_callbacks(bus_driver, probe_callback, observed())
      $display("REVIEW_BUS|%0d|%0h|%08h", request.write, request.address, request.data);
      seq_item_port.item_done();
    end
  endtask
endclass

class register_adapter extends uvm_reg_adapter;
  `uvm_object_utils(register_adapter)
  function new(string name = "register_adapter"); super.new(name); endfunction
  function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
    bus_item item = bus_item::type_id::create("item");
    item.write = rw.kind == UVM_WRITE;
    item.address = rw.addr;
    item.data = rw.data;
    return item;
  endfunction
  function void bus2reg(uvm_sequence_item bus, ref uvm_reg_bus_op rw);
    bus_item item;
    if (!$cast(item, bus)) `uvm_fatal("ADAPTER", "Wrong bus item")
    rw.kind = item.write ? UVM_WRITE : UVM_READ;
    rw.addr = item.address;
    rw.data = item.data;
    rw.status = UVM_IS_OK;
  endfunction
endclass

class wide_register extends uvm_reg;
  `uvm_object_utils(wide_register)
  uvm_reg_field field;
  function new(string name = "wide_register"); super.new(name, 64, UVM_NO_COVERAGE); endfunction
  function void build();
    field = uvm_reg_field::type_id::create("field");
    field.configure(this, 64, 0, "RW", 0, 0, 1, 1, 0);
  endfunction
endclass

class register_block extends uvm_reg_block;
  `uvm_object_utils(register_block)
  wide_register value;
  function new(string name = "register_block"); super.new(name, UVM_NO_COVERAGE); endfunction
  function void build();
    value = wide_register::type_id::create("value");
    value.configure(this);
    value.build();
    default_map = create_map("map", 'h100, 4, UVM_LITTLE_ENDIAN, 1);
    default_map.add_reg(value, 'h20, "RW");
    lock_model();
  endfunction
endclass

class child_sequence extends uvm_sequence#(bus_item);
  `uvm_object_utils(child_sequence)
  static int parent_ok;
  function new(string name = "child_sequence"); super.new(name); endfunction
  task body();
    bus_item item = bus_item::type_id::create("item");
    parent_ok = get_parent_sequence() != null;
    start_item(item);
    item.write = 1; item.address = 'h40; item.data = 'hcafe;
    finish_item(item);
  endtask
endclass

class parent_sequence extends uvm_sequence#(bus_item);
  `uvm_object_utils(parent_sequence)
  function new(string name = "parent_sequence"); super.new(name); endfunction
  task body();
    child_sequence child = child_sequence::type_id::create("child");
    child.start(m_sequencer, this);
  endtask
endclass

class automatic_sequence extends uvm_sequence#(bus_item);
  `uvm_object_utils(automatic_sequence)
  static int runs;
  function new(string name = "automatic_sequence"); super.new(name); endfunction
  task body(); runs++; endtask
endclass

class legacy_sequence extends uvm_sequence#(bus_item);
  `uvm_object_utils(legacy_sequence)
  static int runs;
  function new(string name = "legacy_sequence"); super.new(name); endfunction
  task body(); runs++; endtask
endclass

class matrix_test extends uvm_test;
  `uvm_component_utils(matrix_test)
  uvm_sequencer#(bus_item) sequencer, wrapper_sequencer, instance_sequencer, legacy_sequencer;
  bus_driver driver;
  probe_callback callback;
  register_block model;
  register_adapter adapter;
  string phases;
  int config_value;
  bit factory_ok;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void build_phase(uvm_phase phase);
    base_object created;
    derived_object derived;
    automatic_sequence instance_sequence;
    super.build_phase(phase);
    phases = "build";
    uvm_config_db#(int)::set(this, "", "value", 42);
    if (!uvm_config_db#(int)::get(this, "", "value", config_value))
      `uvm_fatal("CONFIG", "Missing config")
    base_object::type_id::set_type_override(derived_object::get_type());
    created = base_object::type_id::create("created");
    factory_ok = $cast(derived, created);
    sequencer = new("sequencer", this);
    wrapper_sequencer = new("wrapper_sequencer", this);
    instance_sequencer = new("instance_sequencer", this);
    legacy_sequencer = new("legacy_sequencer", this);
    driver = bus_driver::type_id::create("driver", this);
    callback = new();
    uvm_callbacks#(bus_driver, probe_callback)::add(driver, callback);
    model = register_block::type_id::create("model");
    model.build();
    adapter = register_adapter::type_id::create("adapter");
    uvm_config_db#(uvm_object_wrapper)::set(this, "wrapper_sequencer.main_phase", "default_sequence", automatic_sequence::get_type());
    instance_sequence = automatic_sequence::type_id::create("instance_sequence");
    uvm_config_db#(uvm_sequence_base)::set(this, "instance_sequencer.main_phase", "default_sequence", instance_sequence);
    uvm_config_db#(string)::set(this, "legacy_sequencer", "default_sequence", "legacy_sequence");
  endfunction
  function void connect_phase(uvm_phase phase);
    phases = {phases, ">connect"};
    driver.seq_item_port.connect(sequencer.seq_item_export);
    model.default_map.set_sequencer(sequencer, adapter);
    model.default_map.set_auto_predict(1);
  endfunction
  task run_phase(uvm_phase phase);
    parent_sequence sequence_value;
    uvm_status_e status;
    uvm_reg_data_t data;
    phase.raise_objection(this);
    phases = {phases, ">run"};
    sequence_value = parent_sequence::type_id::create("sequence_value");
    sequence_value.start(sequencer);
    model.value.write(status, 64'h01234567_89abcdef, UVM_FRONTDOOR, model.default_map);
    $display("REVIEW|reg_write_status|%0d", status);
    model.value.read(status, data, UVM_FRONTDOOR, model.default_map);
    $display("REVIEW|reg_read_status|%0d", status);
    $display("REVIEW|reg_read_data|%016h", data);
    $display("REVIEW|reg_mirror_data|%016h", model.value.get_mirrored_value());
    #10;
    $display("REVIEW|objection_total|%0d", phase.get_objection().get_objection_total());
    phase.drop_objection(this);
  endtask
  function void extract_phase(uvm_phase phase);
    phases = {phases, ">extract"};
  endfunction
  function void report_phase(uvm_phase phase);
    phases = {phases, ">report"};
    $display("REVIEW|phase_order|%s", phases);
    $display("REVIEW|config_value|%0d", config_value);
    $display("REVIEW|factory_override|%0d", factory_ok);
    $display("REVIEW|sequence_parent|%0d", child_sequence::parent_ok);
    $display("REVIEW|phase_default_sequences|%0d", automatic_sequence::runs);
    $display("REVIEW|legacy_default_sequence|%0d", legacy_sequence::runs);
    $display("REVIEW|callback_calls|%0d", callback.calls);
    $display("REVIEW|bus_transfers|%0d", driver.transfers);
    $display("REVIEW|done|1");
  endfunction
endclass

module feature_matrix;
  initial run_test("matrix_test");
  initial begin #10000; $fatal(1, "Review timeout"); end
endmodule
