0
点赞
收藏
分享

微信扫一扫

spinal HDL - 05 - Spinal HDL - 函数和时钟域


写在前面

本文主要介绍了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

​clock​

定义域的时钟信号。

​reset​

复位信号。如果存在需要复位的寄存器而时钟域不提供,则会显示错误消息。

null

​softReset​

重置,推断额外的同步重置。

null

​clockEnable​

此信号的目标是禁用整个时钟域上的时钟,而无需在每个同步元件上手动实现

null

​frequency​

允许您指定给定时钟域的频率,然后在您的设计中读取它。

UnknownFrequency

​config​

指定信号的极性和复位的性质。

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

​clockEdge​

​RISING​​​, ​​FALLING​

​resetKind​

​ASYNC​​​, ​​SYNC​​​, and ​​BOOT​​ 一些 FPGA 支持(其中 FF 值由比特流加载

​resetActiveLevel​

​HIGH​​​, ​​LOW​

​softResetActiveLevel​

​HIGH​​​, ​​LOW​

​clockEnableActiveLevel​

​HIGH​​​, ​​LOW​

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

​name​

时钟和复位信号的名称

​config​

指定信号的极性和复位的性质

Current config

​withReset​

添加复位信号

true

​withSoftReset​

添加软复位信号

false

​withClockEnable​

添加时钟使能

false

​frequency​

时钟域的频率

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
}

特殊时钟区

慢区

A​​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)
}

重置区

A​​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)
}
}

时钟使能区

A​​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)
}
}

Reference

  1. spinal HDL官方文档


举报

相关推荐

0 条评论