Verification Martial Arts: A Verification Methodology Blog

Archive for 2012

Reusable Sequences in UVM

Posted by haribali on 12th November 2012

In this blog, I will be sharing the necessary steps one has to take while writing a sequence to make sure it can be reusable. Having developed sequences and tests in UVM, while using Verification IPs, I think writing sequences is the most challenging part in verifying any IP.  Careful planning is required to write sequences without which we end up writing one sequence for every scenario from scratch. This makes sequences hard to maintain and debug.

As we know, sequences are made up of several data items, which together form an interesting scenario. Sequences can be hierarchical thereby creating more complex scenarios. In its simplest form, a sequence should be a derivative of the uvm_sequence base class by specifying request and response item type parameter and implement body task with the specific scenario you want to execute.

class usb_simple_sequence extends uvm_sequence #(usb_transfer);

    rand int unsigned sequence_length;
    constraint   reasonable_seq_len { sequence_length < 10 };
    //Constructor
    function new(string name=”usb_simple_bulk_sequence”);
        super.new(name);
    endfunction

    //Register with factory
    `uvm_object_utils(usb_simple_bulk_sequence)

    //the body() task is the actual logic of the sequence
    virtual task body();
        repeat(sequence_length)
        `uvm_do_with(req,   {
            //Setting the device_id to 2
            req.device_id   == 8’d2;
            //Setting transfer type to BULK
            req.type   == usb_transfer::BULK_TRANSFER;
        })
    endtask   : body
endclass

In the above sequence we are trying to send usb bulk transfer to a device whose id is 2. Test writers can invoke this by just assigning this sequence to the default sequence of the sequencer in the top level test.

class usb_simple_bulk_test extends uvm_test;

    …
    virtual function void   build_phase(uvm_phase phase );
        …
        uvm_config_db#(uvm_object_wrapper)::set(this, "sequencer_obj.
        main_phase","default_sequence", usb_simple_sequence::type_id::get());
        …
    endfunction : build_phase
endclass

So far, things look simple and straight forward. To make sure the sequence is reusable for more complex scenarios, we have to follow a few more guidelines.

  • First off, it is important to manage the end of test by raising and dropping objections in the pre_start and post_start tasks in the sequence class. This way we raise and drop objection only in the top most sequence instead of doing it for all the sub sequences.

task pre_start()

    if(starting_phase != null)
    starting_phase.raise_objection(this);
endtask : pre_start


task post_start()

    if(starting_phase   != null)
    starting_phase.drop_objection(this);
endtask : post_start


Note that starting_phase is defined only for the sequence which is started as the default sequence for a particular phase. If you have started it explicitly by calling the sequence’s start method then it is the user’s responsibility to set the starting_phase.

class usb_simple_bulk_test extends uvm_test;

    usb_simple_sequence seq;
    …
    virtual function void main_phase(uvm_phase   phase );
        …
        //User need to set the starting_phase as   sequence start method
        is explicitly called to invoke the sequence
        seq.starting_phase = phase;
        seq.start();
        …
    endfunction : main_phase

endclass

  • Use UVM configurations to get the values from top level test. In the above example there is no controllability given to test writers as the sequence is not using configurations to take values from the top level test or sequence (which will be using this sequence to build a complex scenario). Modifying the sequence to give more control to the top level test or sequence which is using this simple sequence.

class usb_simple_sequence extends uvm_sequence #(usb_transfer);

    rand int unsigned sequence_length;
    constraint reasonable_seq_len {   sequence_length < 10 };
    …
    virtual task body();
        usb_transfer::type_enum local_type;
        bit[7:0] local_device_id;
        //Get the values for the variables in case toplevel
         //test/sequence sets it.
        uvm_config_db#(int   unsigned)::get(null, get_full_name(),
            “sequence_length”, sequence_length);
        uvm_config_db#(usb_transfer::type_enum)::get(null,
            get_full_name(), “local_type”, local_type);
        uvm_config_db#(bit[7:0])::get(null, get_full_name(),?
            “local_device_id”, local_device_id);

        repeat(sequence_length)
        `uvm_do_with(req,   {
            req.device_id   == local_device_id;
            req.type   == local_type;
        })
    endtask : body

endclass

With the above modifications we have given control to the top-level test or sequence to modify the device_id, sequence_length and type. A few things to note here:-  the parameter type and string (third argument) used in uvm_config_db#()::set should be matching the type being used in uvm_config_db#()::get. Make sure to ‘set’ and ‘get’ with exact datatype. Otherwise value will not get set properly, and debugging will become a nightmare.

One problem with the above sequence is: if there are any constraints in the usb_transfer class on device_id or type, then this will restrict the top-level test or sequence to make sure it is within the constraint.

For example if there is a constraint on the device_id in the usb_transfer class, constraining it to be below 10 then top-level test or sequence should constraint it, within this range. If the top-level test or sequence sets it to a value like 15 (which is over 10) then you will see a constraint failure during runtime.

Sometimes the top-level test or sequence may need to take full control, and may not want to enable the constraints which are defined inside the lower level sequences or data items. One example where this is required is negative testing:- the host wants to make sure devices are not responding to the transfer with a device_id greater than 10 and so wants to send a transfer with device_id 15. So to give full control to the top-level test or sequence, we can modify the body task as shown below:-

virtual task body();

    usb_transfer::type_enum local_type;
    bit[7:0] local_device_id;
    int status_seq_len = 0;
    int status_type = 0;
    int status_device_id = 0;

    status_seq_len = uvm_config_db#(int unsigned)::get(null,
        get_full_name(), “sequence_length”, sequence_length);
    status_type = uvm_config_db#(usb_transfer::type_enum)::get(null,
        get_full_name(),“local_type”,local_type);
    status_device_id = uvm_config_db#(bit[7:0])::get(null,
        get_full_name(), “local_device_id”,local_device_id);

    //If status of uvm_config_db::get is true then try to use the values
        // set by toplevel test or sequence instead of the random value.
    if(status_device_id  || status_type)
    begin
        `uvm_create(req)
        req.randomize();
        if(status_type)
        begin
        //Using the value set by top level test or sequence
        //instead of the random value.
            req.type   = local_type;
        end
        if(status_device_id)
        begin
            //Using the value set by top level test or sequence
        //instead of the random value.
            req.device_id   = local_device_id;
        end
    end
    repeat(sequence_length)
        `uvm_send(req)

endtask : body

It is always good to be cautious while using `uvm_do_with as it will add the constraints on top of any existing constraints in a lower level sequence or sequence item.

