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

class serialized_leaf extends uvm_object;
  byte unsigned value;
  `uvm_object_utils_begin(serialized_leaf)
    `uvm_field_int(value, UVM_ALL_ON)
  `uvm_object_utils_end
  function new(string name="serialized_leaf"); super.new(name); endfunction
endclass

class serialized_packet extends uvm_object;
  byte unsigned tag;
  string label;
  int values[];
  serialized_leaf leaf;
  `uvm_object_utils_begin(serialized_packet)
    `uvm_field_int(tag, UVM_ALL_ON)
    `uvm_field_string(label, UVM_ALL_ON)
    `uvm_field_array_int(values, UVM_ALL_ON)
    `uvm_field_object(leaf, UVM_ALL_ON)
  `uvm_object_utils_end
  function new(string name="serialized_packet"); super.new(name); leaf = new(); endfunction
endclass

class serialization_catcher extends uvm_report_catcher;
  int seen[string];
  function new(); super.new("serialization_catcher"); endfunction
  function action_e catch();
    if (get_id() inside {"UVM/COMPARER/INT/BAD_SIZE", "UVM/PRINT/NO_FLUSH", "NULLCP", "OBJ/COPY"}) begin
      seen[get_id()]++;
      return CAUGHT;
    end
    return THROW;
  endfunction
endclass

class legacy_wire_packet extends uvm_object;
  `uvm_object_utils(legacy_wire_packet)
  serialized_packet payload;
  function new(string name="legacy_wire_packet"); super.new(name); payload = new(); endfunction
  function void do_pack(uvm_packer packer);
    packer.pack_field_int(payload.tag, 8);
    packer.pack_string(payload.label);
    packer.pack_field_int(payload.values.size(), 32);
    foreach (payload.values[i]) packer.pack_field_int(payload.values[i], 32);
    // The legacy object marker is four LSB-first bits, followed by the leaf value.
    for (int i=0; i<4; i++) packer.pack_field_int(i == 0 && payload.leaf != null, 1);
    if (payload.leaf != null) packer.pack_field_int(payload.leaf.value, 8);
  endfunction
  function void do_unpack(uvm_packer packer);
    int size, marker;
    payload.tag = packer.unpack_field_int(8);
    payload.label = packer.unpack_string();
    size = packer.unpack_field_int(32);
    if (size < 0 || size > 16) `uvm_fatal("WIRE", "Invalid fixture length")
    payload.values = new[size];
    foreach (payload.values[i]) payload.values[i] = packer.unpack_field_int(32);
    for (int i=0; i<4; i++) marker |= packer.unpack_field_int(1) << i;
    if (marker == 0) payload.leaf = null;
    else begin
      if (payload.leaf == null) payload.leaf = new();
      payload.leaf.value = packer.unpack_field_int(8);
    end
  endfunction
endclass

module serialization_probe;
  initial begin
    serialized_packet original = new("original"), restored = new("restored"), copied = new("copied");
    legacy_wire_packet wire_original = new(), wire_restored = new();
    uvm_tlm_generic_payload gp = new("gp"), restored_gp = new("restored_gp");
    serialization_catcher catcher = new();
    uvm_packer packer;
    uvm_comparer comparer = new();
    uvm_table_printer printer = new();
    byte unsigned data[] = '{8'h96, 8'h21};
    byte unsigned restored_data[];
    bit stream[];
    string stream_text, formatted;
    int count;
    bit equal;
`ifdef REVIEW_UVM20
    uvm_compat_packer legacy = new();
    packer = legacy;
`else
    packer = new();
`endif
    #1;
    uvm_report_cb::add(null, catcher);
    comparer.verbosity = UVM_DEBUG;
    original.tag = 'hc3; original.label = "UVM";
    original.values = '{17, 29}; original.leaf.value = 'h96;
`ifdef REVIEW_UVM20
    legacy.use_metadata = 1;
`else
    packer.use_metadata = 1;
`endif
    count = original.pack(stream, packer);
    stream_text = "";
    foreach (stream[i]) stream_text = {stream_text, stream[i] ? "1" : "0"};
    $display("REVIEW|packet_bits|%0d:%s", count, stream_text);
    count = restored.unpack(stream, packer);
    equal = original.compare(restored, comparer);
    $display("REVIEW|packet_roundtrip|%0d", equal);
    wire_original.payload = original;
    count = wire_original.pack(stream, packer);
    stream_text = "";
    foreach (stream[i]) stream_text = {stream_text, stream[i] ? "1" : "0"};
    $display("REVIEW|explicit_wire_bits|%0d:%s", count, stream_text);
    count = wire_restored.unpack(stream, packer);
    $display("REVIEW|explicit_wire_roundtrip|%0d", original.compare(wire_restored.payload, comparer));
    copied.copy(original);
    original.leaf.value = 'h55;
    $display("REVIEW|deep_copy_leaf|%0h", copied.leaf.value);

    gp.set_address('h1234); gp.set_write(); gp.set_data(data);
    gp.set_data_length(2); gp.set_streaming_width(2);
    gp.set_response_status(UVM_TLM_OK_RESPONSE); gp.set_dmi_allowed(1);
    count = gp.pack(stream, packer);
    $display("REVIEW|gp_compat_count|%0d", count);
    count = restored_gp.unpack(stream, packer);
    restored_gp.get_data(restored_data);
    $display("REVIEW|gp_roundtrip|%0h:%0h:%0h:%0d", restored_gp.get_address(), restored_data[0], restored_data[1], restored_gp.is_dmi_allowed());

    equal = comparer.compare_field_int("too_wide", 0, 0, 65);
    $display("REVIEW|bad_size_equal|%0d", equal);
    $display("REVIEW|bad_size_messages|%0d", catcher.seen["UVM/COMPARER/INT/BAD_SIZE"]);
    printer.print_field("value", 5, 8, UVM_HEX);
    formatted = printer.emit();
    formatted = printer.emit();
    $display("REVIEW|no_flush_messages|%0d", catcher.seen["UVM/PRINT/NO_FLUSH"]);
    copied.copy(null);
    $display("REVIEW|copy_null_legacy|%0d", catcher.seen["NULLCP"]);
    $display("REVIEW|copy_null_new|%0d", catcher.seen["OBJ/COPY"]);
    $display("REVIEW|done|1");
    $finish;
  end
endmodule
