Intro To SystemVerilog
Intro To SystemVerilog
Introduction to
SystemVerilog
Restricted document for designated employees of Nvidia Corporation.
Provided for Nvidia on 17-May-2019.
Restricted document for designated employees of Nvidia Corporation.
Provided for Nvidia on 17-May-2019.
Introduction to
SystemVerilog
Restricted document for designated employees of Nvidia Corporation.
Provided for Nvidia on 17-May-2019.
Restricted document for designated employees of Nvidia Corporation.
Provided for Nvidia on 17-May-2019.
Introduction to
SystemVerilog
Doulos takes great care in developing and maintaining materials to ensure they are an
effective and accurate medium for communicating design know-how. However, the
information provided on a Doulos training course may be out of date or include omissions,
inaccuracies or other errors. Except where expressly provided otherwise in agreement
between you and Doulos, all information provided directly or indirectly through a Doulos
training course is provided “as is” without warranty of any kind.
Doulos hereby disclaims all warranties with respect to this information, whether express or
implied, including the implied warranties of merchantability, satisfactory quality and fitness
for a particular purpose. In no event shall Doulos be liable for any direct, indirect, incidental
special or consequential damages, or damages for loss of profits, revenue, data or use,
incurred by you or any third party, whether in contract, tort or otherwise, arising for your
access to, use of, or reliance upon information obtained from or through a Doulos training
course. Doulos reserves the right to make changes, updates or corrections to the
information contained in its training courses at any time without notice.
Contents
1 Introduction ..................................................................................................3
2 Programming Language Features ................................................................
13
3 Basic Data Types .........................................................................................
29
4 Interfaces .....................................................................................................
43
5 RTL Processes .............................................................................................
61
6 RTL Types ....................................................................................................
75
7 Clocking Blocks ............................................................................................
93
8 Arrays and Queues ......................................................................................
113
9 Bus-Functional Modeling ..............................................................................
127
10 Randomization ........................................................................................
135
11 Coverage ......................................................................................................
147
12 Other Language Features ............................................................................
153
13 The Direct Programming Interface ...............................................................
169
14 Index ............................................................................................................
189
Introduction
Introduction to SystemVerilog 1.0
Introduction
Aim
Topics covered
• What is SystemVerilog?
• Language evolution
• Language features
• Modules, ports, and parameters
• Standard verification methodologies
• References
What is SystemVerilog?
When it was first introduced, SystemVerilog was announced as the world’s first HDVL, or hardware
design and verification language. SystemVerilog is meant to encompass the features of both an HDL, i.e.
Verilog, and a language for functional verification.
The current version of the SystemVerilog standard resulted from the merger of the IEEE 1364-2005
Verilog standard and the IEEE 1800-2005 SystemVerilog standard. Officially, Verilog no longer exists as a
standalone language standard.
SystemVerilog is usually viewed as having three main components, namely SystemVerilog RTL,
SystemVerilog Assertions, and SystemVerilog Test Bench. Each of these three components go by various
names.
Introduction
Introduction to SystemVerilog 1.0
Language Evolution
IEEE 1800
Classes SVA DPI SystemVerilog
(from OpenVera) (unified with PSL) (from SuperLog)
SystemVerilog 3.0
Interfaces
(from SuperLog)
VHDL C
Verilog 2005
Verilog 1995
SystemVerilog has evolved from the Verilog hardware language. At each stage in the process, new
features have been added; Verilog-1995, Verilog-2001 and Verilog-2005 remain as subsets of
SystemVerilog.
The original Verilog language shares some features with VHDL and C.
Verilog-2001 and Verilog-2005 added a few new features to the language, most of which come from
VHDL and C.
SystemVerilog 3.0 again borrows from VHDL and C – by now most of the features of these languages are
part of SystemVerilog – and adds interfaces and assertions.
SystemVerilog 3.1/a/1800 add features for writing testbenches, a new syntax for writing assertions and
new applications programming interfaces (APIs).
module top;
logic aa, bb, qq; 4-state variables
logic [3:0] cc, dd, pp;
blk #(.n(4), .m(4)) inst (.a(aa), .b(bb), .c(cc),
.d(dd), .p(pp), .q(qq));
endmodule
SystemVerilog uses ANSI-style port, parameter, and argument lists from Verilog 2001 in which both
inputs and outputs appear on the same line when declaring modules, tasks, and functions. (Although the
legacy Verilog 1995 port syntax still exists for backward compatibility, there are situations in
SystemVerilog where ANSI-style port lists must be used.)
In Verilog and SystemVerilog, when you insert a range at the front of a comma-separated list of names,
you have to be aware that you are in effect introducing a vector data type which is used for all the names
in the list. So in the example above, inputs c and d are both n-bit, 4-state vectors.
SystemVerilog introduces a new keyword logic that has two different meanings, depending on the context
in which it is used. Either it is equivalent to the old keyword reg, meaning that it is defining a 4-state
variable as opposed to a wire, or it is used to specific that the data type of a variable or a wire is 4-state,
as opposed to something else. Unlike Verilog, where the same 4-state data type is used throughout most
of the language, SystemVerilog fully supports named, user-defined data types.
Another difference between Verilog and SystemVerilog is that SystemVerilog allows an output port to be
connected to a variable: in Verilog, an output port can only be connected to a net/wire.
Introduction
Introduction to SystemVerilog 1.0
reg [7:0] r;
Each bit is 0,1,X, or Z
logic [7:0] l;
In most situations the new logic keyword is synonymous with reg, but it is also possible to use the logic
keyword when declaring wires. However used, logic means that each bit of the variable/wire is 4-state (0,
1, X, or Z). Another new keyword, bit, is used exclusively to define 2-state variables that can have the
values 0 and 1. Either of these new types can be used to define vectors (as shown above).
The keyword var can be used to declare a variable as opposed to a wire. There are almost no situations
where the var keyword is actually necessary, so it is usually omitted.
The keyword logic used in a port declaration implies that the port is a variable, as opposed to a wire. So
in the example above, ports a and p are wires, whereas b, c, q, and r are variables. It very rarely makes
and difference whether an input port is a variable or a wire.
For many experienced Verilog users, one of the most surprising feature of SystemVerilog is that it allows
variables to be used in several contexts where Verilog required the use of a wire. In particular, a
SystemVerilog variable can be assigned by exactly one continuous assignment or can be connected to
exactly one output port. A variable can still be assigned from any number of procedures, of course. In the
example above, we see continuous assignments to variables and wires, and also see output ports
connected to variables and wires. In practice, this means that it is very unusual to need to use wires in
SystemVerilog: variables are adequate most of the time.
Another of SystemVerilog's innovations is shorthand port connections. In the common situation where a
port is connected to a variable (or a wire) of the same name, the port connection can be shortened from
.name(name) to .name, as shown above. The so-called wildcard port connection .* means connect every
port to a variable (or a wire ) of the same name. It is possible to combine a wildcard port connect with
explicit connections so that certain ports are connected explicitly by-name and others are connected using
the wildcard connection.
Introduction
Introduction to SystemVerilog 1.0
SystemVerilog Assertions
Caveats
• C-like control constructs and data types
• Concise RTL
A better Verilog
• VHDL-like package and import
• Assertions
• Classes
• Constraints and coverage based on classes Class-based
• Built-in types - strings, queues, maps verification
• Virtual interfaces
SystemVerilog is a very large and complex language, and as we have already seen could be regarded as
being several different languages rolled into one. As a result, simulator vendors have struggled over the
years to implement the entire language in a consistent and high quality way, and it is very important to be
aware of some of the pitfalls as you start to adopt SystemVerilog. Otherwise you will waste a lot of time
trying to debug your "unusual" coding style or have difficulties porting code between simulators.
There are some areas of SystemVerilog that are pretty solid, that is, well-defined and consistently
implemented. These include the features that are close to the original Verilog language or close to VHDL
or C, and include the new synthesis-aware RTL features and assertions (SVA). The class-based features
are also well-defined and well-implemented, mainly due to their prevalence in the standard verification
methodologies (VMM, OVM, and UVM).
Introduction
Introduction to SystemVerilog 1.0
e Vera
eRM RVM
SV/e SV/SC SV
URM AVM VMM
SV
OVM
Accellera
OVM 2.1.1
SV
UVM
The diagram above shows the "family tree" of functional verification methodologies related to OVM and
UVM. The headings Cadence/Mentor/Synopsys indicates the vendor from which each of the lineages
originated. The superscripts show the principle language of each methodology. The meaning of each
abbreviation is as follows:
• http://ieeexpore.ieee.org
• Search for systemverilog 1800-2012
• http://www.accellera.org
• http://www.doulos.com/knowhow/sysverilog
End
A number of SystemVerilog resources are variable, including manuals, books and web sites.
The SystemVerilog LRM is available to download, free of charge, from the IEEE.
For information on a particular tool’s SystemVerilog support, refer to the tool’s documentation, which may
include User Guides, Release Notes and Application Notes.
A number of books about SystemVerilog have been published. These cover the full range of the
application of SystemVerilog. You can find details of these on the Internet.
SystemVerilog Golden Reference Guide; 410 pages, Doulos Ltd., ISBN 0-9547345-3-X
SystemVerilog Assertions Golden Reference Guide; 102 pages, Doulos Ltd., ISBN 0-9547345-4-8
Features
Programming Language
Programming Language Features
Aim
Topics covered
if (a[i]) continue;
++ --
if (b[i]) break;
end break, continue
f = count;
end
SystemVerilog permits local declarations inside blocks (begin-end or fork-join) without any need to
name those blocks. It also permits local declarations within the first line of a for loop, as shown above.
SystemVerilog supports proper variable initialization. That is, the initial value given to the variable will be
the value of the variable the first time it is read: unlike Verilog, SystemVerilog does not rely on implicit
initial statements to initialize variables.
SystemVerilog adds several so-called 2-state data types such as int, called 2-state because each
individual bit is either 0 or 1 (no Xs or Zs). Type int is 32 bits and signed.
SystemVerilog adds ++, == and all the assignment operators from C. Assignment operators modify the
value of the variable on the left-hand-side.
SystemVerilog adds the keywords break and continue from C. break jumps out of the enclosing loop,
continue jumps to the next iteration of the enclosing loop.
Features
Programming Language
Static vs Automatic Variables
always_comb
begin
static int count = 8; Initialized once at compile time
...
always_comb
begin
automatic int count = 8; Initialized each time around
...
always_comb
begin
int count; static, but no initialization
count = 8;
Variables that are declared locally and initialized inline may be declared as static or automatic. static
variables are only initialized once and retain their values on exit from the block. automatic variables are
re-initialized each time the block is entered.
It is an error to omit the keyword static in contexts in which the variable could be either static or
automatic; some tools would issue a warning and others an error if the static keyword were omitted
before a local static variable initialization. Hence it is better to either include the static/automatic keyword
or use a separate variable assignment rather than an inline initialization.
By default, any local variables declared within a task or function are static, and hence retain their value
between task calls unless they are explicitly declared as automatic. On the other hand, if a task or
function is declared to be automatic, then any local variables are automatic unless explicitly declared as
static.
Features
Programming Language
++, --, and Assignment Operators
• Increment and decrement operators
i++; i = i + 1;
• Assignment operators
if ( a = b ) Error
SystemVerilog provides a collection of interesting new operators, broadly bringing its set of operators into
line with the C language. In particular, “assignment operators” such as += and the C increment and
decrement operators have been added to the language.
Blocking assignment operators may be used in expressions, provided they are enclosed in parenthesis.
This is to prevent the common mistake of writing
if (a=b)
Instead of
if (a==b)
Labeling
• Verilog named block • Block need not be named
task T;
endtask: T
In Verilog begin-end and fork-join blocks could be labelled. Labelling was required if variables were
declared in a block.
In SystemVerilog the label may be repeated at the end, as shown. This is useful for documenting the
code. The label at the end must be the same as the one at the beginning.
Modules, tasks and functions may also have their names repeated at the end.
In SystemVerilog, the block name may appear before the keyword begin or fork. But you cannot have a
name both before and after the begin or fork. In fact, any procedural statement may be labelled in
SystemVerilog. This is especially useful for loops, because they can then be disabled.
Despite enhancing named blocks in this way, one reason for using them is removed: in SystemVerilog
variables may be declared in unnamed blocks!
Features
Programming Language
Time Units
• Verilog `timescale directive gives compilation order dependencies
• Replace with timeunit and timeprecision
module M ( ... );
timeunit 1ns;
Must appear before any declarations
timeprecision 1ps;
Time literal
always #10 ...
Means 10ns
initial #10ns ...
endmodule : M Time literal
In Verilog, the time unit and precision could be specified using the `timescale directive. As is the case with
all the compiler directives, this may create dependencies in the order in which files are compiled.
SystemVerilog allows you to include a timeunits declaration, which consists of a timeunit, a timeprecision,
or both as the first statement(s) in a module (before any declarations, except ports in an ANSI-style
module). The timeunits declaration is applicable only to the module in which it occurs. It is also applicable
to nested modules.
Time literal
SystemVerilog also introduces time literals, which are values of the form number unit. Time literals may
be used wherever delay values can be used in Verilog, for example in procedural assignments.
Do While Loop
initial
begin
static int count = 1;
do
$display("count = ", count++);
while (count < 10);
end
initial
do
begin
static int count = 1;
#1ns $display("count = ", count);
count += 1;
end
while ($time < 10ns);
As we saw earlier, loop constructs and subprograms are enhanced by addition of the break, continue and
return keywords. There is also a new do-while loop, matching the same construct in C. Note that if you
want more than one statement inside a do-while, the statements need to be enclosed within begin...end
(or fork...join).
Features
Programming Language
Immediate Assertions
... if it is true
(nothing)
... If not
Tool-dependent format
# ** Error: Assertion error
# Time 15ns Scope: Top.Design File: design.sv …
SystemVerilog adds an assert statement that can be called from within any procedure, similar to the
assert in VHDL or C.
If the assertion expression is true, then no action is taken: simulation proceeds normally.
However, if the expression is false then the simulator writes out an error message. The exact format of
the message is tool-dependent, but will include the simulation time when the error occurred, the
hierarchical scope that contains the assertion, and the name of the source file and the number of the line
number containing the assertion.
Immediate Assertions
Optional label
The assert can be followed by a so-called action block, that is, a procedural statement that is executed if
the assertion holds each time it is tested, and optionally an else part that is executed only if the assertion
fails. The label in front of the assert is optional, but can be helpful in determining the cause of the
assertion violation.
There are four system tasks, $info, $warning, $error, and $fatal, that print out a messages with an
associated severity level. These four system tasks can only be called in the action block of an assert
statement.
Features
Programming Language
Enhanced Tasks and Functions
Tasks and functions can be declared using ANSI-style argument lists using syntax similar to the ANSI-
style parameter and port lists of modules.
Tasks and function can contain multiple sequential statements without any need to enclose them within a
begin-end block.
Tasks and functions can return control by executing a return statement. In the case of a function, the
return statement can be used to return a value from the function, just like C.
The type returned from a function may be any SystemVerilog type, including a user-defined type
(described later).
• Can pass arguments by name and omit arguments that have default values
initial
T(.b(1), .c(C), .d(D));
Task and function ports have a default direction of input and a default type of logic. Once a direction is
given, subsequent ports default to the same direction.
Named argument mapping can be used when calling tasks and functions. The abbreviated syntax used
when connecting ports is not allowed here.
Features
Programming Language
Void Functions
function int add(int i, j);
return i + j;
endfunction
begin
add(2, 2); Illegal!
print; Void function called as a statement
void'( add(2, 2) ); Type cast to discard return value
end
A void function is a function that does not return a value. A void function is very similar to a task, the
difference being that a void function shares the same restriction as all other functions, namely, a function
is not allowed to consume any time. Calls to void functions are procedural statements, just like calls to
tasks.
The void keyword can be used instead of a type name in certain other contexts too, for example in a type
cast. A void cast in effect throws away the value returned from a function, allowing that function to be
called as a procedural statement as if it were a void function.
The syntax for a type cast in SystemVerilog is a type name, then the single apostrophe, then an
expression in parenthesis.
Functions have also been enhanced in SystemVerilog. Functions may have input, output, inout and ref
arguments (Verilog functions may only have inputs.) As with tasks, function arguments have a default
direction and type of input logic and inherit the direction of previous arguments in the list. Arrays may be
passed as arguments.
The return statement may be used to return from the function before reaching the end.
Features
Programming Language
Type string
int n;
a = b; Assignment
if (b == "Hello") Comparison
In Verilog, you can store string values in arrays. Eight bits are required for each character. Verilog arrays
have fixed sizes, so when you create an array in which string values will be stored, you need to know in
advance how big the strings will be. You use the %s format specifier to display string values.
SystemVerilog introduces a new string data type. A string variable resizes dynamically according to the
length of the string that is assigned to it and you can write string values without using %s.
The string type has many useful operators and methods, including comparison (==, !=, <, <=, >, >=),
concatenation and replication.
Strings can be indexed (like an array), which returns a single byte. Strings are always numbered from 0,
with str[0] being the first character of the string, str.
Methods are functions or tasks that are called by appending a full stop and the method name to a string
variable. Some methods have arguments; these are supplied in parentheses after the method name, like
a task or function call. Parentheses are not needed if there are no arguments.
The slide shows some examples of string methods. There are many more; they are, of course, all
documented in the SystemVerilog LRM.
int i;
logic [7:0] v;
string txt;
$sformat(txt, "i = %0d v = %b", i, v);
$sformat is a member of the $display family, that is, it is concerned with formatting text strings. Whereas
$display prints its output to standard output, $sformat puts its output into its first argument, which must be
a string. There is also a function-form, $sformatf, that returns the formatted string as the value of the
function. This latter from is particularly useful.
Topics covered
• 4-state types
Signed Unsigned Width
logic signed logic 1 bit
logic signed [n:m] logic [n:m] N bits
As well as logic, the new name for reg, SystemVerilog introduces several new so-called 2-state data
types, which can be used to declare scalars or vectors. Each bit of 2-state type is either 0 or 1 – it cannot
be X or Z. The initial value of a 2-state variable is 0.
Many of these types were “borrowed” from C. This makes it easier to interface to existing C functions –
using the DPI, for example – or to translate algorithms written in C into SystemVerilog. The number of bits
used to represent byte, shortint, int and longint data is well defined in SystemVerilog (8, 16, 32 and 64 bits
respectively).
Note the default signing rules for the new 2-valued vector types.
Initial Values
3
2-state 0
4-state 1'bX
real 0.0
string ""
Class handle null
Every variable and wire as an initial value defined by the language. This initial value is 1'bx (unknown) for
a 4-state type, is 0 for a 2-state type, and is the nearest analogy to 0 for other types as shown by the table
above.
3 Examples
Basic Data Types
reg [7:0] r;
logic [7:0] v;
Equivalent
v = u; 8'hFF
logic [31:0] t;
integer unsigned j; u = v; 255
Equivalent
bit [15:0] b;
shortint unsigned u;
Equivalent
SystemVerilog does not introduce any new 4-state variable types. The new name logic is a synonym for
reg – the two can be used interchangeably (with some minor exceptions). logic is a better name than reg
for this variable type, because a hardware register (flip-flop) is not necessarily inferred by it. As we shall
see later, the rules for assigning variables have been relaxed, so that for may designs logic can replace
both reg and wire.
SystemVerilog is not strongly typed where integers are concerned. You can freely assign values between
the different 2- and 4-state types without the need for type conversions or explicit casts. The usual Verilog
truncation and extension rules for assigning integers with different widths apply.
You could use a linting tool to impose strong typing by requiring explicit casting.
If a 4-state value that contains Xs or Zs is assigned to a 2-state variable, the Xs and Zs become 0s.
Some aspects of Verilog-2001 signed arithmetic are a little surprising at first glance, and provide
opportunities for hard-to-trace errors.
As we have already mentioned, arithmetic and copying is always performed using unsigned arithmetic if
one or more of the operands is unsigned. Therefore, it's essential to be aware of how the signed or
unsigned property of an operand is determined.
Part selects
A part select of a vector is always unsigned, even if the original vector was signed.
Signed literals
Based literals such as 'd1, 4'b1001 etc. are treated as unsigned values, whereas implicit decimal literals
such as 1 are signed, as are explicitly signed literals such as 8'sb1001
Conversions
The built-in pseudo-functions $signed() and $unsigned() allow you to convert unsigned expressions or
operands into signed values with the same bit-pattern, and vice versa. For example, the expression
3'b111 is positive, but the expression $signed(3'b111) is negative.
3 Enumerations
Basic Data Types
Enumerations allow you to define a data type whose values have names. Such data types are
appropriate and useful for representing state values, opcodes and other such non-numeric or symbolic
data. Of course, each different name must have an underlying representation as a bit-pattern or integer.
The default is that they are type int and have values the values 0, 1, 2 etc. As we’ll see, SystemVerilog
allows you to control this aspect of the representation easily.
The named values of an enumeration type (known as enumeration constants) act like constants. You can
copy them to and from variables of the enumeration type, compare them with one another and so on.
typedef
typedef may be used to declare a user-defined type. Instead of declaring a variable of an enum type, it is
usually better to declare a user-defined enum type using typedef, and then declare a variable of the user-
defined type.
Type-Checking of Enumerations
3
Enumerations are strongly typed. You can’t copy a numeric value into a variable of enumeration type,
unless you use a type-cast as shown in the last box. According to the IEEE LRM, type-checking should
also be performed when passing values as arguments to a task or function, or when using relational
operators. However, when you use an enumeration in an expression, the value you are working with is
the literal’s integer equivalent; so, for example, it’s OK to compare an enumeration variable with an
integer; and it’s OK to use an enumeration value in an integer expression. (In the example i is an int or
similar.)
3 struct
Basic Data Types
struct {
bit b;
int i;
logic [7:0] v;
} s;
Variable
s.b = 1;
s.i = -8;
s.v = 8'hff;
Assignment pattern
SystemVerilog offers struct and union user-defined data types, modeled on the same idea in C. VHDL
users may be familiar with struct in the form of VHDL record data types.
A struct is declared as shown. Note that unlike the C language, SystemVerilog does not allow the optional
structure tag before the {.
Struct members are selected using the .name syntax, as shown. Structure literals and expressions may
be formed using braces preceded by an apostrophe ('{}) – this is called an assignment pattern.
typedef struct
3
typedef struct {
int x, y;
} point_t;
typedef struct {
point_t ctr;
int radius;
} circle_t; Type
Variable
circle_t c; Assignment pattern using keys
c = '{ ctr:'{x:2, y:3}, radius:4};
$display(c.ctr.x ,, c.ctr.y ,, c.radius);
It is often useful to declare a new type for a struct using the typedef keyword and then use that new type
to declare several variables.
The example above shows structs defined with typedefs and nested two-deep. Note that a value for the
outermost struct can be written using a nested assignment pattern. Furthermore, within an assignment
pattern the fields of the struct can be identified using the field names as keys.
3 Packed Struct
Basic Data Types
31 ... ... 2 1 0
Float32 a, b, c;
a.sign = 0;
a.exponent = 8'h80;
a.mantissa = 23'h400000;
c = 0;
c[30:0] = a[30:0]; Packed type equivalent to vector[n:0]
A packed data type has all of its constituent fields or elements packed together, without any gaps, to form
a single contiguous vector of bits. SystemVerilog supports packed structs and packed arrays. Packed
data types can be based on 2-state or 4-state elements: if any of the values in the struct are 4-state then
all the values are stored as 4-state (even if the native data type of the field would restrict its values to
being 0 and 1).
A significant feature of all packed data types is that it is possible to access the value as if it were a single
vector with its index running down to 0. Hence packed_var[0] would always be the least significant bit of
the variable packed_var.
In the original Verilog language, so-called memories were defined in an unusual way, with two separate
array subscripts. The range before the variable name represents the word size of the memory, whereas
the range after the variable name represents the address space of the memory. The need for backward
compatibility with the legacy syntax causes some peculiarities in the syntax used to define multi-
dimensional arrays in SystemVerilog.
A SystemVerilog array can have any number of packed dimensions, written before the variable name, and
any number of unpacked dimensions, written after the variable name. When it comes to writing a
subscript to access elements or slices of the array, the first unpacked dimension becomes the most
significant index and the last packed dimension becomes the least significant index, leading to a very
peculiar ordering of the subscripts relative to the definition of the dimensions. It is possible to leave
subscripts off the end of the list, subject to some restrictions, as we will see next.
Packed dimensions are guaranteed to be laid out contiguously in memory and thus can be copied to other
packed variables or sliced, but are restricted to 2-state and 4-state types. In contrast, unpacked
dimensions can only be copied to variables with identical unpacked dimensions.
When copying bits to and from packed dimensions, because they are packed, the number of array
dimensions don't need to match, and even the total number of bits copied doesn’t need to match. With
unpacked dimensions, the number of dimensions and the number of elements in each dimension must
match exactly, although subscript bounds and direction do not need to match, only the number of
elements.
The example above also shows an alternative syntax that can only be used for unpacked dimensions,
with int array[8] having the same meaning as int array[0:7], for example.
Equivalent types
A further rule relating to unpacked array copying is that the source and destination array's element types
must be equivalent. The equivalence of two types has a formal definition in the SystemVerilog LRM, but it
can be summarised as: For two packed data types (packed structs, packed arrays, simple vectors) to be
equivalent, they must have the same number of bits, have the same signedness (signed or unsigned),
and the same two-state/four-state attribute. With a very few exceptions, for unpacked types to be
equivalent, they need to be the same type.
Packages
3
Contains parameters,
module MyModule;
import pkg::*; Wildcard import
BusType bus;
assign par = Parity(bus); Only used names are imported
initial
pkg::global_variable ++; Alternative to import
...
A package is a container for shared or global declarations, but may not contain any processes, i.e. assign,
initial or always statements. Packages are declared outside modules.
import package_name::declaration_name;
import package_name::*;
makes all the names in the package candidates for import. They are not imported unless they are actually
used.
Note that wildcard port connection using .* does not cause wildcarded names to be imported, but the
shorthand port connection .name does.
package pkg;
typedef logic [7:0] BusType;
...
endpackage
Using packages to hold type declarations is a good idea, but it presents a small problem when trying to
use types from a package to specify the data type of a module's port.
In the preferred Verilog-2001 or "ANSI" port list style there is a problem: how can we declare the port’s
data type when we have not yet seen the definition?
One possible approach is to import it from a package outside the module. This works well in simple
situations, but has the significant drawback that it puts the type definition into the global or file scope. For
various reasons this is undesirable and we do not recommend it for serious use, even though it is very
convenient when working with only a small number of design files.
It is much preferable to use the package name explicitly to qualify type names in the module's port list.
Using the scope resolution operator :: allows us to specify from which package a definition should be
taken. Although this is a little more verbose than the file-scope import, it is much clearer and Doulos
strongly recommends this technique.
Interfaces
4
Aim
Interfaces
• Introduce the use of SystemVerilog
interfaces for design and modelling
Topics covered
• Interfaces
• Ports and parameters on interfaces
• Modports
• Generic interface ports
Modelling a Bus
4 • As an example, consider the AMBA Advanced Peripheral Bus (APB)
Interfaces
Many designs make use of buses. These may be proprietary or one of a number of industry-standard
buses. As an example, we are going to look at modelling the AMBA Advanced Peripheral Bus (APB), a
simple synchronous bus interface.
The write transfer starts with the address, write data, write signal and select signal all changing after the
rising edge of the clock. The first clock cycle of the transfer is called the SETUP cycle. After the following
clock edge the enable signal PENABLE is asserted, and this indicates that the ENABLE cycle is taking
place. The address, data and control signals all remain valid throughout the ENABLE cycle. The transfer
completes at the end of this cycle.
For a read transfer, the timing of the address, write, select and strobe signals are all the same as for the
write transfer. In the case of a read, the slave must provide the data during the ENABLE cycle. The data
is sampled on the rising edge of clock at the end of the ENABLE cycle.
Full details of APB can be found in the AMBA Specification document, available from ARM Ltd.
(www.arm.com).
Interfaces
PCLK
PSEL
PENABLE
APB PWRITE APB
Master PADDR Slave
PWDATA
PRDATA
An APB system has exactly one bus master, and one or more slaves. We are going to consider first the
simple case of one master and one slave.
This shows how the APB system might be modelled using an ANSI-style port list from Verilog-2001
onward.
Each signal belonging to the bus is modelled as a separate port. All of the functionality of the bus has to
modeled within one or more modules.
In the next slide we are going to replace the list of ports with a single interface port.
Interfaces
logic PCLK, PSEL, PENABLE, PWRITE;
logic [15:0] PADDR;
logic [31:0] PWDATA;
logic [31:0] PRDATA;
endinterface
At their simplest, interfaces are just packages of interconnect. They are more appropriate for this purpose
than structs, because it’s not possible to include nets as part of a struct.
Modules can have ports of an interface type, and these ports can be associated with any suitable
interface instance in just the same way that module ports of net type can be associated with any suitable
net. Note that interface ports don’t have a direction like input or output, they simply have a type, which is
the name of an interface. Interface connections are by default bi-directional, although we shall see that it
is possible to provide direction information using modports.
Interface ports are only allowed with the “ANSI” style of port declarations, where the ports are declared in
the port-list at the top of the module.
It is also necessary to instantiate the interface itself, so that the modules have something to connect to!
referenced
endpackage
interface APB;
Interface has same
logic PCLK, PSEL, PENABLE, PWRITE; scope rules as module
pkg::t_APB_a PADDR;
pkg::t_APB_d PWDATA; Interface can contain
pkg::t_APB_d PRDATA; the same items as
endinterface modules (almost)
Although there are similarities on the surface, interfaces and packages are very different kinds of
language construct. A package must be compiled before being referenced from anywhere else in the
code, and would typically define types, tasks, functions, and localparams. The compilation order is critical.
On the other hand, an interface is like a module in the sense that modules and interfaces can be compiled
in any order (the compiler will accept a module instantiation before compiling the module definition), and
you can use hierarchical references across module and interfaces instances.
Interfaces and modules can contain almost the same kinds of declarations and statements. About the only
thing that an interface cannot contain that a module can is a module instantiation (though that may
change in the future).
Instantiating an Interface
APB_Master APB_Slave
4
(module) instance (module) instance
Interfaces
APB APB
Master APB (interface) Slave
(module) (module)
APB (Interface)
instance
Interfaces are instanced, just like modules and form part of the hierarchy of the design. Having been
instanced, an interface can be connected to module ports that have the correct interface type.
Instantiating an Interface
4 • Interfaces must be instantiated, just like modules
• An interface is like a module that can be connected to a port
Interfaces
Interfaces are declared at the same level as modules and the syntax for an interface instance is identical
to that of the module instance. In fact, interfaces have a lot in common with modules and are perhaps
best understood as fancy modules that have the additional feature of being usable as the types of
interface ports. The interface’s instance name (iface_inst in the example) can be connected to the
interface port of one or more module instances: if an interface port is given a named type (it need not be),
it must be the same name as the interface itself (APB in this example).
Interfaces
t_APB_d PWDATA,
t_APB_d PRDATA
endinterface module Master (APB iport, ...);
...
always begin : ClockGenerator
iport.PCLK = 0;
#Period iport.PCLK = 1;
interface_port.member
#Period;
end : ClockGenerator
...
endmodule
Where an interface is connected to an interface port, members of the interface can be accessed from
within the module using hierarchical references through the interface port. For example, the APB clock
signal PCLK is accessible from the module Master using the name iport.PCLK, where iport is the name of
interface port on the Master module. Almost any named declared in the interface can be accessed in this
way.
module TestBus;
logic Clk = 0;
We have seen that interfaces are very similar to modules. A further similarity is that interfaces, like
modules, can have parameters and ports. The syntax for these is exactly the same as the syntax for
declaring module ports and parameters.
Parameters may be used to specify the widths of the signals in a bus, for example.
Typically, interface ports would be used for system-wide signals like clocks and resets, that are not part of
a specific bus. (The example we have been considering – the APB bus – does include a clock, PCLK.)
An interface’s ports are considered to be members of the interface, and may be accessed in the same
way as the other members (including through modports, as discussed later). The ports must be connected
when the interface is instanced.
Ports on Interfaces
4
Interfaces
Clock
Input
X
Module Interface Module
Interface ports are typically used for signals such as clocks and reset that originate outside of the modules
being interconnected by the interface. Rather than feeding these signals into a module port and from
there out through an interface port to an interface, the signal can be connected directly to the interface.
Synthesis of Interfaces
4 master_inst slave_inst
In cases where interfaces are used as a way to create a bundle of wires (or variables), interfaces can be
synthesized. This slide illustrates an interface being used in a rather straightforward way to encapsulate a
standard bus interconnect scheme. Clearly, if the connected modules are themselves synthesisable, it
would make sense for the connecting interface to be synthesisable too.
Synthesis Results
Interfaces
pins pif pins pif
RE
); );
WE
addr
always ... always @(...pif.clk)
pif.RE <= 1; wdata ... = pif.RE;
... rdata
endmodule endmodule
Input Output
module top (...);
logic clk;
pins pins_inst ( .clk );
master master_inst( .pif(pins_inst) );
slave slave_inst ( .pif(pins_inst) );
endmodule
The interface is indeed synthesisable. Current synthesis tools that support interfaces do so by
"exploding" the interconnect captured in the interface, declaring individual signals in the enclosing (top-
level) module. Naturally these signals must be renamed; typically the interface and modport names are
concatenated with underscores to form a prefix.
It is useful to try a very simple example like this on your synthesis tool, and get the tool to write out a
Verilog-95 netlist after it has performed SystemVerilog analysis (compilation) and elaboration, but before it
has begun its optimisation. In this way you can see exactly how the tool interpreted your SystemVerilog
code.
Modports
4 interface pins (input clk);
logic RE, WE;
logic [7:0] addr;
module master( module slave(
Interfaces
types_pkg::data_t wdata;
pins pif pins pif
types_pkg::data_t rdata;
); );
modport master_mp (
always ... always ...
input clk, rdata,
pif.RE <= 1; ... = pif.RE;
output RE, WE, addr, wdata);
pif.WE <= 0; ... = pif.WE;
modport slave_mp (
... ...
input clk, RE, WE, addr, wdata,
output rdata);
endinterface
An interface can define any number of modports. When a module accesses an interface through a
modport, the modport effectively acts like a filter and restricts the access the module has to the interface.
The module has to honour the input/output directions defined by the modport.
It is important to realise that modport directions are relative to the module. Although they are defined
within the interface, the directions specified in a modport are those that would otherwise appear in the port
list of the corresponding module. In other words, the modport ’s directions are seen from the client
module’s point of view, not from the interface’s point of view. This can be quite confusing at first glance,
although it’s really very sensible because the modport almost becomes part of the module that uses it.
Syntax quirk: having defined a modport, it is entirely up to you whether and where you choose to use it.
The idea is that if you use a modport, the compiler will check that you've obeyed the rules when accessing
the interface. The modport can be ignored entirely, or used only to declare the interface port, or used only
when connecting the interface port to the interface, or used in both places.
APB Modports
• A modport is like a filter that restricts access to the interface 4
• You can always strip out modports – they add nothing but checking
Interfaces
interface APB;
logic PCLK, PSEL, PENABLE, PWRITE;
t_APB_a PADDR;
t_APB_d PWDATA;
t_APB_d PRDATA;
A simple interface like the one we have been using is bi-directional – all the members of the interface are
bi-directional. Also, all the members are accessible to every module that connects to the interface. So it is
possible, for example, for a slave module to drive the APB PCLK signal. This is undesirable – we want
PCLK to be an output of the APB bus master, and an input to the APB slaves.
By adding modports we can specify various different modes of connection to an interface. In this
example, we give the APB interface two modports, representing a master and a slave connection to the
bus respectively. We can then connect the modport, not the interface, to a module instance’s port.
Modports give us a much better compromise between the flexibility of interfaces and the need to control
port direction.
Note that each modport can contain a mixture of input, output and inout connections. Therefore, we do
not specify the direction of a module port if it is going to be connected to a modport. The directions of all
connections in the modport are determined by the modport’s definition; the “client” module has no choice
in the matter.
A modport need not contain all the signals in the interface. This provides the ability to restrict access by
some clients to some members of the modport, because a module connected to a modport can only
access the interface members that are in the modport. Our APB example does not use this feature.
module Slave (
endmodule
APB.slave_mp iport);
...
Can have neither,
endmodule
either, or both
If an interface has modports, you can connect to the modports of the interface instance. There are two
ways of doing this:
Either declare the module’s port type as InterfaceName.ModportName, for example, APB.Master,
You could do both, but one of these is sufficient. If you do this, the modport names would have to match.
You need only declare a modport once within an interface, and then as many modules as you wish can
make use of it. Every time a module instance uses a modport in its connection list, a new modport “stub”
is added to the chosen interface instance.
In the APB example, PRDATA would need to be declared as a wire (or one of the other net types)
because a variable can have only one structural driver.
Interfaces
PWRITE, PADDR, PWDATA, input PRDATA);
endinterface
Connect to any interface that has a master_mp
When creating an interface port, instead of explicitly naming the type of the interface it is possible to
substitute the keyword interface to create a generic interface port. A generic interface port can be
connected to an instance of any kind of interface in the port connection list. When a module attempts to
access the contents of an interface through a generic interface port, the validity of the names being
accessed will be checked at that point.
4
Interfaces
RTL Processes
Aim
RTL Processes
Topics covered
Register transfer
RTL Processes
Combinational
Logic
Registers
Just in case you are new to RTL synthesis, let's quickly recap the essentials. Designing hardware with
SystemVerilog is not about knowing the syntax, but requires that you understanding what RTL synthesis
is all about. An RTL description partitions the digital circuit into registers and the combinational transfer
function between those registers. This structure could be instantiated, though it is more usual to infer the
registers and logic from procedural code. In any particular block there may be some purely combinational
signal paths from input to output, or paths might pass through one register, or two registers, or any
number of registers. In any case, understanding the structure of the hardware block you are designing in
terms of registers and combinational logic is absolutely key to using RTL synthesis.
Combinational Logic
ModuleName Instance(...);
5 Module instantiation
RTL Processes
begin
... Complete assignment
Output = ...;
end No feedback
There are three ways to describe combinational logic in SystemVerilog (just like Verilog or VHDL),
namely: module instantiation, continuous assignments, and combinational always blocks. Note the three
golden rules that we use to teach proper RTL coding style apply here: complete sensitivity, complete
assignment, and no feedback.
• Complete assignment means that every single variable that's assigned within the always must be
assigned in every possible execution path, otherwise you would be inferring transparent latches.
SystemVerilog adds new keywords (unique, priority) and semantics to help with this.
• No feedback means no feedback. This is important. Many synthesis pitfalls are associated with
incomplete sensitivity or incomplete assignment.
Clocked Processes
RTL Processes
Output <= function_of_inputs_and_state;
Nonblocking assignments
Reset
Registers are inferred from clocked processes, which have there own coding rules:
• The sensitivity list (also known as the event control in SystemVerilog) must contain the clock, asynchronous
control signals and nothing else.
• If there are any asynchronous control signals, there must be an if statement to test for their active values and
execute the corresponding asynchronous actions, with the final else branch executed only if all the control
signals are inactive.
• Any assignments to variables that are used outside the process must be nonblocking assignments (to avoid
simulation races), and any assignments to variables that represent flip-flops should be nonblocking
assignments (for good style). Pretty much every coding standard ever written says "Use nonblocking
assignments for flip-flops".
Incomplete assignments are still an issue (e.g. the case where Reset and Enable are both 0 in this
example) but now the values will be stored in a flip-flop between clock cycles anyway, so the incomplete
assignment results in innocent feedback through the flip-flop (or maybe an enable signal on the flip-flop)
rather than an unwanted transparent latch.
5
Unlike always @*, sensitive to embedded functions
...
Variables shall not be assigned by more than one process
RTL Processes
SystemVerilog gives you three special forms of always block dedicated to synthesis. always_comb is for
combinational logic. It is like always @* except that always_comb is also sensitive to any variables read
indirectly by functions called from the always block, and so guarantees complete sensitivity. Synthesis
tools might be expected to check that the logic really is combinational, in other words to check for
complete assignment as well as complete sensitivity. always_latch has exactly the same rules as always
comb, but the intent is different. always_ff is for clocked always blocks. always_ff must have exactly one
event control, which must appear immediately after the always keyword. Synthesis tools might be
expected to check that the coding rules for synthesizing clocked logic had been followed. All three share a
common rule that you can only assign a variable in one process (so you don't get the outputs of regular
logic gates wired together).
The iff variant is provided for the concise expression of clock enables by gating the event expression with
a boolean condition. However, you need to be aware that not all synthesis tools support the iff keyword in
this context.
Synthesis-Friendly If / Case
always_comb 5
priority if (a == b)
a
RTL Processes
f = 1;
b
else if (a == c) f
c
f = 2;
d
else if (a == d)
f = 3;
endmodule
SystemVerilog has two new keywords priority and unique to direct the synthesis of if and case
statements. priority and unique can be used in combinational or clocked always blocks, but we'll
illustrate using always_comb. priority and unique help avoid pitfalls of incomplete assignment and when
directing synthesis to make assumptions about the exclusivity of conditions.
priority indicates explicitly to anyone reading the code that the conditions in the if statement are
evaluated in sequence from top to bottom. Of course, the if statement has this behavior anyway! The
point of priority is to make this explicit. The one change that priority does makes to the semantics of the if
statement is that at least one condition should match - priority implies that the input conditions are
completely decoded. You get a run-time warning if none of the conditions are true and there is no final
else part to the if statement.
priority case
b: f = 1;
b
c: f = 2; f
c
d: f = 3;
d
endcase
endmodule
The priority keyword can also appear in front of a case statement, with exactly the same rules as a
priority if. The expression at the top is compared with each of the statement labels one-by-one until a
match is found, and the labels are allowed to be variables. Again, the priority keyword doesn't change
the meaning of the case statement except to produce a run-time warning if the expression is unmatched,
but it does create a clear expectation that we are synthesizing priority logic. Just to be clear, 'a' should
equal b, c, d, or possibly more than one of these. If it doesn't you get a warning from simulation. You
expect to get priority decoding logic synthesized.
unique if
always_comb
5
unique if (state[0])
RTL Processes
f = 0;
else if (state[1])
f = 1;
else if (state[2])
f = 2;
else if (state[3])
f = 3;
endmodule
The alternative to priority is unique. unique enforces complete assignment (like priority) but also
requires that the branches of the if statement are mutually exclusive. In other words, it means that is this
example the synthesis tool can assume that only one bit of the state vector will be hot (else a warning
during simulation). In other words, it means the conditions of a unique if can always be decoded in
parallel, so the synthesis tool can minimize the logic because it doesn't have to generate priority logic to
arbitrate between conflicting conditions.
unique case
state[0]: f = 0;
state[1]: f = 1;
state[2]: f = 2;
state[3]: f = 3;
endcase
endmodule
And finally, unique case. Again, the keyword unique allows the synthesis tool to assume that exactly one
bit of the 4-bit state vector is hot, and so to minimize the logic. If there are no matches or more than one
match you will get a warning from simulation.
RTL Processes
f = 3;
else if (a ==? 4'b01xx)
f = 2;
else if (a ==? 4'b001x)
f = 1;
else if (a == 4'b0001)
f = 0;
endmodule
Systemverilog includes new wild equality operators. Wild equality allows the kind of pattern matching you
can do with a Verilog casex to be done using an if statement instead: x and z are wild cards. We can now
use if statements and case statements interchangeably in many contexts. The wild equality operator is
much cleaner than the casex, because it is only an x on the right-hand-side that counts as a wild card; an
x on the left-hand-side represents itself. You can see in this example that always_comb and unique are
used to state explicitly that we are describing combinational logic using an if statement in which one and
only one condition matches each time around.
Both x and z count as don't care values, but only on the right hand side of the operator: if x or z should
appear in the left hand operand, they count as themselves. After accounting for don't cares on the right
hand side, if two vectors differ in any bit position then ==? returns false (and !=? returns true). As is the
case with the logical equality operators (== and !=) it is still possible for the wild equality operators to
return an unknown result; this may happen if the left-hand operand includes x or z values.
case inside
always_comb
RTL Processes
4'b001?: f = 1;
4'b0001: f = 0;
endcase
The case inside statement is, in effect, a replacement for Verilog's casex and casez. Unlike the
traditional casex and casez, case inside uses the same asymmetric wildcard matching as the ==?
operator. That is, it is only x's and z's in the case statement branches that count as wildcards, not x's and
z's in the expression at the top of the case statement.
Note that although case inside has been part of the SystemVerilog standard since 2005, it is still not
supported by all synthesis tools.
inside Operator
RTL Processes
begin
{f, g} = 0;
if (a inside { 4'b0??1 } )
f = 1;
g = 1;
end
Same asymmetric wildcard matching
endmodule
End
inside can also be used as a standalone operator, independent of any case inside statement. It uses the
same asymmetric wildcard matching as case inside and ==?. Note that when inside is used as a
standalone operator, the curly braces {} are a necessary part of the syntax.
We will also see the inside operator used to define randomization constraints.
5
RTL Processes
RTL Types
Aim
RTL Types
• Synthesizable data types
• Enums
• Packed structs, unions, and arrays
• Packages, ports and parameters
• Synthesis of interfaces
• signed, unsigned
• typedef to any of the above
• Array of any of the above
At the time of writing, many synthesis tools have at least some support for SystemVerilog, and some have
excellent support. For details for a particular tool, please refer to that tool’s documentation. The slide
shows which data types are likely to be supported for synthesis.
state_t state;
RTL Types
WE <= 0; Non-blocking assignments
RE <= 0;
end
else
begin
case (state)
IDLE: begin
WE <= 0;
if (enable) state <= REQ;
end
REQ: begin
Assigning state some other value would be an error
We can use enums to define the state variable for an FSM. In Verilog you would have to define a set of
parameters, one for each state. In SystemVerilog you can use an enum type to give a name and value to
each state.
Each enum is associated with a base type, which must be one of SystemVerilog’s integral types. The
default base type is int and the enumeration values are numbered counting up from 0. So in the example
shown here, state_t is equivalent to a 32-bit 2-state integer with the enumeration values IDLE through
WRITE0 having the numerical values from 0 through to 5, respectively.
Using an enumeration type in this way helps to catch errors, because assigning the variable state any
value other than the six valid enum values would be caught as an error by the compiler.
Base type
state_t;
You can explicitly define the bit pattern used to represent each state within the enum. This is part of the
SystemVerilog language rather than being some kind of comment or synthesis directive. Also, you can
specify the 'base type' of the enum – logic [5:0] in this case. Since the base type is logic, the default
value of the state variable will be 6'bx, which is neat when describing hardware. (Compare this to VHDL
where the variable always gets initialized to one of the values of the enumeration type.)
0 1 2 7 8 9 6 5
enum { aa, bb, cc, dd = 7, ee, ff, gg = 6, hh = 5 } variable;
RTL Types
Initial value = 0
3 4 5
enum { jj = 3, kk, mm } ano_variable;
Each enumeration value is unambiguously associated with a numeric value. By default the enumeration
values count up from 0 but they can be set explicitly to any value, in any order, providing there are no
duplicates and the values are within the range of the base type.
The default initial value of a variable of an enum type is the default value of the underlying type, not the
first value of the enum type. This means that a variable of an enum type may have an initial value that is
not a legal value for the type. You should be careful to initialise such variables explicitly.
There is a particular potential problem if you are using enums for synthesis. The problem is that synthesis
tools may not follow the simulation semantics. It is best to play safe and not make any assumptions in
your code about what values the synthesis tool may use.
The values must be appropriate for the type. If a value includes a bit width, it must be exactly correct for
the type. If a type is not specified explicitly, it may be specified implicitly by using a sized value – again all
the sized constants in a enumeration must have the same size (unsized value can be used too).
If an enumeration has a 4-state type (i.e. reg, logic or integer), the enumeration constants may include Xs
and Zs. If so, the following constant’s value must be specified explicitly.
struct packed {
4-state packed struct
logic [3:0] x;
int y; x y
6 bit
} vec;
flag;
4 32 1
RTL Types
As a review of material presented in an earlier section, here is an example of using a packed struct.
Remember that a packed struct can be treated as if it were a C struct, i.e. individual fields can be
accessed using ".". Or, you can write a value for a complete struct using an assignment pattern (like an
aggregate in VHDL). At the same time, because the struct is packed, the whole thing can be treated as if
it were a single one-dimensional vector. Hence the assignment vec = 7 is valid in this example, as are the
part-selects on variable vec.
Packed Union
RTL Types
bit [14:8] opcode;
bit [ 7:4] reg1; i.oneAddr.flag = 2'b01;
bit [ 3:0] reg0; i.oneAddr.opcode = 10'h3ff;
} twoAddr_t; i.oneAddr.reg0 = 4'h1;
i.twoAddr.flag = 1'b1;
i.twoAddr.opcode = 7'h7f;
typedef union packed { i.twoAddr.reg1 = 4'h2;
oneAddr_t oneAddr; i.twoAddr.reg0 = 4'h1;
twoAddr_t twoAddr;
} instruction_t; i[7:0] = 0;
SystemVerilog takes this further with packed unions. You may remember unions from C, which 30 years
ago could be used to save memory by re-using it for multiple purposes. SystemVerilog packed unions are
useful in hardware for interpreting the contents of a vector in different ways. In this example we have two
packed structs defining two different instruction formats for the same 16 bit register, and a packed union
that combines these two structs into one. Then when you access the variable i you can pick which
interpretation you want by writing i.oneAddr or i.twoAddr then picking a field from the corresponding
struct. Because the datatypes are packed, you can always fall back to treating the variable like a regular
SystemVerilog vector, i[7:0], ignoring the fields.
Multidimensional Arrays
Versus
logic [7:0][7:0][15:0] arr;
...
arr[row][col] = 16'hffff;
arr[row][col][bitn] = 1'b0;
Packed types are in general preferable to unpacked types for synthesis, because packed types are
always synthesizable in a very straightforward way. It is not just packed structs and packed unions that
can be useful. Packed multidimensional arrays can be useful, particularly to avoid making errors in the
index arithmetic when describing structures.
Compare the upper and lower examples on this slide. Declaring rows and columns as separate array
dimensions is far more intuitive than having one long vector.
RTL Types
vec = 'X; 8'bxxxxxxxx
Streaming operators
i = { >> { 32'hdeadbeef }}; No-op
A bit width cast changes the number of bits in a vector, truncating or extending the vector. The vector will
be truncated or extended anyway in most contexts, but using the bit width cast makes your intent more
explicit and may avoid warnings from synthesis or linting tools.
An unsized value is a single bit value that gets sized according to the context in which it is used, with
every bit being set to the given bit. The value can be '0 '1 'x 'X 'z or 'Z.
The streaming operators may be used to reverse the order of a stream of bits, with the further useful
feature that you can specify the chunk size that is used during the reversal. By default, << simply reverses
at the bit level. These operators may also be used to create packed values from unpacked values.
6
wire struct {logic [7:0] x, y; } coord;
RTL Types
Nets/wires in SystemVerilog can have any 4-state type, including enums and user-defined types, but a net
cannot have a 2-state type (e.g. bit, int).
Ports that happen to be variables can have any data type, because they are variables. Ports that happen
to be nets must have 4-state types.
Although the code examples above are legal according to the SystemVerilog standard, you should
beware tool support issues. Not all tools support SystemVerilog-style types for ports and nets, and the
advantages of using them are not that great anyway.
RTL Types
types_pkg::data_t memory [(2 ** asize)-1:0];
Here is an example showing a type being imported from a package into a module that describes a
memory. The type of the data busses is declared in the package. The memory module is parameterized
with the width asize of the address bus. The memory variable uses the user-defined data_t from the
package, prefixed using the scope resolution operator ::. The memory array has one packed dimension
an one unpacked dimension.
Given this code, synthesis tools may generate a lot of flip-flops and multiplexing, or may actually infer a
RAM. As it happens, both the Altera and Xilinx synthesis tools infer a RAM from this particular code.
Type Parameters
This module works on any data type
module ShiftReg
#(parameter depth = 2, parameter type T = logic)
(input clk, input T data, output T Q);
6 T SR[1:depth];
Ports and variables of parameterised type
SystemVerilog significantly extends the Verilog parameter system by allowing modules to have type
parameters, as shown on the slide. This facility allows us to do many of the things that C++ does using
type templates.
In the example, the internal delay line has depth elements each of type T. The code in the module does
not manipulate the objects of type T in any way, but merely copies them from place to place; it would
therefore work correctly regardless of the type used for T. Our module can thus be parameterised for the
type of data it processes, making it easier to re-use.
A default type must be supplied, in the same way that ordinary parameters must have a default value.
The named parameter association in the module instance makes it easy to see how the parameters are
set.
master_inst slave_inst
always ...
pif.RE <= 1;
logic [7:0] addr;
types_pkg::data_t wdata;
types_pkg::data_t rdata;
always @(...pif.clk)
... = pif.RE;
6
endinterface
...
RTL Types
endmodule endmodule
In cases where interfaces are used as a way to create a bundle of wires (or variables), interfaces can be
synthesized. This slide illustrates an interface being used in a rather straightforward way to encapsulate a
standard bus interconnect scheme. Clearly, if the connected modules are themselves synthesisable, it
would make sense for the connecting interface to be synthesisable too.
6 always ...
pif.RE <= 1;
addr
wdata
always @(...pif.clk)
... = pif.RE;
... rdata
RTL Types
endmodule endmodule
Input Output
module top (...);
logic clk;
pins pins_inst ( .clk );
master master_inst( .pif(pins_inst) );
slave slave_inst ( .pif(pins_inst) );
endmodule
The interface is indeed synthesisable. Current synthesis tools that support interfaces do so by
"exploding" the interconnect captured in the interface, declaring individual signals in the enclosing (top-
level) module. Naturally these signals must be renamed; typically the interface and modport names are
concatenated with underscores to form a prefix.
It is useful to try a very simple example like this on your synthesis tool, and get the tool to write out a
standard Verilog netlist after it has performed SystemVerilog analysis (compilation) and elaboration, but
before it has begun its optimisation. In this way you can see exactly how the tool interpreted your
SystemVerilog code.
PCLK,
APB APB
PSEL etc.
Master
(module) PRDATA
Slave
(module) 6
RTL Types
Tristate bus? Multiplexer? Wire-OR?
There is a significant problem associated with the use of interfaces to model a typical modern on-chip
bus. Such buses usually have a "bus fabric" or other switching scheme to determine which of several
possible sources drives a given signal. Even in the very simple bus, such as APB, the PRDATA signal
can be driven by any one of the slaves. And yet when multiple module instances are connected to an
interface instance through an interface port, each of those module instances will access precisely the
same variables in the interface. In the diagram above, what you want is for each slave module instance to
access different bits of the bus structures in the interface, for example, different bits of the PSEL vector.
There is a straightforward approach to modelling this, which involves coding the bus fabric as a regular
module and using an interface only for the point-to-point connections between bus fabric and attached
masters and slaves, which has the big advantage that it synthesises correctly today in all tools that handle
SystemVerilog interfaces. However, this solution is clumsy if you need to parameterise the number of
masters or slaves. On the next few slides we present a more flexible solution based on the use of
modport expressions.
6 module master(
pins.mas pif
input clk, rdata,
output RE, WE, addr, wdatav); module slave(
pins.sla pif
); modport sla ( );
input clk, RE,WE, addr, rdatav,
RTL Types
output rdata);
endinterface
The issue is that we typically want different instances of a given component (e.g. master or slave) to
access different connections. In this example, wdatav and rdatav have become arrays-of-vectors,
because each master has its own wdata, and each slave has its own rdata. Each master needs to write
to a different element of the wdatav array. But as things stand each master is connected to the interface
in the same way, so there is no nice way to have each master instance access different elements of the
arrays.
Modport Expressions
RTL Types
...
wdata <= ...
endinterface
There is solution, and that is to use 'modport expressions'. A modport expression is effectively an alias for
modport. The master module uses the wdata modport expression. Each master accesses a different
element of the wdatav array by accessing the interface through a different modport. The modports are
selected when the interface port is connected (don't want to fix the choice of modport when declaring the
interface port).
That's fine, but it does mean declaring a whole list of differently-named modports, which could become a
nuisance, especially if the number of connections were parameterized. There is an even better way,
shown on the next slide.
6 module master(
pins pif
modport mas ( ...
end
output .wdata(wdatav[i]) ); module slave(
pins pif
); );
always ... ...
RTL Types
You can use a generate statement to create the modports. (The keyword generate is optional in
SystemVerilog and in Verilog-2005 and is not used here.) There is now only a single modport declared
inside a generate, where the genvar selects which element of wdatav is used in the modport
expression. We end up with a bunch of modports each within a different named block that we can access
by hierarchical name from outside. You can see that at the bottom where we connect the interface port
(pif) by naming a particular modport.
Clocking Blocks
Aim
Topics covered
• Clocking Blocks
7
• Input and output skew
Clocking Blocks
• Clocking drives and synchronization
• #1step sampling
• Signal aliasing
• Clocking blocks versus programs
Basic Testbench
DUT
Stimulus Stim Resp Checking
generation input output
clk
Clock events Clock events
7 Basic Testbench
A standard Verilog testbench is usually a module that contains an instance of the design under test (DUT)
and procedures (initial or always) to provide test stimulus and check the design’s response. The
testbench has to provide test stimulus and sample the response at appropriate times with respect to the
system clock. This is relatively straightforward to code, but is clumsy, because the timing strategy must be
applied explicitly for each drive and sample. Consequently, control of timing is scattered throughout the
testbench and is hard to modify.
Clocking Blocks
• Adds gate-level delays to inputs and samples output
• Isolates verification environment from SystemVerilog DUT/RTL scheduling
Clocking Blocks
Clocking blocks
Hardware verification languages (HVLs) such as e and Vera have no knowledge of simulation time, but
can receive events from the simulation and these events can be used to trigger a burst of activity in the
HVL environment. To implement this, HVL vendors provide some mechanism to link Verilog signals to
variables in the HVL world. These bridge blocks – ports in the e language, interfaces in Vera – are a
convenient place to encapsulate the detailed timing of driving and sampling.
clocking
Although SystemVerilog is a unified language, we nevertheless need a similar way to separate the
verification code from the design under test. SystemVerilog achieves this using a clocking block, a
specialised SystemVerilog construct that provides centralised control of the timing of a group of signals all
related to the same clock.
Test Harness
module Harness;
Design DUT (...);
// Clock generator ...
// Clocking blocks
7 module Testbench;
// Doesn’t instance DUT
endmodule
// or Harness
Clocking Blocks
endmodule
Top-level modules
Harness.name...
$root.Harness.name...
The test harness is a module, which contains an instance of the design, the clocking block(s) and a clock
generator. The verification environment is also a module, although it could be a program (programs are
discussed later.) Both the test harness and the verification environment module could be at the top-level
($root), communicating using hierarchical names.
Top-Level Module
module Harness;
Design DUT (...);
// Clock generator ...?
// Clocking blocks
module Testbench;
// Doesn’t instance DUT
endmodule 7
// or Harness
Clocking Blocks
module Top;
endmodule
Testbench TB_inst ();
Top.Harness_inst.name...
Harness Harness_inst ();
$root.Top.Harness_inst.name...
// Clock generator ...?
endmodule
Top-Level Module
Alternatively, the test environment and the test harness could themselves be instanced in a single top-
level testbench module. If this were the case, it would also be possible to include the clock generator in
this top-level module.
Whether or not you have this top-level module is a matter of personal preference. It doesn’t affect the
overall structure of the test environment.
To_DUT From_DUT
clocking my_cb @(posedge clk);
3ns t t 2ns input #2ns From_DUT;
output #3ns To_DUT;
7 Q
D Q
D
endclocking
clockvar name =
my_cb.variable/net name
Clocking Blocks
clockvars
SystemVerilog’s clocking construct addresses the issue of driving and sampling a design’s inputs and
outputs in a testbench.
Test inputs are driven with a specified output skew delay after the clock. Test outputs are sampled with a
specified input skew before the clock. (Note that the input/output directions are relative to the testbench,
not the design.)
Here is a declaration of a clocking block. This diagram shows when driving and sampling occurs.
Unlike in modules, where you can “break the rules” and assign values to inputs and read the values of
outputs, clocking blocks are stricter – clocking inputs may be sampled, but must not be driven; clocking
outputs may be driven but must not be sampled. If you want to both drive and sample a signal, you would
declare it inout and/or provide input and output skew values.
clk
2ns 3ns
7
From_DUT
Clocking Blocks
my_cb.From_DUT
clockvars
my_cb.To_DUT
To_DUT
Clocking inputs (design outputs) are sampled before the clock – the input skew value (2ns in the example)
specifies how far before. Sampling occurs in the Postponed region – at the very end of the time step,
when $strobe and $monitor are executed.
Clocking outputs (design inputs) are driven after the clock – the output skew value (3ns in the example)
specifies how long after. Clocking drives are scheduled in the NBA region of the appropriate future
timeslot.
Both the variables connected to the DUT and the clockvars will be available in the simulator, so you can
add them to a waveform display, for example.
clk
t t
output #3 To_DUT;
7 endclocking
Typically, a SystemVerilog testbench will include an instance of the device under test (DUT), a clocking
block, a stimulus generator and a response checker. The clocking block acts as an interface between the
DUT and the stimulus/response.
In the example shown, a clocking block called my_cb is declared. This is clocked on the rising edge of
the clock. The input (sampling) skew is #2 and the output (driving) skew is #3. The clocking declaration
also acts as an instance of the clocking block.
clockvar
By prefixing the names To_DUT and From_DUT with the name of the clocking block (my_cb), we have
access to the clockvars defined by the clocking block. When a testbench writes to an output clockvar, or
reads from an input clockvar, the timing defined by the clocking block will be used.
Your testbench can also “go round the back” of the clocking block and access the signals (such as
To_DUT and From_DUT) directly. However, this is strongly discouraged – the clocking block is there to
provide managed access to those signals, with timing and direction appropriately controlled, and it is in
most cases quite inappropriate to subvert that mechanism. Clocking blocks can usefully be added to
interfaces, as we will see shortly, and modports can be used to help enforce access through the clocking
block.
This example shows the clocking block inside a module. Clocking blocks may also be used inside
interfaces.
module Tester;
initial begin synchronize clocking drive 7
@(test_harness.my_cb)
test_harness.my_cb.To_DUT <= 0;
Clocking Blocks
...
This is how a testbench using a clocking block could be structured. Later we will see some important
guidelines about reliable, race-free sampling and driving of signals through a clocking block. At this
stage, though, we merely note that the testbench takes care to reference DUT signals only by means of
the clocking block; it accesses clockvars, and never touches the signals directly.
The test module Tester contains initial blocks to drive the stimulus and check the response. The top-level
test harness module test_harness instances the design, the clocking block and the clock generator. It
would also be possible to have it instance the tester module, but in this example we have chosen to keep
the two blocks separate and to simulate them together as parallel hierarchies that communicate through
cross-module reference.
@(test_harness.my_cb) ...
In the testbench, clocking block signals are driven or sampled in specific cycles, with the exact timing
specified in the clocking block. To wait for the next cycle of the clocking block’s clock event, you can
apply cycle delays by using the clocking block's name in an @ timing control, just as though it were an
event. To wait for multiple clock cycles, just write a loop.
A clocking drive should always be synchronized to the clocking block. Asynchronous clocking drives
simply do not work!
It is very important that your testbench code should never wait for the original clock event. Doing so could
give rise to obscure and hard-to-debug race conditions between your testbench and clocking block input
sampling behaviour. Whenever you want your testbench to wait for the next clock event, use
@(clocking_block_name) as a procedural delay. This fits naturally with the idea that a testbench should
do all its synchronous driving and monitoring through a clocking block rather than by direct manipulation
of signals.
Clocking Blocks
output #0 G; driven in Re-NBA region of current time step
input #1ns output #1ns H; TWO clockvars: one input, one output
endclocking
Doulos strongly discourages use of input #0
The default clocking input skew value is #1step. An input skew of this value means that the input is
sampled in the Postponed region of the immediately preceding time step. The effect of this is exactly as if
the sampling were to take place in the Preponed region of the current time slot. Which has the useful
effect that signals sampled through a clocking block using #1step will yield exactly the same values as are
seen in assertions. The default output skew is #0. This means the clocking output is driven in current time
step – in the re-NBA region.
default : Defaults are overridden with a default statement. Regardless whether the default has its
standard value or a user-specified default, the skew for inputs and outputs can be specified in the input or
output declaration.
input #0 : An input skew of #0 has a special meaning – the input will be sampled in the Observed region
of the current time step. We discourage the use of this feature because its sampling is affected by any
non-zero delays.
posedge : You can also use posedge or negedge for the skew value, in which case the sampling or
driving timeslot is determined by the corresponding edge of the clock signal, rather than by a fixed time
delay. This provides a convenient way to drive DUT inputs on the inactive clock edge.
inout : Inouts have both an input and an output skew. An inout declaration is used to declare a clocking
inout with default skew. To override the default, use the input skew output skew syntax. Note, however,
that inout does not truly create a bi-directional clocking signal; it creates two independent structures in the
clocking block, that are hooked to the same external signal. Two separate clockvars are created,
although they have the same name! In practice this causes no difficulties because it is impossible to read
an output clockvar or to write an input clockvar, so it’s always obvious which of the two clockvars you are
accessing.
Preponed
sample values for assertions
Active =
#N ... #N ...
Events Events
created by created in
7 earlier slots
<= #N NBA
<=
<= #N
future slots
Clocking Blocks
Postponed
Execution
To next time slot
To understand the operation of clocking blocks it is helpful to know how a SystemVerilog simulator
manages its activity at any given moment. This behaviour is standardized across all simulators. When all
current events have been processed, the simulator advances time to the next moment at which there is
activity scheduled. The activity performed by a simulator at a given moment is known as a time slot.
This diagram shows a simplified version of the scheduling model. The key point is that nonblocking
assignments place events in the NBA region; these events are only activated once all the events
generated by blocking assignments have been processed.
The diagram also shows where assertions fit in. Values used by assertions are sampled in the preponed
region of the time slot, before any activity takes place. Any triggered assertions are evaluated in the
observed region, after the event activity has been processed. The postponed region is used by the
system tasks $strobe and $monitor, just before time advances to the next slot.
Scheduler Regions
From previous time slot Preponed Assertion and #1step sampling
Active
#0
Modules Inactive <=
$strobe,
$monitor
NBA
Assertions Observed 7
Re-active
Clocking Blocks
#0
Programs Re-inactive <=
SVA actions
Re-NBA Assign clockvars
Preponed – Variables used in assertions are sampled, and #1step sampling occurs for clocking block
inputs.
Active – Ordinary code runs in this region, including processes and continuous assignments.
Inactive – Contains events that are scheduled to occur after a delay of #0. When the active region
becomes empty, any events in the inactive region are promoted to the active region.
NBA – Contains non-blocking assignments. When the active and inactive regions are both empty, any
non-blocking assignments are promoted to the active regions for completion.
Observed – Properties and assertions are executed in this region (using variable values sampled in the
preponed region).
Reactive – Procedural code contained within programs (as opposed to modules) and the action blocks
associated with concurrent assertions are executed in this region.
Re-inactive – is to the reactive region what the inactive region is to the active region.
Re-NBA – is to the reactive region what the NBA region is to the active region. Clocking block outputs
with #0 delay are assigned in this region.
Postponed - $monitor and $strobe run in this region just before the simulator advances to the next time
step.
7 TB drives... D1 D2 D3 D4
clk
Clocking Blocks
#3 #3 #3 #3 • 2-clock delay
This timing diagram shows how a clocking block and a testbench can work together to generate stimulus
and check response of a DUT. Thanks to the clocking block we can now reliably drive and sample DUT
signals at the appropriate times. However, we have introduced a 2-cycle latency between the testbench
applying a stimulus and seeing the corresponding response. This makes it a little difficult to create truly
reactive testbenches that observe DUT state and create new stimulus based on that state; in our clocking-
block environment, the newly calculated stimulus will be applied two clocks later, rather than on the next
clock as we would probably desire.
Note that, in our example, reading Q1 and driving D3 happen at the same time. It is entirely possible for
the testbench code to read Q1 first, and use this value to influence the value that’s chosen for D3.
However, it is not possible to use Q2 to influence D3. In our diagram, we cannot see the value Q2 until
the fourth clock event, when we are about to drive D4. This could present a problem in the case of a
design with asynchronous feedback.
The really important idea here is that all testbench activity happens at the clock events. The testbench is
cycle-based.
Signal Aliasing
• Clocking signals may be associated with expressions using local or
hierarchical names
Clocking Blocks
begin
@(TH.cb) TH.cb.control <= 3'b100;
repeat (12) @(TH.cb);
assert (TH.cb.Q === 8'b00001010);
...
All the examples so far have declared existing names as clocking inputs and outputs. It is also possible to
declare a clocking input or output to be an alias for an expression. The expression may involve
hierarchical names and must be legal in a port connection to a port that has the same direction as the
clocking signal.
In the example, the output control is associated with the expression {Enable, UpDn, Load}. The
assignment TH.cb.control <= 3’b100 sets Enable to 1, and UpDn and Load to 0.
...
@rise rise.data <= 4'b1010;
@fall fall.data <= 4'b1100; 1010 1100
end
Many designs have more than one clock. A separate clocking block can be declared for each clock.
The same variable might appear as an output in two or more clocking blocks. An important application of
this is for double data rate memory (DDR SDRAM) interfaces, as shown here. The feature would also be
useful if a general-purpose bus was connected to different clock domains in a system.
If the same variable is driven from two different clocking blocks, there is no contention – the behaviour is
“last one wins” as when clocking blocks aren’t being used. Effectively, the clocking blocks take turns to
make assignments to their target signal.
Note that each clocking block updates its target variable only on those cycles where there has been a
clocking drive. If there is no clocking drive to a clockvar at a given cycle, the corresponding variable is not
updated.
Driving a Net
• Clocking drives nets or variables (hides the difference from the testbench)
W L
implicit continuous assign
wire W;
logic L; 7
clocking cb @(posedge clk); t t
output #1 W;
Clocking Blocks
output #1 L; Q Q
endclocking TH.cb.L <= 1; D D
TH.cb.W <= 1;
clk
Testbench writes both using clocking drive
cb.W cb.L
Clocking blocks have the very useful feature that their outputs can be either nets or variables. So far, our
discussion has concentrated on the use of variables as outputs from a clocking. This slide illustrates the
behaviour when a clocking output is a net: the clocking block creates a hidden internal variable, and
copies that variable on to the target wire by zero-delay continuous assignment.
The testbench, accessing signals only through the corresponding clockvars, does not need to be aware of
the difference between wires and variables in the DUT or test harness module.
This behaviour means that it is generally not useful to have multiple clocking blocks driving a given wire;
the values from the various clocking blocks would be resolved, just as if they had been driven through
continuous assign statements.
7 endclocking
Clocking blocks may be declared in interfaces and in modports. This encapsulates the testbench timing
for the interface in the interface itself, instead of in the testbench. In other words, it forms part of the
reusable verification IP for the protocol modelled by the interface. This is the recommended style.
By using a modport with a clocking block, we oblige the testbench to drive and sample the signals
synchronously.
An interface of this type usually needs to be written specially to capture the connectivity between
testbench and design. It is unlikely that an interface forming part of the design itself could be used in this
manner. Clocking blocks are not synthesisable.
• Clocking blocks
• Isolate the test bench from Verilog scheduler issues in the DUT (#1step)
• Encapsulate pin-level timing in the clocking block
Clocking Blocks
Use neither clocking blocks nor programs, just modules
• Use clocking blocks and modules but not programs (recommended)
• Use modules and programs with clocking blocks
End
In Verilog, modules are used to model both the design and the testbench. In SystemVerilog, you can of
course continue to use modules in this way. But SystemVerilog includes program blocks, which are
intended to provide a home for testbench code. By “testbench code” we mean test stimulus and response
checking. Test environments may also contain models of components that interface with the design; these
should continue to be modelled using modules.
Superficially, programs look very much like modules. They may have ports, and are instantiated just as
modules are. They may be declared outside any modules and interfaces. They may also be declared
(i.e. nested) inside modules and interfaces, although at the time of writing there is limited tool support for
nested module or program declarations.
In this course we completely avoid the use of program blocks. Some vendors and writers regard this as
the most appropriate guidance; others strongly recommend the use of programs. If you wish to learn
more about program blocks, you can find further information in the Appendix sections of this course.
7
Clocking Blocks
Aim
• To learn about the various data types that
SystemVerilog introduces for verification
Topics covered
• Dynamic Arrays
•
•
Queues
Associative Arrays
8
Array-like Containers
Queue
8
Associative Array
Arrays and Queues
In this section we are considering three new kinds of class-based container, as shown in the table above.
Despite its name, a dynamic array has a fixed size, but that size is determined each time the dynamic
array is created or re-created by calling new at run-time. In contrast to the dynamic array, the size of a
regular array is fixed at compile-time.
Dynamic Arrays
string names[]; Dynamic array declaration
initial
begin
Create 10 empty strings, names[0] to [9]
names = new[10];
names[0] = "Fred";
... Replace with 20 new strings
names = new[20];
Replace with 3 new strings
...
names = {"Mary", "Mungo", "Midge"};
8
assert( names.size == 3 );
A dynamic array is one whose size may be set or changed during simulation. Resizing is achieved
automatically by assigning new contents. An array of empty elements can be created using the new[]
operator (known as the "constuctor"). This can be used where the variable is declared:
There are two dynamic array methods, .size and .delete. .size returns the number of elements currently
allocated. .delete deletes the entire array.
Initialize
string names[] = new[10];
string more_names[] = {"Peter", "Paul", "Mary" };
initial
begin
... Larger array; keep existing
contents
names = new[20](names);
When resizing an existing array, new on its own would eradicate the contents. To prevent this, new may
take one argument, which is an expression used to initialize the array. In fact it could be the name of a
different dynamic array – the values are copied.
Array-like Containers
8
Associative Array
Queues
• Queue = variable-sized array
• A one-dimensional unpacked array that grows and shrinks automatically
• Declarations:
A queue is an array that has one variable-sized unpacked dimension. It may in addition have one or more
packed dimensions. The difference between a queue and a dynamic array is that a queue is optimized for
adding or removing elements to the existing contents at its front or back.
Queues are declared by specifying the range as [$] or [$:N]. The former is an unbounded queue and the
latter has a maximum index of N, or in other words a maximum size of N+1 elements.
Queues may be initialized when they are declared. If not, they will have no elements.
Queues may be used just like standard unpacked arrays. The only differences are (i) that you can
reference the last element using the index $ and (ii) that when assigning a queue, you don’t need the
same number of elements in the source and target.
If you use an unknown index – one containing Xs or Zs – or if you use an out-of-range index, the result is
the default value of the queue’s element type. For example, this would be 0 for an element type of int or
1’bx for an element type of logic.
Queue Methods
Can pass queues as
int numbers[$];
task/function arguments
8 endtask
printqueue ("numbers", numbers );
• Also
Arrays and Queues
As with dynamic arrays, a number of queue methods are defined, one of which, .size, is illustrated here.
function void insert(index, item) – inserts item at the specified index position.
function type pop_front() – removes item from the start of the queue
function type pop_back() – removes item from the end of the queue
Just as an example, here we show a dynamic array nested inside a struct nested inside a queue. We
have two calls to push_back to add two elements to the initially empty queue. Each push_back call
takes an assignment pattern nested two-deep to express the value of the struct with its nested dynamic
array.
The call to $display illustrates the %p formatter, which can be used to format the values of structs, arrays,
and queues. The output from %p shows three levels of nesting: array-within-struct-within-queue, with
element tags being used to identify the named fields within the struct.
Note that values for dynamic arrays and queues can be written using concatenation {...} or assignment
patterns '{...}. In the example above, although the nested array values were written using concatenation,
they are printed out in the form of assignment patterns.
Array-like Containers
The associative array allows even more flexibility than the queue in the sense that the elements do not
need to be numbered contiguously: there can be gaps in the element numbering, although because of
this, the element ordering is less well-defined than a queue or an array. The index type of an associative
array is not restricted to an integer: the index can be a string or can be an object handle (part of the class-
based language).
Associative Arrays
• Aka. sparse array or non-contiguous array or map
array[1] = 1;
array[10] = 2;
array[100] = 3;
foreach (array[i])
Useful!
8
$display("i = %0d, array[i] = %0d", i, array[i]);
All the arrays we have been looking at have contiguous integral indices. An associative array is one that
does not have a contiguous index – instead it is like a lookup table. The array element corresponding to
the index is not created until a value is assigned to it. Additionally, the index type need not be integral – it
could be string, for example.
An associative array with a wild-card index (i.e. [*]) can have values of any integral type. Two index values
having different widths, even if they have the same numerical value, are considered distinct.
In the example, the assignment to surnames uses an associative array literal, which is a form of
assignment pattern.
if ( surnames.exists("Joe") )
8 $display( surnames["Joe"] ); Smith
surnames.delete("Klaus");
Arrays and Queues
assert( surnames.size == 2 );
$display( surnames["Jawahar"] ); Warning! ""
A number of methods can be used with associative arrays. These enable you to find out about the array
(how many elements?), its members (has this value been stored?) and enable you to iterate through the
array.
The order in which the elements are stored is in accordance with the index’s data type. For example, if the
index is int, the elements are stored in (index) numerical order; if the index is string, they are stored in
lexical order. For other types, the LRM does not clarify the ordering.
If you attempt to read the value of a non-existent element, the simulator should give you a warning, and
the actual value returned will be the default value of the element type.
Foreach
• foreach is used to iterate through an array’s elements
int numbers[10];
foreach ( numbers[i] )
$display( "numbers[%0d] = %d", i, numbers[i];
8
Arrays and Queues
Bus-Functional Modeling
Aim
Topics covered
• Bus-Functional Modeling
• Separate Test from Test Harness
• Tasks/functions in interfaces
Bus-Functional Modeling
A BFM allows the testbench developer to model protocol and timing for a particular bus interface, using
method calls from the testbench to drive DUT signals. The aim is to separate what the test is to do from
the detail of how and when to do it. This separation means that changes to the protocol (or swapping in
an entirely new interface) will require no changes to the tests, so long as the method calls are the same.
In this section we use introduce the ‘separation of concerns’ approach as our first step towards a scalable
and reusable methodology for developing testbenches.
The ideas developed in this topic for structuring the test and the test environment will be a good
foundation for later in the course when we are concerned with class based verification.
The Bus Functional Model models the bus timing and protocol in a set of methods (tasks and functions)
which drive or respond to bus transactions. The DUT-facing side of the BFM models the timing between
signals accurately, both within a clock cycle by adding set up timing if necessary, as well as modelling
multi-cycle transactions. Control on the test bench side of the BFM is by calls to BFM tasks and functions,
collectively referred to as methods. The processess of going from the more abstract method calls to
physical, timed pin wiggles is an important feature of both BFM and test bench design.
We shall see later in the course how we can then place timing and protocol based checks in the BFM,
allowing other checking within the test environment to concentrate on higher level checking.
time T_per_bit;
Bus-Functional Modeling
This simple example models the bus behaviour with 2 methods which are called from the testbench – a
procedural interface. This BFM merely controls the signal, serial, which connects directly to the DUT.
One method, set_bit_time, controls bit timing, while the task, send, serialises the character to be sent
and models the (multi-cycle) interface protocol.
• A DUT facing port interface, providing accurate signal timing and transitions connecting to the DUT
• A testbench facing procedural interface in the form of a set of methods, so that the testbench can make calls
without concern for the implementation detail or timing.
logic serial;
initial
begin: test1
bfm.set_bit_time(52083);
#1000;
bfm.send("H");
9 bfm.send("i");
end
Hierarchical references
endmodule
Bus-Functional Modeling
We can now create an instance of the BFM and connect it to the DUT in the testbench module. At this
stage we will assume that the DUT outputs are appropriately connected, we are only concerned here with
driving the serial wire in to the DUT.
The stimulus generation, itself, is defined in an initial block and makes use of the calls to the BFM
methods. To a test writer (who might not be the same person as the testbench developer), using this API
to control the DUT is more intuitive than having to be concerned with the details of internal testbench
behavior.
However, this approach requires us to instantiate the DUT and testbench blocks (other BFMs, clock
generators etc) in the module, so that when we want to create the remaining 199 tests, we have to copy
too much code. And, of course, the DUT port list might change, so the next step is to separate these
potential changes from the test code.
Bus-Functional Modeling
Continuing the theme of separation of concerns, we can create a test harness to contain an instance of
the DUT and our BFM, along with other testbench blocks. This now means that the testbench consists of
an instance of the test harness along with the test code itself in an initial block, as before. Now the other
199 tests instantiate the same harness and any changes to the testbench or DUT interface is localised
there and doesn’t have to be chased through any number of tests.
The test code in the testbench module now calls the test harness methods which themselves call BFM
methods – the test harness methods are referred to as proxy methods. Note that this test will work happily
with any test harness that exposes the same methods (set_bit_time, send). Similarly the test harness is
written in such a way that it is independent of the test that uses it. This independence of test and
testbench is a critically important feature of the test bench design methodology which is being introduced
in this session. In a real verification project, the planning phase requires a great deal of thought and
documentation to achieve this independence.
The test harness tasks in the example control a single BFM, but in a more complex (real) example, the
proxy tasks might each control one or more BFMs whose behaviour is hidden from the test itself. Thus we
have introduced the possibility of a layered architecture in to the test harness where each layer interacts
with modules in the layer above (responses, perhaps) and below (driving and control).
A problem with the way it is set out in the slide is that the complex test harness will contain a number of
tasks. SystemVerilog provides the interface construct which helps solve this.
Task/Function in Interface
interface serial_if;
logic serial; BFM tasks/functions
in an interface
...
function void set_bit_time (time t);
...
task send (input [7:0] v);
... tasks/functions imported into module
modport mp (import set_bit_time, send);
endinterface
9 iport.set_bit_time(52083);
#1000
iport.send("H");
Bus-Functional Modeling
iport.send("i");
Interfaces, like modules, can contain methos which can be called from outside the interface using the dot
notation for cross module references: iport.send(…). The modport construct provides a way of specifying
signal direction and of providing access to both signals and methods. The modport specifies directionality
from the point of view of the block it is connected to, hence the name which is an abbreviation of module
port. So in the slide, the modport, mp, imports the methods set_bit_time and send and makes them
available to blocks that it connects to. Where an interface is being referenced through a modport, any
task or function that is to be called from a module needs to be listed in the modport declaration.
The interface, unlike modules, can be passed as part of a module’s port list in a very flexible way:
• The type of interface can be passed, serial_if – module code the accesses the required modport
methods or signal by cross module reference, specifying the modport and signal
• The modport can be specified, serial_if.mp, as in the example, in which case a call such as
iport.send(…) is controlled by the modport’s import list.
• A generic interface (with or without a modport) can be used for greater flexibility (other interface types
can be connected).
Note that when importing (or exporting) a method through a modport, it is usual just to provide just the
method name. It is possible to specify the complete prototype in the import/export statement, but at the
time of writing, this feature is not supported by all SystemVerilog simulator.
Bus-Functional Modeling
SystemVerilog interfaces may contain tasks, functions, initial and always procedures, continuous
assignments, assertions – in fact, anything that a module may contain except module instances.
Interfaces can be used to model bus functionality, they provide reusable bus-functional and transaction-
level access to the bus and they can also be a convenient place to do the bus protocol checking through
assertions.
The APB example interface here has methods for performing bus reads and writes. Notice they are
named without reference to the protocol, so that by moving these tasks in to the interface, the testbench
can call an interface read or write without concern for the underlying protocol, so long as the arguments
are correct, with the result that the interface code and the testbench code are ‘de-coupled’ from each
other and therefore each is more re-usable (compare now the situation we had in the earlier slides).
In the example, which shows an interface modelling an APB bus, a modport called TB_Master has been
added. This modport will be connected to the tester module instance that forms part of the testbench. The
TB_Master modport does not contain any interface signals (except PCLK), because the interface signals
themselves will be controlled by the tasks.
An APB Test
endmodule
Bus-Functional Modeling
End
A generic interface with specified modport is passed in the portlist to the test module. As explained earlier,
this allows a modport named TB_Master from any type of interface to be connected and so long as the
modport imports a read and write task with matching prototype, this test code could be reused with
different interfaces.
In this session we have begun to develop some very important themes to creating test environments:
• The Bus Functional Model models the protocol and timing on the bus.
• We have seen how to structure test environment so that the test code can be separated from the test
harness containing a DUT instance, BFMs and other testbench blocks.
• We have seen how separating concerns can lead to clearer and more reusable code by placing tasks
and methods in the BFM
• We have seen how we be able to create a layered structure in the test environment where control is
passed down to the BFMs and responses might be passed back up.
Interfaces (with modports) can be used to model the connectivity between modules in a flexible way
Randomization
Aim
Topics covered
• Testbench Automation
• Random numbers in SystemVerilog
• Randomize with inline constraints
• Random stability
10
Randomization
Random generation of stimuli has the potential of creating unexpected sequences of stimuli and thereby
uncover bugs more effectively than directed testing alone. Directed testing anticipates where a bug might
lie and therefore is less likely to hit unexpected cases or test enough scenarios. On the other hand
applying purely random stimulus is inefficient because most of the generated sequences would be
unrealistic or impossible in a real life application.
Testbench Automation
• Randomization:
• Functional Coverage
Being able to constrain the randomization of stimuli in a repeatable way (for retesting) so as to apply life
like yet unexpected combinations, allows the test team to verify the required design features more
efficiently.
With directed testing one can be sure what a particular input stimulus is testing for and there fore check
the expected DUT response. However, with random stimulus generation, the actual DUT input needs to
be sampled independent of the generation scheme and the expected DUT behaviour must be inferred
and checked using a reference model. By separating the concerns of generating stimuli from those of
sampling and checking, the test bench components can be reused in different configurations.
initial ???????????
??????????
repeat(4)
?????????
$display( $urandom ); ???????????
Each tool uses a different
RNG algorithm
initial ?
?
repeat(4) ?
$display( $urandom_range(0,7) ); ?
Randomization
In SystemVerilog it is still possible to use the IEEE Verilog $random function and the LRM specifies the
algorithm for how it generates pseudo-random numbers.
SystemVerilog adds new system functions and syntax to extend random generation and the standard
allows each tool vendor to implement their own random number generator (RNG) and constraint solver.
Each process and object has its own RNG, each of which is initialised with a seed from the parent thread
at the start of simulation. This means that the pseudo-random sequences generated in threads are
isolated from each other and each RNG will cycle through the same sequence of values given the same
initial seed – a property referred to as random stability.
$urandom and $urandom_range uses the thread’s RNG to generate a sequence of values.
std::randomize
byte unsigned a, b;
logic [7:0] c;
initial
repeat(4)
begin
bit ok; Randomize "scope variables"
std::randomize(a, b, c);
$display(a, b, c);
The std package defines a more powerful randomization function, randomize(), to generate random
values for its arguments, which must be scope variables, in other words, variables from the current scope
as opposed to variables called with a cross module reference.
std::randomize() can be called with a constraint block by using the with {…} syntax. Constraints are
placed inside the braces, each constraint is completed with a semi-colon, including the last constraint.
The example in the slide randomizes the variables a, b and c with the constraints a must be less than b
and b must be less than c.
The function returns a bit,1 if the randomization was successful and 0 otherwise. It is a good idea to test
the result of the randomization call in case any applied constraints caused the randomization to fail.
Notice that you do not need to prefix randomize with std::, although you may if you wish to clarify the
intent or to explicitly use the randomize() function from the std package.
initial
begin: P1 RNG P1 seeded from cmd line
if ( !std::randomize(a, b, c) ) ...
end
initial No effect
begin: P2 RNG P2 seeded from cmd line
repeat(N)
if ( !std::randomize(p, q, r) ) ...
fork
begin: P3 RNG P3 seeded from P2 Effect
repeat(N3)
if ( !std::randomize(p, q, r) ) ...
end
begin: P4 RNG P4 seeded from P2 No effect
repeat(N4)
if ( !std::randomize(x, y, z) ) ...
end
...
10
Randomization
SystemVerilog adds a deterministic random number generator (RNG) to every thread and object. Each
child RNG will be initialised with the next seed provided from the parent thread RNG. The RNG will
generate the same sequence of values given the same initial seed.
In the slide, process P1 uses its own RNG and so will not affect the sequence of random values
generated in process P2 (P1 and P2 have seeds provided by the parent process).
However, P2 is the parent process for P3 and P4 and they are created in a fork…join block after the first
repeat loop has been executed, but since there is no prediction of the order in which they are seeded,
they are initialised with the 2 next values of P2’s RNG – one to P3 and the other to P4. So now, the
sequence of values generated in P3 and P4 will depend on N in the previous repeat loop. However,
having both been initialised, the sequence in P4 will not be affected by how many times std::randomize()
is called in P3 – and vice versa.
randstate = p.get_randstate();
srandom seeds the current process (or object) from an integer – by using the std package process type
one can get a reference to the current process from within a module.
get/set_randstate allow you to save and subsequently restore the random state of the process, but
because the string is tool-specific, these two calls only make sense when used as a pair.
In the example above, you get the identical sequence of 4 random numbers repeated twice.
Note that the string, randstate, returned from get_randstate() is implementation specific and the purpose
is to store the randomization state in a way that can be restored, as shown.
Unrealistic stimulus,
repeat (100) not very useful
{ Adrs, Data, nWr, nRd } = $urandom;
Randomization
Rather than generating completely random values, it is preferable to randomize a variable whose purpose
is to control how other variables are generated. These variables are referred to as control knobs and in
the slide, the cycle_kind control knob is used to select either a read or write cycle.
Randcase
As an alternative to the technique shown on the previous slide, SystemVerilog provides a weighted case
statement – randcase. The syntax is like that of an ordinary case statement, except that there is no case
expression after the keyword randcase, and the randcase item expressions are weights, which indicate
the relative likelihood of the corresponding randcase item being executed.
In the example, the randcase is executed on every clock. The total value of all the randcase item weights
is 100, so each weight represents the probability, expressed as a percentage, of the corresponding
statement being executed. So Reset is asserted 1% of the time, Start 5% of the time and Stop 5% of the
time. 89% of the time, none of these signals is asserted.
Randcase
Randomization
You may remember that in Verilog, case item expressions need not be constant. The same is true of
randcase item expressions.
The example has been rewritten to prevent Start or Stop being asserted for more than one consecutive
clock. This is achieved by using the variable Start_or_stop to adjust the weights in every clock cycle
following one where Start or Stop was asserted.
Randsequence
• Generates a random sequence of actions based on a grammar
randsequence (start)
Productions
There is often a requirement to generate a sequence of data values according to some sort of rules of
“grammar”. SystemVerilog includes the randsequence statement to do this.
A randsequence statement looks rather like a case statement. It generates a grammar-driven sequence of
random productions. Randsequence contains one or more production items, each of which consists of a
name followed by a colon and then a list of one or more alternative rules, separated by |. These are
chosen at random
This simple example illustrates how randsequence works. First the production in parentheses after the
keyword randsequence is selected – start in this example. The production, start, consists of 3 further
productions which will be generated one after the other in the order given. The productions, part1 and
part2, are terminal productions as they contain SystemVerilog code which is executed. The non-terminal
production, part3, includes a control – repeat (100) – which causes the production, repeated, to be called
repeatedly.
The production, repeated, selects one of its 3 productions. If either or or_this is selected, the code block is
executed and the next loop of part3 is executed. If abort was selected, the code block is executed, but the
break forces execution to continue from after the endsequence statement.
This example shows how one can generate interesting random sequences of behaviour from just a few
rules.
Recursive production
Also case and rand join
Arguments to productions
Randomization
This slide illustrates further features of randsequence.
Production start selects one of 3 options according to a weighting so option body is selected 8 times out
of 11, empty 2 out of 11 and abort is selected 1 out of 11 times.
Production choice includes a conditional test and calls production action with an argument list
10
Randomization
Coverage
Aim
Topics covered
• Testbench Automation
• Covergroups
• Coverpoints
• Cross coverage
• Coverage bins
11
Coverage
• Randomization:
• Functional Coverage
11 Collecting functional coverage requires a way of automatically recording what stimuli have been applied to
the design by the test environment. In the Randomization section, we saw how scenarios can be
Coverage
generated in a random, but controlled, fashion. In this section, we look at how we can record what stimuli
have been applied.
It is important to plan early in the verification task to identify the design features to be tested and from
that, workout what scenarios need to be generated. Test bench components monitor the DUT signals –
both the stimulus and response – and so have the information needed to record what has been tested.
With random generation of stimuli, there is no explicit relationship with the resulting DUT response. It is
necessary, therefore, to develop monitoring components to sample signals in the design and from this
infer what stimuli and responses actually occurred rather than were intended to occur. This collection of
functional coverage enables the verification team to identify which tests were performed, and what DUT
behaviour was exercised.
Checking
It is an important principle that the DUT behaviour is monitored independently and concurrent with
stimulus generation. The DUT behaviour is used for both functional coverage collection, so is also used
as an input to a reference model so that the behaviour can also be concurrently checked.
Functional Coverage
• Create covergroups starting from a test plan
module InstructionMonitor (
input bit clk, decode, 14
input logic [2:0] opcode, 9 10
input logic [1:0] mode ); 7 6
5
3
covergroup cg 0
000 001 010 011 100 101 110 111
@(posedge clk iff decode);
coverpoint opcode; Coverage hole
coverpoint mode;
endgroup 18 16
13
7
cg cg_inst = new;
0 1 2 3
...
...
Does not count X or Z
endmodule: InstructionMonitor
Functional coverage is distinct from property coverage which is a part of the SystemVerilog Assertions
11
(SVA) language, although both types of coverage are used and important for evaluating the state of the
Coverage
verification task and most simulators have an integrated coverage database facility that allows you to
assess both forms of coverage together.
At its simplest, we might be interested in counting the number of times a variable takes has each of its
possible values at a point in time. In SystemVerilog, coverage is based on the covergroup construct and
in the example we have two variables, opcode and mode, which define coverpoints. The covergroup is
sampled on each occurrence of the expression @(…), referred to as the sampling event. So in this case,
on each rising clock edge of clk when decode is true, mode and opcode are sampled and the count for
the sampled value is incremented. These counted values are stored in bins; by default there is one bin for
each distinct value. The coverage information can be obtained by calling a coverage method or system
function, or by reading the report that the simulator creates. Any value that is never seen during a
simulation, will have a bin count of zero and is referred to as a coverage hole.
SystemVerilog's coverage system measures only 2-state values, not X or Z values. It is important to
avoid presenting any X/Z values to the coverage mechanism. Consider using assertions with the
$isunknown system function to confirm that this never occurs in practice.
When defined in a module, the covergroup needs to be instantiated like any other variable, but needs to
be created using new.
Covergroup Syntax
• Covergroup can be in a module, interface, program or class
Classes are not
module InstructionMonitor ( discussed here
input bit clk, decode,
input logic [2:0] opcode,
covergroup name
input logic [1:0] mode );
... Don't
... covergroup typically in a BFM or checker
forget!
endmodule: InstructionMonitor
11 Coverage collection incurs a performance and memory cost, so it is important to sample only when
necessary rather than on every occurrence of some frequent event, like a clock edge. One way of
Coverage
selecting the clock edge is to provide a boolean qualifying expression after the (optional) iff keyword. The
purpose of the expression is to provide a short clear control of the sampling event – if there is a complex
expression to be evaluated, this is best done outside the covergroup block and using the result with in the
iff expression.
It is easy to forget to instantiate the covergroup – most cases where no coverage is collected is due to the
covergroup not being instantiated (it’s worth checking any iff qualifying expression too)
Coverage Bins
logic [2:0] opcode;
shortint unsigned data;
By default, a separate bin is created for each possible value of a variable, but it may be that not all values
11
are interesting, possible or, in fact, legal. SystemVerilog provides a bins syntax to control how many bins
Coverage
are to be used and what set or range of values will increment the count for a bin.
Sets
A set of possible values, written using a set-like syntax in curly brackets (braces), can be allocated to a
single named bin. Alternatively, the bin's name is followed by square brackets; this creates an individual
bin for each value in the set. Finally the bin's name can be followed by square brackets containing a
number; this represents a number of bins, over which the set of values is to be distributed. The
distribution of values into bins aims to be as uniform as possible, but the distribution is sometimes a little
surprising unless you take care to make the number of bins an exact divisor of the number of values in the
set.
Value-ranges
Ranges in the set syntax can be specified using [low:high] syntax. Either the low or high limit can be $ to
denote the extreme limit of the value's possible range.
Instance Coverage
By default, coverage is collected across all instances of a covergroup (so called, type coverage). This
might not be what is wanted, so there are a number of options available, one of which, per_instance,
controls whether each separate instance is covered separately or collectively. When per_instance = 1,
each instance coverage is collected separately in addition to the type coverage. By default it is 0.
Cross Coverage
10
Coverpoint labels
11 2 2
0 0 0 1 1
covergroup cg ...
cp1: coverpoint opcode;
cp2: coverpoint mode; 10 3
0 0 1 0 1 1 1
cross cp1, cp2;
mode
endgroup
01 3 3 2 3 2
0 0 0
7
• A bin for each combination 00 3
1 1
4
2
0 0
• A lot of data - use with care
000 001 010 011 100 101 110 111
opcode
End
11 Cross coverage creates coverage for combinations of coverage points. In this way we can count the
number of times opcode was 0 when mode was 0 and so on – here there are 32 bins created.
Coverage
Although we had just the one hole for opcode when considered on its own (100 is never hit) and mode
does have 100% coverage, the combination reveals 11 holes or about 66% coverage. When defining
cross coverage, it is even more important to understand which bins are illegal and uninteresting and
which ranges of values can be assigned to more interesting bins.
It is very easy (through a lack of thorough planning) to end up with a vast number of cross coverage
points, resulting in pessimistic coverage numbers.
Aim
Topics covered
12
module foo;
int n = 2; Module scope
initial
begin: foo Block name foo hides module name foo
static int n = 3; Block scope
assert( n == 3 );
assert( foo.n == 3 );
assert( $root.foo.n == 2 );
$root.top-level-module
assert( $root.foo.foo.n == 3 );
assert( $unit::n == 1 ); $unit::name-at-file-scope
$root
12 In all versions of SystemVerilog, global objects may be referenced using a hierarchical name that begins
with $root. You may also be able to reference global objects with their simple name, provided the name is
not otherwise visible using SystemVerilog’s name search rules.
Other Language Features
$unit
In SystemVerilog, you can place declarations outside of any modules. For example you can declare tasks,
functions and variables outside modules.
Typically, a design in SystemVerilog will be written across several files. Any declarations outside the
modules are called compilation unit declarations. The SystemVerilog standard states that source files can
be grouped into compilation units. The way this is done is tool-dependent. According to the SystemVerilog
LRM, a compilation unit could be one of the following:
• A single file
The declarations in a compilation unit in effect form a package that has the name $unit, and that is
implicitly wildcard-imported into the compilation unit. The declarations cannot be made visible outside the
compilation unit in which they appear.
Enumeration Methods
0 1 2 3
enum {red, green, blue, pink} e = green;
$display( e ); 1
$display( e.first ); 0
assert( e.last == pink );
Tool-dependent
$display( e.first.name ); assert( e.next == blue );
assert( e.num == 4 );
Enumerations provide a way of labelling a set of related constants (referred to as enums) and by default
these values are of type int. The enumeration is defined using the enum keyword. Other integral types
can be declared as the enumeration base type and the rule is that by default, the first declared value (red, 12
in the example above) has the default value of the base type (so 0 in the example). Each neighbouring
enum in the set has an incrementing value, so the value of green is one higher then the value of red and
• first() last() – returns the value of the first or last enum of the set
• next(n) prev(n) – returns the value of the enum n places towards the last (or first, respectively) and wraps, if
necessary. By default, n = 1 and can be left out.
• name() – returns a string representation of the enum’s label. Remember that the enum itself is a named
value, so name() returns the label, as a string, that is associated with that value
You can also assign specific values to one or more enums in the declaration by simply using something
like red = 24’hFF0000, … the next neighbour must then either be assigned a larger value, or
(automatically) get the next increment of the base type. Although enums can be viewed as labelled
constants, it is a compile error to assign an integral value directly to an enum, however it is allowed to
compare an enum as shown in the slide above. At the time of writing, it’s not possible to chain
enumeration methods in all tools, so e.first.name() in the example above, does not guarantee to work.
Many of the datatypes introduced in this session have methods to access or manipulate values, they can
be called using a ‘.’ notation between the variable and the method – very similar to how methods are
called in SystemVerilog Classes and is a very important feature of object-oriented languages.
Equivalent declarations
Typedefs can be very convenient when building up complicated array definitions, as shown on the slide.
12 They can also save you the need to write the same subscript definition more than once.
As we have seen previously, you can write the value of an unpacked array or struct using the assignment
pattern syntax, which has the tic mark ' before the opening curly brace. An assignment pattern can be
Other Language Features
used to write an array literal where all of the elements inside the assignment pattern are themselves
literals.
It is possible, but inefficient, to initialize an array with a large number elements by writing an array literal
with a large number of values, alternatively, one can use replication, though the syntax can be awkward.
12
One can easily sepecify a default value for the unspecified elements in an array literal by using the
reserved word, default, followed by a colon and the required value. This is particularly useful where the
Where an array literal begins with a list of values, these are assigned in order to the corresponding array
elements. A default can then be used for the remaining values. For example:
Alternatively, the index:value syntax format may be used to specify the value for arrayname[index], in
which case the ordering doesn’t matter. At the time of writing not all simulators supported the use of an
array index as a key, however.
Finally, the type of the element may be used, though this syntax is more appropriate in structs, where the
members may have different types.
int k = 1;
initial
twod = '{ 3{ '{k, k+1, k+2, k+3} } }; Assignment pattern
Note that an array literal is an assignment pattern for an array (viz: structure literals which are
12 assignment patterns applied to structs). While an array literal can also contain a replication, it differs
from an an assignment pattern in that all its elements are constant expressions. An assignment pattern
is a more general concept as it can be applied to both arrays and structs and can contain both variable
expressions and default values.
Other Language Features
They both reflect the structure of the array being assigned to, so where the (unpacked) array has more
than one dimension, this is reflected by a nested assignment pattern for eachdimension – as shown
explicitly in the first example. The two dimensional unpacked array twod is assigned an array literal
consisting of 3 nested array literals, each with 4 elements. If you expand the replication in the
assignment pattern of the second example, you will see that it has the same structure. In fact, it is an
error if the assignment pattern or array literal has a different structure to the target of an assignment,
whether or not they both have the same total number of elements.
struct packed {
logic a;
A packed struct behaves like a vector
shortint b;
} s;
A packed array or struct in SystemVerilog occupies contiguous bits in memory i.e. the data is packed
together, thus they behave and can be operated on like vectors in Verilog. Therefore a packed array or
struct can be assigned to by concatenation, using braces without the tick – as shown, as well as being 12
assigned using an assignment pattern. Unlike assignment patterns, concatenations can contain a mix of
scalars and arrays of different lengths.
A further point to note is that the struct in the example is 17 bits wide with b occupying the bottom 16 bits
and a occupying bit 16. Thus frequent access of s.a may have a small performance impact compared to
an unpacked struct where the compiler might have the freedom to place each member of s on a
convenient alignment boundary – something similar might, of course, be said of packed and unpacked
arrays.
3 1 2 Dimension number
logic [7:0] A [1:2][0:15];
assert( $dimensions(A) == 3 );
assert( $high(A) == 2 );
assert( $high(A,1) == 2 );
assert( $high(A,2) == 15 );
assert( $high(A,3) == 7 );
assert( $size(A,3) == 8 );
The array querying functions are system functions (prefixed with a $) that return information about the
We’ve seen examples of arrays with multiple packed and unpacked dimensions, so how are they
numbered? The packed dimensions are easy enough to understand – if you have:
Other Language Features
You have a 32 bit vector representing, say, 4 bytes. It is intuitive to think that you would scan the bits of
the lowest byte from bit 0 to 7 before going on to the next byte and so on. This is what is meant by (in this
case) the byte dimension ([7:0]) being the fastest dimension and the [3:0] dimension being the slowest.
SystemVerilog assigns the value 1 to the slowest dimension ([3:0] in this case) and 2 to the [7:0]
dimension. A useful mnemonic is to think of the way time can be expressed as hours:minutes:seconds
with the hours dragging by (leftmost and slowest dimension) while the seconds flash past (righmost,
fastest dimension)
We might picture this as a table of 8 rows by 16 columns, each element of the table is a packed array as
before. Now we might want to scan across the columns of one row (like a TV screen) before going on to
the next. Intuitively, we then want to process the packed dimensions as before. So now we have the
unpacked dimensions being slower than the packed dimensions with the leftmost unpacked dimension
being the slowest (1) and the rightmost packed dimension being the fastest (4).
In these system functions, dimension defaults to 1, so does not have to be supplied and for fixed sized
arrays (such as the ones here where the size is known at compile time), they behave as constant
functions and can be used in parameters or, indeed, in an array declaration: 12
logic [$size(big_mem_arr, 1)-1:0] other_mem_arr [1:2][0:15];
• $low/$high(arrayname, dimension) – returns the least and highest index, respectively, of the given array
and dimension
• $increment(arrayname, dimension) – returns 1 if $left >= $right and -1 otherwise. This system function is
hardly used at all, if ever.
• $size(arrayname, dimension) – returns the number of elements of the given array and dimension
• $dimensions(arrayname) – returns the number of dimensions of the given array, so will return 1 if handed a
string and 0 if it is, in fact, handed something that is not an array at all i.e. an integer (so called: a scalar
object)
$bits
• How many bits in a variable or type?
assert( $bits(vec) == 8 );
assert( $bits(arr) == 80 );
typedef struct {
int i;
bit [7:0] j;
} unpacked_t;
assert( $bits(unpacked_t) == 40 );
The system function $bits may be used to determine how many data bits a type or expression has. A 4-
$bits may also be used for dynamically sized expressions, such as those involving dynamic arrays.
Other Language Features
When using $bits on an expression, it must not call a function that returns a dynamically sized data type
(such as an array querying function – later in this session), nor a dynamically sized data type name.
Bit-stream Casting
$bits used to size a vector
typedef bit [$bits(unpacked_t)-1:0] packed_t;
assert( $bits(unpacked_t) == 40 );
assert( $bits(packed_t) == 40 );
$bits can be used on fixed sized types (whose size is known at compile time) or expressions to return a
constant expression, so $bits(some_fixed_sized_array) can be used in the declaration of an array:
12
logic [$bits(some_fixed_sized_array)-1:0] another_array;
1. Convert the unpacked type to a generic packed value with the same number of bits (this is the bit stream – it
has no specific format at this stage)
Both of these steps are accomplished by the single call using the bit stream cast in the example.
Implicitly defined
SystemVerilog provides a number of array manipulation methods, which are used to search and order
All these methods operate on unpacked arrays (including dynamic and associative arrays and queues).
Some of them work with packed arrays too.
Other Language Features
The return type is always a queue – even if that queue is empty or contains just a single value.
As an example of an array manipulation method, consider find. In the example shown here, arr is an
unpacked array with 10 elements of type int. q[$] is a queue of ints and is used to hold the value returned
by arr.find.
The find() method takes one optional argument, which designates an element of the array. It returns a
queue – in this case a queue of ints – containing the elements of arr that match the expression in
parenthesis after the word with. This ’with (Expression)’ is mandatory here.
If you omit the argument to find, a default argument called item is created. This is illustrated in the last
line.
Here are the manipulation methods that are used for searching the elements of an array.
with is mandatory 12
• find – elements satisfying the withexpression
These last 4 methods can be called accompanied by with, in which case elements are found which satisy
the expression or a null queue otherwise. For example, in the slide, q = min(x) with (x > 5); returns q = ‘{6}
Note: these methods work in-place: they modify the array itself
These methods are used to order an array. Note that reverse and shuffle must not have a with clause,
12 but sort and rsort may do. In these cases, one might want to sort a struct, say, on a particular field:
struct {
byte red;
Other Language Features
byte green;
byte blue;
} colour ;
colour.sort with ( item.red ); // sort using the red field only
colour.sort( x ) with( {x.blue, x.green} ); // sort by blue then green
assert( vecs.sum == 15 );
assert( vecs.product == 72 );
assert( vecs.and == 0 );
assert( vecs.or == 'b111 );
assert( vecs.xor == 'b011 );
End
These array reduction methods reduce an array to a single value. This value is stored in a queue – which
will end up having one element.
• or – Bitwise or
In each case, if a with clause is specified, the method acts only on the elements that satisfy it.
12
Other Language Features
Aim
Topics covered
13
Interface
The Direct Programming
• C library functions
• Mathematical calculations
• Advanced and efficient file I/O
• ...
There are many reasons why it is sometimes useful or necessary to call C functions from a SystemVerilog
simulation.
You might have existing functions, written in C, that implement algorithms that you are now implementing
13 in hardware. The C function could act as a reference model in a SystemVerilog test environment, or could
be used to calculate the expected hardware response. Also, functions may be available to generate
appropriate stimulus.
Interface
The Direct Programming
Another reason to use C is for all the standard library functions that it provides. For example the C library
includes many common mathematical functions, and provides for efficient file handling.
These are just a few examples; there are many more reasons why calling C functions from SystemVerilog
may be useful and the DPI makes this relatively easy to do.
• No function registration
• No need to traverse object hierarchy to find things
• No synchronisation
The VPI is comprehensive, but complex. It is primarily intended for experienced C programmers to
provide customised SystemVerilog tools. In addition, creating even a simple VPI application is not trivial,
because of the complexity of the interface.
For “ordinary users” of SystemVerilog, there is sometimes a requirement to call existing C functions
directly from SystemVerilog code, or to extend SystemVerilog’s capabilities by writing simple functions in
13
C. The aim of the DPI is to provide a simple mechanism for doing this, thus avoiding the steep learning
Interface
The Direct Programming
curve needed to use the VPI.
The DPI provides an easy-to-understand interface between C and SystemVerilog. It has been designed
so that compiled DPI C code (object code or libraries) is binary-compatible: once compiled, it can be used
with different vendors’ simulators running on the same platform.
We have been talking about C, because this is likely to be the language used. The SystemVerilog LRM
specifies the C layer of the DPI. It is also possible to use C++, provided the top-level functions use C
linkage. Theoretically, other languages could be used too, but no alternatives to C are specified in the
LRM. The language used does not affect the SystemVerilog code in any way.
Finally, the DPI also provides an alternative means of using the VPI functions, because it is possible to
call them from DPI-imported C functions.
System-
Verilog Compiler Simulator
-sv_root
SV compiler may -sv_lib Linked
generate C header .h
-sv_liblist
Here is the flow for using the DPI. This shows the flow in fairly general terms; the exact details for a
specific tool will be provided as part of a tool’s documentation. Vendors usually supply application notes
and examples, which provide a good starting point for your own applications.
13 To use the DPI, you need functions written in C, whose arguments and return types are compatible with
SystemVerilog. You will also need to declare the C functions in your SystemVerilog code. We shall be
discussing what all this means shortly.
Interface
The Direct Programming
Some SystemVerilog compilers will automatically generate C header files from the SystemVerilog
declarations. Tools may also be able to generate appropriate SystemVerilog declarations from existing C
code.
The SystemVerilog LRM includes the source code for the header file svdpi.h, which most DPI C functions
will need to include.
The C code must be compiled and linked using a C compiler, such as gcc or the Microsoft C Compiler, to
create a shared object file (UNIX and Linux) or DLL (Windows). You will need to find out which tools your
simulator supports, and which switches are required. Details for the simulator you are using will be
provided in the tool’s documentation.
Finally, when your run the simulation, you will need to tell the simulator where to find the shared libraries
that contain your DPI C functions. The SystemVerilog LRM recommends three simulator command-line
switches, which are described on the next page.
Tools may have options to simply the process. For example, you may be able to list the C source files
with the SystemVerilog files in a single command.
Command-line Switches
• The following simulator switches are recommended in the LRM, not mandated
Default is current
working directory
-sv_root path Location of lib or liblist files
-sv_liblist bootstrap_file File contains object/libraries
-sv_lib object_file Object/library – no extension
These are the command-line switches, which the SystemVerilog LRM recommends simulators use:
-sv_root directory – the root of the directories mentioned in the other switches. By default, this is the
simulator’s working directory.
-sv_liblist bootstrap_file – the bootstrap_file is a text file containing a list of shared object or library files
13
without the file extension (i.e. without the .so or .dll extension at the end). The first line of the file must be
Interface
The Direct Programming
#!SV_LIBRARIES.
-sv_lib library – where library is the name of shared object or library file without the file extension.
You can have several -sv_lib and -sv_liblist options in the same command line.
Importing a C Function
module foo;
import "DPI-C" function int count_1s (int v);
int i, ones;
always @(i)
ones = count_1s(i);
...
The most common requirement for the DPI is to import C functions, so that they may be called from
SystemVerilog code.
import "DPI-C"
13 To do this, all you need to do is to declare a DPI import function in SystemVerilog. The simplest form of
this is the keyword import followed by the string "DPI-C" (the quotation marks are needed) and then the
function prototype declaration. The function prototype declaration is no different from a standard
Interface
The Direct Programming
SystemVerilog function prototype, although there are one or two restrictions. (A “function prototype” is a
declaration of the function’s name, return type and arguments with no function body and no endfunction
keyword.)
Having declared the DPI import function, you use it just as if the function had been written in
SystemVerilog. From the SystemVerilog viewpoint, an imported function behaves just like a native
SystemVerilog one.
Here is the C code for the C_count_ones function. The SystemVerilog function is called count_ones, so
the C identifier is included in the import declaration. In this example, the SystemVerilog type of the
function’s return value and of the only argument, v, is int. This maps directly to the C type int, so the
corresponding C function returns int and also has one argument, v, of type int.
Types other than int may be used – this will be discussed shortly.
#include "svdpi.h"
int count (int v) {
int n = 0;
unsigned u = v;
while (u) { n += u & 1; u >>= 1; }
return n;
}
If the C function has a different name from the SystemVerilog function, the name of the C function and an
equals sign is included after "DPI-C". By default, the C function should have the same name as the
SystemVerilog function.
13
Interface
The Direct Programming
SV C
byte char
shortint short int
How many bits?
int int
longint long long
real double
shortreal float
For storing C pointers chandle void *
string const char *
bit unsigned char
Scalar values
logic/reg unsigned char
This shows how types are mapped between C and SystemVerilog. Many SystemVerilog types are directly
compatible with corresponding types in C, and it is easy to use them with the DPI. You do need to be
careful, because in C the width of integer and floating types is not defined – int in C may not always be 32
bits – whereas the width of these types is defined in the SystemVerilog LRM.
13 chandle
The SystemVerilog type chandle was included so that DPI tasks and functions could use C pointers. The
Interface
The Direct Programming
values of variables of type chandle are meaningless in SystemVerilog, but could be used to pass a pointer
from one DPI function to another. For example, an imported C function could call the library function
malloc to allocate some memory and return a pointer to the memory. The pointer would be stored in a
SystemVerilog in a variable of type chandle. The pointer could then be passed to another imported C
function.
Scalar (one-bit) values of bit and logic or reg are passed as unsigned char – this is explained on the next
slide. Packed arrays of these types are passed in a different way, which will be explained later.
Exporting a Function to C
module foo;
export "DPI-C" function swap; Just the name, no prototype
endfunction
...
#include "svdpi.h"
char test_swap (char c) {
return swap(c);
}
A DPI import function is one that is written in C and called from SystemVerilog; a DPI export function is
written in SystemVerilog and called from C. A function exported like this would only be called from a C
function that was imported into SystemVerilog, or from a C function that was, directly or indirectly, called
from an imported function.
The syntax for exporting a function is the keyword export, the string "DPI-C" and the name of the exported
13
function. Optionally, the name of the C function may be provided too (C identifier). There is not a function
Interface
The Direct Programming
prototype in a DPI export declaration. In addition the function must be defined in SystemVerilog in the
usual way. The names of the function’s arguments and the types of the arguments and the function’s
return value match as before.
Need a context function to allow the imported function to call an exported function
The SystemVerilog type logic [7:0] maps to the C type svLogicVecVal*, which, although part of the
SystemVerilog language standard, has a simulator-specific representation.
13 When the logic [7:0] value is passed from SystemVerilog to C and back to SystemVerilog, the original
SystemVerilog value is guaranteed to be recovered. Such transparency is a general and deliberate
Interface
The Direct Programming
feature of the DPI: the actual implementation of the DPI call should be transparent from the point-of-view
of the SystemVerilog code.
() required SystemVerilog
initial
c_task(8'b01xzzx10);
We have been talking about importing and exporting functions. It is also possible to import C functions as
SystemVerilog tasks and to export SystemVerilog tasks.
The C programming language only has functions; there no direct equivalent in C to tasks in
SystemVerilog. Now you might expect that an imported or exported SystemVerilog task would correspond
to a void function in C. This is not so: the C function must return an int. The reason for this will be
13
explained later. For now, just accept that the C function usually returns the int 0.
Interface
The Direct Programming
Imported and exported tasks and functions may have zero, one or more arguments. If there are no
arguments, the parentheses are still required in a DPI import declaration, even though they would not be
required if the task or function were written in SystemVerilog.
When creating a "sandwich" like this where SystemVerilog calls back to SystemVerilog through a DPI
layer, the SystemVerilog task may consume time (or interact with the SystemVerilog scheduler in any
other way it likes).
Scalar values of bit and logic (or reg) are imported and exported as type svLogic, which is defined as
unsigned char in the header file svdpi.h. This header file is defined in the SystemVerilog LRM, and should
be included in most DPI functions.
13 svdpi.h defines the macros (#define – C pre-processor constants) sv_0, sv_1, sv_z and sv_x
corresponding to SystemVerilog logic values.
Interface
The Direct Programming
Packed Arrays
• Packed arrays (logic or bit) are passed using the canonical representation
0 1 X Z
word 1 avalue 0 0 1 1 0
word 2 bvalue 0 0 0 1 1
31 3 2 1 0
Packed arrays of type bit or logic are passed using a canonical representation, which is identical to that
used to represent four-state values in the VPI (s_vpi_vecval).
Interface
The Direct Programming
32 bits. The first element of the array represents the 32 least significant bits. The type of the array is
svBitVecVal for type bit and svLogicVecVal for types logic and reg. This type is composed of two 32-bit
integers, avalue and bvalue. Each SystemVerilog bit is represented by a pair of bits as shown in the slide.
The DPI provides a number of library functions to help you work with the canonical representation; these
functions are declared in svdpi.h. For example, there are functions to perform bit- and part-select
operations on variables of type svBitVecVal and svLogicVecVal. You will find full details in the
SystemVerilog LRM.
The canonical representation of packed arrays was introduced in the P1800 version of SystemVerilog.
Version 3.1a used a different representation. The string "DPI-C" in an import or export declaration means
that this canonical representation is being used.
You can access and interpret the bits of the SystemVerilog canonical representation from the C code, but
beware that different simulators use different field names in the structs.
13
Interface
The Direct Programming
String Arguments
import "DPI-C" function string c_func (
input string is, output string os, inout string ios);
string a = "a_string";
string b = "b_string";
initial
$display( c_func("abc", a, b) );
SystemVerilog strings are represented in C using const char *. (i.e. a pointer to const char)
String inputs are passed by value, so the corresponding type for the SystemVerilog function input s_in is
const char * in C. Similarly, the C return type is also const char *.
String outputs and inouts are passed by reference, so the C type for s_out and s_inout is const char **
13
(i.e. a pointer to a pointer to a char. Again it is the pointer itself that is const, not the string being pointed
Interface
The Direct Programming
to.)
The C string corresponding to a function output is undefined when the function is called.
#include "svdpi.h"
void c_func (const svOpenArrayHandle h) {
int i;
for (i = svLow(h,1); i <= svHigh(h,1); i++) {
svLogicVecVal* ptr = (svLogicVecVal*)svGetArrElemPtr1(h,i);
int data_in;
data_in = (int)(ptr->aval); // Questa & VCS
data_in = (int)(ptr->a); // IUS
}
}
Imported function arguments may use open arrays. These are arrays that have the ranges of one or more
dimensions specified using the syntax []. This syntax enables the same C function to be called with
different sizes of SystemVerilog array associated with the same argument. Packed and unpacked
dimensions may be open.
13 Open arrays are like multidimensional dynamic arrays, which is why they use the same syntax as
dynamic arrays. Dynamic arrays are in fact passed to DPI functions using arguments that are open
Interface
The Direct Programming
arrays.
Open array type arguments are passed by handle – the corresponding C type is svOpenArrayHandle. The
C header file svdpi.h includes declarations of open array querying functions and functions that you can
use to access the values. Full details can be found in the SystemVerilog LRM.
Note that, unlike other array dimensions, the ranges corresponding to open array dimensions are not
normalised in C. For example, the C code would access the first element of array two (using its handle)
with the index 1, not 0.
int itask () {
initial fork
int wasDisabled;
itask; if ( wasDisabled = etask() )
#7 disable itask; svAckDisabledState();
join return wasDisabled;
}
SystemVerilog tasks do not have a return value, so you might expect C functions corresponding to
imported tasks to return void. This is not the case – they must return int. This is to support the possibility
that the imported task might be disabled.
Suppose an imported task calls an exported task. As the exported task is written using SystemVerilog
statements, it may include delays or other timing controls. If so, the imported task may block when it is
13
called. What happens if the imported task is disabled?
Interface
The Direct Programming
The answers is that the corresponding C function returns immediately with a value indicating that it was
disabled. This is illustrated in the next slide.
The imported task itask is declared with the keyword context. This is required if the corresponding C
function is going to call an exported task. The precise meaning of context will be explained later.
3 Waits ...
task etask; #10; endtask
2 Call etask
int itask () {
etask();
if ( svIsDisabledState() )
svAckDisableState();
return svIsDisabledState() ;
} 5 etask returns
initial fork
1
itask; Call itask 6 itask acknowledges
#7 disable itask; any disable
4 … disable!
join 7 itask returns
This shows, step-by-step, what happens when an imported task is disabled. It also shows an alternative
(and preferred) way to check whether a task was disabled.
The imported task, itask, is called from SystemVerilog. The simulator calls the corresponding C function.
13 The C function calls the exported task, etask. The simulator calls the corresponding SystemVerilog task.
Interface
The Direct Programming
The SystemVerilog task, etask encounters a delay, #10, and starts waiting.
As a result, the simulator stops executing etask and the C function etask returns. The disabled status of
that task is available through the DPI function svIsDisabledState(). Normally this function returns zero,
but if it returns 1 the caller (C function itask) knows that etask returned due to a disable. The simulator is
now in the disabled state.
Before returning, itask must acknowledge the disable. It does this by calling the function
svAckDisableState.
itask now returns the value of svIsDisabledState() and control returns to the caller.
Whilst in the disabled state, a function may not call exported tasks or functions.
We have seen that imported tasks that call exported tasks must be declared with the keyword context.
Imported tasks and functions may be declared context, pure or neither context nor pure (the default).
context
A context task or function is allowed to call exported tasks and functions and to access SystemVerilog
data objects (other than its arguments) by calling the PLI or VPI. The context of a SystemVerilog task or
13
function is the hierarchical scope in which it was declared (not called). The context of an exported task or
Interface
The Direct Programming
function called from an imported task or function is the context of the import declaration and not that of the
export declaration.
Special DPI utility routines exist that allow imported tasks and functions to find out and change their scope
(context) and to operate on it.
Imported tasks and functions should only be declared context if they need to be, otherwise simulation
performance may be degraded unnecessarily.
pure
A pure function is one whose return value depends only on the values of its inputs and has no side-
effects. This means it must not perform any file operations, it must not read or write anything (environment
variables, shared memory, …), and it must not access global or static variables. Simulators can optimise
calls to pure functions, because they can make assumptions about their behaviour.
13
Interface
The Direct Programming
Index
$bits 162, 163 Base type 78
$dimensions 160, 161 begin...end 18
$error 22 BFM 128
$fatal 22 bins 151
$high 160, 161 bit 7
$increment 161 Bit-stream casting 163
$info 22 break 14, 20
$left 161 Bus functional model 128
$low 161 byte 30
$monitor 105 C Programming Language 171
$random 137 C++ 171
$right 161 Canonical representation 181
$root 154 case inside 72
$sformat 28 Cast 35, 163
$sformatf 28 chandle 176
$signed 33 char 30
$size 160, 161 clocking
$strobe 105 modport 110
$unit 154 Clocking block 95
$unsigned 33 Multiple clocking 108
$urandom 137 nets 109
$urandom_range 137 Signal alias 107
$warning 22 skew 98
%p 121 Skew 103
[$] 118 Clocking drive 101
==? 71 clockvar 98
Active region 105 Combinational logic 64
Alias (Clocking) 107 context (DPI) 185, 187
always_comb
always_ff
always_latch
14, 66
65, 66
66
continue
covergroup
instantiation using new
14, 20
150
150
14
and 167 coverpoint 150
Index
APB 44 cross 152
Argument 24 cross coverage 152
pass by name 24 Cycle delay 102
Array 39 Data type
associative 123 2-state 30
dynamic 115 bit 30
literal 123 byte 30
Array literal 156 enum 34
Array Manipulation Methods 164 int 30
Array querying functions 160 logic 30
Arrays packed 38
DPI 184 signed 30
assert 21 struct 36
Assignment pattern 36, 37, 121, 123, 156, 158 typedef 34
Associative array 123 Data Types
at_least 151 Synthesis 76
automatic 15, 16 default 157
AVM 11 Default value 31
Index
Replication 158 unique_index 165
return 23, 26 Unpacked array 39
reverse 166 unsigned 32, 33
rsort 166 URM 11
RVM 11 UVM 11
Scheduler regions 104, 105 var 8
Scope (DPI) 187 Verilog 5
self 140 VHDL 5
set_randstate 140 VMM 11
shortint 30 void 25
shuffle 166 VPI 187
signed 32, 33 wasDisabled 186
size 124 while 20
array 115 Wild equality 71
queue 120 Wildcard port connection 8
Skew (Clocking) 103 wire 7, 8
sort 166 with 164
srandom 140 xor 167
14
Index
14
Index