Also note that if you have more variables to ‘set’ and ‘get’ then I recommend you create the object and set the values in the created object, and then set this object using uvm_config_db from the top-level test/sequence (instead of setting each and every variable inside this object explicitly). This way we can improve runtime performance by not searching each and every variable (when we execute uvm_config_db::get) , and instead get all variables in one shot using the object.

virtual task body();

    usb_simple_sequence local_obj;
    int   status = 0;
    status = uvm_config_db#usb_simple_sequence)::get(null,
        get_full_name(),“local_obj”,local_obj);

    //If status of uvm_config_db::get is true   then try to use
    //the values set in the object we received.
    if(status)
    begin
        `uvm_create(req)
        this.sequence_length   = local_obj.sequence_length;
        //Copy the entire req object inside the object which we
        //received from uvm_config_db   to the local req.
        req.copy   (local_obj.req);
    end
    else
    begin
        //If we did not get the object from top level sequence/test
        //then create one and   randomize it.
        `uvm_create(req)
        req.randomize();
    end
    repeat(sequence_length)
        `uvm_send(req)

endtask : body

  • Always try to reuse the simple sequences by creating a top level sequence for complex scenarios. For example, in below sequence am trying to send bulk transfer followed by an interrupt transfer to 2 different devices. For this scenario I will be using our usb_simple_sequence as shown below:-

class usb_complex_sequence extends uvm_sequence #(usb_transfer);

    //Object of simple sequence   used for sending bulk transfer
    usb_simple_sequence simp_seq_bulk;
    //Object of simple sequence used for sending interrupt transfer
    usb_simple_sequence simp_seq_int;
    …
    virtual task body();
        //Variable for getting device_id for bulk transfer
        bit[7:0] local_device_id_bulk;
        //Variable for getting device_id for   interrupt transfer
        bit[7:0] local_device_id_int;
        //Variable for getting sequence length for   bulk
        int unsigned local_seq_len_bulk;
        //Variable for getting sequence length for   interrupt
        int unsigned local_seq_len_int;

        //Get the values for the variables in case top level
        //test/sequence sets it.

        uvm_config_db#(int unsigned)::get(null, get_full_name(),
        “local_seq_len_bulk”,local_seq_len_bulk);

        uvm_config_db#(int unsigned)::get(null, get_full_name(),
        “local_seq_len_int”,local_seq_len_int);

        uvm_config_db#(bit[7:0])::get(null, get_full_name(),
        “local_device_id_bulk”,local_device_id_bulk);

        uvm_config_db#(bit[7:0])::get(null, get_full_name(),
        “local_device_id_int”,local_device_id_int);

        //Set the values for the variables to the   lowerlevel
        //sequence/sequence item, which we got from
        //above uvm_config_db::get.
        //Setting the values for bulk sequence
        uvm_config_db#(int unsigned)::set(null,   {get_full_name(),”.”,
        ”simp_seq_bulk”}, “sequence_length”,local_seq_len_bulk);
        uvm_config_db#(usb_transfer::type_enum)::set(null, {get_full_name(),
        “.”,“simp_seq_bulk”} , “local_type”,usb_transfer::BULK_TRANSFER);
        uvm_config_db#(bit[7:0])::set(null,   {get_full_name(), “.”,
        ”simp_seq_bulk”}, “local_device_id”,local_device_id_bulk);

        //Setting the values for interrupt   sequence
        uvm_config_db#(int unsigned)::set(null,   {get_full_name(),”.”,
        ”simp_seq_int”}, “sequence_length”,local_ seq_len_int);
        uvm_config_db#(usb_transfer::type_enum)::set(null, {get_full_name(),
        “.”,“simp_seq_int”} , “local_type”,usb_transfer::INT_TRANSFER);
        uvm_config_db#(bit[7:0])::set(null,{get_full_name(),“.”,
        ”simp_seq_bulk”},“local_device_id”,local_device_id_int);

        `uvm_do(simp_seq_bulk)
        simp_seq_bulk.get_response();
        `uvm_send(simp_seq_int)
        simp_seq_int.get_response();
    endtask : body

endclass

Note that in the above sequence, we get the values using uvm_config_db::get from the top level test or sequence, and then we set it to a lower level sequence again using uvm_config_db::set. This is important without this if we try to use `uvm_do_with and pass the values inside the constraint block then this will be applied as an additional constraint instead of setting these values.

I came across these guidelines and learned them, at times the hard way. So I am sharing them here. I sure hope these will come in handy when you use sequences that come pre-packed with VIPs to build more complex scenarios, and also when you wish to write your own sequences from scratch. If you come across more such guidelines or rules of thumb for writing re-usable, maintainable and debuggable sequences, please share them with me.

Posted in UVM, Uncategorized | No Comments »

Webinar on Deploying UVM Effectively

Posted by Shankar Hemmady on 22nd October 2012

Over the past two years, several design and verification teams have begun using SystemVerilog testbench with UVM. They are moving to SystemVerilog because coverage, assertions and object-oriented programming concepts like inheritance and polymorphism allow them to reuse code much more efficiently.  This helps them in not only finding the bugs they expect, but also corner-case issues. Building testing frameworks that randomly exercise the stimulus state space of a design-under-test and analyzing completion through coverage metrics seems to be the most effective way to validate a large chip. UVM offers a standard method for abstraction, automation, encapsulation, and coding practice, allowing teams to build effective, reusable testbenches quickly that can be leveraged throughout their organizations.

However, for all of its value, UVM deployment has unique challenges, particularly in the realm of debugging. Some of these challenges are:

  • Phase management: objections and synchronization
  • Thread debugging
  • Tracing issues through automatically generated code, macro expansion, and parameterized classes
  • Default error messages that are verbose but often imprecise
  • Extended classes with methods that have implicit (and maybe unexpected) behavior
  • Object IDs that are distinct from object handles
  • Visualization of dynamic types and ephemeral classes

