时钟域
简介
在 SpinalHDL 中,时钟和复位信号可以组合起来创建**时钟域**。时钟域可以应用于设计的某些区域,然后实例化到这些区域中的所有同步元件将**隐式**使用该时钟域。
时钟域的应用方式类似于堆栈,这意味着当您的设计位于给定时钟域中,您仍然可以将该设计应用到另一个时钟域。
请注意,寄存器在创建时捕获其时钟域,而不是在赋值时捕获。因此,请确保在所需的 ClockingArea
内创建它们。
实例化
定义时钟域的语法如下(使用EBNF语法):
ClockDomain(
clock: Bool
[,reset: Bool]
[,softReset: Bool]
[,clockEnable: Bool]
[,frequency: IClockDomainFrequency]
[,config: ClockDomainConfig]
)
这个定义有五个参数:
参数 |
描述 |
默认值 |
---|---|---|
|
定义时钟域中的时钟信号 |
|
|
复位信号。如果存在需要复位的寄存器,而时钟域没有提供复位,则会显示错误消息 |
null |
|
复位意味着额外的同步复位 |
null |
|
该信号的目标是禁用整个时钟域上的时钟,而无需在每个同步元件上手动实现 |
null |
|
允许您指定给定时钟域的频率,然后在您的设计中读取它。该参数不生成PLL或其他硬件来控制频率 |
UnknownFrequency |
|
指定信号的极性和复位的性质 |
当前配置 |
在设计中定义具有指定属性时钟域的示例如下:
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() 可以指定寄存器的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)
}
}