写在前面
本文主要介绍了spinal HDL语言的函数和时钟域。
函数(Function)
使用 Scala 函数生成硬件的方式与 VHDL/Verilog 完全不同,原因有很多:
- 您可以实例化寄存器、组合逻辑和其中的组件。
- 您不必使用限制信号分配范围的
process
/@always
块。 - 一切都是通过引用传递的,这允许轻松操作。
例如,您可以将总线作为参数提供给函数,然后该函数可以在内部对其进行读/写。还可以从 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]
)
这个定义需要五个参数:
Argument | Description | Default |
| 定义域的时钟信号。 | |
| 复位信号。如果存在需要复位的寄存器而时钟域不提供,则会显示错误消息。 | null |
| 重置,推断额外的同步重置。 | null |
| 此信号的目标是禁用整个时钟域上的时钟,而无需在每个同步元件上手动实现 | null |
| 允许您指定给定时钟域的频率,然后在您的设计中读取它。 | UnknownFrequency |
| 指定信号的极性和复位的性质。 | Current 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 bit))
}
配置
除了构造函数参数之外,每个时钟域的以下元素都可以通过一个ClockDomainConfig
类进行配置:
Property | Valid values |
| |
| |
| |
| |
| |
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]
)
这个定义有六个参数:
Argument | Description | Default |
| 时钟和复位信号的名称 | |
| 指定信号的极性和复位的性质 | Current config |
| 添加复位信号 | 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
}
}
外部时钟
可以定义一个时钟域,该域由源中任何地方的外部驱动。然后它会自动将时钟和复位线从顶级输入添加到所有同步元件。
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
实例具有以下可以调用的函数:
name | Description | Return |
frequency.getValue | 返回时钟域的频率 | Double |
hasReset | 如果时钟域有复位信号则返回。 | Boolean |
hasSoftReset | 如果时钟域有软复位信号,则返回 | Boolean |
hasClockEnable | 如果时钟域有时钟使能信号,则返回 | Boolean |
readClockWire | 返回源自时钟信号的信号 | Bool |
readResetWire | 返回源自复位信号的信号 | Bool |
readSoftResetWire | 返回源自软复位信号的信号 | Bool |
readClockEnableWire | 返回源自时钟使能信号的信号 | Bool |
isResetActive | 当重置处于活动状态时返回 True | Bool |
isSoftResetActive | 当软复位处于活动状态时返回 True | Bool |
isClockEnableActive | 当时钟使能有效时返回真 | 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 个或多个由目标时钟域驱动的触发器来防止亚稳态。中提供的函数将实例化必要的触发器(触发器的数量取决于参数)以减轻这种现象。
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官方文档