HOME    COMMUNITY    BLOGS & FORUMS    VIP Central
VIP Central
 

PCIe: Accelerating Debug

Posted by VIP Experts on April 23rd, 2015

In this video, Paul Graykowski of Synopsys gives an overview of the PCI Express VIP’s capabilities that will support your efforts to accelerate the debug process:

You can learn more about our VIPs at Verification IP Overview, or download the Datasheet for PCIe and MPCIe.

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

The HDCP 2.2 Authentication Process – an Introduction

Posted by VIP Experts on April 21st, 2015

When digital content is transmitted, it is susceptible to unauthorized copying and interceptions. Hence protecting content has become an important factor in the transmission of audiovisual content. In 2003, Intel developed an encryption technique called the High-bandwidth Digital Content Protection (HDCP) protocol to protect audio and video data between a transmitter (transmitting the audio visual content such as a Blu-ray player) and a receiver such as a Monitor. If a transmitting device is transmitting the content HDCP protected then the receiver must also support HDCP in order to receive the content correctly.

You can learn more about the HDCP 2.2 Authentication Process by downloading our whitepaper, Demystifying the HDCP 2.2 Authentication Process.

HDCP protocol is now managed by Digital Content Protection (DCP), LLC, an Intel subidiary, which licenses technologies for the protection of commercial digital content. For every HDCP protected digital content must follow the HDCP protocol and also must have a license issued by DCP, LLC.

History of HDCP
In earlier devices that support the 1.X version of HDCP, such as HDCP1.4, the receiver demonstrates that it has valid secret keys, device private key. Transmitter authenticates that the receiver has valid keys, and then both devices share a secret session key that will be used during encryption as depicted in Figure 1. The authentication strength was reasonable using SHA-1 encryption algorithm in key exchanges. Most of the authentication and encryption was proprietary between devices that support HDCP1.4. Encryption uses a proprietary stream cipher.

HDCP-1-X

Introduction to HDCP2.2
HDCP 2.2 specification applies state of the art cryptography standards, such as RSA and AES, and uses them in authentication and encryption respectively which makes it much more secure than the previous HDCP1.X protocols.

HDCP 2.2 protocol works in 3 phases: the first phase, Authentication, is to verify that the receiver is genuine and authorized to receive the digital content. During the second phase, Encryption, transmitter can start sending the encrypted data to receiver, which will then decrypt it using keys exchanged during the authentication step. In the event that legitimate devices are compromised, the third phase, Renewability, allows the HDCP transmitters to identify such compromised devices and prevent transmission of HDCP content.

HDCP2.2 Authentication Protocol
Before transmitting the audio visual content, the transmitter must make sure using the authentication protocol that the receiver is genuine and authorized to receive the protected content.

The Authentication Protocol consists of:
1. Authentication and key exchange (AKE): Checks that the receiver contains a valid un-revoked public key certificate.
2. Locality Check: A check to make sure that the receiver is placed nearby and restricts the transmission to a locality.
3. Session key exchange (SKE): A common shared session key is exchanged which will be used to encrypt the data itself.
4. Authentication with repeater: An option step when sink is a repeater i.e., Subsequent sink device can be attached. Transmitter checks that none of the receivers in the topology is un-authorized.

In the next blog post, we will discuss the basics of RSA cryptography. An advanced version of RSA is the underlying cryptography standard used during the Authentication and key exchange.

You can learn more about the HDCP 2.2 Authentication Process by downloading our whitepaper, Demystifying the HDCP 2.2 Authentication Process.

Posted in HDCP, HDMI, Methodology | No Comments »

PCIe VIP: Accelerating Verification

Posted by VIP Experts on April 16th, 2015

In this video, Paul Graykowski of Synopsys gives an overview of the PCI Express VIP’s capabilities that will support your efforts to accelerate the verification process: http://bit.ly/1CWit0q

You can learn more about our VIPs at Verification IP Overview, or download the Datasheet for PCIe and MPCIe.

Posted in Methodology, PCIe, SystemVerilog, UVM | No Comments »

MIPI Soundwire: Digital Audio Simplified

Posted by VIP Experts on April 14th, 2015

MIPI Alliance has come up with a new protocol standard for sound interface called SoundWire. SoundWire is a robust, scalable, low complexity, low power, low latency, two-pin (clock and data) multi-drop bus that allows for the transfer of multiple audio streams and embedded control/commands. To understand its specification and design verification needs, it is important to understand basics of digital audio transmission.

