System Tasks
System tasks are built-in commands for simulation and system
functions such as printing messages, reading and writing files,
monitoring and displaying simulation time and the signal
values at a specific simulation time. All system task keywords
start with $.
The Verilog standard permits EDA tool vendors to define
system tasks unique to their particular tool using the
Programming Language Interface (PLI).
To maintain portability between EDA tools, it is
recommended to use only the standard system tasks defined as
part of the Verilog language.
Some of the more common system tasks are listed below:
$display $write $stop $finish
$fopen $fclose $fwrite $monitor
$display is most useful for displaying values of variables or
strings or expressions. It has the following syntax.
$display(arg1, arg2, ..., argN);
arg1, arg2, ..., argN can be a mixture of strings, variables or
expressions. A $display inserts a newline at the end of the
string. A $display without any arguments produces a newline.
$write is similar to $display except that $write does not put a
newline at the end of a string by default.
$monitor has the following syntax.
$monitor(arg1, arg2, ..., argN);
arg1, arg2, ..., argN can be variables, signal names or strings.
$monitor is used to print the values of the variable or signal
as they change.
Only one monitoring list can be active at a time. If there is
more than one $monitor in your code, the last $monitor will
be the active one.
Monitoring can be turned on or off during simulation using
$monitoron and $monitoroff respectively. Monitoring is
turned on by default at the beginning of the simulation.
$stop causes the simulation to suspend temporarily. The
designer can then examine the values of signals or debug the
design in an interactive mode.
$finish terminates the simulation.
To open a file, we write the following:
file_handle = $fopen(“filename”);
Each successive call to $fopen returns a 32-bit value (called
multichannel descriptor). A bit is set in the descriptor to
represent a file that has been opened. As files are opened, bit 1
is set, bit 2 is set, and so on, up to bit 30 set, respectively.
$fopen returns a 0 if the file could not opened.
$fdisplay , $fmonitor, $fwrite, and $fstrobe are used to write
to files. For example, $fdisplay has the following syntax.
$fdisplay(file_descriptor, arg1, arg2, ..., argN);
arg1, arg2, ..., argN can be variables, signal names or strings.
The file_descriptor can be a file handle or a bitwise
combination of file handles (to write to more than one file at
the same time). Verilog will write the output to files that have
a 1 set in the bit position of the file descriptor.
The command to close a file is $fclose(file_handler);
Behavioral Modelling of Hardware
Behavioural models describe functionality of a hardware
system in an algorithmic manner independent of technology.
The models describe the functional relationship between the
inputs and outputs of a logic circuit, but do not model the
physical properties such as propagation delays (which depend
on the technology that produced the hardware).
Design work is more effective through the use of behavioral
models, and a synthesis tool can then be used to develop the
physical implementation, rather than design at the gate level.
A behavioral or algorithmic model therefore looks like a
computer program that describes the behavior of the system.
Verilog provides two different control structures to allow
designers to describe model behavior in a computer-program
manner:
• case statement.
• if statement (also known as conditional statement).
if Statement
There are three types of if statement.
Example: Type 1 if statement.
// If the expression is evaluated to true (1 or non-zero value),
// then execute the statements, otherwise
// statements are skipped.
if(expression) begin
statement1;
statement2;
end
Example: Type 2 if-else statement.
if(expression) begin
// If the expression is evaluated to true (1 or non-zero value),
// then execute the following.
statement1;
statement2;
end
else
// If the expression is evaluated to false (x or zero), then
// execute the following.
statement3;
Example: Type 3 nested if-else statement.
if(expression1)
statement1;
else if(expression2)
statement2;
else if(expression3)
statement3;
else
statement4;
case Statement
This is an alternative to type 3 of if statement (nested if-else).
When the number of expressions is large, use of if statement
becomes cumbersome. A better way is to use case statement.
Example:
//***********************************
case(expression)
item1: statement1;
item2: begin
statement2;
statement3;
end
// More codes...
default: default_statement;
endcase
The case statement searches from top to bottom to find a
match between the case expression and a case item. The case
expression and a case item are compared bit by bit.
If their widths do not match, the shorter one is zero filled to
match the wider one. When the first match is found, the
remaining case items are not considered. The case statement
can be nested.
Example:
Model a 4-to-1 multiplexer using case statement.
//###########################################
// 4-to-1 multiplexer.
module mux4to1
(output out,
input ip_data3, ip_data2, ip_data1, ip_data0,
input ip_s1, ip_s0);
//***********************************
// Recompute the signal out if any input signal changes.
// All input signals that cause a recomputation of op_out to
// occur must go into the always @(...)
always @(ip_s1, ip_s0, ip_data3, ip_data2, ip_data1,
ip_data0) begin
case ({ip_s1, ip_s0})
2'b00: out = ip_data0;
2'b01: out = ip_data1;
2'b10: out = ip_data2;
2'b11: out = ip_data3;
// for cases where s0 or s1 is x or z
default: out = 1'bx;
endcase
end
endmodule
casex and casez Statements
There are two variations of the case statement.
• In casex, the x and z values in the case item are treated as
don’t-care.
• In casez, z values in the case item are treated as don’t-care.
The use of casex and casez allows comparison of only non-x
or non-z positions when matching the case expression with
the case items: x and z positions are not compared at all.
Example:
Write the code for a 16-bit loadable up/down counter. The
counter has a load signal and a reset signal, both active on the
rising edge of the clock. The reset signal has priority over the
load signal. If the load signal is not active, then the counter
counts up or down depending on the state of the up signal.
//#############################################
module counter
(output reg [15:0] op_count, // Count.
input [15:0] ip_data, // Data.
input ip_load, // Parallel load.
input ip_updown, // Count up.
input ip_clock, // Clock signal.
input ip_reset); // Active-high synchronous
// reset signal.
//***********************************
always @(posedge ip_clock)
casex({ip_reset, ip_load, ip_updown})
// casex means that x and z are treated as don’t-care.
3'b1xx : op_count <= 16'h0000;
// ip_reset has priority over ip_load and ip_updown.
// ip_load and ip_updown are not compared at all due to the x’s
// in the case item.
3'b01x : op_count <= ip_data;
3'b001 : op_count <= op_count + 16'h0001;
3'b000 : op_count <= op_count - 16'h0001;
default : op_count <= 16'bx;
endcase
endmodule
Note that because of the way the signals over-ride, it is
actually easier to write the counter using if statements, as
shown below.
always @(posedge ip_clock)
if(ip_reset)
op_count <= 16'h0000;
else if(ip_load)
op_count <= ip_data;
else if(ip_updown)
op_count <= op_count + 16'h0001;
else
op_count <= op_count - 16'h0001;
Loops
There are four types of loops:
for repeat while forever
All loops must appear inside a procedural block. Loops can
contain delay expressions. Loops can be disabled using the
keyword disable
for is generally used when there is a fixed beginning and end
to the loop.
Example:
// Increment and display count from 0 to 127 using for loop.
module counter_for;
//***********************************
integer i_count;
//***********************************
initial
for(i_count = 0; i_count < 128; i_count = i_count + 1)
$display("Count = %d", i_count);
endmodule
The repeat construct executes the loop a fixed number of
times. It cannot be used to loop on a general logical
expression (must be a number that is a constant, variable or
signal. x and z are treated as zero).
Example:
// Increment and display count from 0 to 127 using repeat loop.
module counter_repeat;
//***********************************
integer i_count;
//***********************************
initial begin
i_count = 0;
repeat(128) begin
$display("Count = %d", i_count);
i_count = i_count + 1;
end
end
endmodule
while are used if the loop is simply looping on a condition. As
long as the expression is evaluated to true, the statements in
the while loop will be executed.
Example:
// Increment and display count from 0 to 127 using while loop.
module counter_while;
//***********************************
integer i_count;
//***********************************
initial begin
i_count = 0;
while (i_count < 128) begin
$display("Count = %d", i_count);
i_count = i_count + 1;
end
end
endmodule
forever Loop does not contain any expression and executes
forever until the $stop is encountered.
Example:
module clock_gen
(output reg op_clock1,
output reg op_clock2);
//***********************************
initial begin
op_clock1 = 1'b0;
forever #10 op_clock1 = ~op_clock1; // Clock with period 20
end
endmodule
generate Statement
The generate statement allows Verilog codes to be generated
dynamically (before the simulation begins).
It is used to replicate independent copies of the following
Verilog codes: net declarations, register variable declarations,
parameter redefinitions, continuous assignments, always
block, initial block, tasks and functions.
For example, a parameterised module can be instantiated
repeatedly for different bit lengths.
There are three variants of generate statement.
• generate-for block.
• generate-if block.
• generate-case block.
generate-for Block
A generate-for block allows the following constructs to be
instantiated multiple times using a for loop:
Variable declarations, Modules, User defined primitives
(UDPs) and gate primitives, Continuous assignments,
and initial and always blocks.
We shall only look at this form of generate block as the other
2 types are very similar to it.
Example:
Given the following 8-bit adder module definition, develop a
24-bit adder using the generate statement.
Suppose the 8-bit adder has the following port declaration:
module add8
(output [7:0] op8_sum,
output op8_cout,
input [7:0] ip8_a, ip8_b,
input ip8_cin);
Then a 24-bit adder can be generated with:
module add24_rca
(output [23:0] op24_sum,
output op24_cout,
input [23:0] ip24_a, ip24_b,
input ip24_cin);
//***********************************
// Interconnects.
wire [3:0] w_carry;
//***********************************
assign w_carry[0] = ip24_cin;
assign op24_cout = w_carry[3];
//***********************************
// Generate the required number of instance.
generate
genvar k; // k is the adder stage number: stage 0, stage 1...
for(k = 0; k <= 2; k = k + 1) begin: LEVEL
add8 ADD
(.op8_sum (op24_sum[((k+1)*8 - 1) : k*8]),
// [7:0], [15:8], [23:16]
.op8_cout (w_carry[k+1]), // [1], [2], [3]
.ip8_a (ip24_a[((k+1)*8 - 1) : k*8]),
.ip8_b (ip24_b[((k+1)*8 - 1) : k*8]),
.ip8_cin (w_carry[k])); // [0], [1], [2]
end
endgenerate
endmodule
Prior to simulation, the simulator reads the code in the
generate blocks. The generate blocks are simply a
convenient way to replace multiple repetitive Verilog
statements with a single statement inside a loop.
genvar is used to declare a nonnegative integer that is used
only in the evaluation of the generate-for block.
The model generates a 24-bit adder from 3 copies of an 8-bit
adder, with instance names LEVEL[0].ADD,
LEVEL[1].ADD and LEVEL[2].ADD.