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

简介: 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


例如,如果您使用validreadypayload信号定义一个简单的总线,那么您可以在其中定义一些有用的函数。

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]
)

这个定义需要五个参数:

image.png

在设计中定义特定时钟域的应用示例如下:

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类进行配置:

image.png

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]
)

这个定义有六个参数:

image.png

这种方法的优点是创建具有已知/指定名称而不是继承名称的时钟和复位信号。

创建后,您必须分配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实例具有以下可以调用的函数:

image.png

下面包含一个示例,其中 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


  1. spinal HDL官方文档
目录
相关文章
|
存储 文字识别 算法
文字识别OCR常见问题之图片超过40M不返回结果如何解决
文字识别OCR(Optical Character Recognition)技术能够将图片或者扫描件中的文字转换为电子文本。以下是阿里云OCR技术使用中的一些常见问题以及相应的解答。
750 2
|
传感器 IDE 开发工具
【FastBond2阶段1——基于ESP32C3开发的简易IO调试设备】
【FastBond2阶段1——基于ESP32C3开发的简易IO调试设备】
514 0
|
存储 缓存 测试技术
ZYNQ-AXI Interconnect IP介绍
ZYNQ-AXI Interconnect IP介绍
3376 0
ZYNQ-AXI Interconnect IP介绍
|
3月前
|
人工智能 自然语言处理 数据可视化
数字展厅升级重点推荐方案:AI数字人+智慧大屏实现低成本高互动
传统展厅升级痛点多?AI数字人+智慧大屏方案来了!无需更换硬件,低成本快速部署,支持智能问答、多语种讲解、后台一键更新内容。适配政务、企业、文旅等多场景,提升互动体验与运营效率,助力展厅迈向“可对话、会思考”的智慧新时代。
326 0
|
移动开发 算法 数据安全/隐私保护
基于FPGA的QPSK调制+软解调系统,包含testbench,高斯信道模块,误码率统计模块,可以设置不同SNR
本文介绍了基于FPGA的QPSK调制解调系统,通过Vivado 2019.2进行仿真,展示了在不同信噪比(SNR=1dB, 5dB, 10dB)下的仿真效果。与普通QPSK系统相比,该系统的软解调技术显著降低了误码率。文章还详细阐述了QPSK调制的基本原理、信号采样、判决、解调及软解调的实现过程,并提供了Verilog核心程序代码。
520 26
|
安全 API Android开发
Android打开USB调试命令
【6月更文挑战第20天】
848 1
|
C++ Windows
Qt 窗口置顶
Qt 窗口置顶
477 1
|
算法 数据可视化 Python
Python用MCMC马尔科夫链蒙特卡洛、拒绝抽样和Metropolis-Hastings采样算法
Python用MCMC马尔科夫链蒙特卡洛、拒绝抽样和Metropolis-Hastings采样算法
|
图形学 Python
pyqt的学习(三)----鼠标点击和按键处理
pyqt的学习(三)----鼠标点击和按键处理
457 1
|
存储 算法 计算机视觉
维特比解码(Viterbi Decoding
维特比解码(Viterbi Decoding)是一种用于解码卷积编码(Convolutional Coding)的算法,由 Andrew Viterbi 在 1968 年提出。卷积编码是一种前向纠错编码技术,用于提高数据传输的可靠性。在卷积编码中,数据被组织成一定大小的块,并用一个纠错码附加到数据块中。在接收端,维特比解码算法根据接收到的编码数据,通过比较不同可能的解码路径的权重,来找到最有可能的解码路径,从而实现对数据的解码。
909 4