Verilog · Chapter 14.2 · Behavioural Modeling
Sequential & Parallel Blocks in Verilog — begin...end vs fork...join
Inside a procedural block, statements are grouped two ways. A begin-end block is sequential, running its statements in order one after another, and it is the everyday workhorse. The procedural constructs such as if, else, case, and loops each govern a single statement, so any time you need more than one you wrap them in a begin-end block. A fork-join block is parallel, starting all of its statements at the same time so they run concurrently, and it is a testbench construct rather than synthesizable hardware. This page covers both, including the sequential ordering of begin-end and the common bug it prevents when a statement escapes its if because the grouping was omitted, named blocks, and the timing difference where delays accumulate in a sequential block but run from a common start in a parallel one.
Foundation15 min readVerilogbegin endfork joinBehaviouralStatement Blocks
Chapter 14 · Section 14.2 · Behavioural Modeling
1. The Engineering Problem
An engineer writes two assignments under an if, expecting both to be conditional:
always @(posedge clk)
if (en)
a <= 1; // intended to be conditional on 'en'
b <= 2; // ALSO intended conditional — but it is NOTOnly a <= 1 is governed by the if. The indentation suggests both are, but an if controls exactly one statement — so b <= 2 is outside the if and executes on every clock edge, regardless of en. The bug is the missing begin...end: without it, the second statement escapes the if.
always @(posedge clk)
if (en) begin // begin...end groups BOTH statements as one
a <= 1;
b <= 2;
end // now both are conditional on 'en' ✓The procedural constructs (
if,else,case,always, loops) each govern a single statement. To put more than one statement under them, group withbegin...end. Omitting it lets statements silently escape their control structure.
This page drills statement grouping: the sequential begin...end (and the bug above), named blocks, and the parallel fork...join for testbench concurrency.
2. Mental Model — Sequential begin...end, Parallel fork...join
3. begin...end — Sequential Grouping
begin...end bundles statements that execute in order:
always @(posedge clk)
if (load) begin
valid <= 1'b1;
data <= data_in;
count <= count + 1;
end // three statements, all under the ifWithout the begin...end, only valid <= 1'b1; would be under the if and the other two would run every cycle (§1). The block makes the three statements a single grouped statement that the if governs. begin...end appears wherever a construct needs to control more than one statement — which is almost everywhere in behavioural code. It is fully synthesizable.
Visual A — sequential vs parallel grouping
begin...end (sequential) vs fork...join (parallel)
data flow4. Why Grouping Is Necessary
The procedural constructs each take exactly one statement:
if (cond) statement; // ONE statement
if (cond) begin // ...or one BLOCK (many statements)
statement1;
statement2;
end
for (i = 0; i < N; i = i + 1) begin // loop body needs begin...end for >1 stmt
sum = sum + a[i];
cnt = cnt + 1;
endAn if, else, case branch, loop body, or always body that needs more than one statement must wrap them in begin...end. A single statement can stand alone, but the moment there are two, the block is required — and omitting it is the §1 bug. The safe habit many teams adopt: always use begin...end for control-structure bodies, even single statements, so adding a second later cannot create the escape bug.
5. Named Blocks
A block can be named (begin : name), which enables two things:
always @(posedge clk) begin : update_logic
integer i; // local declaration (allowed in a named block)
for (i = 0; i < 4; i = i + 1)
mem[i] <= 0;
end
// disable a named block (early exit, mostly testbench):
// disable update_logic;- Local declarations — a named block can declare its own variables (
integer i;), scoped to the block. disable— a named block can be terminated early withdisable block_name(used for loop break / early exit, mostly in testbenches).
Naming is optional for ordinary grouping; it is needed when you want local variables or disable.
6. fork...join — Parallel Grouping
fork...join runs its statements concurrently — all start together — and is a testbench construct:
// testbench: apply two stimulus streams in parallel
initial begin
fork
drive_clock(); // these run...
drive_reset(); // ...concurrently...
apply_stimulus(); // ...all starting now
join // block completes when ALL finish
$finish;
end- All statements in the
fork...joinbegin at the same time and run in parallel. - The block completes when all of them complete (
joinwaits for all). - It is not synthesizable in general — parallel statement threads do not map to hardware; it models concurrency in simulation.
fork...join is used in testbenches to launch concurrent stimulus (a clock generator, a reset sequence, a stimulus driver, a watchdog — all at once). (SystemVerilog adds join_any and join_none variants; plain fork...join waits for all and is the Verilog-1364 form.)
7. Sequential vs Parallel Timing
The clearest difference appears with # delays:
// SEQUENTIAL — delays accumulate:
begin
#5 a = 1; // a = 1 at t = 5
#5 b = 1; // b = 1 at t = 10 (5 after a)
end
// PARALLEL — delays from the common start:
fork
#5 a = 1; // a = 1 at t = 5
#5 b = 1; // b = 1 at t = 5 (both relative to fork start)
joinIn begin...end, each delay is relative to the previous statement, so they add up. In fork...join, each delay is relative to the fork start, so the statements happen at their own offsets from the same starting point. This matters in testbench stimulus, where the timing of applied signals depends on which block type you use.
8. Worked Examples
8.1 Example 1 — grouping in a clocked block
always @(posedge clk or negedge rst_n)
if (!rst_n) begin
q <= 1'b0;
valid <= 1'b0;
end else if (en) begin
q <= d;
valid <= 1'b1;
endEach branch groups two assignments with begin...end, so both are governed by their condition. Without the blocks, only the first assignment in each branch would be conditional — the §1 bug, repeated per branch.
8.2 Example 2 — a named block with a local variable
always @(*) begin : parity_calc
integer i;
parity = 1'b0;
for (i = 0; i < 8; i = i + 1)
parity = parity ^ data[i]; // (combinational; @* and = per 14.1/14.3)
endThe named block parity_calc declares a local loop variable i. (This computes parity with a loop; ^data reduction does the same, 10.5 — shown to illustrate the named block.)
8.3 Example 3 — parallel testbench stimulus
initial begin
fork
begin #5 clk = 1; end // these start together
begin rst_n = 0; #20 rst_n = 1; end
begin #100 $finish; end // watchdog
join
endThree concurrent stimulus threads in a fork...join — a clock pulse, a reset sequence, and a finish timer — all starting at once. This is the testbench use of parallel blocks (and ties to the watchdog pattern of 8.5).
9. Industry Perspective
begin...endis everywhere. Every multi-statementif/case/loop/alwaysbody uses it; many teams require it even for single statements to prevent the escape bug.fork...joinis testbench-only. Concurrent stimulus, parallel checkers, and watchdogs use it; it never appears in synthesizable design.- The missing-
begin...endbug is a lint and review target. Tools and reviewers flag statements that look conditional by indentation but aren't, because it is a common, silent bug. - Named blocks enable local scope and
disable. Used for loop control and local declarations, mostly in verification code.
10. Common Mistakes
- Missing
begin...end— a second statement escapes itsif/else/loop and runs unconditionally (§1, DebugLab 1). fork...joinin synthesizable design — not synthesizable; testbench-only (§6, DebugLab 2).- Confusing sequential and parallel timing — delays accumulate in
begin...end, run from the start infork...join(§7, DebugLab 3). - Local declaration in an unnamed block — local variables need a named block (§5).
- Relying on indentation — Verilog ignores indentation; only
begin...endgroups (§1).
11. Debugging Lab
Three statement-block debug post-mortems
12. Interview Q&A
13. Exercises
Exercise 1 — Group or escape?
For if (en) a <= 1; b <= 2;, which assignment is conditional on en, and how do you make both conditional?
Exercise 2 — Sequential vs parallel timing
For #5 x = 1; #10 y = 1;, give the times x and y are set when wrapped in (a) begin...end; (b) fork...join.
Exercise 3 — Choose the block
Which block (begin...end or fork...join) for: (a) two register updates in a synthesizable always; (b) concurrent clock and reset stimulus in a testbench; (c) a loop body with three statements.
Exercise 4 — Fix the escape
Rewrite so both statements are under the if:
if (load) data <= in; valid <= 1;14. Summary
Statement grouping has two forms:
begin...end— sequential — statements run in order; the everyday, synthesizable block that bundles multiple statements into one where a construct expects a single statement. Delays accumulate.fork...join— parallel — statements start together and run concurrently; a testbench-only, non-synthesizable concurrency construct. Delays run from the common start.- Grouping is necessary because
if/else/case/loops/alwayseach govern one statement — omittingbegin...endlets statements escape (the missing-beginbug). - Named blocks (
begin : name) enable local declarations anddisable.
The discipline this page instils:
- Use
begin...endfor any multi-statement control body — and often even for single statements, to prevent the escape bug. - Keep
fork...joinin testbenches — it is not synthesizable. - Mind the timing — sequential delays accumulate; parallel delays run from the start.
The next page is the chapter's crown jewel: Chapter 14.3 Blocking & Non-Blocking Assignments — = versus <=, the single most important rule in behavioural modeling and the most common source of RTL bugs.
Related Tutorials
- always & initial Blocks — Chapter 14.1; the procedural blocks these statements live in.
- Behavioural Modeling — Chapter 14 overview; the procedural-modeling context.
- Simulation Control — Chapter 8.5; the watchdog/fork pattern in testbenches.
- Loops — Chapter 14.6; loop bodies that use
begin...endgrouping.