spinal.core
组件
本文档描述了该语言的核心组件。它涵盖了大部分情况
核心语言组件如下:
*时钟域*,允许在设计中定义和操作多个时钟域
存储器实例化,允许自动实例化 RAM 和 ROM 存储器。
IP 实例化,使用现有的 VHDL 或 Verilog 组件实例化。
赋值
When / Switch
组件层次结构
Area
函数
实用函数
VHDL生成器
时钟域定义
在 Spinal中,时钟和复位信号可以组合起来创建**时钟域**。时钟域可以应用于设计的某些区域,该区域的所有实例化的同步元件将**隐式地**使用该时钟域。
时钟域像堆栈一样工作,这意味着,如果您的逻辑位于给定时钟域中,您仍然可以在其上应用另一个时钟域。
时钟域语法
定义时钟域的语法如下(使用EBNF语法):
ClockDomain(clock : Bool[,reset : Bool[,enable : Bool]]])
这个定义需要三个参数:
时钟域的时钟信号
可选的
reset
复位信号。如果需要重置的寄存器并且其时钟域没有提供重置,则会出现错误提示可选的
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
类进行配置:
属性 |
有效值 |
---|---|
|
|
|
|
|
|
|
|
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)
赋值
有多种赋值运算符:
Symbol |
描述 |
---|---|
:= |
标准赋值,相当于 VHDL/Verilog 中的“<=”
最后一次分配获胜,值在下一个delta周期更新
|
/= |
相当于 VHDL 中的 := 和 Verilog 中的 =(不常用)
值立即更新
|
<> |
2 个信号之间的自动连接。通过输入/输出设置推断信号方向
Similar behavioral than :=
|
// 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,大小是手动计算的 |
There are 2 cases where spinal automatically resize things :
Assignment |
问题 |
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
- 一切都按参考工作,这允许许多操作。For example you can give to a function an bus as argument, then the function can internally read/write it.您还可以返回一个组件、总线以及 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 中提供了更多工具和实用程序