Time for a new series of posts on agilesoc.com. I think it’ll be best to call this a series of challenges to the functional verification community at large. I’ll point out techniques that have irked me, wonder aloud why we use them and then challenge people to propose alternatives. The important part of this is not to agree or disagree with me (though obviously feel free to do either). What I’m hoping for is that people will stop and do a little navel gazing (aka: a powerful technique for fostering creative thinking that we don’t schedule nearly enough time for), to think a bit about what we do and why we do it. I’m sure some of these posts will come off as ranting which I’ll say from the outset I won’t be apologizing for! But I’m looking to get people talking, not put people on the defensive. These are meant as honest, constructive food-for-thought so keep that in mind as you’re reading.
Ready? It’s time to get heated! It’s time to get opinionated! It’s time to pick sides!!
You’re either with me, or you’re with: the UVM sequencer!
Has anyone’s initial impression of the UVM sequencer been oh ya… that makes sense? I’m guessing not. Here’s why that wasn’t my first impression.
My pet peeve with the UVM sequencer has to do with complexity and usability. It – and everything in and around it – makes performing the simplest function very cumbersome. Not to mention that getting started with it probably requires dedicated training material and/or a technical how-to paper and/or some example code.
A few observations directly from the UVM 1.1a library:
- the sequencer is 3 levels of hierarchy derived from the uvm_component. There’s the uvm_sequencer which extends the uvm_sequencer_param_base which extends the uvm_sequencer_base. If you concatenate the class definitions for each of those files, you have 1978 lines (that includes comments and whitespace).
- you can’t use the uvm_sequencer without the uvm_sequence which is another 3 layers of hierarchy and 1838 lines built upon the uvm_transaction. The uvm_sequence extends uvm_sequence_base which extends uvm_sequence_item.
- you also need the uvm_sequencer_analysis_fifo which is a specialization of the uvm_tlm_fifo with a minimal understanding of TLM
- as far as I understand, the uvm_sequencer can only be connected to the uvm_driver (that seems to be the intention anyway). Likewise, the uvm_driver can only be connected to the sequencer.
- if you want to use the uvm_driver without the complexity of the uvm_sequencer, I think you’re out of luck.
That’s 8 class definitions – 4 of them parameterized – and 3904 lines before you do anything! Now, you have a decision to make.
Option 1: Use the UVM sequencer
Now let’s say you want to do something simple, like build a write/read processor mechanism for driving/polling device configuration/statistics. First you’ll need to derive your new transaction from a uvm_sequence_item with the properties for your transaction. Let’s say it includes address, data and a transaction type (i.e. write or read). Then you need to define a sequence for configuration which instantiates an array of transactions with the means for defining the content. You’ll also need to derive a BFM from the uvm_driver. With your sequence, sequence item and driver in hand, then you can instantiate and connect everything in your testbench, though you’ll first need to define the systemverilog interface to connect the driver to the design and pass it to the environment through the uvm_config_db. Finally, you apply your sequence to the sequencer in your configure phase and you’re done.
From all that, you’re adding 3 more class definitions and an interface definition for a total of 7 classes, 4 parameterized classes and an interface. All that to dump a register configuration to your design.
Option 2: Don’t use the UVM sequencer
Contrast the above with a simpler approach of an interface-based BFM with a task-based user interface. To perform the same simple function, you could have exactly 1 entity with 2 methods.
interface simple_bfm(input *, output *); task write(addr, data); ... endtask task read(addr, data); ... endtask endinterface
To use this crude BFM, you’d define a list of configuration writes in your testbench somewhere and you’re done.
Comparing Complexity and Simplicity
Complexity, multi-level parameterized hierarchies and indirection are (generally) bad and that’s why I have a problem with option 1 being the default approach people take with UVM. There’s just way to much there and there’s nothing intuitive about the usage model. Is it extensible? Sure. Is it flexible? Absolutely. Is it powerful? Definitely… with a capital ‘P’. Is it practical? I don’t think so. Not practical enough to be the standard, best practice template for driving transactions to a design anyway.
Contrast option 1 with option 2. Simplicity and clean interfaces are (generally) good. That’s why I like the idea of option 2. It’s extremely simple, it requires no manual and there’s very few opportunities to screw it up. The usage model seems pretty straight forward. Is it extensible? Not at all, but it does the job it’s meant to do. Is it flexible? Maybe if you parameterize the bit widths but not more than that. Is is practical? Absolutely. It is absolutely practical for the problem it’s designed to address.
You’re Either With Me Or…
A way back when, I think I’m safe in saying option 2 was the default solution for hardware developers. As time has passed though, our default solution evolved into something far different. Without noticing, we’ve blown past the fine line that separates simple, practical solutions from solutions that go completely overboard in the name of reusability, extensibility and flexibility.
And we’ve kept on going.
Head-to-head, the simple BFM in option 2 is better than the uvm_sequencer and option 1. While I appreciate the effort that went into the building the uvm_sequencer and I appreciate that it does solve the problems it’s designed to address, my opinion is that things have spun out of hand and it’s just too complicated for the average Joe to use productively (or the above average Joe for that matter).
If you disagree and you’re about to suggest we need the uvm_sequencer for driving randomized transaction sequences and all the other fancy stimulus generation we like to do, pause for a second and think back to your first impression. I’ll assume, like mine, it wasn’t oh ya… that makes sense.
The uvm_sequencer does have it’s place or it wouldn’t have been invented. I believe that. But it could be time to consider other, simpler ways to solve the same problem.
-neil
PS: If you had the chance to redesign the uvm_sequencer, what would it look like?
OK, I’ll bite. Let me start off by saying that in general I agree. The UVM is a lot of code and it’s not very intuitive. But I put an asterisk on the “not very intuitive” statement. I’ll explain later. For now, let’s just work with what we have with the UVM. My first contention is that you said this, which is not true:
“if you want to use the uvm_driver without the complexity of the uvm_sequencer, I think you’re out of luck.”
You can write any code you want in a uvm_driver. There’s no “UVM enforcer” built into any compiler that I know of. The UVM is pretty much always presented in it’s full glory just to illustrate everything it provides, but you are *not* required to use it all. If you don’t need a sequencer, don’t use one! The trap that people are falling into that I think you and I agree on is that people are not following the rule of You Aren’t Going to Need It (YAGNI). Don’t write code that you think you might need someday. Wait until you actually need it before you write it. When you do need it, the UVM is there to help. If you don’t need it, don’t think you have to use everything you see in the UVM class reference!
Now I’m ready to explain the asterisk mentioned above. Is a sequencer intuitive? When I first saw it, it kind of was, I’m sad to say. You see, if you have had the need for extensible, flexible, re-usable code that can handle any data type while being stuck with a statically typed language (like C++ or SystemVerilog), you’ve seen UVM-like code before. If you haven’t then I absolutely agree, not intuitive. For me personally, I’m afraid I have to admit that a lot of the UVM is basically stuff I had seen before in my C++ days. Ever heard of the book Design Patterns by the Gang of Four? You are probably lucky if you haven’t had to read it. I will admit that sequencers and the function object (AKA, functor) pattern of sequences I have seen more in books than in real life, but the rest of the UVM, not too far fetched at all.
Sadly, there’s just no way around this if you choose to work in a language that is statically typed yet your code needs to be flexible, extensible, and re-usable. Something like the UVM is the unfortunate inevitability. You make a point that not all your code has to be all those things, and that simple, straightforward functions and tasks can get you a long way, but I think the popularity and traction of the UVM can be attributed to the fact that people really want flexibility, re-use, etc. To me that says that the industry apparently craves a language more like Python or Ruby but got a language closely related to Java and C++ instead[1]. For better or worse, the UVM is best workaround we have right now. The good news is that the UVM is not quite as bad as you make it out to be.
1. Interesting post and discussion on that here: http://www.coolverification.com/2011/08/uvm-and-the-death-of-systemverilog.html
Bryan, thanks for stepping up! I’ll take your comment as a half vote for me and half vote for the sequencer :).
A couple of new questions to pose to whoever is next…
* bryan points out the rule of “you aren’t going to need it” and how a lot of people fail to follow it. does the example of extensibility/flexibility/reusability in the form of the uvm sequencer breed needless extensibility/flexibility/reusability (aka: over-engineering) of a stimulus path? Or is that something we’d do anyway?
* if design patterns were general knowledge in the verification community, would it make the uvm sequencer any less complex and/or more practical (i.e. would understanding the uvm sequencer justify it’s complexity)?
Thanks Bryan!
-neil
Ha! It is actually a half vote for you and a half vote for the notion that using lots of design patterns is a sign that you have picked the wrong language. Interesting reading on the matter:
http://blog.plover.com/2006/09/11/
http://www.cincomsmalltalk.com/userblogs/ralph/blogView?entry=3335803396
http://blog.plover.com/prog/johnson.html
I guess I also did give half a vote (now I’m using more than my one vote) for the UVM because we don’t currently have a viable verification language that has absorbed the patterns that would obviate much of the UVM’s complexity (Cue the specman e proponents?). Until that happens, the UVM does an OK (not great) job of writing, wrapping up, and hiding some of the design pattern code for you.
You have to understand where the UVM sequencer comes from in the very first place. It just try to implement the Specman eRM sequence/driver architecture.
In Specman, sequence is just a mean to reuse TCM (time consuming methods, similar to task in SV) with some sort of scheduling support. It’s pretty straightforward in Specman and it actually makes lots of to package TCM inside sequence this way, since TCM can’t exists in void, it has to live inside a struct.
The only problem is this architecture does not translate well into SV due to inherit problem of how the language is designed. I double there are any easy way to solve this problem with SV.
I absolutely agree with you.
I view OVM/UVM as nothing more than the EDA’s industry attempt at selling more services.
Having done DV for eons, i dare challenge anyone out there in who can get a DV project done faster using OVM/UVM than me using straight forward class-based SV.
UVM is akin to using a sledgehammer to kill an ant.
Just look at the the syntax of callbacks to decide on throttling a sequence item based on feedback from the DUT. Just plain ugly syntax.
paul, I don’t know if I’d go as far as saying uvm is an attempt at selling more services, but I do think the skepticism of eda companies is partly deserved. I look at uvm as the latest in the evolution of verification *frameworks* that have been and continue to be optimized at a rate that is much faster than user adoption and understanding. the sequencer is my first example. Given the constraints of systemverilog, it’s a very well designed/optimized solution created by good intentioned people that very few users actually understand or use productively.
…or, like you say, a sledgehammer for killing ants sums it up nicely as well!
thanks for reading and taking the time to comment.
-neil
I also agree with you. UVM sequencer does nothing than just selecting a sequence or transaction in its FIFO, then throw it to the UVM driver. We can choose the arbitration to be random, round-robin, user-defined. For random arb, the sequence/transaction are already random usually, so choosing randomly a random one doesn’t make sense. For round-robin arb, this simply does nothing and is useless (but consumes alot of code). For user-defined arb, users cannot know well about the sequencer’s state, so usually, we cannot use it. The interface bt. UVM sequencers and UVM drivers is too complicated so if you don’t user UVM sequencers, but writing your own one to connect with UVM drivers, it will take a lot of time.
I agree. Probably, after class support in SV, we just tried to copy everything from specman e and c++ into the verification (i know e is for verification but it is vendor specific) and have failed. Interfaces are powerful constructs and some more flexibility inside interface constructs can help a long way. Also, randsequence, randcase have not been used upto full potential. It is possible to write more elegant, concise code using plain SV with no external thousands of lines of code and that removes a lot of learning cycle. Even if you know UVM, there is some time needed before you become an expert. And verification engineers should be experts in how verification should be done.