Kika's
Blog
摄于 图片简介 | CC BY 4.0 | 换一张

Chisel的最后赋值生效原则

2026-02-23 11 views

如果要让我列出名为“让我感觉Chisel比verilog/sv好用的特性”的一个列表的话,“最后赋值生效”(Last Assignment Wins)这个赋值原则肯定位列其中。这个玩意非常之好用,但是也会出问题,今天发现自己因此写了一个隐秘的bug,故总结一下。

在 Chisel 的思维模式中,:=其实应该看作是一种“提议连接”(Proposing a connection),而非verilog中的赋值语义,提议连接的工作流程如下:

  1. 代码从上到下执行,不断提出新的连接方案。
  2. 没有任何when包裹的赋值是无条件提议。
  3. when包裹的赋值是带条件的提议。
  4. Chisel编译器在解析完当前作用域后,会收集所有对该信号的提议,并根据条件和出现的先后顺序(后面的优先级高),综合出一个多路选择器Mux网络。

下面具体结合代码讨论一下几种使用情况:

第一种情况下,在同一个作用域内有多个平行的:=,那么最终只有最后那一个:=会生效

val out = Wire(UInt(8.W))
out := 0.U 

when (condA) {
  out := 1.U  // 如果 condA 为真,这个赋值会被下面的覆盖
  out := 2.U  // 最终在 condA 为真时生效的赋值
}

第二种情况下,嵌套的 when 相当于更深层级的条件覆盖。内层的 when 会覆盖外层 when 的赋值(如果内层条件满足的话)。

val out = Wire(UInt(8.W))
out := 0.U 

when (condA) {
  out := 1.U
  when (condB) {
    out := 2.U // 当 condA 且 condB 都为真时,2.U 覆盖 1.U
  }
}

第三种情况,有多个平行的when,每个when内对同一个Wire进行提议连接:=。此时需要注意,即便condAcondB两个逻辑表达式并不互斥,第二个whencondB为真时,也会将第一个when的提议完全覆盖掉。

val ready = Wire(Bool())

// 1. 默认值
ready := true.B

// 2. 第一个平行的 when
when (condA) {
  ready := some_logic_1
}

// 3. 第二个平行的 when
when (condB) {
  ready := some_logic_2
}

其得到的verilog类似于:

assign ready = condB ? some_logic_2 : (condA ? some_logic_1 : 1);

而并不是可能错误理解的:

assign ready = (!condB || some_logic_2) && (!condA || some_logic_1);