Debugging even simple issues can be an arduous task without UVM-aware tools. Here is a public webinar that reviews how to utilize VCS and DVE to most effectively deploy, debug and optimize UVM testbenches.

Link at http://www.synopsys.com/tools/verification/functionalverification/pages/Webinars.aspx

Posted in Coverage, Metrics, Debug, UVM | No Comments »

Using UVM Register Model

Posted by Vidyashankar Ramaswamy on 22nd October 2012

When I was preparing for a customer presentation on UVM RAL, I could not understand what the UVM base class library is saying about updating the values of desired value and the mirror value registers. Also I felt that the terms used are not exactly reflecting the intent. After spending some time, I came up with a table which will help to understand the behavior when the register model APIs are called. . . . .

 For the complete post please visit   http://www.vip-central.org/blog/

Posted in Uncategorized | No Comments »

VCS Built-in TLI connectivity for UVM to SystemC TLM 2.0

Posted by vikasg on 20th September 2012

Vikas Grover | Sr. Manager, Central Verification | AMD-India

One of the challenges faced in SOC verification is to validate the designs in mixed language and mixed  abstraction level. SystemC is widely used language to define the system model at higher level of abstraction.  SystemC is an IEEE standard language for System Level modeling and it is rich with constructs for  describing models at various levels of abstraction i.e. Untimed, Timed, Transaction Level, Cycle Accurate,  and RTL. The transaction level model simulates much faster than RTL model, besides OSCI defined the TLM  2.0 interface standard for SystemC which enables SystemC model interoperability and reuse at transaction  level.

On the other side, SystemVerilog is a unified language for design and verification. It is effective for designing advance testbenches for both RTL and Transaction level models, since it has features like constraint randomization for stimulus generation, functional coverage, assertions, object oriented constructs(like class,inheritance etc). Early availability of standard methodologies (providing framework and testbench coding guidelines for resue) like VMM, OVM, UVM enabled wide adoption for System Verilog in industry. The UVM 1.0 Base Class Library which was   released on Feb 2011  includes OSCI TLM 2.0 socket interface to enable interoperability for UVM with SystemC . Essentially it allows UVM testbench to include SystemC TLM 2.0 reference models. The UVM testbench can pass (or receive) transactions from SystemC models. The transaction passed across System Verilog ßàSystemC could be TLM 2.0 generic payload OR uvm_sequence_item. The implementation of UVM to SC TLM 2.0 communication is vendor dependent.

Starting with with the 2011.03 release, VCS provides a new TLI adaptor which enables UVM TLM 2.0 sockets to communicate with SC TLM 2.0 based environment to pass transactions across language domains.  You can also check out  a couple of earlier post from John Aynsley, (VMM-to-SystemC Communication Using the TLI and  Blocking and Non-blocking Communication Using the TLI) on SV-SystemC communication using TLI.   In this Blog, I am going to describe VCS TLI connectivity mechanism between UVM and SystemC. There are other advance TLI features in VCS ( like direct access of data, invoking task/functions  across SV and SC language),  message unification across UVM-SC, transaction debug techniques, extending TLI adaptor for user defined interface other than VMM/UVM/TLM2.0 which can be written about on later.

With the support for TLM2.0 interfaces in both UVM and VMM, the importance of OSCI TLM2.0 across both SystemC and SystemVerilog is now apparent. UVM provides the following TLM2.0 socket interfaces (for both blocking and non-blocking communication)

  • uvm_tlm_b_initiator_socket
  • uvm_tlm_b_target_socket
  • uvm_tlm_nb_initiator_socket
  • uvm_tlm_nb_target_socket
  • uvm_analysis_port
  • uvm_subscriber

SystemC TLM2.0 consists of following TLM 2.0 interface

  • tlm_initiator_socket
  • tlm_target_socket
  • tlm_analysis_port

The Built-in TLI adaptor solution for VCS is a general purpose solution to simplify the transaction passing across UVM and  SystemC as shown below. The transactions can be TLM 2.0 generic payload OR uvm_sequence_item object. The UVM 1.0 does have the TLM 2.0 generic payload class as well.

The Built-in TLI adaptor is available as a pre-compiled library with VCS. The user would need to follow two simple steps to include the TLI adaptor in his/her verification environment.

  1. Include a header file in System Verilog and SystemC code. The System Verilog header file provides a package which implements the bind function parameterized on uvm_sequence_item object.
  2. Invoke the bind function on System Verilog and SystemC side to connect each socket across language.  The bind function has a string argument which must be unique for each socket connection across System Verilog and SystemC.

The code snippet for above steps is shown below. The TLI adaptor code is highlighted in orange/blue color.  The UVM Initiator  “initiator_udf” from System Verilog is driving SystemC Target “ target_udf” using the  TLM  blocking socket.

The TLI adaptor bind function uses the unique string “str_udf_pkt” to identify the socket connectivity across SystemVerilog and SystemC domain.  For multiple sockets, the user needs to invoke the TLI bind function once for each socket. The TLI adaptor supports both blocking and non-blocking transport interfaces for sockets to communicate across System Verilog and SystemC.

Thus, the Built-in UVM-SC TLI adaptor capability of VCS ensures that SystemC can be connected seamlessly in UVM based verification environment.

Posted in Communication, Interoperability, SystemC/C/C++, Tools & 3rd Party interfaces, Transaction Level Modeling (TLM) | 2 Comments »

Introducing VIP-central.org

Posted by Amit Sharma on 17th August 2012

On August 8, Synopsys announced the launch of VIP-Central.org, a technical community site focused on system-on-chip (SoC) verification engineers and users of verification IP (VIP). This is a new resource which will be meant to provide relevant forums and blogs focused on verification of industry-standard protocols and bus interfaces.  Through the resources and blogs  on this  community driven portal, I expect that  engineers can accelerate their understanding of  intricacies and foibles of each protocol and also effectively leverage industry-available verification IP to verify these protocols while employing methodologies such as UVM, OVM and VMM.

