HOME    COMMUNITY    BLOGS & FORUMS    VIP Central
VIP Central
 

Reusable Sequences in UVM

Posted by VIP Experts on March 26th, 2015

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

How do you Verify the AMBA System Level Environment?

Posted by VIP Experts on March 19th, 2015

In my previous blog, AMBA based Subsystems: What does it take to verify them?, I had discussed some of the key verification challenges when it comes to verifying complex SOCs based on AMBA based subsystems. It was observed that it would indeed be useful to have an extensible AMBA based verification environment which can be tweaked minimally so that it can be reused for new systems or derivatives.

To enable SOC verification engineers to create highly configurable AMBA fabric, the system environment should provide place-holders for hooking the DUT with any of the quintessential AMBA VIP components such as AXI3/4/ACE, AHB or APB. With the use of AMBA System environment we can configure it to instantiate as many number of AXI/AHB/APB VIP with minimal additional code. Thus, such an environment would need to encapsulate the following among others:

  • CHI system environment
  • AXI (3/4/ACE) system environment
  • AHB system environment
  • APB system environment
  • A virtual sequencer
  • An array of AMBA System Monitors
  • Configuration descriptor of the AMBA system environment which can be used to configure the underlying CHI/AXI/AHB/APB System environment

The figure below shows a representation of such a verification environment:

AMBA-System-Env

Let’s see what features in UVM can come in handy for creating a robust environment for some of the important system level capabilities:

  • Layered virtual sequencers to achieve synchronization between various components: A system sequencer which manages synchronization across the bus fabric can be modeled as a virtual sequencer with references to the virtual sequencers within CHI System Env, AXI System Env, AHB System Env and APB System Env.
  • Leveraging Analysis ports for system level checks, score boarding and response handling: Each of the Port Monitors in the CHI, AXI, AHB & APB Master and Slave Agent would ideally have an analysis port. At the end of the transaction, the Master and Slave Agents respectively write the completed transaction object to the analysis port. Such upstream ports and downstream ports can be specified to be used by the system monitor to track transformations and responses across the fabric as well as to perform routing checks.
  •  Using callbacks to enable user extensions and to extract coverage and throughput measurement: Callbacks are an access mechanism that enable the insertion of user-defined code and allow access to objects for performance analysis and throughput measurements in the case of the AMBA system environment.
  • A comprehensive sequence library to be run on the virtual sequencer in the System environment: UVM allows for a logical collection of sequences to be registered to a sequence library and this collection can execute on an associated sequencer. A system level sequencer then coordinates the execution of these collection of sequences across different sequencers to create an interesting mix of scenarios while targeting the maximum coverage for a system level stimulus perspective

From a verification perspective, system level checks are key. As mentioned earlier, they can include:

  •  Data Integrity checks across CHI, AXI, AHB and APB ports
  •  Transaction routing checks across CHI, AXI, AHB and APB ports

In my next blog, I will talk about this aspect in more detail. I would walk you through the capabilities that you would need in your System monitor to easily perform the checks mentioned above.

Authored by Satyapriya Acharya 

Here’s where you can find more information on Verification IP for AMBA 4 AXI.

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

Programming AXI-ACE VIP to Generate Error Scenarios

Posted by VIP Experts on March 17th, 2015

VIP manager Tushar Mattu of Synopsys describes how to program  AXI-ACE VIP to generate error scenarios

For more information, please check out Verification IP for AMBA 4 AXI.

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

How to Integrate uvm_reg with AXI VIP

Posted by VIP Experts on March 12th, 2015

VIP manager Tushar Mattu of Synopsys gives insights on how to effectively integrate uvm_reg with AXI VIP http://bit.ly/1xboMLS

For more information, please check out Verification IP for AMBA 4 AXI.

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

Start using AXI VIP with some basic understanding of UVM

Posted by VIP Experts on March 10th, 2015

