Verification Academy

Search form

My Account Menu

  • Register
  • Log In
  • Topics
  • Courses
  • Forums
  • Patterns Library
  • Cookbooks
  • Events
  • More
  • All Topics
    The Verification Academy offers users multiple entry points to find the information they need. One of these entry points is through Topic collections. These topics are industry standards that all design and verification engineers should recognize. While we continue to add new topics, users are encourage to further refine collection information to meet their specific interests.
    • Languages & Standards

      • Portable Test and Stimulus
      • Functional Safety
      • Design & Verification Languages
    • Methodologies

      • UVM - Universal Verification Methodology
      • UVM Framework
      • UVM Connect
      • FPGA Verification
      • Coverage
    • Techniques & Tools

      • Verification IP
      • Simulation-Based Techniques
      • Planning, Measurement, and Analysis
      • Formal-Based Techniques
      • Debug
      • Clock-Domain Crossing
      • Acceleration
  • All Courses
    The Verification Academy is organized into a collection of free online courses, focusing on various key aspects of advanced functional verification. Each course consists of multiple sessions—allowing the participant to pick and choose specific topics of interest, as well as revisit any specific topics for future reference. After completing a specific course, the participant should be armed with enough knowledge to then understand the necessary steps required for maturing their own organization’s skills and infrastructure on the specific topic of interest. The Verification Academy will provide you with a unique opportunity to develop an understanding of how to mature your organization’s processes so that you can then reap the benefits that advanced functional verification offers.
    • Universal Verification Methodology (UVM)

      • Advanced UVM
      • Basic UVM
      • Introduction to UVM
      • UVM Connect
      • UVM Debug
      • UVMF - One Bite at a Time
    • Featured Courses

      • Introduction to ISO 26262
      • Introduction to DO-254
      • Clock-Domain Crossing Verification
      • Portable Stimulus Basics
      • Power Aware CDC Verification
      • Power Aware Verification
      • SystemVerilog OOP for UVM Verification
    • Additional Courses

      • Assertion-Based Verification
      • An Introduction to Unit Testing with SVUnit
      • Evolving FPGA Verification Capabilities
      • Metrics in SoC Verification
      • SystemVerilog Testbench Acceleration
      • Testbench Co-Emulation: SystemC & TLM-2.0
      • Verification Planning and Management
      • VHDL-2008 Why It Matters
    • Formal-Based Techniques

      • Formal Assertion-Based Verification
      • Formal-Based Technology: Automatic Formal Solutions
      • Formal Coverage
      • Getting Started with Formal-Based Technology
      • Handling Inconclusive Assertions in Formal Verification
      • Sequential Logic Equivalence Checking
    • Analog/Mixed Signal

      • AMS Design Configuration Schemes
      • Improve AMS Verification Performance
      • Improve AMS Verification Quality
  • All Forum Topics
    The Verification Community is eager to answer your UVM, SystemVerilog and Coverage related questions. We encourage you to take an active role in the Forums by answering and commenting to any questions that you are able to.
    • UVM Forum

      • Active Questions
      • Solutions
      • Replies
      • No Replies
      • Search
      • UVM Forum
    • SystemVerilog Forum

      • Active Questions
      • Solutions
      • Replies
      • No Replies
      • Search
      • SystemVerilog Forum
    • Coverage Forum

      • Active Questions
      • Solutions
      • Replies
      • No Replies
      • Search
      • Coverage Forum
    • Additional Forums

      • Announcements
      • Downloads
      • OVM Forum
  • Patterns Library
    The Verification Academy Patterns Library contains a collection of solutions to many of today's verification problems. The patterns contained in the library span across the entire domain of verification (i.e., from specification to methodology to implementation—and across multiple verification engines such as formal, simulation, and emulation).
    • Implementation Patterns

      • Environment Patterns
      • Stimulus Patterns
      • Analysis Patterns
      • All Implementation Patterns
    • Specification Patterns

      • Occurrence Property Patterns
      • Order Property Patterns
      • All Specification Patterns
    • Pattern Resources

      • Start Here - Patterns Library Overview
      • Whitepaper - Taking Reuse to the Next Level
      • Verification Horizons - The Verification Academy Patterns Library
      • Contribute a Pattern to the Library
  • All Cookbooks
    Find all the methodology you need in this comprehensive and vast collection. The UVM and Coverage Cookbooks contain dozens of informative, executable articles covering all aspects of UVM and Coverage.
    • UVM Cookbook

      • UVM Basics
      • Testbench Architecture
      • DUT-Testbench Connections
      • Configuring a Test Environment
      • Analysis Components & Techniques
      • End Of Test Mechanisms
      • Sequences
      • The UVM Messaging System
      • Other Stimulus Techniques
      • Register Abstraction Layer
      • Testbench Acceleration through Co-Emulation
      • Debug of SV and UVM
      • UVM Connect - SV-SystemC interoperability
      • UVM Versions and Compatibility
      • UVM Cookbook
    • Coding Guidelines & Deployment

      • Code Examples
      • UVM Verification Component
      • Package/Organization
      • Questa/Compiling UVM
      • SystemVerilog Guidelines
      • SystemVerilog Performance Guidelines
      • UVM Guidelines
      • UVM Performance Guidelines
    • Coverage Cookbook

      • Introduction
      • What is Coverage?
      • Kinds of Coverage
      • Specification to Testplan
      • Testplan to Functional Coverage
      • Bus Protocol Coverage
      • Block Level Coverage
      • Datapath Coverage
      • SoC Coverage Example
      • Requirements Writing Guidelines
      • Coverage Cookbook
  • All Events
    No one argues that the challenges of verification are growing exponentially. What is needed to meet these challenges are tools, methodologies and processes that can help you transform your verification environment. These recorded seminars from Verification Academy trainers and users provide examples for adoption of new technologies and how to evolve your verification process.
    • Upcoming & Featured Events

      • Low Power Verification - 4/29
      • Fault Campaign for Mixed-Signal - 5/4
      • User2User - 5/26
      • Webinar Calendar
    • On-Demand Webinars

      • CDC+RDC Analysis
      • Basic Abstraction Techniques
      • Safety Analysis Techniques
      • QVIP Workflow and Debug for PCIe
      • Writing a Proxy-driven Testbench
      • Achieving High Defect Coverage
      • Visualizer Features
      • All On-Demand Webinars
    • Recording Archive

      • Siemens EDA 2021 Functional Verification Webinar Series
      • Improving Your SystemVerilog & UVM Skills
      • Should I Kill My Formal Run?
      • Visualizer Debug Environment
      • Industry Data & Surveys
      • All Recordings
    • Conferences

      • DVCon 2021
      • DVCon 2020
      • DAC 2019
      • All Conferences
    • Mentor Learning Center

      • SystemVerilog Fundamentals
      • SystemVerilog UVM
      • View all Learning Paths
  • About Verification Academy
    The Verification Academy will provide you with a unique opportunity to develop an understanding of how to mature your organization's processes so that you can then reap the benefits that advanced functional verification offers.
    • Blog & News

      • Verification Horizons Blog
      • Academy News
      • Academy Newsletter
      • Technical Resources
    • Verification Horizons Publication

      • Verification Horizons - March 2021
      • Verification Horizons - November 2020
      • Verification Horizons - July 2020
      • Issue Archive
    • About Us

      • Verification Academy Overview
      • Subject Matter Experts
      • Contact Us
    • Training

      • Questa Basic
      • Questa Advanced
      • Mastering Questa
  • Home
  • Verification Horizons
  • June 2019
  • Fun with UVM Sequences - Coding and Debugging

