In this post, we discuss one of the most important constructs in verilog – the always block.
In contrast to combinational logic, sequential circuits use a clock and require storage elements such as flip flops.
As a result, the output signals are synchronised to the circuit clock and changes do not occur immediately.
We use the always block to write code which executes sequentially in verilog. This is crucial when describing sequential logic circuits in verilog.
As always, there are a number of exercises at the end of this post. However, it is worth reading the blog on writing a basic testbench in verilog before tackling these exercises. This will allow us to simulate some of the circuits which we design in these exercises.
The Always Block in Verilog
When we write verilog, we use procedural blocks to create statements which are executed sequentially. Procedural blocks are particularly important for the modelling of sequential digital circuits.
In contrast, verilog continuous assignment statements execute concurrently (i.e. in parallel) in our designs. This matches the nature of the underlying circuits, which consist of a number of separate logic gates.
The always block is one of the most commonly used procedural blocks in verilog. Whenever one of the signals in the sensitivity list changes state, all of the statements in the always block execute sequentially.
The verilog code below shows the general syntax for the always block. We talk about the sensitivity list in more depth in the next section.
always @(<sensitivity_list>) begin // Code to be executed goes here end
However, we need to be careful when using this construct as there are some features which are unique to verilog.
Beginners often find it difficult to understand the way that signals are updated in an always block.
When we use always blocks, we can update the value of our signals either in parallel or sequentially. This depends on whether we use non or non-blocking assignment, which we discuss in more depth later in this post.
In order to be an effective verilog designer, it is important that we have a good understanding of the always block.
Let’s look at some of the key features of the always block in more detail.
Any code which we write within an always block runs continuously. This means that the statements within the code block are executed in sequence until we reach the last line.
Once the last line in the sequence has been executed, the program then loops back to the first line. All of the statements in the always block are then executed in sequence again.
However, this behaviour is not representative of a real circuit which will remain in a steady state until one of the input signals changes state.
We use the sensitivity list in the alway block to emulate this behaviour.
To do this, the code within the always block will only execute after one of the signals in the sensitivity list changes state.
Flip Flop Example
Let’s consider how we would model a basic D type flip flop using the always block as an example.
As with all clocked flip flops, the output of a D type flip flop only changes state when there is a positive clock edge.
As a result of this, we include the clock signal in the sensitivity list so that the always block only executes when there is a rising edge on the clock signal.
The verilog code below shows how we would model a D type flip flop using the always block.
always @(posedge clock) begin q <= d; end
In this code example, we use the posedge macro to determine when there is a transition from 0 to 1. The single line of code within the always block is executed when this macro evaluates as true. This line of code assigns the value of D to the output signal (Q).
When we use the posedge macro in verilog, all other changes of state are simply ignored. This is exactly what we would expect from a D type flip flop.
Verilog also has a negedge macro which has the opposite functionality. When we use this macro, the always block will execute whenever the clock changes from 1 to 0.
We can also omit this macro altogether. In this case, the code executes whenever a signal in the sensitivity list changes state.
We should only ever use the posedge macro for clock signals in our verilog design. This is because synthesis tools will attempt to utilize clock resources within the FPGA to implement it.
Multiple Signals in a Sensitivity List
There are instances when we will want to include more than one signal in the sensitivity list.
A common example of this is when we write code to model the behaviour of flip flops with asynchronous resets.
When this is the case, we need the flip flop model to perform an action whenever the reset or clock signals change state.
To do this we simply list both of the signals inside the sensitivity list and separate them with the or keyword.
The code snippet below shows how we write such a flip flop.
always @(posedge clock or posedge reset) begin if (reset) begin q <= 1'b0; end else begin q <= d; end end
As this example uses an active high reset, we again use the posedge macro in the sensitivity list.
An active high reset means that the reset is only active when it is equal to one.
We then use a construct known as an if statement to determine whether the always block triggered by the reset signal or the clock signal. We will discuss the if statement in a future blog post, although it’s functionality is fairly self explanatory.
Blocking and Non-Blocking Assignment in Verilog
In the code examples we have seen so far in this series of posts, we have used two different types of assignment operators.
This is because verilog has two different types of assignment – blocking and non-blocking.
When we write code with non-blocking assignments we use the <= symbol whilst blocking code uses the = symbol.
When we use continuous assignment in verilog, we can only use blocking assignment.
We can use both types of assignment in procedural block. However, blocking assignment typically results in combinational logic circuits being implemented after synthesis. In contrast, non-blocking assignment normally results in sequential circuits after synthesis.
Blocking assignment is the simplest of the two techniques to understand. When we assign signals using blocking assignment in verilog, the signals update their value as soon as the line of code is executed.
We commonly use this type of assignment to write combinational logic in verilog. However, in some circumstances we can use it to create sequential circuits.
In contrast, signals which use the non-blocking technique are not updated immediately after assignment. Instead, verilog uses assignment scheduling to update the values.
This is a little tricky to understand, so let’s consider it in a bit more depth.
When we write verilog code using non-blocking assignment, our code still executes sequentially. However, the signals which we are assigning do not get updated in this way.
To demonstrate why this is the case, let’s consider the twisted ring counter circuit below.
always @(posedge clock) begin q_dff1 <= ~q_dff2; q_dff2 <= q_dff1; end
First, let’s look at the behaviour if the signals did update immediately.
If we assume that the output of both flip flops is 0b when a clock edge occurs, then the second line in the code will set the output of DFF1 to 1b.
We can then see that the line of code immediately beneath this would set the output of DFF2 to 1. This is clearly not the intended behaviour of this circuit.
To overcome this issue, non blocking assignment in verilog uses scheduled assignment.
As a result, signal changes don’t occur immediately after assignment but are instead scheduled to occur at a future time.
Normally, the signals update their value at the end of a simulation cycle. This refers to the time it takes the simulator to execute all of the code for a given time step.
To better demonstrate the way scheduled assignment works, let’s again consider the simple dual flip flop circuit.
When a rising edge if detected, the simulator firstly executes the statement to update DFF1. Once this line has been executed, an update to the output of DFF1 is scheduled to happen.
The simulator then runs the second line of code, this time using the original value of the DFF1 flip flop and schedules the update of DFF2.
As there are only two statements in this design, the simulation cycle is now complete. At this point, all of the scheduled changes are applied and the values are updated for both flip flops.
To further demonstrate the difference between blocking and non blocking assignments in verilog, we will again model a basic two flip flop twisted ring counter circuit. The code snippet below shows he implementation of this circuit.
always @(posedge clock) begin q_dff1 <= ~q_dff2; q_dff2 <= q_dff1; end
However, we can also look at the output of a synthesis tool such as vivado to display a diagram of the resulting circuit. The circuit diagram below shows this circuit.
We can see that there are two flip flops in the circuit whilst the not gate is implemented using LUT1.
Now let’s take at look at the circuit we would get if we used blocking assignment in the code.
The verilog code below shows how we could (incorrectly) attempt to model this circuit using blocking assignment.
always @(posedge clock) begin q_dff1 = ~q_dff2; q_dff2 = q_dff1; end
This results in the circuit shown below after synthesis.
We can see from this that using non blocking has resulted in the removal of the second flip flop from our circuit.
The reason for this should be fairly obvious, given what we have learnt about blocking assignment so far. As the value of the q_dffb is immediately assigned to the same value as q_dff1, the circuit model does not imply that there should be a flip flop in this signal path.
This example actually shows us one of the most important differences between blocking and non blocking assignment in verilog.
When we use non-blocking assignment, the synthesis tool will always place a flip flop in the circuit. This means that we can only use non blocking assignment to model sequential logic.
In contrast, we can use blocking assignment to create either sequential or combinational circuits.
However, we should only use blocking assignment to model combinational logic circuits in verilog. The reasons for this is that our code will be much easier to understand and maintain.
Combinational Logic in Always Blocks
Up to this point, we have only considered the modelling of sequential circuits using always block.
Although this is the most common use case, we can also model combinational logic using this approach.
As an example, the code below shows how we can use an always block to model the AND-OR circuit which we discussed in the post on the verilog operators.
always @(a or b or c) begin logic_out = (a & b) | c; end
We see that this code is almost identical to the example in the last post.
The only major difference here is the fact that it’s encased within an always block. We also remove the verilog assign keyword from the statement as we no longer need it.
We can also see from this example how the sensitivity list is more complex for combinational circuits than sequential circuits.
There are actually two methods which we can use to write the sensitivity list when modelling combinational logic circuits.
The first method we can use is to list each of the inputs to the circuit separated by the or keyword. This is the method we have used in the example code above.
Alternatively, we can use the * character, which tells our Verilog tools to automatically decide which signals to include in the sensitivity list. This technique is preferable as it has the advantage of being easier to maintain.
The code snippet below shows how we would use both of these methods
// Sensitivity list with all signals listed always @ (a or b or c) // Sensitivity list using the special character always @ (*)
Generally speaking, using the always block to model combinational logic adds boiler plate code to our design.
Therefore, we only use the always block to model combinational logic circuits in a few circumstances where it can simplify the modelling of complex combinational logic.
One instance where it can be useful to use an always block to model combinational logic is when we want to model a multiplexor.
In this case, we can use a construct known as the case statement to model the multiplexor. In comparison to the methods we discussed in the post on modelling combinational logic in verilog, this provides a simpler and more intuitive way of modelling large multiplexors.
We talk about the verilog case statement in more detailed in a future blog post. However, the code snippet below shows how we would use the case statement to model a simple four to one multiplexor.
always @(*) case (addr) begin 0 : begin // This branch executes when addr = 0 mux_out = a; end 1 : begin // This branch executres when addr = 1 mux_out = b; end 2 : begin // This branch executes when addr = 2 mux_out = c; end 3 : begin // This branch executes when addr = 3 mux_out = d; end endcase end
The case statement is fairly simple to understand, as it uses a variable to select one of a number of branches to execute.
We can include as many different branches as we require in the case statement.
In addition, we use the default branch to catch any values which we haven’t explicitly listed.
In order to use this as a multiplexor, we use the variable as if it were the address pins.
We can then assign the output of the multiplexor to the required value based on which branch we are executing.
What is the difference between continuous assignment and procedural blocks (such as the always block) in verilog?show answer
We use procedural blocks such as the always block to execute code sequentially in verilog. In contrast, continuous assignment is executed in parallel.show hide
Why do we use sensitivity lists in the verilog always block?show answer
They define the list of signals that an always will wait on before resuming the execution of code.hide answer
What is the difference between blocking and non-blocking assignment in verilog?show answer
When we use blocking assignmenthide answer
Which type of assignment can we use in continuous assignment? What about in procedural blocks?show answer
When we write code using continuous assignment, we can only use blocking assignment.
We can use both types of assignment within a verilog procedural block. However, non-blocking assignment normally results in a sequential implementation after synthesis. In contrast to this, blocking assignment normally results in a combinational implementation.hide answer
Write the code for a 4 input NAND gate using an always blockshow answer
always @(*) begin nand_out = a ~& b ~& c ~& d; endhide answer