With the increasing number of complex protocols used in SoCs, verification engineers face a tough challenge to quickly acquire the protocol expertise needed to verify a SoC as well as all of its on-chip fabrics and off-chip interfaces. The challenge is made tougher by the frequent release of new and more sophisticated generations of protocols to improve performance, power and quality of service. Verification engineers must complete many tasks requiring both protocol and methodology expertise including developing environments, integrating VIP, using and modifying test sequences, debugging complex protocol results and analyzing coverage data.  The site would  aggregate information from industry experts across the verification community, providing best practices and ideas for better verification performance, protocol debug, methodology, verification planning, coverage management and ease-of-use. I  am sure most of the discussions and blogs in VIP-Central.org would be relevant to the folks who follow the Verification Martial Arts blog and I would encourage everyone to register and start contributing at : http://www.vip-central.org

Posted in Uncategorized | No Comments »

Non blocking communication in TLM2.0

Posted by haribali on 2nd July 2012

In this blog I want to introduce how non-blocking communication works in TLM-2.0. This is same in UVM, VMM and SystemC. Only the names of the functions and arguments may differ.

As name suggests, non-blocking communication returns immediately. Due to this behavior target may or may not process the request immediately when the nb_transport_fw function is called. For this very reason we have backward path using which target can intimate initiator whenever the request is processed using nb_transport_bw method.  We can look at this as 2 function calls one from initiator to target for sending the request and second is from target to initiator for sending the response once the request is processed.

If this is as simple as shown above then why do we need an additional argument in form of phase and a return type. Let’s see what these additional arguments are conveying. I have used the term “partner” (as in link partner) in the blog to identify either target or initiator.

Phase argument in the nb_transport function calls is used to communicate the phase of this transaction. Phase can be one of the following

BEGIN_REQ: This is sent by initiator along with the transaction to tell the target that this is a new request.

END_REQ: This is sent by target to tell the initiator that request has accepted but not yet processed.

BEGIN_RESP: This is sent by target to tell initiator that request is processed and this is the response for the corresponding request.

END_RESP: This is sent by the initiator to tell target that response has been accepted. With this the transaction is complete.

Return type for the nb_transport function calls is used to tell the partner (initiator/target, whoever initiated the call) whether the transaction is processed at this stage or not.  Return type can be one of the following

TLM_ACCEPTED: This is used to tell the partner (Initiator/target, whoever initiated the call) that transaction is accepted but processing has not yet started. Whenever this is returned we can simple ignore the arguments as they are not changed.

Example:

  • Target returns TLM_ACCEPTED when it has accepted the request sent by the initiator but not processed yet. Initiator need not process the function arguments as they are not changed by the target.

TLM_UPDATED: This is returned to tell the partner (initiator/target, whoever initiated the call) that the transaction is accepted and the function arguments are modified.

Example:

  • Target returns TLM_UPDATED when it has accepted the request sent by the initiator and modified the function arguments. Initiator need to take action depending on the phase argument.

TLM_COMPLETED: This is returned to tell the partner (initiator/target, whoever initiated the call) that the transaction is completed and the function arguments are modified.

Example:

  • Target returns TLM_COMPLETED when it has accepted the request sent by the initiator and completed processing it. Initiator need to take action depending on the phase argument.

In summary, non-blocking interface has a protocol associated with it which is used to make sure each transaction is properly processed and communicated the response back. If you see the blocking interface, this is not required as initiator waits for target to complete the transaction. In non-blocking there would be no way for initiator to know if the target processed the transaction or not without this protocol.

Non-blocking coding style is generally used in coding accurate models in virtual prototypes. If you are planning to use TLM models from virtual platform to speed up your simulations or for any other reasons then you will end up connecting the non-blocking TLM models to your verification environment where non-blocking interface will be useful. I welcome you to share other use cases of non-blocking interface.

Posted in Transaction Level Modeling (TLM), UVM | No Comments »

Build your own code generator!

Posted by Amit Sharma on 25th June 2012

One of the critical problems developers cope with during SoC development process (architecture plan, RTL design, verification, software development) is the constant need to synchronize between different forms of the same data structure: the SoC registers database. The SoC registers database can be found at the SoC architecture team (who write the SoC registers description document), design engineers (who implement the registers structure in RTL code), verification engineers (who write the verification infrastructure – such as RAL code, and write verification tests – such as exhaustive read/write tests from all registers), and software engineers (who use the registers information to write embedded software).

Since the same information is used in different forms, it is only natural to have a single, central database which will hold all SoC registers data. You would ideally like to generate all the required output files  (documentation, UVM or VMM Regsiter Abstraction Model, RTL, C headers, …) from this central database ..  Different vendors and CAD teams provide different automation solutions for doing this.

The RALF specification contains all of the necessary information to generate RTL and testbench  code that implements the specified registers. There are many ways to code and implement RTL, so it is not possible to provide a general purpose solution.  As far as the testbench abstraction model is concerned, there are multiple ways of customizing your model post generation in both UVM and VMM. Callbacks, factories, configurable RAL model  attributes are some of the ways through which the desired customization can be brought in.  “The ‘user’ in RALF : get ralgen to generate ‘your’ code” highlights a very convenient way of adding of bringing in  SystemVerilog compatible code which will be copied as-is into the RAL model and in the desired scope. When it comes down to generating the RTL and the ‘C’ headers, we cannot leave the customization to such a late stage.  Also, different organizations and project groups have their own RTL and C-code coding styles which means  a generated output of a very generic nature might not be very helpful. For RTL generation, engineers would want the generated code to be power and gate-count efficient. Similar for C registers header generation, it often needs to follow coding styles and match CPU Firmware API. How do we bring in all this customizations to the end user?

Using the RALF C++ API, you have full access to parsed RALF data (through C++ routines), which you can use to implement a customized RTL code generator, or any other feature that needs RALF data. So, you can use  it to generate your C header files, HTML documentation, or translate the i/p RALF files to another register description format, custom covergroups and coverage convergence sequences (DAC 2012 User Track poster 6U.8 — Register Verification on a Fast Lane: Using Automation to Converge on UVM REG Coverage Models)

