You are on page 1of 82

SystemVerilog basics

Jean-Michel Chabloz
How we study SystemVerilog
Huge language:
last LRM has 1315 pages
Not possible to cover everything, we cover
maybe 5% of the constructs
You can succeed in the course using only the
subset of the language that is treated in these
slides
If you want you are free to use other
constructs, research them by yourself

SystemVerilog Hello World
module M();

initial
$display(Hello world);

endmodule
SystemVerilog simple program
module M();
logic a,b;
logic [7:0] c;

assign b = ~a;

initial begin
a <= 0;
#20ns;
repeat(40)
#5ns a <= ~a;
#20ns $display(c);
$finish();
end

initial
c <= 0;

always @(posedge a)
c <= c + 1;

endmodule
SystemVerilog syntax
Case sensitive
C-style comments: // or /*comment*/
Code blocks delimited by begin end. If
a single-line, can be omitted
Modules
Lets start to consider systems without
hierarchy (no submodules)
A module contains objects declarations
and concurrent processes that operate in
parallel.
Initial blocks
Always blocks
Continuous assignments
(instantiations of submodules)
SystemVerilog basic data types
Data types:
logic 4 valued data type:
0, 1, X, Z
initialized to X
can also be called reg // deprecated verilog legacy name
bit 2 valued data type:
0, 1
initialized to 0
Defining a data type:
bit a;
logic b;
bit c, d;


Packed arrays of logic and bits
bit [5:0] a;
logic [2:0] b;
logic [0:2047] [7:0] c; //array of 2048 bytes
integer: equivalent to logic [31:0]
int, byte: equivalents to bit [31:0] and bit [7:0]
arrays of bits and logics default to unsigned,
can be overriden with the keyword signed
ex: bit signed [7:0] a;


Literals
Decimal literal:
a <= 54; // automatically extended to the length of a with 0
padding
a <= d54;
a <= 12d54; // specifies that a is 12-bits wide
Unspecified length:
1, 0, x, z // fills with all 1s, 0s, xs, zs
binary literal
12b1000_1100_1110 // underscores can be put anywhere
except the beginning of a literal
b11011 // automatically resized with zeroes if fed to something
bigger
hexadecimal literal:
12hc; // 000000001100
hcd: // .0000011001101
Packed array access
Single element access:
bit [7:0] a
a[5] <= a[6];
bit [9:0][7:0] b:
b[5] <= 15;
Packed arrays can be sliced:
bit [7:0] a;
a[3:2] <= 2b10;
a[3:0] <= a[7:4];
bit [2047:0][7:0] a; bit [1:0][7:0] b;
a[2047:2046] <= b

packed structures
equivalent to a packed array subdivided into named
fields:
example: 48 bit packed array






can be accessed as pack1[15:0] <= b0;
can access pack1[9:4] <= 15;
can be accessed as pack1.d <= b0;
the whole struct can be resetted with pack1 <= b0;
unpacked struct (no packed keyword) allow only acces
through the named fields (pack1.d <=b0);

struct packed {
int a;
bit [7:0] c;
bit [7:0] d;
} pack1;
Other data types
Enumerated data type:
enum bit [1:0] {idle, writing, reading} state
If skipping the type an int type is assumed
Can be typedeffed (like all other data types):
typedef enum {red, green, blue, yellow, white,
black} Colors;
Colors [2:0] setOfColors; // array of 3 elements of
type colors





Types in SV
SystemVerilog is a weakly-typed language
advantages: simpler and shorter code
disadvantages: easy to do mistakes
Many assignment-compatible types
a bit and a logic are assignment-compatible, we can assign one
to the other
a longer array can be assigned to a shorter one or viceversa
(truncation or extension will happen automatically)
arrays can be indexed by logic arrays, bit arrays
a packed struct has the same properties as an array
struct packed {bit[3:0] a, b;} can be assigned a bit array, a logic
array, etc.
ifs, whiles, etc. can take as condition bits, logic, arrays,
etc.
non-zero values count as TRUE, all-zero values count as false
if a is a bit or logic, then we can write if (a==1) or if (a), they do
the same thing
Processes
Modules contain processes (initial, always)
code inside processes is called procedural
code
Initial: executed only once, at the beginning of the
simulation

