跨时钟域违例(Clock crossing violation)

一、简介

SpinalHDL会检查用户设计中的寄存器只会与相同时钟域的寄存器以组合逻辑方式连接。

二、例子

下述代码:

class TopLevel extends Component {
  val clkA = ClockDomain.external("clkA")
  val clkB = ClockDomain.external("clkB")

  val regA = clkA(Reg(UInt(8 bits)))   // PlayDev.scala:834
  val regB = clkB(Reg(UInt(8 bits)))   // PlayDev.scala:835

  val tmp = regA + regA                // PlayDev.scala:838
  regB := tmp
}

会报错:

CLOCK CROSSING VIOLATION from (toplevel/regA :  UInt[8 bits]) to (toplevel/regB :  UInt[8 bits]).
- Register declaration at
  ***
  Source file location of the toplevel/regA definition via the stack trace
  ***
- through
      >>> (toplevel/regA :  UInt[8 bits]) at ***(PlayDev.scala:834) >>>
      >>> (toplevel/tmp :  UInt[8 bits]) at ***(PlayDev.scala:838) >>>
      >>> (toplevel/regB :  UInt[8 bits]) at ***(PlayDev.scala:835) >>>

有多种修改方式如下:

  1. 跨时钟域标记(crossClockDomain tag)

    可以利用crossClockDomain来向SpinalHDL编译器传递“不用担心这种跨时钟赋值”的信息:

    class TopLevel extends Component {
        val clkA = ClockDomain.external("clkA")
        val clkB = ClockDomain.external("clkB")
    
        val regA = clkA(Reg(UInt(8 bits)))
        val regB = clkB(Reg(UInt(8 bits))).addTag(crossClockDomain)
    
    
        val tmp = regA + regA
        regB := tmp
    }
    
  2. setSyncronousWith

    用户还可以通过使用一个ClockDomain对象的setSynchronousWith方法指定两个时钟域一起同步。

    class TopLevel extends Component {
        val clkA = ClockDomain.external("clkA")
        val clkB = ClockDomain.external("clkB")
        clkB.setSyncronousWith(clkA)
    
        val regA = clkA(Reg(UInt(8 bits)))
        val regB = clkB(Reg(UInt(8 bits)))
    
    
        val tmp = regA + regA
        regB := tmp
    }
    
  3. BufferCC

    当交换单比特信号(如Bool类型), 或格雷码时, 可以使用BufferCC安全地跨时钟域。

    警告:不要对多比特信号使用BufferCC, 因为如果时钟是异步的, 接收端有损坏读取的风险。更多信息请参见“Clock Domain”章节。

    class AsyncFifo extends Component {
        val popToPushGray = Bits(ptrWidth bits)
        val pushToPopGray = Bits(ptrWidth bits)
    
        val pushCC = new ClockingArea(pushClock) {
            val pushPtr     = Counter(depth << 1)
            val pushPtrGray = RegNext(toGray(pushPtr.valueNext)) init(0)
            val popPtrGray  = BufferCC(popToPushGray, B(0, ptrWidth bits))
            val full        = isFull(pushPtrGray, popPtrGray)
            ...
        }
    
        val popCC = new ClockingArea(popClock) {
            val popPtr      = Counter(depth << 1)
            val popPtrGray  = RegNext(toGray(popPtr.valueNext)) init(0)
            val pushPtrGray = BufferCC(pushToPopGray, B(0, ptrWidth bits))
            val empty       = isEmpty(popPtrGray, pushPtrGray)
            ...
        }
    }