A for
loop is the most widely used loop in software, but it is primarily used to replicate hardware logic in Verilog. The idea behind a for
loop is to iterate a set of statements given within the loop as long as the given condition is true. This is very similar to the while
loop, but is used more in a context where an iterator is available and the condition depends on the value of this iterator.
Syntax
for (<initial_condition>; <condition>; <step_assignment>) begin
// Statements
end
The keyword for
is used to specify this type of loop and has three parts:
- Initial condition to specify initial values of signals
- A check to evaluate if the given condition is true
- Update control variable for the next iteration
The initial condition and updates to control variable are included in the for
loop and is not required to be specified separately unlike a while
loop. A while
loop is more general purpose and is mostly used only when the given statements are required to be repeated as long as a given condition. However the for
loop tyically has a definite beginning and end controlled by the step variable.
Here is a simple example that illustrates the usage of a for loop.
module my_design;
integer i;
initial begin
// Note that ++ operator does not exist in Verilog !
for (i = 0; i < 10; i = i + 1) begin
$display ("Current loop#%0d ", i);
end
end
endmodule
Simulation Log ncsim> run Current loop#0 Current loop#1 Current loop#2 Current loop#3 Current loop#4 Current loop#5 Current loop#6 Current loop#7 Current loop#8 Current loop#9 ncsim: *W,RNQUIE: Simulation is complete.
Design Example
Let us take a look at how an 8-bit left shift register can be implemented in Verilog without a for
loop and then compare it with the code using a for
loop just to appreciate the utility of a looping construct.
module lshift_reg (input clk, // Clock input
input rstn, // Active low reset input
input [7:0] load_val, // Load value
input load_en, // Load enable
output reg [7:0] op); // Output register value
// At posedge of clock, if reset is low set output to 0
// If reset is high, load new value to op if load_en=1
// If reset is high, and load_en=0 shift register to left
always @ (posedge clk) begin
if (!rstn) begin
op <= 0;
end else begin
if (load_en) begin
op <= load_val;
end else begin
op[0] <= op[7];
op[1] <= op[0];
op[2] <= op[1];
op[3] <= op[2];
op[4] <= op[3];
op[5] <= op[4];
op[6] <= op[5];
op[7] <= op[6];
end
end
end
endmodule
The same behavior can be implemented using a for
loop which will reduce the code and make it scalable for different register widths. If the width of the register is made a Verilog parameter, the design module will become scalable and the same parameter can be used inside the for
loop.
module lshift_reg (input clk, // Clock input
input rstn, // Active low reset input
input [7:0] load_val, // Load value
input load_en, // Load enable
output reg [7:0] op); // Output register value
integer i;
// At posedge of clock, if reset is low set output to 0
// If reset is high, load new value to op if load_en=1
// If reset is high, and load_en=0 shift register to left
always @ (posedge clk) begin
if (!rstn) begin
op <= 0;
end else begin
// If load_en is 1, load the value to op
// else keep shifting for every clock
if (load_en) begin
op <= load_val;
end else begin
for (i = 0; i < 8; i = i + 1) begin
op[i+1] <= op[i];
end
op[0] <= op[7];
end
end
end
endmodule
Testbench
The testbench code is shown below and instantiates the design.
module tb;
reg clk;
reg rstn;
reg [7:0] load_val;
reg load_en;
wire [7:0] op;
// Setup DUT clock
always #10 clk = ~clk;
// Instantiate the design
lshift_reg u0 ( .clk(clk),
.rstn (rstn),
.load_val (load_val),
.load_en (load_en),
.op (op));
initial begin
// 1. Initialize testbench variables
clk <= 0;
rstn <= 0;
load_val <= 8'h01;
load_en <= 0;
// 2. Apply reset to the design
repeat (2) @ (posedge clk);
rstn <= 1;
repeat (5) @ (posedge clk);
// 3. Set load_en for 1 clk so that load_val is loaded
load_en <= 1;
repeat(1) @ (posedge clk);
load_en <= 0;
// 4. Let design run for 20 clocks and then finish
repeat (20) @ (posedge clk);
$finish;
end
endmodule