initial begin
#10ns;
a <= 1b1;
#20ns;
a <= 1b0;
end
Processes
Always - no sensitivity list: triggers as soon as
it finishes executing

always begin
#10ns;
a <= 1b1;
#20ns;
a <= 1b0;
end
Processes
Always - with sensitivity list: triggers when it
has finished executing and one of the events
in the sensitivity list happens

always @(posedge b, negedge c) begin
#10ns;
a <= 1b1;
#20ns;
a <= 1b0;
end
posedge: positive edge
negedge: negative edge
signal name: any toggle
Always block, combinational
process
The sensitivity list must contain all elements in
the right-hand side
always @(a,b) begin
c <= a+b;
end
SystemVerilog allows using always_comb
instead
the sensitivity list is automatically compiled
always_comb begin
c <= a+b;
end

Always block, flip-flop
D flip-flop with async reset:




Possible to specify always_ff to declare intent
we declare to the compiler we want to do a FF (in the sense
of edge-triggered logic, can also be an FSM), if it is not an FF
we get an error

always @(posedge clk, negedge rst) begin
if (rst==0)
q <= 0;
else // posedge clk, rst==1
q <= d;
end
always_ff @(posedge clk, negedge rst) begin
if (rst==0)
q <= 0;
else // posedge clk, rst==1
q <= d;
end
Procedural code
if (a==1) begin
//code
end
while (a==1) begin
//code
end
forever begin // loops forever
//code
end
for (i=0; i<3; i++) begin // loops three times
//code
end
if (a) begin // 1 counts as true
//code
end
repeat (3) begin
//code
end
if trees
if (condition) begin

end
else begin

end
if trees
if (condition1) begin

end
else if (condition2) begin

end
else if (condition3) begin

end
else begin

end

