The behavioral modeling style is a higher abstraction in the entire saga of Verilog programming. By higher abstraction, what is meant is that the designer only needs to know the algorithm of the circuit to code it. Hence, this modeling style is also occasionally referred to as an algorithmic modeling style. The designer does not need to know the gate-level design of the circuit.
In this post, we will take an in-depth look at behavioral modeling. It’s various features, their syntax, statements, and examples. This is a one-stop explanation of behavioral modeling in Verilog.
Contents
What is Behavioral Modeling?
We read about the other abstraction layers earlier in this Verilog course. Behavioral modeling is the topmost abstraction layer. Under this style, we describe the behavior and the nature of the digital system.
There are several ways we can code for a behavioral model in Verilog. We generally use the truth table of the system to deduce the behavior of the circuit, as done in this article: Verilog code for full adder circuit.
Behavioral modeling contains procedural statements that control the simulation and manipulate the data types of the variables involved. There is a ‘procedure’ under which these statements are executed, and this procedure contains a ‘sensitivity list’ that controls the execution of the procedure.
Too many big words?
Fret not!
Before diving into the various types of procedural statements, let’s start with something fundamental. Let’s take a look at how to assign a value to a variable in the Verilog behavioral style.
Continuous assignment statements
These assignments change the output net variables once there is any change in the input signal. It allows the use of Boolean logic rather than gate connections. The left-hand side of an assignment is a variable to which the right-side value is to be assigned and must be a scalar or vector net or concatenation of both. The right-hand side of an assignment, separated from the left-hand side by the equal (=) character, can be a net, a reg, or any expression that evaluates a value including function calls.
Procedural assignment statements
Procedural assignments are employed for updating the memory variables. These assignments control the flow and keep updating the new data to the variables in the left-hand side expression on the activation of the sensitivity list. It represents a logical statement in hardware design. It just represents the boolean logic or the algebraic expression of the circuit. These appear only under the always
block, which has been discussed in later sections.
There are two kinds of procedural assignment statements:
- Blocking statements
- Non-blocking statements
The concept of blocking vs. non-blocking signal assignments is a unique one to hardware description languages. The main reason to use either Blocking or Non-blocking assignments is to generate either combinational or sequential logic.
Blocking statements
Blocking assignments are executed in the order they are coded. Hence, they are sequential. Since they block the execution of the next statement, until the current statement is executed, they are called blocking assignments. The assignment is made with the “=” symbol.
- Example:
// Blocking Assignment initial begin // here, the begin-end clause is used because there are more than one statements in the initial block #10 a = 0; // 10 time units delay has been given a with value 0 #11 a = 1; // 11 time units delay to a variable with value 1 #12 a = 0; // 12 time units delay to a with value 0 #13 a = 1; // 13 time units delay to a with value 1 end
Non-blocking statements
Non-blocking assignments are executed in parallel. Since the execution of the next statement is not blocked due to the execution of the current statement, they are called non-blocking statements. Assignments are made with the “<=” symbol.
- Example:
// Non-blocking Assignment initial begin // These statements will get executed without the intervention of the other statements // In other words, their execution will not be blocked by the other ones #10 b <= 0; // A delay of 10 time units has been given to the variable b with 0 value. #11 b <= 1; // 11 time units delay to b with value 1 #12 b <= 0; // 12 time units delay to b with value 0 #13 b <= 1; // 13 time units delay to b with value 1 end
<= Non-blocking Assignment – Sequential logic
= Blocking Assignment – Combinational logic
Let’s start with the primary construct of a behavioral model.
Structured procedural statement
The primary mechanism for modeling the behavior of design are the following statements:
- Initial statement
- Always statement
These statements execute concurrently with each other. The order of these statements doesn’t matter. The execution of an initial
or always
statements give the program a new control flow. These get executed at time t = 0.
Initial Statement
This executes only once. It begins its execution at the start of the simulation at time t = 0.
- The syntax for the
initial
statement is:
initial
[timing_control] procedural_statement
where a procedural_statement is one of the statements we are going to discuss in this post. The timing control will specify a delay time. A detailed explanation of timing control is discussed further.
- Here is an example of the initial statement.
reg system; initial #12 system=2;
The initial statement executes at time 0, which causes the system
variable to be assigned the value 2 after 12-time units.
Always Statement
In contrast to the initial statement, an always
statement executes repeatedly, although the execution starts at time t=0.
- The syntax for always statement is:
always
[timing_control] procedural_statement
- We may use the following example when we have to provide a clock
clk
signal to a system in Verilog.
always @posedge clk #5 clk=~clk;
This always statement produces a waveform with a period of 10-time units that only change upon the positive edge (thus the keyword posedge
) of the signal. The time unit is defined in the timescale directive compiler.
Procedural continuous statement
A procedural continuous assignment is a procedural statement, that is, it can appear inside an always
statement block or an initial
statement block.
Now, this assignment can override all other assignment statements to a net or a register.
Point to be noted here is, this is different from a continuous assignment; a continuous assignment occurs outside the initial
or always
block.
There are two kinds of procedural continuous assignments.
- Assign – deassign: these assign to registers.
- Force – release: these primarily assign to nets, although they can also be used for registers.
Assign – deassign
The keywords assign
and deassign
can be used for registers or a concatenation of registers only. It can not be used for memories and bit- or part-select of a register.
- The syntax is:
assign register_name = expression;
deassign register_name;
- Consider the example:
if (preset) assign q = 1; // assign procedural statement else deassign q; // deassign procedural statement
Force – release
The keywords force
and release
can be used for nets, registers, bit- or part select of a net (not register), or a concatenation.
- The syntax is:
force net_or_register_name = expression;
release net_or_register_name;
- Here’s an example; you’d notice that’s not much different from the procedural statement in the previous section.
if (preset) force q = preset; // force procedural statement else release q; // release procedural statement
Deassign and release de-activate a procedural continuous assignment. The register value remains after the de-activation until a new value is assigned.
Block statement
A block statement enables a procedure to execute a group of two or more statements to act and execute syntactically like a single statement. There are two types of block statements. These are:
- Sequential Block
- Parallel Block
Sequential Block
Statements inside this block are executed sequentially. You can add delay time in each of its statements.
That will be relative to the simulation time of the execution of the previous statement. Once the execution of the current sequential block is over, the statements or blocks followed just after the current block will get executed.
Now, this sequential block is demarcated by the keywords begin
… end
, which marks the beginning of the block, just like any high-level programming language (like the C programming language).
- The syntax for a sequential block is:
begin
procedural_statements;end
- Here is an example of a waveform generation:
begin #2 clk = 1; #5 clk = 0; #3 clk = 1; #4 clk = 0; end
In the above example, assume that the sequential block will execute for 10-time units. The first statement, thus, executes after 12-time units. The second statement after 17-time units and so on.
Parallel Block
A parallel block has the delimiters fork
… join
(the sequential block has begin
… end
). The statements in the parallel block are executed concurrently.
Stated another way, all the statements inside a block, need to be executed before the control passes out of the block.
- The syntax for a parallel block is:
fork
procedural_statements;join
- Let’s take an example to show how the delay time works in the parallel block.
fork #19 clk=~clk; #10 clk=~clk; #14 clk=~clk; #20 clk=~clk; join
Assume that the simulation time for the above example is 10-time units. Now the first statement will be executed after 10 + 19 = 29-time units, the second statement after 20-time units, and the last statement will take 30-time units. Therefore, after 30-time units, the execution control will be transferred out of the block.
Timing control
The timing control is usually associated with procedural statements. These are helpful in providing a delay to a particular statement and expression or can make up the sensitivity list Let’s say we are dealing with a design where the operation is sensitive to an event, say, a particular edge on the clock signal. In this case, the sensitivity list will consist of the timing control.
Two types of controls exist:
- Delay control
- Event control
Delay control
This is useful when we want some time gap or delay between the execution of one or more statements. It is basically a “wait for delay” before executing that statement in which delay has been provided.
- The syntax for a delay control is:
#delay procedural_statement;
- Here is an example:
initial begin #2 clk=1; end
The initial statement starts its execution at 0 time. The value of clk
gets assigned to 1 every 2 seconds.
Event control
The execution of the statements can be synchronized with the change in the event. This event is controlled by the governing signals. Now there are two types of event control:
- Edge triggered event control
- Level sensitive event control
The form of an edge trigger event control is:
@ event
procedural_statement;
as in the example:
@ event (posedge clock) c = n;
The statement where the value of n variable is transferred to c output gets activated once there occurs a positive edge to the clock signal.
posedge
is detected when the signal goes from 1 to unknown or from 0 to unknownnegedge
is detected when the signal goes from 0 to unknown or from 1 to unknown
The level-sensitive event control is basically a type of wait statement. It waits for a condition to become true and then it’ll carry forward it’s operation.
- Here is the syntax:
wait
( condition )
procedural_statement;
The procedural statement will execute if the condition is evaluated out to be true, otherwise, it will wait for the condition to become true. If the condition is already true then the statement will be executed immediately.
- Example of this:
wait (s < 22) sum=0;
In this instance, the statement sum=0 will execute once the value of s variable is greater than 22.
Conditional statement
The conditional statements are used to decide whether a statement will be executing or not by evaluating a certain condition.
if statement
Now the basic syntax for an if-statement is:
if
(condition_1) procedural_statement_1;
If the condition_1 is evaluated to be a true expression, then the further procedural statements are executed.
The example below shows that the sum
variable has a value of less than 56 which justifies the execution of the statements followed in the begin
… end
block.
if(sum<56) begin grade=C; total=total+1; end
if-else statement
- Syntax:
if
(condition_1) procedural_statement_1;else
procedural_statement_2;
If condition_1 is true, procedural_statement_1 is executed, otherwise procedural_statement_2 is executed.
- Consider the following example:
if(reset) output=0; else output=input;
nested if-else-if
- The syntax for nested if-else-if is:
if
(condition_1) procedural_statement_1;else if
(condition_2) procedural_statement_2;else
procedural_statement_3;
If condition_1, and condition_2, are evaluated as a true expression, then, procedural_statement_1 and procedural_statement_2 will execute respectively and explicitly. Otherwise, the third procedural statement procedural_statement_3 is executed. This syntax combines each category. You may either use a single if-else block or nest up according to your needs of the circuit. Here is the code for the full adder circuit in behavioral modeling using the if-else statement.
Case statement
The case statement is a multi-way deciding statement which first matches a number of possibilities and then transfers a single matched input to the output. This is most useful in decoding various operations inside a processor.
- The syntax is a follows:
case
(expression) case_item1 : single statement; case_item2 : single statement; case_item3 : begin multiple statements end default : statement endcase
Visit this post to see how the case statement can be efficiently used in implementing a demultiplexer.
Don’t care in a Case statement
In the case statement described in the above section, the values x and z are interpreted literally. There are two other forms of case statements: casex and casez. The syntax is the same as that for a case statement. The only difference is in the keyword.
- In
casez
statement, the value z appears in the case expression, and if any case_item is considered as a don’t care, that bit is discarded. - In
casex
statement, both the values for the x and z are considered as don’t cares.
Loops
There are four looping statements in Verilog:
- while loop
- for loop
- repeat loop
- forever loop
While loop
The syntax for a while loop is:
while
(condition) procedural_statement;
This loop will keep on iterating and executing till the condition is evaluated to be false (0 value). If the condition is an undefined or impedance value, then it is taken as a false statement, hence the loop and the statements under, will not be executed.
- Here’s an example:
while (en>0) begin a=a << 1; b= b - 1; end
For loop
This loop statement is of the form:
for
(initial_assignment ; condition ; step_assignment)
A for loop statement repeats the execution of the procedural statements for a certain number of times till the condition is true. The initial_assignment statement specifies the initial value of the loop or the index variable. The condition specifies the condition for which the loop will keep executing, and the step_assignment mostly directs the variable to be incremented or decremented.
- For example:
integer i; for(i=0; i<n-1; i++) Bus[i]=Bus[i+1];
Repeat loop
This loop, as the name suggests, repeats the execution of the procedural_statement a specified number of times.
- The syntax for a repeat loop is:
repeat
(loop_count) procedural_statement;
In the example below, the loop_count is denoted by count, and the procedural_statement sum=sum+10 will be executed till the count.
repeat (count) sum=sum+10;
Forever loop
- The syntax is:
forever
procedural_statement;
This loop continuously executes the procedural_statement. Thus to get out of such kind of loop, a disable statement may be used with the procedural statement.
- Here is an example of this form of the loop.
initial forever #10 clk=~clk;
This was an in-depth glossing over of the main elements of the behavioral modeling style in Verilog. As always, if there are any doubts, let us know in the comments section. Make sure to apply these concepts in your programming practice routines. Check out the various examples in the sidebar for behavioral modeling for reference.