Recently I worked with a user who was responsible for verifying an AXI interface. This user did not have a UVM background, but was conversant with SystemVerilog. The user was faced with the challenge of learning UVM as well as coming up to speed with an understanding of the VIP: both at the same time, under tight verification timelines. Figuring out how much UVM knowledge would suffice to integrate the VIP, then coding the testbench around the VIP to run and debug the verification environment, appeared to be the first few challenges. I proposed a simple approach: let us begin with a simple directed testbench, get some AXI tests going using the VIP, gain some confidence in terms of understanding the core functionality of the DUT and VIP, and then, in parallel, learn some UVM basics as well. Later, I suggested, he can move on to advanced testing using constrained-random verification where he will need more advanced UVM knowledge, for instance, the application of virtual sequences.

Here’s where you can find more information on Synopsys’ Verification IP for AMBA 4 AXI.

Here are the steps used to integrate AXI VIP to start verification of an AXI interface in a simple directed environment. This approach for directed testing achieves good performance as well.

The testbench example below shows one AXI master VIP connected to a DUT slave. The actual example also uses a VIP in lieu of a slave DUT.

1) Import and include required VIP packages/files

Synopsys’ VIPs are delivered as SystemVerilog packages. These packages define a unique namespace for the VIP, but to make the VIP easier to use, the VIP namespace can be imported into the global namespace. In addition to SystemVerilog packages, some aspects of SVT VIPs such as SystemVerilog interfaces are delivered as global files that must be included because they must exist in both the design and testbench domain.

`include "uvm_pkg.sv"
`include "svt_axi_if.svi"

/** Include the AXI SVT UVM package */
`include "svt_axi.uvm.pkg"

module test_top;
/** Import UVM Package */
import uvm_pkg::*;
/** Import the SVT UVM Package */
import svt_uvm_pkg::*;
/** Import the AXI VIP */
import svt_axi_uvm_pkg::*;
…
…
endmodule


2) Connect the VIP Interface to the DUT signals

VIPs are delivered with SystemVerilog interfaces which provide the signal connectivity required. An instance of these interfaces must be declared and the signals from these interfaces must be connected to the DUT. Here in this example both the master(vip) and the slave(vip) are connected back to back. One can easily replace the required VIP model with corresponding DUT model.

  /** VIP Interface instance representing the AXI bus */
  svt_axi_if axi_if();
  assign axi_if.common_aclk = SystemClock;

  /** Testbench reset */
  logic tb_reset;

  /**
   * Assign the testbench reset to the reset pins of the VIP
   * interface.
   */
  assign axi_if.master_if[0].aresetn = tb_reset;

  /* connection from master[0] to slave[0], connected back to back */
  assign axi_if.slave_if[0].awvalid = axi_if.master_if[0].awvalid;
  assign axi_if.slave_if[0].awaddr = axi_if.master_if[0].awaddr;
  …
  assign axi_if.master_if[0].arready = axi_if.slave_if[0].arready;
  assign axi_if.master_if[0].rvalid = axi_if.slave_if[0].rvalid;
  assign axi_if.master_if[0].rlast = axi_if.slave_if[0].rlast;
  …
  …
/** make rest of assignments (you can alternately choose the SystemVerilog bind approach
/** for that you can refer to “amba_svt/tb_axi_svt_uvm_intermediate_sys”
/** example from VIP installation

3) Create dummy UVM test for objection management and UVM low execution

Directed_test is a dummy test, which extends uvm_test. This allows the UVM phasing mechanism to execute, and manages the objection from the run phase for a directed test written in a procedural block using an event (end_test) synchronization.

class Directed_test extends uvm_test;

   /** UVM Component Utility macro */
  `uvm_component_utils(Directed_test)

   /** Class Constructor */
  function new(string name = "Directed_test", uvm_component parent=null);
    super.new(name,parent);
  endfunction: new

  //Objection management co-ordinated by uvm_test
  virtual task run_phase(uvm_phase phase);
      super.run_phase(phase);
      phase.raise_objection(this);
      @end_test;  //this event will be triggered by directed test from initial-begin-end block
      phase.drop_objection(this);
  endtask

