Jump to content

Of begin_event, end_event, transaction recording, and pipelined interfaces


Recommended Posts

What is the proper use of begin_tr() and end_tr() and their associated events?  There are these very nice descriptions for begin_event and end_event.

 

  // Variable: begin_event
  //
  // A <uvm_event> that is triggered when this transaction's actual execution on the
  // bus begins, typically as a result of a driver calling <uvm_component::begin_tr>.

  // Processes that wait on this event will block until the transaction has
  // begun.
  //
  // For more information, see the general discussion for <uvm_transaction>.
  // See <uvm_event> for details on the event API.
  //
  uvm_event begin_event;

 

  // Variable: end_event
  //
  // A <uvm_event> that is triggered when this transaction's actual execution on
  // the bus ends, typically as a result of a driver calling <uvm_component::end_tr>.

  // Processes that wait on this event will block until the transaction has
  // ended.
  //
  // For more information, see the general discussion for <uvm_transaction>.
  // See <uvm_event> for details on the event API.
  //
  //| virtual task my_sequence::body();
  //|  ...
  //|  start_item(item);    \
  //|  item.randomize();     } `uvm_do(item)
  //|  finish_item(item);   /
  //|  // return from finish item does not always mean item is completed
  //|  item.end_event.wait_on();
  //|  ...
  //
  uvm_event end_event; 


Great! That is exactly what I want and would expect.  Then begin_tr has even more verbiage that reinforces that:


  // Function: begin_tr
  //
  // This function indicates that the transaction has been started and is not
  // the child of another transaction. Generally, a consumer component begins
  // execution of a transactions it receives.
  //
  // Typically a <uvm_driver> would call <uvm_component::begin_tr>, which
  // calls this method, before actual execution of a sequence item transaction.
  // Sequence items received by a driver are always a child of a parent sequence.
  // In this case, begin_tr obtains the parent handle and delegates to <begin_child_tr>.
  //
  // See <accept_tr> for more information on how the
  // begin-time might differ from when the transaction item was received.
  //
  // This function performs the following actions:
  //  blah blah good details here ...
  extern function integer begin_tr (time begin_time=0);

 

begin_child_tr() and end_tr() have similar verbiage.  Notice the lines I've highlighted.  Now let's go look at the sequence side of things.  According the the UVM user's guide, the basic execution flow of a transaction in a sequence is as follows (this is also what the uvm_do macro implements):

  1. Call start_item() to create the item via the factory. 
  2. Optionally call pre_do() or some other functionality.
  3. Optionally randomize item.
d) Optionally call mid_do() or some other functionality, if desired.
  4. Call finish_item().
  5. Optionally call post_do() or some other functionality.
  6. Optionally call get_response(). 

However, start_item() has this bit of code in it. 


    `ifndef UVM_DISABLE_AUTO_ITEM_RECORDING
      void'(sequencer.begin_child_tr(item, m_tr_handle, item.get_root_sequence_name()));
    `endif

 

Which in turn calls uvm_component::begin_tr().  *Screeeeeeeching halt sound*  Whaaat?  The function description for begin_tr() just said it is "typically called by a driver".  But, it turns out that begin_tr() is in fact typically called by the sequence executing the transaction (indirectly via the sequencer).  Well, it turns out that this is documented, but the documentation has a bit of a multiple personality disorder.  Here's a snip from the uvm_transaction class documentation:


//------------------------------------------------------------------------------
//
// CLASS: uvm_transaction
//
// ... blah blah blah ...
//
// The intended use of this API is via a <uvm_driver> to call <uvm_component::accept_tr>,
// <uvm_component::begin_tr>, and <uvm_component::end_tr>
during the course of
// sequence item execution. These methods in the component base class will
// call into the corresponding methods in this class to set the corresponding
// timestamps (accept_time, begin_time, and end_tr), trigger the
// corresponding event (<begin_event> and <end_event>, and, if enabled,
// record the transaction contents to a vendor-specific transaction database.

 

I like how the intended use model (as previously stated) was reiterated here.  Which is then followed by this single line, which finally gets around to stating how this thing actually works.


// Note that start_item/finish_item (or `uvm_do* macro) executed from a
// <uvm_sequence #(REQ,RSP)> will automatically trigger
// the begin_event and end_events via calls to begin_tr and end_tr.

 

Oh, so the default configuration of the UVM library is to not implement the intended methodology, and instead encourage bad behavior.  That's nice.  Now let's follow that up with this line trying to reiterate the intended use model again.


// While convenient, it is generally the responsibility of drivers to mark a
// transaction's progress during execution. 

 

Ok. got it. My drivers should trigger these events, not the sequences.  This has been repeatedly stated at least 4 times up to this point!  Surely it must be as simple as calling begin_tr/end_tr at the appropriate times in my driver and the start_item/finish_item methods will adapt accordingly, no? 


