Rich Edelman, Mentor Graphics
Fremont, CA US
This paper will summarize previous work about SystemVerilog  UVM  transaction recording, transaction modeling and the supporting transaction recording APIs. This discussion will span a wide spectrum, from simple concepts such as transaction begin and transaction end, to more advanced concepts such as relationships, “tags”, and other transaction attributes.
The SystemVerilog UVM contains a transaction modeling abstraction, and has the ability to record this transaction model using a vendor specific API. This transaction model and vendor specific saved database is very powerful for debug, performance analysis and modeling for communication.
Figure 1 - Layers of transaction recording
The UVM contains multiple layers of transaction modeling, including a transaction model in components, transactions and sequences. Furthermore, a uvm_recorder is the lowest level interface used for recording to the vendor specific database. In addition to the class based modeling abstractions, the UVM provides macros (commonly known as the field automation macros) to automate the recording of transaction attributes.
Unfortunately this layered transaction model is overly complicated, hard to understand, and confusing to use. It should be redesigned or at least re-architected. Due to the nature of the UVM development environment this kind of large development may either never occur, or may take a long time. So to be effective with transaction recording and modeling in the short to medium term, we will have to use the existing API, and learn to adapt to its limitations and quirks.
Transaction modeling and recording has a long history . Every year there are improvements to vendor tools, and to supporting libraries like the UVM. Transaction recording can be used for many things, including debug, coverage and traffic analysis.
Transactions are useful in many places of a verification environment, including drivers, tests, monitors, register models and C based code .
GENERAL TRANSACTION MODELING
The transaction model has streams, transactions on those streams, and attributes on those transactions .
Figure 2 - Streams, Transactions and Attributes
A stream is a collection of transactions, recorded over time. A stream has a name, and usually exists somewhere within the test bench hierarchy – for example a driver might have a stream which represents all the transactions that have occurred on that driver. The driver stream would exist in the driver. It would appear as a “member variable” for the driver.
A stream might also be created at the top level – a generic stream for example that contains all the error transactions from a test bench. Any error that is seen is immediately placed on the error stream at the top level. This top level error stream, when displayed provides a fast reference to any error conditions that exist.
A stream can also be created with a hierarchical name of its own, not mirroring any test bench structure. This kind of stream is useful for creating collections of streams which together represent data flow or some communication flow. The focus of the stream is the communication, instead of errors or on which component a transaction was executing.
Streams are often drawn as a row in a graphical waveform. Each stream occupies its own row. The transactions on a stream may overlap. When drawn, this can cause a confusing display; so many tools choose to draw overlapping transactions in different ways. One solution is to use “sub-streams” where each sub-stream contains non-overlapping transactions. Since each sub-stream is guaranteed to have no overlapping transactions, it can be drawn in a straightforward way.
Most of the time as simulation is progressing, transactions are produced which are overlapping. It is impossible for a test bench writer to know ahead of time whether transactions are going to overlap or not. Different random seeds can affect overlap. Different modes of protocols can affect overlap. A recording system which manages overlap will eliminate the difficulties with drawing overlapping transactions.
TRANSACTIONS AND ATTRIBUTES
After a stream has been created, transactions can be recorded. A transaction has a begin time and an end time. A transaction also has attributes (name-value pairs). A transaction may also have relationships with other transactions.
A transaction is “started” or begun on a stream. When the transaction has completed, it is “stopped” or ended on a stream.
This sequence of begin and end is what records a transaction. Once a transaction has begun, a transaction handle exists which represents that transaction. Over the course of a simulation many transaction handles will be created.
When a transaction handle is no longer needed, it is destroyed and free’d.
An attribute is added to a transaction handle. For example a READ transaction might have the ADDRESS attribute, with the value 0xffff0111.
Each transaction can have as many attributes as needed. The attributes have a simple string name, and a value. The attribute value can be any legal SystemVerilog data type, including user defined types like packed structs or arrays of arrays.
RELATIONS AND TAGS
Transactions can have relationships with each other. A relationship is very simple. It is a simple string name that associates two handles. A example transaction relationship is: transaction A is a parent of transaction B.
Figure 3 - Transactions with relations
There are well known relationships, but generally any relationship can be captured. Two sets of well-known relationships are the parent/child relationship and the predecessor/successor relationship.
A relationship exists between two transactions, but as a special case, a relationship can also be a “tag” that is placed on one transaction. For example if an error transaction could be tagged with the error relationship.
It is a relation on itself, and will be useful as a marker either in later analysis or in later display. For example, any transaction that has an error tag might be colored red when it is drawn.
Tags are very useful to group transactions into arbitrary collections.
UVM Component and UVM Transaction
The UVM component and UVM transaction both contain recording APIs. These APIs are inter-related.
Appendix  contains some component API snippets, and Appendix  contains some transaction API snippets. Please refer to the UVM source code for the complete implementations.
Each of the APIs (component and transaction) suffer similar problems. They are overly complicated with many assumptions and behaviors that are inter-locked with other features.
For example, the component API begin_tr() contains code to check whether this component is a sequencer. If so, then some parent_handle code is executed. There is no easy way to specify parent handles or create hierarchy – except through using sequences. Additionally, this component is a “base” class, and should not know anything about sequencers (which are extended from components).
In addition, the begin_tr() code and end_tr() code in the component and the transaction both do recording, as well as event triggering. This mix of recording and event triggering should be removed.
The uvm_transaction::begin_tr has a flag called record_enable, which is never turned on by any code in the UVM. Furthermore, the uvm_component begin_tr calls the uvm_transaction begin_tr to create “link” transactions, which appear to never be ended, or free’d, resulting in a large memory leak if record_enable is ever turned on.
Unfortunately both the UVM Transaction and the UVM Component recording interfaces are problematic. They conflate recording transactions with event notification, and transaction completeness. They merge beginning a transaction with ending a transaction (UVM Transaction begin_tr may call end_tr). They have unwritten assumptions – about existence of streams, about parent handles, about transaction types. These deficiencies and interlocking conflated functionality require a new implementation that fixes these historic issues. Unfortunately, this kind of change may be long in the making. There are current proposals for fixing transaction recording in the Accellera UVM committee . The improvements may come quickly and may be backward compatible.
Before the improvements can be implemented, the transaction recording API is still quite useful, and quite usable, but does require some planning, and does require that you use it in a new way.
In the UVM, a sequence is a widely used stimulus generation technique. A sequence is really a function call . It is a function call which may cause a transaction on a driver, which in turn causes a transaction on the bus. So a sequence can be thought of as a function call which causes a bus transaction.
In the UVM a sequence is implemented as a functor which is started by calling a “start” routine [Figure 4]. This start routine has been instrumented [Figure 5] in the UVM so that at the beginning of start a transaction is begun, and at the end of start, a transaction is ended. Appendix  contains a snippet of the uvm_sequence::start routine.
Figure 4 - Starting a sequence
Figure 5 - uvm_sequence::start pseudocode
This automatic calling of begin and end is the automation of transaction recording that exists in the UVM. This automation is quite handy, and useful, but its handiness and usefulness are diminished by the fact that many other places in the test bench or the UVM have transactions which may need to be recorded, but since no automation exists, the test bench writer is left to manage himself.
The attributes of a transaction can be recorded in many ways; the easiest way is to implement a function called do_record which will be called by the recording mechanism. Do_record is responsible for recording the meaningful attributes of the transaction.
The do_record routine can be auto-generated by the field automation macros or the user can write it as in Figure 6.
Figure 6 - do_record
The macro uvm_record_attribute is a simple call to a PLI routine that will support recording actual types.
Figure 7 - Type specific recording
Field automation – just say no
“Field Automation” is the automation of collecting information about the fields (or attributes) of a transaction. These macros are used to provide a kind of primitive introspection and to generate code automatically.
The field automation macros can be used to specify that a certain class member variable – a transaction attribute – should be printed, or should be recorded, or should have an initial value.
While these concepts are a good idea, the macro implementation and SystemVerilog limitations have made the field automation macros a non-recommended feature. The field automation macros should be avoided. They are easy to use in the beginning, but are quite difficult to debug, and are a proven performance problem . Although field automation macros are not to be used some of the automation machine can still be used – for example implementing do_record.
The UVM contains a recording policy class – a class which implements a recording policy. The recording policy is the lower level API that the UVM uses. The UVM transaction contains a handle to a recorder. When the UVM transaction begin_tr() is called, it in turn calls “recorder.begin_tr()” to actually perform the recording of a transaction begin.
The uvm_recorder API is listed in Appendix . It suffers from losing data type information, as well as being tied to the way the transaction handle is managed from the component level.
The existing UVM recording API relies on SystemVerilog function calls to record data values, but SystemVerilog function calls do not support function overloading – the ability to have a “f(int i) and an f(double d)”. This means that using function calls requires one function per data type, or the data type has to be converted to a common data type. Unfortunately the existing API converts most data to bit vectors of some parameterized maximum length, default is 4096. This “flattening to bit-vectors” can be mitigated somewhat by clever vendor specific recording implementations, but the actual type cannot be fully recovered.
TRANSACTION RECORDING API
The low level transaction recording API that is implemented below the uvm_recorder is lightweight, easy to use, and is what-you-see-is-what-you-get. (See Appendix  for an API listing) Improvements to the UVM transaction recording API should evolve up from it. The uvm_component and uvm_transaction based APIs should be discarded and re-implemented, simplifying and separating functionality.
The low level API creates a data model on which higher level concepts can be layered. For example the low level API has a stream. In the upper level API, a component may have one or many streams.
The transaction simulation and recording models are clear and have been used for many years. The UVM transaction recording API (inherited from OVM) suffers many problems as outlined. In the following section some recommendations for usage and improvements will be outlined.
CURRENT LIMITATIONS OR ISSUES
Streams cannot be directly created. They can be created indirectly using uvm_component::begin_tr.
The end_tr call does the recording – it calls uvm_recorder::record().
Uvm_component::begin_tr may call an end_tr on a previous transaction.
Parent transactions are ill defined and only partially supported. They are not a general feature, but are tailored to sequences.
The uvm_recorder gets its tr_handle from uvm_transaction before being called. The do_record code relies on this being set properly. This is a general problem with the API. An opened transaction will not be closed or have attributes added until later in simulation. During this time the open handle must live somewhere, and be retrieved later when needed. Currently it is held in the uvm_transaction. What if three recordings are made for the same transaction? (One component based, one an error transaction, and another a communication tag based stream).
Uvm_recorder functions do not support most data types, using them results in data type information loss.
The uvm_recorder class should be rewritten as a transparent extension to underlying vendor specific code, and it should support recording type specific data values (as demonstrated by the uvm_record_attribute macro [Figure 7]). It should stay simple, powerful and transparent.
The uvm_component and uvm_transaction recording APIs should be removed. They offer very little capability for non-sequence objects. They do offer complexity and confusion, while implementing multiple different orthogonal functionality within the same routines. At the minimum, they should be simplified, and offer a simple collection of functionality – like beginning
Multiple streams in any component or sequence or object can be managed by a uvm_recorder. Each stream represented by a uvm_recorder. The recorder can manage handles for transactions that have begun.
WHAT TO DO CURRENTLY?
The existing low level API can create a stream and return its handle. It can create a transaction on a stream handle, and end a transaction. It can add attributes to a transation. It can create relationships between transaction handles. It offers more control, flexibility and precision. Unfortunately it is vendor specific.
The component begin_tr and end_tr can create streams. Attributes can be added, with some loss of data type specifics.
Below are two instrumentations of a monitor task; one with the low level API; one with begin_tr/end_tr.
Monitor with low-level recording
Monitor with current component recording
The monitoring instrumentation above is relatively straightforward using either technique, but when finer control is needed, as in the case of burst below, the issues with model level appear.
A burst driver instrumented below requires the fine control of transaction start and attribute recording that the low level API offers – simple begin_tr and end_tr won’t work.
Driver with burst
The SystemVerilog UVM transaction recording interface suffers from many usability and design issues, but can be used to create streams, transactions and attributes. By using the existing API judiciously, problems can be avoided, and the desired modeling can be achieved.
In order to use the full power available from the recorded transaction model, the careful users can use vendor specific function calls along with the SystemVerilog UVM transaction recording interface to achieve a more detailed recorded model.
The UVM will continue to evolve, and the transaction recording API is high on the list of improvements that will surely be released soon.
For sequences, rely on the current automation. For other components, like monitors and drivers, either use the existing API, supplying begin_tr with the stream name (no default name), or use the low level PLI calls directly. Never use the field automation macros – always implement do_record yourself, and get type specific information recorded.
 SystemVerilog LRM. IEEE 1800-2009.
 SystemVerilog UVM, www.accellera.org
 OSCI TLM Standard, www.systemc.org
 Frank Ghenassia (editor), “Transaction-level Modeling with SystemC: TLM Concepts and Applications for Embedded Systems”, Springer, 2005.
 Alain Clouard, Kshitz Jain, et. al., Using transactional level models in a SoC design flow, SystemC:methodologies and applications, pp. 29-63, Kluwer 2003, ISBN: 1-4020-7479-4
 Draft Standard for Verilog Transaction Recording, www.boyd.com/1364_btf/report/full_pr/attach/435_IEEE_TR_Proposal_04.pdf
 Rich Edelman, Mark Glasser, Bill Cox, IPSOC2004, “Debugging SOC Designs with Transactions”.
 Rich Edelman, IP08, Transactions in an OVM SystemVerilog Verification Environment.
 Sudeep Pasricha, Nikil Dutt, Mohamed Ben-Romdhane, Extending the Transaction Level Modeling Approach for Fast Communication Architecture Exploration. DAC 2004.
 Adam Erickson, Are OVM & UVM Macros Evil? A Cost-Benefit Analysis, DVCON 2010.
 Rich Edelman, Adam Rose, Andreas Meyer, Raghu Ardeishar, Jason Polychronopoulos, You Are In a Maze of Twisty Little Sequences, All Alike or Layering Sequences for Stimulus Abstraction, DVCON 2010.
 Rich Edelman, Transaction Analysis and Debug Across Language Boundaries and between Abstraction Levels, IP-ESC 2009.
 Rex Chen, Bindesh Patel, Jun Zhao, UVM Transaction Recording Enhancement, DVCON 2011.
 Rich Edelman, Sequences in SystemVerilog, DVCON 2008.
 Nicolas Leblond, Tiempo, How Transaction Viewing Accelerates Debug of Asynchronous SystemVerilog Designs, Verification Horizons, February 2011.
APPENDIX 1 – UVM Recorder API
APPENDIX 2 – PLI based, vendor specific API example
APPENDIX 3 – UVM Transaction API
APPENDIX 4 – UVM Component API
APPENDIX 5 – UVM Sequence API