Traditionally, sound is recorded and transmitted as analog signals. However analog signal is sensitive to noise: it is difficult to send analog signals over a long distance without introducing significant noise. It is also expensive to transmit high quality analog signals over short distances. The solution to this problem is digitization of analog signals: send it in digital form, and again convert it to analog for sound reproduction. This process involves a number of steps and adds complexity to the system, but it is more reliable and cost effective. If designed properly, the degradation of signal due to digitization can be insignificant. 

You can learn more about the common digital audio formats, Pulse Code Modulation (PCM) and Pulse Density Modulation (PDM), which are target applications for MIPI Soundwire, by downloading our whitepaper.

PCM

Pulse Code Modulation (PCM)

PDM

Pulse Density Modulation (PDM)

 

Posted in Methodology, MIPI | No Comments »

Freescale and Xilinx Engineers: Managing SoC Verification Complexity

Posted by VIP Experts on April 9th, 2015

At DVCon 2015, a couple of our key customers shared their viewpoints on how they manage growing verification complexity. This video begins with Michael Sanie highlighting the Synopsys Verification Continuum, and several key technologies that currently address the industry’s need to “Shift-Left” for faster time-to-market. Later, Amol Bhinge of Freescale and Prashanth Gurunath of Xilinx share how their leading SoC design teams have achieved success by collaborating with Synopsys.

michael-sanie-dvcon-2015
Industry Leaders Verify with Synopsys
Michael Sanie, Senior Director of Verification Marketing
Synopsys


prashanth-gurunath-dvcon-2015
Addressing Unique Verification Challenges of Mixed-signal with VCS AMS
Prashanth Gurunath, Sr. Member Technical Staff Engineer
Xilinx


amol-bhinge-dvcon-2015
Reducing SoC Verification and Software Validation Turn Around Time
Amol Bhinge, Senior SoC Verification Manager
Freescale


See more at: http://www.synopsys.com/Tools/Verification/FunctionalVerification/Pages/dvcon-2015-video.aspx#sthash.unWqj90D.dpuf

Please let us know if you are interested in hearing more such stories and viewpoints from leading engineering teams.

Posted in AMBA, DDR, Debug, DesignWare, HDMI, LPDDR, Methodology, MIPI, PCIe, SystemVerilog, Test Suites, USB, UVM | No Comments »

PCIe Verification IP Overview

Posted by VIP Experts on April 7th, 2015

In this video, VIP Senior Manager Paul Graykowski of Synopsys gives an overview of the PCI Express Verification IP: http://bit.ly/1DTe6si

You can learn more about our VIPs at Verification IP Overview, or download the Datasheet for PCIe and MPCIe.

Posted in Debug, PCIe, SystemVerilog, Test Suites, UVM | No Comments »

AMBA: Stitch it all up to ACE your Test

Posted by VIP Experts on April 2nd, 2015

The ARM® AMBA4® specification for the connection and management of functional blocks in a system-on-chip (SoC) now features Advanced eXtensible Interface (AXI)™ coherency extensions (ACE)™ in support of multi-core computing. The ACE specification enables system-level cache coherency across clusters of multi-core processors. When planning the functional verification of such a system, these coherency extensions bring their own complex challenges, such as system-level cache coherency validation and cache state transition validation. At any given time, it’s important to verify that the ACE interconnect can maintain cache coherency across the different ACE masters in the system. With coherency support now in the hardware, together with an associated support protocol, the complexity of the system and the underlying components has increased substantially.  The verification of such systems thus faces several challenges. Let’s get a grasp of the requirements on the stimulus generation infrastructure  for validating a cache coherent system.

Here’s where you can find more information on our Verification IP.

An AMBA ACE interconnect-based system can have a variety of masters and slaves connected by a coherent interconnect. Individually, each master and slave component can support a complete ACE, ACE-Lite™, AXI4™ or AXI3™ protocol and can work with a different bus widths or clock frequencies. The different permutations involve the following parameters:  cache states, transaction types, burst lengths and sizes, snoop mechanisms, snooped cache states, snoop responses, support for speculative fetches, support for snoop filtering, and user-specified scheduling of interconnect.

All these cross combinations lead to a very large verification space, creating four key challenges:

  1. Generating stimulus mapped to all of these include, ensuring each individual master, slave or interconnect is fully compatible with the protocol it supports;
  2. Ensuring all possible combinations of concurrent access among initiating masters, snooped masters and slave main memory are verified and are in compliance with the ACE specification;
  3. Ensuring all user-specific features are covered and working as expected; and
  4. Ensuring the completeness of verification


