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.
What is Behavioral Modeling?
We read about the abstraction layers in Verilog earlier in this 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?
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.
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 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 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.
// 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 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.
// 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
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
always statements give the program a new control flow. These get executed at time t = 0.
This executes only once. It begins its execution at the start of the simulation at time t = 0.
- The syntax for the
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.
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:
- We may use the following example when we have to provide a clock
clksignal 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
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;
- Consider the example:
if (preset) assign q = 1; // assign procedural statement else deassign q; // deassign procedural statement
Force – release
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;
- 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.
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
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
end, which marks the beginning of the block, just like any high-level programming language.
- The syntax for a sequential block is:
- 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.
A parallel block has the delimiters
join (the sequential block has
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:
- 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.
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
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:
- 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.
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:
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 unknown
- negedge 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 )
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.
The conditional statements are used to decide whether a statement will be executing or not by evaluating a certain condition.
Now the basic syntax for an if-statement is:
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 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;
- The syntax for nested if-else-if is:
else if(condition_2) procedural_statement_2;
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.
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_item1 : single statement;
case_item2 : single statement;
case_item3 : begin
default : statement
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.
There are four looping statements:
- while loop
- for loop
- repeat loop
- forever loop
The syntax for a while loop is:
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
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];
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:
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;
- The syntax is:
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.