UVM Gotchas

Author: Robin Hotchkiss, Principal Verification Consultant

UVM register layer prediction modes don’t have the same behavior

What are we talking about?

This post talks about the difference between implicit and explicit prediction with front-door accesses in a UVM register model.  These differences can cause intermittent false-positive failures due to hard to debug thread execution order issues in the simulator.  This issue can surface when developing product specific tests or even when running the UVM pre-defined sequences. 

What’s the problem?

The UVM user guide has a section that describes integrating a register model that touches on three modes of prediction.  Prediction in this context is the mechanism of how the register model is kept in sync with the design under test.

  • Implicit Prediction
    • Active use of the model is when the test calls model class tasks to perform a transfer to interact with the design.  These tasks automatically update the model before the task completes with the latest values.  For example, on a write action the transfer is initiated and after it completes the model is updated to match the data that was sent.  A read action updates the model with the data that was returned when the transfer completes.
  • Explicit Prediction
    • In this mode the active use of the model does not directly perform prediction during the execution of the tasks.  Instead a separate passive monitor is used to feed a UVM predictor class instance to update the model state.  In this way the model is still triggering the read and write transfers to the DUT, but the passive components are keeping the model in sync with the design.
  • Passive Prediction
    • This mode isn’t really any different from the explicit prediction description except it implies there is no active use of the model, just the passive update.  This means that other parts of the design or environment may be causing changes to the design state, and the passive components above are being used to keep the register model in sync with the design.

The description of the different modes, provided above, isn’t that much different from those that can be found in the UVM user guide, so what’s the problem?  The problem stems from how these modes are executed.  With implicit prediction, a single execution thread is used to trigger the transfer, wait for it to complete and then update the model state.  When explicit prediction is used, the active tasks execute in a separate thread of execution to that of the components performing the passive prediction.  If you have worked with SystemVerilog for a while, or any multi-threaded language, talk of concurrent threads working on a single resource should start to raise some concerns.

In the following basic example, the code is performing a basic read and then using a get call to obtain the value of a field in the register.  While this code doesn’t look multi-threaded, using explicit prediction has that effect.

m_reg.read(.status (l_status), .value (l_value));

l_reg_status_flag = m_reg.status_flag.get();

if (l_reg_status_flag)

  // flag is true

else

  // flag is false

With implicit prediction, the register and field values will be updated before the “read” call completes, so the value returned from the “get“ call will match the value returned by the transfer.  However, with explicit prediction things can be a bit more uncertain depending how the simulator has decided to schedule and switch between executing threads.  For example:

  1. Read completes -> prediction completes -> get completes
  2. Read completes -> get completes -> prediction completes

In the first order example, the code will behave the same as the implicit prediction in all cases.  The simulator decided to switch to the predictor thread on the return of the read call.  All is good with the world.  However, the second order example isn’t as fortunate.  Are you thinking this is a contrived example? Yes, it is, but code similar to this is not uncommon and even exists in the UVM pre-defined sequence used to perform the bit bash test. 

What’s the solution?

Cheapest and dirtiest? Add a delay after the access but before accessing the model value.  You may have seen code that uses #0 to attempt to encourage a simulator to reschedule and allow other threads to execute.  However, the #0 delay doesn’t dictate any thread ordering behaviour, and the simulator can choose the same thread to continue, so won’t solve anything.  The delay needs to be sufficient to ensure the prediction has completed.  Not nice, not pretty, but it works around the immediate issue and allows things to progress.  I said it was cheap and dirty, and it is.  Any solution like this is not going to be robust, so I strongly recommend you find a better solution.  Fragile code, as such a delay can be, can open your code up to other language/tool pitfalls that may cause you or others in the team to waste more time and effort later in the project.

So how do we provide a cleaner fix?  You will need to resort to the parts the SystemVerilog language offers to enforce ordering.  One solution I have used in the past employed a generic UVM callback to synchronise the active accesses with the passive prediction using semaphores.  I created a simple function that attached the callback onto all the registers in the model.  The helper function was called after the model was created and was given the model as a parameter.  The synchronisation between the passive and active operations means that the explicit prediction mimics the behaviour of the implicit prediction and solves the observed problem.  I’ve provided the code below, so you can take a look at one possible solution.

The example solution assumes a simple register operation.  A model with more complex register behaviours may have other requirements that are not addressed by this code. 

