The often-misunderstood UVM factory object provides a path to modify testcase behavior without the associated doom of touching already released code.
by Hamilton Carter, Senior Editor
All of us have seen some variation of the following flow chart:
The basic message, “If you can get away without touching it, you might just be OK,” applies as well to design verification as it does to all other areas of engineering. While revision control systems ideally provide a way to back out any changes made, the best choice is still never to touch the code at all. The dark side of revision control in this scenario is that everyone else can always find out if you did touch the code!
The ability to add new more effective testcases to an existing verification environment without touching it is where the factory object shines. This object—introduced into verification environments everywhere with the advent of the Universal Verification Methodology—lives only in ignominy for many of us. For those who haven’t seen its power leveraged, the factory objet serves mainly as the source of a seemingly endless stream of required registration calls. It is often touted as providing a set of utility methods that all verification objects and components can leverage, but that’s only its most obvious advantage, and not the seat of its more subtle, true power.
Let’s take a look at a hypothetical verification environment. You’ve, of course, taken great care to register all your classes with the factory. In addition, you’ve been careful enough to have your sequencer only use the base class of your stimulus or transaction item. Your testcases are running well and the device is well exercised. You were clever and collected functional coverage so you could see what your environment was accomplishing. So, when a design engineer happens by your cube one day to ask if you’ve ever tested a series of three transactions with long payloads followed by a transaction with no data payload and two others with payloads of four bytes or less, you merely have to check your regression coverage results. And, lo and behold, the coverage just isn’t there.
Here’s where you have an important decision to make. Do you touch any of the source files of your well-oiled verification environment? Keep in mind that if you do and the regression breaks tomorrow—whether or not you had anything to do with it—you’ll be one of the poor old SOBs that touched it. You will of course be presumed guilty until you waste sufficient time to prove your innocence. Consequently, the obvious answer to the above question is “No, there’s no way in hell I’m touching that code!” This is when the factory object really shines. This is where you’ll thank your lucky stars you made all the seemingly goofy registration calls.
Rather than touching your existing source, you simply open a new file and define a transaction class that has your base transaction class mentioned above as its parent. Within the confines of this class, you make all the necessary modifications to produce traffic in the manner defined by your new favorite design engineer.
Having defined this class, you open a new testcase file with a few new lines of code that tell the factory object to slip your new traffic into the test using one of its built-in methods like ‘set_type_override_by_type’. Loosely translated, set_type_override_by_type tells the factory object, “Anytime someone asks for a base_transaction object, don’t give them one, give them a designers_favorite transaction instead,” (see section 4.7.3 of the UVM user’s guide[pdf] for more details).
Now, add the two new files to your regression build and kick off a series of tests. You’re all done! After a suitable regression you can pass along the results to the design and verification team. With any luck, you’ve exacerbated a new bug scenario, but at the end of the day, you were able to slip a new testcase into the verification environment without modifying a single existing file. Well done!
Cautionary Notes from Doulos’ John Aynsley
Of course, exploiting the factory relies on everybody having the discipline to use it at every appropriate point, even when they might have had something very different on their minds. No more sneaky obj = new when you should be doing obj = sometype::type_id:: create. You’ve gotta get into the habit of typing that magic spell every time you instantiate a transaction, sequence, or component, even if you yourself have no intention of instantiating anything but a fixed type.
Also, everybody has to remember that everybody else is entitled to come along after the fact and use the factory to tweak the behavior of the original code. Hence
seq = sequence_class::type_id::create();
is not a good idea, even if sequence_class is trivial, because who knows what exotic object type might get substituted after the fact by the factory? You really need
seq = sequence_class::type_id::create();
ok = seq.randomize();
just in case the type of the object gets a whole lot more complicated than you imagined, and actually does need randomizing at run time.
John Aynsley is co-founder and CTO at Doulos, where he runs the technical team as well as consulting for customers and delivering training courses and seminars. John has spent his entire career working in EDA, specializing in hardware description and verification languages, in particular VHDL, SystemC, SystemVerilog, and now UVM.
Hamilton Carter is the author of Metric Driven Verification with Shankar Hemmady, and has been awarded 14 patents in semiconductor design verification. His body of work in this field has been cited over 200 times.