Skip to content

Design Domains

DFHDL offers three key domain abstractions—dataflow (DF), register-transfer (RT), and event-driven (ED)—all within a single HDL, as illustrated in the following figure. This unique capability allows developers to employ a cohesive syntax to seamlessly blend these abstractions: DF, RT, and ED. Each abstraction brings its own set of advantages in terms of control, synthesizability, simulation speed, and functional correctness.

The RT abstraction mirrors the capabilities found in languages like Chisel and Amaranth, while the ED abstraction aligns with the functionalities of VHDL and Verilog. Through an intelligent compilation process, the DFHDL compiler transitions from the higher-level DF abstraction through RT and ultimately to ED. The choice of compilation dialect—whether VHDL 93/2008 or Verilog/SystemVerilog—determines the final ED code representation.

design-domains design-domains

Dataflow (DF) Domain

The dataflow domain provides the highest level of abstraction, focusing on data dependencies rather than timing.

Key Features

  • Timing-agnostic descriptions
  • Implicit state handling
  • Token stream semantics
  • History access via .prev

Example

1
2
3
4
class Accumulator extends DFDesign:
  val input = UInt(8) <> IN
  val sum = UInt(16) <> OUT init 0
  sum := sum.prev + input  // Implicit state handling

Register Transfer (RT) Domain

The RT domain provides explicit control over registers and timing while maintaining hardware-friendly abstractions.

Domain Configuration

Clock Configuration

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// Clock edge options
val risingEdge = ClkCfg.Edge.Rising   // Rising edge triggered
val fallingEdge = ClkCfg.Edge.Falling // Falling edge triggered

// Clock configuration with options
val clkCfg = ClkCfg(
  edge = ClkCfg.Edge.Rising,    // Clock edge sensitivity
  rate = 50.MHz,                // Clock frequency
  portName = "clk",             // Port name in generated code
  inclusionPolicy = ClkCfg.InclusionPolicy.AsNeeded // Port inclusion policy
)

Reset Configuration

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// Reset mode options
val asyncRst = RstCfg.Mode.Async  // Asynchronous reset
val syncRst = RstCfg.Mode.Sync    // Synchronous reset

// Reset active level
val activeHigh = RstCfg.Active.High // Reset active at 1
val activeLow = RstCfg.Active.Low   // Reset active at 0

// Reset configuration with options
val rstCfg = RstCfg(
  mode = RstCfg.Mode.Sync,         // Reset mode
  active = RstCfg.Active.High,     // Active level
  portName = "rst",                // Port name in generated code
  inclusionPolicy = RstCfg.InclusionPolicy.AsNeeded // Port inclusion policy
)

RTDomain Configuration

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// Complete domain configuration
val domainCfg = RTDomainCfg(clkCfg, rstCfg)

// Special configurations
val combDomain = RTDomainCfg.Comb           // Combinational domain (no clk/rst)
val defaultDomain = RTDomainCfg.Default     // Default configuration
val derivedDomain = RTDomainCfg.Derived     // Derived from parent domain

// Domain without reset
val noRstDomain = domainCfg.norst           // Remove reset from configuration

Inclusion Policies

The InclusionPolicy determines how clock and reset ports are included in the generated code:

  • AsNeeded: Only includes ports when they are actually used
  • AlwaysAtTop: Always includes ports at the top level with @unused annotation if not used

Domain Types

Basic RT Domain

1
2
3
4
class BasicRTDesign extends RTDesign(domainCfg):
  val x = UInt(8) <> IN
  val y = UInt(8) <> OUT.REG init 0
  y := x.reg  // Registered on configured clock edge

Multiple Clock Domains

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class MultiClockDesign extends RTDesign(mainCfg):
  // Main domain at 100MHz
  val mainClk = ClkCfg(edge = ClkCfg.Edge.Rising, rate = 100.MHz)
  val mainRst = RstCfg(mode = RstCfg.Mode.Sync, active = RstCfg.Active.High)

  // Slow domain at 25MHz
  val slowClk = ClkCfg(edge = ClkCfg.Edge.Rising, rate = 25.MHz)
  val slowDomain = new RTDomain(RTDomainCfg(slowClk, mainRst)):
    val slow_reg = UInt(8) <> VAR.REG init 0

  // Fast domain at 200MHz
  val fastClk = ClkCfg(edge = ClkCfg.Edge.Rising, rate = 200.MHz)
  val fastDomain = new RTDomain(RTDomainCfg(fastClk, mainRst)):
    val fast_reg = UInt(8) <> VAR.REG init 0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class RelatedDomainsDesign extends RTDesign(mainCfg):
  val baseDomain = new RTDomain(baseCfg):
    val base_reg = UInt(8) <> VAR.REG init 0

  // Inherits clock/reset from baseDomain
  val relatedDomain = new baseDomain.RelatedDomain:
    val related_reg = UInt(8) <> VAR.REG init 0

  // Related domain without reset
  val noRstRelated = new baseDomain.RelatedDomain(baseCfg.norst):
    val norst_reg = UInt(8) <> VAR.REG init 0

Register Types and Initialization

Register Declarations vs Aliases

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class RegisterPatterns extends RTDesign(cfg):
  val x = UInt(8) <> IN

  // Register Declaration - creates a new register
  val reg1 = UInt(8) <> VAR.REG init 0  // Variable register
  val out1 = UInt(8) <> OUT.REG init 0  // Output register

  // Register Alias - creates a registered version of a signal
  val delayed = x.reg      // One cycle delay of x
  val delayed2 = x.reg(2)  // Two cycle delay of x