I have seen two instances of the need to generate a different register specification in the recent past and that is one of the reasons I decided to put this down in a blog.   Let me talk about the first instance..

One of the project groups were in the process of migrating from their own SV base classes to UVM. They had their own Register description format from which they used to generate their Register Abstraction Model. This was a proven flow.

So, when they migrated to UVM , they wanted to have a flow which would validate the changes that they are doing..

Given that they were moving to using RALF and ‘ralgen’, they didn’t want to create Register Specification in the legacy format anymore. So, they wanted to have some automation for generating scripts in the earlier format.  So, how did they go about doing this?..   They took the RAL C++ APIs and using them there were able to create the necessary automation to to generate the legacy format from RALF in no time.. (From what I remember, it was a half days work).. Everyone were involved in doing what they were best at, and that helped in the overall scheme of things.

The other customer had their own format from which they were generating RTL, firmware code and HTML. They had the necessary automation to create RALF for generating the UVM register Model. They also had a mechanism in place to generate IPXACT from this format as well as vice versa.. So, to complete the traceability matrix, they wanted a RALF  to IPXACT conversion.. Again, the most logical approach was to take the RALF CPP APIs and get those to iterate through the parsed RALF data and generate IPXACT.. Though, this effort is not complete, it just took a day or so to be able to generate valid IPXACT1.5 schema and all that is required now is some more additional work to smoothen the corners.

How do you start using these APIs and build your own code/html generators? You need to include ‘ralf.hpp” (which is in $VCS_HOME/include) in your ‘generator’ block. And then to compile the code, you need to pick up the shared library libralf.so from the VCS installation.

$CPP_COMPILER $CFLAGS -I${VCS_HOME}/include –L${VCS_HOME}/lib –lralf your-file.cpp $LDFLAGS

#include
#include "ralf.hpp"
int main(int argc, char *argv[])
{
// Check basic command line usage…
if (argc < 3) {
fprintf(stderr, "Error: Wrong Usage.\n");
// Show Correct Usage …
exit(1);
}
/**
* Parse command line arguments to get the essential
* constructor arguments. See documentation
* of class ralf::vmm_ralf’s constructor parameters.
*/
{

/**
* Create a ralf::vmm_ralf object by passing in proper
constructor arguments. */
ralf::vmm_ralf ralf_data(ralf_file, top, rtl_dir,
inc_dir);
/**
* Get the top level object storing the parsed RALF
* block/system data and traverse that, top-down, to get
* access to complete RALF data.
*/
const ralf::vmm_ralf_blk_or_sys_typ * top_lvl_blk_or_sys
= ralf_data.getTopLevelBlockOrSys();
#ifndef GEN_RTL_IN_SNPS_STYLE
/*
* TODO–Traverse the parsed RALF data structure top-down
* using/starting-from ‘top_lvl_blk_or_sys’ for getting
* complete access to the RALF data and then, do whatever
* you would want to do, with the parsed RALF data. One
* typical usage of parsed RALF data could be, to generate
* RTL code in your own style.
*/
//
// TODO – Add your RTL generator code here.
//
#else
/*
* As part of this library, Synopsys also provides a
* default RTL generator, which can be invoked by
* invoking ‘generateRTL()’ method of ‘ralf::vmm_ralf’
* class, as demonstrated below.
*/
ralf_data.generateRTL();
#endif
}

Essentially, you have a handle to the parsed database, and with the available APIs you can do whatever you want with it :) ..  The documentation of the APIs are in the documentation shipped with the VCS installation.. Also, if you are like me and would rather hack away at existing code rather than start with something from scratch, you can just check with Synopsys support to give you existing templates to dump out code in specific format, and you can starting modifying that for your requirements..

Posted in Automation, Coverage, Metrics, Customization, Interoperability, Register Abstraction Model with RAL, Tools & 3rd Party interfaces, UVM, VMM infrastructure | No Comments »

DVE and UVM on wheels

Posted by Yaron Ilani on 22nd May 2012

Sometimes driving to work can be a little bit boring so a few days ago I decided to take advantage of this time slot to introduce myself and tell you a little bit about the behind-the-scenes of my video blog. Hope you’ll like it !

Hey if you’ve missed any of my short DVE videos – here they are:

Posted in Debug, UVM, Verification Planning & Management | No Comments »

Are You Afraid of Breakpoints?

Posted by Yaron Ilani on 17th May 2012

Are you afraid of breakpoints? Don’t worry, many of us have been.  After all, breakpoints are for software folks, not for us chip heads, right??
Well, not really… In many ways, chip verification is pretty much a software project.
Still, most of the people I know fall into one of these two categories – the $display person, or the breakpoints person.

The former doesn’t like breakpoints. He or she would rather fill up their code with $display’s and UVM_INFO’s and recompile their code every time around.
That’s cool.
The latter likes and appreciates breakpoints and uses them whenever possible instead of traditional $display commands.

So if you are the second type – here’s some great news from DVE that will help you be even more efficient.
And $display folks – stay tuned as it may be time for you to finally convert :-)

In this short video I show how to use Object ID’s – a new infrastructure in DVE – to break in a specific class instance !

 

Want more ?

Go here to catch up with our latest DVE and UVM short videos:
http://www.youtube.com/playlist?list=PL06DA5270480FC7E1

Posted in Debug, SystemVerilog, UVM, Uncategorized | No Comments »

Namespaces, Build Order, and Chickens

Posted by Brian Hunter on 14th May 2012

As described in the video, vkits are our convenient method of lumping together reusable UVM packages with the interface(s) that they operate on. Because code within packages can only peek or poke wires that are contained by a virtual interface, it is often useful to wrap these together somehow, and vkits are our technique at Cavium for doing that.

What goes in a vkit? Anything that is reusable. From simple agents and the interfaces they work on to complete UVM environments that connect these agents together, including scoreboards, sequence libraries, types, and utility functions.

What does not go in a vkit are items that are bound to a specific testbench, including the tests themselves.

