Mastering Verilog Always Blocks: Syntax, Blocking vs Non-Blocking, and SystemVerilog Extensions

目次

1. Introduction

What is the role of the always block in Verilog?

In Verilog HDL, a hardware description language widely used in digital circuit design, the always block plays a crucial role. Instead of describing hardware behavior like software, Verilog represents circuits by defining “under what conditions signals change”. Among these, the always block is a fundamental construct used to describe what actions should be executed when certain conditions occur.

Why do we need the always block?

In Verilog, there are two main types of circuit behaviors you can describe:

  • Combinational logic: output changes immediately when inputs change
  • Sequential logic: output changes in sync with a clock signal or timing events

A simple assign statement cannot handle complex conditions or memory elements. This is where the always block comes in.

For example, to describe conditional branching or flip-flop behavior, you need an always block with control structures such as if or case.

Common patterns of the always block

The always block has several common usage patterns depending on the circuit type being designed:

  • always @(*)
    → Used for combinational logic
  • always @(posedge clk)
    → Sequential logic triggered on the rising edge of the clock
  • always @(posedge clk or negedge rst)
    → Sequential logic with asynchronous reset or more complex control

Thus, understanding the always block, a core syntax of Verilog, is an essential first step for hardware designers.

Purpose of this article

This article provides a comprehensive guide to the always block in Verilog, covering basic syntax, practical usage, common pitfalls, and SystemVerilog extensions.

  • Learn the correct way to write always blocks
  • Understand why synthesis errors occur
  • Clarify the difference between = and <=
  • Avoid common beginner mistakes

We aim to make this a practical and easy-to-understand guide for anyone with such questions.

2. Basic Syntax and Types of always Blocks

Basic syntax of the always block

In Verilog, an always block repeatedly executes statements based on a specific sensitivity list. The basic syntax is:

always @(sensitivity list)
begin
  // statements
end

The key part here is the “sensitivity list,” which defines which signals trigger execution when they change.

Using always @(*) for combinational logic

In combinational logic, the output must update immediately whenever inputs change. In this case, use @(*) as the sensitivity list.