How do you go about creating a stimulus generation infrastructure that can handle all these?

Here we try to show you how it can be done? The idea is to ensure you have all the right building blocks to start with and progressively organize them and stitch them together to get to what you need.

Credits: http://www.geekalerts.com/lego-tonka-heavy-assault-truck/

What we do in our VC Verification IP (VIP) is something very similar: we start with some tried and tested native SystemVerilog UVM atomic sequences and progressively move toward creating complicated ones. Given the functionality that UVM provides, it is much more convenient to stitch together low-level, proven or validated scenarios to create more complex ones. Instead of creating a flattened implementation in the sequence body, hierarchical or nested sequences can be created to leverage the basic sequences which have been created earlier. This can go up to multiple levels of hierarchy and thus it becomes possible to converge towards meeting the requirements of the most complex scenarios.

As the complexity increases across multiple ACE components, there might be a requirement to coordinate the sequences across multiple sequencers and drivers. These are achieved through virtual sequences and sequencers. The other important functionality from a stimulus generation perspective is the grouping of sequences and the creation of hierarchical sequences. In UVM, similar sequences can be grouped together into a sequence library. Using the UVM base classes, you can create the library infrastructure which would allow any sequence to be registered to the sequence library through the library APIs. Once the library is picked in the simulation, the default functionality causes a random number of sequences to be picked up and executed. Thus, the sequence library which ships with the AXI VIP can be used can be used as a starting point for meeting the user’s requirements.

The default mode of sequence library can be modified by changing the parameters of the associated configuration class. Thus, one can enable a specific number of sequences to be picked up, enable random cyclic sequences, as well as program a user-defined sequence execution.

Creating custom rules for the sequence library would help not only to streamline multiple sequences in different simulations but also to avoid redundancy and move progressively toward convergence of all interesting system-level scenarios. Hence, without having to write multiple tests, I can create custom-defined sequence execution across multiple sequence libraries, across different interfaces, through a virtual sequencer, resulting in a stimulus management setup that helps meet all the stimulus generation requirements much faster.  In some of the  scenarios, the sequences have to be aware of the functional configuration to enable reconfiguration based on the system-level requirements. VC VIP leverages most of the functionality provided by the UVM Resource Mechanism to provide the configurability and the sophisticated stimulus generation requirements in the ACE context. The configurability enabled through the Resource Mechanism is a very interesting topic in itself, and I can talk about it in a subsequent post.

Here I have an example which shows how to create custom sequence generation requirement using the sequence collection. Let us take the case where we need to verify all the cacheline states associated with a Readclean transaction. (“ReadClean is a read transaction which is used in a region of memory that is shareable with other masters. A ReadClean transaction is guaranteed not to pass responsibility for updating main memory to the initiating master. Typically a ReadClean transaction is used by a master that wants to obtain a clean copy of a cache line, such as a master with a write-through cache.”) So, what how do you initiate such a transaction?

A ReadClean transaction would require a cache line initialization followed by cache line invalidation, then a basic Readclean. Lets see what you have as building blocks. First, you take the ‘cache line initialization’ sequence which initializes the cache line states of a master’s cache and its peer’s caches to a set of random but valid states. This ensures that all the different cache line state transitions for a coherent transaction initiated by a master are verified. This would require a sequence which would write into the initiating masters local cache. Data is now dirty in local cache. This would be followed by another master initiating a ‘write’ into memory. Data is now clean in local cache and the data in cache matches the data in memory. This is followed by a ‘BasicReadShared’ which will read data into per master’s local cache by getting clean data from the initiating Master.  Then you would need a sequence that invalidates cache lines of a master. This would be your cache line invalidation sequence. This may be required for non-speculative load transactions.  Finally , you need to have a basic Readclean sequence:

A  complete verification scenario can be mimicked using the nested sequences and virtual sequences. With the hierarchical approach, it becomes relatively easy to model any scenario generation requirements regardless of how complicated they are. This approach when combined with the virtual sequences helps to leverage this functionality across multiple interfaces and is highly relevant in the system context as shown above. Thus, multiple virtual sequences that are part of the library can perform a combination of different sequential coherent transactions from different masters to the same slave or different slaves.

