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官方文档
目录
相关文章
|
算法 测试技术 开发工具
m基于FPGA的2ASK调制解调系统verilog实现,包含testbench测试文件
m基于FPGA的2ASK调制解调系统verilog实现,包含testbench测试文件
157 0
|
存储 开发工具 异构计算
第三章 硬件描述语言verilog(二) 功能描述-组合逻辑(下)
第三章 硬件描述语言verilog(二) 功能描述-组合逻辑
965 0
第三章 硬件描述语言verilog(二) 功能描述-组合逻辑(下)
|
存储 算法 测试技术
m基于FPGA的8ASK调制解调系统verilog实现,包含testbench测试文件
m基于FPGA的8ASK调制解调系统verilog实现,包含testbench测试文件
125 0
|
算法 异构计算
m基于FPGA的4ASK调制解调系统verilog实现,包含testbench测试文件
m基于FPGA的4ASK调制解调系统verilog实现,包含testbench测试文件
129 0
|
存储 缓存 算法
m基于FPGA的交织解交织系统verilog实现,包含testbench
m基于FPGA的交织解交织系统verilog实现,包含testbench
316 0
|
算法 关系型数据库 MySQL
FPGA:Verilog HDL程序的基本结构
FPGA:Verilog HDL程序的基本结构
169 0
FPGA:Verilog HDL程序的基本结构
|
算法 异构计算
基于FPGA的直接序列扩频通信verilog设计,包括汉明编译码,扩频解扩,同步模块以及testbench
基于FPGA的直接序列扩频通信verilog设计,包括汉明编译码,扩频解扩,同步模块以及testbench
322 0
基于FPGA的直接序列扩频通信verilog设计,包括汉明编译码,扩频解扩,同步模块以及testbench
|
存储 算法 异构计算
基于Verilog HDL的状态机描述方法
⭐本专栏针对FPGA进行入门学习,从数电中常见的逻辑代数讲起,结合Verilog HDL语言学习与仿真,主要对组合逻辑电路与时序逻辑电路进行分析与设计,对状态机FSM进行剖析与建模。
168 0
基于Verilog HDL的状态机描述方法
|
算法 异构计算
Verilog HDL函数与任务的使用
⭐本专栏针对FPGA进行入门学习,从数电中常见的逻辑代数讲起,结合Verilog HDL语言学习与仿真,主要对组合逻辑电路与时序逻辑电路进行分析与设计,对状态机FSM进行剖析与建模。
125 0
Verilog HDL函数与任务的使用
|
算法 异构计算
Verilog HDL数据流建模与运算符
⭐本专栏针对FPGA进行入门学习,从数电中常见的逻辑代数讲起,结合Verilog HDL语言学习与仿真,主要对组合逻辑电路与时序逻辑电路进行分析与设计,对状态机FSM进行剖析与建模。
214 0
Verilog HDL数据流建模与运算符