endclass

4) Instantiate the VIP components

The master and slave VIP agent classes must be constructed and configured. After initializing these configuration objects, they are sent to respective agent instances in the UVM hierarchy using the UVM resource database.

initial begin
   `uvm_info("Directed_test", "Entered...", UVM_MEDIUM)

    master_0 = svt_axi_master_agent::type_id::create("master_0",null);

    master_cfg0 = svt_axi_port_configuration::type_id::create("master_cfg0",null);

   /** set required  interface for agent instances */
    master_cfg0.set_master_if(axi_if.master_if[0]);

  /** Program  agent configuration parameters  */
    master_cfg0.data_width = 256;

   /**Pass master and slave configuration using resource database */
    uvm_config_db#(svt_axi_port_configuration)::set(null, "*master_0", "cfg", master_cfg0);

5) Start UVM execution

The UVM run_test() method starts the UVM execution and the argument to it is used as default test to execute.

    /** Start the UVM execution */
    fork
       run_test("Directed_test");
    join_none

6) Reset the DUT
DUT reset code must be called/executed before executing any transaction.

     /* Reset logic */
    `uvm_info("reset_logic", "Entered...", UVM_LOW)
    tb_reset = 1'b1;
    repeat(10) @(posedge SystemClock);
    tb_reset = 1'b0;
    repeat(10) @(posedge SystemClock);
    tb_reset = 1'b1;
    `uvm_info("reset_logic", "Exiting...", UVM_LOW)

7) Initiate the traffic from Master

Now we’re ready to start creating transactions from the master. The example below creates a WRITE transaction, sets all required fields and sends it to VIP master driver using the execute_item() method.

/* Create and Send atomic transaction */
    `uvm_info("atomic_transation", "Entered...", UVM_MEDIUM)
    begin
      svt_axi_master_transaction axi_trans;

          axi_trans = new();
          axi_trans.port_cfg          = cfg.master_cfg[0];
          axi_trans.xact_type       = svt_axi_transaction::WRITE;
          axi_trans.addr                = 32'h0000_0000;
          axi_trans.burst_type     = svt_axi_transaction::INCR;
          axi_trans.burst_size      = svt_axi_transaction::BURST_SIZE_32BIT;
          axi_trans.atomic_type  = svt_axi_transaction::NORMAL;
          axi_trans.burst_length = 16;
          axi_trans.data                = new[axi_trans.burst_length];
          axi_trans.wstrb              = new[axi_trans.burst_length];

          /** Send the atomic write transaction */
         master_0.sequencer.execute_item(axi_trans);     //send axi transaction to driver

      `uvm_info("atomic_transation", "Ended...", UVM_MEDIUM)
    end

8 ) Trigger End of Test

The end_test event is used to enable the objection that was raised for the run_phase() to drop. This signals the end of the run phase, and the rest of the UVM phases will execute once the run phase is done. This signals the end of the test.

    //Trigger uvm end of test
    end_test;
   `uvm_info("Directed_test", "Exited...", UVM_MEDIUM)
end

Authored by Tushar Mattu

Here’s where you can find more information on Synopsys’ Verification IP for AMBA 4 AXI.

Posted in AMBA, Methodology, SystemVerilog, UVM | 1 Comment »

How to Use the AXI VIP Debug Port

Posted by VIP Experts on March 5th, 2015

VIP manager Tushar Mattu of Synopsys gives insights on how to effectively use the AXI VIP Debug Port  http://bit.ly/18QYPMs

For more info, please check out Verification IP for AMBA 4 AXI.

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

Power Management of PCIe PIPE Interface

Posted by VIP Experts on March 3rd, 2015