Fun with UVM Sequences - Coding and Debugging

Verification Horizons - Tom Fitzpatrick, Editor

Fun with UVM Sequences - Coding and Debugging Rich Edelman - Mentor, A Siemens Business

In a SystemVerilog UVM [2] testbench, most activity is generated from writing sequences. This article will outline how to build and write basic sequences, and then extend into more advanced usage. The reader will learn about sequences that generate sequence items; sequences that cause other sequences to occur and sequences that manage sequences on other sequencers. Sequences to generate out of order transactions will be investigated. Self-checking sequences will be written.

INTRODUCTION

A UVM sequence is a collection of SystemVerilog code which runs to cause “things to happen”. There are many things that can happen. A sequence most normally creates a transaction, randomizes it and sends it to a sequencer, and then on to a driver. In the driver, the generated transaction will normally cause some activity on the interface pins. For example a WRITE_READ_SEQUENCE could generate a random WRITE transaction and send it to the sequencer and driver. The driver will interpret the WRITE transaction payload and cause a write with the specified address and data.

Figure 1


CREATING A SEQUENCE

A UVM sequence is just a SystemVerilog object that is constructed by calling new. It can be constructed from many different places, but normally a test might construct sequences and then run them – they embody the test. For example a test might be pseudo-coded as:

LOAD ALL MEMORY LOCATIONS
READ ALL MEMORY LOCATIONS, 
  CHECK THAT EXPECTED VALUES MATCH.}

