时钟域

简介

在 SpinalHDL 中,时钟和复位信号可以组合起来创建**时钟域**。时钟域可以应用于设计的某些区域,然后实例化到这些区域中的所有同步元件将**隐式**使用该时钟域。

时钟域的应用方式类似于堆栈,这意味着当您的设计位于给定时钟域中,您仍然可以将该设计应用到另一个时钟域。

请注意,寄存器在创建时捕获其时钟域,而不是在赋值时捕获。因此,请确保在所需的 ClockingArea 内创建它们。

实例化

定义时钟域的语法如下(使用EBNF语法):

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

这个定义有五个参数:

参数

描述

默认值

clock

定义时钟域中的时钟信号

reset

复位信号。如果存在需要复位的寄存器,而时钟域没有提供复位,则会显示错误消息

null

softReset

复位意味着额外的同步复位

null

clockEnable

该信号的目标是禁用整个时钟域上的时钟,而无需在每个同步元件上手动实现

null

frequency

允许您指定给定时钟域的频率,然后在您的设计中读取它。该参数不生成PLL或其他硬件来控制频率

UnknownFrequency

config

指定信号的极性和复位的性质

当前配置

在设计中定义具有指定属性时钟域的示例如下:

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 bits))
}

当不需要 Area 时,也可以直接应用时钟域。存在两种语法:

class Counters extends Component {
  val io = new Bundle {
    val enable = in Bool ()
    val freeCount, gatedCount, gatedCount2 = out UInt (4 bits)
  }
  val freeCounter = CounterFreeRun(16)
  io.freeCount := freeCounter.value

  // In a real design consider using a glitch free single purpose CLKGATE primitive instead
  val gatedClk = ClockDomain.current.readClockWire && io.enable
  val gated = ClockDomain(gatedClk, ClockDomain.current.readResetWire)

  // Here the "gated" clock domain is applied on "gatedCounter" and "gatedCounter2"
  val gatedCounter = gated(CounterFreeRun(16))
  io.gatedCount := gatedCounter.value
  val gatedCounter2 = gated on CounterFreeRun(16)
  io.gatedCount2 := gatedCounter2.value

  assert(gatedCounter.value === gatedCounter2.value, "gated count mismatch")
}

配置

除了 构造函数参数之外,每个时钟域的以下元素都可以通过 ClockDomainConfig类进行配置:

属性

有效值

clockEdge

RISING, FALLING

resetKind

某些 FPGA 支持的 ASYNCSYNCBOOT (其中 FF 值由比特流加载)

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
  }
}

默认情况下, ClockDomain 应用于整个设计。该默认域的配置为:

  • Clock:上升沿

  • Reset :异步,高电平有效

  • 无时钟使能

这对应于以下 ClockDomainConfig

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

内部时钟

另一种创建时钟域的语法如下:

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

该定义有六个参数:

参数

描述

默认值

name

clkreset 信号的名称

config

指定信号的极性和复位的性质

当前配置

withReset

添加复位信号

true

withSoftReset

添加软复位信号

false

withClockEnable

添加时钟使能

false

frequency

时钟域频率

UnknownFrequency

这种方法的优点是使用已知/指定的名称而不是继承的名称来创建时钟和复位信号。

创建后,您必须分配 ClockDomain 的信号,如下例所示:

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
  }
}

警告

在您创建时钟域的其他组件中,您不得使用 .clock.reset,而应使用 .readClockWire.readResetWire ,如下所示。对于全局时钟域,您必须始终使用这些 .readXXX 函数。

外部时钟

您可以在源中的任何位置定义由外部驱动的时钟域。然后,它会自动将时钟和复位线从顶层输入添加到所有同步元件。

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

ClockDomain.external 函数的参数与 ClockDomain.internal 函数中的参数完全相同。下面是使用 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
  }
}

生成 HDL 时的信号优先级

在当前版本中,复位和时钟使能信号具有不同的优先级。它们的顺序是: asyncReset, clockEnable, syncResetsoftReset

请注意,clockEnable 的优先级高于syncReset。如果在禁用clockEnable(尤其是在仿真开始时)时执行同步重置,则门控寄存器将不会重置。