Lately we have seen a trend of serial data transfers in place of parallel data transfer for improved performance and data integrity. One example of this is the migration from PCI/PCI-X to PCI Express. A serial interface between two devices results in fewer number of pins per device package. This not only results in reduced chip and board design cost but also reduces board design complexity. As serial links can be clocked considerably faster than parallel links,  they would be  highly scalable in terms of performance.

However, to accelerate verification of PCI Express based sub-systems and to accelerate the PCI Express endpoint development time ,  PIPE (PHY Interface for the PCI Express Architecture) was defined by Intel and was published for industry review in 2002. PIPE is a standard interface defined between a PHY sub-layer which handles the lower levels of serial signaling and the Media Access Layer (MAC) which handles addressing/access control mechanisms. The following diagram illustrates the role PIPE plays in partitioning the PHY layer for PCI Express.

Partitioning Phy Layer

(Source:  PHY Interface for thePCI Express Architecture specification, Version 2.00)

With this interface, developers can validate their designs without having to worry about the analog circuitry associated with teh Phy interface.  For the MAC core verification, the PHY Bus Functional Model (BFM) would  be connected directly to it. Without PIPE, it would be required to have the PHY and Serdes (serializer/deserializer) combination along with the Root Complex BFM. Additionally, the user would have to ensure the correctness of the PHY and SerDes behavior as well with the serial interface.

Given the value of the PIPE interface, it is now being widely used. In our recent experiences, we have observed that the different power states in the PIPE interface can create some confusion with respect to their interpretation. This blog post and the next will throw some light on the different power states of this interface. Hopefully, this will lead to a better understanding of the same. The assumption here is that the reader has a high level understanding of PCIe LTSSM.

Power states of PIPE

The power management signals allow the PHY to minimize the power consumption.  Four power states, P0, P0s, P1, and P2 are defined for this interface. P0 state is the normal operational state for the PHY. One it transitions from P0 to a lower power state, the PHY can immediately take appropriate power saving measures.

All power states are represented by signals PowerDown [2:0](MAC output). The Bit representation is as follows:

2]   [1]   [0]                           Description

0       0      0                            P0, normal operation

0       0      1                            P0s, low recovery time latency, power saving state

0       1      0                            P1, longer recovery time latency, lower power state

0       1      1                            P2, lowest power state.

PIPE interface power state can be correlated with power state of LTSSM as mentioned in Base specification (Refer to PCI_Express_Base_r3.0_10Nov10).

  1. P0 is equivalent to LTSSM State where Data/Order Set can transfer
  2. P0s is equivalent to L0s of LTSSM
  3. P1 is equivalent to Disabled, all Detect , and L1.Idle  state of LTSSM
  4. P2 is equivalent to L2 of LTSSM

Power state transitions in PIPE

In states P0, P0s and P1, the PHY is required to keep PCLK operational. For all state transitions between these three states, the PHY indicates successful transition into the designated power state  by a single cycle assertion of PhyStatus.

There is a limited set of legal power state transitions that a MAC can cause the PHY to make. Referencing the main state diagram of the LTSSM in the base specification and the mapping of LTSSM states to PHY power states described in the preceding paragraphs, those legal transitions are:

  1. P0 to P0s
  2. P0 to P1
  3. P0 to P2
  4. P0s to P0
  5. P1 to P0
  6. P2 to P1

Given that we understand the valid power state transitions,  I would capture more details about the individual power states and all possible transitions in more detail in my subsequent blog.  Stay tuned.

You can get more information at Synopsys Verification IP and VC Verification IP Datasheet.

Authored by Saurabh Shrivastava

 

Posted in Methodology, PCIe | No Comments »

How to Integrate AXI VIP into a UVM Testbench

Posted by VIP Experts on February 26th, 2015

Here, Synopsys VIP manager Tushar Mattu describes how best we can integrate AXI VIP into a UVM Testbench:  http://bit.ly/1Ay3zfb 

For more info, please check out Verification IP for AMBA 4 AXI.