Verilog supports a few compiler directives that essentially direct the compiler to treat the code in a certain way. For example, a portion of the code may represent an implementation of a certain feature and there should be some way to not include the code in the design if the feature is not used.
This can be solved with conditional compilation where the designer can wrap the code within compiler directives which tell the compiler to either include or exclude the code for compilation when the given named flag is set.
Syntax
Conditional compilation can be achieved with Verilog `ifdef
and `ifndef
keywords. These keywords can appear anywhere in the design and can be nested one inside the other.
The keyword `ifdef
simply tells the compiler to include the piece of code until the next `else
or `endif
if the given macro called FLAG is defined using a `define
directive.
// Style #1: Only single `ifdef
`ifdef <FLAG>
// Statements
`endif
// Style #2: `ifdef with `else part
`ifdef <FLAG>
// Statements
`else
// Statements
`endif
// Style #3: `ifdef with additional ifdefs
`ifdef <FLAG1>
// Statements
`elsif <FLAG2>
// Statements
`elsif <FLAG3>
// Statements
`else
// Statements
`endif
The keyword `ifndef
simply tells the compiler to include the piece of code until the next `else
or `endif
if the given macro called FLAG is not defined using a `define
directive.
Design Example with `ifdef
module my_design (input clk, d,
`ifdef INCLUDE_RSTN
input rstn,
`endif
output reg q);
always @ (posedge clk) begin
`ifdef INCLUDE_RSTN
if (!rstn) begin
q <= 0;
end else
`endif
begin
q <= d;
end
end
endmodule
Testbench
module tb;
reg clk, d, rstn;
wire q;
reg [3:0] delay;
my_design u0 ( .clk(clk), .d(d),
`ifdef INCLUDE_RSTN
.rstn(rstn),
`endif
.q(q));
always #10 clk = ~clk;
initial begin
integer i;
{d, rstn, clk} <= 0;
#20 rstn <= 1;
for (i = 0 ; i < 20; i=i+1) begin
delay = $random;
#(delay) d <= $random;
end
#20 $finish;
end
endmodule
Note that by default, rstn will not be included during compilation of the design and hence it will not appear in the portlist. However if a macro called INCLUDE_RSTN is either defined in any Verilog file that is part of the compilation list of files or passed through the command line to the compiler, rstn will be included in compilation and the design will have it.
Experiment by adding and removing +define+INCLUDE_RSTN from 'Compile & Run Options' on the left pane to know the difference.
Verilog `ifdef `elsif Example
The following example has two display statements inside separate `ifdef
scopes which does not have a default `else
part to it. So this means that by default nothing will be displayed. If the macro either MACRO is defined, the corresponding display message is included and will be displayed during simulation
module tb;
initial begin
`ifdef MACRO1
$display ("This is MACRO1");
`elsif MACRO2
$display ("This is MACRO2");
`endif
end
endmodule
Simulation Log # With no macros defined ncsim> run ncsim: *W,RNQUIE: Simulation is complete. # With +define+MACRO1 ncsim> run This is MACRO1 ncsim: *W,RNQUIE: Simulation is complete. # With +define+MACRO2 ncsim> run This is MACRO2 ncsim: *W,RNQUIE: Simulation is complete.
Verilog `ifndef `elsif Example
The same code can be written with `ifndef
and results will be just the opposite.
module tb;
initial begin
`ifndef MACRO1
$display ("This is MACRO1");
`elsif MACRO2
$display ("This is MACRO2");
`endif
end
endmodule
Simulation Log # With no macros defined ncsim> run This is MACRO1 ncsim: *W,RNQUIE: Simulation is complete. # With +define+MACRO1 ncsim> run ncsim: *W,RNQUIE: Simulation is complete. # With +define+MACRO2 ncsim> run This is MACRO1 ncsim: *W,RNQUIE: Simulation is complete. # With +define+MACRO1 +define+MACRO2 ncsim> run This is MACRO2 ncsim: *W,RNQUIE: Simulation is complete.
Verilog Nested `ifdef Example
`ifdef
and its flavors can be nested one inside the other to create complex ways of code inclusion and exclusion with defined macros.
module tb;
initial begin
`ifdef FLAG
$display ("FLAG is defined");
`ifdef NEST1_A
$display ("FLAG and NEST1_A are defined");
`ifdef NEST2
$display ("FLAG, NEST1_A and NEST2 are defined");
`endif
`elsif NEST1_B
$display ("FLAG and NEST1_B are defined");
`ifndef WHITE
$display ("FLAG and NEST1_B are defined, but WHITE is not");
`else
$display ("FLAG, NEST1_B and WHITE are defined");
`endif
`else
$display ("Only FLAG is defined");
`endif
`else
$display ("FLAG is not defined");
`endif
end
endmodule
Simulation Log # Without defining any macro ncsim> run FLAG is not defined ncsim: *W,RNQUIE: Simulation is complete. # With +define+FLAG +define+NEST1_B ncsim> run FLAG is defined FLAG and NEST1_B are defined FLAG and NEST1_B are defined, but WHITE is not ncsim: *W,RNQUIE: Simulation is complete. # With +define+FLAG +define+NEST1_B +define+WHITE ncsim> run FLAG is defined FLAG and NEST1_B are defined FLAG, NEST1_B and WHITE are defined ncsim: *W,RNQUIE: Simulation is complete. # With +define+FLAG ncsim> run FLAG is defined Only FLAG is defined ncsim: *W,RNQUIE: Simulation is complete. # With +define+WHITE ncsim> run FLAG is not defined ncsim: *W,RNQUIE: Simulation is complete. # With +define+NEST1_A ncsim> run FLAG is not defined ncsim: *W,RNQUIE: Simulation is complete.
Note that as long as the parent macro is not defined, definition of any other nested macro within it does not get compiled. For example, NEST1_A or WHITE macro definitions without FLAG does not make the compiler pick up the nested code.
How does a sequencer communicate with the driver ?
The driver class contains a TLM port called uvm_seq_item_pull_port
which is connected to a uvm_seq_item_pull_export
in the sequencer in the connect phase of a UVM agent. The driver can use TLM functions to get the next item from the sequencer when required.

Why do we need the driver sequencer API methods ?
These API methods help the driver to get a series of sequence_items from the sequencer's FIFO that contains data for the driver to drive to the DUT. Also, there is a way for the driver to communicate back with the sequence that it has finished driving the given sequence item and can request for the next item.
What is the port in driver called ?
Declaration for the TLM ports can be found in the class definitions of uvm_driver
and uvm_sequencer
as follows.
// Definition of uvm_driver
class uvm_driver #(type REQ=uvm_sequence_item,
type RSP=REQ) extends uvm_component;
// Port: seq_item_port
// Derived driver classes should use this port to request items from the
// sequencer. They may also use it to send responses back.
uvm_seq_item_pull_port #(REQ, RSP) seq_item_port;
// Port: rsp_port
// This port provides an alternate way of sending responses back to the
// originating sequencer. Which port to use depends on which export the
// sequencer provides for connection.
uvm_analysis_port #(RSP) rsp_port;
REQ req;
RSP rsp;
// Rest of the code follows ...
endclass
What is the TLM port in sequencer called ?
A uvm_sequencer
has an inbuilt TLM pull implementation port called seq_item_export
which is used to connect with the driver's pull port.
// Definition of uvm_sequencer
class uvm_sequencer #(type REQ=uvm_sequence_item, RSP=REQ)
extends uvm_sequencer_param_base #(REQ, RSP);
// Variable: seq_item_export
// This export provides access to this sequencer's implementation of the
// sequencer interface.
uvm_seq_item_pull_imp #(REQ, RSP, this_type) seq_item_export;
// Rest of the class contents follow ...
endclass
How is a driver connected to a sequencer ?
The port in uvm_driver
is connected to the export in uvm_sequencer
in the connect
phase of the UVM component in which both the driver and sequencer are instantiated. Typically, a driver and sequencer are instantiated in a uvm_agent
.
class my_agent extends uvm_agent;
`uvm_component_utils (my_agent)
my_driver #(my_sequence_item) m_drv;
uvm_sequencer #(my_sequence_item) m_seqr;
virtual function void connect_phase (uvm_phase phase);
// Always the port is connected to an export
m_drv.seq_item_port.connect(m_seqr.seq_item_export);
endfunction
endclass
The connect between a driver and sequencer is a one-to-one connection. Multiple drivers are not connected to a sequencer nor are multiple sequencers connected to a single driver. Once the connection is made, the driver can utilize API calls in the TLM port definitions to receive sequence items from the sequencer.

