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.
PS: If you had the chance to redesign the uvm_sequencer, what would it look like?