Posted in AMBA, SystemVerilog, UVM | No Comments »

Parameterized Interfaces and Reusable VIP (3 of 3)

Posted by VIP Experts on February 24th, 2015

This is the third part of a three part series discussing SystemVerilog interfaces and strategies for dealing with parameterization.

You can get more information at Synopsys Verification IP and VC Verification IP Datasheet.

In the first part of this series, the basic concepts of SystemVerilog interfaces were introduced, and the problems that parameterization of those interfaces introduce to testbench code was described.  In the second part, the method of using an accessor class to shield the VIP code from the parameterization effects was described, but this solution imposes new restrictions on VIP access to that interface.  In this final post of this series, a process is introduced that allows the testbench to make use of parameterized interfaces without imposing any limits on the way that the VIP can access the interface that it is provided.

Maximum Footprint: Best of both worlds

The problem that parameterized interfaces introduce to dynamic testbench code cannot be resolved using today’s existing SystemVerilog functionality.  Therefore we must design a way to avoid exposing these parameters to the VIP code.  The previous post in this series introduced the accessor class to achieve this.  An alternate solution is to define a maximum footprint style interface that the VIP can interact with, and a parameterized interface that wraps this maximum footprint interface and connects to the signals that it needs from the max footprint interface.

Maximum footprint style interfaces define the maximum width for each signal and individual VIP components can be configured to utilize bit slices from those signals.  This allows for multiple VIP instances with different widths and does not require parameterized classes to work with parameterized interfaces.  The following code segments demonstrate these concepts.

First, we define the max footprint style interface.  Note that this interface is not parameterized because this is the interface that the VIP code will interact with:

  // Redefinable max footprint
  `ifndef MAX_DATA_WIDTH
   `define MAX_DATA_WIDTH 32
  `endif

  interface max_footprint_if;
    logic clk;
    logic[`MAX_DATA_WIDTH-1:0] data_in;
    logic[`MAX_DATA_WIDTH-1:0] data_out;

    clocking active_cb @(posedge clk);
      default input #1 output #1;
      input data_in;
      output data_out;
    endclocking

    modport active_mp (clocking active_cb);
  endinterface

  typedef virtual max_footprint_if max_footprint_vif;

Next, the parameterized interface is defined which is used to wrap the max footprint interface:

  interface param_if#(int width = 8);
    logic clk;
    logic[width-1:0] data_in;
    logic[width-1:0] data_out;

    max_footprint_if internal_if();

    assign internal_if.clk = clk;

    // Z values driven on unused inputs of the max footprint
    assign internal_if.data_in = {`MAX_DATA_WIDTH'hz, data_in};

    // Only selected output values used from the max footprint
    assign data_out = internal_if.data_out[width-1:0];
  endinterface

After this, the VIP code is implemented to work with the max footprint interface rather than the parameterized interface.  The one additional class shown below which is not shown in earlier posts is the configuration class which is used to define the signal width for each VIP instance.  Another complication that this solution creates is that the VIP must use bitslicing techniques when sampling and driving signals.  This is unfortunate, but SystemVerilog is fully equipped to handle this.

  //=======================================================================
  class cust_cfg extends uvm_object;
    rand int data_width;
    constraint valid_data_width {
      data_width inside {8, 16, 32};
    }
…

  //=======================================================================
  class cust_driver extends uvm_driver#(cust_data);
    max_footprint_vif vif;
    cust_cfg cfg;

    `uvm_component_utils(cust_driver)

    function void build_phase(uvm_phase phase);
      if (!uvm_config_db#(max_footprint_vif)::get(this, "", "vif", vif))
        `uvm_fatal("build", "A valid virtual interface was not received.");
      if (!uvm_config_db#(cust_cfg)::get(this, "", "cfg", cfg))
        `uvm_fatal("build", "A valid configuration was not received.");
…

    task consume_from_seq_item_port();
        seq_item_port.get_next_item(req);
        vif.active_cb.prop_out <= ((req.prop <> (`MAX_DATA_WIDTH-cfg.data_width));
        @(vif.active_cb);
