赋值

有多个赋值运算符:

符号

描述

:=

标准赋值,相当于 VHDL/Verilog 中的``<=``。

\=

相当于 VHDL 中的 := 和 Verilog 中的 =。该值会立即就地更新。仅适用于组合信号,不适用于寄存器。

<>

2 个信号或相同类型的两个信号线束之间的自动连接。通过使用信号定义(输入/输出)来推断方向。 (与 :=类似的行为)

当多路复用时(例如使用 when,请参阅 When/Switch/Mux。),最后一个有效的赋值 := 为准。否则,向同一范围内的同一信号赋值两次会导致重叠错误。 SpinalHDL 默认情况下会假设这是一个无意的设计错误,并因错误而停止实例细化。对于特殊用例,可以根据具体情况以编程方式允许重叠赋值。 (参见 赋值覆盖(Assignment overlap))。

val a, b, c = UInt(4 bits)
a := 0
b := a
//a := 1 // this would cause an `assignment overlap` error,
         // if manually overridden the assignment would take assignment priority
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

它还支持线束赋值(将所有位信号转换为适当位宽的 Bits 类型的单位总线,然后在赋值表达式中使用更宽的形式)。在赋值表达式的左侧和右侧使用 () (Scala 元组语法)将多个信号捆绑在一起。

val a, b, c = UInt(4 bits)
val d       = UInt(12 bits)
val e       = Bits(10 bits)
val f       = SInt(2  bits)
val g       = Bits()

(a, b, c) := B(0, 12 bits)
(a, b, c) := d.asBits
(a, b, c) := (e, f).asBits           // both sides
g         := (a, b, c, e, f).asBits  // and on the right hand side

重要的是要理解,在 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

位宽检查

SpinalHDL 检查赋值左侧和右侧的位数是否匹配。有多种方法可以改变给定 BitVector (Bits, UInt, SInt)的位宽:

调整位宽的技术

描述

x := y.resized

将 y 改变位宽后的副本分配给 x,其位宽是从 x 推断出来的。

x := y.resize(newWidth)

为 x 赋值一个 y 变为 newWidth 位宽后的副本。

x := y.resizeLeft(newWidth)

对 x 赋值 y 变为 newWidth 位宽后的副本。如果需要,可在 LSB 处进行填充。

所有改变位宽方法都可能导致生成的位宽比 y 的原始位宽更宽或更窄。当发生加宽时,额外的位将用零填充。

x.resized 根据赋值表达式左侧的目标位宽推断转换方法,并遵循与 y.resize(someWidth) 相同的语义。表达式 x := y.resized 相当于 x := y.resize(x.getBitsWidth bits)

虽然示例代码片段显示了赋值语句的使用方法,但 resize 系列方法可以像任何普通 Scala 方法一样进行级联。

在一种情况下,Spinal 会自动调整位宽的大小:

// U(3) creates an UInt of 2 bits, which doesn't match the left side (8 bits)
myUIntOf_8bits := U(3)

因为 U(3) 是一个“弱”位计数推断信号,SpinalHDL 会自动加宽它。这可以被认为在功能上等同于 U(3, 2 bits).resized ,但是请放心,如果场景需要缩小范围,SpinalHDL 将做正确的事情并报告错误。当尝试赋值 myUIntOf_8bits``时,如果字面量需要 9 位(例如 ``U(0x100)),则会报告错误。

组合逻辑环(Combinatorial loops)

SpinalHDL 检查您的设计中是否存在组合逻辑环(锁存器)。如果检测到,会引发错误,并且 SpinalHDL 将打印造成循环的路径。

CombInit

CombInit 可用于复制信号及其当前的组合逻辑赋值。主要用例是能够稍后覆盖复制后信号,而不影响原始信号。

val a = UInt(8 bits)
a := 1

val b = a
when(sel) {
    b := 2
    // At this point, a and b are evaluated to 2: they reference the same signal
}

val c = UInt(8 bits)
c := 1

val d = CombInit(c)
// Here c and d are evaluated to 1
when(sel) {
    d := 2
    // At this point c === 1 and d === 2.
}

如果我们查看生成的 Verilog,会发现``b`` 不存在。由于它是引用的 a 的副本,因此这些变量指代相同的 Verilog 信号。

always @(*) begin
  a = 8'h01;
  if(sel) begin
    a = 8'h02;
  end
end

assign c = 8'h01;
always @(*) begin
  d = c;
  if(sel) begin
    d = 8'h02;
  end
end

CombInit 在辅助函数中特别有用,可确保返回值不引用输入。

// note that condition is an elaboration time constant
def invertedIf(b: Bits, condition: Boolean): Bits = if(condition) { ~b } else { CombInit(b) }

val a2 = invertedIf(a1, c)

when(sel) {
   a2 := 0
}

没有 CombInit 的话,如果 c == false(而不是 c == true),a1a2 会引用相同的信号,并且 a1 被赋值为零。有了 CombInit ,无论 c 的值是多少,我们都有一致的行为(CombInit创建新的信号)。