// To allow the driver to control
// sequence item timestamps, events, and recording, you must add
// +define+UVM_DISABLE_AUTO_ITEM_RECORDING when compiling the UVM package.

 

Oh, of course!.  Now that we've been repeatedly told what the intended use model is, we finally discover that to actually use it, we must set an obscurely named flag that seems to refer only to automatic transaction recording.  Gosh, why didn't I think of that first?


// Alternatively, users may use the transaction's event pool, <events>,
// to define custom events for the driver to trigger and the sequences to wait on. Any
// in-between events such as marking the begining of the address and data
// phases of transaction execution could be implemented via the
// <events> pool.

 

Alternatively? It seems to me, that given that it's the default configuration of the library, it is the primary method to do so. And it also happens to be the only method I've seen demonstrated in every EDA vendor and service provider training I've seen on the subject. It goes on to talk about exactly why you should implement things in the intended way.


// In pipelined protocols, the driver may release a sequence (return from
// finish_item() or it's `uvm_do macro) before the item has been completed.
// If the driver uses the begin_tr/end_tr API in uvm_component, the sequence can
// wait on the item's <end_event> to block until the item was fully executed,
// as in the following example.
//
//| task uvm_execute(item, ...);
//|     // can use the `uvm_do macros as well
//|     start_item(item);
//|     item.randomize();
//|     finish_item(item);
//|     item.end_event.wait_on();
//|     // get_response(rsp, item.get_transaction_id()); //if needed
//| endtask
//|
//

 

Yep.  That is indeed the problem I want to solve. If only UVM would get out of it's own way.  It looks like someone was tasked with adding automated transaction recording to UVM and they decided to hi-jack the begin_event and end_event in order to do so.  Except they discovered a lot of VIP (most VIP?) didn't bother to make the appropriate calls to begin_tr and end_tr.  So they said, "Why don't I make the calls for them!", and here we are.  Unfortunately it breaks using begin_event and end_event for anything except automatic transaction recording.

 

To build a "proper" agent, I'm supposed to call begin_tr and end_tr in my driver.  This means I have to use +define+UVM_DISABLE_AUTO_ITEM_RECORDING, and lose that feature.  On top of that, other VIP is not going to implement the calls to begin_tr and end_tr, because they are going to assume the sequence does it for them.  So if you were using those events with other VIP, your tests are now broke until that other VIP is updated.

 

Taking this VIP the other way doesn't work either.  Calling begin_tr/end_tr from your driver and using it in a testbench that doesn't have the +define will cause double triggering of the events.  Maybe that won't mess anything up, but it certainly could.

 

So I ask these questions:

▪   Do you have a pipelined interface in your design?

▪   Are you interested in when your transactions start and end?

▪   Do you utilize begin_event and end_event?

▪   If not, what do you do instead?

▪   Would you utilize begin_event and end_event if they worked properly?

▪   If the default behavior of the library was changed to not automatically trigger these events, would it break your existing tests?

▪   Does anyone use the automatic transaction recording?

 

I think this really needs to be fixed. It simply means creating a couple of events specifically for automatic transaction recording to use, rather than hijacking the begin/end events.  If the automatic recording could take advantage of the developer manually triggering the begin/end events for greater accuracy, that would be great.  But not doing so, would be no worse than we have now.

Link to comment
Share on other sites

Excellent description!

 

This has indeed been fixed and will be in the next release of UVM.

 

See http://www.eda.org/svdb/view.php?id=3586

 

The default transaction recording will be maintain since it works for the simple, in-order protocols, but a more complex protocol driver will be able to turn it off AT RUNTIME and then explicitly mark the transaction execution time points:

 

 

  task run_phase(uvm_phase phase);
    seq_item_port.disable_auto_item_recording();

    while(1) begin
      uvm_sequence_base pseq;
      seq_item_port.get_next_item(req);
      `uvm_info("Driver", "Received item :", UVM_MEDIUM)
      req.print();
      pseq = req.get_parent_sequence();
      accept_tr(req);
      #2;
      begin_child_tr(req, (pseq == null) ? 0 : pseq.get_tr_handle(),
                     req.get_root_sequence_name());


      #3;
      fork
        automatic simple_item tr = req;
        begin
          #12;
          `uvm_info("Driver", "Completed item :", UVM_MEDIUM)
          tr.print();
          end_tr(tr);
        end
      join_none
      seq_item_port.item_done();
    end
  endtask: run_phase

 

 

Link to comment
Share on other sites

  • 2 weeks later...

Brilliant! I am going to bookmark this thread for all of the times that users have given me grief over this just so that I can come back to this and laugh.

 

It is indeed nice that this will be fixed in 1.2.  Unfortunately the 1.0 and 1.1 codebase will live on at customer sites for a very long time, and so the confusion will live on as well.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...