…

    task sample_signals();
        bit[31:0] sampled_prop_in = ((vif.active_cb.prop_in <> (`MAX_DATA_WIDTH-cfg.data_width));
VM_LOW);
        @(vif.active_cb);
…

  //=======================================================================
  class cust_agent extends uvm_agent;
    `uvm_component_utils(cust_agent)

    max_footprint_vif vif;
    cust_driver driver;

    function void build_phase(uvm_phase phase);
      if (!uvm_config_db#(max_footprint_vif)::get(this, "", "vif", vif))
        `uvm_fatal("build", "A valid virtual interface was not received.");
      if (!uvm_config_db#(cust_cfg)::get(this, "", "cfg", cfg))
        `uvm_fatal("build", "A valid configuration was not received.");

      uvm_config_db#(max_footprint_vif)::set(this, "driver", "vif", vif);
      uvm_config_db#(cust_cfg)::set(this, "driver", "cfg", cfg);
      driver = cust_driver::type_id::create("driver", this);
…

Finally, the testbench code is presented.  Testcases can access VIPs without parameterization, and the top level module where the interfaces are instantiated can work with parameterized interfaces.  The additional step of creating a configuration object for each VIP instance is also shown (this is not an extra step, as this would have been needed with the earlier solutions as well but was left out for conciseness).

Utilizing max footprint style interfaces for VIP access to signals allows VIP code to be created without requiring that the VIP code be parameterized.  Defining parameterized interfaces allow testbenches to take advantage of the improved integration capabilities that they enable.  The strategy of using parameterized interfaces to wrap max footprint interfaces enables the benefits of both styles.

  //=======================================================================
  class cust_test extends uvm_test;
    cust_cfg cfg8;
    cust_cfg cfg16;
    cust_cfg cfg32;

    cust_agent agent8;
    cust_agent agent16;
    cust_agent agent32;

    virtual function void build_phase(uvm_phase phase);
      cfg8 = new("cfg8");
      cfg8.data_width = 8;
      uvm_config_db#(cust_cfg)::set(this, "agent8", "cfg", cfg8);
      agent8 = cust_agent::type_id::create("agent8", this);

      cfg16 = new("cfg16");
      cfg16.data_width = 16;
      uvm_config_db#(cust_cfg)::set(this, "agent16", "cfg", cfg16);
      agent16 = cust_agent::type_id::create("agent16", this);

      cfg32 = new("cfg32");
      cfg32.data_width = 32;
      uvm_config_db#(cust_cfg)::set(this, "agent32", "cfg", cfg32);
      agent32 = cust_agent::type_id::create("agent32", this);
    endfunction
  endclass

  //=======================================================================
  module test_top;
    param_if#(8) if8();
    param_if#(16) if16();
    param_if#(32) if32();

    initial begin

      uvm_config_db#(max_footprint_vif)::set(uvm_root::get(), "uvm_test_top.agent8", "vif", if8.internal_if);
      uvm_config_db#(max_footprint_vif)::set(uvm_root::get(), "uvm_test_top.agent16", "vif", if16.internal_if);
      uvm_config_db#(max_footprint_vif)::set(uvm_root::get(), "uvm_test_top.agent32", "vif", if32.internal_if);

      run_test("cust_test");
    end
  endmodule

Utilizing max footprint style interfaces for VIP access to signals allows VIP code to be created without requiring that the VIP code be parameterized. Defining parameterized interfaces allow testbenches to take advantage of the improved integration capabilities that they enable. The strategy of using parameterized interfaces to wrap max footprint interfaces enables the benefits of both styles.

Authored by Aron Pratt

You can get more information at Synopsys Verification IP and VC Verification IP Datasheet.

Posted in Methodology, SystemVerilog, UVM | No Comments »