The video describes the wildcard import syntax as an “egregiously bad idea.” First and foremost, doing so can lead to namespace pollution, which comes about when one engineer independently adds types or classes to their package and only later finds out that they conflict with those of another package. Secondly, wildcard imports prevent our shorter naming conventions of having an agent_c, drv_c, env_c, etc., within each package.

Not described in the video are CSR packages that are auto-generated by RAL, IP-XACT, or your script of choice. These packages should be independent of your vkits, such that your vkits refer to them with their explicit scopes (i.e., chx_csr_pkg::PLUCKING_CFG_C).

Future posts will go into more detail about how we architect UVM testbenches and some of our other conventions that work within this framework. Until then, I’ve got a lot of pies to eat.

PS. I’ll be at DAC this year! Come see me on Tuesday, June 5, during the “Industry Leaders Verify with Synopsys” lunch. Hopefully they’ll be serving some of my favorite foods!

Posted in Organization, Structural Components, SystemVerilog, Tutorial, UVM | 6 Comments »

Customizing UVM Messages Without Getting a Sunburn

Posted by Brian Hunter on 24th April 2012

The code snippets presented are available below the video.

my_macros.sv:

   `define my_info(MSG, VERBOSITY) \
      begin \
         if(uvm_report_enabled(VERBOSITY,UVM_INFO,get_full_name())) \
            uvm_report_info(get_full_name(), $sformatf MSG, 0, `uvm_file, `uvm_line); \
      end

  `define my_err(MSG)         \
      begin \
         if(uvm_report_enabled(UVM_NONE,UVM_ERROR,get_full_name())) \
            uvm_report_error(get_full_name(), $sformatf MSG, UVM_NONE, `uvm_file, `uvm_line); \
      end

   `define my_warn(MSG)        \
      begin \
         if(uvm_report_enabled(UVM_NONE,UVM_WARNING,get_full_name())) \
            uvm_report_warning(get_full_name(), $sformatf MSG, UVM_NONE, `uvm_file, `uvm_line); \
      end

   `define my_fatal(MSG)       \
      begin \
         if(uvm_report_enabled(UVM_NONE,UVM_FATAL,get_full_name())) \
            uvm_report_fatal(get_full_name(), $sformatf MSG, UVM_NONE, `uvm_file, `uvm_line); \
      end

my_init.sv:

  initial begin
      my_report_server_c report_server = new("my_report_server");

      if($value$plusargs("fname_width=%d", fwidth)) begin
         report_server.file_name_width = fwidth;
      end
      if($value$plusargs("hier_width=%d", hwidth)) begin
         report_server.hier_width = hwidth;
      end

      uvm_pkg::uvm_report_server::set_server(report_server);

      // all "%t" shall print out in ns format with 8 digit field width
      $timeformat(-9,0,"ns",8);
   end

my_report_server.sv:

