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.
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.
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 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 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.
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.
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.
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.
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.
There are primarily two ways to start a sequence : use a
`uvm_do macro, or use the
start() method. If you have read How to execute sequences via `uvm_do macros ?, you might already know that
`uvm_do macros eventually call the
start() method, and the macros act as a wrapper to execute both data items and sequences on the default sequencer "m_sequencer".
There are ocassions when some components in the testbench keep running forever and cause the simulation to hang. Another case is when performing SoC level C tests, where you could have written a
while (1) code expecting an interrupt to cause the loop to break but, instead not get the interrupt at all. Not a good place to be in, especially if you tried running it in your local machine instead of an LSF farm. Let's look at what UVM has to offer to get around this.
As you might already know, modern SoC chips integrate many IPs and peripheral blocks which might be grouped together to form certain sub-systems. For example, a camera subsystem might capture signals from a device placed outside via MIPI CSI2 interface, process it using a graphic engine, and an internal DMA could send the processed data to some location in memory. Since a sub-system contains multiple IPs/peripherals, we will have to write vectors to test the basic functionalities of each block. Taking the example above, we want to know if all the MIPI lanes have been exercised, or if the graphic engine has had some kind of transaction to all the modules connected to it.