class ext_ral_predictor_sync_cb

  extends uvm_reg_cbs;

  //—————————————————————————-

  // internal variables

  //——————————————————————

  semaphore         m_waiting_on_ext_predict;

  semaphore         m_ext_predict_done;

  //—————————————————————————-

  // UVM macros

  //——————————————————————

  `uvm_object_utils( ext_ral_predictor_sync_cb )

  //—————————————————————————-

  // new

  //——————————————————————

  function new (string name = “ext_ral_predictor_sync_cb”);

    super.new(name);

    m_ext_predict_done = new(0);

    m_waiting_on_ext_predict = new(0);

  endfunction : new

  //—————————————————————————-

  // pre_read

  //——————————————————————

  virtual task pre_read (uvm_reg_item rw);

    if (rw.path == UVM_FRONTDOOR)

    begin

      m_waiting_on_ext_predict.put();

    end

  endtask : pre_read

  //—————————————————————————-

  // pre_write

  //——————————————————————

  virtual task pre_write (uvm_reg_item rw);

    if (rw.path == UVM_FRONTDOOR)

    begin

      m_waiting_on_ext_predict.put();

    end

  endtask : pre_write

  //—————————————————————————-

  // post_read

  //——————————————————————

  virtual task post_read (uvm_reg_item rw);

    if (rw.path == UVM_FRONTDOOR)

    begin

      m_ext_predict_done.get();

    end

  endtask : post_read

  //—————————————————————————-

  // post_write

  //——————————————————————

  virtual task post_write (uvm_reg_item rw);

    if (rw.path == UVM_FRONTDOOR)

    begin

      m_ext_predict_done.get();

    end

  endtask : post_write

  //—————————————————————————-

  // post_predict

  //——————————————————————

  virtual function void post_predict (input uvm_reg_field fld,

                                      input uvm_reg_data_t previous,

                                      inout uvm_reg_data_t value,

                                      input uvm_predict_e kind,

                                      input uvm_path_e path,

                                      input uvm_reg_map map);

    if (m_waiting_on_ext_predict.try_get())

    begin

      m_ext_predict_done.put();

    end

  endfunction : post_predict

endclass : ext_ral_predictor_sync_cb

//—————————————————————————-

// add_ral_ext_predictor_sync

//——————————————————————

function void add_ral_ext_predictor_sync(uvm_reg_block p_reg_block);

  ext_ral_predictor_sync_cb l_pred_sync_cb;

  uvm_reg l_reg_list[$];

  uvm_reg_field l_field_list[$];

  string l_name;

  // Get a list of all registers

  l_reg_list.delete();

  p_reg_block.get_registers(.regs(l_reg_list));

  // step through all the registers

  foreach (l_reg_list[i])

  begin

    // get the first field of the register

    l_field_list.delete();

    l_reg_list[i].get_fields(.fields(l_field_list));

    // get the name of the register, to create a name for the callback

    l_name = {l_reg_list[i].get_name(), “_sync_cb”};

    // create the callback instance for this register

    l_pred_sync_cb = ext_ral_predictor_sync_cb::type_id::create( l_name );

    // add the callback to the first field of the register

    uvm_reg_field_cb::add( l_field_list[0], l_pred_sync_cb );

  end

endfunction : add_ral_ext_predictor_sync

XtremeEDA is an experienced partner you can trust!!

Cadence Design Systems helps engineers pick up the development tempo. A leader in the market for electronic design automation (EDA) software, Cadence sells and leases software and hardware products used to design integrated circuits (ICs), printed circuit boards (PCBs), and other electronic systems. Semiconductor and electronics systems manufacturers use its products to build components for wireless devices, networking equipment, and other applications. The company also provides maintenance and support, and offers design and methodology consulting services. Customers have included Pegatron, Silicon Labs, and Texas Instruments. Cadence gets more than half of its sales from customers outside the US.

Synopsys, Inc. (Nasdaq:SNPS) provides products and services that accelerate innovation in the global electronics market. As a leader in electronic design automation (EDA) and semiconductor intellectual property (IP), Synopsys’ comprehensive, integrated portfolio of system-level, IP, implementation, verification, manufacturing, optical and field-programmable gate array (FPGA) solutions help address the key challenges designers face such as power and yield management, system-to-silicon verification and time-to-results. These technology-leading solutions help give Synopsys customers a competitive edge in quickly bringing the best products to market while reducing costs and schedule risk. For more than 25 years, Synopsys has been at the heart of accelerating electronics innovation with engineers around the world having used Synopsys technology to successfully design and create billions of chips and systems. The company is headquartered in Mountain View, California, and has approximately 90 offices located throughout North America, Europe, Japan, Asia and India.

asicNorth was established in January 2000 with one purpose in mind: deliver the highest quality design services possible. In an industry that can be quite volatile at times, it is important to have a design partner that you can depend upon to deliver the skills you need when you need them. A project can only be successful if there are:

Top quality skills on the team
Communication with the customer
Attention to detail
Cost sensitivity
Focus on the schedule

Today, asicNorth is enabling high-tech industry leaders and startups alike with a combination of digital, analog, and mixed-signal design capabilities. Driven to produce successful results, asicNorth is Making Chips Happen™.

Codasip delivers leading-edge RISC-V processor IP and high-level processor design tools, providing IC designers with all the advantages of the RISC-V open ISA, along with the unique ability to customize the processor IP. As a founding member of RISC-V International and a long-term supplier of LLVM and GNU-based processor solutions, Codasip is committed to open standards for embedded and application processors. Formed in 2014 and headquartered in Munich, Germany, Codasip currently has R&D centers in Europe and sales representatives worldwide. For more information about our products and services, visit www.codasip.com. For more information about RISC-V, visit www.riscv.org.

Founded in 1999, Avery Design Systems, Inc. enables system and SOC design teams to achieve dramatic functional verification productivity improvements through the use of

Formal analysis applications for RTL and gate-level X verification;

Robust Verification IP for PCI Express, USB, AMBA, UFS, MIPI, DDR/LPDDR, HBM, HMC, ONFI/Toggle, NVM Express, SCSI Express, SATA Express, eMMC, SD/SDIO, Unipro, CSI/DSI, Soundwire, and CAN FD standards.

Siemens EDA
The pace of innovation in electronics is constantly accelerating. To enable our customers to deliver life-changing innovations to the world faster and to become market leaders, we are committed to delivering the world’s most comprehensive portfolio of electronic design automation (EDA) software, hardware, and services.