As the complexity of protocols continues to increase and evolve, the infrastructure required for their verification needs to scale up in sophistication as well. Advanced Methodologies such as UVM have been continually evolving to keep up with the many complex requirements. Verification of basic cache coherent systems is in itself challenging. The additional complexity that the ACE protocol brings demands a lot from the verification methodology used. The  functionality provided with the UVM library with respect to sequence generation, distributed phasing and configuration management as well as the recent updates  (available through the UVM-1.2 library) can enables VIP implementations to leverage these to meet the verification requirements in different systems.

Authored by Amit Sharma

Here’s where you can find more information on our Verification IP.

Posted in AMBA, Methodology, SystemVerilog, UVM | No Comments »

Virtual Sequences in UVM: Why, How?

Posted by VIP Experts on March 31st, 2015

In my previous blog post, I discussed guidelines to create reusable sequences. Continuing on this thread, here I am going to talk about virtual sequences and the virtual sequencer. Common questions I hear from users include: why do we need a virtual sequence? How can we use it effectively?

Here’s where you can find more information on our Verification IP

Most UVM testbenches are composed of reusable verification components unless we are working on block-level verification of a simple protocol like MIPI-CSI. Consider a scenario of verifying a simple protocol; In this case, we can live with just one sequencer sending the stimulus to the driver. The top-level test will use this sequencer to process the sequences (as described in the previous blog post). Here we may not need a virtual sequence (or a virtual sequencer).

But when we are trying to integrate this IP into our SOC (or top-level block), we surely want to consider reusing out testbench components, which have been used to verify these blocks. Let us consider a simple case where we are integrating two such blocks: two sequencers driving these two blocks. From top-level test, we will need a way to control these two sequencers.

This can be achieved by using a virtual sequencer and virtual sequences. Other way of doing it is to call sequence’s start method explicitly from the top-level test by passing the sequencer to the start method.

I am going to explain this usage by taking an example, where USB host is integrated in an AXI environment. Let’s see how we can control USB sequencer and AXI sequencer from top-level test. For this particular test, I want to configure the AXI registers and then send USB transfers. For configuring AXI registers am using a sequence say axi_cfg_reg_sequence and for sending USB transfers am using the sequence (usb_complex_sequence) which I have used in the previous blog post. Below is an example where multiple sequencers are controlled without using a virtual sequence.

//Top-level test where multiple sequencers are controlled from the
//phase method.
class axi_cfg_usb_bulk_test extends uvm_test;
  `uvm_component_utils(usb_ltssm_bulk_test)

  //Sequences which needs to be exercised
  usb_reset_sequence u_reset_seq;
  axi_reset_sequence a_reset_seq;
  usb_complex_sequence u_bulk_seq;
  axi_cfg_reg_sequence a_cfg_reg_seq;

  function new (strint name=”axi_cfg_usb_bulk_test”,
                uvm_component parent=null);
    …
  endfunction: new

  //Call the reset sequences in the reset_phase
  virtual task reset_phase (uvm_phase phase);
    phase.raise_objections(this);
    …
    //Executing sequences by calling the start method directly by passing the
    //corresponding sequencer
    a_reset_seq.start(env.axi_master_agent_obj.sequencer);
    u_reset_seq.start(env.usb_host_agent_obj.sequencer);
    …
    phase.drop_objections(this);
  endtask:reset_phase

  virtual task main_phase (uvm_phase phase);
    phase.raise_objections(this);
    …
    //Executing sequences by calling the start method directly by passing the
    //corresponding sequencer
    a_cfg_reg_seq.start(env.axi_master_agent_obj.sequencer);
    u_bulk_seq.start(env.usb_host_agent_obj.sequencer);
    …
    phase.drop_objections(this);
  endtask:main_phase
endclass: axi_cfg_usb_bulk_test

This is not the most efficient way of controlling the sequencers as we are directly using the simple sequences inside the test and making it complex. By doing this, we cannot reuse these complex scenarios further to develop more complex scenarios. Rather if we try to create a sequence and use this sequence in the test, then we can re-use these sequences in other tests (or sequences) as well. Also it will be easier to maintain and debug these sequences compared to creating entire scenario in the top-level test.

Having understood why we need virtual sequence and virtual sequencer, let’s see how this can be achieved by taking the same example shown above.

First thing we need to do is to create a virtual sequencer. Note that virtual sequences can only associate with virtual sequencer (but not with non-virtual sequencer). Virtual sequencer is also derived from uvm_sequencer like any other non-virtual sequencer but is not attached to any driver. Virtual sequencer has references to the sequencers we are trying to control. These references are assigned from top environment to the non-virtual sequencers.

//Virtual sequencer having references to non-virtual sequencers
Class system_virtual_sequencer extends uvm_sequencer;
  //References to non-virtual sequencer
  usb_sequencer usb_seqr;
  axi_sequencer axi_seqr;

  function new (string name=”usb_ltssm_bulk_test”,
                uvm_component parent=null);
    …
  endfunction: new

  `uvm_component_utils(system_virtual_sequencer)

