CFS Vision

Why Does uvm_reg::read() Return Wrong Data?

In this post, I will highlight the reason and a simple solution for a common problem: the data returned by the uvm_reg::read() task is not the same as the data returned by the DUT.

If you want to jump directly to the most likely solution, click here.


Let’s assume that we have a very simple DUT with an APB interface used to access a read-write register called DATA.

We’ve built a verification environment around it, implemented a register model and connected it to the DUT through an APB agent. All the reads and writes to and from the DATA register work correctly.

Simple environment architecture

The problem is that, even though the automatic UVM checks during read accesses signal no error, the value returned by the read() task is incorrect.

Signature of the read() task from uvm_reg class

Below you can find an example of the code which highlights this problem:

uvm_reg_data_t data_wr = 32'h82;
uvm_reg_data_t data_rd;
      
env.reg_block.DATA.write(status, data_wr);
env.reg_block.DATA.read(status, data_rd);
      
if(data_wr != data_rd) begin
   `uvm_fatal("DUT_ERROR", 
     $sformatf("Written data was 'h%0x but read data was 'h%0x", 
        data_wr, data_rd))
end

I’ve built a simple EDA Playground project highlighting this problem.

There are two paths in the UVM code that can “fill” the returned data of the read() task. Selecting one of these two paths is controlled by the provides_responses field of uvm_reg_adapter.

Let’s analyze these two paths to understand how to identify and fix the problem.

1. Default setup

Most engineers will go for the default option when it comes to factors influencing the value returned by the read() task. This means that the provides_responses field of uvm_reg_adapter is not changed, leaving it to its default value, which is 0.

Let’s see how the return data is built in this setup.

Everything happens in task do_bus_read() from uvm_reg_map class. Here is a code snippet from this task showcasing the relevant lines:

How data is built for the read() task
  1. At line 2069, the code calls reg2bus() function to transform the rw_access (of type uvm_reg_bus_op) into bus_req (of type uvm_sequence_item), a sequence item which it can send to the sequencer of the agent connected to the register interface. In our example, this is the APB agent.
  2. Lines 2075 and 2081 send that bus_req to the sequencer.
  3. Line 2092 uses the same bus_req item it sent to the sequencer to transform it back to something this task can handle, meaning rw_access (of type uvm_reg_bus_op) – this is the key part – it is using the item driven by the sequencer-driver pair in the agent!
  4. Lines 2095, 2109 and 2120 simply rearrange the information correctly.

The key takeaway is that the read() task will use data updated by the driver!

Solution

The solution is pretty simple – we just need to update the read data in the driver as the read() task returns the data extracted from the object sent to the driver. This can be easily missed by many of us as we do not implement any monitoring logic in the driver … the driver is for driving, not monitoring, right?

In our example, the solution is to sample the read data once the driver knows it is available:

protected virtual task drive_transaction(cfs_apb_item_drv item);
  ...
  while(vif.pready !== 1) begin
    @(posedge vif.pclk);
  end

  if(item.dir == CFS_APB_READ) begin
    //SOLUTION: Save in the object the read data returned by the DUT
    item.data = vif.prdata;
  end
  ...
endtask

You can play around with the project implementing this fix in the following EDA Playground project.

2. Advanced setup (wait for responses)

In this setup, one would have to configure provides_responses field of uvm_reg_adapter to be equal to 1. This would look something like this:

class cfs_dut_env extends uvm_env;
  ...
  virtual function void connect_phase(uvm_phase phase);
    ...
    //APB transaction adapter
    cfs_apb_reg_adapter adapter = 
      cfs_apb_reg_adapter::type_id::create("adapter", this);
      
    //Configure the adapter to wait for responses from the APB agent
    adapter.provides_responses = 1;
    ...
  endfunction
  ...
endclass

Remember, if you go with this option, the driver must provide a response, otherwise, you will see that your write() or read() tasks get stuck.

You can do this via two options:

  1. Provide a response via the item argument of the item_done() function.
  2. Provide a response via the put_response() function.

In this EDA Playground example, where you can see this problem, I went with the put_response() function.

class cfs_apb_driver extends uvm_driver#(cfs_apb_item_drv);
  ...
  protected virtual task drive_transaction(cfs_apb_item_drv item);
    ...
    //Make available the response
    seq_item_port.put_response(item);
    ...
  endtask
  ...
endclass

As in the previous setup, everything happens in task do_bus_read() from uvm_reg_map class. Here is a code snippet from this task with the relevant lines:

How data is built for the read() task
  1. Line 2088 gets the response provided by the driver.
  2. Line 2089 transforms that response into something this task can handle, meaning rw_access (of type uvm_reg_bus_op) – this is the key part – it is using response provided by the driver.
  3. Lines 2095, 2109 and 2120 simply rearrange the information correctly.

The key takeaway is that the read() task will use response sent by the driver!

Solution

There are many ways in which one can build the response of the driver. But in the end one will find the root cause by tracing the response set by the driver via item_done() or put_response() functions. So the solution will be to fix the way the response object is built.

In our example, because we are passing the same object to put_response(), we can apply the same solution as in the default setup:

protected virtual task drive_transaction(cfs_apb_item_drv item);
  ...
  while(vif.pready !== 1) begin
    @(posedge vif.pclk);
  end

  if(item.dir == CFS_APB_READ) begin
    //SOLUTION: Save in the object the read data returned by the DUT
    item.data = vif.prdata;
  end

  seq_item_port.put_response(item);
  ...
endtask

You can play around with the project implementing this fix in the following EDA Playground project.


Hope you found this useful!

By the way, if you want to learn how to connect a register model to an agent, you can find all the required information in the article: How to Startup uvm_reg SystemVerilog Library.


If you want to gain an in-depth knowledge on how to do module level verification using SystemVerilog and UVM language then checkout my Udemy course called “Design Verification with SystemVerilog/UVM


Cristian Slav

Add comment