Design
module ring_ctr #(parameter WIDTH=4)
(
input clk,
input rstn,
output reg [WIDTH-1:0] out
);
always @ (posedge clk) begin
if (!rstn)
out <= 1;
else begin
out[WIDTH-1] <= out[0];
for (int i = 0; i < WIDTH-1; i=i+1) begin
out[i] <= out[i+1];
end
end
end
endmodule
Testbench
module tb;
parameter WIDTH = 4;
reg clk;
reg rstn;
wire [WIDTH-1:0] out;
ring_ctr u0 (.clk (clk),
.rstn (rstn),
.out (out));
always #10 clk = ~clk;
initial begin
{clk, rstn} <= 0;
$monitor ("T=%0t out=%b", $time, out);
repeat (2) @(posedge clk);
rstn <= 1;
repeat (15) @(posedge clk);
$finish;
end
endmodule
Simulation Log ncsim> run T=0 out=xxxx T=10 out=0001 T=50 out=1000 T=70 out=0100 T=90 out=0010 T=110 out=0001 T=130 out=1000 T=150 out=0100 T=170 out=0010 T=190 out=0001 T=210 out=1000 T=230 out=0100 T=250 out=0010 T=270 out=0001 T=290 out=1000 T=310 out=0100 Simulation complete via $finish(1) at time 330 NS + 0
A member declared as local
is available only to the methods of the same class, and are not accessible by child classes. However, nonlocal methods that access local
members can be inherited and overridden by child class.
Example
In the following example, we will declare two variables - one public
and another local
. We expect to see an error when a local member of the class is accessed from somewhere outside the class. This is because the keyword local
is used to keep members local and visible only within the same class.
When accessed from outside the class
class ABC;
// By default, all variables are public and for this example,
// let's create two variables - one public and the other "local"
byte public_var;
local byte local_var;
// This function simply prints these variable contents
function void display();
$display ("public_var=0x%0h, local_var=0x%0h", public_var, local_var);
endfunction
endclass
module tb;
initial begin
// Create a new class object, and call display method
ABC abc = new();
abc.display();
// Public variables can be accessed via the class handle
$display ("public_var = 0x%0h", abc.public_var);
// However, local variables cannot be accessed from outside
$display ("local_var = 0x%0h", abc.local_var);
end
endmodule
As expected, the compiler gives out a compilation error pointing to the line where a local member is accessed from outside the class.
Simulation Log
$display ("local_var = 0x%0h", abc.local_var);
|
ncvlog: *E,CLSNLO (testbench.sv,24|47): Access to local member 'local_var' in class 'ABC' is not allowed here.
irun: *E,VLGERR: An error occurred during parsing. Review the log file for errors with the code *E and fix those identified problems to proceed. Exiting with code (status 1).
In the above example, we can remove the line that causes a compilation error and see that we get a good output. The only other function that accesses the local
member is the display() function.
module tb;
initial begin
ABC abc = new();
// This should be able to print local members of class ABC
// because display() is a member of ABC also
abc.display();
// Public variables can always be accessed via the class handle
$display ("public_var = 0x%0h", abc.public_var);
end
endmodule
Simulation Log ncsim> run public_var=0x0, local_var=0x0 public_var = 0x0 ncsim: *W,RNQUIE: Simulation is complete.
When accessed by child classes
In this example, let us try to access the local
member from within a child class. We expect to see an error here also because local
is not visible to child classes either.
// Define a base class and let the variable be "local" to this class
class ABC;
local byte local_var;
endclass
// Define another class that extends ABC and have a function that tries
// to access the local variable in ABC
class DEF extends ABC;
function show();
$display ("local_var = 0x%0h", local_var);
endfunction
endclass
module tb;
initial begin
// Create a new object of the child class, and call the show method
// This will give a compile time error because child classes cannot access
// base class "local" variables and methods
DEF def = new();
def.show();
end
endmodule
As expected, child classes cannot access the local
members of their parent class.
$display ("local_var = 0x%0h", local_var);
|
ncvlog: *E,CLSNLO (testbench.sv,10|43): Access to local member 'local_var' in class 'ABC' is not allowed here.
irun: *E,VLGERR: An error occurred during parsing. Review the log file for errors with the code *E and fix those identified problems to proceed. Exiting with code (status 1).