这是一个例子:

val clockedArea = new ClockEnableArea(clockEnable) {
  val reg = RegNext(io.input) init(False)
}

它将生成 Verilog HDL 代码,例如:

always @(posedge clk) begin
  if(clockedArea_newClockEnable) begin
    if(!resetn) begin
      clockedArea_reg <= 1'b0;
    end else begin
      clockedArea_reg <= io_input;
    end
  end
end

如果该行为有问题,一种解决方法是使用when 语句作为时钟使能,而不是使用ClockDomain.enable 功能。这对于未来的改进是开放的。

语境

您可以通过在任何地方调用 ClockDomain.current 来检索您所在的时钟域。

返回的 ClockDomain 实例具有以下可以调用的函数:

名称

描述

返回类型

frequency.getValue

返回时钟域的频率。
这是您配置域的任意值。

Double

hasReset

如果时钟域有复位信号则返回

Boolean

hasSoftReset

返回时钟域是否有软复位信号

Boolean

hasClockEnable

返回时钟域是否有时钟使能信号

Boolean

readClockWire

返回从时钟信号派生的信号

Bool

readResetWire

返回一个从复位信号派生的信号

Bool

readSoftResetWire

返回从软复位信号派生的信号

Bool

readClockEnableWire

返回从时钟使能信号派生的信号

Bool

isResetActive

当复位有效时返回 True

Bool

isSoftResetActive

当软复位有效时返回 True

Bool

isClockEnableActive

当时钟使能有效时返回 True

Bool

下面包含一个示例,其中通过 UART 控制器使用频率来设置其时钟分频器:

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
}

跨时钟域设计

SpinalHDL 在编译时检查是否存在不需要的/未指定的跨时钟域信号读取。如果您想读取另一个 ClockDomain 逻辑区发出的信号,则应给目标信号增加 crossClockDomain 标记,如下例所示:

//             _____                        _____             _____
//            |     |  (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
}

一般来说,可以使用2个或更多由目标时钟域驱动的触发器来防止亚稳态。 spinal.lib._ 中提供的 BufferCC(input: T, init: T = null, bufferDepth: Int = 2) 函数将实例化必要的触发器(触发器的数量将取决于 bufferDepth 参数)来减轻这种现象。

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
}

警告

BufferCC 函数仅适用于 Bit 类型的信号,或作为格雷编码计数器运行的 Bits 信号(每个时钟周期仅翻转 1 位),并且不能用于多位跨时钟域信号。对于多位情况,建议使用 StreamFifoCC 来满足高带宽要求,或者在带宽要求不高的情况下使用 StreamCCByToggle 来减少资源使用。

特殊计时逻辑区

慢时钟逻辑区

SlowArea 用于创建一个逻辑区,使用比当前时钟域慢的新时钟域:

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 clockDomain 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)
}

警告

SlowArea 中使用的时钟信号与父区域相同。而 SlowArea 会添加一个时钟启用信号,以减慢其内部的采样率。换句话说,ClockDomain.current.readClockWire 将返回快速(父域)时钟。要获取时钟使能信号,请使用 ClockDomain.current.readClockEnableWire

启动复位

clockDomain.withBootReset() 可以指定寄存器的resetKind为BOOT。 clockDomain.withSyncReset() 可以指定寄存器的resetKind为SYNC(同步复位)。

class  Top extends Component {
    val io = new Bundle {
      val data = in Bits(8 bit)
      val a, b, c, d = out Bits(8 bit)
    }
    io.a  :=  RegNext(io.data) init 0
    io.b  :=  clockDomain.withBootReset()  on RegNext(io.data) init 0
    io.c  :=  clockDomain.withSyncReset()  on RegNext(io.data) init 0
    io.d  :=  clockDomain.withAsyncReset() on RegNext(io.data) init 0
}
SpinalVerilog(new Top)

复位时钟域

ResetArea 用于创建一个新的时钟域区域,其使用指定的复位信号与当前时钟域复位相结合进行复位·:

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 用于在当前时钟域中添加额外的时钟使能信号:

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)
  }
}