Testing UVM Drivers (Without The Sequencer)

So. A couple weeks ago I introduced SVMock. It’s a mocking framework for use with SVUnit that makes it easier to isolate, check and control behaviour of Systemverilog classes. Unsurprisingly, the response to that announcement averaged out to tepid. A few people were immediately interested. I’m sure a huge number of people didn’t care, probably because mocking has never been on their unit test radar. Then there were people in the middle who were interested but I didn’t give them enough to get over the great-but-now-what hurdle.

This post is for the last group, the people that reckon SVMock can help them write better unit tests but don’t quite see how. The test subject to get the point across: the uvm_driver.

UVM drivers have come up time and time again as a suggested blog topic and example. The uvm_driver and uvm_sequencer are very tightly coupled (via the seq_item_port) so people have long pondered testing one without the other. I’ve never felt the effort of decoupling them was worth it so the arrangement I’ve used until now has been to test them as an integrated unit without any mocking. It’s a practical approach that has served me well enough; one that Tudor Timisescu has written up nicely on his Verification Gentleman blog.

Finally, though, with SVMock I feel like I have an alternative to the integrated sequencer/driver arrangement. I can create a mock for the seq_item_pull_port that gives me direct control of the driver interaction while avoiding the sequencer and sequence. So far it feels like less overhead than the normal integrated sequencer/driver approach. Better though is that it gives a great example of what you can do with SVMock, even if you don’t like it for this specific application.

For the example we’re using an AMBA APB driver. APB is perfect because it’s a simple protocol that let’s us focus on how the mock works without getting bogged down in the pin level behaviour. To be clear, what we’re looking for is direct control over the driver via the mock while minimizing infrastructure and dependencies. To do that, we’ll eliminate the sequencer/sequence requirement with a mock uvm_seq_item_pull_port#(). In pictures, that’ll take us from arrangement ‘A’ to arrangement ‘B’ in…

Boiled down, the purpose of the uvm_seq_item_pull_port#() is to manage the interaction between the sequencer and driver. The uvm_seq_item_pull_port#() has a lot of options for interaction. But like other parts of the UVM library, thankfully, people can stick to a useful subset and ignore the rest. That’s what we’ll do here. Our driver uses the get_next_item, item_done and put_response methods so our mock will insert checking and/or control in place of the default behaviour of each method. We do that like this…

…where item_pull_port_t is a typedef I declare as…

Notice that we have to fully define the possibilities for each argument for the task and function macros (i.e. all the methods we’re mocking have scalar arguments, item_done has an argument with a default). I’ve started using /*comments*/ as placeholders to maintain the argument order to the macros (the price of containing everything within the language without the need for more powerful pre-processing in place of the plain old verilog pre-processing).

This SVMock would be enough if all we were interested in was tracking how the get_next_item, item_done and put_response methods were called (i.e. we could use the mock to track how many times the methods were called and check the arguments they’re called with). In the case of the seq_item_pull_port, however, we need to replace the behaviour of each of those methods because they lead to fatal errors without a real sequencer is connected. This is where my favourite SVMock feature comes in: the ability to re-map functionality.

Using get_next_item as an example…

Effectively, I’m declaring _get_next_item as a potential alternative to get_next_item thus relieving me of the corresponding sequencer dependencies. In place of those dependencies (which I intentionally don’t understand 😉 ), the new _get_next_item just pulls from a simple mailbox. The mailbox is what I’ll use to feed transactions to the driver in my unit tests.

I do a similar mapping for put_response. Instead of sending responses back to a sequencer, I stuff them in a different mailbox that I’ll use for checking in my unit tests…

Last is item_done. With item_done there’s nothing special to do other than avoid the fatal errors of the real item_done. To do that, I created a mapping to a method that does nothing…

To review, we now have a uvm_seq_item_pull_port_mock that declares/maps functional replacements for get_next_item, item_done and put_response that eliminate interaction with a real sequencer.

Mock in hand, we can jump over to our driver unit test template. To use the mock in our unit test template, we have to create an instance of it and replace the uvm_seq_item_pull_port created by default in the driver. So we declare the driver and mock…

…then create each and do the replacement of the default port like this…

The final step to cut out the sequencer dependencies altogether is to tell the mock to use our new mappings. We do that with ON_CALL macros in the setup method…

I think of the ON_CALL.will_by_default as similar to using function pointers in a language like C++. Systemverilog doesn’t have function pointers hence the extra steps. But the result is the same, calls to get_next_item will be redirected to _get_next_item instead. Likewise for item_done and put_response.

So what does all that get us?

First, passing transactions to the driver is very simple. We can create transactions one at a time and put them in the mock.item_mb instead of creating a sequence then starting that sequence on a sequencer and having the driver get the individual transactions from a real uvm_seq_item_pull_port…

Second, grabbing read responses is also very simple. We can poll the mock.rsp_mb instead of having responses travel back to a sequencer via a real uvm_seq_item_pull_port…

The overarching bottom line here is that the mock isolates our driver testing and buffers us from potential bugs in our sequencer or sequences. If you keep your sequencers and sequences very simple, that may not be a big deal. If you have complicated sequencers and sequences it’s a very big deal.

For reference, this apb_driver example is included in the SVMock release package under svmock/examples/class/uvm_driver. If you download the release package and scroll through the uvm_seq_item_pull_port_mock.sv and apb_driver_unit_test.sv you’ll see all these code snippets within the context of the complete example.

-neil

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.