Behavioral Modelling โ Part 3
Deep coverage of if/if-else constructs, assign-deassign, the repeat construct, for loops, and the disable statement โ with complete synthesizable and testbench examples.
๐ if and if-else Constructs โ Introduction
The if statement is the most fundamental control flow construct in behavioral Verilog. It allows a procedural block to conditionally execute statements based on the value of an expression โ modelling priority logic, enable conditions, and state transitions.
When synthesized, an if-else chain produces a priority multiplexer tree โ the first condition that evaluates true wins. This is fundamentally different from case, which produces a balanced multiplexer without priority.
else if has lower priority than the preceding condition โ synthesis produces a cascaded priority chain.else in a combinational block leaves outputs undefined for some conditions โ synthesis infers a latch.begin...end blocks for multiple statements in any branch.๐ Syntax and Forms
Verilog provides three forms of the if construct, each building on the previous:
// โโ Form 1: Simple if โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ if (condition) statement; // executes only when condition is TRUE // no else โ output holds previous value! // โโ Form 2: if-else โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ if (condition) statement_true; // executes when condition is TRUE else statement_false; // executes when condition is FALSE // โโ Form 3: if-else if-else chain โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ if (cond_1) stmt_1; // highest priority else if (cond_2) stmt_2; else if (cond_3) stmt_3; else stmt_4; // lowest priority โ catches all other cases // โโ Multi-statement branches: always use begin...end โโโโโโโโโโโ if (rst_n == 1'b0) begin count <= 8'h00; flags <= 4'b0000; valid <= 1'b0; end else begin count <= count + 1; valid <= 1'b1; end
Fig 2 โ if-else Decision Diagram
Condition Evaluation Rules
| Condition value | if branch taken? | Explanation |
|---|---|---|
| 1 (or any non-zero) | โ Yes โ TRUE | Any non-zero value is treated as logic true |
| 0 | โ No โ FALSE | Zero means false, else branch taken |
| x (unknown) | โ No โ FALSE | Unknown treated as false โ else branch taken |
| z (high-Z) | โ No โ FALSE | High-impedance treated as false |
else branch silently. This can hide bugs during simulation โ always ensure condition signals are properly initialized and driven.
โ ๏ธ Latch Avoidance
The most critical rule for if statements in combinational always @(*) blocks: every output must be assigned in every possible execution path. If a path exists where an output isn’t assigned, the synthesis tool infers a latch to hold the previous value.
โ Missing else โ Latch inferred
always @(*) begin if (en) out = data; // No else! // When en=0: out holds // โ synthesis infers latch end
โ Default assignment โ No latch
always @(*) begin out = 8'h00; // default first if (en) out = data; // override if en // All paths covered // โ pure combinational end
โ Multi-output: one missed
always @(*) begin if (sel) begin a = x; b = y; end else begin a = p; // b not assigned! // b gets a latch end end
โ All outputs defaulted first
always @(*) begin a = 8'h00; // defaults b = 8'h00; if (sel) begin a = x; b = y; end else a = p; // b defaults to 0 โ end
๐ Priority vs Parallel Decode
The choice between if-else and case has direct hardware implications. Understanding which to use is a key RTL design skill.
๐ฃ if-else โ Priority tree (cascaded)
always @(*) begin if (irq3) grant=2'b11; else if (irq2) grant=2'b10; else if (irq1) grant=2'b01; else grant=2'b00; end // irq3 has highest priority โ // even if irq2 also asserted, // grant still = 2'b11
๐ต case โ Balanced MUX (equal cost)
always @(*) begin case (sel) 2'b00: out=in0; 2'b01: out=in1; 2'b10: out=in2; 2'b11: out=in3; endcase end // All branches equal priority // No priority tree โ faster
| Design need | Use | Reason |
|---|---|---|
| Priority interrupt controller | if-else | Highest-numbered IRQ must always win |
| ALU operation selector | case | All opcodes equal โ no priority needed |
| Async reset / sync load | if-else | Reset must dominate over all other conditions |
| State machine output decode | case | States are mutually exclusive โ equal cost MUX |
| One-hot priority encode | if-else or casez | Need to detect highest-priority set bit |
๐ก if / if-else Examples
Fig 4 โ Synchronous counter with enable, load, and reset
module counter_full ( input clk, rst_n, input en, load, input [7:0] load_val, output reg [7:0] cnt ); always @(posedge clk or negedge rst_n) begin if (!rst_n) cnt <= 8'h00; // highest: async reset else if (load) cnt <= load_val; // sync load else if (en) cnt <= cnt + 1; // count when enabled else cnt <= cnt; // hold (explicit) end endmodule
Fig 5 โ Nested if: 2-bit Arithmetic Logic Unit
always @(*) begin result = 8'h00; // default โ prevents latches carry = 1'b0; ovf = 1'b0; if (mode == 1'b0) begin // Arithmetic mode if (sub) begin {carry, result} = a - b; ovf = (a[7] != b[7]) && (result[7] != a[7]); end else begin {carry, result} = a + b; ovf = (a[7] == b[7]) && (result[7] != a[7]); end end else begin // Logic mode if (op == 2'b00) result = a & b; else if (op == 2'b01) result = a | b; else if (op == 2'b10) result = a ^ b; else result = ~a; end end
Fig 6 โ if in Sequential Logic: Moore FSM Traffic Light
module traffic_fsm ( input clk, rst_n, output reg [2:0] lights // [2]=Red [1]=Amber [0]=Green ); localparam RED=2'd0, GREEN=2'd1, AMBER=2'd2; reg [1:0] state; reg [4:0] timer; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin state <= RED; timer <= 5'd30; end else begin timer <= timer - 1; if (timer == 0) begin // timer expired โ change state if (state == RED) begin state <= GREEN; timer <= 5'd25; end else if (state == GREEN) begin state <= AMBER; timer <= 5'd5; end else begin state <= RED; timer <= 5'd30; end end end end // Output decode (combinational) always @(*) begin lights = 3'b000; if (state == RED) lights = 3'b100; else if (state == GREEN) lights = 3'b001; else lights = 3'b010; end endmodule
๐ assign โ deassign Construct
The procedural assign and deassign statements are a special construct used inside procedural blocks to override the normal assignment to a reg variable. They force a register to follow a continuous expression โ ignoring any normal procedural assignments โ until deassign is called to release it.
assign keyword (data-flow level) is completely different from the procedural assign statement used inside always/initial blocks. The procedural form overrides the reg‘s normal driving.
assign continuously drives a reg with an expression, overriding any clock-triggered assignments to it.deassign releases the override. The reg retains its last forced value until a normal procedural assignment updates it.assign/deassign are not supported by synthesis tools. Use only in simulation and testbenches.// โโ Basic syntax โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ assign reg_var = expression; // force reg_var to track expression deassign reg_var; // release; reg_var holds last value // โโ Modelling an asynchronous clear D flip-flop โโโโโโโโโโโโโโโ module dff_async_clr ( input clk, d, clr, // clr: active-high async clear output reg q ); always @(clr) begin if (clr) begin assign q = 1'b0; // force q=0 asynchronously whenever clr=1 end else begin deassign q; // release q โ normal clocking resumes end end always @(posedge clk) begin q <= d; // normal D capture (overridden when clr=1) end endmodule
// Force an internal signal to a specific value for N cycles initial begin // Normal stimulus #100; // Force internal counter to max value to test overflow assign dut.counter = 8'hFF; // hierarchical assign @(posedge clk); // wait one clock deassign dut.counter; // release โ normal operation // Verify overflow flag asserted @(posedge clk); if (!dut.overflow) $display("FAIL: overflow not detected"); else $display("PASS: overflow detected correctly"); $finish; end
assign / deassign vs force / release
| Construct | Targets | Overrides | Use case |
|---|---|---|---|
| assign / deassign | reg only | Procedural assignments | Modelling async preset/clear |
| force / release | reg and wire | All drivers including continuous assigns | Testbench debugging, fault injection |
force/release and UVM register backdoor access have largely replaced assign/deassign in testbenches. However, assign/deassign remains useful for modelling async clear/preset in behavioral flip-flop models.
๐ The repeat Construct
The repeat statement executes a statement or block a fixed, pre-determined number of times. The count is evaluated once at the start โ it cannot be changed during execution. This makes repeat ideal for testbench timing (waiting N clock cycles) and simple initialization patterns.
evaluated once at start
repeat has no built-in loop variable. Use for instead when you need an index.repeat(N) @(posedge clk); โ wait exactly N clock cycles before the next test step.repeat with constant count can synthesize if the body is simple combinational logic (loop unrolling). With delays or events, it is simulation-only.๐ก repeat Examples
Fig 9 โ repeat: timing control in testbenches
// โโ Wait N clock cycles โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ repeat(5) @(posedge clk); // wait 5 rising edges repeat(10) @(negedge clk); // wait 10 falling edges // โโ Generate N clock pulses โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ repeat(8) begin clk = 0; #5; clk = 1; #5; // 8 complete clock periods end // โโ Apply a burst of N data bytes โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ integer byte_idx; byte_idx = 0; repeat(4) begin @(posedge clk); tx_data = payload[8*byte_idx +: 8]; byte_idx = byte_idx + 1; end // โโ Variable count (evaluated once at entry) โโโโโโโโโโโโโโโโโโโ integer n_cycles; n_cycles = 12; repeat(n_cycles) @(posedge clk); // waits exactly 12 cycles // Changing n_cycles inside the loop has no effect on count // โโ Synthesizable repeat: bit counting (constant iterations) โโโ integer ones; reg [7:0] tmp; ones = 0; tmp = data; repeat(8) begin ones = ones + tmp[0]; // count 1s in LSB tmp = tmp >> 1; // shift right end // ones = popcount(data)
Fig 10 โ Serial data transmitter using repeat
task uart_send_byte; input [7:0] byte_in; integer bit_idx; begin // Start bit (low) tx = 1'b0; @(posedge baud_clk); // Data bits โ 8 iterations using repeat bit_idx = 0; repeat(8) begin tx = byte_in[bit_idx]; // LSB first @(posedge baud_clk); bit_idx = bit_idx + 1; end // Stop bit (high) tx = 1'b1; @(posedge baud_clk); $display("Sent: 0x%02h", byte_in); end endtask
๐ The for Loop
The for loop is the most versatile loop construct in Verilog. It provides an index variable, an exit condition, and a step expression โ all in one compact header. Unlike repeat, you have full access to the loop counter inside the body.
// โโ Basic syntax โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ for (init; condition; step) begin statement; end // โโ Simple 8-iteration example โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ integer i; for (i=0; i<8; i=i+1) begin out[i] = in[7-i]; // bit reversal: out[0]=in[7], out[7]=in[0] end // โโ Count down โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ for (i=7; i>=0; i=i-1) begin $display("Bit %0d = %b", i, data[i]); end // โโ Step by 2 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ for (i=0; i<16; i=i+2) begin even_mem[i/2] = mem[i]; // pack even-addressed bytes end
i = 0 โ runs exactly once before the loop startsi < N โ if FALSE โ exit loop; if TRUE โ execute bodyi = i + 1 โ then go back to step 2๐ก for Loop Examples
Fig 13 โ Memory operations with for loop
reg [7:0] mem_a[0:255], mem_b[0:255]; integer i, j; initial begin // Zero-initialise all 256 locations for (i=0; i<256; i=i+1) mem_a[i] = 8'h00; // Fill with ascending values for (i=0; i<256; i=i+1) mem_a[i] = i[7:0]; // Deep copy: mem_b โ mem_a for (i=0; i<256; i=i+1) mem_b[i] = mem_a[i]; // Linear search: find value 0xAB j = -1; for (i=0; i<256; i=i+1) if (mem_a[i] == 8'hAB) j = i; if (j >= 0) $display("Found 0xAB at address %0d", j); end
Fig 14 โ Synthesizable for: CRC calculation
module crc8 ( input [7:0] data_in, input [7:0] crc_in, // running CRC (feed 8'h00 initially) output reg [7:0] crc_out ); integer i; reg [7:0] tmp; always @(*) begin tmp = data_in ^ crc_in; // 8 iterations โ synthesizer unrolls this into 8 XOR stages for (i=0; i<8; i=i+1) begin if (tmp[7]) tmp = (tmp << 1) ^ 8'h07; // CRC-8/SMBUS polynomial else tmp = (tmp << 1); end crc_out = tmp; end endmodule
Fig 15 โ for loop: Barrel shifter (synthesizable)
module barrel_shift #(parameter N=8) ( input [N-1:0] data_in, input [$clog2(N)-1:0] shift_amt, output reg [N-1:0] data_out ); integer i; always @(*) begin data_out = {N{1'b0}}; for (i=0; i<N; i=i+1) begin if (i >= shift_amt) data_out[i] = data_in[i - shift_amt]; end end endmodule
for loop with a constant bound (e.g., i < 8) is synthesized by unrolling โ the tool replicates the body N times. This is how complex combinational logic (CRC, parity, popcount) is efficiently described in few lines of RTL. Variable bounds are generally not synthesizable.
๐ The disable Construct
The disable statement terminates execution of a named block or named task, skipping any remaining statements within it. It is Verilog’s equivalent of break (for loops) or early return (for tasks) in C, but operates on named scopes rather than loop constructs.
disable works only on named begin-end blocks or named tasks. Anonymous blocks cannot be disabled.break in a loop or return from a function.disable is a simulation construct. Synthesis tools do not support it โ use it in testbenches only.๐ก disable Examples
Fig 17 โ break from a for loop using disable
// โโ Linear search: stop at first match โโโโโโโโโโโโโโโโโโโโโโโโ integer i, found_addr; found_addr = -1; begin : search_loop // name the block โ required for disable for (i=0; i<256; i=i+1) begin if (mem[i] == target) begin found_addr = i; disable search_loop; // โ break out of the for loop end end end // search_loop ends here โ disable jumps to this point if (found_addr >= 0) $display("Found at address %0d", found_addr); else $display("Not found");
Fig 18 โ Timeout using disable in a fork-join
initial begin : sim_top fork // Thread 1: wait for DUT to finish begin : wait_done wait(dut_done); $display("DUT completed at t=%0t", $time); disable timeout_wdog; // cancel the timeout end // Thread 2: watchdog timeout begin : timeout_wdog #10000; // maximum allowed time $display("TIMEOUT โ DUT did not finish!"); disable wait_done; // cancel the wait thread $finish; end join end
Fig 19 โ disable inside a named task
task send_packet; input [7:0] pkt [0:3]; integer k; begin : send_body // Check link is up before sending if (!link_up) begin $display("ERROR: Link down โ aborting send"); disable send_body; // early exit โ equivalent to return end // Send 4 bytes for (k=0; k<4; k=k+1) begin @(posedge clk); tx_data = pkt[k]; tx_valid = 1'b1; // Error injection check mid-packet if (error_injected) begin $display("Aborting mid-packet at byte %0d", k); disable send_body; // abort the for loop and task end end tx_valid = 1'b0; $display("Packet sent OK"); end // send_body endtask
๐ Summary and Comparison
A quick reference comparing all five constructs covered in this module:
| Construct | Purpose | Has loop variable? | Synthesizable? | Primary use |
|---|---|---|---|---|
| if / if-else | Conditional branch โ execute one path or another | โ | โ Yes | RTL priority logic, reset/load/enable chains |
| assign / deassign | Force a reg to track an expression; release it | โ | โ No | Async clear/preset models, testbench forcing |
| repeat(N) | Execute body exactly N times | โ No index | โ ๏ธ Constant N only | Testbench: wait N clocks, N-cycle bursts |
| for (init; cond; step) | Counted loop with full index control | โ Yes (integer) | โ ๏ธ Constant bound only | RTL: bit ops, CRC; TB: memory init, search |
| disable | Early exit from a named block or task | โ | โ No | Testbench: break from loops, timeouts, abort |
if / if-else โ synthesizable, models priority hardwareassign / deassign โ simulation only, models async controlrepeat(N) โ compact, testbench-friendly, no loop variablefor โ RTL (const bound) or testbench (variable bound)disable on a named block โ simulation onlyalways @(*) and always @(posedge clk)), stick to if/else and for with constant bounds. Everything else โ assign/deassign, disable, and repeat with event controls โ belongs in testbenches and simulation models only.