There might be a sequence to write all memory locations from A to B. And another sequence to read all memory locations from A to B. Or something simpler: A WRITE_READ_SEQUENCE that first writes all the memory locations and then reads all the memory locations.

The test below creates a sequence inside a fork/join_none. There will be four sequences running in parallel. Each sequence has a LIMIT variable set and starts to run at the end of the fork/join_none. Once all of the forks are done, the test completes.

class test extends uvm_test;
  `uvm_component_utils(test)
 
  my_sequence seq;
  ...
  task run_phase(uvm_phase phase);
    phase.raise_objection(this);
    for (int i = 0; i < 4; i++) begin
      fork
        automatic int j = i;
        seq = new($sformatf("seq%0d", j));
        seq.LIMIT = 25 * (j+1);
        seq.start(sqr);
      join_none
    end
    wait fork;
    phase.drop_objection(this);
  endtask
endclass

RUNNING A SEQUENCE — CREATING AND SENDING A SEQUENCE ITEM

The sequence below, ‘my_sequence’, is a simple sequence which creates transactions and sends them to the sequencer and driver. In the code below, the body () task is implemented. It is a simple for-loop which iterates through the loop LIMIT times. LIMIT is a variable in the sequence which can be set from outside.

Within the for-loop, a transaction object is constructed by calling new () or using the factory. Then start_item is called to begin the interaction with the sequencer. At this point the sequencer halts the execution of the sequence until the driver is ready. Once the driver is ready, the sequencer causes ‘start_item’ to return. Once start_item has returned, then this sequence has been granted permission to use the driver. Start_item should really be called “REQUEST_TO_SEND”. Now that the sequence has permission to use the driver, it randomizes the transaction, or sets the data values as needed. This is the so-called “LATE RANDOMIZATION” that is a desirable feature. The transactions should be randomized as close to executing as possible, that way they capture the most recent state information in any constraints.

After the transaction has been randomized, and the data values set, it is sent to the driver for processing using ‘finish_item’. Finish_item should really be called “EXECUTE_ITEM”. At this time, the driver gets the transaction handle and will execute it. Once the driver calls ‘item_done ()’, then finish_item will return and the transaction has been executed.

class my_sequence extends uvm_sequence#(transaction);
  `uvm_object_utils(my_sequence)
 
  transaction t;
  int LIMIT;
  ...
  task body();
    for (int i = 0; i < LIMIT; i++) begin
      t = new(“t”);
      start_item(t);
      t.data = i+1;
      if (!t.randomize())
        `uvm_fatal(get_type_name(), "Randomize FAILED")
      finish_item(t);
    end
  endtask
endclass

EXECUTING A SEQUENCE ITEM — THE DRIVER

The driver code is relatively simple. It derives from a uvm_driver and contains a run_phase. The run_phase is a thread started automatically by the UVM core. The run_phase is implemented as a forever begin-end loop. In the begin-end block the driver calls seq_item_port.get_next_item (t). This is a task which will cause execution in the sequencer – essentially asking the sequencer for the next transaction that should be executed. It may be that no transaction is available, in which case this call will block. (There are other non-blocking calls that can be used, but are beyond the scope of this article, and not a recommended usage). When the sequencer has a transaction to execute, then the get_next_item call will unblock and return the transaction handle in the task argument list (variable ‘t’ in the example below). Now the driver can execute the transaction.

For this example, execution is simple – it prints a message using the transactions’ convert2string () call, and waits for an amount of time controlled by the transactions ‘duration’ class member variable.

Once that ‘execution’ is complete, the seq_item_port.item_done () call is made to signal back to the sequencer and in turn the sequence that the transaction has been executed.

class driver extends uvm_driver#(transaction);
  `uvm_component_utils(driver)
 
  transaction t;
  ...
  task run_phase(uvm_phase phase);
    forever begin
      seq_item_port.get_next_item(t);
      `uvm_info(get_type_name(), 
               $sformatf("Got %s", t.convert2string()), UVM_MEDIUM)
      #(t.duration); // Execute...
      seq_item_port.item_done();
    end
  endtask
endclass

CONTROLLING OTHER SEQUENCES

Sequences can have handles to other sequences; after all, a sequence is just a class object with data members, and a “task body()”, which will run as a thread.

Virtual Sequences

The so-called “virtual sequence” – a sequence which may not generate sequence items, but rather starts sequences on other sequencers. This is a convenient way to create parallel operations from one control point.

A virtual sequence simply has handles to other sequences and sequencers. It starts them or otherwise manages them. The virtual sequence may have acquired the sequencer handles by being assigned from above, or by using a configuration database lookup, or other means. It may have constructed the sequence objects, or have acquired them by similar other means. The virtual sequence is like a puppet master, controlling other sequences.

A virtual sequence might look like:

sequenceA_t sequenceA;
sequenceB_t sequenceB;
sequencerA sqrA;
sequencerB sqrB; 
 
task body();
 sequenceA.start(sqrA);
 sequenceB.start(sqrB);
 …

Related Sequences

In the code snippet below, there are two sequences, ping and pong. They each have a handle to each other. They are designed to take turns. First one sends five transactions, then the other, and so on. See the appendix for the complete code.

First the handles are constructed and a run limit is set up.

ping_h = new("ping_h");
ping_h.LIMIT = 25;
pong_h = new("pong_h");
pong_h.LIMIT = 40;

Then the handles get their “partner” handle.

ping_h.pong_h = pong_h;
pong_h.ping_h = ping_h;

Finally the two sequences are started in parallel.

fork
 ping_h.start(sqr);
 pong_h.start(sqr);
join

WRITING A SELF-CHECKING SEQUENCE

A self-checking sequence is a sequence which causes some activity and then checks the results for proper behavior. The simplest self-checking sequence issues a WRITE at an address, then a READ from the same address. Now the data read is compared to the data written. In some ways the sequence becomes the GOLDEN model.

class write_read_sequence extends my_sequence;
  `uvm_object_utils(write_read_sequence)
  ...
 
  task body();
    for (int i = 0; i < LIMIT; i++) begin
      t = new($sformatf("t%0d", i));
      start_item(t);
      if (!t.randomize())
        `uvm_fatal(get_type_name(), "Randomize FAILED")
      t.rw = WRITE;
      finish_item(t);
    end
 
    for (int i = 0; i < LIMIT; i++) begin
      t = new($sformatf("t%0d", i));
      start_item(t);
      if (!t.randomize())
        `uvm_fatal(get_type_name(), "Randomize FAILED")
      t.rw = READ;
      t.data = 0;
      finish_item(t);
 
      // Check
      if (t.addr != t.data) begin
        `uvm_info(get_type_name(), $sformatf("Mismatch. 
	           Wrote %0d, Read %0d", 
           t.addr, t.data), UVM_MEDIUM)
        `uvm_fatal(get_type_name(), "Compare FAILED")
      end
    end
  endtask
