背景
在使用 SpinalHDL 进行硬件描述时,开发者可能会遇到 OLD NETLIST RE-USED
相关的错误。这个错误通常在特定的代码组织方式下,尤其是在多次执行硬件生成(Elaboration)过程时出现。
报错信息
典型的报错信息如下:
[error] OLD NETLIST RE-USED : ??? : Bits[X bits]) is used to drive the spinal.core.internals.SwitchStatement@YYYYYYYY statement, but was defined in another netlist.
[error] Be sure you didn't defined a hardware constant as a 'val' in a global scala object.
[error] at path.to.your.Component.<init>(YourComponent.scala:LINE_A)
[error] at path.to.your.Generator$.$anonfun$main$N(YourGenerator.scala:LINE_B)
其中:
Bits[X bits]
指示了被重用的硬件常量类型及其位宽。
spinal.core.internals.SwitchStatement@YYYYYYYY
或其他类似 SpinalHDL 内部结构,指出了该常量被用于何种硬件逻辑。
YourComponent.scala:LINE_A
和 YourGenerator.scala:LINE_B
指出了错误发生的具体代码位置。
原因分析
SpinalHDL 在将 Scala 代码转换为硬件描述(如 Verilog)的过程中,会构建一个内部的硬件图(Netlist)。每一个硬件元素,例如一个 Bits
常量、一个寄存器或一个组合逻辑块,在这个图中都有其唯一的表示。
当使用 Scala 的 object
来定义硬件常量,并且这些常量通过 val
声明时,例如:
// 全局 Scala 对象
object MyConstants {
val MY_CONSTANT_A = B"0101" // B"..." 创建一个 Bits 对象
val MY_CONSTANT_B = U(7, 4 bits) // U(...) 创建一个 UInt 对象
}
class MyComponent extends Component {
val someLogic = Bits(4 bits)
switch(someLogic) {
is(MyConstants.MY_CONSTANT_A) { /* ... */ }
// ...
}
}
MyConstants.MY_CONSTANT_A
这个 Bits
对象在 MyConstants
这个 object
首次被 JVM 加载时创建,并且在整个 JVM 的生命周期内是同一个实例。
如果在一个 JVM 实例中多次执行 SpinalHDL 的硬件生成过程(例如,在测试套件中多次实例化并编译同一个组件,或者在 IDE/SBT 中反复运行生成脚本而没有重启 JVM),SpinalHDL 会尝试将同一个 MyConstants.MY_CONSTANT_A
实例用于构建新的硬件图。
在我的具体场景里:
object DecoderGen {
import spinal.core.sim._
def main(args: Array[String]) {
SpinalConfig(
defaultConfigForClockDomains = ClockDomainConfig(resetKind = ASYNC)
).generateVerilog(new Decoder())
// To test with a specific instruction:
SimConfig.withWave.compile(new Decoder).doSim { dut =>
...
}
}
这里 generateVerilog 和 compile 分别执行了一次生成。
SpinalHDL 的设计哲学不允许同一个硬件节点实例被用于多个独立的硬件图(Netlist)。这是因为每个硬件图都应该有其自己独立的节点集合。当检测到这种情况时,就会抛出 OLD NETLIST RE-USED
错误,提示开发者硬件常量可能在全局对象中被定义为 val
。
解决方案
解决此问题的核心在于确保每次 SpinalHDL 进行硬件生成时,所使用的硬件常量都是新创建的实例,而不是重用之前生成过程中已存在的实例。
将全局 object
中定义硬件常量的 val
修改为 def
:
// 全局 Scala 对象
object MyConstants {
def MY_CONSTANT_A = B"0101"
def MY_CONSTANT_B = U(7, 4 bits)
}
class MyComponent extends Component {
val someLogic = Bits(4 bits)
switch(someLogic) {
is(MyConstants.MY_CONSTANT_A) { /* ... */ } // 每次调用 MyConstants.MY_CONSTANT_A 都会创建一个新的 Bits 实例
// ...
}
}
解释:
- 当使用
def
定义时,MyConstants.MY_CONSTANT_A
不再是一个预先计算好的 Bits
实例。
- 相反,每次在代码中引用
MyConstants.MY_CONSTANT_A
时,def
后面的表达式 B"0101"
会被重新求值,从而创建一个全新的 Bits
对象实例。
- 这样,在每次 SpinalHDL 的硬件生成过程中,
switch
语句或其他逻辑中使用的都是一个专属于当前生成过程的新 Bits
实例,避免了对旧 Netlist 中节点的重用。
通过这种修改,可以有效避免 OLD NETLIST RE-USED
错误,确保硬件生成过程的正确性和独立性。