ASSIGNMENT OVERLAP 错误发生在信号被多次完全赋值且未显式允许覆盖的场景。这里分享我的排查经验:
一、核心触发条件
必须同时满足以下条件才会报错:
- 同一作用域内(相同 when/switch 分支或模块顶层)
- 两次及以上完整赋值(覆盖信号全部位宽)
- 未添加
allowAssignmentOverride
标签
- 非 poison 字面量赋值(非
??
或 ?
操作符)
二、典型错误场景
场景 1:直接重复赋值
val a = UInt(8 bits)
a := 0 // 位置 Line 10
a := 1 // 位置 Line 11 ← 触发错误
特征:连续使用 :=
对同一信号完整赋值
场景 2:无条件分支覆盖
when(condA) {
b := 0xAA
}.elsewhen(condB) {
b := 0xBB
}.otherwise {
b := 0xCC // 位置 Line 20
}
b := 0xDD // 位置 Line 22 ← 触发错误
原理:最后的赋值在 when 结构外,覆盖所有条件分支
场景 3:Switch 语句未完全覆盖
switch(state) {
is(s1) { c := 0xFF }
is(s2) { c := 0x0F }
}
// 无 default 分支 ← 若 state 可能为 s3/s4...
c := 0x00 // 位置 Line 30 ← 可能触发错误
特征:switch 未包含所有可能状态时,外部赋值会产生覆盖
场景 4:嵌套作用域泄露
def func() = {
d := 0x11 // 位置 Line 40
}
when(cond) {
func()
d := 0x22 // 位置 Line 43 ← 触发错误
}
原理:函数调用中的赋值与 when 分支中的赋值属于同一作用域
特殊场景:多个子类实例冲突
!!!哪怕报错是同一行赋值,一定要检查所有的 LHS 的 occurance
假设父类代码:
setup.issueQueueInput = queues.intIssueQueue.io.pop // 多实例共享同一队列
则创建两个子类会产生冲突。
方案:实例隔离连接
class AluExecutionUnit(val id: Int) extends... {
val aluSetup = new Area {
- setup.issueQueueInput = queues.intIssueQueue.io.pop
+ setup.issueQueueInput = queues.allocIssueQueue(id) // 专用队列
}
}
三、排查流程图
graph TD
A[发现ASSIGNMENT OVERLAP错误] --> B{检查错误位置}
B --> C[定位目标信号]
C --> D[查找信号所有赋值点]
D --> E{存在多个完整赋值?}
E -->|是| F[检查作用域是否相同]
E -->|否| G[检查位宽操作]
F --> H{有allowAssignmentOverride标签?}
H -->|无| I[确认是否允许覆盖]
H -->|有| J[标签使用错误]
四、解决方案
- 添加覆盖许可标签(慎用)
mySignal.addTag(allowAssignmentOverride)
- 重构代码结构:
// 错误示例
when(condX) { e := 1 }
when(condY) { e := 2 } // 可能重叠
// 正确修改
when(condX) {
e := 1
} otherwiseWhen(condY) {
e := 2
}
- 使用部分赋值:
// 原完整赋值
f := 0xFF
// 改为部分赋值
f(7 downto 4) := 0xF
f(3 downto 0) := 0xF
五、调试技巧
- 使用
getScalaLocationLong
查看完整信号定义位置
- 通过
bt.assignedBits.dump()
输出位宽覆盖状态
- 检查信号作用域链:
println(mySignal.rootScopeStatement.getPathName)
注意:该检查仅针对组合逻辑(isComb),时序逻辑的多次赋值不会触发此错误。
另外我已经给 SpinalHDL 提交了 PR,在目前 dev 分支已经合入,可以显示上次的赋值位置,解决此问题的难度大大降低。