Today, project teams build huge verification environments, where verification consumes 40-70% of the resources needed in a typical cycle. Because a verification environment typically contains concurrent mechanisms for controlling traffic streams to device input ports, and for checking outstanding transactions at the output ports, Verilog and VHDL have traditionally been used for building verification environments. Unfortunately, it is widely recognized that for more complex verification environments and problems, these languages do not contain the necessary constructs for modeling the verification environment efficiently.
As a result, many project teams have moved to using higher-level languages such as C and C++ to be more efficient in creating the verification environment. Unfortunately, these general-purpose languages do not have any built-in constructs for modeling hardware concepts such as concurrency, operating in simulation time, or manipu lating vectors of various bit widths. Without these constructs, handling device-specific needs such as controlling synchronization between traffic streams, checking correct timing and formatting traffic data are extremely difficult and time-consuming. Project teams often use a mix of HDL and C/C++ code to attack this verification problem, spending a good deal of time on the interface between the languages.
In addition, advanced methodologies require testbenches and testbench languages to implement advanced concepts like constraints for test generation, assertions and definition of coverage scenarios for functional coverage analysis.
With these problems in mind, what should a verification language look like? It should combine the best features of the most popular HDLs and general-purpose languages:
With these features in place, verification engineers can focus much sooner on what needs to be verified rather than how to do it (implementing the environment infrastructure).
- Specifying the traffic and traffic parameters in the same terms as the device specification.
- Automatically generating these traffic streams with the ability to target corner cases of the des ign.
- Storing and checking outstanding transactions.
- Checking protocol adherence.
- Collecting functional feedback on the stimulus and the device under test.
- Automatically responding to feedback from the device during simulation.
This article shows how the e verification language implements all these, as compared to traditional verification environments using Verilog/VHDL/C/C++.
Developing a Verification Environment
To keep the discussion concrete and complete, this article focuses on one sample application domain: a networking device processing Ethernet frames. The same steps in building a verification environment and very similar results are applicable to almost any other application domain.
The verification steps we go through are:
Step 1: Generating traffic streams
- Generating traffic streams
- Driving traffic into the design (stimuli)
- Checking these data streams
- Checking protocols and timing
- Tracking progress
- Modifying the environment due to spec changes or product derivatives
- Writing scenarios
When building a verification environment, the verification engineer often starts by modeling the device input stimulus. In Verilog, the designer is limited in how to model this traffic because of the lack of high-level data structures and the notion of dynamic lists. As a result, sequences of traffic items are often represented as a series of task calls to initialize the frame, build its header, and payload. This "cut-and-paste" code is lengthy, hard to read and hard to maintain (if there is need to add another attribute passed to all task calls).
The verification engineer often needs constructs better suited for modeling and manipulating frame sequences. C provides some of these. However, it becomes apparent that C was not dev eloped with hardware in mind as soon as bit vectors need to be specified on non-four byte boundaries and manipulated. Unfortunately, the engineer still has to write the functions to manipulate the lists and, to use memory efficiently, now has to worry about correctly allocating and de-allocating memory and handling pointers. These memory problems can result in hours of chasing bus errors and segmentation faults.
The e language provides constructs for modeling traffic streams with built-in functions for generating them and automatically takes care of memory allocation and garbage collection (memory de-allocation): Notice that in e, there is no need to create generation functions for each struct. The generator is built-in, and once the structs are declared, meaningful stimuli can be generated. Defining a field ('frames' in the above example), which is a "list of frames", is enough to have a sequence of frames gene rated. The built in generator performs memory allocation dynamically, and a built-in garbage collection takes care of de-allocation.
Step 2: Driving stimuli into the device
With the data structures in place, the engineer has to consider how data will flow though the verification environment. Typically, a task or function will drive data into each of the device's input ports and a task or function will pull data from each of the device's output ports.
In Verilog, the device specific synchronization for that port is fairly easy because the engineer has direct control over the signals. However, many other functions are typically needed to synchronize the basic traffic flow, format the data at each end, and print the data structures for debugging and post-run checking.
C and C++ help with organizing and formatting data, but the benefit is eaten away because these general-purpose languages have no concept of simulation time or hardware signals. Special code must be written t o interface with the specific Verilog Programming Language Interface (PLI) to drive and sample simulation signals at the appropriate simulation time. Moreover, PLI-related code often needs to be tailored for different simulators. In addition, traffic structures built in C/C++ must be manually converted to the bit, byte or word format the device expects to receive and send.
The e language in this case combines the benefit of both worlds. It has the knowledge of simulation time, constructs for concurrency and built-in controllability, and observability of the simulation signals, regardless of the simulator. Further, each structure in the environment automatically has pre-defined pack and unpack functions for converting it to a bit stream, or vice versa, from bit stream into the data structure format and fields.
The following code generates, prints, packs and sends several frames into the device, making use of all of these built-in functions. Step 3: Checking data streams
To check that the device works correctly, monitoring tasks or functions need to be written. In the case of an Ethernet device, such tasks would pull data from the output ports and compare it with the expected data. The expected data is often determined within a self-checking environment by storing the outstanding transactions, or through a reference model of the device.
As with traffic streams, Verilog's lack of built-in lists makes it impossible to create dynamic scoreboards to verify the data integrity and routing of outstanding transactions. This inflexibility leads to built-in list sizes that use up memory and create assumptions in the code that are difficult to maintain if this code is to be portable.
Built-in lists and list functions, recursive data structure comparison, and automatic memory allocation are key features in the e verification language. These constructs make it much easier to implement data i ntegrity checking: Step 4: Checking protocols and timing using assertions
Because Verilog and VHDL are directly tied to the simulator, it is easy to interact with the device to get timing information. Unfortunately, most of the interesting timing checks are not straightforward and contain multiple edge dependencies. A timing check might be easy to specify in an English specification, but it is another story to write it in procedural code, be it Verilog, VHDL, C or C++. As a result, in the past few years, declarative assertions (in languages such as PSL/Sugar) have become an appealing approach.
The e language includes a built-in assertion language, similar in expressiveness to PSL/Sugar. Constructs to capture timing scenarios, automated capabilities to observe the device and checking constructs to relate timing scenarios are all built-in. Such an assertion language makes it easy to specify and combine complex timing scenarios that can be used for even the most difficult protocol checking.
Let's take as an example a PCI behavior rule, form the PCI bus specification: Step 5: Tracking progress
A key requirement in verification is having a reliable metric that shows the progress towards hitting all the verification goals. Functional coverage, a metric that tracks which functionality of the device was verified, is being recognized as a highly reliable measure. Neither Verilog, VHDL nor C/C++ have the notion of functional coverage, causing many project teams to create elaborate combinations of log files, parsed by various scripts which then produce some summary reports. This enables very limited capabilities, and yet another maintenance burden on the implementers of the verification environment.
The e verification language has built-in functional coverage constructs, supporting simple value coverage, value-transition coverage and cross-coverage. Step 6: Specification changes and product derivatives extensibility
Once the environment is in place, the engineer might need to extend or modify it to accommodate changes or updates to the specification. Verilog, VHDL and C clearly fall short in this area because they provide no constructs to incorporate any changes: modifications need to be done within the original code. Changing the original code often results in bugs because the intent of the original code is not clearly known.
C++ provides nice constructs for inheritance and aggregation, but how does this affect the code that actually generates the traffic? This code typically has to be changed in numerous places (for instance, change many member functions) to accommodate the new data structure, or be changed retroactively to be "generic." In short, C++ inheritance creates comp lex design phases that can force the engineer to go through many design iterations.
The e verification language offers the notion of extensibility, which is a key feature of an Aspect-Oriented Programming (AOP) language, and provides the constructs necessary to modify or change the functionality of the environment without having to change the original code in any form. For more information on AOP languages, visit the Aspect-Oriented Software development website or the Eclipse aspect-oriented extension to the Java programming language website.
Let's assume the engineer needs to add SNAP frames as a variant on the Ethernet frames. SNAP frames differ from Ethernet frames only in two added fields. We can easily adapt our environment without modification, adding the new data and functionality on top of the existing code: Ex tensibility is not only useful for layering tests, fixing bugs and incorporating spec changes, it is also the critical feature that allows for easy verification reuse. Extensibility encourages a layered approach and enables entire "aspects" to be layered on top, of or across, an existing module. This is also critical for creating configurable verification components.
Step 7: Writing scenarios
Once the verification environment is in place, the project team now needs to focus on writing scenarios to verify that the device behaves according to specification.
The problem with Verilog and C is that you have to write explicit randomization code for each attribute the designer wants to randomize. There is also often interest in mechanisms to weight particular attributes towards values that denote typical or corner case scenarios. Such mechanisms in Verilog and C are typically non-obvious, maintenance burdens.
e provides not only the built-in capability to randomly genera te any data structure simple or complex automatically with weighted distribution, but it also provides constructs for constraining the generated values within acceptable ranges. Additionally, e provides mechanisms to easily layer test-specific constraints for particular tests, and take run-time feedback from the device to focus this random test into hard-to-reach corner cases: Combining this generation capability with the temporal assertion language gives the verification engineer a powerful capability for identifying complex, internal corner case scenarios and generating specific traffic to create conflicts deep within the device: Conclusion
Although HDLs have traditionally been used to create verification environments, many project teams are now looking to move to more robust languages and tools. HDLs are great for design but not good enough for verification. Device complexity has made it too difficult to model the complex traffic and timing scenarios and is driving project teams to look at the special-purpose languages.
In addition to the e verification language, languages like SystemC, PSL/Sugar, and SystemVerilog address some of the deficiencies in C++, VHDL and Verilog. However, while some are more mature and complete than others, none of them contain all of the features included in the e verification language.
This paper has demonstrated how the e verification language significantly reduces the time needed to create a verification environment and test suite, as well as enables the most modern and powerful verification methodologies. Using a high-level verification language that is combined with the built-in engines of a testbench automation tool, a project team can be more productive much sooner in the project schedule, thus reduce the time and resources needed for the verification environment and significantly increase the quality of the final product.
Zeev Kirshenbaum is a lead product engineer at Verisity Design, Inc. Through his roles at Verisity, which included R&D, verification methodology development, and technical marketing, Zeev has been involved with different aspects of functional verification, both from the user and methodology perspective, and the technology behind the implementation.