Verification teams don't typically verify testbench components. But this Qualcomm Technologies IP team realized the necessity of unit testing a critical testbench component and the corresponding debug time and frustration it could prevent for downstream IP and chip teams.
This experience report documents the team's first time unit testing with SVUnit, from start to finish. We discuss the justification made with managers to have engineers assigned, how to pick the right test subject to ensure a positive outcome, the method used for verifying testbench checkers, component defect rate, framing unit testing and the defects found as opportunities for improvement and lessons learned for long term maintenance. The focus of the test method is the SVUnit UVM report mocking to write automated tests for uvm_error checks.
WHY IS DEBUG ACCEPTABLE IN SOC DEVELOPMENT?
Debug has become a fundamental part of SoC development. So fundamental, in fact, that in 2014 verification engineers estimated 37% of their time was spent debugging code. Assuming an eight hour day, 37% is roughly two hours and 53 minutes a day, everyday, spent debugging issues that were more than likely created by the development team itself. For hardware developers this is a horrid statistic that signals an obvious breakdown in development.
Data from the same survey suggests that adoption of advanced verification methods is on the rise. While the proliferation of advanced verification methods may be perceived in industry as a positive sign, there's been no noticeable change in mean time lost on debug for the six year period dating back to 2010. This suggests that advanced verification methods have failed to help engineers produce the high quality code necessary to avoid debug. Time lost to debug, therefore, continues with no probable end in site.
BREAKING THE DEBUG CYCLE
From a quality perspective, the data suggests that not only are the methods we use to produce code inadequate, the advanced methods being encouraged and adopted within industry provide little in the way of improvement. To break the trend, therefore, teams should consider that:
- Poor initial code quality is leading to irresponsible bug rates and substantial time and money lost
- Focusing on and/or relying on advanced verification techniques and industry best practices to rectify the loss is a flawed strategy
- A more effective solution is likely to come from new industry practices
These are the three steps this team went through on their way to unit testing a critical UVM testbench component with SVUnit.
THE UNIT UNDER TEST (UUT)
The ABC is a critical component of the XYZ SoC. It is a bridge/interconnect that facilitates a high bandwidth connection between large processor subsystems and various memory and peripheral subsystems. There are multiple instances of ABC on the XYZ SoC which magnifies its importance.
Initial versions of the ABC were verified using a typical approach. A block level ABC testbench was created to verify it in isolation. Once verified in isolation, the ABC was integrated and verified in larger subsystems and finally the XYZ SoC testbenches. The block level ABC testbench was a self-checking constrained random testbench supplemented by embedded SVAs.
The typical approach to verifying the ABC produced typical issues. Namely, bugs undetected in the block level verification went on to require debug investments in dependent subsystem and XYZ SoC tests.
Because of the importance of the ABC and corresponding bug rate, the group manager overseeing testbench development initiated a discussion around improving code quality and eliminating bugs. Having heard of SVUnit and being familiar with unit testing, he suggested a unit testing pilot project focused on one problematic component within the ABC testbench: the DEF checker.
For a number of reasons, the DEF checker was an excellent starting point for improving the quality of the ABC testbench because:
- Many of the bugs found in the ABC testbench were attributed to protocol violations that should have been flagged by the DEF checker
- Protocol violations intended to be flagged by the DEF checker were well documented in a series of tables
- While protocol rules were believed to be fully implemented in the DEF checker, there was low confidence in the correctness of each rule
- Loose coupling between the DEF checker and surrounding testbench meant the DEF checker could be easily isolated and tested with few external dependencies
- The DEF checker flagged protocol violations using uvm_errors which could be captured and validated using the SVUnit UVM report mock
Following an hour long presentation to leadership responsible for the XYZ SoC, it was decided that three weeks would be dedicated to the unit testing pilot in hope unit testing would avoid further time lost to debug of the ABC.
THE SVUNIT UVM REPORT MOCK
Unit testing of the DEF checker relied heavily on SVUnit and the SVUnit UVM Report Mock. The SVUnit UVM report mock enables automated testing of uvm_errors to increase confidence that testbench checkers are defect free. It is a scoreboard style checker where actual and expected errors are logged and compared to decide a PASS/FAIL result.
Unit tests typically go through the following steps to verify a uvm_error is being flagged properly:
- Set the expectation of a particular error by calling the svunit_uvm_report_mock::expect_error(...) function
- Apply a violation to the UUT
- Call svunit_uvm_report_mock::verify_complete() to ensure actual errors are flagged by the UUT as expected. (The verify_complete() function returns 1 when expected and actual errors match; 0 otherwise.)
- Terminate the test with an SVUnit assertion based on the return value of svunit_uvm_report_mock::verify_complete()
To illustrate, we refer to code snippets from the SVUnit UVM Report Mock example packaged with SVUnit. In the first snippet, we have a UUT with a method called verify_arg_is_not_99(). In this trivial example, verify_arg_is_not_99() is designed to flag a uvm_error for input arguments set to 99; all other values are ignored.
To test the verify_arg_is_not_99() function, we can write a set of SVUnit unit tests. This first test validates the error condition of 99 is properly flagged by a uvm_error.
The test _99_is_an_error follows the bullet steps outlined previously. One uvm_error is expected thus the expect_error() method is called once. Calling the expect_error() method pushes an item onto the expected queue of the log message scoreboard inside the SVUnit UVM Report Mock. The UUT function is then called with an argument of 99. Assuming correct behaviour, this should result in one item being pushed onto the actual queue of the log message scoreboard. Finally, we terminate the verify_complete in a FAIL_IF assertion to do an in-order comparison between the actual and expected queues thereby confirming a match between expected and actual errors.
The mechanism not shown in the tests is a redirection of uvm_errors to the SVUnit UVM Report Mock instead of the normal UVM reporting facilities. This is done by redefining the uvm_error macro to call the svunit_uvm_report_mock::actual_error(...) function. The macros are redefined as follows:
To exhaustively verify a checker, tests can also be written for happy path scenarios to ensure uvm_errors are not erroneously flagged. For example, we may wish to verify that values other than 99 do not flag a uvm_error from the verify_arg_is_not_99() function. The following test, other_numbers_are_not_an_error, does just that. No errors are expected, the function is called for all possible arg values other than 99 and the verify_complete() is called to ensure the expected number of errors - in this case 0 - are flagged by the UUT.
A final feature of the SVUnit UVM Report Mock is the ability to expect a specific MSG and/or ID from an error. To do that, expected MSG and ID strings are passed to the expect_error() function.
With expected MSG and ID arguments, the verify_arg_is_not_99() function must trigger the uvm_error with the expected MSG and ID; simply flagging a uvm_error is not enough. (NOTE: for the DEF checker unit tests, no MSG or ID arguments were specified. As long as uvm_errors were flagged as expected, the message content of the uvm_errors was deemed unimportant.)
Using SVUnit, the expect_error() function, the redefined macros and the verify_complete() function, checking of uvm_errors can be automated in unit tests. The automated tests replace crude eyeball checking of error logs and/or blind trust that checkers behave as intended.
PROCEDURE AND RESULTS FROM THE DEF CHECKER
To test the DEF checker, 109 unit tests were written to validate a total of 65 protocol rules. Most protocol checks required exactly one unit test while others required two or more to cover obvious corner cases. The SVUnit UVM Report Mock was used to automate checking as described in the previous section.
Only basic scenarios were used to validate uvm_errors fired for each protocol violation. While the DEF checker did include checks for more complex scenarios, those scenarios were not tested due to time constraints.
In dedicating one verification engineer to write focused unit tests over a period of 13.5 days, a total of 12 bugs were found in the DEF checker. In some cases, bugs were found in the implementation of a check. In others, the intended protocol check was missing entirely.
Of all 12 bugs found in the DEF checker, two were masking bugs in the corresponding ABC RTL implementation. Therefore, as a result of unit testing the DEF checker, two bugs were prevented from infiltrating real XYZ SoC silicon.
These results matched the expectation of the verification team; that bugs remained in the DEF checker even after more than a year of intense use in both subsystem and SoC tests. They also marked the end of a successful unit testing pilot.
In this unit testing pilot, a number of lessons were learned:
- Buy-in from managers and the foresight to allocate three weeks to unit test the DEF checker was key. Without it, the development team may or may not have captured the bugs found and defects may or may not have made it to silicon.
- Sensible code partitioning and loose coupling between verification components made writing focused unit tests straightforward. The ease at which the DEF checker could be decoupled from the surrounding testbench made it very easy to directly isolate, stimulate and validate protocol checks.
- Code dependencies between the DEF checker and surrounding testbench were broken wherever possible in the interest of short simulation runtimes. Depending breaking and the reduced code base lead to runtimes of roughly two minutes 30 seconds for the entire unit test suite running on a single CPU.
- Reintroducing real dependencies at the conclusion of the unit testing effort would have protected against code changes breaking the unit test suite. This was not done though until follow-up testing easily broke the unit test suite through simple changes to the DEF checker and its dependencies. Runtime with all dependencies in tact was four minutes 30 seconds for the entire unit test suite running on a single CPU.
Debug is only inevitable because development teams use process and techniques that result in poor initial code quality. The time and money we lose to debug is something we make possible. Through recognition of this fact and by changing development habits we can reduce the time and money wasted on debug.
While industry does not yet consider unit testing an advanced verification technique, unit testing nevertheless proves to be a very straightforward and methodical technique for improving code quality. For this team, allocating three weeks of effort to unit testing the DEF checker was a productive way to eliminate bugs. Unit testing not only improved the quality of the DEF checker, it increased confidence in the ABC testbench and prevented two bugs from being taped-out on the XYZ SoC; all for relatively minimal effort.
Back to Top