No elsif construct, but this is equivalent
Bitwise logic operators
Bitwise logic operators return a number of bits
equal to the length of the inputs:
&: and
| : or
^ : xor
~ : not
Negate one bit/logic array:
a <= ~a
Do a bit-wise OR between two bit/logic arrays:
c <= a | b
logic operators
logic operators return one bit only, treat as one
everything that is non-zero:
&&: and
| |: or
! : not
for one-bit elements if (!a) is equal to if (~a)
for a 4-bit elements, if a=1100
if(!a) will not execute (!a returns 0)
if(~a) will execute (~a returns 0011 which is not all-
zeros)
comparisons
equality: ==
diseguality: !=
greather than: >
lower than: <
greater or equal than: >=
lower or equal than: <=
arithmetic
+ and can be used with logic arrays, bit arrays,
automatically wrap around:
up counter:
.
11101
11110
11111
00000
00001
00010
.
Timing Control in Processes
#10ns: waits for 10 ns
#10: wait for 10 time units time unit specified during elaboration or
with a `timescale directive in the code
#(a): wait for a number of time units equal to the value of variable a
#(a*1ps): wait for a number of picoseconds equal to the value of a

@(posedge a): waits for the positive edge of a
@(b): wait until b toggles

wait(expr): waits until expr is true
wait(b): wait until b is one

Timing checks can be bundled with the next instr: #10ns a<=!a
fork join
Spawn concurrent processes from a single process: A is printed at
30ns; B at 20ns; join waits until both subprocesses have finished,
the last display takes place at 40ns

initial begin
#10ns;
fork
begin
#20ns;
$display( A\n" );
end
begin
#10ns;
$display( B\n" );
#20ns;
end
join
$display(both finished);
end


Procedural assignments
Non-blocking assignment
<=
takes place after a delta delay
Blocking assignment
=
takes place immediately
The two can be mixed but probably not a
good idea

Procedural assignments
blocking assignments correspond to the VHDL
variable assignment :=
non-blocking assignments correspond to the
VHDL signals assignment <=
BUT:
In VHDL := is reserved for variables, <= for signals
In Verilog, both can be used for variables
Possible to mix them - but probably not a good idea
A better idea is to use some objects as VHDL
variables and only assign them with =, others as
VHDL signals and only assign them with <=
Procedural assignments
always @(posedge clk) begin
a <= b;
b <= a;
end
always @(posedge clk) begin
a = b;
b = a;
end
Procedural assignments
initial begin
a = 1;
$display(a);
end
initial begin
a <= 1;
$display(a);
end
initial begin
a <= 1;
#10ns;
$display(a);
end
Default assignments
default values to avoid latches and to avoid writing long if
else trees
works like in VHDL (the last write is kept)

always_comb
a <= 0; // default value of a

if (c)
if (b==100)
a <= 1;
end
Console/control commands
introduced with the $ keyword
$display used to display information to the
console
ex:
$display(hello); // displays hello
$display(a); // displays the value of a, depending
on its data type
$stop(), $finish(): stop (break) and finish
(terminate) the simulation
Direct generation of random
numbers
$urandom returns a 32-bit random unsigned
number every time it is called
Can be automatically assigned to shorter values,
automatic clipping will take place:
bit a; a <= $urandom;
To generate a random number between 0 and
59 we can use: $urandom%60 (modulus)
Note: if not seeded, every time the testbench is
run we get the same values
this behavior is required for being able to repeat tests
Direct generation of random
numbers
$urandom_range(10,0) returns a random
number between 10 and 0
Note: if not seeded, every time the
testbench is run we get the same values
this behavior is required for being able to
repeat tests
Event-driven simulation
The simulator executes in a random order any of
the operations scheduled for a given timestep.
It continues until the event queue for the
timestep is empty, then advances time to the
next non-empty timestamp

This might create race conditions:
What happens is not defined by the rules of
SystemVerilog
No error is signaled
The behavior of the system might be simulator-
dependent or even change from run to run
Race condition
initial begin
#10ns;
a = 1;
end

initial begin
#10 ns;
a = 0;
end

initial begin
#20 ns;
$display(a);
end
Race condition
initial begin
#10ns;
a <= 1;
end

initial begin
#10 ns;
a <= 0;
end

initial begin
#20 ns;
$display(a);
end
Race condition
initial begin
#10ns;
a <= 1;
end

initial begin
#10 ns;
a = 0;
end

initial begin
#20 ns;
$display(a);
end
Race condition
initial begin
#10ns;
a = 1;
a = 0;
end

initial begin
#20 ns;
$display(a);
end
Race conditions
Happen when two different processes try
to write the same signal during the same
time step
Ways to avoid:
dont write the same signal in different
processes, unless you really know what you
do (you know that the two processes will
never write the signal in the same time step)
Continuous assignments
continuously performs an assignment
outside procedural code
ex: assign a = b+c;
Note: module input/output ports count as
continuous assignments
can be done on variables or nets
nets can be driven by multiple continuous
assignments, variables no
Variables vs Nets
Variables:
Are defined as: var type name
Example: var logic a (logic is default, can be
omitted)
The keyword var is the default, it can be
omitted
So when we define something like
logic a;
bit [7:0] b;
we are actually defining variables

Variables vs Nets
Variables can be assigned:
in a procedural assignment (blocking or non-
blocking assignment inside an initial or always
process)
By a single continuous assignment
How variables work
initial
#10ns a <= 1;

initial
#20ns a <= 0;

a variable keeps the newest value that is written
to it

VARIABLES HAVE NOTHING TO DO WITH
VHDL VARIABLES

Variables vs Nets
Nets:
Different types: wire, wand, wor, etc. We consider
only wire
Are defined as: wire type name
Examples: wire logic a, wire logic [2:0] c
logic is the default and can be omitted
A wire cannot be a 2-valued data type
A net can be assigned only by one or more
continuous assignments, cannot be assigned
into procedural code
Variables vs Nets
So there is only one thing
in SV that nets can do
and that variables cannot:
be driven by multiple
continuous assignments
Nets should be used
when modeling tri-state
buffers and buses
The value is determined
by a resolution function
0 1 X Z
0 0 X X 0
1 X 1 X 1
X X X X X
Z 0 1 X Z
Objects scope
objects declared inside
modules/programs:
local to that module/program
objects declared inside blocks (ifs, loops,
etc.) between a begin and an end:
local to that block of code
Subroutines
Functions: return a value, cannot consume
time
Tasks: no return, can consume time
Functions
input/ouput/inout ports (inouts are read at the beginning and written
at the end)
the keyword input is the default, no need to specify it.
cannot consume time
a function can return void (no return value)
It is allowed to have non-blocking assignments, writes to clocking
drivers and other constructs that schedule assignments for the
future but dont delay the function execution

function logic myfunc3(input int a, output int b);
b = a + 1;
return (a==1000);
endfunction
Tasks
task light (output color, input [31:0] tics);
repeat (tics)
@ (posedge clock);
color = off; // turn light off.
endtask: light

Tasks can consume time, they do not return
values
Packages
type definitions, functions, etc. can be defined in packages

package ComplexPkg;
typedef struct {
shortreal i, r;
} Complex;
function Complex add(Complex a, b);
add.r = a.r + b.r;
add.i = a.i + b.i;
endfunction
function Complex mul(Complex a, b);
mul.r = (a.r * b.r) - (a.i * b.i);
mul.i = (a.r * b.i) + (a.i * b.r);
endfunction
endpackage
Packages
Stuff that is in package can be called as:
c <= PackageName::FunctionName(a,b)
or
the package can be imported, then we can just
write:
c <= FunctionName(a,b);

Importing a package is done through:
import PackageName::*;
Unpacked arrays
bit a [5:0];
Arrays can have multiple unpacked
dimensions or can even mix packed and
unpacked dimensions:
logic [7:0] c [0:2047]; // array of 2048 bytes
Unpacked dimensions cannot be sliced, only
single elements can be accessed
They do not reside in memory in contiguous
locations they can be bigger than a packed
array because of this reason


dynamic arrays
arrays with an unpacked dimension that is
not specified in the code:
logic [7:0] b [];
Can only be used after having been
initialized with the keyword new
b = new[100];
Can be resized with: b = new[200](b);
After having been initialized it can be used
like any other array with unpacked
dimension
associative arrays
associative array of bytes:
declared as logic [7:0] a [*];
Acts exactly as a vector of 2^32 bytes:
a[4102345432] <= 8b10000110 is legal
Memory space is allocated only when used
Slow to access the elements in terms of
simulation time
If we would try to write to all locations we would
crash everything or generate an error
Ideal to model big memories used only sparsely
queues
logic [7:0] q[$];
Supports all operations that can be done on
unpacked arrays
q.push_front(a); // pushes element to the front
q.push_back(a); // pushes element to the back
b=q.pop_back(); // pops element from back
b=q.pop_front(); // pops element from front
q.insert(3,a) // inserts element at position 3
q.delete(3) // deletes the third element
q.delete() // delete all the queue
q.size() // returns size of queue
queues
Can also be accessed using slicing and $:
q = { q, 6 }; // q.push_back(6)
q = { e, q }; // q.push_front(e)
q = q[1:$]; // q.pop_front() or q.delete(0)
q = q[0:$-1]; // q.pop_back() or q.delete(q.size-1)
q = { q[0:pos-1], e, q[pos:$] }; // q.insert(pos, e)
q = { q[0:pos], e, q[pos+1:$] }; // q.insert(pos+1, e)
q = {}; // q.delete()
Structure
Hierarchy is encapsulated and hidden in
modules
module dut (
output bit c,
input bit [7:0] a,
input bit [7:0] b);

// module code (processes, continuos assignments,
instantiations of submodules)
endmodule

There exists legacy verilog port declaration
methods
Structure
Verilog legacy port declarations

module test(a,b,c);
input logic [7:0] a;
input b; //unspecified type: logic
output bit [7:0] c;

endmodule
Structure
Module declaration with ports

module simple_fifo (
input bit clk,
input bit rst,
input bit [7:0] a,
output bit [7:0] b);

// module code (processes, continuous
assignments, instantiations of submodules)

endmodule
Structure
Module instantiation in a top-level testbench
module tb (); // top-level testbench has no inputs/outputs
bit clk, reset;
bit [7:0] av, bv;

simple_fifo dut(.clk(clk), // module instantiation
.rst(reset),
.b(bv),
.a(av));

always
#5ns clk <= !clk;

initial
#30ns reset <= 1;

initial begin
forever
#10ns av <= $random();
end

endmodule
Module instantiation
Module instantiation in a top-level testbench
Ports can be named out of order

module tb ();
bit clk, reset;
bit [7:0] av, bv;

simple_fifo dut(.clk(clk), // module instantiation
.rst(reset),
.a(av),
.b(bv));

always
#5ns clk <= !clk;

initial
#30ns reset <= 1;

initial begin
forever
#10ns av <= $random();
end

endmodule
Module Instantiation
If signals have the same names in the including and the
included modules we can use the syntax (.*) for port
connection.

module tb ();
bit clk, rst;
bit [7:0] a, b;

simple_fifo dut(.*); // a->a; b->b; clk->clk; rst-> rst

always
#5ns clk <= !clk;

initial
#30ns rst <= 1;

initial begin
forever
#10ns a <= $random();
end

endmodule
Module Instantiation
positional connection, each signal is connected
to the port in the same position easy to make
errors

module tb (); // top-level testbench has no inputs/outputs
bit clk, reset;
bit [7:0] av, bv;

simple_fifo dut(clk,reset,av,bv);

always
#5ns clk <= !clk;

initial
#30ns rst <= 1;

initial begin
forever
#10ns a <= $random();
end

endmodule

Module instantiation

module tb ();
bit clk, reset;
bit [7:0] av, bv;

simple_fifo dut(.*, // ports are connected to signals with the same name
.a(av), // except the ones named later
.b()); // b is left open

always
#5ns clk <= !clk;

initial
#30ns reset <= 1;

initial begin
forever
#10ns av <= $random();
end

endmodule
Parameters
used to express configurability
module tb (); // top-level testbench
has no inputs/outputs
bit clk, rst;
bit [7:0] a, b;

simple_fifo #(
.DEPTH(64),
.WIDTH(8))
dut (
.clk(clk),
.rst(rst),
.a(a),
.b(b));



endmodule
module #(
parameter DEPTH=64,
parameter WIDTH=8
)
simple_fifo (
input logic clk,
input logic rst,
input logic [WIDTH-1:0] a,
output logic [WIDTH-1:0] b
);

localparam internal_param_name;

endmodule
Parameters
used to express configurability
module tb (); // top-level testbench
has no inputs/outputs
bit clk, rst;
bit [7:0] a, b;

simple_fifo #(
.DEPTH(64),
.WIDTH(8))
dut (
.clk(clk),
.rst(rst),
.a(a),
.b(b));



endmodule
module #(
parameter DEPTH=64,
parameter WIDTH=8
)
simple_fifo (
input logic clk,
input logic rst,
input logic [WIDTH-1:0] a,
output logic [WIDTH-1:0] b
);

localparam internal_param_name;

endmodule
Parameters
Positional instantiation
module tb (); // top-level testbench
has no inputs/outputs
bit clk, rst;
bit [7:0] a, b;

simple_fifo #(64,8)
dut (
.clk(clk),
.rst(rst),
.a(a),
.b(b));



endmodule
module #(
parameter DEPTH=64,
parameter WIDTH=8
)
simple_fifo (
input logic clk,
input logic rst,
input logic [WIDTH-1:0] a,
output logic [WIDTH-1:0] b
);

localparam internal_param_name;

endmodule
Parameters
Other notation
module tb (); // top-level testbench
has no inputs/outputs
bit clk, rst;
bit [7:0] a, b;

simple_fifo #(
.DEPTH(64),
.WIDTH(8))
dut (
.clk(clk),
.rst(rst),
.a(a),
.b(b));



endmodule
module simple_fifo (
input logic clk,
input logic rst,
input logic [WIDTH-1:0] a,
output logic [WIDTH-1:0] b
);
parameter DEPTH=64
parameter WIDTH=8
localparam internal_param_name;

endmodule
Parameters
If not overwritten, they keep their default value
Can have a type:
parameter logic [7:0] DEPTH = 64;
localparam: like parameters, but cannot be
modified hierarchically during the instantiation
Used to indicate a parameter that there is not
any sense for it to be modified by some higher
block in the hierarchy
Hierarchical access
From anywhere, it is possible to access any
object in the hierarchy by accessing through its
full path starting from the top module name:

a <= top.dut.subDutUnit.intObjectName;

The testbench can monitor any internal DUT
signal (white/grey box verification) without
having the signal forwarded through ports
SystemVerilog Programs
Programs are and look like modules, but:
They cannot contain always blocks
They cannot include modules
Simulation finishes automatically when all
initial have been completed
Always blocks are the basic building block
of RTL code, not needed by the
testbenches
Programs can do everything that is
needed for verification
Clocking block
default clocking ck1 @(posedge clk);
default input #1ns output #1ns; // reading and writing skew
input a; // a, b, objects visible from this scope
output b; // input: can read; output: can write
output negedge rst; // overwrite the default skew to the
negedge
// of the clock
inout c; // inout: can both read and write
input d = top.dut.internal_dut_signal_name;
endclocking

Inside a module/program, we access signals for read/write inside processes in this
way:
ck1.a <= 1b1;
c = ck1.b; // or c <= ck1.b;
ck1.d <= e;

A write will take place 1ns after the clock edge, a read will read the value that the
signal had 1ns before the clock edge


Clocking block
The clocking block makes the timing
relation (positive-edge of the clock or other
explicit)
Since we only verify synchronous systems
with a single clock, we need a single
clocking block
We add only one and specify it as default
It gives a input and output skew to
read/write to the signals
The input/output skew can also be omitted

Clocking blocks and programs
The rules of SV say that if we access signals from a program
through a clocking block, there will be no race condition between
testbench and DUT
Even if no timing skew is specified
When there is a default clocking block in a program/module we can
use the ##n timing construct: wait n cycles as specified by the
default clocking block
examples:
##3; // wait 3 cycles
##(2*a); //wait a number of cycles equal to the double of the value of
variable a
initial begin
##3 reset <= 0;
forever begin
##1 a <= ~a;
end
end
##1 executed at a time instant in which there is no clock edge will
delay by a fraction of the clock cycle (wait until the first clock edge
only)
generate
Used to generate processes and continuous assignments
No need of generate endgenerate statements
Define a variable of type genvar
We can then do a generate using a loop or if with with the genvar

Example:
genvar i;

initial ##20 b <= 0;

for(i=0;i<3;i++) begin
initial begin ##(i) a <= $urandom; end
if (i==1) begin
always @(posedge clk) begin

end
end
end
named blocks
after every begin there can be an optional name
Allows hierarchical access to local variables and
to find the variables in the simulator window

ex:
initial begin : inputController

if (a==10) begin : terminationHandler

end
end
Good Testbench Structure
Write all your
testbenches in this
way
The testbench
program must use
access all DUT
signals in read and
write through the
default clocking
block
Only timing
construct allowed
in the testbench
program: ##


top module
generation of clock
testbench
program

drive
DUT inputs,

check
DUT outputs
DUT
clk
Good Testbench Structure
Alternative: several programs are allowed
all use clocking block in links to the DUT


top module
generation of clock
testbench
program

drive inputs
DUT
clk
testbench
program

check outputs
Good testbench structure
You can use any structure you like, everything is allowed
The one below is more UVM-like
top module
generation of clock
testbench
program
drive some inputs
DUT
clk
testbench
program
collect outputs
translate them into
a higher level
model
testbench
program
drive other inputs
program
check outputs
Good testbench structure
You can use multiple programs, but given the
size of the testbenches used in this course, one
program that does everything is a good choice
All inputs/outputs to the DUT through the default
clocking block
Only timing construct allowed: ## (no need for
other levels of granularity)
Try to keep separated the different functions
(input generation, output checking) using several
initial blocks

You might also like