endclass: system_virtual_sequencer

//Top level environment, where virtual sequencer’s references
//are connected to non-virtual sequencers
class system_env extends uvm_env;
  //Agents where the non-virtual sequencers are present
  usb_host_agent usb_host_agent_obj;
  axi_master_agent axi_master_agent_obj;
  //Virtual sequencer
  system_virtual_sequencer sys_vir_seqr;

  `uvm_component_utils(system_env)

  function new (string name=”system_env”, uvm_component parent=null);
   …
  endfunction: new

  function void connect_phase(uvm_phase phase);
    //Assigning the virtual sequencer’s references to non-virtual sequencers
    sys_vir_seqr.usb_seqr = usb_host_agent_obj.sequencer;
    sys_vir_seqr.axi_seqr = axi_master_agent_obj.sequencer;
  endfunction: connect_phase

endclass: system_virtual_sequencer

Now we have virtual sequencer with the references to our non-virtual sequencers, which we want to control, let’s see how we can control these non-virtual sequencers using virtual sequences.

Virtual sequences are same as any other sequence but it is associated to a virtual sequencer unlike non-virtual sequences, hence it needs to indicate which non- virtual sequencer it has to use to execute the underlying sequence. Also note that virtual sequence can only execute sequences or other virtual sequences but not the items. Use `uvm_do_on/`uvm_do_on_with to execute non-virtual sequences and `uvm_do/`uvm_do_with to execute other virtual sequences.

//virtual sequence for reset operation
class axi_usb_reset_virtual_sequence extends uvm_sequence;

  `uvm_object_utils(axi_usb_reset_virtual_sequence)

  //non-virtual reset sequences
  usb_reset_sequence u_reset_seq;
  axi_reset_sequence a_reset_seq;

  function new (string name=” axi_usb_reset_virtual_sequence”,
                uvm_component parent=null);
    …
  endfunction: new

  …

  task body();
    …
    //executingnon-virtual sequence on the corresponding
    //non-virtual sequencer using `uvm_do_on
    `uvm_do_on(a_reset_seq, p_sequencer.axi_seqr)
    a_reset_seq.get_response();
    `uvm_do_on(u_reset_seq, p_sequencer.usb_seqr)
    u_reset_seq.get_response();
  endtask: body

endclass: axi_usb_reset_virtual_sequence

//virtual sequence for doing axi register configuration
//followed by USB transfer
class axi_cfg_usb_bulk_virtual_sequence extends uvm_sequence;

  `uvm_object_utils(axi_cfg_usb_bulk_virtual_sequence)
  `uvm_declare_p_sequencer(system_virtual_sequencer)

  //Re-using the non-virtual sequences
  usb_complex_sequence u_bulk_seq;
  axi_cfg_reg_sequence a_cfg_reg_seq;

  function new (string name=” axi_cfg_usb_bulk_virtual_sequence”,
                uvm_component parent=null);
    …
  endfunction: new

  task body();
    …
    //executingnon-virtual sequence on the corresponding
    //non-virtual sequencer using `uvm_do_on
    `uvm_do_on(a_cfg_reg_seq, p_sequencer.axi_seqr)
    a_cfg_req_seq.get_response();
    `uvm_do_on(u_bulk_seq, p_sequencer.usb_seqr)
    u_bulk_seq.get_response();
  endtask: body

endclass: axi_cfg_usb_bulk_virtual_sequence

In the above virtual sequence, we are executing axi_cfg_reg_sequence and then usb_complex_sequence. Now having virtual sequence and virtual sequencer ready, let’s see how we can execute this virtual sequence from the top-level test.