endclass

WRITING A TRAFFIC GENERATOR SEQUENCE

A video traffic generator can be written to generate a stream of background traffic that mimics or models the amount of data a video display might require. There is a minimum bandwidth that is required for video. That calculation will change with the load on the interface or bus, but a simplistic calculation is good enough for this example. The video traffic will generate a “screen” of data 60 times a second. Each screen will have 1920 by 1024 dots. Each dot is represented by a 32 bit word. Using these numbers, the traffic generator must create 471MB per second.

class video extends my_sequence;
  `uvm_object_utils(video)
 
  int xpixels = 1920;
  int ypixels = 1024;
  int screendots;
  int rate;
  bit [31:0] addr;
 
  int x;
  int y;
 
  video_transaction t;
 
  task body();
    screendots = xpixels * ypixels;
    rate = 1_000_000_000 / (60 * screendots);
    forever begin
      addr = 0;
      for (x = 0; x < xpixels; x++) begin
        for (y = 0; y < ypixels; y++) begin
          t = new($sformatf("t%0d_%0d", x, y));
          start_item(t);
          if (!t.randomize())
            `uvm_fatal(get_type_name(), "Randomize FAILED")
          t.rw = WRITE;
          t.addr = addr++;
          t.duration = rate;
          finish_item(t);
        end
      end
    end
  endtask
