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

class access_item extends uvm_sequence_item;
  `uvm_object_utils(access_item)
  bit write;
  int unsigned address, data;
  bit [3:0] byte_enable;
  uvm_status_e status;
  function new(string name="access_item"); super.new(name); endfunction
endclass

class access_driver extends uvm_driver#(access_item);
  `uvm_component_utils(access_driver)
  int unsigned memory[int unsigned];
  int transfers;
  bit responses, fail_access;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  task run_phase(uvm_phase phase);
    access_item item, response;
    forever begin
      seq_item_port.get_next_item(item);
      #1;
      item.status = fail_access ? UVM_NOT_OK : UVM_IS_OK;
      if (!fail_access) begin
        if (item.write) begin
          for (int i=0; i<4; i++)
            if (item.byte_enable[i]) memory[item.address][i*8+:8] = item.data[i*8+:8];
        end
        else item.data = memory.exists(item.address) ? memory[item.address] : 0;
      end
      transfers++;
      $display("REVIEW_BUS|%0d|%0h|%08h|%h|%0d", item.write, item.address, item.data, item.byte_enable, item.status);
      if (responses) begin
        response = new("response");
        response.set_id_info(item);
        response.write = item.write;
        response.address = item.address;
        response.data = item.data;
        response.status = item.status;
        response.byte_enable = item.byte_enable;
        seq_item_port.item_done(response);
      end
      else seq_item_port.item_done();
    end
  endtask
endclass

class access_adapter extends uvm_reg_adapter;
  `uvm_object_utils(access_adapter)
  function new(string name="access_adapter");
    super.new(name);
    supports_byte_enable = 1;
  endfunction
  function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
    access_item item = new("access");
    uvm_reg_data_t data = rw.data;
`ifdef REVIEW_UVM20
    if ($test$plusargs("BE_ADAPTER_COMPENSATION") && $test$plusargs("BIG_ENDIAN") && rw.kind == UVM_WRITE)
      data = {<< byte {data}};
`endif
    item.write = rw.kind inside {UVM_WRITE, UVM_BURST_WRITE};
    item.address = rw.addr;
    item.data = data;
    item.byte_enable = rw.byte_en;
    return item;
  endfunction
  function void bus2reg(uvm_sequence_item bus, ref uvm_reg_bus_op rw);
    access_item item;
    if (!$cast(item, bus)) `uvm_fatal("ACCESS", "Unexpected transaction type")
    rw.kind = item.write ? UVM_WRITE : UVM_READ;
    rw.addr = item.address;
    rw.data = item.data;
`ifdef REVIEW_UVM20
    if ($test$plusargs("BE_ADAPTER_COMPENSATION") && $test$plusargs("BIG_ENDIAN") && !item.write)
      rw.data = {<< byte {rw.data}};
`endif
    rw.byte_en = item.byte_enable;
    rw.status = item.status;
  endfunction
endclass

class byte_write_sequence extends uvm_sequence#(access_item);
  bit responses;
  int unsigned address;
  function new(string name="byte_write_sequence"); super.new(name); endfunction
  task body();
    access_item item = new("byte_write"), response;
    start_item(item);
    item.address = address;
    item.write = 1;
    item.data = 'h66;
    item.byte_enable = 4'b0001;
    finish_item(item);
    if (responses) get_response(response);
  endtask
endclass

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

class access_memory extends uvm_mem;
  function new(string name="memory"); super.new(name, 8, 32, "RW", UVM_NO_COVERAGE); endfunction
endclass

class access_block extends uvm_reg_block;
  `uvm_object_utils(access_block)
  access_register first, second;
  access_memory memory;
  function new(string name="access_block"); super.new(name, UVM_NO_COVERAGE); endfunction
  function void build();
    first = new("first"); second = new("second"); memory = new();
    first.configure(this); second.configure(this); memory.configure(this);
    first.build(); second.build();
    default_map = create_map("map", 'h100, 4,
      $test$plusargs("BIG_ENDIAN") ? UVM_BIG_ENDIAN : UVM_LITTLE_ENDIAN, 1);
    default_map.add_reg(first, 'h20, "RW");
    default_map.add_reg(second, 'h40, "RW");
    default_map.add_mem(memory, 'h80, "RW");
    lock_model();
  endfunction
endclass

class access_test extends uvm_test;
  `uvm_component_utils(access_test)
  uvm_sequencer#(access_item) sequencer;
  access_driver driver;
  access_adapter adapter;
  access_block model;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    sequencer = new("sequencer", this);
    driver = new("driver", this);
    adapter = new();
    driver.responses = $test$plusargs("RESPONSES");
    adapter.provides_responses = driver.responses;
    model = new(); model.build();
  endfunction
  function void connect_phase(uvm_phase phase);
    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);
    uvm_status_e status, status_a, status_b;
    uvm_reg_data_t value, values[] = '{64'h76543210, 64'h44556677};
    uvm_reg_data_t read_values[];
    int before_field;
    byte_write_sequence byte_write = new();
    phase.raise_objection(this);
    model.first.write(status, 64'h01234567_89abcdef);
    $display("REVIEW|write_status|%0d", status);
    before_field = driver.transfers;
    model.first.low_byte.write(status, 'h55);
    $display("REVIEW|field_status|%0d", status);
    $display("REVIEW|field_transfers|%0d", driver.transfers - before_field);
    model.first.read(status, value);
    $display("REVIEW|read_status|%0d", status);
    $display("REVIEW|field_preserved_data|%016h", value);
    $display("REVIEW|mirror|%016h", model.first.get_mirrored_value());
    driver.memory[$test$plusargs("BIG_ENDIAN") ? 'h120 : 'h124] = 'hdeadbeef;
    if ($test$plusargs("DIRECT_FIELD_WRITE")) begin
      byte_write.responses = driver.responses;
      byte_write.address = $test$plusargs("BIG_ENDIAN") ? 'h124 : 'h120;
      byte_write.start(sequencer);
      void'(model.first.low_byte.predict('h66, -1, UVM_PREDICT_WRITE));
    end
    else model.first.low_byte.write(status, 'h66);
    model.first.read(status, value);
    $display("REVIEW|adjacent_hardware_data|%016h", value);
    model.memory.burst_write(status, 1, values);
    $display("REVIEW|burst_write_status|%0d", status);
    read_values = new[2];
    model.memory.burst_read(status, 1, read_values);
    $display("REVIEW|burst_read_status|%0d", status);
    $display("REVIEW|burst0|%016h", read_values[0]);
    $display("REVIEW|burst1|%016h", read_values[1]);
    fork
      model.first.write(status_a, 64'h11112222_33334444);
      model.second.write(status_b, 64'haaaabbbb_ccccdddd);
    join
    $display("REVIEW|concurrent_status|%0d:%0d", status_a, status_b);
    model.first.read(status, value);
    $display("REVIEW|concurrent_first|%016h", value);
    model.second.read(status, value);
    $display("REVIEW|concurrent_second|%016h", value);
    driver.fail_access = 1;
    model.first.write(status, '1);
    $display("REVIEW|failed_write_status|%0d", status);
    $display("REVIEW|failed_write_mirror|%016h", model.first.get_mirrored_value());
    driver.fail_access = 0;
    $display("REVIEW|done|1");
    phase.drop_objection(this);
  endtask
endclass

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