//Top-level test where virtual sequence is set to virtual sequencer
class axi_cfg_usb_bulk_test extends uvm_test;
  …
  virtual function void build_phase(uvm_phase phase );
    …

    //Configuring variables in underlying sequences
    uvm_config_db#(int unsigned)::set(this,
      ”env.sys_vir_seqr.axi_cfg_usb_bulk_virtual_sequence.u_bulk_sequence”,
      ”sequence_length”,10);

    //Executing the virtual sequences in virtual sequencer’s 
    //appropriate phase.
    //Executing reset virtual sequence in reset_phase
    uvm_config_db#(uvm_object_wrapper)::set(this,
             "env.sys_vir_seqr.reset_phase", "default_sequence",
             axi_usb_reset_virtual_sequence::type_id::get());

    //Executing the main virtual sequence in main_phase
    uvm_config_db#(uvm_object_wrapper)::set(this,
                     "env.sys_vir_seqr.main_phase", "default_sequence",
                     axi_cfg_usb_bulk_virtual_sequence::type_id::get());
    …
  endfunction : build_phase
endclass

Until now we understood why and how we can use virtual sequences. We should also keep few things in mind while using virtual sequence and virtual sequencer to save a lot of debugging time.

1. While configuring the variables in the sequences (which are executed using virtual sequences) we have to use path thru virtual sequence. In above example, using the non-virtual sequencer path for setting the variables in the lower level sequence, will not work.

uvm_config_db#(int unsigned)::set(this,”env.usb_host_agent_obj.sequencer.u_bulk_sequence”,”sequence_length”,10);

Even though u_bulk_sequence is running on the usb_host_agent_obj.sequencer, this will not work because this sequence is created by the virtual sequence and hence hierarchal path should be from virtual sequence but not using non-virtual sequencer. So the right way of setting variables is using the virtual sequence path.

uvm_config_db#(int unsigned)::set(this,”env.sys_vir_seqr.axi_cfg_usb_bulk_virtual_sequence.u_bulk_sequence”,”sequence_length”,10);

This is also true for factory overrides. For example below factory override will not work for the same above reason.

set_inst_override_by_type(”env.usb_host_agent_obj.*”,usb_transfer_item::get_type(), cust_usb_transfer_item::get_type());

In the above example we are trying to change the underlying sequence item with a new derived type from top-level test. For doing this we need to use the virtual sequencer path.

set_inst_override_by_type(”env.sys_vir_seqr.*”,usb_transfer_item::get_type(), cust_usb_transfer_item::get_type());

Rules of Thumb are:
• If the sequence is created by a virtual sequence directly or indirectly, then any hierarchical path in factory overrides or in configurations should use virtual sequencer’s hierarchical path.
• If the sequence is created by a non-virtual sequence, then any hierarchical path in factory overrides or configurations should use non-virtual sequencer’s hierarchical path.

2. Even though we have virtual sequencer to control multiple sequencers, in some tests, we may just need a single sequencer (for example USB sequencer alone). In such cases, we have to use the non-virtual sequencer’s hierarchical path directly (not the virtual sequencer’s reference path) for configuring the variables or factory overrides. Using the virtual sequencer’s reference path will not work as the hierarchy of non-virtual sequencer is incorrect.

uvm_config_db#(uvm_object_wrapper)::set(this, “env.sys_vir_seqr.usb_seqr.main_phase”, “default_sequence”, usb_complex_sequence::type_id::get());

Above configuration will not work, as non-virtual sequencer (usb_seqr/usb_host_agent_obj.sequencer) is actually created in the agent, so the parent for this sequencer is agent but not the virtual sequencer, though the reference is in virtual sequencer. Hence we should not use virtual sequencer path when trying to set variables in the actual sequencer, instead we have to use the hierarchical path through the agent (actual parent to the sequencer).

uvm_config_db#(uvm_object_wrapper)::set(this, “env.usb_host_agent_obj.sequencer.main_phase”, “default_sequence”, usb_complex_sequence::type_id::get());

3. Whenever we are using virtual sequencer and want to control non-virtual sequencers from virtual sequencer, make sure to set the default_sequence in all the actual sequencers to null.

uvm_config_db#(uvm_object_wrapper)::set(this, “env.usb_host_agent_obj.sequencer.main_phase”, “default_sequence”, null);
uvm_config_db#(uvm_object_wrapper)::set(this, “env.axi_master_agent_obj.sequencer.main_phase”, “default_sequence”, null);

This is important because if there is any default_sequence set, then our non-virtual sequencer will be running both the default_sequence and the sequence from the virtual sequence. To control non-virtual sequencers solely from virtual sequencer, we need to set the default_sequence of the non-virtual sequencers as null.

