Behavioral Modeling in Verilog - Part 19
2025-08-11 | By DWARAKAN RAMANATHAN
Introduction:
Behavioral modeling in Verilog describes circuit behavior at high abstraction levels using high-level structures similar to high-level languages like C. Structural or dataflow modeling is hardware, but not functionality. Behavioral modeling allows designers to describe circuits at a level of abstraction using variables, conditions, and loops to describe how signals flow through the design. Procedural blocks, like Always and Initial, are a key part of behavioral modeling. The blocks control how the system responds to control inputs and changing signals. For example, an always block is employed to represent flip-flops, counters, and state machines, and an initial block is typically employed for simulation-specific activities like initializing values or printing debugging messages.
To add variables to behavioral modeling, we predominantly employ reg and integer data types. reg type retains data from clock cycle to clock cycle and therefore is a significant part in sequential logic, and integer is easy to work with for loop counters and arithmetic. Behavioral modeling is robust in the sense that it can add decision-making and iteration features and thus make designs readable and manageable.
Block vs Non-Blocking Assignments
In Verilog, one of the most important things when it comes to behavioral modeling is to understand the Blocking and Non-blocking assignments. Whether an assignment is Blocking (=) or Non-Blocking (<=) completely determines the behavior and the order of execution of simulation.
The blocking assignments will execute immediately within the same simulation cycle and in a top-down manner. Which means that if a next statement is there in the same always block, then the next statement will not proceed until the current one completes.
always @(posedge clk) begin a = b + c; // Blocking assignment d = a * 2; // Executes after 'a' is updated end
In contrast to blocking assignments, non-blocking assignments don’t update the value immediately, but they are scheduled to take effect at the end of the simulation cycle. For sequential logic and flip-flops, this is the preferred approach since it does not leave any chance of unintended race conditions and ensures that all assignments within a clock cycle occur simultaneously.
always @(posedge clk) begin a <= b + c; // Non-blocking assignment d <= a * 2; // Uses previous value of 'a' end
If a=b+c and b+c updates a, d will use the old value of a as non-blocking assignments postpone their updates. For proper functioning of a sequential circuit, it is recommended to use <= in flip-flop based designs and = for combinational logic inside an always @(*) block.
Sequential vs Parallel Execution:
SystemVerilog supports parallel and sequential execution. Let's look at an example to understand them.
Sequential execution is when one statement gets executed at a time inside a begin-end block. Sequential execution can be useful to model current State Machines, Counters, and any circuits where the order of execution matters each and every time.
always @(posedge clk) begin a = b + c; d = a * 2; // This executes only after 'a' is assigned end
Parallel execution is when multiple always blocks will be executing concurrently, or values are being assigned by a different always block. This example is the best example to refer to if you get confused: updating b and c in parallel.
always @(posedge clk) a = b + c; always @(posedge clk) d <= a * 2;
In the above example, a and d update independently, allowing for parallel processing. This concept is fundamental in designing pipelined and multi-threaded hardware architectures.
Control Flow Constructs:
With behavioral modeling, control flow constructs come into play, and hence Verilog starts looking a lot like other conventional programming languages, with respect to flexibility.
If-else Statements
This feature of a typical programming language allows some conditional execution based on the values of the signal, and it's a bitter fact. The following examples give you a clear idea of how If-else statements are used.
always @(posedge clk) begin if (enable) out <= data; else out <= 0; end
Case Statements
The case statement is used when there are multiple things to do, and a single thing has to be selected. It's widely useful in Multiplexers and state machines.
always @(posedge clk) begin case(sel) 2'b00: out = in0; 2'b01: out = in1; 2'b10: out = in2; 2'b11: out = in3; default: out = 0; endcase end
Verilog Loop Constructs
Verilog also provides several different loop constructs, such as for, while, repeat, and forever, that are useful when we want to specify repeated behavior for hardware description or testbench.
integer i; always @(posedge clk) begin for (i = 0; i < 8; i = i + 1) begin mem[i] = 0; end end
While loop runs until some condition becomes true, repeat runs a defined number of times. The forever statement will be executed endlessly and is typically controlled by other mechanisms, like disable, wait, etc., in testbenches.
Event-Driven Simulation and Verilog Non-Hardware Constructs
Verilog simulations are event-driven by nature, meaning a change in signal values causes events to be triggered, which in turn causes blocks of code to be run (like always or initial). This simulation strategy makes the simulation process generally more efficient than other methodologies. Instead of re-running the entire simulation over again at each time step, only those parts of the design that have changed are re-evaluated. The initial block is the perfect place for all your testbench and non-synthesizable code. It executes once at the beginning of the simulation. The initial block in Verilog's procedural part is just like main () in C. When the simulation starts, initial blocks will be executed to model some initial events that must have occurred before time 0. You can apply test vectors and print output using various delay functions, like #, and also show debugging information using the $Display and $Monitor.
Verilog Initial block
Here we have a simple testbench leveraging the idea of Event-Driven and Non-Hardware Constructs:
module test; reg clk, reset; initial begin clk = 0; forever #5 clk = ~clk; // Clock toggles every 5 time units end initial begin reset = 1; #15 reset = 0; #100 $finish; end initial begin $monitor("Time = %0t, clk = %b, reset = %b", $time, clk, reset); end endmodule
These constructs (initial, #, $monitor) are extensions to behavioral modeling in use only for simulation, testing, and debugging purposes. Since they are not eligible for physical implementation (synthesizable), that means they won’t be translated into a hardware block. They are vital validation and verification activities, too.
Optimizing Simulation Performance
When our design is more complex and larger, one important issue is how to make the simulation faster and save more time when we are using behavioral modeling. Below are some points that may increase the performance of the simulation.
- Remove unnecessary always blocks. Combining logic is possible to reduce sensitivity list complexity.
- Use @(*) in combinatorial logic. Makes the simulator only evaluate when needed.
- Avoid using delays (#) Synthesizable logic unnecessarily. Signals are needed in testbenches for time delays, but they are overhead in terms of final designs.
- Break your testbench into more functions and tasks so that any test procedure can be reused easily.
- To avoid the condition that will create spikes in the output, it is recommended to put a default: case on floating or undefined conditions on any specific output signal.
Conclusion:
Behavioral modeling in Verilog is a powerful way to design digital circuits at a high level of abstraction. It allows the creation of efficient, synthesized models and robust testbenches by utilizing procedural blocks for combinational and sequential execution, event-driven simulation, and non-hardware constructs, like initial and $display. Having a grasp of the principles of parallelism, correct types of assignment, and simulation optimization allows for faster development cycles with less design bugs. From implementing a processor to debugging a simple counter, behavioral modeling makes the HDL experience more intuitive and productive.