endclass

A more complete traffic generator would adjust the arrival rate based on the current conditions – as the other traffic goes up or down, the video traffic generation rate should be adjusted.

WRITING SEQUENCES THAT ARE SYNCHRONIZED WITH EACH OTHER

Sequences will be used to synchronize other sequences (so called virtual sequences). Often two sequences need to have a relationship between them formalized. For example they cannot enter their critical regions together – they must go single-file. Or they can only run after some common critical region has passed.

The code below declares two sequences which are to be synchronized (synchro_A_h and synchro_B_h). It also declares a synchronizer. There is nothing special about these classes – they have simply agreed to use some technique to be synchronized.

synchro synchro_A_h;
 synchro synchro_B_h;
 synchronizer s;
 
  s = new();
  synchro_A_h = new("synchroA");
  synchro_B_h = new("synchroB");

The synchronized sequences get a handle to the synchronizer and a starting address.

  synchro_A_h.s = s;
  synchro_A_h.start_addr = 2;
  synchro_B_h.s = s;
  synchro_B_h.start_addr = 2002;

The synchronizer control is rather simple. It just says “GO” for 20 ticks and “STOP” for 100 ticks.

fork
 forever begin
  #100;
  s.state = GO;
  #20;
  s.state = STOP;
 end
join_none

The synchronized sequences are started. They run to completion and then simply get restarted. They run forever.

fork
 forever begin
  synchro_A_h.start(sqr);
 end
 forever begin
  synchro_B_h.start(sqr);
 end
join_none

The simple synchronizer with two states — GO and STOP.

typedef enum bit { STOP, GO } synchro_t;
 
class synchronizer;
  synchro_t state;
endclass

The class that uses a synchronizer to NOT execute until told to do so.

class synchro extends my_sequence;
  `uvm_object_utils(synchro)
 
  bit [31:0] start_addr;
  bit [31:0] addr;
  synchronizer s;
 
  synchro_transaction t;
 
  task body();
    forever begin
      addr = start_addr;
      // Is it safe?
      while (s.state == STOP) begin
        #10;
        `uvm_info(get_type_name(), "Waiting...", UVM_MEDIUM)
      end
      t = new($sformatf("t%0d", addr));
      start_item(t);
      if (!t.randomize())
        `uvm_fatal(get_type_name(), "Randomize FAILED")
      t.rw = WRITE;
      t.addr = addr++;
      finish_item(t);
    end
  endtask
endclass

In simulation, the sequence waits until the synchronizer is in the GO state. Once in the GO state, then the synchronized code generates a transaction using new, and then calls start_item/finish_item to execute it. After waiting for access to the driver and then executing, the synchronized sequence comes back to the top of the loop and checks the synchronizer state. It will either GO again, or STOP/WAIT as shown in the example below.

Figure 2


IMPLEMENTING AN INTERRUPT SERVICE ROUTINE WITH SEQUENCES

Sequences will be used to provide an "interrupt service routine". Interrupt service routines “sleep” until needed. This is a unique kind of sequence. In this example implementation it creates an “interrupt service transaction” and does start_item and finish_item. In this way, it can send that ISR transaction handle to the driver. The driver is then going to hold that handle UNTIL there is an interrupt.

As part of the drivers’ job of handling the SystemVerilog Interface, it will handle the interrupts. In this case, handling the interrupt means that some data is put into the “held handle” and then the handle is marked done. Meanwhile, the interrupt service sequence has been waiting for the transaction to be marked DONE. In some parlance this is known as ITEM REALLY DONE. In the UVM, there are other mechanisms for handling this kind of thing, but they are unreliable and more complicated than this solution.

class interrupt_transaction extends transaction;
  `uvm_object_utils(transaction)
  int VALUE;
  bit DONE;
endclass
 
