Clock domains

Introduction

In SpinalHDL, clock and reset signals can be combined to create a clock domain. Clock domains can be applied to some areas of the design and then all synchronous elements instantiated into those areas will then implicitly use this clock domain.

Clock domain application works like a stack, which means that if you are in a given clock domain you can still apply another clock domain locally.

Instantiation

The syntax to define a clock domain is as follows (using EBNF syntax):

ClockDomain(
  clock: Bool
  [,reset: Bool]
  [,softReset: Bool]
  [,clockEnable: Bool]
  [,frequency: IClockDomainFrequency]
  [,config: ClockDomainConfig]
)

This definition takes five parameters:

Argument

Description

Default

clock

Clock signal that defines the domain

reset

Reset signal. If a register exists which needs a reset and the clock domain doesn’t provide one, an error message will be displayed

null

softReset

Reset which infers an additional synchronous reset

null

clockEnable

The goal of this signal is to disable the clock on the whole clock domain without having to manually implement that on each synchronous element

null

frequency

Allows you to specify the frequency of the given clock domain and later read it in your design

UnknownFrequency

config

Specify the polarity of signals and the nature of the reset

Current config

An applied example to define a specific clock domain within the design is as follows:

val coreClock = Bool
val coreReset = Bool

// Define a new clock domain
val coreClockDomain = ClockDomain(coreClock, coreReset)

// Use this domain in an area of the design
val coreArea = new ClockingArea(coreClockDomain) {
  val coreClockedRegister = Reg(UInt(4 bit))
}

Configuration

In addition to constructor parameters, the following elements of each clock domain are configurable via a ClockDomainConfigclass:

Property

Valid values

clockEdge

RISING, FALLING

resetKind

ASYNC, SYNC, and BOOT which is supported by some FPGAs (where FF values are loaded by the bitstream)

resetActiveLevel

HIGH, LOW

softResetActiveLevel

HIGH, LOW

clockEnableActiveLevel

HIGH, LOW

class CustomClockExample extends Component {
  val io = new Bundle {
    val clk    = in Bool
    val resetn = in Bool
    val result = out UInt (4 bits)
  }

  // Configure the clock domain
  val myClockDomain = ClockDomain(
    clock  = io.clk,
    reset  = io.resetn,
    config = ClockDomainConfig(
      clockEdge        = RISING,
      resetKind        = ASYNC,
      resetActiveLevel = LOW
    )
  )

  // Define an Area which use myClockDomain
  val myArea = new ClockingArea(myClockDomain) {
    val myReg = Reg(UInt(4 bits)) init(7)

    myReg := myReg + 1

    io.result := myReg
  }
}

By default, a ClockDomain is applied to the whole design. The configuration of this default domain is:

  • Clock : rising edge

  • Reset : asynchronous, active high

  • No clock enable

This corresponds to the following ClockDomainConfig:

val defaultCC = ClockDomainConfig(
  clockEdge        = RISING,
  resetKind        = ASYNC,
  resetActiveLevel = HIGH
)

Internal clock

An alternative syntax to create a clock domain is the following:

ClockDomain.internal(
  name: String,
  [config: ClockDomainConfig,]
  [withReset: Boolean,]
  [withSoftReset: Boolean,]
  [withClockEnable: Boolean,]
  [frequency: IClockDomainFrequency]
)

This definition takes six parameters:

Argument

Description

Default

name

Name of clk and reset signal

config

Specify polarity of signals and the nature of the reset

Current config

withReset

Add a reset signal

true

withSoftReset

Add a soft reset signal

false

withClockEnable

Add a clock enable

false

frequency

Frequency of the clock domain

UnknownFrequency

The advantage of this approach is to create clock and reset signals with a known/specified name instead of an inherited one.

Once created, you have to assign the ClockDomain’s signals, as shown in the example below:

