赋值
赋值操作
有多个赋值操作符:
符号 | 描述 |
---|---|
:= | 标准赋值,相当于 < = 在 VHDL/Verilog。对变量的最后一次赋值;直到下一次模拟增量循环时,值才会更新。 |
\= | 相当于: = 在 VHDL 中,= 在 Verilog,这个值会立即更新到位。 |
<> | 两个信号或同一类型的两个信号束之间的自动连接。方向是通过使用信号方向(进/出)来推断的。(类似于: =) |
// Because of hardware concurrency, `a` is always read as '1' by b and c
val a, b, c = UInt(4 bits)
a := 0
b := a
a := 1 // a := 1 "wins"
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
// Automatic connection between two UART interfaces.
uartCtrl.io.uart <> io.uart
在 SpinalHDL 中,信号(组合/顺序)的本质是在它的声明中定义的,而不是通过它被赋值的方式,理解这一点很重要。所有数据类型实例都将定义一个组合信号,而Reg (…)的数据类型实例将定义一个时序(寄存器)信号。
val a = UInt(4 bits) // Define a combinational signal
val b = Reg(UInt(4 bits)) // Define a registered signal
val c = Reg(UInt(4 bits)) init(0) // Define a registered signal which is set to 0 when a reset occurs
宽度检查
检查赋值的左侧和右侧的位计数是否匹配。有多种方法可以调整给定 BitVector 的宽度(Bits,UInt,SInt) :
调整大小的技巧 | 描述 |
---|---|
x := y.resized | x用 y 的调整大小的副本分配 x,将自动推断调整大小值以匹配 x |
x := y.resize(newWidth) | 用 y 的调整大小的副本分配 x,大小是手动计算的 |
在一个案例中,Spinal 会自动调整一个值:
Assignment | Problem | SpinalHDL action |
---|---|---|
myUIntOf_8bit := U(3) | 创建了一个2位的 UInt,它与左边(8位)不匹配 | 由于 u (3)是一个“弱”位计数推断信号,SpinalHDL 自动调整它的大小 |
组合循环
检查你的设计中是否没有组合循环(锁存)。如果检测到一个,就会产生一个错误,然后 SpinalHDL 将打印循环的路径。
When/Switch/Mux
When
正如在 VHDL 和 Verilog 中一样,信号可以在满足特定条件时有条件地分配:
when(cond1) {
// Execute when cond1 is true
}.elsewhen(cond2) {
// Execute when (not cond1) and cond2
}.otherwise {
// Execute when (not cond1) and (not cond2)
}
Switch
正如在 VHDL 和 Verilog 中一样,当信号有一个确定的值时,可以有条件地对信号进行分配:
switch(x) {
is(value1) {
// Execute when x === value1
}
is(value2) {
// Execute when x === value2
}
default {
// Execute if none of precedent conditions met
}
}
Local declaration
可以在 when/switch 语句中定义新信号:
val x, y = UInt(4 bits)
val a, b = UInt(4 bits)
when(cond) {
val tmp = a + b
x := tmp
y := tmp + 1
} otherwise {
x := 0
y := 0
}
检查定义在作用域内的信号只分配给作用域内的信号。
Mux
如果你只需要一个带 Bool 选择信号的 Mux,有两个等价的语法:
Syntax语法 | Return | Description描述 |
---|---|---|
Mux(cond, whenTrue, whenFalse) | T | 当 cond 为 True 时返回 True,否则返回 false |
cond ? whenTrue | whenFalse | T | 当 cond 为 True 时返回 True,否则返回 false |
val cond = Bool
val whenTrue, whenFalse = UInt(8 bits)
val muxOutput = Mux(cond, whenTrue, whenFalse)
val muxOutput2 = cond ? whenTrue | whenFalse
Bitwise selection
按位选择在语法上类似于 VHDL。
Example 例子
val bitwiseSelect = UInt(2 bits)
val bitwiseResult = bitwiseSelect.mux(
0 -> (io.src0 & io.src1),
1 -> (io.src0 | io.src1),
2 -> (io.src0 ^ io.src1),
default -> (io.src0)
)
另外,如果所有可能的值都包含在你的 mux 中,你可以省略默认值:
val bitwiseSelect = UInt(2 bits)
val bitwiseResult = bitwiseSelect.mux(
0 -> (io.src0 & io.src1),
1 -> (io.src0 | io.src1),
2 -> (io.src0 ^ io.src1),
3 -> (io.src0)
)
muxLists (…)是另一种以元组序列作为输入的按位选择。下面是一个将128位分成32位的例子:
val sel = UInt(2 bits)
val data = Bits(128 bits)
// Dividing a wide Bits type into smaller chunks, using a mux:
val dataWord = sel.muxList(for (index <- 0 until 4) yield (index, data(index*32+32-1 downto index*32)))
// A shorter way to do the same thing:
val dataWord = data.subdivideIn(32 bits)(sel)
规则
学习 SpinalHDL 背后的语义很重要,这样你才能理解幕后真正发生了什么,以及如何控制它。这些语义由多个规则定义:
-
信号和寄存器并发运行(并行行为,如 VHDL 和 Verilog)。
-
对一个组合信号的赋值就像表示一个总是正确的规则。
-
对寄存器的赋值就像表示一个应用于其时钟域的每个周期的规则。
-
对于每个信号,最后一个有效的赋值才有效。
-
在硬件开发过程中,每个信号和寄存器都可以以 OOP 方式作为对象进行操作。
并发性
分配每个组合或寄存器信号的顺序对行为没有影响。
例如,下面两段代码是等价的:
val a, b, c = UInt(8 bits) // Define 3 combinational signals
c := a + b // c will be set to 7
b := 2 // b will be set to 2
a := b + 3 // a will be set to 5
这相当于:
val a, b, c = UInt(8 bits) // Define 3 combinational signals
b := 2 // b will be set to 2
a := b + 3 // a will be set to 5
c := a + b // c will be set to 7
更一般地说,当您使用: = 赋值运算符时,就像为左侧信号/寄存器指定一个新规则。
Last valid assignment wins
如果一个组合信号或寄存器被分配多次,最后一个有效的赢。
举个例子:
val x, y = Bool() // Define two combinational signals
val result = UInt(8 bits) // Define a combinational signal
result := 1
when(x) {
result := 2
when(y) {
result := 3
}
}
这将产生下列真相表:
x | y | result |
---|---|---|
False | False | 1 |
False | True | 1 |
True | False | 2 |
True | True | 3 |
信号和寄存器与 Scala 的交互(OOP 引用 + 函数)
在 SpinalHDL 中,每个硬件元素都由一个类实例来建模。这意味着您可以通过使用实例的引用来操作它们,例如将它们作为参数传递给函数。例如,下面的代码实现了一个寄存器,当 inc 为 True 时递增,当 clear 为 True 时清除(clear 优先于 inc) :
val inc, clear = Bool() // Define two combinational signals/wires
val counter = Reg(UInt(8 bits)) // Define an 8 bit register
when(inc) {
counter := counter + 1
}
when(clear) {
counter := 0 // If inc and clear are True, then this assignment wins (Last valid assignment rule)
}
你可以通过混合前面的例子和赋值给 counter 的函数来实现完全相同的功能:
val inc, clear = Bool()
val counter = Reg(UInt(8 bits))
def setCounter(value : UInt): Unit = {
counter := value
}
when(inc) {
setCounter(counter + 1) // Set counter with counter + 1
}
when(clear) {
counter := 0
}
你也可以在函数中集成条件检查:
val inc, clear = Bool()
val counter = Reg(UInt(8 bits))
def setCounterWhen(cond : Bool,value : UInt): Unit = {
when(cond) {
counter := value
}
}
setCounterWhen(cond = inc, value = counter + 1)
setCounterWhen(cond = clear, value = 0)
并且指定应该为函数赋值的内容:
val inc, clear = Bool()
val counter = Reg(UInt(8 bits))
def setSomethingWhen(something : UInt, cond : Bool, value : UInt): Unit = {
when(cond) {
something := value
}
}
setSomethingWhen(something = counter, cond = inc, value = counter + 1)
setSomethingWhen(something = counter, cond = clear, value = 0)
前面所有的例子在 RTL 生成和 SpinalHDL 编译器的角度上都是严格等价的。这是因为SpinalHDL 只关心 Scala 运行时和在那里实例化的对象,而不关心 Scala 语法本身。换句话说,从生成的 RTL 生成/SpinalHDL 透视图来看,当您在 Scala 中使用生成硬件的函数时,就像函数是内联的一样。对于 Scala 循环也是如此,因为它们将以展开的形式出现在生成的 RTL 中。