Conditionals
DFHDL supports two main types of conditional constructs: if
expressions and match
expressions. Both can be used for control flow and value selection in hardware designs.
If Expressions
Basic Syntax
1 2 |
|
condition
must be a DFHDLBoolean
orBit
valueconsequent
andalternative
are the code blocks executed based on the condition- The
else
clause is optional
Examples
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Rules
- Type Consistency: Both branches must produce compatible types if used as an expression
- Scope: Variables declared inside if blocks are only visible within that block
- Hardware Generation: Each branch generates hardware - both paths exist in parallel
- Constant Conditions: If conditions are constant, unused branches may be optimized out during elaboration
Match Expressions
Match expressions provide pattern matching capabilities similar to Scala's match expressions but optimized for hardware description.
Basic Syntax
1 2 3 4 |
|
Pattern Types
-
Literal Patterns:
1 2 3 4
x match case 0 => y := 0 case 1 => y := 1 case _ => y := x
-
Multiple Values:
1 2 3 4
x match case 1 | 2 | 4 => y := 0 case 3 | 5 | 6 => y := 1 case _ => y := 2
-
Struct Patterns:
1 2 3 4
pixel match case Pixel(0, y) => output := y case Pixel(x, 0) => output := x case Pixel(x, y) => output := x + y
-
Guard Patterns:
1 2 3 4
x match case n if n > 10 => y := 1 case n if n < 0 => y := 0 case _ => y := x
-
Bit Patterns with Wildcards:
1 2 3 4
bits match case b"1??0" => y := 1 case b"0??1" => y := 0 case _ => y := x
-
Bit Extractor Patterns:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
// Match and extract a 32-bit section between DEAD and BEEF y match case h"DEAD${secret: B[32]}BEEF" => // Use extracted secret bits // Extract multiple sections y match case h"DE${part1: B[16]}AD${part2: B[16]}BEEF" => // Use part1 and part2 bits // Store extracted bits in variables val h"DEAD${extracted: B[32]}BEEF" = input: @unchecked // Extract multiple sections into variables val h"DE${first: B[16]}ADBE${second: B[16]}EF" = input: @unchecked // Using guards with extracted bit fields y match case h"DEAD${secret: B[32]}BEEF" if secret > h"20000000" => // Match when the secret section is greater than 0x20000000 case h"DE${part1: B[16]}AD${part2: B[16]}BEEF" if part1 == h"FFFF" => // Match when part1 is all ones
Bit extractor patterns allow you to:
- Match specific bit patterns while extracting variable sections
- Use hex or binary notation for the fixed parts
- Specify the width of extracted sections using B[width]
syntax
- Store extracted bits in variables for later use
- Extract multiple sections in a single pattern match
- Use guards to add conditions on extracted bit fields
Match to If Conversion
During compilation, match expressions are typically converted to if-else chains for hardware implementation. For example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Complex Selectors
When using expressions as match selectors, a temporary variable is created:
1 2 3 4 5 6 7 8 9 10 11 |
|
Rules
- Exhaustiveness: Match expressions must cover all possible cases
- Pattern Order: Patterns are evaluated in order, first match wins
- Type Safety: All case branches must produce compatible types if used as an expression
- Hardware Implementation:
- Converts to if-else chains for most cases
- Optimizes bit pattern matching into efficient comparisons
- Extracts struct fields into temporary variables when needed
Best Practices
- Use Match for Multi-Way Branching: When dealing with multiple cases, match is often clearer than nested if-else
- State Machines: Match expressions are ideal for state machine implementations
- Pattern Priority: Put more specific patterns before general ones
- Wildcards: Use
_
for catch-all cases - Guards: Use sparingly as they may generate more complex hardware
Hardware Implications
Both if
and match
expressions generate multiplexer circuits in hardware. The choice between them should consider:
- Readability: Match expressions are often clearer for multiple cases
- Hardware Efficiency: Simple if-else may generate simpler hardware
- Timing: Complex conditions may impact critical paths
- Resource Usage: Each branch generates hardware, even if mutually exclusive
Examples
State Machine
1 2 3 4 5 6 7 8 |
|
Decoder
1 2 3 4 5 6 7 8 9 |
|
Complex Pattern Matching
1 2 3 4 5 6 7 8 9 |
|
Usage Modes
DFHDL conditionals can be used in two ways: as statements and as expressions.
Statement Usage
When used as statements, if
and match
are used for their side effects (like assignments) rather than producing a value:
1 2 3 4 5 6 7 8 9 10 |
|
Expression Usage
When used as expressions, if
and match
produce values that can be assigned or used in computations:
1 2 3 4 5 6 7 8 9 10 11 |
|
Key differences: 1. Type Requirements: - Statements: No return type consistency required between branches - Expressions: All branches must return compatible types
- Assignment Context:
- Statements: Can contain multiple assignments per branch
-
Expressions: Must evaluate to a single value
-
Variable Declaration:
1 2 3 4 5 6 7 8 9 10 11
// Direct assignment from expression val res2 = UInt(8) <> VAR res2 := (if (i) 1 else if (!i) x.bits.uint else 2) // Using statement form for multiple assignments if (i) x := 1 y := 2 else x := 3 y := 4
Expression vs. Statement Form
DFHDL conditionals can be written in two equivalent forms that generate different hardware structures:
Expression Form
In expression form, the conditional is part of the right-hand side of an assignment:
1 2 3 4 5 6 7 |
|
Statement Form
In statement form, the assignments are inside the conditional branches:
1 2 3 4 5 6 7 8 9 10 |
|
Key Differences
- Hardware Structure:
- Expression form: Generates a single multiplexer with the condition as select
-
Statement form: Generates multiple assignments with enable signals derived from conditions
-
Code Organization:
- Expression form: Better when the only difference between branches is the value being assigned
-
Statement form: Better when branches perform multiple operations or have different side effects
-
Timing Implications:
- Expression form: All values are computed in parallel, then selected
-
Statement form: Each branch's logic is gated by its condition
-
Common Use Cases:
1 2 3 4 5 6 7 8 9 10 11 12
// Expression form - simple value selection val result = UInt(8) <> OUT result := (if (valid) computed_value else 0) // Statement form - multiple operations if (valid) result := computed_value status := VALID counter := counter + 1 else result := 0 status := INVALID
Choose the form that best matches your design's requirements: - Use expression form for simple value selection - Use statement form for complex control flow or multiple operations per branch