class interrupt_sequence extends my_sequence;
  `uvm_object_utils(interrupt_sequence)
 
  interrupt_transaction t;
 
  task body();
    forever begin
      t = new("isr_transaction");
      start_item(t);
      finish_item(t);
      wait(t.DONE == 1);
      `uvm_info(get_type_name(), $sformatf("Serviced %0d",
	         t.VALUE), UVM_MEDIUM)
    end
  endtask
endclass
 
class driver extends uvm_driver#(transaction);
  `uvm_component_utils(driver)
 
  transaction t;
  interrupt_transaction isr;
  bit done;
  int value;
 
  bit [31:0] mem[1920*1024];
 
  task interrupt_service_routine(interrupt_transaction isr_h);
    `uvm_info(get_type_name(), "Setting ISR", UVM_MEDIUM)
    done = 0;
    isr_h.DONE = 0;
    wait(done == 1);
    isr_h.VALUE = value;
    isr_h.DONE = 1;
  endtask
 
  task run_phase(uvm_phase phase);
    forever begin
      seq_item_port.get_next_item(t);
 
      if ($cast(isr, t)) begin
        fork
          interrupt_service_routine(isr);
        join_none
      end
      else begin
        ... 
        // REGULAR driver processing
        ...
        if (AN INTERRUPT OCCURS) begin
          done = 1;
          value = mem[t.addr];
        end
      end
      seq_item_port.item_done();
    end
  endtask
endclass

SEQUENCES WITH “UTILITY LIBRARIES”

Sequence "utility libraries" will be created and used. Utility libraries are simple bits of code that are useful for the sequence writer – helper functions or other abstractions of the verification process.

The open_door sequence below does just as its name implies. It opens the door to the sequencer and driver. Outside calls can now be made at will using the sequence object handle (seq.read () and seq.write () for example).

class open_door extends my_sequence;
  `uvm_object_utils(open_door)
 
  read_transaction r;
  write_transaction w;
 
  task read(input bit[31:0]addr, output bit[31:0]data);
    r = new("r");
    start_item(r);
    if (!r.randomize())
      `uvm_fatal(get_type_name(), "Randomize FAILED")
    r.rw = READ;
    r.addr = addr;
    finish_item(r);
    data = r.data;
  endtask
 
  task write(input bit[31:0]addr, input bit[31:0]data);
    w = new("w");
    start_item(w);
    if (!w.randomize())
      `uvm_fatal(get_type_name(), "Randomize FAILED")
    w.rw = WRITE;
    w.addr = addr;
    w.data = data;
    finish_item(w);
  endtask
 
  task body();
    `uvm_info(get_type_name(), "Starting", UVM_MEDIUM)
    wait(0);
    `uvm_info(get_type_name(), "Finished", UVM_MEDIUM)
  endtask
endclass

The open_door is constructed and then started using normal means. Then a test program can issue reads and writes simply as in the RED lines below.

  open_door open_door_h;
 
    open_door_h = new("open_door");
    fork
      open_door_h.start(sqr);
      begin
        bit [31:0] rdata;
        for (int i = 0; i < 100; i++) begin
          open_door_h.write(i, i+1);
          open_door_h.read(i, rdata);
          if ( rdata != i+1 ) begin
            `uvm_info(get_type_name(), $sformatf("Error: Wrote '%0d', Read '%0d'",
               i+1, rdata), UVM_MEDIUM)
            //`uvm_fatal(get_type_name(), "MISMATCH");
          end
        end
      end
    join_none

CALLING C CODE FROM SEQUENCES

Calling C code (using DPI-C) from sequences is easy, but there are a few limitations. DPI import and export statements cannot be placed inside a class – so they must be outside the class in file, global or package scope. As such, they have no design or class object scope.

import "DPI-C" function void c_code_add(output int z, 
     input int a, input int b);
export "DPI-C" function sv_code;

A DPI-C export function or task is just a SystemVerilog function or task that has been “exported” using the export command.

function void sv_code(int z);
  $display("sv_code(z=%0d)", z);
endfunction

A DPI-C import function or task is a C function with a return value. For a task, the return value is an int (See the SystemVerilog LRM for details). For a function, the return value, is whatever the return value should be.

