Judging by advertising and datasheets and other promotional materials, verification is pretty much a simple, clear-cut, well-solved problem. Actually, that’s not quite true. Judging by any of those docs, verification was a complex, obtuse, poorly-solved problem until we released our last product, after which it’s become simple, clear-cut, and well-solved. Yeah, that’s more like it.
Of course, experience or any candid conversation reveals otherwise. Many tools and techniques have been developed over the last few years to address the burgeoning verification burden, but they remain a collection of tools and techniques that can be applied in many different ways by many different people. And that’s part of the problem: even within a company, they’re applied many different ways by many different people.
Synopsys and ARM collaborated to assemble a collection of best practices into what they call their Verification Methodology Manual (VMM), and it’s something Synopsys uses pretty consistently as a reliable framework for verification using SystemVerilog. But a single framework becomes a more tenuous concept given all of the different environments in which verification and validation occur. (I know, to a regular dude, verification and validation sound like two words for the same thing – unless you’re a therapist, in which case validation is something very different – but for specialists and ISO-9000 aficionados, they mean two different things – and I can never remember which is which.)
There’s the design environment where pieces of the design are verified, and then there’s the integration environment where everything comes together to play nice, and then there’s the lab, where the actual silicon comes back and needs testing. Each of these is done by someone else. In fact, it’s useful to have one environment that can work for all of these, but there are a number of challenges.
First, different engineers have different skills and experience with different languages. Second, the simulation and the testing environments, while overlapping, have some significant differences. In particular, a simulation environment can give you access to lots of internal details regarding what’s going on inside the design by peering into registers, observing memory, and getting reports on test coverage through the interaction between the testbench and models. Real silicon doesn’t necessarily provide all of that visibility, so any single environment needs a way to “stub out” the things that can’t be done in the lab.
With respect to languages, SystemVerilog will be known by verification engineers, C will be known by software engineers, and design engineers will mostly be comfortable with Verilog or VHDL. Renaissance engineers will be fluent in them all. Most engineers will have some familiarity with TCL, since it can be used for scripting so many tools. It is possible to get all of these languages to talk to each other, albeit in a round-about way, to allow TCL – the common denominator – to be used for much of the verification.
Getting C to talk to SystemVerilog
A robust verification environment is most likely going to be created using SystemVerilog based on the power and level of abstraction of the language. In order to facilitate hardware/software interaction, C can interact directly with SystemVerilog via the Direct Programming Interface (DPI).
Using the DPI, a SystemVerilog task – essentially a function – can be exported to C. Likewise, C functions can be imported into SystemVerilog. Each language sees the function or task as if it were native, but at elaboration time – essentially when the simulation environment links all of the pieces into an executable whole – C templates for SystemVerilog tasks are hooked to the tasks themselves, and SystemVerilog calls to C functions are connected.
This allows models written in C to be executed in a SystemVerilog verification suite and allows C functions to control what happens during verification or provide or retrieve information from the environment. Single data structures can be maintained, and both languages have access to them.
Adding TCL to the mix
TCL, while useful for scripting tools, cannot directly interface with a SystemVerilog program. There is no DPI for TCL. So any attempt to use TCL to control a verification environment is going to have to find some other way to do it
But TCL is really just a set of libraries built over C, and C can talk to SystemVerilog. So C can actually act like an interlocutor between TCL and SystemVerilog. But even there it’s a tad complicated, since you can’t call a C function directly from TCL. Instead, you have to define a custom command in TCL and then map it to a C function. Somewhat less convenient, but doable.
There are a few TCL library commands available in C to help out with this. In particular, the tcl_CreateObjCommand command allows you to register and map a C function by providing a command name and the name of a C function to map it to, as well as any data and a deletion procedure. Essentially, this acts like a constructor, and the deletion procedure that you can optionally pass acts like a destructor, more or less. (OK, for you purists and experts, I’m open to the possibility that, as a laydude, there may be some subtle strict semantics regarding the implications of a constructor or destructor that I’m blowing off; I’m not being that specific, so let it goooo…).
If you are replacing an existing TCL command with a custom version, you can use the tcl_DeleteCommand function first, followed by tcl_CreateObjCommand. In particular, you can replace the standard exit routine with a custom exit routine so that when the TCL session is complete, a custom C function can carry on afterwards. You can also register a custom exit handler for cleaning up memory and such using tcl_CreateExitHandler, passing the name of a routine that will act as the handler.
Finally, a TCL script that does the real work, possibly including any new commands that have been written (and which were registered to C functions using tcl_CreateObjCommand) can be run using the tcl_EvalFile function, where you specify the script file.
All of this registration stuff can be put into a C function – let’s call it tcl_AppInit – that is then used when TCL is started up. More on that in a minute.
Using this with VMM
So, given that we can arrange for TCL to talk to C to talk to SystemVerilog and vice versa, we can make use of this in the context of the VMM. Samir Patel at LSI did some very specific work on this and has provided the details in a separate article. Some high-level concepts are discussed here. The VMM specifies a series of steps to be taken, and somewhere in the middle there are two steps called “Start” and “Wait_for_end.” This is the place where various custom verification processes can be created to do different things. In particular, we can spawn off a set of TCL tests here.
One of the nice things about SystemVerilog is that you can start multiple parallel tasks or processes or whatever you want to call them, with flexibility on whether or not you wait for them to finish before doing something else – that is, whether or not they’re blocking. The fork/join structure is used for this. Within the structure, each command is launched for execution, and they may execute in any order or in parallel; they’re independent of each other. The join line specifies how they come back together. Using “join” by itself is equivalent to saying “join_all” – that is, nothing beyond the fork/join structure will execute until all processes started within the structure are complete – this is a fully blocking configuration.
Another option is to use “join_any” – in this case, further execution is blocked until one of the processes returns, at which point the program continues. Or, you can use “join_none,” which proceeds without waiting for anything before continuing – this is a non-blocking configuration.
When launching TCL in this context, there’s a bit of a catch: apparently once the TCL tests are launched, there is no clean return to say you’re done. So we have to fake it by creating an event that will be fired when everything is done so that we know when we’re done. For example, a routine that calls the TCL functions and then displays a message when done can’t just wait until the TCL function is complete and then display the message – the TCL portion will never return. So instead, you use a fork/join and launch both the TCL portion and an instruction to wait until the event fires, using join_any. (If you used plain old join, you’d lock up permanently, since the TCL process would never come back.) Once the event fires, you’re released from the fork/join, and then you can execute the “I’m done” message (or whatever it says).
This waiting around for things to complete isn’t unusual. The main function of the Wait_for_end block is to issue waits for various processes that have been launched in a blocking manner. In other words, a fork is created within which all of the wait instructions are launched, and by using a plain join command, nothing will happen until they all return. Of course, if there’s a problem somewhere, that could be an issue, so there are also timeouts defined using fork/join_any, so that things can proceed if any of the processes times out.
So as far as the VMM is concerned, in the Start block things go off and get done, and then it sits around in the Wait_for_end block until everything comes back (or it gives up). In the context of using TCL, VMM can call a SystemVerilog routine that then uses DPI to call a C function that can launch TCL through a call to tcl_Main, which starts the TCL interpreter. When calling tcl_Main, there are some expected arguments that may or may not be relevant, but have to be handled – and apparently it’s not obvious that you have to do that. The first two specify a calling function’s name and possible arguments (they’re called argc and argv, the latter being an array); those can be faked if necessary, but can’t be ignored. More important is the last argument, which is the name of a C function used to register commands and such – this is the tcl_AppInit C function discussed above. So the tcl_Main gets things going by allowing registration and mapping of commands and pointing to the script file when the interpreter starts up.
The TCL script can be tied into lots of different C functions, which can be tied into SystemVerilog functions to do the actual simulations, testing, whatever. Once that’s done, we have to work our way back to the basic VMM flow. That means coming from TCL back to C back to SystemVerilog. By registering a custom exit command in TCL, we can tell it to run a specific C function after finishing. That C function (which also has to have some obligatory arguments that may need to be faked) can then call, via DPI, a SystemVerilog task that can fire the event stating that everything is done.
Any tasks that are sitting around waiting for that event are now freed up. If we were waiting to display an “I’m finally done” message, that will display. And our contribution to the list of things being awaited in Wait_for_end will be in place.
So in this manner, it’s possible not only to use TCL to run SystemVerilog tests, but also to integrate it into the VMM in a way that allows a single environment to be used in numerous settings and for specific functions to be used or stubbed out as appropriate to the setting. Is it easy? Well, kind of, once you understand it. Does it take work if you’re figuring it out for the first time? Yes; just ask Samir. Is it clean? Hmmm… clean is a relative term. You could say that it’s not, given the gyrations you go through to get everything hooked and synched up. But if it works once put together, it can acquire the feel of cleanliness. Is it elegant? Well, yeah, kind of. It’s a pity that “elegant” tends to refer to situations where nasty problems that you wish were easy are solved by circuitous means that aren’t messy. When “circuitous” turns to “spaghetti-oid”, then we depart from the realm of elegant. And this certainly doesn’t have the feel of spaghetti.