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.
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.
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.
A few months ago, I was involved in writing a couple of tests that had to be run using RTL netlists with scan chains in them. Since this involved a lot of gate level signals, it was already cumbersome to debug. The idea was to enter the scan mode and shift out values in the chain and then be able to observe the value of a particular flop, after so many cycles at the output pad. So, there was a need to check if we got the right value at the pin after scan entry.
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.
Coverage metrics are widely used in SV/UVM verification to improve quality of the test suite and estimate the effort required to finish the verification task. They indicate how much of the design code has been exercised by existing set of tests, and provide an idea of how to write future tests that can target certain coverage holes. You can perform a code and functional coverage analysis after every regression to identify how many tests should be developed in order to target specific features of the design. Many times you'll find that in spite of trying every combination of input stimuli, there are certain pieces of code that simply does not get hit or exercised in simulation. You might have stumbled onto something called as unreachable code or dead code. As the name implies, it is part of the source code of a program or RTL that can never be executed because there is no control path to the code. Dead code can also be a piece of code that may be executed but does produce any effect on the output.
What is verification ?
Functional verification is the process of verifying that the logic design conforms to specification. For example, if the design is a simple 4-bit counter, functionally speaking, the counter should count from 0000 to 1111 and roll back to 0000. Verification is the task of verifying that the counter "does what it is supposed to do" - count from 0000 to 1111. If the design has some fault, and the count stops at 1100, then there's a bug in the design, which needs to be corrected. As you can see, this will make or break the design. In modern computer chips, it is very important to perform functional verification before the chip is sent for production. You wouldn't want to buy a product which contains a chip that doesn't work right ?
What is GLS ?
GLS is short for Gate Level Simulation, and as the name suggests, they are simulations run on a gate level netlist. As you might already know, the verilog design code is synthesized with a set of technology library files into gate level netlists, and will contain a load of buffers and inverters placed by PnR tools to correct timing. Simulations are performed on this netlist to see if there are X propagations or functional failures.