Some things are best described with tables—each column shows the values for one category, and each row encapsulates one given set of values for all categories.
This blog describes how to use tables in your verification environment code to make the code more readable and more easily maintained. The tables can be written in the code or they can be pulled in from an external file. For example, you can pull a Microsoft Excel file defining device configuration into your verification environment, eliminating the need to manually convert the data in the excel file into code. More than that, as the last example in this blog illustrates, you can create any code you want based on data read from Excel.
Our first example is a basic one, illustrating how to create tables in the code itself, by using the ein_table. Assume a device whose configuration is defined by bus type, bus width, and speed. Constraining the configuration unit using a table format is simple to write and, more importantly, simple to read and maintain:
type bus_type : [ISA, EISA, VL_BUS, PCI];
unit config {
b_type : bus_type;
b_width : byte;
b_speed : byte;
keep (b_type, b_width, b_speed) in_table {
ISA, [16, 32], 8;
EISA, 32, 8;
PCI, [32, 64], 33;
ISA, 64, [66, 133];
};
};
The information about the legal configuration modes is required not only during generation. Some checks, for example, might depend on the device configuration. You can use tables anywhere in the code:
if (b_type, b_width, b_speed) in_table {
ISA, 64, [66, 133];
} {
///… write checks that are relevant to this mode
};
if (b_type, b_width, b_speed) in_table {
ISA, [16, 32], 8;
EISA, 32, 8;
PCI, [32, 64], 33;
} {
///… actions that are relevant to this mode
};
};
This code is valid, but what about reuse? Copying and pasting these lines is error prone and challenging to maintain. Instead of copying the tables, let’s define a table once and use it multiple times in the code. To define a table, we define one or more rows using the etable_row type:
define <slow'table_row> "SLOW_CONFIGURATIONS" as {
ISA, [16, 32], 8;
EISA, 32, 8;
PCI, [32, 64], 33;
};
define <normal'table_row> "FAST_CONFIGURATIONS" as {
ISA, 64, [66, 133];
};
define <all'table_row> "ALL_CONFIGURATIONS" as {
SLOW_CONFIGURATIONS;
FAST_CONFIGURATIONS;
};
After these tables are defined, they can be used in constraints, in checks, just about anywhere:
keep (b_type, b_width, b_speed) in_table {
ALL_CONFIGURATIONS;
};
check() is also {
if (b_type, b_width, b_speed) in_table {
FAST_CONFIGURATIONS;
} {
///… write checks that are relevant to this mode
};
if (b_type, b_width, b_speed) in_table {
SLOW_CONFIGURATIONS;
} {
///… actions that are relevant to this mode
};
};
Now let’s take this one step further. Not only can you define tables in e, you can also read tables from another file – for example, from a CVS or an Excel file. With this capability, the person who writes the configuration file needs to know nothing about how the verification environment is structured and implemented, and the person who writes the verification environment doesn’t have to be told of every change in the configuration.
For reading the configuration table from a CVS file, you would use the ecsv_to_table() operator. For reading the configuration table from an Excel file, you would use the eexcel_to_table() operator.
For example, assume the CSV file “config.cvs” contains these two tables:
SLOW_CONFIGURATIONS
#type, #width, #speed
ISA, "[16,32]", 8
EISA, 32, 8
PCI, "[32, 64]", 33
FAST_CONFIGURATIONS
#type, #width, #speed
ISA, 64, "[66, 133]"
You can read these tables from your e code as follows:
keep (b_type, b_width, b_speed) in_table {
table from csv_to_table("config.csv" "SLOW_CONFIG") with {
<#type>, <#width>, <#speed>;
};
};
And why stop there? Tables can be a good source for creating code that follows any kind of regular pattern. Each row in the table holds the values for one instantiation of the pattern.
For example, if the verification environment defines a core unit that contains several fields, the properties for specific cores can be provided by an external file. The following code reads a table and, for each row in the table, instantiates a unit of the type core and constrains its fields according to the row’s data:
table from csv_to_table("config.csv", "Cores Info") with {
<#id> : <#id> core is instance;
keep <#id>.kind == <#kind>;
keep <#id>.address == <#address>;
};
As you can see – the code within the “with {}” block is simple e code, and each <#> is replaced with the value in the relevant column.
The input file could look something like this:
Cores Info
#id, #kind, #address, #enabled, #retainable
CORE0, A34, 0x1000, TRUE, TRUE
CORE1, BL, 0x8000, FALSE, TRUE
CORE2, B5, 0xaa00, TRUE, FALSE
//...
These constructs – in_table, table_row, and table from file with are all part of Specman. Use them to make your verification environment more user friendly, more readable -- and more reusable.
Enjoy Verification, Enjoy e,
Team Specman