spinal.core 组件

本文档描述了该语言的核心组件。它涵盖了大部分情况

核心语言组件如下:

  • *时钟域*,允许在设计中定义和操作多个时钟域

  • 存储器实例化,允许自动实例化 RAM 和 ROM 存储器。

  • IP 实例化,使用现有的 VHDL 或 Verilog 组件实例化。

  • 赋值

  • When / Switch

  • 组件层次结构

  • Area

  • 函数

  • 实用函数

  • VHDL生成器

时钟域定义

Spinal中,时钟和复位信号可以组合起来创建**时钟域**。时钟域可以应用于设计的某些区域,该区域的所有实例化的同步元件将**隐式地**使用该时钟域。

时钟域像堆栈一样工作,这意味着,如果您的逻辑位于给定时钟域中,您仍然可以在其上应用另一个时钟域。

时钟域语法

定义时钟域的语法如下(使用EBNF语法):

ClockDomain(clock : Bool[,reset : Bool[,enable : Bool]]])

这个定义需要三个参数:

  1. 时钟域的时钟信号

  2. 可选的 reset 复位信号。如果需要重置的寄存器并且其时钟域没有提供重置,则会出现错误提示

  3. 可选的 enable 使能信号。该信号的目标是禁用整个时钟域上的时钟,而无需在每个同步元件上手动实现。

在设计中定义具有指定属性时钟域的示例如下:

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 bits))
}

时钟配置

除了 此处 给出的构造函数参数之外,每个时钟域的以下元素都可以通过 ClockDomainConfig 类进行配置:

属性

有效值

clockEdge

RISING, FALLING

ResetKind

ASYNC, SYNC

resetActiveHigh

true, false

clockEnableActiveHigh

true, false

class CustomClockExample extends Component {
  val io = new Bundle {
    val clk = in Bool()
    val resetn = in Bool()
    val result = out UInt (4 bits)
  }
  val myClockDomainConfig = ClockDomainConfig(
    clockEdge = RISING,
    resetKind = ASYNC,
    resetActiveLevel = LOW
  )
  val myClockDomain = ClockDomain(io.clk,io.resetn,config = myClockDomainConfig)
  val myArea = new ClockingArea(myClockDomain){
    val myReg = Reg(UInt(4 bits)) init(7)
    myReg := myReg + 1

    io.result := myReg
  }
}

默认情况下,时钟域应用于整个设计。缺省的配置是:

  • clock :上升沿触发

  • reset:异步,高电平有效

  • 无使能信号

外部时钟

您可以在任何地方定义由外部驱动的时钟域。然后,它会自动将时钟和复位线从顶层输入添加到所有同步元件。

class ExternalClockExample extends Component {
  val io = new Bundle {
    val result = out UInt (4 bits)
  }
  val myClockDomain = ClockDomain.external("myClockName")
  val myArea = new ClockingArea(myClockDomain){
    val myReg = Reg(UInt(4 bits)) init(7)
    myReg := myReg + 1

    io.result := myReg
  }
}

跨时钟域设计

SpinalHDL 在编译时检查是否存在不需要的/未指定的跨时钟域信号访问。如果您想读取另一个 ClockDomain (时钟域)发出的信号,则应给目标信号增加 crossClockDomain 标记,如下例所示:

val asynchronousSignal = UInt(8 bits)
...
val buffer0 = Reg(UInt(8 bits)).addTag(crossClockDomain)
val buffer1 = Reg(UInt(8 bits))
buffer0 := asynchronousSignal
buffer1 := buffer0   // Second register stage to be avoid metastability issues
// Or in less lines:
val buffer0 = RegNext(asynchronousSignal).addTag(crossClockDomain)
val buffer1 = RegNext(buffer0)

赋值

有多种赋值运算符:

符号

描述

:=

标准赋值,相当于 VHDL/Verilog 中的“<=”
最后一次分配获胜,值在下一个delta周期更新

/=

相当于 VHDL 中的 := 和 Verilog 中的 =(不常用)
值立即更新

<>

2 个信号之间的自动连接。通过输入/输出设置推断信号方向
与 := 类似的行为
//Because of hardware concurrency is always read with the value '1' by b and c
val a,b,c = UInt(4 bits)
a := 0
b := a
a := 1  //a := 1 win
c := a

var x = UInt(4 bits)
val y,z = UInt(4 bits)
x := 0
y := x      //y read x with the value 0
x \= x + 1
z := x      //z read x with the value 1

SpinalHDL 检查左右分配端的位数是否匹配。有多种方法可以改变 BitVector (Bits、UInt、SInt)的位数:

改变位宽的方式

描述

x := y.resized

将 y 改变位宽后的副本赋值给 x,自动推断位宽以匹配 x

x := y.resize(newWidth)

将 y 改变位宽后的副本分配给 x,大小是手动计算的

有两种情况会导致spinal自动调整位宽:

赋值

问题

SpinalHDL行为

myUIntOf_8bit := U(3)

U(3) 创建一个 2 位的 UInt,与左侧不匹配

由于 U(3) 是“弱”位推断信号,SpinalHDL 自动调整其位宽

myUIntOf_8bit := U(2 -> False default -> true)

右侧部分推断出 3 位 UInt,与左侧部分不匹配

SpinalHDL 将默认值重新应用于丢失的位

When / Switch

