Transactions in an OVM SystemVerilog Verification Environment
By Rich Edelman, Mentor Graphics
San Jose, CA US
Abstract
Modeling a verification environment with transactions encompasses many areas, including test bench design and debug, golden model comparison, functional verification between abstraction levels and overall system operation. This paper will discuss certain techniques for modeling with transactions in each of these different abstraction levels and how to effectively combine them using the OVM [1] and SystemVerilog [6].
Introduction
Transactions have been used in verification environments for many years [7][3]. A transaction can represent a bus transaction or a packet transaction or an entire test. Using transactions meant that you needed to consider many aspects of the transaction. Among the considerations – what is being modeled, when is it valid – when does it begin and end, what is contained in the transaction and how this transaction is related to other transactions.
With the release of the SystemVerilog OVM, generating transactions has become quite easy – with certain abstractions in the test environment automatically mapped to transaction attribute like “begin” and “end”.
The OVM SystemVerilog class library contains an ovm_transaction base class and an ovm_sequence_item base class. (Figure 1)
An ovm_sequence usually generates ovm_sequence_items and then “puts” them to an ovm_sequencer/ovm_driver pair. (Figure 3)
In addition, there is another fundamental transaction in the OVM – it is the “recorded transaction” – or the transaction that is recorded into a database.
Figure 1 - UML - transactions and sequences
Terminology
The transaction modeling terminology used here will follow along with previous work [9][10]. A stream is used to contain or host transactions. A transaction occurs on a stream. A stream is defined to have no overlapping transactions. If a transaction would overlap another transaction on a stream, then a new stream is automatically created and the transaction is hosted there.
A recorded transaction has a beginning time and an ending time. A recorded transaction also has attributes – (name, value) pairs – attached to it. Two recorded transactions can have a relationship – like parent/child or predecessor/successor.
A recorded transaction can begin or end in the past. It cannot begin or end in the future.
A stream can be thought of as a drawn row on a waveform window, or as a finite state machine controlling an interface [10]. A stream is a way to organize transactions – for drawing, analysis or recording convenience.
A recorded transaction and an ovm_transaction are related to each other. The recorded transaction is the “occurrence” of the ovm_transaction, recorded on a stream. The ovm_transaction contains a handle, tr_handle, which is the handle to the recorded transaction. This is an important link between the ovm_transaction during simulation, and the recorded transaction, recorded on the stream.
In the OVM, a transaction can be recorded “on the ovm_component” or “by the ovm_transaction”. A transaction recorded on the ovm_component is a transaction which passes through, or executes on a component. For example, a driver might execute a transaction, and record it. A transaction recorded by the ovm_transaction
Kinds of transactions
There are many kinds of transactions, four of which are outlined below. An abstract transaction is just that – an arbitrary collection of communication or computation that is grouped together. A bus can have transactions – either protocol specific, or generic. A communication fabric can have transactions – like “transfer item A from Initiator 1 to Target 3”. There are many other kinds of transactions, which may be modeled, simulated, recorded and debugged similarly.
Abstract Transactions
Transactions are usually thought of as communication or computation – Merriam-Webster [4] 2.b. says “b: a communicative action or activity involving two parties or things that reciprocally affect or influence each other”.
As an abstraction, transactions are very useful, since they allow higher level abstractions, and provide a vehicle for more abstraction reasoning about the behavior of the system. Studies have been done showing that at a constant productivity rate; a human can “do” N things in a day. The N things should represent as much communication or computation as possible, in order to achieve maximum productivity.
“Send a network packet from A to B” is a good example of an abstract transaction. “Run the test named read_all_registers” is another.
An abstract transaction usually starts other lower-level transactions that perform work. Those lower level transactions in turn create transactions, etc, until there is some transaction which performs communication or computation. Once all the lower level transactions complete, we might reason about the state of the system, and the length of time that was required by the transaction.
Bus Independent Transactions
Bus independent transactions are abstract transactions – just a lower level transaction – WRITE(addr, data) or READ(addr, data). Bus independent transactions are used to specify communication without regard to the underlying bus implementation or protocol. Whether the bus implements a burst protocol or allows out-of-order transactions is not important at this level.
Tests can be written at this level and re-used across various bus implementations.
Bus Dependent Transactions
A bus dependent transaction is tied explicitly to an underlying bus implementation and specification. Two different bus implementations will have different bus dependent transactions. A bus dependent transaction represents the physical data transfer of information across a bus – it understands how to issue a WRITE on an AHB bus, for example.
Transfer Transactions
Transfer transactions are not thought of as bus transactions, but rather simply specify that some data is being transferred from point A to point B. For example, network or switches might have transactions written for them to move data across them. A transfer might move data from port 1 to port 4 on the switch.
Design Structures
One of the most common and direct applications of transaction modeling is to model communication on design structures such as busses or interfaces. Each bus or interface is modeled as a stream, with activity on the bus represented as transaction activity.
SystemVerilog Interface
SystemVerilog adds an “interface” construct as a way to collect pins of a bus or other related signals. In addition, tasks and functions may be defined in the interface to manipulate the signals (Figure 3 #5). In the example below when the write() task is called, the address and data are passed in, and the signaling occurs to actually cause a WRITE on the bus. When the read() task is called, the address is passed in and a READ on the bus occurs. When the data is returned on the bus, it is also returned to the caller of the task.
interface stopwatch_if(input bit clk);
<Pin Definitions removed>
bit[31:0] data_i;
// Value WRITTEN.
// data_i written to REGISTER[addr]
// (when rwenable ACTIVE, rw = 0).
bit[31:0] data_o;
// Value READ.
// data_o read from REGISTER[addr]
// (when rwenable ACTIVE, rw = 1).
bit[31:0] addr;
// Address bits for writing and
// reading the registers.
task read(
ADDR_T l_addr, output DATA_T l_data);
tr = ovm_begin_transaction(“-”,s, “READ”);
$add_attribute(tr, READ, "my_op");
$add_attribute(tr, l_addr, "my_address");
addr = l_addr;
rwenable = 1;
count = 0;
rw = 1;
@(posedge clk);
@(posedge clk);
l_data = data_o;
$add_attribute(tr, l_data, "my_data");
ovm_end_transaction(tr);
rwenable = 0;
count = 1;
endtask
task write(
ADDR_T l_addr, DATA_T l_data);
tr = ovm_begin_transaction(“-”,s, “WRITE”);
$add_attribute(tr, WRITE, "my_op");
$add_attribute(tr, l_addr, "my_address");
$add_attribute(tr, l_data, "my_data");
addr = l_addr;
data_i = l_data;
rwenable = 1;
count = 0;
rw = 0;
@(posedge clk);
@(posedge clk);
ovm_end_transaction(tr);
rwenable = 0;
count = 1;
endtask
endinterface
Assertions
Many previous examples (including [5] and [2]) have been created showing how assertions can be used to recognize a transaction on a bus or collection of pins, then form an ovm_transaction derived class. The ovm_transaction derivative is then entered into the class based testbench. For example, an assertion could be used in the DUT or the interface in Figure 3 (#5 or #6). Once the assertion recognizes the transaction, an analysis could be written using an analysis port or other TLM connection.
Transactions in Testbench Structures
Figure 3 is a canonical diagram representing some hypothetical testbench. It is used here to demonstrate the many places that transactions might occur.
tlm_fifo
A tlm_fifo is a basic building block of transaction level tests and test benches.
Figure 2 - Simple producer/consumer
While it is possible to record transactions in tlm_fifos – for example starting the recording when the transaction enters the fifo, and ending the recording when it exits the fifo – this is a transaction about waiting. While a transaction is in a fifo, it is usually not causing anything to happen – it is waiting. When a transaction is retrieved from a fifo, then it is usually operated on, or causes some action – like pin wiggles in a driver.
Measuring waiting can be useful, but can be achieved more easily using the “accept time”. When a transaction is “received” the accept time can be set. When a transaction causes some action, the begin time can be set. The difference between accept time and begin time is the time that the transaction was available to cause work, but did not – it was waiting.
Figure 3 - Canonical Testbench
OVM Components
OVM components including sequencers, scoreboards, coverage objects, monitors and plain components or threaded components can be annotated for various purposes. The component based recording API allows any component to record any transaction it sees, creates or receives.
A driver can be annotated to record transactions it drives or receives (Figure 3 #4).
class my_driver
#(type REQ = ovm_sequence_item,
type RSP = ovm_sequence_item)
extends ovm_driver #(REQ, RSP);
function new(string name, ovm_component p);
super.new(name, p);
endfunction
task run();
int tr;
forever begin
seq_item_port.get(req);
tr = begin_tr(req, "driver");
$add_attribute(tr, req.op,"my_op");
$add_attribute(tr, req.address, "my_address");
if (req.op == transaction_pkg::WRITE)
$add_attribute(tr, req.data, "my_data");
// Bus cycle....calculate the response
//
if (req.op == transaction_pkg::READ)
$add_attribute(tr,
rsp.data, "my_data");
rsp_port.write(rsp);
end_tr(req);
end
endtask
endclas
OVM Sequences
The code below (Figure 3 #2), a collection of sequences, is adapted from the OVM examples, and demonstrates the automatic recording of transactions using the field automation macros.
The sequences in this example generate ‘simple_items’. A simple_item is just a transaction containing an address and data field, which will be randomized by the calling sequences.
class simple_item extends ovm_sequence_item;
rand int unsigned addr;
constraint c1 { addr < 16'h2000; }
rand int unsigned data;
constraint c2 { data < 16'h1000; }
`ovm_object_utils_begin(simple_item)
`ovm_field_int(addr, OVM_ALL_ON)
`ovm_field_int(data, OVM_ALL_ON)
`ovm_object_utils_end
virtual task body();
#20;
endtask
endclass : simple_item
The ‘simple_seq_do’ sequence is a simple sequence which generates four items. Each item is the same type – a simple_item.
class simple_seq_do
extends ovm_sequence #(simple_item);
function new(string name="simple_seq_do");
super.new(name);
endfunction
`ovm_sequence_utils(simple_seq_do,
simple_sequencer)
simple_item item;
virtual task body();
#2;
`ovm_do(item)
`ovm_do(item)
`ovm_do(item)
`ovm_do(item)
endtask
endclass : simple_seq_do
The ‘simple_seq_do_with’ sequence is similar to a simple_seq_do, but adds inline constraints. The inline constraints are used to refine the items values for randomization. Three items are generated.
class simple_seq_do_with
extends ovm_sequence #(simple_item);
function new(string name="simple_seq_do_with");
super.new(name);
endfunction
`ovm_sequence_utils(simple_seq_do_with,
simple_sequencer)
simple_item item;
virtual task body();
#2;
`ovm_do_with(item,
{addr == 16'h0123; data == 16'h0456;})
`ovm_do_with(item,
{addr == 16'h0124; data == 16'h0457;})
`ovm_do_with(item,
{addr == 16'h0125; data == 16'h0458;})
endtask
endclass : simple_seq_do_with
The ‘simple_seq_do_with_vars’ sequence is similar to a simple_seq_do_with, but now the inline constraints are constrained against a variable, not a constant. Four items are generated.
class simple_seq_do_with_vars
extends ovm_sequence #(simple_item);
function new(string name =
"simple_seq_do_with_vars");
super.new(name);
endfunction
`ovm_sequence_utils(simple_seq_do_with_vars,
simple_sequencer)
rand int unsigned start_addr;
constraint c1 { start_addr < 16'h0200; }
rand int unsigned start_data;
constraint c2 { start_data < 16'h0100; }
simple_item item;
virtual task body();
#2;
`ovm_do_with(item,
{addr == start_addr; data == start_data;})
`ovm_do_with(item,
{addr == start_addr; data == start_data+1;})
`ovm_do_with(item,
{addr == start_addr; data == start_data+2;})
`ovm_do_with(item,
{addr == start_addr; data == start_data+3;})
endtask
endclass : simple_seq_do_with_vars
The ‘simple_seq_sub_seqs’ sequence is different than the three preceding. It is actually a “virtual sequence”, or perhaps a test (Figure 3 #1 or #2) – producing no transactions directly, but instead calling three lower level sequences to generate transactions.
The sequences “seq_do”, “seq_do_with” and “seq_do_with_vars” are called in turn. This loop is repeated 100 times.
class simple_seq_sub_seqs
extends ovm_sequence #(simple_item);
function new(string name =
"simple_seq_sub_seqs");
super.new(name);
endfunction
`ovm_sequence_utils(simple_seq_sub_seqs,
simple_sequencer)
simple_seq_do seq_do;
simple_seq_do_with seq_do_with;
simple_seq_do_with_vars seq_do_with_vars;
virtual task body();
for (int i = 0; i < 100; i++) begin
#10;
`ovm_do(seq_do)
#10;
`ovm_do(seq_do_with)
#10;
`ovm_do_with(seq_do_with_vars,
{seq_do_with_vars.start_addr == 16'h0003;
seq_do_with_vars.start_data == 16'h0009;})
#10;
end
endtask
endclass : simple_seq_sub_seqs
OVM Registers
In register testing, an address map independent transaction is generated – WRITE(”regA”, 42). This transaction causes an address lookup sometime later, which creates an address specific transaction –WRITE(0x1000, 42). That bus transaction is accepted by a driver, and turned into a bus transaction on the pins of the bus. The driver understands how to convert WRITE(0x1000, 42) into an AHB or AXI transaction.
Comparing transactions
Transactions are easily compared in the OVM using the built-in classes like in-order-comparator and algorithmic-comparator or a custom designed comparator in a scoreboard.
Relations
Any large system is likely to have most if not all of these kinds of transactions and have relationships between them. In order to build the relationships, the transactions and the environment must be designed with this in mind.
When a transaction is started, its parent may wish to be registered as the parent. When a transaction finishes it may have a reference saved, so that the next transaction can be created with a successor/predecessor relationship.
With the OVM, some of these relationships can be created automatically. For example, a sequence may be the parent of a sequence_item that it starts. A driver may be the predecessor of all the traffic generated on a bus.
As a general purpose solution to finding parents or children, a string lookup table can be created. This lookup table can find parents or children (or any relation), given the proper key. This infrastructure does not yet exist in the OVM, but has been used by individual testbench architects.
Conclusions
SystemVerilog provides the facilities for transaction modeling and test bench construction using object-oriented techniques, which improve maintenance and productivity.
The OVM Class Library further enhances this productivity by providing a collection of base classes which provide necessary structure and functionality, like drivers, scoreboards, sequences, configuration, fifos
Using SystemVerilog and the OVM allows for the easy generation of transactions for use in debug and analysis. The canonical testbench can be instrumented with transactions in a variety of ways, suitable to the need. Additionally the OVM provides ways to automatically record many of these transactions into a debug database.
The current API for recording is historical, and will change once a common use-model is proposed, negotiated and accepted. One of the easiest areas to clarify is the recording of transaction attributes. Currently the only data type recorded is a 1024 bit vector – that can easily be fixed with the current $add_attribute(). Another approach is to provide a more transparent layering for the existing PLI – effectively promoting the PLI API into the SystemVerilog OVM base class api.
The SystemVerilog OVM combines object oriented techniques with a powerful base class library and automated or customized transaction recording providing a powerful mechanism for transaction modeling, debug and analysis in tests and testbenches.
References
[2] Mark Glasser, Adam Rose, Tom Fitzpatrick, Dave Rich, Harry Foster, “The Verification Cookbook”,
2006, Mentor Graphics Corporation.
[3] Frank Ghenassia (editor), “Transaction-level Modeling with SystemC: TLM Concepts and Applications for Embedded Systems”, Springer, 2005.
[4] Merriam Webster
[5] Verification Horizons, Q3’06-Vol.2, #3, p 17.
[6] SystemVerilog LRM. IEEE 1800-2005.
[8] Draft Standard for Verilog Transaction Recording
[9] Rich Edelman, IPSOC 2005, “A SystemVerilog DPI Framework for reusable transaction level testing, debug and analysis of SOC designs”.
[10] Rich Edelman, Mark Glasser, Bill Cox, IPSOC2004, “Debugging SOC Designs with Transactions”.
Appendix
An implementation possible with Questa follows. This implementation relies on the underlying PLI routines [8] to do the proper recording into the WLF database. An alternative implementation could pretty print the values into a text file, and produce a text database of transactions – similar to the SCV text transaction file [7].
function TRH ovm_create_fiber (
string name,
string t,
string scope);
return $create_transaction_stream(name, t);
endfunction
function void ovm_set_attribute_by_name (
TRH txh,
string nm,
logic [1023:0] value,
string radix,
TRH numbits=0);
$add_attribute(txh, value, nm);
endfunction
function TRH ovm_begin_transaction(
string txtype,
TRH stream,
string nm,
string label="",
string desc="",
time begin_time=0);
return $begin_transaction(
stream, nm, begin_time);
endfunction
function void ovm_end_transaction (
TRH handle,
time end_time=0);
$end_transaction(handle, end_time);
endfunction
function void ovm_link_transaction(
TRH h1,
TRH h2,
string relation="");
if (relation == "")
relation = "successor";
$add_relation(h1, h2, relation);
endfunction
function void ovm_free_transaction_handle(
TRH handle);
$free_transaction(handle);
endfunction
|
Related Articles
- Managing an Adaptive Verification Environment with the Open Verification Methodology
- Development of Verification Environment for Layered Protocol using SystemVerilog
- Design patterns in SystemVerilog OOP for UVM verification
- API-based verification: Effective reuse of verification environment components
- Modeling and Verification of Mixed Signal IP using SystemVerilog in Virtuoso and NCsim
New Articles
- Quantum Readiness Considerations for Suppliers and Manufacturers
- A Rad Hard ASIC Design Approach: Triple Modular Redundancy (TMR)
- Early Interactive Short Isolation for Faster SoC Verification
- The Ideal Crypto Coprocessor with Root of Trust to Support Customer Complete Full Chip Evaluation: PUFcc gained SESIP and PSA Certified™ Level 3 RoT Component Certification
- Advanced Packaging and Chiplets Can Be for Everyone
Most Popular
- System Verilog Assertions Simplified
- System Verilog Macro: A Powerful Feature for Design Verification Projects
- UPF Constraint coding for SoC - A Case Study
- Dynamic Memory Allocation and Fragmentation in C and C++
- Enhancing VLSI Design Efficiency: Tackling Congestion and Shorts with Practical Approaches and PnR Tool (ICC2)
E-mail This Article | Printer-Friendly Page |