always @(*) begin
  if (a == 1'b1)
    y = b;
  else
    y = c;
end

This means whenever a, b, or c changes, the always block executes and recalculates y.

Advantages of using @(*)

  • Automatically includes all referenced signals in the sensitivity list
  • Prevents mismatches between simulation and synthesis results

Using always @(posedge clk) for sequential logic

In sequential logic, state changes occur in sync with a clock signal. In this case, specify posedge clk in the sensitivity list.

always @(posedge clk) begin
  q <= d;
end

Here, the value of d is latched into q at the rising edge of the clock. The operator <= represents a non-blocking assignment, which is the standard for sequential logic.

posedge vs negedge

  • posedge: triggered on the rising edge
  • negedge: triggered on the falling edge

Select the appropriate edge depending on the design requirements.

always @(posedge clk or negedge rst) with asynchronous reset

In more complex circuits, reset functionality is often required. A block with asynchronous reset can be written as:

always @(posedge clk or negedge rst) begin
  if (!rst)
    q <= 1'b0;
  else
    q <= d;
end

With this description, q is immediately reset when rst is low, otherwise it captures d on the clock edge.

Combinational vs Sequential Circuits

Type of Circuitalways to useBehavior
Combinationalalways @(*)Output updates immediately based on inputs
Sequentialalways @(posedge clk)Operates in sync with the clock

3. Types of Assignments in always Blocks

Two assignment operators in Verilog

Inside a Verilog always block, you can use two different assignment operators:

  • =: Blocking assignment
  • <=: Non-blocking assignment

Misunderstanding these differences can lead to unexpected behavior and mismatches between simulation and synthesis, making this one of the most important points to learn.

Blocking assignment (=)

A blocking assignment executes sequentially, one statement after another, similar to software control flow.

always @(*) begin
  a = b;
  c = a;
end

Here, a = b executes first, and then c = a uses the updated value of a. The order of statements directly affects logic behavior.

Typical use cases

  • Control structures (if, case) in combinational logic
  • Logic that does not require state holding

Non-blocking assignment (<=)

A non-blocking assignment means that all statements are evaluated simultaneously and updated together, expressing hardware’s parallel nature.

always @(posedge clk) begin
  a <= b;
  c <= a;
end

Both a <= b and c <= a are evaluated at the same time and updated after the clock edge. Therefore, c receives the previous value of a.

Typical use cases

  • Sequential logic (registers, flip-flops)
  • Accurate state propagation across multiple signals

Blocking vs Non-blocking Assignments: Comparison

FeatureBlocking (=)Non-blocking (<=)
Execution orderSequential, one after anotherEvaluated simultaneously, updated together
Typical usageCombinational logicSequential logic
Update timingImmediately appliedApplied after the clock edge
Common pitfallsUnintended latch generationValues not updated or propagated as expected

What happens if you mix them?

You should avoid mixing = and <= in the same block or on the same signal. For example:

always @(posedge clk) begin
  a = b;
  a <= c;
end

This code assigns to a twice using different methods, making the final stored value ambiguous, which may appear correct in simulation but fail in hardware.

Guidelines for usage

  • Use = inside always @(*) blocks (combinational)
  • Use <= inside always @(posedge clk) blocks (sequential)

Following this simple rule helps prevent many common mistakes.

4. Common Pitfalls and Best Practices with always Blocks

Mistakes in sensitivity lists

Incorrect sensitivity lists can cause hidden bugs

In Verilog, the sensitivity list (@(...)) must explicitly include all signals that trigger execution. Here’s an example where only part of the signals are included:

always @(a) begin
  if (b)
    y = 1'b1;
  else
    y = 1'b0;
end

This code does not respond to changes in b. As a result, when b changes, y will not update, causing a bug.

Solution: use @(*)

To avoid missing signals in the sensitivity list, use @(*) as follows:

always @(*) begin
  if (b)
    y = 1'b1;
  else
    y = 1'b0;
end

@(*) automatically includes all signals referenced in the block, improving both maintainability and reliability.

Unintended latch generation

Missing if/case branches creates latches

If not all cases assign values, the synthesis tool infers that the variable must “hold” its value, creating a latch:

always @(*) begin
  if (enable)
    y = d; // y is undefined when enable == 0
end

Even though it looks fine, a latch is inserted because y does not get updated when enable is 0.

Solution: always assign a value

always @(*) begin
  if (enable)
    y = d;
  else
    y = 1'b0; // y is always defined
end

By explicitly assigning a value in every case, you can prevent unwanted latches.

Overly complex conditionals

Complicated if or case statements can lead to undefined behavior or missing logic if not all conditions are covered.

Typical mistake: no default in a case statement

always @(*) begin
  case(sel)
    2'b00: y = a;
    2'b01: y = b;
    2'b10: y = c;
    // 2'b11 not handled → y may be undefined
  endcase
end

Solution: add a default clause

always @(*) begin
  case(sel)
    2'b00: y = a;
    2'b01: y = b;
    2'b10: y = c;
    default: y = 1'b0; // safety net
  endcase
end

Adding default ensures that outputs are always defined, improving design robustness.

Controlling multiple signals in one block

When controlling multiple signals in a single always block, assignment order and missing cases can create unintended dependencies. In complex designs, consider splitting logic into multiple always blocks for clarity and safety.

Summary of common pitfalls

ProblemCauseSolution
Output not updatingMissing signals in sensitivity listUse @(*) for automatic detection
Latch generatedNot all branches assign valuesAlways include else or default
Undefined behaviorCase statement missing conditionsAdd default branch
Overly complex controlToo many signals in one blockSplit into multiple always blocks

5. Extensions of always in SystemVerilog

always_comb: for combinational logic

Overview

always_comb works similarly to always @(*) but explicitly indicates combinational logic.

always_comb begin
  y = a & b;
end

Main benefits

  • Automatically generates sensitivity list
  • Tools warn when unintended latches are inferred
  • Prevents conflicts with previously defined variables

Example (Verilog vs SystemVerilog)

// Verilog
always @(*) begin
  y = a | b;
end

// SystemVerilog
always_comb begin
  y = a | b;
end

always_ff: for sequential logic (flip-flops)

Overview

always_ff is designed for clock-driven sequential logic, requiring explicit edge conditions like posedge clk or negedge rst.

always_ff @(posedge clk or negedge rst_n) begin
  if (!rst_n)
    q <= 1'b0;
  else
    q <= d;
end

Main benefits

  • Allows only non-blocking assignments (<=)
  • Tools check sensitivity list correctness
  • Code readability improves as it is clearly sequential

always_latch: for latch-based logic

Overview

always_latch is used when you intentionally describe latch behavior. However, in most designs, unintended latches should be avoided.

always_latch begin
  if (enable)
    q = d;
end

Points to note

  • If some branches skip assignment, a latch is explicitly created
  • Use only when latches are truly required

SystemVerilog usage summary

ConstructPurposeEquivalent in VerilogFeatures
always_combCombinational logicalways @(*)Auto sensitivity list, latch detection
always_ffFlip-flopsalways @(posedge clk)Clock-synchronous, safer assignments
always_latchLatchesalways @(*) with incomplete branchesExplicit latch design, error detection

SystemVerilog is becoming the standard

In modern development, SystemVerilog constructs are increasingly recommended for readability and safety. With better syntax checking, using always_ff and always_comb helps prevent “looks correct but doesn’t work” issues.

Especially in large-scale or team-based projects, explicit constructs make design intent clear, improving code reviews and maintainability.

6. FAQ: Common Questions About the always Block

This section answers frequently asked questions about always blocks in Verilog and SystemVerilog, focusing on practical concerns that often come up in design projects. It covers common beginner to intermediate issues seen in real-world development.

Q1. Should I use if or case inside an always block?

A. It depends on the number and complexity of conditions:

  • For 2–3 simple conditions → if is easier to read
  • For multiple distinct states → case is clearer and expresses intent better

Using case also enforces the expectation of covering all possible cases, helping reduce mistakes.

Q2. What happens if I omit signals in the sensitivity list?

A. If the sensitivity list is incomplete, some signal changes won’t trigger the block, leaving outputs outdated.

This may cause simulation vs synthesis mismatches. To prevent this, always use @(*) or SystemVerilog always_comb.

Q3. Why do unintended latches appear in my design?

A. If if or case statements don’t assign a value to a variable in every possible path, the synthesis tool infers that the value must be held, and creates a latch.

Bad example:

always @(*) begin
  if (en)
    y = d; // y is held when en == 0
end

Solution:

always @(*) begin
  if (en)
    y = d;
  else
    y = 1'b0; // always assigned
end

Q4. Can I mix = and <= in the same block?

A. Generally, no. Mixing blocking and non-blocking assignments in the same block, especially on the same signal, may cause simulations to work but hardware to fail.

  • Combinational logic → use = (blocking)
  • Sequential logic → use <= (non-blocking)

Rule of thumb:

Always use a consistent assignment style per signal.

Q5. What’s the difference between always_ff and always @(posedge clk)?

A. Functionally, they behave the same, but always_ff is safer and more readable.

Comparisonalways @(posedge clk)always_ff
SensitivityMust be manually specifiedChecked automatically
Assignment errorsBlocking assignments may compileInvalid assignments cause errors
ReadabilityMay obscure circuit intentClearly indicates sequential logic

Q6. Is it okay to control multiple signals in one always block?

A. It’s possible, but if too many signals are included, debugging and maintenance become difficult. Consider splitting into multiple blocks when:

  • Each output behaves independently
  • You mix synchronous and asynchronous logic

Q7. What happens if I use <= in combinational logic?

A. It may still work in simulation, but during synthesis, it can create unexpected logic. Stick to blocking (=) assignments for combinational logic.

7. Conclusion

always blocks are the foundation of Verilog design

In Verilog hardware design, the always block is a powerful tool that allows you to describe both combinational and sequential circuits. It not only expands your design possibilities but also clarifies control flow and timing. For both beginners and professionals, always is essential knowledge.

Key takeaways

  • The differences and usage of always @(*) vs always @(posedge clk)
  • The distinction between = (blocking) and <= (non-blocking) assignments
  • How to avoid common mistakes such as latch generation and incomplete sensitivity lists
  • SystemVerilog extensions (always_comb, always_ff, always_latch) for safer design
  • Practical answers to common real-world questions (FAQ)

Precision determines quality

In hardware description, what you write is exactly what gets implemented. Even small mistakes can become hardware bugs. Since always is central to behavior, accuracy, correct assignment type, and complete condition coverage are critical.

Next steps: advancing to higher-level design

Once you master the always block, you can move on to:

  • Finite State Machine (FSM) design
  • Pipelining and streaming architectures
  • Developing IP cores and FPGA implementation

You can also broaden your skills by learning SystemVerilog and VHDL, making you adaptable across different design environments.

Final thoughts for hardware designers

In circuit design, it’s not just about “making it work.” What’s required is correct behavior, robustness for future changes, and clarity for team development.
Through this article, we hope you gained both fundamental knowledge of always blocks and an appreciation for safe, reliable design practices.