UVM is widely used and beloved by design verification engineers. It is simple: work visibility and job security. In old days of using Verilog or SystemVerilog to write a direct test, verification can be done by designers too. In fact more than often designers find it time consuming to document and explain the design to verification engineers they just do verification themselves. Not anymore with UVM! UVM is like C++ and heavily uses abstract concepts such as class, config, phases, etc. A RTL designer can easily get lost with a UVM test bench and to say nothing of modify and design a UVM test by himself.
This article is intended for a RTL designer to quick grasp the guts of UVM.
I am a RTL designer myself. Yes, in the beginning I got lost too when looked at UVM test cases developed by my colleagues and equally confused when read online UVM tutorials. There are so many classes defined, handles (pointers) assigned back and forth, config db usage, … But all I want to know at the beginning is how UVM is connected to my design under test (DUT) and how UVM drives stimulus.
Let’s take below as an example. This diagram is from chipverify.com which is the #1 UVM tutorial site to my opinion.
This DUT has three interfaces, APB, wishbone, and PCIe. For UVM test bench, each interface has an env or agent, like pcie env or pcie agent. Each env/agent has three UMV modules, driver, monitor, and sequencer. Driver is the one connected to your DUT interface or ports. This answers the first question, how UVM is connected to DUT. A little bit detail is driver uses a concept called interface to connect to DUT.
For the 2nd question, how UVM drives stimulus. Let’s say you want to drive DUT with two transactions #A1 and #A2 on APB first, then #P1 on PCIe, and finally #W1 and #W2 on wishbone. How to let UVM bench do it?
The answer is above transactions are specified in UVM sequence module. It is really the UVM sequence module takes care of when and what data are driven into DUT. In UVM, you can think sequencer doesn’t have guts and it just hooks up sequencer and driver together.
Let’s look at a even simpler case that we want to drive two transaction #A1 and #A2 on DUT APB ports. One way is you can define a sequence which only drives one transaction. Then define another sequence which calls the previous sequence twice and each time with different parameters to get #A1 and #A2. Another way is to define just one sequence which drives two transactions, #A1 and #A2.
We can go back to the original question. The difference is now we have three interfaces and their transactions need to be in certain order. Answer is we need to use virtual sequence. A virtual sequence basically just holds sequences which belong to multiple DUT interfaces or UVM drivers. In this virtual sequence, you first call APB sequence to send #A1 and #A2, then call PCIe sequence to send #P1, and finally call wishbone sequence to send #W1 and #W2. That’s ALL!
There are three ways to achieve virtual sequences. For this topic, there is another good post at Virtual Sequence and Virtual Sequencer in UVM.
(Yes, this is another thing about UVM really confuses beginners that before you understand one way they already tell you guess what there are other ways to do it. At the end you understand none of them.)
Above diagram shows one way to achieve virtual sequence by using virtual sequencer. A virtual sequencer does nothing other than hold three pointers to APB sequencer, PCIe sequencer, and Wishbone sequencer. Again, it is really the virtual sequence does the real job.
Some words about randomization. Above example drives a sequence of #A1 and #A2 to APB, #P1 to PCIe, and #W1 and #W2 to Wishbone. Their order can be easily altered in virtual sequencer by using fork-join. For each transaction, some randomization can be done to for example payload data. A common question raised by RTL designer is how to randomize the interface signal or even internal signals to “fully” test a module. First, UVM does not try to drive DUT internal signals. (This is generally true but with exceptions such as RAL backdoor access to regs) UVM tries to drive DUT ports/interfaces to put DUT under certain states. Second, UVM uses transaction concept to drive DUT interface. Its focus is to drive the interface in a standard way called transaction and then different data can be passed in multiple transactions. This job is done inside a UVM driver.
I know this post can be offending to many readers. No code at all??!! Well, I intend it this way!
And code is on the way …