Tasks and functions form the core of the code in various functional verification environments. For example, for bus functional models, you will need tasks such as read, write, request, and grant working over the interface. For data such as packets, cells, and frame to be delivered over an interface defined through a bus functional model, you need methods to copy and compare the data, and to display the data as and when appropriate.
We go back to the previous blog article that talks about efficient data generation using the VMM approach, and now talk about some of the methods associated with data item like the ATM cell.
One of the first methods that we need is called the constructor method for the ATM cell object; this is needed in object-oriented programming to be able to create a new instance of the object such as the ATM cell. Here, we just need to call its parent class’ constructor method called new() which is predefined for us. So defining the constructor methods is fairly mechanical and looks something like this:
//constructor method for the atm_cell class
function new();
super.new(log);
endfunction: new
There is an allocate() method for vmm_data to create a new instance of data transaction. It allocates a new instance of the same type as the object instance and returns a reference to the instance.
//allocate() method for the atm_cell class
function vmm_data allocate(vmm_data to = null);
atm_cell transaction = new;
return transaction;
endfunction: allocate
Typically, for any data item that we create, we will need a printing utility to able to display the relevant portions of the data item being generated. We can define a display method for printing information regarding the ATM cell being generated.
//display method for the atm_cell class
//to print a prefix message followed by vpi and vci fields
function string psdisplay (string prefix-msg);
$sformat (psdisplay, “ %s : [%d :%d]”,
prefix-msg, this.vpi, this.vci);
endfunction: psdisplay
Just like the constructor and allocate methods, the definition of the copy method is fairly mechanical and only has a section of code that is specific to the data item being defined. It copies the current value of the object instance into the specified object instance. If no object instance is specified then a new instance is allocated. The copy method for the ATM cell looks as follows:
//copy method for the atm_cell class
function vmm_data copy (vmm_data to = null);
atm_cell cell_copy;
if (to == null)
cell_copy = new();
else if (!$cast(cell_copy, to)) begin
‘vmm_fatal (this.log, “Not an ATM cell”);
cell_copy = null;
return;
end
super.copy_data(cell_copy);
cell_copy.vpi = this.vpi;
cell_copy.vci = this.vci;
cell_copy.pt = this.pt;
cell_copy.clp = this.clp;
cell_copy.hec = this.hec;
for (i=0; i < 48; i++)
cell_copy.payload[i] = this.payload[i];
copy = cell_copy;
endfunction: copy
The ATM cell specific parts of the copy code are highlighted in red and the rest of the code remains the same for all data copy methods. These are carried out after a call to the base class copy_data method which takes care of all of base member copying. The copy method can create a duplicate of the main transaction. Often, you create a copy of the transaction and alter some fields for generating appropriate transactions.
Similar to the copy method, each data item typically should provide a compare method to allow comparison of data items. It compares the current value of the object instance with the current value of specified object instance and returns TRUE if identical and FALSE if different. If case the value is different then a descriptive text of first difference found is also returned in the specified string. The kind argument may be used to implement different comparison functions i.e., full compare, compare of random fields etc.
//compare method for the atm_cell class
function vmm_data compare (input vmm_data to,
output string diff,
input int kind = -1);
atm_cell cell;
if (to == null) begin
‘vmm_fatal(log, “Cannot compare a NULL reference”);
return 0;
end
else if (!$cast(cell, to) begin
‘vmm_fatal(log, “Not an ATM cell instance compare”);
return 0;
end
if (cell.vpi != this.vpi) begin
$sformat(diff, “vpi %0d != %0d”, this.vpi, cell.vpi);
return 0;
end
if (cell.vci != this.vci) begin
$sformat(diff, “vci %0d != %0d”, this.vci, cell.vci);
return 0;
end
if (cell.pt != this.pt) begin
$sformat(diff, “pt %0d != %0d”, this.pt, cell.pt);
return 0;
end
if (cell.clp != this.clp) begin
$sformat(diff, “clp %0d != %0d”, this.clp, cell.clp);
return 0;
end
if (cell.hec != this.hec) begin
$sformat(diff, “hec %0d != %0d”, this.hec, cell.hec);
return 0;
end
for (i=0; i < 48; i++) begin
if (cell.payload[i] != this.payload[i])
$sformat(
diff, “payload[%0d] %0d != %0d”, i, this.payload[i], cell.payload[i]);
return 0;
end
return 1;
endfunction: compare
Once you have added these properties and methods, a vmm_data class would look something as listed below. Once the data object is created and filled up appropriately to provide a sequence of ATM cells, how do we transfer that information to the DUT? Also, we will need to be able to collect data coming out of DUT into a vmm_data object for further analysis to help validation at higher-level. These bring in the concepts of transactors and channel. We will continue our discussions in upcoming blog articles on verification methodology keeping RTL engineers in mind.
class atm_cell extends vmm_data;
//create a log class for printing facilities
static vmm_log log = new (“ATM_CELL”,“ATM_CELL”);
rand bit [11:0] vpi;
rand bit [15:0] vci;
rand bit [2:0] pt;
rand bit clp;
rand bit [7:0] hec;
rand bit [7:0] payload[48];
//add a constraint to pt
constraint pt_constraint1 {pt > 3;}
//constructor method for the atm_cell class
function new();
super.new(log);
endfunction: new
//allocate() method for the atm_cell class
function vmm_data allocate(vmm_data to = null);
atm_cell transaction = new;
return transaction;
endfunction: allocate
//display method for the atm_cell class
//to print a prefix message followed by vpi and //vci fields
function string psdisplay (string prefix-msg);
$sformat (psdisplay, “ %s : [%d :%d]”,
prefix-msg, this.vpi, this.vci);
endfunction: psdisplay
//copy method for the atm_cell class
function vmm_data copy (vmm_data to = null);
atm_cell cell_copy;
if (to == null)
cell_copy = new();
else if (!$cast(cell_copy, to)) begin
‘vmm_fatal (this.log, “Not an ATM cell”);
cell_copy = null;
return;
end
super.copy_data(cell_copy);
cell_copy.vpi = this.vpi;
cell_copy.vci = this.vci;
cell_copy.pt = this.pt;
cell_copy.clp = this.clp;
cell_copy.hec = this.hec;
for (i=0; i < 48; i++)
cell_copy.payload[i] = this.payload[i];
copy = cell_copy;
endfunction: copy
//compare method for the atm_cell class
function vmm_data compare (input vmm_data to,
output string diff,
input int kind = -1);
atm_cell cell;
if (to == null) begin
‘vmm_fatal(log, “Cannot compare a NULL reference”);
return 0;
end
else if (!$cast(cell, to) begin
‘vmm_fatal(log, “Not an ATM cell instance compare”);
return 0;
end
if (cell.vpi != this.vpi) begin
$sformat(diff, “vpi %0d != %0d”, this.vpi, cell.vpi);
return 0;
end
if (cell.vci != this.vci) begin
$sformat(diff, “vci %0d != %0d”, this.vci, cell.vci);
return 0;
end
if (cell.pt != this.pt) begin
$sformat(diff, “pt %0d != %0d”, this.pt, cell.pt);
return 0;
end
if (cell.clp != this.clp) begin
$sformat(diff, “clp %0d != %0d”, this.clp, cell.clp);
return 0;
end
if (cell.hec != this.hec) begin
$sformat(diff, “hec %0d != %0d”, this.hec, cell.hec);
return 0;
end
for (i=0; i < 48; i++) begin
if (cell.payload[i] != this.payload[i])
$sformat(diff, “payload[%0d] %0d != %0d”, i, this.payload[i], cell.payload[i]);
return 0;
end
return 1;
endfunction: compare
endclass: atm_cell
Tuesday, July 7, 2009
Monday, June 22, 2009
Making Data Generation Effective and Efficient
Continuing on benefits for those who are used to working with RTL language and building on a methodology-based logging mechanism, we will next look into the aspects of data or payload generation mechanism that are facilitated via a comprehensive VMM verification methodology.
Data generation coupled with transaction mechanism to deliver data to the DUT is the central activity in creation of testcases in any verification environment. A well thought out base infrastructure can help immensely with the efficiency of overall verification effort.
The synthesizable subsets of language such as Verilog, VHDL, and SystemVerilog require design and verification engineers to deal with bits, vectors of bits, and integers. The use of enum and struct allows the level of abstraction to move up a bit but the elements of these enums and structs must themselves be bits, bit vectors, and integers.
In working with testbenches, one has to move up the level of abstraction using models without such restrictions and allowing working with data items such as packets, frames, instructions, and commands.
In SystemVerilog, struct and class can model packets, frames, instructions, and commands. For example, one type ATM cell is modeled using a struct as follows:
typedef struct {
bit [11:0] vpi;
bit [15:0] vci;
bit [2:0] pt;
bit clp;
bit [7:0] hec;
bit [7:0] payload[48];
} atm_cell
The same ATM cell can be described using class as follows:
class atm_cell;
bit [11:0] vpi;
bit [15:0] vci;
bit [2:0] pt;
bit clp;
bit [7:0] hec;
bit [7:0] payload[48];
endclass: atm_cell
The key differences between the use of struct, as may be familiar to an RTL user, and class are the following: i) a struct being an integral type leads to allocation of memory when declared whereas class is a dynamic type and memory is not allocated until it is instantiated. ii) a class may also include tasks and functions written to operate on its contents; we will look into this aspect a bit more later, and iii) class instances can be moved around by moving their references as opposed to integral data types such as struct where you have to move the entire data item. This results in run-time efficiency.
One would like to create large number of such ATM cells of different types for verification purposes. Random stimulus generation in SystemVerilog has evolved far beyond the use of $random system task and verification methodology such as VMM abstract it up further to provide ease of use in the key task of data or payload generation in the verification environment.
The vmm_data base class provides a standard set of properties and methods and we will talk about this more later. The same ATM cell data looks as follows with vmm_data:
class atm_cell extends vmm_data;
……
rand bit [11:0] vpi;
rand bit [15:0] vci;
rand bit [2:0] pt;
rand bit clp;
rand bit [7:0] hec;
rand bit [7:0] payload[48];
……
endclass: atm_cell
Going beyond the class definition of ATM cell, there are several things to note here:
1. In this definition, an existing class vmm_data is being extended; vmm_data is a base class that provides a standard set of properties and methods that are useful in creating verification environment. We had discussed benefits of class and all of those benefits such as modeling of operations, transformations, alteration of default behavior, and efficient movement of instances are applicable here.
2. With extension of vmm_data, you can easily control randomization of data values. With appropriate added code to the class variable, one can always achieve this but a lots of background work has already been done with vmm_data thereby making life easier for the verification engineer.
3. With the specification of fields of typical data items such as a packet as an extension of vmm_data, it is easy to create a set of random packets while constraining the values within legal or illegal limits as needed for the verification task. In contrast, traditional testbenches would require one procedure per transaction. You can constrain the fields to take up value within specified limits. These are called constraints ; a constraint to limit value of the field pt to be greater than 3 would look something like this:
constraint pt_constraint1 {pt > 3}
4. You may notice some dots at the front and the end part of class definition as an extension of vmm_data. Additional properties and methods, that operate over the ATM cell packet data, are added there. For example, you would need printing capabilities for the packet and a vmm_log, discussed in previous blog, object can be added about this packet. This can added as an instance of vmm_log base class named “ATM_CELL”:
static vmm_log log = new (“ATM_CELL”, “ATM_CELL”);
5. You will have to add a display method for the ATM cell data to display appropriate fields of the packet when packet is generated or delivered at an interface. This is one of the methods you will typically add to vmm_data.
6. There are four other methods that are typically added to a vmm_data item: constructor, allocator, copy, and compare. The constructor of the object makes a call to the parent constructor class’ new() method, a new instance of data transaction is created via the allocate() , and copy() method allows copying of data object, and compare() method allows checking for equality of two copies of the data object. We will get into the details of methods that go with objects in a future discussion.
Once you have added these properties and methods, a vmm_data class would look something as listed below. The code pieces for methods have been left out for now and will be brought in the discussion of methods for data creation objects.
class atm_cell extends vmm_data;
//create a log class for printing facilities
static vmm_log log = new (“ATM_CELL”,“ATM_CELL”);
rand bit [11:0] vpi;
rand bit [15:0] vci;
rand bit [2:0] pt;
rand bit clp;
rand bit [7:0] hec;
rand bit [7:0] payload[48];
//add a constraint to pt
constraint pt_constraint1 {pt > 3;}
//add a display() method here for atm_cell
//add a constructor()method for atm_cell
//add an allocate() method for atm_cell
//add a copy() method for atm_cell
//add a compare() method for atm_cell
endclass: atm_cell
Once the data object is created and filled up appropriately to provide a sequence of ATM cells, how do we transfer that information to the DUT? Also, we will need to be able to collect data coming out of DUT into a vmm_data object for further analysis to help validation at higher-level. These bring in the concepts of transactors and channel. We will continue our discussions in upcoming blog articles on verification methodology keeping RTL engineers in mind.
Data generation coupled with transaction mechanism to deliver data to the DUT is the central activity in creation of testcases in any verification environment. A well thought out base infrastructure can help immensely with the efficiency of overall verification effort.
The synthesizable subsets of language such as Verilog, VHDL, and SystemVerilog require design and verification engineers to deal with bits, vectors of bits, and integers. The use of enum and struct allows the level of abstraction to move up a bit but the elements of these enums and structs must themselves be bits, bit vectors, and integers.
In working with testbenches, one has to move up the level of abstraction using models without such restrictions and allowing working with data items such as packets, frames, instructions, and commands.
In SystemVerilog, struct and class can model packets, frames, instructions, and commands. For example, one type ATM cell is modeled using a struct as follows:
typedef struct {
bit [11:0] vpi;
bit [15:0] vci;
bit [2:0] pt;
bit clp;
bit [7:0] hec;
bit [7:0] payload[48];
} atm_cell
The same ATM cell can be described using class as follows:
class atm_cell;
bit [11:0] vpi;
bit [15:0] vci;
bit [2:0] pt;
bit clp;
bit [7:0] hec;
bit [7:0] payload[48];
endclass: atm_cell
The key differences between the use of struct, as may be familiar to an RTL user, and class are the following: i) a struct being an integral type leads to allocation of memory when declared whereas class is a dynamic type and memory is not allocated until it is instantiated. ii) a class may also include tasks and functions written to operate on its contents; we will look into this aspect a bit more later, and iii) class instances can be moved around by moving their references as opposed to integral data types such as struct where you have to move the entire data item. This results in run-time efficiency.
One would like to create large number of such ATM cells of different types for verification purposes. Random stimulus generation in SystemVerilog has evolved far beyond the use of $random system task and verification methodology such as VMM abstract it up further to provide ease of use in the key task of data or payload generation in the verification environment.
The vmm_data base class provides a standard set of properties and methods and we will talk about this more later. The same ATM cell data looks as follows with vmm_data:
class atm_cell extends vmm_data;
……
rand bit [11:0] vpi;
rand bit [15:0] vci;
rand bit [2:0] pt;
rand bit clp;
rand bit [7:0] hec;
rand bit [7:0] payload[48];
……
endclass: atm_cell
Going beyond the class definition of ATM cell, there are several things to note here:
1. In this definition, an existing class vmm_data is being extended; vmm_data is a base class that provides a standard set of properties and methods that are useful in creating verification environment. We had discussed benefits of class and all of those benefits such as modeling of operations, transformations, alteration of default behavior, and efficient movement of instances are applicable here.
2. With extension of vmm_data, you can easily control randomization of data values. With appropriate added code to the class variable, one can always achieve this but a lots of background work has already been done with vmm_data thereby making life easier for the verification engineer.
3. With the specification of fields of typical data items such as a packet as an extension of vmm_data, it is easy to create a set of random packets while constraining the values within legal or illegal limits as needed for the verification task. In contrast, traditional testbenches would require one procedure per transaction. You can constrain the fields to take up value within specified limits. These are called constraints ; a constraint to limit value of the field pt to be greater than 3 would look something like this:
constraint pt_constraint1 {pt > 3}
4. You may notice some dots at the front and the end part of class definition as an extension of vmm_data. Additional properties and methods, that operate over the ATM cell packet data, are added there. For example, you would need printing capabilities for the packet and a vmm_log, discussed in previous blog, object can be added about this packet. This can added as an instance of vmm_log base class named “ATM_CELL”:
static vmm_log log = new (“ATM_CELL”, “ATM_CELL”);
5. You will have to add a display method for the ATM cell data to display appropriate fields of the packet when packet is generated or delivered at an interface. This is one of the methods you will typically add to vmm_data.
6. There are four other methods that are typically added to a vmm_data item: constructor, allocator, copy, and compare. The constructor of the object makes a call to the parent constructor class’ new() method, a new instance of data transaction is created via the allocate() , and copy() method allows copying of data object, and compare() method allows checking for equality of two copies of the data object. We will get into the details of methods that go with objects in a future discussion.
Once you have added these properties and methods, a vmm_data class would look something as listed below. The code pieces for methods have been left out for now and will be brought in the discussion of methods for data creation objects.
class atm_cell extends vmm_data;
//create a log class for printing facilities
static vmm_log log = new (“ATM_CELL”,“ATM_CELL”);
rand bit [11:0] vpi;
rand bit [15:0] vci;
rand bit [2:0] pt;
rand bit clp;
rand bit [7:0] hec;
rand bit [7:0] payload[48];
//add a constraint to pt
constraint pt_constraint1 {pt > 3;}
//add a display() method here for atm_cell
//add a constructor()method for atm_cell
//add an allocate() method for atm_cell
//add a copy() method for atm_cell
//add a compare() method for atm_cell
endclass: atm_cell
Once the data object is created and filled up appropriately to provide a sequence of ATM cells, how do we transfer that information to the DUT? Also, we will need to be able to collect data coming out of DUT into a vmm_data object for further analysis to help validation at higher-level. These bring in the concepts of transactors and channel. We will continue our discussions in upcoming blog articles on verification methodology keeping RTL engineers in mind.
Thursday, May 28, 2009
Simplifying Management of Debug Statements
The importance of debug statements or simulation messages cannot be overstated in the context of efficient simulation process management. All of the components of a typical testbench such as transactors, assertions, monitors, scoreboards, and testcases use messages to indicate progress, important events, warnings, errors or potential efforts, and abnormal or normal completion of simulation. These indicators along with additional information to help diagnose the problems play a huge role in efficient simulation process management.
A common practice as used in the context of Verilog or VHDL simulation management is to either implement a simulation logging module with appropriate tasks to indicate notes, warnings, and errors or ensure results by parsing simulation output.
An implementation of logging module helps simulation keep track of its own progress and allows consistency of messages coming from various sources. However, this needs to be carefully thought out given a number of different possible sources of these messages in a given simulation environment, different severities associated with these debug messages, and multiple message types associated with each source. Any implementation of consistent and robust simulation messaging service requires a lot of effort and careful thinking to help enable efficient simulation process management.
It is great to see that all of this has been captured in the construction of vmm_log class (think of it as a module for reporting events and important occurrences) in VMM thus allowing to keep our focus on the main simulation tasks i.e., creation of appropriate testcases to reach the goals of functional coverage. Several convenient macros have been created simplifying use of the vmm_log class to report notes, warnings, errors, debug messages, and fatal issues.
For example, if you are working with the verification of PCI interface where data link layer implements sequencing of transaction layer packets and protects the contents of the packets using a 32-bit CRC. You want to be kept informed of the number of packets received so far and when computed CRC does not match received CRC, you will like to issue an error message. These two types of messages can be serviced as follows:
`vmm_note(log, $psprintf(“Received TLP packet number %d at time %d”, pcount, $time));
:
:
if (computedCRC != receivedCRC)
`vmm_error (log, $psprintf (“PCIXactor:Received CRC %h does not match computed CRC %h for packet number %d at time %d”, receivedCRC, computedCRC, pCount, $time) );
While transfer of packets are happening at one interface, we could be also be monitoring events to ensure adherence to the protocol. For example, a target must assert DEVSEL before any other response within 1 to 3 clocks following the address phase or else an error could be indicated as follows:
`vmm_error(log, $psprintf(“PCIChecker: At time %d the target erroneously asserted STOP or TRDY before asserting DEVSEL”, $time));
In the code examples above, log is simply an instance of vmm_log class. The underlying VMM infrastructure has several built-in handles and one of them counts message as an error if the logging severity is set to error. As a result, in our example, PCI errors would automatically increment the error count and simulation can be aborted when combined errors from different sources exceed certain threshold.
This simplifies simulation process management immensely. There is consistent messaging from different components of testbench while providing complete programmability of message types and severities along with typical simulation handles used in simulation process.
As a verification consultant, I always come across engineers who have been successfully doing verification using Verilog over the years and question any switch of methodology given the TTM pressures they face with every product. They are aware of some of limitations of their current methodology but want clear pointers to the features of any new methodology that help alleviate existing pain points. Being a long time Verilog user myself, I think VMM’s logging infrastructure is one such pointer. It is simple yet very effective in improving the overall simulation process management.
It will be great to hear about your experiences with management of debug statements during the course of verification process and the importance it plays in overall verification efficiency.
A common practice as used in the context of Verilog or VHDL simulation management is to either implement a simulation logging module with appropriate tasks to indicate notes, warnings, and errors or ensure results by parsing simulation output.
An implementation of logging module helps simulation keep track of its own progress and allows consistency of messages coming from various sources. However, this needs to be carefully thought out given a number of different possible sources of these messages in a given simulation environment, different severities associated with these debug messages, and multiple message types associated with each source. Any implementation of consistent and robust simulation messaging service requires a lot of effort and careful thinking to help enable efficient simulation process management.
It is great to see that all of this has been captured in the construction of vmm_log class (think of it as a module for reporting events and important occurrences) in VMM thus allowing to keep our focus on the main simulation tasks i.e., creation of appropriate testcases to reach the goals of functional coverage. Several convenient macros have been created simplifying use of the vmm_log class to report notes, warnings, errors, debug messages, and fatal issues.
For example, if you are working with the verification of PCI interface where data link layer implements sequencing of transaction layer packets and protects the contents of the packets using a 32-bit CRC. You want to be kept informed of the number of packets received so far and when computed CRC does not match received CRC, you will like to issue an error message. These two types of messages can be serviced as follows:
`vmm_note(log, $psprintf(“Received TLP packet number %d at time %d”, pcount, $time));
:
:
if (computedCRC != receivedCRC)
`vmm_error (log, $psprintf (“PCIXactor:Received CRC %h does not match computed CRC %h for packet number %d at time %d”, receivedCRC, computedCRC, pCount, $time) );
While transfer of packets are happening at one interface, we could be also be monitoring events to ensure adherence to the protocol. For example, a target must assert DEVSEL before any other response within 1 to 3 clocks following the address phase or else an error could be indicated as follows:
`vmm_error(log, $psprintf(“PCIChecker: At time %d the target erroneously asserted STOP or TRDY before asserting DEVSEL”, $time));
In the code examples above, log is simply an instance of vmm_log class. The underlying VMM infrastructure has several built-in handles and one of them counts message as an error if the logging severity is set to error. As a result, in our example, PCI errors would automatically increment the error count and simulation can be aborted when combined errors from different sources exceed certain threshold.
This simplifies simulation process management immensely. There is consistent messaging from different components of testbench while providing complete programmability of message types and severities along with typical simulation handles used in simulation process.
As a verification consultant, I always come across engineers who have been successfully doing verification using Verilog over the years and question any switch of methodology given the TTM pressures they face with every product. They are aware of some of limitations of their current methodology but want clear pointers to the features of any new methodology that help alleviate existing pain points. Being a long time Verilog user myself, I think VMM’s logging infrastructure is one such pointer. It is simple yet very effective in improving the overall simulation process management.
It will be great to hear about your experiences with management of debug statements during the course of verification process and the importance it plays in overall verification efficiency.
Subscribe to:
Posts (Atom)
