Creating an anatomically correct model for poking and prodding.
I don’t know about you, but I am looking forward to the day where we won’t even have to go to the doctor’s office for an exam. Instead, we will all have scanners in our homes that will transmit full digital models to our doctors who can then poke, prod, and examine us remotely.
This is essentially what the UVM register layer allows and does. The UVM register layer acts similarly by modeling and abstracting registers of a design. It attempts to mirror the design registers by creating a model in the verification testbench. By applying stimulus to the register model, the actual design registers will exhibit the changes applied by the stimulus.
The benefit of this approach comes from the high level of abstraction provided. The bus protocols for accessing registers can change from design to design, but any stimulus developed for verification of the registers doesn’t have to. This makes it easy to port code from one project to the next if the registers are the same. Taking a look at Fig. 1 provides a better understanding of what a register model implementation might look like with respect to the UVM environment.
Fig. 1 Taken from the UVM user guide, this image shows a register model relative to the rest of the verification environment.
One thing that is interesting about Fig. 1 is the ‘Generator’ bubble. Modern designs have hundreds if not thousands of registers and tens of thousands of register fields. Manually trying to write SystemVerilog code to represent those thousands of registers and register fields would be a gruesome task. This is where generators come into play. A generator’s job is to take the register specifications of a design (usually in the form of a spreadsheet) and automatically ‘generate’ the equivalent register model in SystemVerilog code. In order to use generators or even understand their output, one should first have a good grasp of the UVM register layer.
So how exactly does the register layer work?
First, the register model is built using an organized hierarchical structure. The structure incorporates memory, registers, and address maps into address blocks. Ultimately the blocks communicate with an adapter and receive updates from a predictor, both of which interact with the rest of the verification environment. Once the structure is built, register access API are used to send signals to the DUT where a monitor will report back any information to the register model for the purposes of synchronization.
Register Layer Structure
Each register has its own class and each field belonging to the register is created and configured within the class.
Fig. 2 Randomization is facilitated by declaring the register fields with ‘rand‘.
Registers are then organized into blocks where a register map is also declared. The register map organizes the registers according to their designated addresses. These blocks are then instantiated in a uvm_environment or the uvm_test depending on preference.
Fig. 3 A register and register map being created within a register block.
Up next is the creation of an adapter in the agent. The adapter is what makes abstraction possible. It acts as a bridge between the model and the lower levels. Its function is twofold: it must convert register model transactions to the lower level bus transactions and it must convert any lower level bus transactions to register model transactions.
Fig. 4 The register adapter converts register transactions to bus transactions and vice versa.
The predictor receives information from the monitor such that changes in values in the registers of the DUT are passed back to the register model.
uvm_reg_predictor parameterized with the my_transaction_class
Eventually, the structure that is created looks similar to Figure 5, below.
Fig. 5 Structure of the register model relative to the rest of the UVM testbench.
Until now, only registers have been considered, but the register layer also allows memory to be modeled as well. You can see from the image above a memory module is represented in the model, but that discussion is for another time.
Now that the structure has been described; how do we generate stimulus?
Register access is dictated by the register API. Methods like write() and read() called from a sequence will send data through the register model to the sequencer, then the driver, and ultimately to the DUT. The monitor picks up any activity and sends it back to the predictor, at which point the predictor will send the data to the adapter where the bus data is converted to a register model format for the register model value to be updated through a predict() method call.
Fig. 6 A sequence is created to call the API (write() and read()) which will cause movement of data.
Sequences are built to house any register access method calls. The block is declared (notice it does not have to call the create() method) and the registers are referenced hierarchically in the body task to call write() and read(). There also exists peek() and poke() which are utilized for individual field accesses. Many other register access methods exist, including: mirror(), get(), set(), reset(), and more.
Obviously, not everything about the UVM register layer is explicitly talked about here, but I tried to provide an overall idea of what the register layer is and how it’s used so that getting started won’t be so intimidating. There are plenty of examples out there. In order to really get to know the register layer, tons of additional reading, example browsing, and actual practice has to be done. For training and more information on UVM in-general, you can take the Fast Track to UVM Training as well as view more blog posts on the topic of UVM on the Aldec website.
Leave a Reply