Simulating Synchronous systems

Techniques to sequence events for synchronous logic in verilog simulations

612

Simulating synchronous systems

For a combinatorial logic circuit, outputs are a direct function of the circuit’s inputs. Therefore, when simulating such circuits, the

Synchronous systems, however, not only need their inputs to be within certain timing parameters, but it is often the case that inputs to the circuit need to be sequenced as well. When simulating something like a processor bus,

An initial block creates a sequence of procedural statements. Unlike the always block, it executes immediately when a simulation begins (not triggered by a sensitivity list). Unlike always blocks, initial blocks are generally not synthesizable. Therefore they are used in simulations to control how stimulus is applied to the simulated module (also known as the unit under test [UUT]). Often simulations make use of both always and initial blocks and multiple of each can be used. For example an always block can be used to simulate a clock:

always
	#5 clk = ~clk;

This always block does not have a sensitivty list or clocking event. It will run continuously, but since it has a time delay in it, it will only execute once every 5 ticks.

In the always block above value of the signal clk will toggle with a cycle time of 10 ticks. However, before the value of the clock can be inverted, it needs to be initialized to something, otherwise it’s value will be undefined (Seen as ‘X’ in most simulators). This can be accomplished in an initial block:

initial
	clk=0;

When the simulation starts, the clock is immediately assigned a value of ‘0’. and at time t=5, it will toggle to ‘1’. This illustrates that the statements made in one block can affect other block’s procedural execution. Thus, you need to be careful when assigning values in multiple blocks.

Note that there can be multiple initial and always blocks, they will all execute in parallel. This is very powerful, but can also make managing your code a little tricky. For many simulations, an always block simulating a clock, and an initial block to sequence other inputs is all you will need.

Initial blocks can be thought of as blocks that execute their contents once. An always block can execute its contents repeatedly, and can be optionally triggered by a clock event or sensitivity list. Both can contain flow control statements such as delays, loops, or if-then structures. Thus, while an initial block will only execute its body once, it can execute statements repeatedly using a loop.

Flow control

Delays are very commonly used to control the execution flow of simulation code. For example, a reset can be held high for a specified amount of time. The example block below would simulate a reset being held high for 60 ticks before being driven low. This is useful for synchronous resets, as they need be asserted during an active clock edge.

initial
begin
	rst<=1;
	#60;
	rst<=0;
end

If we were using the clock which was defined in the earlier example (with a 10 tick cycle time), the 60 tick delay in this example would be plenty to ensure synchronous resets are properly handled. However, for a much slower clock it might not be asserted long enough. In this case you would have to modify your code to have a longer delay before deasserting the reset. Another way to approach this would be to make the reset time independent of the clock frequency, but instead use the clock itself to determine how long to assert the reset. As a synchronous reset needs an active clock edge with the reset asserted, we could just leave the reset held high until we encounter a clock edge. This can be implemented in a procedural block using an @ statement.

The @ statement will block execution in the current code block its condition is met. This can be a clock event or a sensitivity list, the same as used for always block triggering. Remember that signals listed in a sensitivity list will trigger when their values change, as such @ statements are most commonly used to block until clock events occur.

For example, when driving driving a reset @(posedge clk) could be used to hold reset asserted until a positive clock edge occurs, then deassert it. The following code block holds a reset signal high for at least 1 whole clock cycle, before deasserting it. Using this method, the code can work with a clock being simulated with any frequency.

initial
begin
	rst<=1;
	@(posedge clk);//wait for a clock edge
	@(posedge clk);//wait for the next cycle
	rst<=0;
end

When sequencing inputs for a synchronous system, relying on delays can become cumbersome, as you might have to calculate how long signals will need to be held to fit within specific clock cycles. Therefore in many cases it is much better to base sequencing on clock edges or handshaking signals. In fact, ‘@(posedge clk)’ can be used to synchronize your events to the next positive clock edge (illustrated below)

For example if you wanted to simulate a data value and its handshaking signal (In this example we assume the handshake is asserted for a single clock cycle), you could do something like the code example below. This also illustrates that we can synchronize from a arbitrary time-tick to a clock’s timing.

initial
begin
	//initialize the value of signals
	data_valid<=1'b0;
	data_value<=0;
	
	#233; //wait for some arbitrary amount of time

	@(posedge clk); //wait for the start of next clock cycle (synchronization)
	data_value<=32'd1234;
	data_valid <=1'b1;
	@(posedge clk); //wait till end of this cycle
	data_valid<=1'b0;
end

The wait statement

Another way to implement flow blocking is using the wait statement. Where the @ statement uses a sensitivity list or clock edge, the wait statement blocks execution until the statement in its parenthesis evaluates true (non-zero); The @ statement is event-based (clock edge or signal change) and the wait statement is level-based.

For example, to simulate an acknowledge signal to the above ‘data_valid’ signal, an always block can be used, which blocks until data_valid goes high. This is shown in the example below:

//every time data_valid is driven,
//drive data_ack for one cycle 
always
begin
	wait(data_valid);
	@(posedge clk);
	data_ack<=1;
	@(posedge clk);
	data_ack<=0;
end