class InternalClockWithPllExample extends Component {
  val io = new Bundle {
    val clk100M = in Bool
    val aReset  = in Bool
    val result  = out UInt (4 bits)
  }
  // myClockDomain.clock will be named myClockName_clk
  // myClockDomain.reset will be named myClockName_reset
  val myClockDomain = ClockDomain.internal("myClockName")

  // Instantiate a PLL (probably a BlackBox)
  val pll = new Pll()
  pll.io.clkIn := io.clk100M

  // Assign myClockDomain signals with something
  myClockDomain.clock := pll.io.clockOut
  myClockDomain.reset := io.aReset || !pll.io.

  // Do whatever you want with myClockDomain
  val myArea = new ClockingArea(myClockDomain) {
    val myReg = Reg(UInt(4 bits)) init(7)
    myReg := myReg + 1

    io.result := myReg
  }
}

External clock

You can define a clock domain which is driven by the outside anywhere in your source. It will then automatically add clock and reset wires from the top level inputs to all synchronous elements.

ClockDomain.external(
  name: String,
  [config: ClockDomainConfig,]
  [withReset: Boolean,]
  [withSoftReset: Boolean,]
  [withClockEnable: Boolean,]
  [frequency: IClockDomainFrequency]
)

The arguments to the ClockDomain.external function are exactly the same as in the ClockDomain.internal function. Below is an example of a design using ClockDomain.external:

class ExternalClockExample extends Component {
  val io = new Bundle {
    val result = out UInt (4 bits)
  }

  // On the top level you have two signals  :
  //     myClockName_clk and myClockName_reset
  val myClockDomain = ClockDomain.external("myClockName")

  val myArea = new ClockingArea(myClockDomain) {
    val myReg = Reg(UInt(4 bits)) init(7)
    myReg := myReg + 1

    io.result := myReg
  }
}

Context

You can retrieve in which clock domain you are by calling ClockDomain.current anywhere.

The returned ClockDomain instance has the following functions that can be called:

name

Description

Return

frequency.getValue

Return the frequency of the clock domain

Double

hasReset

Return if the clock domain has a reset signal

Boolean

hasSoftReset

Return if the clock domain has a soft reset signal

Boolean

hasClockEnable

Return if the clock domain has a clock enable signal

Boolean

readClockWire

Return a signal derived from the clock signal

Bool

readResetWire

Return a signal derived from the soft reset signal

Bool

readSoftResetWire

Return a signal derived from the reset signal

Bool

readClockEnableWire

Return a signal derived from the clock enable signal

Bool

isResetActive

Return True when the reset is active

Bool

isSoftResetActive

Return True when the soft reset is active

Bool

isClockEnableActive

Return True when the clock enable is active

Bool

An example is included below where a UART controller uses the frequency specification to set its clock divider:

val coreClockDomain = ClockDomain(coreClock, coreReset, frequency=FixedFrequency(100e6))

val coreArea = new ClockingArea(coreClockDomain) {
  val ctrl = new UartCtrl()
  ctrl.io.config.clockDivider := (coreClk.frequency.getValue / 57.6e3 / 8).toInt
}

Clock domain crossing

SpinalHDL checks at compile time that there are no unwanted/unspecified cross clock domain signal reads. If you want to read a signal that is emitted by another ClockDomain area, you should add the crossClockDomain tag to the destination signal as depicted in the following example:

//             _____                        _____             _____
//            |     |  (crossClockDomain)  |     |           |     |
//  dataIn -->|     |--------------------->|     |---------->|     |--> dataOut
//            | FF  |                      | FF  |           | FF  |
//  clkA   -->|     |              clkB -->|     |   clkB -->|     |
//  rstA   -->|_____|              rstB -->|_____|   rstB -->|_____|



// Implementation where clock and reset pins are given by components' IO
class CrossingExample extends Component {
  val io = new Bundle {
    val clkA = in Bool
    val rstA = in Bool

    val clkB = in Bool
    val rstB = in Bool

    val dataIn  = in Bool
    val dataOut = out Bool
  }