class my_report_server_c extends uvm_report_server;
   `uvm_object_utils(my_report_server_c)

   string filename_cache[string];
   string hier_cache[string];

   int    unsigned file_name_width = 28;
   int    unsigned hier_width = 60;

   uvm_severity_type sev_type;
   string prefix, time_str, code_str, fill_char, file_str, hier_str;
   int    last_slash, flen, hier_len;

   function new(string name="my_report_server");
      super.new();
   endfunction : new

   virtual function string compose_message(uvm_severity severity, string name, string id, string message,
                                           string filename, int line);
      // format filename & line-number
      last_slash = filename.len() - 1;
      if(file_name_width > 0) begin
         if(filename_cache.exists(filename))
            file_str = filename_cache[filename];
         else begin
            while(filename[last_slash] != "/" && last_slash != 0)
               last_slash--;
            file_str = (filename[last_slash] == "/")?
                       filename.substr(last_slash+1, filename.len()-1) :
                       filename;

            flen = file_str.len();
            file_str = (flen > file_name_width)?
                       file_str.substr((flen - file_name_width), flen-1) :
                       {{(file_name_width-flen){" "}}, file_str};
            filename_cache[filename] = file_str;
         end
         $swrite(file_str, "(%s:%6d) ", file_str, line);
      end else
         file_str = "";

      // format hier
      hier_len = id.len();
      if(hier_width > 0) begin
         if(hier_cache.exists(id))
            hier_str = hier_cache[id];
         else begin
            if(hier_len > 13 && id.substr(0,12) == "uvm_test_top.") begin
               id = id.substr(13, hier_len-1);
               hier_len -= 13;
            end
            if(hier_len < hier_width)
               hier_str = {id, {(hier_width - hier_len){" "}}};
            else if(hier_len > hier_width)
               hier_str = id.substr(hier_len - hier_width, hier_len - 1);
            else
               hier_str = id;
            hier_str = {"[", hier_str, "]"};
            hier_cache[id] = hier_str;
         end
      end else
         hier_str = "";

      // format time
      $swrite(time_str, " {%t}", $time);

      // determine fill character
      sev_type = uvm_severity_type'(severity);
      case(sev_type)
         UVM_INFO:    begin code_str = "%I"; fill_char = " "; end
         UVM_ERROR:   begin code_str = "%E"; fill_char = "_"; end
         UVM_WARNING: begin code_str = "%W"; fill_char = "."; end
         UVM_FATAL:   begin code_str = "%F"; fill_char = "*"; end
         default:     begin code_str = "%?"; fill_char = "?"; end
      endcase

      // create line's prefix (everything up to time)
      $swrite(prefix, "%s-%s%s%s", code_str, file_str, hier_str, time_str);
      if(fill_char != " ") begin
         for(int x = 0; x < prefix.len(); x++)
            if(prefix[x] == " ")
               prefix.putc(x, fill_char);
      end

      // append message
      return {prefix, " ", message};
   endfunction : compose_message
endclass : my_report_server_c

Posted in Debug, Messaging, SystemVerilog, UVM, Verification Planning & Management | 1 Comment »

Leveraging UVM for verifying High-speed IOs with Verilog-AMS

Posted by Shankar Hemmady on 17th April 2012

When the industry decided to standardize on UVM, we had high hopes that some day we will be able to use the standard to raise the level of abstraction and solve many open issues. Take the case of AMS verification of High-speed IOs, which has largely been the turf of hand-crafted custom designs verified mostly with directed tests.

Warren Anderson and his team at AMD here describe a simple, innovative approach they used with UVM and Verilog-AMS running VCS and CustomSim/XA… yes, UVM really works wonders when used prudently with the right flow.

http://blogs.synopsys.com/analoginsights/2012/04/17/uvm-based-random-verification-using-customsim-vcs-for-analog-mixed-signal-designs/

Posted in AMS, UVM | No Comments »

Using the VMM Datastream Scoreboard in a UVM environment

Posted by Amit Sharma on 2nd February 2012

Implementing the response checking mechanism in a self-checking environment remains the most time-consuming task. The VMM Data Stream Scoreboard package facilitates the implementation of verifying the correct transformation, destination and ordering of ordered data streams. This package is intuitively applicable to packet-oriented design, such as modems, routers and protocol interfaces. This package can also be used to verify any design transforming and moving sequences of data items, such as DSP data paths and floating-point units. Out-of-the-box, the VMM data stream scoreboard can be used to verify single-stream designs that do not modify the data flowing through them. For example, it can be used to verify FIFOs, Ethernet media access controllers (MACs) and bridges.

The VMM data scoreboard can also be used to verify multi-stream designs with user-defined data transformation and input-to-output stream routing. The transformation from input data items into expected data items is not limited to one-to-one transformation. An input data item may be transformed into multiple expected data items (e.g. segmenters) or none (e.g. reassemblers). Compared to this, the functionality available through UVM in-order comparator or the algorithmic comparator is significantly less. Thus, users might want to have access to the functionality provided by the VMM DS Scoreboard in a UVM environment. Using the UBUS example available in $VCS_HOME/doc/examples/uvm/integrated/ubus as a demo vehicle, this article shows how simple adapters are used to integrate the VMM DS scoreboard in a UVM environment and thus get access to more advanced scoreboarding functionality within the UVM environment

The UBUS example uses an example scoreboard to verify that the slave agent is operating as a simple memory. It extends from the uvm_scoreboard class and implements a memory_verify() function to makes the appropriate calls and comparisons needed to verify a memory operation. An uvm_analysis_export is explicitly created and implementation for ‘write’ defined. In the top level environment, the analysis export is connected to the analysis port of the slave monitor.

ubus0.slaves[0].monitor.item_collected_port.connect(scoreboard0.item_collected_export);

The simple scoreboard with its explicit implementation of the comparison routines suffices for verifying the basic operations, but would require to be enhanced significantly to provide more detailed information which the user might need. For example, lets take the ‘test_2m_4s’ test. Here , the environment is configured to have 2 Masters and 4 slaves.. Depending on how the slave memory map is configured, different slaves respond to different transfers on the bus. Now, if we want to get some information on how many transfer went into the scoreboard for a specific combination (eg: Master 1 to Slave 3), how many were verified to be processed correctly etc, it would be fair enough to conclude that the existing scoreboarding schemes will not suffice..

Hence, it was felt that the Data Stream Scoreboard with its advanced functionality and support for data transformation, data reordering, data loss, and multi-stream data routing should be available for verification environments not necessarily based on VMM. From VCS  2011.12-1, this integration have meed made very simple.  This VMM DS scoreboard implements a generic data stream scoreboard that accepts parameters for the input and output packet types. A single instance of this class is used to check the proper transformation, multiplexing and ordering of multiple data streams. The scoreboard class now  leverages a policy-based design and parameterized specializations to accepts any ‘Packet’ class or d, be it VMM, UVM or OVM.

The central element in policy-based design is a class template (called the host class, which in this case in the VMM DS Scoreboad), taking several type parameters as input, which are specialized with types selected by the user (called policy classes), each implementing a particular implicit method (called a policy), and encapsulating some orthogonal (or mostly orthogonal) aspect of the behavior of the instantiated host class. In this case, the ‘policies’ implemented by the policy classes are the ‘compare’ and ‘display’ routines.

By supplying a host class combined with a set of different, canned implementations for each policy, the VMM DS scoreboard can support all different behavior combinations, resolved at compile time, and selected by mixing and matching the different supplied policy classes in the instantiation of the host class template. Additionally, by writing a custom implementation of a given policy, a policy-based library can be used in situations requiring behaviors unforeseen by the library implementor .

So, lets go through a set of simple steps to see how you can use the VMM DS scoreboard in the UVM environment

Step 1: Creating the policy class for UVM and define its ‘policies’

image

Step 2: Replacing the UVM scoreboard with a VMM one extended from “vmm_sb_ds_typed” and specialize it with the ubus_transfer type and the previous created uvm_object_policy.

class ubus_example_scoreboard extends vmm_sb_ds_typed #(ubus_transfer,ubus_transfer, uvm_object_policy);

`vmm_typename(ubus_example_scoreboard)

endclass: ubus_example_scoreboard

Once, this is done, you can either declare an VMM TLM Analysis export to connect to the Bus Monitor in the UBUS environment or use the pre-defined on in the VMM DS scoreboard

vmm_tlm_analysis_export #(ubus_example_scoreboard,ubus_transfer) analysis_exp;

Given that for any configuration, one master and slave would be active, define the appropriate streams in the constructor (though this is not required if there are only single streams, we are defining this explicitly so that this can scale up to multiple input and expect streams for different tests)

this.define_stream(0, “Slave 0″, EXPECT);
this.define_stream(0, “Master 0″, INPUT);

Step 2 .a: Create the ‘write’ implementation for the Analysis export

Since, we are verifying the operation of the slave as a simple memory, we just add in the appropriate logic to insert a packet to the scoreboard when we do a ‘WRITE’ and an expect/check when the transfer is a ‘READ’ with an address that has already been written to.

image

Step 2.b: Implement the stream_id() method

You can use this method to determine to which stream a specific ‘transfer’ belongs to based on the packet’s content, such as a source or destination address. In this case, the BUS Monitor updates the ‘slave’ property of the collected transfer w.r.t where the address falls on the slave memory map.

image

image

Step 3: Create the UVM Analysis to VMM Analysis Adapter

