The highest level of abstraction in Verilog — describe what a circuit does using procedural constructs, blocking and non-blocking assignments, and the full power of initial and always blocks.
Behavioral modelling is the highest level of abstraction in Verilog. Instead of describing circuit structure (gate level) or signal flow (data flow), it describes what the circuit does — its algorithm, its sequence of operations, its response to events — and lets the synthesis tool figure out the hardware implementation.
Behavioral code looks very similar to a C program: it uses variables, conditional statements, loops, and sequential execution inside procedural blocks. This makes complex designs fast to write, easy to read, and straightforward to simulate.
initial or always blocks — procedural contexts where statements execute one after another.assign statements which all run simultaneously.reg (and integer, real, time) variables — never directly to wire nets.initial, #delays, and some loop forms are simulation-only.// ── Gate Level (8 NAND gates, cross-coupled) ────────────────── nand g1(s, d, clk); nand g2(r,s,clk); // ... 6 more gates // ── Data Flow Level (complex expression) ────────────────────── // Not natural for flip-flops — behavioral is much better // ── Behavioral Level (readable, concise) ────────────────────── always @(posedge clk or negedge rst_n) begin if (!rst_n) q <= 1'b0; // async reset else q <= d; // capture D on rising edge end
Inside a procedural block, statements assign values to reg variables using one of two assignment operators — blocking (=) and non-blocking (<=). Choosing the correct one is critical for correct simulation and synthesis.
always @(posedge clk) begin a = b; // a gets b NOW b = a; // b gets new a! end // Result: a=b, b=b (b unchanged) // NOT a swap!
always @(posedge clk) begin a <= b; // schedule: a ← b b <= a; // schedule: b ← a end // Result: a=old_b, b=old_a // ✅ Correct register swap!
always @(*)always @(posedge clk) blocks// ── Variable Assignments ────────────────────────────────────── a = b & c; // blocking — immediate update q <= d; // non-blocking — deferred update // ── Conditional Statements ──────────────────────────────────── if (en) out = in; else if (rst) out = 0; else out = out; // hold — may infer latch! // ── Case Statements ─────────────────────────────────────────── case (sel) 2'b00: out = in0; 2'b01: out = in1; default: out = 8'bx; endcase // ── Loops ───────────────────────────────────────────────────── for (i=0; i<8; i=i+1) mem[i] = 8'h00; // initialise memory // ── Timing Controls ─────────────────────────────────────────── #10; // delay 10 time units (sim only) @(posedge clk); // wait for rising clock edge @(a or b); // wait for any change on a or b wait(done); // wait until done goes high // ── System Tasks (simulation) ───────────────────────────────── $display("q = %b", q); $finish;
A blocking assignment blocks execution of the next statement until it completes. The updated value is immediately available for subsequent statements in the same block — just like a variable assignment in C.
// Combinational always block — use blocking assignments always @(*) begin p = a & b; // p updated immediately q = p | c; // q uses the NEW value of p ✅ y = ~q; // y uses the NEW value of q ✅ end // Using a temporary variable (blocking only makes sense) always @(*) begin tmp = a + b; // intermediate result result = (tmp > 8'hFF) ? 8'hFF : tmp; // saturate end
A non-blocking assignment schedules an update without blocking. All RHS values are captured first, then all LHS targets are updated simultaneously at the end of the current time step. This is how real flip-flops behave — all capture their inputs at the same clock edge.
// Clocked sequential block — use non-blocking assignments always @(posedge clk) begin q0 <= d; // captures d at clock edge q1 <= q0; // captures OLD q0 — correct shift-register behaviour ✅ q2 <= q1; // captures OLD q1 end // All three capture simultaneously — models 3-stage shift register // With blocking (=): q1=q0=d, q2=q1=d — all become d! Wrong!
q <= input reads the old value of its input, so the pipeline shifts correctly.Verilog’s behavioral model provides two distinct procedural constructs — initial and always. This split (bifurcation) is intentional: each serves a fundamentally different purpose in the design and verification flow.
initial blocks in one module all start simultaneously#delays and event controls for timingalways blocks in one module all run concurrentlyposedge clk) or sensitivity lists (@(*))The initial block executes its body exactly once, starting at simulation time zero. It is used almost exclusively in testbenches — for driving stimulus, initialising memories, and controlling simulation flow.
// ── Single statement ────────────────────────────────────────── initial clk = 0; // no begin/end needed for one statement // ── Multiple statements — needs begin/end ───────────────────── initial begin clk = 0; reset = 1; data = 8'h00; #20 reset = 0; // release reset after 20 time units #5 data = 8'hAB; // apply data at t=25 #10 data = 8'hCD; // change data at t=35 #50 $finish; // end simulation at t=85 end // ── Multiple initial blocks (all run at t=0) ────────────────── initial clk = 0; // block 1: initialise clock initial begin // block 2: apply test stimulus rst = 1; #10 rst = 0; end initial begin // block 3: monitor outputs $monitor("%0t: q=%b", $time, q); end
reg [7:0] rom [0:15]; // 16-entry × 8-bit ROM integer i; initial begin // Method 1: explicit values rom[0] = 8'h00; rom[1] = 8'hFF; rom[2] = 8'hA5; rom[3] = 8'h5A; // Method 2: loop initialisation for (i=0; i<16; i=i+1) rom[i] = i[7:0]; // fill with 0,1,2...15 // Method 3: load from file $readmemh("program.hex", rom); // read hex values from file end
initial blocks entirely. For reset behaviour in hardware, use a synchronous or asynchronous reset signal in your always block — not an initial statement.The always block is the workhorse of behavioral RTL modelling. It runs continuously, re-triggering whenever its sensitivity list events occur. Every flip-flop, latch, and combinational logic block in a real design is described using always.
// ── Form 1: Combinational logic — @(*) ─────────────────────── always @(*) begin // @* = all signals on RHS case (opcode) 2'b00: alu_out = a + b; 2'b01: alu_out = a - b; 2'b10: alu_out = a & b; default: alu_out = 8'b0; endcase end // ── Form 2: Synchronous sequential logic — posedge clk ──────── always @(posedge clk) begin if (rst) q <= 1'b0; else q <= d; end // ── Form 3: Async reset — posedge clk + negedge rst_n ───────── always @(posedge clk or negedge rst_n) begin if (!rst_n) q <= 1'b0; // reset any time rst_n goes low else q <= d; end // ── Form 4: Clock generation (testbench) ────────────────────── always #5 clk = ~clk; // toggle every 5 units → period=10
The sensitivity list (the part inside @(...)) tells the always block which events trigger re-evaluation. Getting this right is critical — a wrong or incomplete sensitivity list is one of the most common RTL bugs.
// Missing 'c' — simulation latch always @(a or b) begin y = a & b | c; // c not in list! end // Sim: y doesn't update when c changes // Synth: correct combinational logic // → sim/synth mismatch!
// Use @(*) — always correct always @(*) begin y = a & b | c; end // @* automatically includes all // signals read on the RHS — a, b, c // Sim and synth always match ✅
always block doesn’t assign a value to an output in every possible condition, the synthesizer infers a latch to hold the last value. This is almost always unintentional. Always provide a default in case and an else in every if for combinational blocks.always @(*) begin if (en) q = d; // No else — q holds when en=0 // → latch inferred! end
always @(*) begin if (en) q = d; else q = 1'b0; // defined for all cases end
Behavioral blocks support the full range of procedural control flow constructs — similar to C but with hardware-specific semantics.
// Simple if-else always @(*) begin if (a > b) result = a; else if (a == b) result = 8'h00; else result = b; end // Priority encoder (if-else chain = priority hardware) always @(*) begin if (in[3]) code = 2'b11; // highest priority else if (in[2]) code = 2'b10; else if (in[1]) code = 2'b01; else code = 2'b00; end
// case — exact match (no wildcards) always @(*) begin case (opcode) 3'b000: out = a + b; 3'b001: out = a - b; 3'b010: out = a & b; 3'b011: out = a | b; default: out = 8'bx; // ← always include default! endcase end // casez — z (or ?) acts as wildcard (don't-care) always @(*) begin casez (priority_in) 4'b1???: out = 2'b11; // bit 3 set — highest priority 4'b01??: out = 2'b10; 4'b001?: out = 2'b01; 4'b0001: out = 2'b00; default: out = 2'bxx; endcase end // casex — both x and z act as wildcards // Use casez in RTL; casex can mask real x bugs in simulation
// for — counted loop (unrolled by synthesis for fixed bounds) always @(*) begin for (i=0; i<8; i=i+1) out[i] = in[7-i]; // bit reversal end // while — condition-controlled (use with care in RTL) initial begin i = 0; while (i < 10) begin @(posedge clk); i = i + 1; end end // repeat — fixed number of iterations initial begin repeat(5) @(posedge clk); // wait 5 clock cycles $display("5 cycles elapsed"); end // forever — infinite loop (testbench clock gen) initial begin clk = 0; forever #5 clk = ~clk; // equivalent to always #5 clk=~clk end
for loops with constant bounds are synthesizable — the tool unrolls them. while, repeat, and forever are generally not synthesizable and should be used in testbenches only.module counter_4bit ( input clk, rst_n, // clock, active-low async reset input up_down, // 1=count up, 0=count down input load, // synchronous load enable input [3:0] data_in, output reg [3:0] count, output tc // terminal count flag ); always @(posedge clk or negedge rst_n) begin if (!rst_n) count <= 4'b0000; // async reset else if (load) count <= data_in; // synchronous load else if (up_down) count <= count + 1'b1; // count up else count <= count - 1'b1; // count down end // Combinational flag — terminal count assign tc = (up_down && count==4'hF) || (!up_down && count==4'h0); endmodule
module seq_detector_101 ( input clk, rst_n, x, output detected ); // State encoding localparam S0=2'b00, S1=2'b01, S2=2'b10, S3=2'b11; reg [1:0] state, next_state; // Always 1: State register — sequential, non-blocking always @(posedge clk or negedge rst_n) begin if (!rst_n) state <= S0; else state <= next_state; end // Always 2: Next-state + output — combinational, blocking always @(*) begin next_state = S0; // default — prevents latches case (state) S0: next_state = x ? S1 : S0; // waiting for 1 S1: next_state = x ? S1 : S2; // got 1, wait for 0 S2: next_state = x ? S3 : S0; // got 10, wait for 1 S3: next_state = x ? S1 : S2; // got 101, detected! endcase end // Mealy output — combinational assign detected = (state == S3) && x; endmodule
module piso_8bit ( input clk, rst_n, input [7:0] parallel_in, input load, // 1=load parallel, 0=shift output serial_out // MSB first ); reg [7:0] shift_reg; always @(posedge clk or negedge rst_n) begin if (!rst_n) shift_reg <= 8'h00; else if (load) shift_reg <= parallel_in; // load else shift_reg <= {shift_reg[6:0], 1'b0}; // shift left end assign serial_out = shift_reg[7]; // MSB is always the output endmodule
module counter_tb; reg clk, rst_n, up_down, load; reg [3:0] data_in; wire [3:0] count; wire tc; // Instantiate DUT counter_4bit dut ( .clk(clk), .rst_n(rst_n), .up_down(up_down), .load(load), .data_in(data_in), .count(count), .tc(tc) ); // Clock generator — 10ns period initial clk = 0; always #5 clk = ~clk; // Stimulus and self-checking initial begin // Initialise and reset {rst_n, up_down, load} = 3'b001; data_in = 4'h5; @(posedge clk); #1; if (count !== 4'h5) $display("FAIL load: expected 5, got %0d", count); // Release load, count up load = 0; rst_n = 1; up_down = 1; repeat(5) @(posedge clk); if (count !== 4'hA) $display("FAIL count_up: expected 10, got %0d", count); else $display("PASS count_up"); $finish; end // Waveform dump initial begin $dumpfile("counter_tb.vcd"); $dumpvars(0, counter_tb); end endmodule
| Situation | Use | Reason |
|---|---|---|
| Combinational logic | always @(*) with = | @(*) is complete, = gives immediate values for intermediate calculations |
| Sequential logic | always @(posedge clk) with <= | <= models simultaneous capture correctly, prevents race conditions |
| Async reset | @(posedge clk or negedge rst_n) | rst_n in sensitivity list triggers reset without waiting for clock |
| FSM state register | Separate sequential always block | Clean separation of state register and combinational next-state logic |
| Stimulus / testbench | initial with = and #delays | One-shot, can use delays, not synthesized |
| Missing else / default | Always include | Prevents unintentional latch inference in combinational always blocks |
| Mixing = and <= | Never mix in same block | Causes simulation/synthesis mismatches |