
Design
module johnson_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;
johnson_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
ncsim> run T=0 out=xxxx T=10 out=0001 T=50 out=0000 T=70 out=1000 T=90 out=1100 T=110 out=1110 T=130 out=1111 T=150 out=0111 T=170 out=0011 T=190 out=0001 T=210 out=0000 T=230 out=1000 T=250 out=1100 T=270 out=1110 T=290 out=1111 T=310 out=0111 Simulation complete via $finish(1) at time 330 NS + 0
What are loops ?
A loop is a piece of code that keeps executing over and over. A conditional statement is typically included in a loop so that it can terminate once the condition becomes true. If the loop runs forever, then the simulation will hang indefinitely.
Different types of looping constructs in SystemVerilog are given in the table below.
forever | Runs the given set of statements forever |
repeat | Repeats the given set of statements for a given number of times |
while | Repeats the given set of statments as long as given condition is true |
for | Similar to while loop, but more condense and popular form |
do while | Repeats the given set of statements atleast once, and then loops as long as condition is true |
foreach | Used mainly to iterate through all elements in an array |
forever
This is an infinite loop, just like while (1)
. Note that your simulation will hang unless you include a time delay inside the forever
block to advance simulation time.
module tb;
// This initial block has a forever loop which will "run forever"
// Hence this block will never finish in simulation
initial begin
forever begin
#5 $display ("Hello World !");
end
end
// Because the other initial block will run forever, our simulation will hang!
// To avoid that, we will explicity terminate simulation after 50ns using $finish
initial
#50 $finish;
endmodule
Note that simulation would have continued indefinitely if $finish
was not called.
ncsim> run Hello World ! Hello World ! Hello World ! Hello World ! Hello World ! Hello World ! Hello World ! Hello World ! Hello World ! Simulation complete via $finish(1) at time 50 NS + 0
repeat
Used to repeat statements in a block a certain number of times. The example shown below will display the message 5 times and continues with rest of the code.
module tb;
// This initial block will execute a repeat statement that will run 5 times and exit
initial begin
// Repeat everything within begin end 5 times and exit "repeat" block
repeat(5) begin
$display ("Hello World !");
end
end
endmodule
ncsim> run Hello World ! Hello World ! Hello World ! Hello World ! Hello World ! ncsim: *W,RNQUIE: Simulation is complete.
while
You already know this if you know verilog/C. It'll repeat the block as long as the condition is true. Counter is initially zero and increments until it reaches 10.
module tb;
bit clk;
always #10 clk = ~clk;
initial begin
bit [3:0] counter;
$display ("Counter = %0d", counter); // Counter = 0
while (counter < 10) begin
@(posedge clk);
counter++;
$display ("Counter = %0d", counter); // Counter increments
end
$display ("Counter = %0d", counter); // Counter = 10
$finish;
end
endmodule
ncsim> run Counter = 0 Counter = 1 Counter = 2 Counter = 3 Counter = 4 Counter = 5 Counter = 6 Counter = 7 Counter = 8 Counter = 9 Counter = 10 Counter = 10 Simulation complete via $finish(1) at time 190 NS + 0
for
Similar to verilog/C, this allows you to mention starting value, condition and incremental expression all on the same line.
module tb;
bit clk;
always #10 clk = ~clk;
initial begin
bit [3:0] counter;
$display ("Counter = %0d", counter); // Counter = 0
for (counter = 2; counter < 14; counter = counter + 2) begin
@(posedge clk);
$display ("Counter = %0d", counter); // Counter increments
end
$display ("Counter = %0d", counter); // Counter = 14
$finish;
end
endmodule
ncsim> run Counter = 0 Counter = 2 Counter = 4 Counter = 6 Counter = 8 Counter = 10 Counter = 12 Counter = 14 Simulation complete via $finish(1) at time 110 NS + 0
do while
This executes the code first and then checks for the condition to see if the code should be executed again.
module tb;
bit clk;
always #10 clk = ~clk;
initial begin
bit [3:0] counter;
$display ("Counter = %0d", counter); // Counter = 0
do begin
@ (posedge clk);
counter ++;
$display ("Counter = %0d", counter); // Counter increments
end while (counter < 5);
$display ("Counter = %0d", counter); // Counter = 14
$finish;
end
endmodule
ncsim> run Counter = 0 Counter = 1 Counter = 2 Counter = 3 Counter = 4 Counter = 5 Counter = 5 Simulation complete via $finish(1) at time 90 NS + 0
foreach
This is best suited to loop through array variables, because you don't have to find the array size, set up a variable to start from 0 until array_size-1 and increment it on every iteration.
module tb_top;
bit [7:0] array [8]; // Create a fixed size array
initial begin
// Assign a value to each location in the array
foreach (array [index]) begin
array[index] = index;
end
// Iterate through each location and print the value of current location
foreach (array [index]) begin
$display ("array[%0d] = 0x%0d", index, array[index]);
end
end
endmodule
ncsim> run array[0] = 0x0 array[1] = 0x1 array[2] = 0x2 array[3] = 0x3 array[4] = 0x4 array[5] = 0x5 array[6] = 0x6 array[7] = 0x7 ncsim: *W,RNQUIE: Simulation is complete.
Design
module gray_ctr
# (parameter N = 4)
( input clk,
input rstn,
output reg [N-1:0] out);
reg [N-1:0] q;
always @ (posedge clk) begin
if (!rstn) begin
q <= 0;
out <= 0;
end else begin
q <= q + 1;
`ifdef FOR_LOOP
for (int i = 0; i < N-1; i= i+1) begin
out[i] <= q[i+1] ^ q[i];
end
out[N-1] <= q[N-1];
`else
out <= {q[N-1], q[N-1:1] ^ q[N-2:0]};
`endif
end
end
endmodule
Testbench
module tb;
parameter N = 4;
reg clk;
reg rstn;
wire [N-1:0] out;
gray_ctr u0 ( .clk(clk),
.rstn(rstn),
.out(out));
always #10 clk = ~clk;
initial begin
{clk, rstn} <= 0;
$monitor ("T=%0t rstn=%0b out=0x%0h", $time, rstn, out);
repeat(2) @ (posedge clk);
rstn <= 1;
repeat(20) @ (posedge clk);
$finish;
end
endmodule
ncsim> run T=0 rstn=0 out=0xx T=10 rstn=0 out=0x0 T=30 rstn=1 out=0x0 T=50 rstn=1 out=0x1 T=70 rstn=1 out=0x3 T=90 rstn=1 out=0x2 T=110 rstn=1 out=0x6 T=130 rstn=1 out=0x7 T=150 rstn=1 out=0x5 T=170 rstn=1 out=0x4 T=190 rstn=1 out=0xc T=210 rstn=1 out=0xd T=230 rstn=1 out=0xf T=250 rstn=1 out=0xe T=270 rstn=1 out=0xa T=290 rstn=1 out=0xb T=310 rstn=1 out=0x9 T=330 rstn=1 out=0x8 T=350 rstn=1 out=0x0 T=370 rstn=1 out=0x1 T=390 rstn=1 out=0x3 T=410 rstn=1 out=0x2 Simulation complete via $finish(1) at time 430 NS + 0
Verilog design and testbench typically have many lines of code comprising of always
or initial
blocks, continuous assignments and other procedural statements which become active at different times in the course of a simulation.
Event Types
- Update Events: Triggered by changes in signal values.
- Evaluation Events: Occur when processes like
always
orassign
blocks are evaluated due to update events in any arbitrary order.
Since these events can happen at different times, they are better managed and ensured of their correct order of execution by scheduling them into event queues that are arranged by simulation time.
module tb;
reg a, b, c;
wire d;
// 'always' is a process that gets evaluated when either 'a' or 'b' is updated.
// When 'a' or 'b' changes in value it is called an 'update event'. When 'always'
// block is triggered because of a change in 'a' or 'b' it is called an evaluation
// event
always @ (a or b) begin
c = a & b;
end
// Here 'assign' is a process which is evaluated when either 'a' or 'b' or 'c'
// gets updated
assign d = a | b ^ c;
endmodule
Event Queue
A simulation step can be segmented into four different regions. An active event queue is just a set of processes that need to execute at the current time which can result in more processes to be scheduled into active or other event queues. Events can be added to any of the regions, but always removed from the active region. When all events in the active queue for the current time step has been executed, the simulator advances time to the next time step and executes its active queue.
Active Region
Contains events that are currently being processed. These events can be executed in any order. All active events occur at the current simulation time.
Inactive Region
Holds normally scheduled events that are set to execute after all active events have been processed. Events in this region are evaluated after the active region is complete.
Nonblocking Assignment (NBA) Region
This region handles nonblocking assignments made during the active region, ensuring they are assigned after all active and inactive events have been processed.
Monitor Region
Processes monitoring tasks such as $monitor
and $strobe
, which report values after all other events have been executed.
Example
module tb;
reg x, y, z
initial begin
#1 x = 1;
y = 1;
#1 z = 0;
end
endmodule
Simulation starts at time 0, and the first statement is scheduled to be executed when simulation time reaches 1 time unit at which it assigns x and y to 1. This is the active queue for the current time which is 1 time unit. Simulator then schedules the next statement after 1 more time unit at which z is assigned 0.
Event Processing Flow
When the simulator advances through time, it follows this general flow:
- Execute all events in the active region.
- Move any remaining events from the inactive region to the active region for processing.
- Process nonblocking assignments from the NBA region.
- Finally, handle any monitoring tasks in the monitor region.
What makes simulation nondeterministic ?
There can be race conditions during simulation that end up giving different outputs for the same design and testbench. One of the reasons for nondeterministic behavior is because active events can be removed from the queue and processed in any order.
Verilog simulation depends on how time is defined because the simulator needs to know what a #1 means in terms of time. The `timescale
compiler directive specifies the time unit and precision for the modules that follow it.
Syntax
`timescale <time_unit>/<time_precision>
// Example
`timescale 1ns/1ps
`timescale 10us/100ns
`timescale 10ns/1ns
The time_unit is the measurement of delays and simulation time while the time_precision specifies how delay values are rounded before being used in simulation.
Use the following timescale constructs to use different time units in the same design. Remember that delay specifications in the design are not synthesizable and cannot be converted to hardware logic.
`timescale
for base unit of measurement and precision of time- $printtimescale system task to display time unit and precision
$time
and$realtime
system functions return the current time and the default reporting format can be changed with another system task$timeformat
.
Character | Unit |
---|---|
s | seconds |
ms | milliseconds |
us | microseconds |
ns | nanoseconds |
ps | picoseconds |
fs | femtoseconds |
The integers in these specifications can be either 1, 10 or 100 and the character string that specifies the unit can take any value mentioned in the table above.
Example #1: 1ns/1ns
// Declare the timescale where time_unit is 1ns
// and time_precision is also 1ns
`timescale 1ns/1ns
module tb;
// To understand the effect of timescale, let us
// drive a signal with some values after some delay
reg val;
initial begin
// Initialize the signal to 0 at time 0 units
val <= 0;
// Advance by 1 time unit, display a message and toggle val
#1 $display ("T=%0t At time #1", $realtime);
val <= 1;
// Advance by 0.49 time unit and toggle val
#0.49 $display ("T=%0t At time #0.49", $realtime);
val <= 0;
// Advance by 0.50 time unit and toggle val
#0.50 $display ("T=%0t At time #0.50", $realtime);
val <= 1;
// Advance by 0.51 time unit and toggle val
#0.51 $display ("T=%0t At time #0.51", $realtime);
val <= 0;
// Let simulation run for another 5 time units and exit
#5 $display ("T=%0t End of simulation", $realtime);
end
endmodule
The first delay statement uses #1 which makes the simulator wait for exactly 1 time unit which is specified to be 1ns with `timescale
directive. The esecond delay statement uses 0.49 which is less than half a time unit. However the time precision is specified to be 1ns and hence the simulator cannot go smaller than 1 ns which makes it to round the given delay statement and yields 0ns. So the second delay fails to advance the simulation time.
The third delay statement uses exactly half the time unit [hl]#0.5[/lh] and again the simulator will round the value to get #1 which represents one whole time unit. So this gets printed at T=2ns.
The fourth delay statement uses a value more than half the time unit and gets rounded as well making the display statement to be printed at T=3ns.
ncsim> run T=1 At time #1 T=1 At time #0.49 T=2 At time #0.50 T=3 At time #0.51 T=8 End of simulation ncsim: *W,RNQUIE: Simulation is complete.
The simulation runs for 8ns as expected, but notice that the waveform does not have smaller divisions between each nanosecond. This is because the precision of time is the same as the time unit.

