时钟域
简介
在 SpinalHDL 中,时钟和复位信号可以组合起来创建**时钟域**。时钟域可以应用于设计的某些区域,然后实例化到这些区域中的所有同步元件将**隐式**使用该时钟域。
时钟域的应用方式类似于堆栈,这意味着当您的设计位于给定时钟域中,您仍然可以将该设计应用到另一个时钟域。
请注意,寄存器在创建时捕获其时钟域,而不是在赋值时捕获。因此,请确保在所需的 ClockingArea 内创建它们。
实例化
定义时钟域的语法如下(使用EBNF语法):
ClockDomain(
  clock: Bool
  [,reset: Bool]
  [,softReset: Bool]
  [,clockEnable: Bool]
  [,frequency: IClockDomainFrequency]
  [,config: ClockDomainConfig]
)
这个定义有五个参数:
| 参数 | 描述 | 默认值 | 
|---|---|---|
| 
 | Clock signal that defines the domain. | |
| 
 | 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 | 
| 
 | Reset which infers an additional synchronous reset. | null | 
| 
 | 该信号的目标是禁用整个时钟域上的时钟,而无需在每个同步元件上手动实现 | null | 
| 
 | Allows you to specify the frequency of the given clock domain and later read it in your design. This parameter does not generate a PLL or more hardware to control the frequency. | UnknownFrequency | 
| 
 | Specify the polarity of signals and the nature of the reset. | 当前配置 | 
在设计中定义具有指定属性时钟域的示例如下:
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类进行配置:
| 属性 | 有效值 | 
|---|---|
| 
 | 
 | 
| 
 | 某些 FPGA 支持的  | 
| 
 | 
 | 
| 
 | 
 | 
| 
 | 
 | 
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]
)
该定义有六个参数:
| 参数 | 描述 | 默认值 | 
|---|---|---|
| 
 | clk 和 reset 信号的名称 | |
| 
 | 指定信号的极性和复位的性质 | 当前配置 | 
| 
 | 添加复位信号 | true | 
| 
 | 添加软复位信号 | false | 
| 
 | 添加时钟使能 | false | 
| 
 | 时钟域频率 | 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, syncReset 和 softReset。
Please be careful that clockEnable has a higher priority than syncReset. If you do a sync reset when the clockEnable is disabled (especially at the beginning of a simulation), the gated registers will not be reset.
这是一个例子:
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
If that behavior is problematic, one workaround is to use a when statement as a clock enable instead of using the ClockDomain.enable feature. This is open for future improvements.
语境
您可以通过在任何地方调用 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() could specify register’s resetKind as BOOT.
clockDomain.withSyncReset() could specify register’s resetKind as SYNC (sync-reset).
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)
  }
}