写在前面
本文主要介绍了spinal HDL语言的函数和时钟域。
函数(Function)
使用 Scala 函数生成硬件的方式与 VHDL/Verilog 完全不同,原因有很多:
- 例如,您可以将总线作为参数提供给函数,然后该函数可以在内部对其进行读/写。还可以从 Scala返回一个组件、一个总线或任何其他东西。
RGB转灰色
例如,如果您想通过使用系数将红/绿/蓝颜色转换为灰度,您可以使用函数来应用它们:
// Input RGB color val r, g, b = UInt(8 bits) // Define a function to multiply a UInt by a Scala Float value. def coef(value: UInt, by: Float): UInt = (value * U((255 * by).toInt, 8 bits) >> 8) // Calculate the gray level val gray = coef(r, 0.3f) + coef(g, 0.4f) + coef(b, 0.3f)
Valid Ready Payload bus
例如,如果您使用valid
、ready
和payload
信号定义一个简单的总线,那么您可以在其中定义一些有用的函数。
case class MyBus(payloadWidth: Int) extends Bundle with IMasterSlave { val valid = Bool() val ready = Bool() val payload = Bits(payloadWidth bits) // Define the direction of the data in a master mode override def asMaster(): Unit = { out(valid, payload) in(ready) } // Connect that to this def <<(that: MyBus): Unit = { this.valid := that.valid that.ready := this.ready this.payload := that.payload } // Connect this to the FIFO input, return the fifo output def queue(size: Int): MyBus = { val fifo = new MyBusFifo(payloadWidth, size) fifo.io.push << this return fifo.io.pop } } class MyBusFifo(payloadWidth: Int, depth: Int) extends Component { val io = new Bundle { val push = slave(MyBus(payloadWidth)) val pop = master(MyBus(payloadWidth)) } val mem = Mem(Bits(payloadWidth bits), depth) // ... }
时钟域(Clock domains)
在 SpinalHDL 中,可以组合时钟和复位信号来创建时钟域。时钟域可以应用于设计的某些区域,然后实例化到这些区域的所有同步元素将隐式使用该时钟域。
时钟域应用就像一个堆栈,这意味着如果您在给定的时钟域中,您仍然可以在本地应用另一个时钟域。
实例化
定义时钟域的语法如下(使用 EBNF 语法):
ClockDomain( clock: Bool [,reset: Bool] [,softReset: Bool] [,clockEnable: Bool] [,frequency: IClockDomainFrequency] [,config: ClockDomainConfig] )
这个定义需要五个参数:
在设计中定义特定时钟域的应用示例如下:
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)) }
配置
除了构造函数参数之外,每个时钟域的以下元素都可以通过一个ClockDomainConfig
类进行配置:
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
应用于整个设计。这个默认域的配置是:
- 时钟:上升沿
- 复位:异步,高电平有效
- 无时钟使能
这对应于以下内容ClockDomainConfig
:
val defaultCC = ClockDomainConfig( clockEdge = RISING, resetKind = ASYNC, resetActiveLevel = HIGH )
内部时钟
创建时钟域的另一种语法如下:
ClockDomain.internal( name: String, [config: ClockDomainConfig,] [withReset: Boolean,] [withSoftReset: Boolean,] [withClockEnable: Boolean,] [frequency: IClockDomainFrequency] )
这个定义有六个参数:
这种方法的优点是创建具有已知/指定名称而不是继承名称的时钟和复位信号。
创建后,您必须分配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 } }
外部时钟
可以定义一个时钟域,该域由源中任何地方的外部驱动。然后它会自动将时钟和复位线从顶级输入添加到所有同步元件。
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 } }
语境
可以通过ClockDomain.current
在任何地方调用来检索您所在的时钟域。
返回的ClockDomain
实例具有以下可以调用的函数:
下面包含一个示例,其中 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 个或多个由目标时钟域驱动的触发器来防止亚稳态。中提供的函数将实例化必要的触发器(触发器的数量取决于参数)以减轻这种现象。
BufferCC(input: T, init: T = null, bufferDepth: Int = 2)``spinal.lib._``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 }
特殊时钟区
慢区
ASlowArea
用于创建比当前慢的新时钟域区域:
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) }
重置区
AResetArea
用于创建一个新的时钟域区域,其中一个特殊的复位信号与当前时钟域复位相结合:
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) } }
时钟使能区
AClockEnableArea
用于在当前时钟域中添加额外的时钟使能:
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) } }
Reference
- spinal HDL官方文档