  // sample dataIn with clkA
  val area_clkA = new ClockingArea(ClockDomain(io.clkA,io.rstA)) {
    val reg = RegNext(io.dataIn) init(False)
  }

  // 2 register stages to avoid metastability issues
  val area_clkB = new ClockingArea(ClockDomain(io.clkB,io.rstB)) {
    val buf0   = RegNext(area_clkA.reg) init(False) addTag(crossClockDomain)
    val buf1   = RegNext(buf0)          init(False)
  }

  io.dataOut := area_clkB.buf1
}


// Alternative implementation where clock domains are given as parameters
class CrossingExample(clkA : ClockDomain,clkB : ClockDomain) extends Component {
  val io = new Bundle {
    val dataIn  = in Bool
    val dataOut = out Bool
  }

  // sample dataIn with clkA
  val area_clkA = new ClockingArea(clkA) {
    val reg = RegNext(io.dataIn) init(False)
  }

  // 2 register stages to avoid metastability issues
  val area_clkB = new ClockingArea(clkB) {
    val buf0   = RegNext(area_clkA.reg) init(False) addTag(crossClockDomain)
    val buf1   = RegNext(buf0)          init(False)
  }

  io.dataOut := area_clkB.buf1
}

In general, you can use 2 or more flip-flop driven by the destination clock domain to prevent metastability. The BufferCC(input: T, init: T = null, bufferDepth: Int = 2) function provided in spinal.lib._ will instantiate the necessary flip-flops (the number of flip-flops will depends on the bufferDepth parameter) to mitigate the phenomena.

class CrossingExample(clkA : ClockDomain,clkB : ClockDomain) extends Component {
  val io = new Bundle {
    val dataIn  = in Bool
    val dataOut = out Bool
  }

  // sample dataIn with clkA
  val area_clkA = new ClockingArea(clkA) {
    val reg = RegNext(io.dataIn) init(False)
  }

  // BufferCC to avoid metastability issues
  val area_clkB = new ClockingArea(clkB) {
    val buf1   = BufferCC(area_clkA.reg, False)
  }

  io.dataOut := area_clkB.buf1
}

Warning

The BufferCC function is only for signals of type Bit, or Bits operating as Gray-coded counters (only 1 bit-flip per clock cycle), and can not used for multi-bit cross-domain processes.

Special clocking Areas

Slow Area

A SlowArea is used to create a new clock domain area which is slower than the current one:

class TopLevel extends Component {

  // Use the current clock domain : 100MHz
  val areaStd = new Area {
    val counter = out(CounterFreeRun(16).value)
  }

  // Slow the current clockDomain by 4 : 25 MHz
  val areaDiv4 = new SlowArea(4) {
    val counter = out(CounterFreeRun(16).value)
  }

  // Slow the current clockDomainn to 50MHz
  val area50Mhz = new SlowArea(50 MHz) {
    val counter = out(CounterFreeRun(16).value)
  }
}

def main(args: Array[String]) {
  new SpinalConfig(
    defaultClockDomainFrequency = FixedFrequency(100 MHz)
  ).generateVhdl(new TopLevel)
}

ResetArea

A ResetArea is used to create a new clock domain area where a special reset signal is combined with the current clock domain reset:

class TopLevel extends Component {

  val specialReset = Bool

  // The reset of this area is done with the specialReset signal
  val areaRst_1 = new ResetArea(specialReset, false) {
    val counter = out(CounterFreeRun(16).value)
  }

  // The reset of this area is a combination between the current reset and the specialReset
  val areaRst_2 = new ResetArea(specialReset, true) {
    val counter = out(CounterFreeRun(16).value)
  }
}

ClockEnableArea

A ClockEnableArea is used to add an additional clock enable in the current clock domain:

class TopLevel extends Component {

  val clockEnable = Bool

  // Add a clock enable for this area
  val area_1 = new ClockEnableArea(clockEnable) {
    val counter = out(CounterFreeRun(16).value)
  }
}