spinal HDL - 08 - 赋值

本文详细介绍了SpinalHDL这种硬件描述语言中的赋值操作,包括标准赋值、即时赋值和连接赋值,并探讨了其并发性和信号类型(组合/时序)的本质。同时,文章讨论了宽度检查、组合循环检测、条件分配(When/Switch/Mux)以及位选择和位操作。此外,还解释了SpinalHDL中的并发规则,如最后有效赋值生效原则,以及信号和寄存器与Scala的交互方式。这些概念对于理解和编写高效、无 锁存 的硬件设计至关重要。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

赋值

赋值操作

有多个赋值操作符:

符号描述
:=标准赋值,相当于 < = 在 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.resizedx用 y 的调整大小的副本分配 x,将自动推断调整大小值以匹配 x
x := y.resize(newWidth)用 y 的调整大小的副本分配 x,大小是手动计算的

在一个案例中,Spinal 会自动调整一个值:

AssignmentProblemSpinalHDL 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语法ReturnDescription描述
Mux(cond, whenTrue, whenFalse)T当 cond 为 True 时返回 True,否则返回 false
cond ? whenTrue | whenFalseT当 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位的例子:

../../_images/MuxList.png

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

这将产生下列真相表:

xyresult
FalseFalse1
FalseTrue1
TrueFalse2
TrueTrue3

信号和寄存器与 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 中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FPGA and ICer

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值