The simple void function c_code_add () is defined below. It has two inputs and “returns” a value in the pointer *z. This C function calls the exported SystemVerilog function ‘sv_code ()’.

t.c:
#include "stdio.h"
#include "dpiheader.h"
 
void
c_code_add(int *z, int a, int b)
{
  *z = a + b;
  sv_code(*z);
}

The dpiheader.h is a handy way to check the API for DPI-C. In this example, the dpiheader.h (below) is very simple.

void c_code_add( int* z, int a, int b);
void sv_code( int z);

This sequence does nothing particularly special, it generates transactions, but it does call a C function. (c_code_add RED line below). In terms of writing sequences that call C code, there is really nothing special to do in terms of sequences. The DPI-C code must be properly written and must be declared in a proper scope.

class use_c_code_sequence extends my_sequence;
  `uvm_object_utils(use_c_code_sequence)
 
  int z;
 
  c_code_transaction t;
 
  task body();
    forever begin
      `uvm_info(get_type_name(), "Starting", UVM_MEDIUM)
      for (int i = 0; i < 10; i++) begin
        for (int j = 0; j < 10; j++) begin
          c_code_add(z, i, j);
          t = new($sformatf("t%0d", i));
          start_item(t);
          if (!t.randomize())
            `uvm_fatal(get_type_name(), "Randomize FAILED")
          t.duration = z;
          t.rw = WRITE;
          finish_item(t);
        end
      end
      `uvm_info(get_type_name(), "Finished", UVM_MEDIUM)
    end
  endtask
endclass

CALLING SEQUENCES FROM C CODE

Calling sequences from C code is harder than calling C code from sequences. This is because sequences are class objects. Class objects do not have “DPI-C Scope”, so in order to call into a sequence (or start a sequence), other means must be used. There are many other references on techniques to do this.

Figure 3


SEQUENCES AND TRANSACTIONS RECORDING

In the example code discussed throughout this article, each of the sequences is running in parallel – at the same time on the single sequencer. The sequences (and recorded streams are listed below as children of that sequencer).

Each row is a sequence executing. It is easy to see in the two screenshots above (figures 4 and 5) how the sequences each take turns sending and executing a transaction on the driver.

Figure 4


Figure 5


CONCLUSION

The reader of this article now knows that sequences are not mysterious or things to be afraid of, but rather that sequences are simply "code" – usually stimulus or test code. That code can be written to do many different things, from the original “random transaction generation” to synchronization to interrupt service routines. Sequences are just code – important code that causes stimulus generation and results checking.

REFERENCES

[1] SystemVerilog, 1800-2017 - IEEE Standard for SystemVerilog--Unified Hardware Design, Specification, and Verification Language
[2] UVM LRM: IEEE Standard for Universal Verification Methodology Language Reference Manual

All source code is available from the author. Contact rich_edelman@mentor.com for access or download from the Verification Academy.

This article was previously presented at DVCon US 2019.

Back to Top

Table of Contents

Verification Horizons Articles:

  • Is Your Verification in Jeopardy?

  • SystemC FMU for Verification of Advanced Driver Assistance Systems

  • Fun with UVM Sequences - Coding and Debugging

  • Creating Tests the PSS Way in SystemVerilog

  • Formal Bug Hunting with “River Fishing” Techniques

  • Don’t Forget the Protocol! A CDC Protocol Methodology to Avoid Bugs in Silicon

  • Auto-Generating Implementation-Level Sequences for PSS

  • UVMF, Beyond the ALU Generator Tutorial Extending Actual Test Control of the DUT Inputs

Siemens Digital Industries Software

Siemens Digital Industries Software

##TodayMeetsTomorrow

Solutions

  • Cloud
  • Mendix
  • Siemens EDA
  • MindSphere
  • Siemens PLM
  • View all portfolio

Explore

  • Digital Journeys
  • Community
  • Blog
  • Online Store

Siemens

  • About Us
  • Careers
  • Events
  • News and Press
  • Newsletter
  • Customer Stories

Contact Us

USA:

phone-office +1 800 547 3000

See our Worldwide Directory

  • Contact Us
  • Support Center
  • Give us Feedback
©2021 Siemens Digital Industries Software. All Rights Reserved.
Terms of Use Privacy Cookie Policy