Example #2: 10ns/1ns
The only change made in this example compared to the previous one is that the timescale has been changed from 1ns/1ns to 10ns/1ns. So the time unit is 10ns and precision is at 1ns.
// Declare the timescale where time_unit is 10ns
// and time_precision is 1ns
`timescale 10ns/1ns
// NOTE: Testbench is the same as in previous example
module tb;
// To understand the effect of timescale, let us
// drive a signal with some values after some delay
reg val;
initial begin
// Initialize the signal to 0 at time 0 units
val <= 0;
// Advance by 1 time unit, display a message and toggle val
#1 $display ("T=%0t At time #1", $realtime);
val <= 1;
// Advance by 0.49 time unit and toggle val
#0.49 $display ("T=%0t At time #0.49", $realtime);
val <= 0;
// Advance by 0.50 time unit and toggle val
#0.50 $display ("T=%0t At time #0.50", $realtime);
val <= 1;
// Advance by 0.51 time unit and toggle val
#0.51 $display ("T=%0t At time #0.51", $realtime);
val <= 0;
// Let simulation run for another 5 time units and exit
#5 $display ("T=%0t End of simulation", $realtime);
end
endmodule
Actual simulation time is obtained by multiplying the delay specified using #
with the time unit and then it is rounded off based on precision. The first delay statement will then yield 10ns and the second one gives 14.9 which gets rounded to become 15ns.
The third statement similarly adds 5ns (0.5 * 10ns) and the total time becomes 20ns. The fourth one adds another 5ns (0.51 * 10) to advance total time to 25ns.
ncsim> run T=10 At time #1 T=15 At time #0.49 T=20 At time #0.50 T=25 At time #0.51 T=75 End of simulation ncsim: *W,RNQUIE: Simulation is complete.
Note that the base unit in waveform is in tens of nanoseconds with a precision of 1ns.

Example #3: 1ns/1ps
The only change made in this example compared to the previous one is that the timescale has been changed from 1ns/1ns to 1ns/1ps. So the time unit is 1ns and precision is at 1ps.
// Declare the timescale where time_unit is 1ns
// and time_precision is 1ps
`timescale 1ns/1ps
// NOTE: Testbench is the same as in previous example
module tb;
// To understand the effect of timescale, let us
// drive a signal with some values after some delay
reg val;
initial begin
// Initialize the signal to 0 at time 0 units
val <= 0;
// Advance by 1 time unit, display a message and toggle val
#1 $display ("T=%0t At time #1", $realtime);
val <= 1;
// Advance by 0.49 time unit and toggle val
#0.49 $display ("T=%0t At time #0.49", $realtime);
val <= 0;
// Advance by 0.50 time unit and toggle val
#0.50 $display ("T=%0t At time #0.50", $realtime);
val <= 1;
// Advance by 0.51 time unit and toggle val
#0.51 $display ("T=%0t At time #0.51", $realtime);
val <= 0;
// Let simulation run for another 5 time units and exit
#5 $display ("T=%0t End of simulation", $realtime);
end
endmodule
See that the time units scaled to match the new precision value of 1ps. Also note that time is represented in the smallest resolution which in this case is picoseconds.
ncsim> run T=1000 At time #1 T=1490 At time #0.49 T=1990 At time #0.50 T=2500 At time #0.51 T=7500 End of simulation ncsim: *W,RNQUIE: Simulation is complete.
