UVM has a builtin transaction recorder as part of uvm_transaciton. This is deepdive into how it works.

How to use transaction recorder Link to heading

First, The recording should be enabled. In this example, this is part of sequence body where enable_recording is called with stream name.

    pkt = apb_rw::type_id::create("apb_rw");
    pkt.enable_recording("packet_stream");
    start_item(pkt);
    pkt.randomize();
    finish_item(pkt);

Next, run_phase can call accpet_tr, begin_tr and end_tr to log the transactions in the stream.

  task run_phase(uvm_phase phase);
    forever begin
      seq_item_port.get_next_item(pkt);

      accept_tr(pkt);

      begin_tr(pkt);

      // driver signals
      //...

      end_tr(pkt);


      seq_item_port.item_done();
    end
  endtask

The output is file tr_db.log with streams and transactions.

  CREATE_STREAM @0 {NAME:packet_stream T:TVM SCOPE: STREAM:1}
BEGIN @0 {TXH:2 STREAM:1 NAME:apb_rw TIME=0  TYPE="Begin_End, Link" LABEL:"" DESC=""}
  SET_ATTR @0 {TXH:2 NAME:accept_time VALUE:0   RADIX:UVM_TIME BITS=64}
END @0 {TXH:2 TIME=0}
FREE @0 {TXH:2}
BEGIN @0 {TXH:3 STREAM:1 NAME:apb_rw TIME=0  TYPE="Begin_No_Parent, Link" LABEL:"" DESC=""}
  SET_ATTR @0 {TXH:3 NAME:accept_time VALUE:0   RADIX:UVM_TIME BITS=64}
END @0 {TXH:3 TIME=0}
FREE @0 {TXH:3}

uvm_component infrastructure Link to heading

Transaction recording methods are called in the components. They delegate the calls to uvm_transaction. For example, uvm_component::accept_tr calls tr.accept_tr

function void uvm_component::accept_tr (uvm_transaction tr,
                                        time accept_time=0);
  uvm_event e;
  tr.accept_tr(accept_time);
  do_accept_tr(tr);
  e = event_pool.get("accept_tr");
  if(e!=null)
    e.trigger();
endfunction

And uvm_component::begin_tr calls tr.begin_tr or tr.begin_child_tr depending the type of transaction.

  tr_h = 0;
  if(has_parent)
    link_tr_h = tr.begin_child_tr(begin_time, parent_handle);
  else
    link_tr_h = tr.begin_tr(begin_time);

uvm_transaction infrastructure Link to heading

In uvm_transaction, enable_recording does the following:

  • picks up the uvm_default_recorder if recorder is not passed.
  • creates stream in m_recorder
  • sets record_enable
  if (recorder == null)
    recorder = uvm_default_recorder;
  m_recorder = recorder;

  this.stream_handle = m_recorder.create_stream(stream, "TVM", scope);
  record_enable = 1;

And for begin_tr and other function, uvm_transaction delegates uvm_recoder.

    if(!has_parent)
      tr_handle = m_recorder.begin_tr("Begin_No_Parent, Link",
                    stream_handle, get_type_name(),"","",this.begin_time);
    else begin
      tr_handle = m_recorder.begin_tr("Begin_End, Link",
                    stream_handle, get_type_name(),"","",this.begin_time);
      if(parent_handle)
        m_recorder.link_tr(parent_handle, tr_handle, "child");
    end

uvm_recorder infrastructure Link to heading

Finally, The last doll of this programmatic “Russian dolls” uvm_recorder!

uvm_default_recorder is a singleton defined in uvm_object_globals.svh

uvm_recorder uvm_default_recorder = new();

And uvm_recoder calls $fdisplay to print to log file.

  virtual function integer begin_tr(string txtype,
                                     integer stream,
                                     string nm,
                                     string label="",
                                     string desc="",
                                     time begin_time=0);
    if (open_file()) begin
      m_handles[++handle] = 1;
      $fdisplay(file,"BEGIN @%0t {TXH:%0d STREAM:%0d NAME:%s TIME=%0t  TYPE=\"%0s\" LABEL:\"%0s\" DESC=\"%0s\"}",
        $time,handle,stream,nm,begin_time,txtype,label,desc);
      return handle;
    end
    return -1;
  endfunction