I hope you find this post useful for understanding virtual sequences and save debugging time with the guidelines outlined. I am sure there will be other guidelines while using virtual sequences, which we learn the harder way debugging complex environments; please share any such guidelines with me.

Authored by Hari Balisetty, Broadcom

Here’s where you can find more information on our Verification IP.

Posted in AMBA, Debug, Methodology, MIPI, SystemVerilog, USB, UVM | No Comments »

Reusable Sequences in UVM

Posted by VIP Experts on March 26th, 2015

In this blog, I describe the necessary steps one has to take while writing a sequence to make sure it can be reusable. Personally, I feel 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.

Here’s where you can find more information on our Verification IP.

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. 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.

Authored by Hari Balisetty, Broadcom

Here’s where you can find more information on our Verification IP

Posted in Debug, Methodology, SystemVerilog, USB, UVM | No Comments »

Ins and outs of SS Link Training in USB3.0

Posted by VIP Experts on March 24th, 2015

As you may know, USB3.0 has a state machine called LTSSM (Link training and status state machine) which is responsible for

  1. Initialization and link training
  2. Power management transitions
  3. Link error recovery and other connectivity issues.

Here’s where you can find more information on our Verification IP.

LTSSM has 12 high level states as shown below. In this blog, we will examine the states that are involved in link training, and see how link partners moves to state U0 where actual transfers begin.

Link training is the sequence of events which takes place during the initialization of link after power on reset or when warm reset is directed. This is basically to detect the link partner and train the link before starting to do any kind of transfers on the link. As shown in above diagram, the link training sequence starts at Rx.Detect  (the power ON state for  both an upstream port and a downstream port), and ends with exit to U0 which is the normal operation state where packets are transmitted and received. Before link training, both upstream and downstream ports will be in SS.Disabled state (the state where a port’s SuperSpeed connectivity is removed). In the Polling state, link training is enabled through the LFPS (Low Frequency Periodic Signal) handshake used to communicate information without SS signaling

The following figure shows the link training states along with their sub-states.

How do you configure your Verification IP for completing the SuperSpeed link training sequence in lock step with the DUT?  Synopsys’ USB 3.0 and 3.1 Verification IPs define several timers and parameters with the appropriate default values which makes it a simple task to achieve this. Some of these parameters map to the USB specification and a few are added to aid the verification of the operation of the DUT in both normal or error conditions. The default values ensure that you can enable the Link Training sequence without having to override any of the parameter values.

In some cases you may need to tweak the parameters to try out different corner cases or to further slow down the time taken for link training. Here, we will specifically look at the link training sequence (due to warm reset or power on reset) and will correlate the parameters in the VIP which affect this sequence.

All the names mentioned in italics are variables in svt_usb_configuration class in USB VIP, names/values in square brackets [] are USB specification names/values.  If the direction of port is not mentioned it is taken to be for both upstream (host) and downstream (device) port.

Rx.Detect.Reset

Entry to this state can happen because of a warm reset or a power on reset. Watch out for the parameters below as physical power goes to P2 state. Depending on the previous physical power state, the VIP uses one (or more) of the below timer value.

  • p0_to_p2_transition_time
  • p3_to_p2_transition_time
  • p3_to_p0_transition_time
  • allow_p2_p3_direct_transition

If the entry is not due to warm reset then it moves directly to Rx.Detect.Active. If the entry is due to Warm Reset then the transition depends upon the type of the port VIP is configured as.

  • If the VIP is configured as a downstream port (host) then it transmits LFPS for t_reset_timeout [tReset] time. This is called as warm reset sequence. As soon as timer expires, the VIP stops sending LFPS and moves to Rx.Detect.Active.
  • If the VIP is configured as an upstream port (device) then transition to Rx.Detect.Active happens after host (DUT connected to VIP) completes sending LFPS warm reset signaling.

Rx.Detect.Active

This state is used for detecting the link partner at the other end. In this state, the VIP performs ‘receiver termination’ detection (receiver_detect_time) and moves to Polling.LFPS, if ‘receiver termination’ is detected. If the receiver termination is not detected then it does the following.

  • If VIP is a downstream port (host) then it moves to Rx.Detect.Quiet.
  • If VIP is an upstream port (device) then it moves to Rx.Detect.Quiet for rx_detect_termination_detect_count [8] times. If low impedance termination is not detected even after this then moves to SS.Disabled.

NOTE: ‘Receiver termination’ is detected differently in serial vs PIPE3 interface.

Rx.Detect.Quiet