Register Access Patterns

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class RegisterAccess extends RTDesign(cfg):
  val x = UInt(8) <> IN
  val reg = UInt(8) <> VAR.REG init 0
  val out = UInt(8) <> OUT.REG init 0

  // CORRECT: Writing to register input using .din
  reg.din := x            // Updates register input
  out.din := reg         // Updates output register input

  // INCORRECT: Attempting to write to register output
  reg := x               // Error: Can't write to register output
  out := reg            // Error: Can't write to register output

  // Reading from registers (always reads output)
  val value = reg        // Reads register output
  val outValue = out     // Reads output register value

Register Composition

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class RegisterComposition extends RTDesign(cfg):
  val x = UInt(8) <> IN

  // Using register declarations
  val reg1 = UInt(8) <> VAR.REG init 0
  val reg2 = UInt(8) <> VAR.REG init 0
  reg1.din := x
  reg2.din := reg1      // Chaining registers

  // Using register aliases
  val stage1 = x.reg    // Same as reg1
  val stage2 = x.reg(2) // Same as reg2, but more concise

  // Mixing declarations and aliases
  val reg3 = UInt(8) <> VAR.REG init 0
  reg3.din := stage2    // Can mix both styles

Advanced Register Patterns

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class AdvancedRegisters extends RTDesign(cfg):
  val x = UInt(8) <> IN
  val y = UInt(8) <> IN

  // Conditional registration
  val reg = UInt(8) <> VAR.REG init 0
  if (x > 10)
    reg.din := y     // Register y when x > 10
  else
    reg.din := x     // Register x otherwise

  // Register with enable
  val enReg = UInt(8) <> VAR.REG init 0
  val en = Bit <> IN
  if (en)
    enReg.din := x   // Only update when enabled

  // Multiple cycle delays with processing
  val processed = (x + 1).reg(2)  // Add 1 and delay 2 cycles

Common Patterns and Pitfalls

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class RegisterPitfalls extends RTDesign(cfg):
  val x = UInt(8) <> IN
  val reg = UInt(8) <> VAR.REG init 0

  // GOOD: Explicit input/output separation
  reg.din := x + 1        // Write to input
  val result = reg + 2    // Read from output

  // BAD: Attempting to write to output
  reg := x + 1           // Error: Writing to output

  // GOOD: Register alias for simple delays
  val delayed = x.reg    // Clean and clear intent

  // BAD: Unnecessary register declaration for simple delay
  val regDelay = UInt(8) <> VAR.REG init 0
  regDelay.din := x      // More verbose than needed

Event-Driven (ED) Domain

The ED domain provides the lowest level of abstraction, with explicit process blocks and event sensitivity.

Process Types

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class EDExample extends EDDesign:
  val clk = Bit <> IN
  val rst = Bit <> IN
  val x = UInt(8) <> IN
  val y = UInt(8) <> OUT

  // Combinational process
  process(all):
    y := x + 1

  // Clock-sensitive process
  process(clk.rising):
    y := x

  // Clock and reset process
  process(clk, rst):
    if (rst)
      y := 0
    else if (clk.rising)
      y := x

Assignment Types

  • Blocking (:=): Immediate effect
  • Non-blocking (:==): Scheduled update
    1
    2
    3
    process(clk.rising):
      val temp = x  // Blocking read
      y :== temp    // Non-blocking write
    

Domain Interaction

Cross-Domain Communication

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class CrossDomainExample extends RTDesign(mainCfg):
  val x = UInt(8) <> IN

  val domainA = new RTDomain(cfgA):
    val reg_a = UInt(8) <> VAR.REG init 0
    reg_a := x.reg

  val domainB = new RTDomain(cfgB):
    val reg_b = UInt(8) <> VAR.REG init 0
    reg_b := domainA.reg_a.reg  // Cross-domain registration

Domain Flattening

During compilation, nested domains are flattened while preserving clock and reset relationships:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// Original nested domains
val innerDomain = new RTDomain(innerCfg):
  val reg = UInt(8) <> VAR.REG init 0

// After flattening
val innerDomain_reg = UInt(8) <> VAR.REG init 0
process(innerDomain_clk.rising):
  if (innerDomain_rst) 
    innerDomain_reg := 0
  else 
    innerDomain_reg := next_value

Compilation Flow

  1. Domain Resolution:
  2. Flattens nested domains
  3. Resolves clock and reset configurations
  4. Establishes domain hierarchies

  5. State Management:

  6. Converts DF .prev to explicit registers
  7. Handles RT register declarations
  8. Manages ED process state variables

  9. Process Generation:

  10. Converts DF and RT to ED processes
  11. Optimizes sensitivity lists
  12. Handles blocking/non-blocking assignments

  13. Backend Generation:

  14. Generates VHDL or Verilog code
  15. Preserves timing relationships
  16. Maintains design hierarchy

Best Practices

  1. Domain Selection:
  2. Use DF for algorithmic descriptions
  3. Use RT for timing-critical paths
  4. Use ED for low-level control

  5. Clock Domain Crossing:

  6. Use explicit synchronization
  7. Maintain clear domain boundaries
  8. Document clock relationships

  9. State Management:

  10. Initialize all registers
  11. Use appropriate reset strategies
  12. Consider reset domains

  13. Performance Optimization:

  14. Balance domain abstractions
  15. Use appropriate clock domains
  16. Consider resource utilization