Part 1 · Language Foundations · Intermediate

Procedural Code & Scheduling

Hub — the always family, blocking vs nonblocking, tasks vs functions, the event scheduler's regions, initial/final ordering, and flow control.

Overview

Procedural code is where SystemVerilog stops being a netlist description and becomes a programming language — but a programming language whose statements execute inside a discrete-event scheduler with strict rules about when each statement runs relative to clock edges, other processes, and assertion sampling. Most “my simulation behaves differently from my synthesis” bugs, and nearly every testbench race condition, trace back to misunderstanding these rules rather than to syntax.

This topic walks from the intent-checked always_comb/always_ff blocks, through the blocking-vs-nonblocking distinction that makes flip-flops simulate correctly, into the stratified event regions that explain why those rules work — and finishes with the task/function and flow-control machinery you use to build monitors, drivers, and checkers. Scheduling questions (the two-flop swap, where assertions sample, what #0 really does) are interview staples at every seniority level.

Sub-topics

  1. always_comb, always_ff, always_latch — intent checking, sensitivity inference, tool enforcement.

  2. Blocking vs Nonblocking Assignments — scheduling semantics, the two-register swap, race diagrams.

  3. Tasks vs Functions — time consumption, automatic vs static lifetime, argument directions.

  4. The Event Scheduler & Stratified Regions — active/NBA/observed/reactive, #0 abuse, assertion sampling.

  5. initial, final & Startup Ordering — nondeterministic initial order, end-of-sim reporting, TB startup.

  6. Loops & Flow Control — foreach/repeat/forever, break/continue, named blocks, disable, monitor idioms.

diagram
PROCEDURAL CODE & SCHEDULING — TOPIC MAP

  ┌─────────────────────────────────────────────────────────┐
  │              one simulation time slot                   │
  │                                                         │
  │   Active ──► Inactive(#0) ──► NBA ──► Observed ──►      │
  │     ▲                                  (assertions)     │
  │     │                                      │            │
  │     │                                      ▼            │
  │     └◄──── iterate ◄──── Reactive(program) ──► Postponed│
  └──────────────────────────┬──────────────────────────────┘
                             │ explains the behavior of
        ┌──────────────┬─────┴──────┬───────────────┐
        ▼              ▼            ▼               ▼
  ┌───────────┐ ┌────────────┐ ┌──────────┐ ┌──────────────┐
  │  always   │ │  =  vs <=  │ │ initial/ │ │ tasks, loops │
  │  family   │ │ (blocking/ │ │  final   │ │ fork, disable│
  │ comb/ff/  │ │    NBA)    │ │ ordering │ │ (TB control) │
  │  latch    │ └────────────┘ └──────────┘ └──────────────┘
  └───────────┘
   RTL intent      RTL correctness   sim lifecycle   TB machinery

Key takeaways

  • Every procedural statement runs inside scheduler regions — region order explains races, not luck.

  • always_comb/always_ff/always_latch declare intent that tools verify; plain always declares nothing.

  • Blocking for combinational and TB sequencing; nonblocking for clocked state — one rule, zero races.

  • Scheduling questions (swap idiom, #0, assertion sampling) are interview staples — learn the regions once.