System Verilog Basics
System Verilog Basics
Jean-Michel Chabloz
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)
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
bit [9:0][7:0] b:
b[5] <= 15;
packed structures
equivalent to a packed array subdivided into named fields:
example: 48 bit packed array
struct packed { int a; bit [7:0] c; bit [7:0] d; } pack1;
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);
Types in SV
SystemVerilog is a weakly-typed language
advantages: simpler and shorter code disadvantages: easy to do mistakes
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; posedge: positive edge a <= 1b1; #20ns; negedge: negative edge a <= 1b0; signal name: any toggle end
Procedural code
if (a==1) begin //code end if (a) begin // 1 counts as true //code end
for (i=0; i<3; i++) begin // loops three times //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
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 allzeros)
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 .
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
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
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
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 simulatordependent 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
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;
Variables vs Nets
Variables can be assigned:
in a procedural assignment (blocking or nonblocking assignment inside an initial or always process) By a single continuous assignment
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
X Z
0
1
0
X
X X
1 X
0
1
X X X X X
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
Packages
type definitions, functions, etc. can be defined in packages
package ComplexPkg; typedef struct { shortreal i, r; } Complex; function Complex add(Complex a, add.r = a.r + b.r; add.i = a.i + b.i; endfunction function Complex mul(Complex a, mul.r = (a.r * b.r) - (a.i * mul.i = (a.r * b.i) + (a.i * endfunction endpackage
b);
Packages
Stuff that is in package can be called as:
c <= PackageName::FunctionName(a,b)
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 [*];
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
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
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
Parameters
used to express configurability
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
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
Parameters
used to express configurability
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
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
Parameters
Positional instantiation
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
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
Parameters
Other notation
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 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
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
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
clk
testbench program drive DUT inputs, check DUT outputs DUT