Lesson 08: Combinational Behavioral Modeling
8.1 Introduction to Combinational Behavioral Modeling
Introduction to Combinational Behavioral Modeling
Combinational behavioral modeling is a method of describing combinational logic using procedural constructs in Verilog. Instead of connecting gates or writing continuous assignments, designers use always blocks and conditional statements to define how outputs respond to inputs.
What Is Combinational Logic?
Combinational logic is a type of digital circuit in which the output depends only on the current input values. It does not store any previous state or memory.
Examples of combinational circuits include:
- multiplexers (MUX),
- decoders,
- comparators,
- arithmetic units (adders, subtractors).
In general, combinational logic can be described as: output = function(input).
Behavioral Modeling for Combinational Logic
In Verilog, combinational logic can be described using a procedural block:
always @(*) begin
// combinational logic description
end
The symbol @(*) means that the block is sensitive to all input signals used inside the block. Whenever any input changes, the logic inside the block is re-evaluated.
Example: 2-to-1 Multiplexer
The following example shows a simple combinational circuit written using behavioral modeling:
always @(*) begin
if (sel)
y = a;
else
y = b;
end
In this example:
- The output y depends only on the current values of sel, a, and b.
- No clock is involved.
- No memory element is created.
Key Characteristics of Combinational Behavioral Modeling
- No clock signal is required.
- No storage or memory is used.
- The output must always reflect the current input values.
- The logic is described using procedural statements such as if and case.
Combinational vs Sequential Behavioral Modeling
It is important to distinguish combinational logic from sequential logic:
| Type | Trigger | Memory | Typical Block |
|---|---|---|---|
| Combinational | Input changes | No | always @(*) |
| Sequential | Clock edge | Yes | always @(posedge clk) |
This lesson focuses only on combinational logic. Sequential logic will be covered in the next lesson.
Important Note
Although behavioral modeling is easy to write, it must still represent correct combinational behavior. If the logic description is incomplete, unintended hardware, such as latches, may be inferred.
In the following sections, we will examine how to write correct combinational code and how to avoid common design errors.
Summary
Combinational behavioral modeling provides a flexible and readable way to describe logic circuits in Verilog. It uses procedural constructs to define how outputs respond to inputs without involving memory or clock signals.
In this lesson, we will build on this foundation and learn how to write correct and synthesizable combinational RTL code.
8.2 Using always @(*) for Combinational Logic
Using always @(*) for Combinational Logic
In behavioral modeling, combinational logic is typically described using an always block with a sensitivity list. The correct form for combinational logic is:
always @(*) begin
// combinational logic
end
What Does @(*) Mean?
The symbol @(*) represents an automatically generated sensitivity list.
It tells the simulator:
- Trigger this block whenever any input used inside the block changes.
- Ensure the output is always updated based on the latest input values.
This behavior matches the requirement of combinational logic: the output must respond immediately to any input change.
Why Not Use a Manual Sensitivity List?
In older Verilog code, designers manually wrote sensitivity lists:
// Not recommended
always @(a or b or sel) begin
if (sel)
y = a;
else
y = b;
end
This approach is error-prone. If a signal is missing from the sensitivity list, the simulation result may be incorrect.
For example, if b is not included, changes to b will not trigger the block, causing a simulation mismatch.
Therefore, the use of @(*) is strongly recommended.
Example: Combinational Logic with always @(*)
always @(*) begin
if (a > b)
max = a;
else
max = b;
end
In this example:
- The output max depends on inputs a and b.
- Whenever either input changes, the block is executed.
- No clock is involved.
Incorrect Usage: Using Clock in Combinational Logic
A common mistake is using a clock edge in combinational logic:
// Incorrect
always @(posedge clk) begin
y = a & b;
end
This creates sequential behavior (register), not combinational logic.
If a clock edge is used, the output will update only at that edge, meaning the circuit now contains memory.
Key Guidelines for Using always @(*)
- Use always @(*) for combinational logic.
- Do not include clock edges in combinational blocks.
- Ensure all inputs are reflected in the block's logic.
- The output should update whenever any input changes.
Important Note
Even when using always @(*), incorrect coding inside the block may still produce unintended hardware.
In the next sections, we will examine conditional statements and how incomplete assignments may lead to unintended latch inference.
Summary
The always @(*) construct is the standard way to describe combinational logic in behavioral modeling. It ensures that the logic responds correctly to all input changes and avoids simulation errors caused by incomplete sensitivity lists.
8.3 Conditional Statements (if-else)
Conditional Statements (if-else)
Conditional statements are commonly used in combinational behavioral modeling to describe decision-making logic. The most frequently used construct is the if-else statement.
This structure allows the output to be selected based on one or more conditions. It is widely used in multiplexers, comparators, and priority logic circuits.
Basic Syntax of if-else
always @(*) begin
if (condition)
y = value1;
else
y = value2;
end
The condition is evaluated first. If the condition is true, the first assignment is executed. Otherwise, the alternative assignment is used.
Example: 2-to-1 Multiplexer
always @(*) begin
if (sel)
y = a;
else
y = b;
end
This is a simple multiplexer implementation:
- If sel = 1, output is a.
- If sel = 0, output is b.
Nested if-else Statements
Multiple conditions can be evaluated using nested if-else statements:
always @(*) begin
if (a > b)
max = a;
else if (b > c)
max = b;
else
max = c;
end
The conditions are evaluated in order from top to bottom. Once a condition is true, the remaining conditions are not evaluated.
Priority Behavior of if-else
The if-else structure creates priority logic.
This means:
- The first condition has the highest priority.
- Lower conditions are evaluated only if previous ones are false.
always @(*) begin
if (req1)
grant = 2'b01;
else if (req2)
grant = 2'b10;
else
grant = 2'b00;
end
In this example:
- If both req1 and req2 are active,
- req1 has higher priority.
Common Mistake: Missing else Branch
A very common mistake is forgetting to include an else branch.
// Incorrect
always @(*) begin
if (sel)
y = a;
end
In this case, when sel = 0, the output y is not assigned.
This may lead to unintended hardware behavior, which will be discussed in a later section.
Guidelines for Using if-else in Combinational Logic
- Always use always @(*) for combinational logic.
- Ensure all possible conditions are covered.
- Include an else branch whenever necessary.
- Understand that if-else creates priority logic.
Important Note
Incorrect or incomplete use of if-else may lead to unintended latch inference.
In the following sections, we will study how to use case statements and how to avoid these design issues.
Summary
The if-else statement is a powerful tool for describing conditional logic in combinational circuits. It allows designers to implement selection and priority behavior clearly.
However, it must be used carefully to ensure that all outputs are properly assigned under all conditions.
8.4 Case Statements in Combinational Logic
Case Statements in Combinational Logic
In combinational behavioral modeling, the case statement is used to describe multi-way decision logic. It is commonly used in decoders, multiplexers, and state-based combinational logic.
Unlike if-else, which creates priority logic, the case statement is typically used for parallel decision-making.
Basic Syntax of case Statement
always @(*) begin
case (expression)
value1: y = result1;
value2: y = result2;
default: y = default_result;
endcase
end
The expression is evaluated, and its value is compared against each case item. When a match is found, the corresponding assignment is executed.
Example: 4-to-1 Multiplexer
always @(*) begin
case (sel)
2'b00: y = a;
2'b01: y = b;
2'b10: y = c;
2'b11: y = d;
default: y = 1'b0;
endcase
end
In this example:
- The output depends on the value of sel.
- Each case represents one possible input condition.
- The default branch ensures safe behavior.
Parallel vs Priority Logic
It is important to understand the difference between if-else and case:
| Structure | Behavior |
|---|---|
| if-else | Priority (top condition has highest priority) |
| case | Parallel (each case is mutually exclusive) |
This difference affects the synthesized hardware. Using the wrong structure may lead to unintended circuit behavior.
Importance of default Case
The default branch is used to handle all unspecified cases.
// Recommended
always @(*) begin
case (sel)
2'b00: y = a;
2'b01: y = b;
default: y = 1'b0;
endcase
end
Without a default, some input conditions may not assign a value to the output.
This may lead to unintended behavior, which will be discussed in the next section.
Common Mistake: Missing Cases
// Incorrect
always @(*) begin
case (sel)
2'b00: y = a;
2'b01: y = b;
endcase
end
In this example, when sel = 2'b10 or 2'b11, the output y is not assigned.
This situation can cause unintended hardware behavior.
Guidelines for Using case in Combinational Logic
- Use always @(*) for combinational logic.
- Cover all possible input cases.
- Always include a default branch.
- Use case for mutually exclusive conditions.
Important Note
Similar to if-else, incomplete case statements may lead to unintended latch inference.
In the next section, we will examine this problem in detail and introduce a key design rule for combinational logic.
Summary
The case statement provides a clean and structured way to describe multi-way combinational logic. It is especially useful when dealing with multiple discrete input conditions.
Proper use of default and complete case coverage is essential for correct and reliable hardware design.
8.5 Conditional Operator in Combinational Logic
Conditional Operator in Combinational Logic
In Verilog, the conditional operator ?: provides a compact way to describe simple combinational selection logic. It is often used when one output must be selected from two possible values based on a condition.
Basic Syntax
y = (condition) ? value1 : value2;
This means:
- If the condition is true, assign value1 to the output.
- If the condition is false, assign value2 to the output.
Example: 2-to-1 Multiplexer
always @(*) begin
y = (sel) ? a : b;
end
In this example:
- If sel = 1, then y = a.
- If sel = 0, then y = b.
This is equivalent to a simple if-else statement.
Equivalent if-else Form
// Conditional operator
always @(*) begin
y = (sel) ? a : b;
end
// Equivalent if-else form
always @(*) begin
if (sel)
y = a;
else
y = b;
end
Both versions describe the same combinational logic. The choice depends on readability and design complexity.
Advantages of the Conditional Operator
- Short and compact syntax
- Easy to use for simple selection logic
- Useful for small multiplexers and simple comparisons
For simple expressions, the conditional operator often makes the code more concise.
Nested Conditional Operators
The conditional operator can also be nested:
always @(*) begin
y = (sel == 2'b00) ? a :
(sel == 2'b01) ? b :
(sel == 2'b10) ? c : d;
end
This can describe a multi-way selection, but nested conditional expressions quickly become difficult to read.
For complex decision logic, a case statement is usually a better choice.
Conditional Operator vs if-else vs case
| Construct | Best Use |
|---|---|
| ?: | Simple two-way selection |
| if-else | Priority logic and conditional branching |
| case | Multi-way selection with mutually exclusive cases |
A simple design guideline is:
- Use the conditional operator for simple logic.
- Use if-else or case for more complex logic.
Important Caution
The conditional operator is still part of combinational logic. Therefore, it must be used carefully and completely. If a conditional expression is written incorrectly or incompletely, the resulting logic may not match the designer’s intent.
Also, nested conditional operators may reduce readability and make debugging harder.
Summary
The conditional operator ?: is a compact way to describe simple combinational logic in Verilog. It is especially useful for two-way selection logic, such as small multiplexers.
However, for more complex conditions, designers should use if-else or case for better clarity and maintainability.
8.6 Avoiding Unintended Latch Inference (Rule 01)
Avoiding Unintended Latch Inference (Rule 01)
One of the most critical issues in combinational behavioral modeling is unintended latch inference. This problem often occurs when outputs are not assigned under all possible conditions.
In most FPGA designs, latches are not desired and may cause incorrect or unpredictable behavior. Therefore, it is essential to understand how latches are unintentionally created and how to avoid them.
1. What Is a Latch?
A latch is a storage element that holds its previous value when no new assignment is made.
Unlike combinational logic, which depends only on current inputs, a latch introduces memory behavior.
In combinational design, this is usually not intended.
2. Problem Example: Missing Assignment
// Incorrect: may infer latch
always @(*) begin
if (sel)
y = a;
end
In this example:
- When sel = 1, y = a.
- When sel = 0, y is not assigned.
Since no assignment is made, the synthesis tool must preserve the previous value of y, which results in a latch.
3. Root Cause of Latch Inference
The root cause of unintended latch inference is:
- Not assigning outputs in all possible conditions
- Incomplete if-else or case statements
When a value is not explicitly assigned, the hardware must "remember" the previous value, which introduces storage behavior.
4. Rule 01: Assign All Outputs in Combinational Logic
Rule 01:
All outputs in a combinational always @(*) block must be assigned in all possible conditions.
This ensures that no memory element (latch) is inferred.
5. Correct Design: Complete Assignment
// Correct
always @(*) begin
if (sel)
y = a;
else
y = b;
end
In this version, the output y is always assigned, regardless of the value of sel.
6. Recommended Coding Style: Default Assignment
A common and recommended approach is to assign a default value at the beginning of the block:
// Recommended style
always @(*) begin
y = b; // default assignment
if (sel)
y = a;
end
This ensures that the output always has a defined value, even if no condition is met.
7. Case Statement Example
// Incorrect
always @(*) begin
case (sel)
2'b00: y = a;
2'b01: y = b;
endcase
end
Missing cases will cause an incomplete assignment.
// Correct
always @(*) begin
case (sel)
2'b00: y = a;
2'b01: y = b;
default: y = 1'b0;
endcase
end
8. Practical Debug Tip
If you observe unexpected behavior in the simulation:
- Check whether all outputs are assigned in every branch.
- Check for missing else or default.
- Look for signals that hold their previous value unexpectedly.
9. Summary
Unintended latch inference is one of the most common mistakes in combinational design. It is not caused by syntax errors, but by an incomplete logic description.
By following Rule 01 and ensuring complete assignments, designers can avoid unintended memory behavior and create correct combinational circuits.
8.7 Recommended Coding Style for Combinational Logic
Recommended Coding Style for Combinational Logic
After learning how to write combinational logic using always @(*), if-else, case, and the conditional operator, it is important to follow a consistent coding style.
A clean and disciplined coding style helps ensure correctness, readability, and easier debugging.
1. Always Use always @(*) for Combinational Logic
This ensures that the logic responds to all input changes automatically. Do not manually list signals in the sensitivity list.
// Correct
always @(*) begin
y = a & b;
end
2. Assign Default Values at the Beginning
To avoid unintended latch inference, assign default values to outputs at the start of the block.
// Recommended
always @(*) begin
y = 1'b0; // default value
if (sel)
y = a;
end
This guarantees that the output always has a defined value.
3. Ensure All Conditions Are Covered
Every possible input condition must assign a value to the output.
- Use else in if-else
- Use default in case
// Correct
always @(*) begin
case (sel)
2'b00: y = a;
2'b01: y = b;
default: y = 1'b0;
endcase
end
4. Use Blocking Assignment (=)
In combinational logic, always use blocking assignment:
// Correct
y = a & b;
Do not use non-blocking assignment <= in combinational blocks.
5. Keep Combinational and Sequential Logic Separate
Do not mix clocked logic with combinational logic.
// Incorrect
always @(posedge clk) begin
y = a & b; // This is sequential, not combinational
end
Always keep combinational logic in always @(*).
6. Use Clear and Consistent Formatting
- Indent nested statements properly
- Align if / else blocks clearly
- Use meaningful variable names
always @(*) begin
y = 1'b0;
if (sel)
y = a;
else
y = b;
end
7. Choose the Right Construct
| Construct | Recommended Usage |
|---|---|
| ?: | Simple two-way selection |
| if-else | Priority-based decisions |
| case | Multi-way selection (parallel logic) |
8. Recommended Coding Template
// Standard combinational template
always @(*) begin
// Default assignment
y = 1'b0;
// Decision logic
case (sel)
2'b00: y = a;
2'b01: y = b;
default: y = 1'b0;
endcase
end
This template is safe, readable, and avoids unintended latch inference.
Summary
A consistent coding style is essential for writing correct combinational logic. By following these guidelines, designers can avoid common errors and produce clean, reliable RTL code.
These practices will also make debugging easier and improve code readability in larger designs.
8.8 Common Mistakes in Combinational Design
Common Mistakes
This section summarizes the most common mistakes in combinational behavioral modeling. These are frequently tested in quizzes, exams, and practical labs.
Students should be able to identify these issues quickly and correct them.
1. Missing Assignment (Latch Inference)
// Exam Trap
always @(*) begin
if (sel)
y = a;
end
Problem: Output not assigned when sel = 0
Result: Latch inferred
Fix:
always @(*) begin
if (sel)
y = a;
else
y = b;
end
2. Missing default in case Statement
// Exam Trap
always @(*) begin
case (sel)
2'b00: y = a;
2'b01: y = b;
endcase
end
Problem: Not all cases covered
Result: Latch inferred or undefined behavior
Fix:
always @(*) begin
case (sel)
2'b00: y = a;
2'b01: y = b;
default: y = 1'b0;
endcase
end
3. Using Non-blocking Assignment in Combinational Logic
// Exam Trap
always @(*) begin
y <= a & b;
end
Problem: Wrong assignment type
Fix: Use =
y = a & b;
4. Using Clock in Combinational Logic
// Exam Trap
always @(posedge clk) begin
y = a | b;
end
Problem: This creates sequential logic, not combinational
Fix:
always @(*) begin
y = a | b;
end
5. Missing Default Assignment Pattern
Bad Style:
always @(*) begin
if (sel == 2'b00)
y = a;
else if (sel == 2'b01)
y = b;
end
Problem: Some conditions not assigned
Recommended Style:
always @(*) begin
y = 1'b0; // default
if (sel == 2'b00)
y = a;
else if (sel == 2'b01)
y = b;
end
6. Misusing if-else vs case
Key Exam Concept:
| Construct | Behavior |
|---|---|
| if-else | Priority logic |
| case | Parallel logic |
Using the wrong structure may produce incorrect hardware.
7. Overusing Nested Conditional Operator
// Hard to read
y = (sel==2'b00)?a :
(sel==2'b01)?b :
(sel==2'b10)?c : d;
Problem: Poor readability
Better: Use case
8. Quick Exam Checklist
Before submitting your answer, check:
- Are all outputs assigned in every condition?
- Is always @(*) used?
- Are you using = instead of <=?
- Does case include default?
- Are you accidentally creating a latch?
Summary
Most combinational design errors are not syntax errors but logic errors. These mistakes often appear in exams and practical design tasks.
By recognizing these patterns, students can quickly identify incorrect designs and apply the correct coding style.
8.9 Summary
Summary
In this lesson, we introduced combinational behavioral modeling using Verilog. This modeling style allows designers to describe logic circuits using procedural constructs such as always @(*), if-else, case, and the conditional operator.
Key Concepts
- Combinational logic depends only on current input values.
- No clock or memory element should be used.
- Outputs must always reflect input changes immediately.
Essential Coding Rules
- Use always @(*) for combinational logic.
- Use blocking assignment =.
- Assign all outputs in every condition (Rule 01).
- Use else or default to cover all cases.
Choosing the Right Construct
| Construct | Usage |
|---|---|
| ?: | Simple selection logic |
| if-else | Priority-based decisions |
| case | Multi-way selection |
Most Important Concept: Avoid Latch
The most critical concept in this lesson is avoiding unintended latch inference.
Latches are created when outputs are not assigned in all conditions. This introduces memory behavior, which violates the definition of combinational logic.
Always follow: Rule 01: Assign all outputs in combinational logic.
Exam Focus
Students should be able to:
- Identify missing assignments in if-else and case.
- Recognize when a latch is inferred.
- Distinguish between combinational and sequential code.
- Choose the correct modeling construct for a given problem.
Final Design Guideline
A good combinational design follows this structure:
always @(*) begin
// Default assignment
y = 1'b0;
// Decision logic
case (sel)
2'b00: y = a;
2'b01: y = b;
default: y = 1'b0;
endcase
end
This ensures correctness, readability, and avoids unintended hardware behavior.
Looking Ahead
In the next lesson, we will move to sequential behavioral modeling, where clock signals, registers, and memory elements are introduced.
Understanding the difference between combinational and sequential logic is essential for all digital system design.