UVM Scoreboard

In previous sections, we learned how a UVM agent instantiates sequencer, driver and a monitor. And inside the agent, how Sequencer and driver‘s analysis ports are connected inside connect function of the agent. In earlier sections, we also learned about uvm test, uvm environment, sequencer, driver, transactions, sequences and concept of factory and uvm configuration database. In this section, let’s look at the missing piece of the puzzle – UVM Scoreboard, which is instantiated inside the uvm environment.

UVM Scoreboard: A scoreboard tracks transaction level activity that is going in and out of the DUT to ensure that DUT is functioning as per the specification. It looks at transactions at input of Design Under Test and compares it with transactions coming out of the DUT. For example, it may track packets in vs. packets out to see if all the packets sent into a communication device made it out intact. Below diagram shows where scoreboard (highlighted in red) fits in the big picture. After that, you can look at the sample code of the uvm scoreboard.

UVM Scoreboard

Here is the sample code for a typical uvm scoreboard. Let’s look at the code first and then we will learn what each line does.

`uvm_analysis_imp_dec(_fifo)

class add_scoreboard extends uvm_scoreboard #(packet_c);

`uvm_component_utils(add_scoreboard);

 uvm_analysis_imp_dec_fifo(#packet_c) actual_port;

 uvm_analysis_fifo (#packet_c) expected_port;     

 packet_c expected_queue[$];

function new(string name =””, uvm_component parent);

    super.new(name,parent);

endfunction

 function build(string name=””,uvm_component p);

    super.build(name,p);

        actual_port = new(“actual_port”,this);

        expected_port = new(“expected_port”,this);

 endfunction

 virtual function write_fifo(input packet_c txn)

      packet_c txn_act;

      packet_c exp_item;

      txn_act = packet_c::typeid::create(“txn_act”,this);

      txn_act = txn;                        

      if(expected_queue.size==0)

          `uvm_error(UVM_ERROR,”\n No transaction is expected”, UVM_LOW);

      else begin

          exp_item= expected_queue.pop_front();

          if (txn_act.compare(exp_item)) begin

              `uvm_info(“\n Transaction match”)

          else

              `uvm_error(“\n Expected result %0d doesnt match actual result %0d”,exp_item.result,txn_act.result)

 endfunction

 task run()

        forever begin

            packet_c txn_exp;

            txn_exp  = packet_c::typeid::create(“txn_exp”,this);    

            expected_port.get(txn_exp);

            expected_queue.push_back(txn_exp);

        end

 endtask

endclass

In above code, add_scoreboard class is defined by extending it from standard uvm_scoreboard class which is parameterized, so we need to also declare the kind of transaction it will handle. Like all other uvm components that we saw, add_scoreboard is also registered in factory using `uvm_component_utils macro.

If you notice, in the first line of code `uvm_analysis_imp_dec macro is called with argument _fifo. By calling this macro, we are defining a parameterized class uvm_analysis_imp_dec_fifo which will have a write_fifo virtual function. This write function is called by the port connected to it(-actual_port is the one that has the write function). We need to implement this write_fifo virtual function as the parameterized class’ doesn’t give us the functionality. If you observe, we have declared actual_port object of type uvm_analysis_imp_dec_fifo. So, this actual_port has the write function that we need to implement. The actual_port is connected to add_monitor‘s mon_ap in connect function of env class. If you recall, the add_monitor is always looking for a transaction on output of the DUT. As soon as a transaction appears on the output of the DUT, mon_ap calls the the above write function in collect_txn() task which is defined inside add_monitor class.

Then we declare a uvm analysis fifo with name expected_port. This expected port is connected to add_driver‘s drv_ap analysis port in connect function of env class. So, whenever driver gets the transaction from the sequencer, it calls drv_ap.put(), which passes the transaction to scoreboard where expected_port.get() is called as expected port is connected to drv_ap. ( – put() and get() are mailbox functions used in System Verilog and mailbox APIs are underlying building block for the analysis ports and fifos).

After declaring expected_port, we declare a queue of type packet_c and call it expected_queue. This will be used to store the transactions that driver sends to scoreboard. Then, like all other uvm components, new function is defined and after that inside the build function, actual_port and expected_port are created.

Then, we implement the write_fifo virtual function which is created when `uvm_analysis_imp_dec(_fifo) macro is called. In this function, we declare txn_act and exp_item transactions of type packet_c. txn_act will hold handle to actual transaction that monitor passes and exp_item will hold transaction that we pop from the expected queue. After declaring txn_act and exp_item, we create the txn_act transaction using standard typeid::create uvm API. Then txn_act points to txn which is input argument to the write_fifo virtual function.

If the write function was called by monitor and expected queue is empty, it means a spurious transaction came out of DUT, which is clearly a bug that should be flagged. The self-check does the job of first checking if expected_queue.size is 0(-meaning no transaction in expected queue). If expected_queue size is zero, then test will fail. If not, we first pop the expected transaction out of the expected_queue and then compare it with the actual transaction txn_act. Both are compared, if both match “Transaction match” string is printed in the simulation log, otherwise if they don’t match, then error is flagged and test fails.

Inside the run task, a forever loop is run in which txn_exp transaction of type packet_c is created and then it gets the handle to expected transaction as soon as it gets available by Driver which is continuously sending transactions to expected_port as explained above. And, when expected transaction is available, it is pushed to the expected queue.

So, in this section, we learned how a typical uvm scoreboard is coded and how it can get transactions from other uvm components (Driver, Monitor) through analysis ports. We also saw how we can check for errors in case DUT is not behaving as expected.

Next section focuses on Coverage Collection in UVM. We will see what is coverage collection and how coverage is coded in UVM,

To get notifications for our upcoming blogs and tutorials, please LIKE our Facebook fan page