Wait here until rx_detect_quiet_timeout [12ms timer] expires and move back to Rx.Detect.Active.

Figure 1: LFPS signalling in Polling.LFPS

Polling.LFPS

Once the link partner is detected in Rx.Detect state, both (upstream and downstream) ports go into Polling for training the link. In this state, the VIP transmits LFPS until burst timer polling_lfps_burst_time) expires. It also counts the number of bursts received and checks if the exit criteria (check below for exit criteria) are met. If the exit criteria are not met, then the VIP would wait for the ‘repeat timer’ (polling_lfps_repeat_time) to expire and then start the above process (transmitting LFPS) again.

Exit Criteria for exiting to Polling.RxEq.

  • polling_lfps_sent_count (no. of LFPS bursts sent) [16]
  • polling_lfps_received_count (no. of LFPS received) [2]
  • polling_lfps_sent_after_received_count (no. of LFPS bursts sent after receiving first LFPS from the link partner) [4]

It is important to meet the exit criteria before polling_lfps_timeout [360ms timer] expires. If timeout expires then VIP does one of the following depending on the direction of the port

  • If downstream port (Host) then it moves to Rx.Detect
  • If upstream port (Device) then it moves to SS.Disabled

Figure 2: TSEQ getting exchanged during Polling.RxEq

Polling.RxEq

This state is used to train equalization logic by exchanging TSEQ (Equalization Training Sequence) ordered sets. One can skip this state by enabling ltssm_skip_polling_rxeq. After transmitting polling_rxeq_tseq_count [65,536] number of TSEQ ordered sets VIP will move to Polling.Active.

Polling.Active

In this state, the VIP will keep on sending TS1’s until it receives polling_active_received_ts_count number of TS1/TS2 continuously. Every time a different sequence is received, the VIP will reset the counter. Thus, it makes sure either TS1 or TS2’s are received continuously for polling_active_received_ts_count [8] number of times.

It is important to meet the exit criteria before polling_active_timeout [12ms timer] expires. If this timeout expires, then the VIP does one of the following:

  • If it’s a downstream port (Host),  it would  move to Rx.Detect
  • If it’s an upstream port (Device), then it would move to SS.Disabled.

Figure 3: TS1 and TS2 getting exchanged in Polling.Active and Polling.Configuration

Polling.Configuration

In this state, the VIP will start sending TS2’s but will only start counting them after receiving the first TS2 from the link partner.

Exit Criteria: This state is exited once the following counts are met.

  • polling_configuration_received_ts2_count [8] //No. of TS2 received
  • polling_configuration_sent_ts2_count [16] //No. of TS2’s sent after receiving the first TS2 from the link partner

It is important to meet the exit criteria before polling_configuration_timeout [12ms timer] expires. If timeout expires then the VIP does one of the following

  • If it is a downstream port (Host), it moves to Rx.Detect
  • If it is a upstream port (Device, it moves to SS.Disabled.

Polling.Idle

First thing that the VIP does in this state is to check if the reset bit is set in the TS2’s received in Polling.Configuration and if the port direction is upstream (device). If it detects these two conditions, the VIP moves directly to Hot.Reset state of LTSSM.

If above condition is false then exit to U0 when below ‘counts’ are reached.

  • polling_idle_received_idle_count [8] // No. of idle’s received
  • polling_idle_sent_idle_count [16]      // No.  of idle’s sent after receiving the first idle from the link partner.

The important point here is to meet the exit criteria before polling_idle_timeout [2ms timer] expires. If timeout expires then the VIP does one of the following

  • If downstream port (Host) then move to Rx.Detect
  • If upstream port (Device) then move to SS.Disabled.

Figure 4: U0 state at the end of link training

Once Polling.Idle is done, link moves to U0 state where physical power changes to P0 and the actual transfers starts. Thru out this sequence at any given point of time, upstream port (device) moves to Rx.Detect on warm reset. Most VIP’s has an option to start in U0 state (usb_ss_initial_ltssm_state) so that users can skip entire initial link training and can start doing the transfers right from the start of simulation. Sometimes the DUT which is connected to VIP may not have such option to skip link training, in those cases we recommend to play with the configuration parameters mentioned in this blog to minimize the simulation time in link training.

We hope this blog post helps you to plan out your different link training sequences as per your needs.

Authored by Hari Balisetty, Broadcom

Here’s where you can find more information on our Verification IP

Posted in Methodology, SystemVerilog, USB | No Comments »