The uvm_analysis_to_vmm_analysis is used to connect any UVM component with an analysis port to any VMM component via an analysis export. The adapter will convert all incoming UVM transactions to a VMM transaction and drive this converted transaction to the VMM component through the analysis port-export. If you are using the VMM UVM interoperability library, you do not have to create the adapter as it will be available in the library

image

image

Create the ‘write’ implementation for the analysis export in the adapter

The write method, called via the <analysis_export> would just post the receive UBUS transfer from the UVM analysis port to the VMM analysis port.

image

Step 4: Make the TLM connections

In the original example, the item_collected_port of the slave monitor was connected to the analysis export of the example scoreboard. Here, the DataStream scoreboard has an analysis port which expects a VMM transaction. Hence, we need the adapter created above to intermediate between the analysis port of the UVM Bus monitor and the analysis export of the VMM DS scoreboard..

image

Step 5: Define Additional streams if required for multi-master multi-slave configurations

This step is not required for a single master/slave configuration. However, would need to create additional streams so that you can verify the correctness on all the different permutations in terms of tests like “test_2m_4s” .

In this case, the following is added in the test_2m_2s in the connect_phase()

image

Step 6: Add appropriate options to your compile command and analyze your results

Change the Makefile by adding –ntb_opts rvm on the command line and add +define+UVM_ON_TOP

vcs -sverilog -timescale=1ns/1ns -ntb_opts uvm-1.1+rvm +incdir+../sv ubus_tb_top.sv -l comp.log +define+UVM_ON_TOP

And that is all, as far and you are ready to go and validate your DUT with a more advanced scoreboard with loads of built-in functionality. This is what you will get when you execute the “test_2m_4s” test

Thus, not only do you have stream specific information now, but you now have access to much more functionality as mentioned earlier. For example, you can model transformations, checks for out of order matches, allow for dropped packets, and iterate over different streams to get access to the specific transfers. Again, depending on your requirements, you can use the simple UVM comparator for your basic checks and switch over to the DS scoreboard for the more complex scenarios with the flip of a switch in the same setup. This is what we did for a UVM PCIe VIP we developed earlier ( From the Magician’s Hat: Developing a Multi-methodology PCIe Gen2 VIP) so that the users has access to all the information they require. Hopefully, this will keep you going, till we have a more powerful UVM scoreboard with some subsequent UVM version

Posted in Communication, Interoperability, Reuse, Scoreboarding, UVM, VMM infrastructure | 2 Comments »

Why do we need an integrated coverage database for simulation and formal analysis?

Posted by Shankar Hemmady on 23rd January 2012

Closing the coverage gap has been a long-standing challenge in simulation-based verification, resulting in unpredictable delays while achieving functional closure. Formal analysis is a big help here. However, most of the verification metrics that give confidence to a design team are still governed by directed and constrained random simulation. This article describes a methodology that embraces formal analysis along with dynamic verification approaches to automate functional convergence: http://soccentral.com/results.asp?CatID=488&EntryID=37389

I would love to learn what you do to attain functional closure.

Posted in Coverage, Metrics, Formal Analysis, Verification Planning & Management | No Comments »

How do I debug issues related to UVM objections?

Posted by Vidyashankar Ramaswamy on 19th January 2012

Recently one of the engineers I work with in the networking industry was describing the challenges in debugging the UVM timeout error message. I was curious and looked into his test bench. After spending an hour or so, I was able to point out the master/slave driver issue where the objection was not dropped and the simulation thread hung waiting for the objections to drop. Then I started thinking, why not use the run time option to track the status of the objection: +UVM_OBJECTION_TRACE? Well, this printed detailed messages about the objections, a lot more than what I was looking for! The problem now was to decipher the overwhelming messages spitted by the objection trace option! In a hierarchical test bench, there could be hundreds of component, and you might be debugging some SoC level test bench which you didn’t write or are familiar with. Here is an excerpt of the message log using the built in objection trace:

….
VM_INFO @ 0: reset [OBJTN_TRC] Object uvm_test_top.env.master_agent.mast_drv dropped 1 objection(s): count=0 total=0
UVM_INFO @ 0: reset [OBJTN_TRC] Object uvm_test_top.env.master_agent.mast_mon raised 1 objection(s): count=1 total=1UVM_INFO @ 0: reset [OBJTN_TRC] Object uvm_test_top.env.master_agent added 1 objection(s) to its total (raised from source object ): count=0 total=2
UVM_INFO @ 0: reset [OBJTN_TRC] Object uvm_test_top.env added 1 objection(s) to its total (raised from source object ): count=0 total=2
….
UVM_INFO @ 0: reset [OBJTN_TRC] Object uvm_test_top.env.master_agent.mast_mon dropped 1 objection(s): count=0 total=0
UVM_INFO @ 0: reset [OBJTN_TRC] Object uvm_test_top.env.slave_agent.drv raised 1 objection(s): count=1 total=1
….

As a verification engineer, you want to begin debugging the component or part of the test bench code which did not lower the objection as soon as possible. You want to minimize looking into the unfamiliar test bench code as much as possible or stepping through the code using a debugger.

The best way is to call the display_objections() just before timeout has been reached. As there is no callback available in the timeout procedure, I thought of writing the following few lines of code which can be forked off in any task based phase. I would recommend doing this in your base test which can be extended to create feature-specific tests. You can save some CPU processing cycles by coding this into a run time option:

top = uvm_root::get();
fork
begin
#(top.phase_timeout -1);
phase.phase_done.display_objections();
end
join_none

Output of the log message using the above code is shown below:

————————————————————————————
The total objection count is 2
————————————————————————————
Source              Total
Count               Count             Object
————————————————————————————
0                          2                   uvm_top
0                          2                       uvm_test_top
0                          2                           env
0                          1                               master_agent
1 1 mast_drv
0                          1                               slave_agent
1 1 slv_drv
———————————————————————————-

From the above table, it is clear that the master and slave driver did not drop the objection. Now you can look into the master and slave driver components, and further debug why these components did not drop their objection. There are many different ways to achieve the same results. I welcome you to share your thoughts and ideas on this.

Posted in Debug, Messaging, UVM | No Comments »