Loooooops. A very powerful tool. SystemVerilog has many different types of loops that can be used in a variety of ways. Using the correct loop type in the correct context helps in readability, maintenance, and shorter code. Here are a few tips to know when to use what type of loop in SystemVerilog.
The one thing that verification engineers spend most of their time on is debug. The most traditional way of debugging any problem is by sifting through logfiles to understand exactly what went wrong in a simulation. A lot of things can cause the testbench to break and the test to fail, and the logfiles can only help if you put intelligent display messages in them.
In an earlier post, I had given a small introduction on why performance verification is necessary for today's system on chips, along with a few key metrics that can be measured. Since any system will have multiple masters and multiple slaves, it is quite important to exercise these elements in various combinations such that the fabric is stressed and its internal arbiters and buffers are exhausted.
One of the most commonly asked questions in System Verilog for interviews is how to uniquely constrain any given array, or any two variables. There are a couple of workarounds for this, but we didn't have a clean shortcut to achieve this very useful functionality until the 2012 IEEE revision.
Hardware behavior is made more configurable through control registers, and the verification of these registers has become one of the primary items in the to-do list of any design. It is quite pointless to test any other feature of a design if we cannot access/modify its register bits to change any of its functionality. Registers are typically accessed using low-bandwidth bus protocols like AMBA Advanced Peripehral Bus, IBM On-Chip Peripheral Bus or something similar developed in-house by a semiconductor firm. It's because of these registers that software can exercise greater control on the overall behavior of the chip, and most design specifications have a number of pages detailing the functionalities of each bit of every register.
SystemVerilog constraints are pretty amazing ! Lets see one use case where constraints are used to generate two queues of random sizes with unique values. Let us assume total number of elements in each queue should be less than or equal to 10.
In a previous post, we saw that
covergroups are also inherited by child classes and the result of
sample() on coverage of both base and child classes. Although we can keep building new covergroups in each derived child class, it would be worth to explore if the same covergroup can be overridden with a new set of coverpoints and bins in the child class.
The first way to sample covergroups is to specify an event like clock edge or an event handle that can be triggered from elsewhere in the testbench. The second way to sample covergroups is to explicitly call
sample() at places where we want the variables to be sampled. The first method is usually preferred for repetitive sampling at regular event triggers. For example, we can sample the variables on every positive edge of the clock or whenever an event called "interrupt" happens. The best way to sample values at a set of specific places in the testbench is to call
sample() method as required.
There are times when a signal within the design has to be probed or monitored for certain tasks in the testbench. Typically these are accessed via hierarchical references and tend to break when the same code is ported to different projects because of changes in design hierarchy. UVM has a set of DPI implementation tasks for backdoor accesses that has a similar effect and achieves much better code reusability.
Object oriented programming has a feature called inheritance that allows child classes to inherit members from its parent class without having to redeclare them in the child class. It's a great way to reuse existing code, and to make changes to testbenches without touching the base class structure. A
covergroup is a System Verilog keyword that allows the user to declare and define the variables to be sampled for functional coverage. In this post, I'll just share what simulation results tell us about how inherited covergroups behave and how their coverage numbers are affected.
An interconnect is the backbone of any system as many processor cores, DMA, graphic engines, memory and other I/O devices connect to it. Performance requirements have undergone a steep climb in today's sophisticated world where electronic chips can be found everywhere including consumer appliances, healthcare, industrial controls, and automobiles. Whatever the field may be, the consumer always expect top notch performance without any visible lag or mediocre user experience. Hence, in recent years another field of verification has sprung up in additional to functional - performance.
An interconnect is the backbone of an SoC as many cores, IPs and memory blocks are connected to it and hence is usually an important part of the verification plan. Each master and slave would probably be running on different clocks, have different data bus-widths and hence different requirements. From a testbench perspective, it would be interesting to think about how to organize all these design parameters so that any change in the design results in a lower effort to reflect that change in the testbench. This post will describe one way to do that in a System-Verilog based testbench.
What I like about the register layer in UVM is that it provides a very convenient interface to program registers in a design with minimal trouble. I said minimal because I feel that it is still under the process of evolvement to provide the user with a complete set of API, and hence might require a little work-around here and there - or well, it could be that I haven't found the right solution yet. So, I am going to describe an out of the way approach I used recently to randomize registers to my whims and selectively write certain registers.
UVM factory mechanism makes the testbench more flexible and re-usable by allowing components to be overriden via the
type_id::create() method. The idea is that at run-time, an object of the overridden data-type will be returned instead of the original. However, it might give a compilation error when a member of the new sub-class component is being accessed in the new environment unless properly casted. This post will describe the scenario and how to overcome the error by casting.
UVM sequence macros are a great way of reducing code and hiding away some details.
`uvm_do macros enable a sequence item to be created, randomized and executed on a sequencer all from a single line of code.
`uvm_create is another macro which simply creates an object of a sequence item so that it can be handled later on. Let's see what the name of an object created by
`uvm_create would look like. Unlike a typical
type_id::create() method where you get to specify a required name,
`uvm_create does not have any, not that it matters, but just for trivia.
Constrained randomization is a powerful feature of System Verilog that are generally applied to members of class objects which can be later extended, inherited and overridden. Sometimes we do not need the full blown feature set provided by classes to perform simple variable randomizations and would probably hesitate to create a class structure just to hold such variables. A simpler mechanism to randomize data that do not belong to a class is provided by the scope randomization function
Many a time I have written functions that end up having only a single line of code in it and wished for a better alternative. System Verilog (1800-2009) has a construct called
let that defines a template expression that can be used for customization and text replacement.
In the UVM world there exists a function to reset a register block within a model. This is a step that many beginners often overlook because the need to invoke this might not be very clear until errors crop up. The register model primarily holds three different kinds of values each for a different purpose. Let's see how the
reset() method affects each of them.
UVM register model is quite extensive and has many useful API that help query registers and fields based on their names. Typically, registers are accessed by hierarchical references and there may be a better alternative in cases where there is a consistent naming scheme applied to all registers in the model.
Creating a global singleton object that can be referenced from elsewhere in the testbench is sometimes a good thing. This is very similar to the way
static variables in a class work - only one variable is created and made accessible for all class objects. In this case, a single class object is created that can be accessed from other testbench components. As an example, you could have such a class object to contain all the design or testbench specification features like number of masters and slaves, or clock frequency requirements for each interface, etc. Let's see how to effectively create a singleton object.
The testbench I was working on took quite some time to compile, elaborate and output an executable file. The design is an interconnect that has mappings from different masters to various slaves and has service registers within it accessible by specific masters. There were a block of registers for each master that would control how the transactions from the master would behave, and this required writing a lot of sequences to configure and simulate different configurations for each register. I wanted a better way to run and test a particular register set configuration without re-compiling the entire testbench and design.
It's a Hardware Verification Language. As you might already know, hardware (computer chips) is designed using a Hardware Description Language (VHDL, Verilog) which is then synthesized into gates like NOR, NAND and sequential elements like Flip-Flops. So before you do synthesis, which is a tedious process, you would want to make sure that the functionality aspect of your HDL-constructed design looks good.
Yes. UVM has a class-based dynamic queue that can be allocated on demand, passed and stored by reference. Eventhough
uvm_queue is a parameterized class extended from
uvm_object, it is not registered with the factory and hence invocation of
new() function is the correct way to create a queue object.
One of the first few items in the checklist for a failing testcase is the clock to the module. Usually an external crystal oscillator would be fed into a PLL block within the SoC to obtain and supply derivative clocks to all other parts of the system. So if a peripheral module does not respond when its control registers are being read it would be helpful to check if the clocks to the module are running and are of the correct frequency along with top level connections.