赋值
赋值操作
有多个赋值操作符:
// 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,有两个等价的语法:
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 } }
这将产生下列真相表:
信号和寄存器与 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 中。