We ran a webinar looking at how certain features of UVM can be emulated using VHDL. In particular, how can VHDL be configured?
The basic idea is that as a verification engineer or designer, you may want to run a whole set of tests one after another, such as when performing regressions. Regressions are used to see if a bug that was once fixed has re-occurred (i.e. the design has regressed or gone backwards). To do this it's very convenient if you can run multiple tests with different parameters, but without having to edit and re-compile code.
Between VHDL and the simulation tools, there are various options
If you want to know about the first three, we will be running the webinar again in the future - and when you register for our webinars you can download the presentation to view later.
Here, however, we are going to briefly present and provide the code for the last option - a kind of database. The idea is that the user can configure constants, initial values of signals, and initial values of variables by retrieving values from a database. In fact values could be read in during simulation (not just at initialization). From the users' point of view, they can look up values using some kind of unique ID for the object in the design that they want to initialize.
Luckily VHDL provides a way of creating a unique ID, using so-called "general attributes":
a'SIMPLE_NAME -- String name of a a'INSTANCE_NAME -- string hierarchical path a'PATH_NAME -- string hierarchical path excluding instance information
Here are some examples of typical output, in the order used above:
a :tb(bench):u2@e2(a): :tb:u2
What this means is that we can create a database file like this:
:tb(bench):u1@e1(a)::debug true :tb(bench):monitor:i 200
which consists of key/value pairs. The key is the instance name (returned by 'instance_name) of the item we want to set, and the following line is the value. We then can write a package to read in this file and make it easy to use. Here's an example of how the package can be used:
use work.configpack.all; architecture a of e1 is begin process variable debug : boolean; begin debug := configpack.get(debug'INSTANCE_NAME); -- ... end architecture a;
The code for the configuration package is available for download. What it does is:
We could use protected types, but it's a bit easier to read and understand a package, plus it has the advantage that it works with any simulator that has support for at least VHDL 1076-1993.
From the user's point of view, they include a package that looks like this:
package configpack is -- false means write debug messages constant nDebug : boolean := true; constant configArraySize : POSITIVE := 100; constant configPathLength : positive := 200; constant configValueLength : positive := 20; constant configFileName : string := "config.txt"; impure function get(key: string) return integer; impure function get(key: string) return boolean; impure function get(key: string; length : positive) return string; end package configpack;
Notice the overloaded get functions.
To give you an idea of the code, here's an extract from the package body, where the various data variables and constants are set:
use std.textio.all; use work.textutils.all; package body configpack is type configInfoT is record path : string(1 to configPathLength); -- lookup key value : string(1 to configValueLength); -- value end record configInfoT; type configInfoArrayT is array (1 to configArraySize) of configInfoT; constant nConfigItems : natural := getFileLineCount(configFileName) / 2; impure function getConfigInfo(configFileName : string)... constant ConfigInfo : configInfoArrayT := getConfigInfo(configFileName); impure function get(key: string) return integer ...
Note how we use functions and constants which are declared in a package body, but invisible to the outside world. This is an example of data hiding or encapsulation in software jargon. The user of the package doesn't have to know about these internal details, and can't see them as they are not made visible in the package itself.
The configuration package body makes use of some text utilities for trimming, normalising, and matching strings. These utilities are in the package textutils.
So if the user creates a file containing:
*monitor:i 200
what happens when they call get? Below you'll see the code of the function that retrieves integer values:
impure function get(key: string) return integer is variable matchCnt : natural := 0; variable value : integer; begin for I in 1 to nConfigItems loop if match(trim(configInfo(I).path), key) then matchCnt := matchCnt + 1; value := INTEGER'VALUE(configInfo(I).value); print("match found" & key); end if; end loop; assert matchCnt /= 0 report "ERROR: no matches for path " & key; assert matchCnt <= 1 report "ERROR: More than one match for path " & key; return value; end function;
This function has various advanced features, mainly that it is impure. An impure function can return a different value each time it is called (since it accesses an array of data declared outside itself). The function also does a reasonable amount of error checking.
We can also show you here a snippet of the database loading code. This is the code that reads in the database file and stores the keys (instance names) and values in a array of records. Each record contains two strings, one for the key and one for the value. The reading code must cope with variable length data, and the trick is to use L.all to refer to the contents of a line buffer when using textio. This is because a variable of type line is actually of type access string which is a VHDL way of declaring a pointer. To get at the data pointed at, in C you would write *L. In VHDL you use instead L.all. We also make extensive use of our text utility functions for trimming and normalising strings - and the match function which has the nice feature that it accepts a wildcard. Here is the code for reading the file:
-- expect pairs of lines, path followed by value for i in 1 to nConfigItems loop if not endfile(F) then readline(F, L); assert L'LENGTH > 0 report "ERROR: Empty line in file "; configData(I).path(1 to trim(L.all)'LENGTH) := trim(L.all); readline(F, L); assert L'LENGTH > 0 report "ERROR: Empty line in file "; configData(I).value(1 to trim(L.all)'LENGTH) := trim(L.all); end if; end loop;
As you can see there's quite a lot of code, so now probably the best thing is for you to download and experiment with it yourself. If you follow the download link, you'll find a zip file containing the code for the two packages, and a very simple example of usage.
Click here to get the code. In exchange, we will ask you to enter some personal details. To read about how we use your details, click here. On the registration form, you will be asked whether you want us to send you further information concerning other Doulos products and services in the subject area concerned.