与 VHDL 和 Verilog 一样,信号线和寄存器可以通过使用 when 和 switch 语法进行条件赋值

when(cond1){
  //execute when      cond1 is true
}.elsewhen(cond2){
  //execute when (not cond1) and cond2
}.otherwise{
  //execute when (not cond1) and (not cond2)
}

switch(x){
  is(value1){
    //execute when x === value1
  }
  is(value2){
    //execute when x === value2
  }
  default{
    //execute if none of precedent condition meet
  }
}

您还可以在when/switch 语句中定义新信号。如果您想计算中间值时,它很有用。

val toto,titi = UInt(4 bits)
val a,b = UInt(4 bits)

when(cond){
  val tmp = a + b
  toto := tmp
  titi := tmp + 1
} otherwise {
  toto := 0
  titi := 0
}

组件/层次结构

与 VHDL 和 Verilog 一样,您可以定义可用于构建设计层次结构的组件。但与它们不同的是,您不需要在实例化时绑定它们。

class AdderCell extends Component {
  //Declaring all in/out in an io Bundle is probably a good practice
  val io = new Bundle {
    val a, b, cin = in Bool()
    val sum, cout = out Bool()
  }
  //Do some logic
  io.sum := io.a ^ io.b ^ io.cin
  io.cout := (io.a & io.b) | (io.a & io.cin) | (io.b & io.cin)
}

class Adder(width: Int) extends Component {
  ...
  //Create 2 AdderCell
  val cell0 = new AdderCell
  val cell1 = new AdderCell
  cell1.io.cin := cell0.io.cout //Connect carrys
  ...
  val cellArray = Array.fill(width)(new AdderCell)
  ...
}

定义输入/输出的语法如下:

语法

描述

返回类型

in/out(x : Data)

设置 x 为输入/输出

x

in/out Bool()

创建输入/输出Bool值

Bool

in/out Bits/UInt/SInt(x bits)

创建相应类型的输入/输出端口

T

组件互连有一些规则:

  • 组件只能读取子组件的输出/输入信号值

  • 组件可以读取输出/输入端口值

  • 如果由于某种原因,您需要从层次结构中的远处读取信号时(调试、临时补丁),您可以使用 some.where.else.theSignal.pull() 返回的值来实现。

Area

有时,创建一个组件来定义某些逻辑是多余的并且过于冗长。对于这种情况,您可以使用 Area 逻辑区 :

class UartCtrl extends Component {
  ...
  val timer = new Area {
    val counter = Reg(UInt(8 bits))
    val tick = counter === 0
    counter := counter - 1
    when(tick) {
      counter := 100
    }
  }
  val tickCounter = new Area {
    val value = Reg(UInt(3 bits))
    val reset = False
    when(timer.tick) {          // Refer to the tick from timer area
      value := value + 1
    }
    when(reset) {
      value := 0
    }
  }
  val stateMachine = new Area {
    ...
  }
}

函数

使用 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 总线

例如,如果您定义一个简单的 Valid Ready Payload 总线,则可以在其中定义有用的函数。

class MyBus(payloadWidth:  Int) extends Bundle {
  val valid = Bool()
  val ready = Bool()
  val payload = Bits(payloadWidth bits)

  //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 Fifo(payloadWidth, size)
    fifo.io.push << this
    return fifo.io.pop
  }
}

VHDL生成

有一个小组件和一个生成相应 VHDL 的 main

// spinal.core contain all basics (Bool, UInt, Bundle, Reg, Component, ..)
import spinal.core._

//A simple component definition
class MyTopLevel extends Component {
  //Define some input/output. Bundle like a VHDL record or a verilog struct.
  val io = new Bundle {
    val a = in Bool()
    val b = in Bool()
    val c = out Bool()
  }

  //Define some asynchronous logic
  io.c := io.a & io.b
}

//This is the main of the project. It create a instance of MyTopLevel and
//call the SpinalHDL library to flush it into a VHDL file.
object MyMain {
  def main(args: Array[String]) {
    SpinalVhdl(new MyTopLevel)
  }
}

实例化 VHDL 和 Verilog IP

在某些情况下,将 VHDL 或 Verilog 组件实例化到 SpinalHDL 设计中可能会很有用。为此,您需要定义 BlackBox,它就像一个组件,但其内部实现应由单独的 VHDL/Verilog 文件提供给仿真/综合工具。

class Ram_1w_1r(_wordWidth: Int, _wordCount: Int) extends BlackBox {
  val generic = new Generic {
    val wordCount = _wordCount
    val wordWidth = _wordWidth
  }

  val io = new Bundle {
    val clk = in Bool()

    val wr = new Bundle {
      val en = in Bool()
      val addr = in UInt (log2Up(_wordCount) bits)
      val data = in Bits (_wordWidth bits)
    }
    val rd = new Bundle {
      val en = in Bool()
      val addr = in UInt (log2Up(_wordCount) bits)
      val data = out Bits (_wordWidth bits)
    }
  }

  mapClockDomain(clock=io.clk)
}

实用工具

SpinalHDL 核心包含一些实用工具:

语法

描述

返回类型

log2Up(x : BigInt)

返回表示 x 状态所需的位数

Int

isPow2(x : BigInt)

如果 x 是 2 的幂,则返回 true

Boolean

Spindle.lib 中提供了更多工具和实用程序