View Course Path

Behavioral Modeling Style in Verilog

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 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.

A procedure in Verilog corresponds to the same context that a function in C programming does. It is a particular block of statements called procedural statements. If we compare it with the high-level language, it comes out to be that the function arguments and parameters in a language like C, Python are the same as that of the procedural statements.
The sensitivity list controls when all statements in the always block (explained later) will start to be evaluated. It is a set of ports or variables defined for a block that will stimulate the rest of the statements.

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:

  1. Blocking statements
  2. 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:

  1. Initial statement
  2. 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.

A net represents the physical connections between logic gates. There are many types of nets. For example wire, wand, wor, tri, triand, uwire, etc. Wire is the most commonly used type of a net. Reg can be used to model physical registers that can hold a value. (Net’s can’t store values). However, it is not always necessary that a reg element is always a storage device. 

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.

  1. Assign – deassign: these assign to registers.
  2. 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.

It’s possible that you might end up in confusion about whether to assign the variable or go with the force syntax. Here’s a handy tip. Go forth with the assign statement whenever you’re using a net variable. It is necessary, however, to use a delay statement after overriding a statement by the later one. You can use force and release while doing gate-level simulation to work around reset connectivity problems

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:

  1. Sequential Block
  2. 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 beginend, 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 forkjoin (the sequential block has beginend). 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:

  1. Delay control
  2. 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:

  1. Edge triggered event control
  2. 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 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 )
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 beginend 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:

  1. while loop
  2. for loop
  3. repeat loop
  4. 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.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Top