形式化验证
介绍
SpinalHDL 允许生成 SystemVerilog 断言 (SVA) 的子集。主要是断言(assert)、假设(assume)、覆盖(cover)和其他一些内容。
此外,它还提供了形式化验证后端,允许直接在开源 Symbi-Yosys 工具链中运行形式化验证。
形式化验证后端
您可以通过以下方式运行组件的形式化验证:
import spinal.core.formal._
FormalConfig.withBMC(15).doVerify(new Component {
// Toplevel to verify
})
目前支持3种模式:
withBMC(depth)
withProve(depth)
withCover(depth)
安装要求
要安装 Symbi-Yosys,您有几种选择。您可以在以下位置获取预编译包:
https://github.com/YosysHQ/fpga-toolchain/releases(EOL - 由 oss-cad-suite 取代)
或者你可以从头开始编译:
示例
外部断言
这是一个简单计数器和相应的形式化测试代码的示例。
import spinal.core._
// Here is our DUT
class LimitedCounter extends Component {
// The value register will always be between [2:10]
val value = Reg(UInt(4 bits)) init(2)
when(value < 10) {
value := value + 1
}
}
object LimitedCounterFormal extends App {
// import utilities to run the formal verification, but also some utilities to describe formal stuff
import spinal.core.formal._
// Here we run a formal verification which will explore the state space up to 15 cycles to find an assertion failure
FormalConfig.withBMC(15).doVerify(new Component {
// Instantiate our LimitedCounter DUT as a FormalDut, which ensure that all the outputs of the dut are:
// - directly and indirectly driven (no latch / no floating wire)
// - allows the current toplevel to read every signal across the hierarchy
val dut = FormalDut(new LimitedCounter())
// Ensure that the state space start with a proper reset
assumeInitial(ClockDomain.current.isResetActive)
// Check a few things
assert(dut.value >= 2)
assert(dut.value <= 10)
})
}
内部断言
如果您愿意,可以将形式化验证语句直接嵌入到 DUT 中:
class LimitedCounterEmbedded extends Component {
val value = Reg(UInt(4 bits)) init(2)
when(value < 10) {
value := value + 1
}
// That code block will not be in the SpinalVerilog netlist by default. (would need to enable SpinalConfig().includeFormal. ...
GenerationFlags.formal {
assert(value >= 2)
assert(value <= 10)
}
}
object LimitedCounterEmbeddedFormal extends App {
import spinal.core.formal._
FormalConfig.withBMC(15).doVerify(new Component {
val dut = FormalDut(new LimitedCounterEmbedded())
assumeInitial(ClockDomain.current.isResetActive)
})
}
外部激励
如果您的 DUT 有输入,您需要从测试代码中驱动它们。您可以使用所有常规硬件语句来执行此操作,但您也可以使用形式化验证中的 anyseq、anyconst、allseq、allconst 语句:
class LimitedCounterInc extends Component {
// Only increment the value when the inc input is set
val inc = in Bool()
val value = Reg(UInt(4 bits)) init(2)
when(inc && value < 10) {
value := value + 1
}
}
object LimitedCounterIncFormal extends App {
import spinal.core.formal._
FormalConfig.withBMC(15).doVerify(new Component {
val dut = FormalDut(new LimitedCounterInc())
assumeInitial(ClockDomain.current.isResetActive)
assert(dut.value >= 2)
assert(dut.value <= 10)
// Drive dut.inc with random values
anyseq(dut.inc)
})
}
更多关于断言/past(以前某个时钟内的状态)的例子
例如,我们可以检查该值是否在正向计数(如果尚未达到 10):
FormalConfig.withBMC(15).doVerify(new Component {
val dut = FormalDut(new LimitedCounter())
assumeInitial(ClockDomain.current.isResetActive)
// Check that the value is incrementing.
// hasPast is used to ensure that the past(dut.value) had at least one sampling out of reset
when(pastValid() && past(dut.value) =/= 10) {
assert(dut.value === past(dut.value) + 1)
}
})
假设内存中的内容
这是一个示例,我们希望防止值 1
出现在内存中:
class DutWithRam extends Component {
val ram = Mem.fill(4)(UInt(8 bits))
val write = slave(ram.writePort)
val read = slave(ram.readAsyncPort)
}
object FormalRam extends App {
import spinal.core.formal._
FormalConfig.withBMC(15).doVerify(new Component {
val dut = FormalDut(new DutWithRam())
assumeInitial(ClockDomain.current.isResetActive)
// assume that no word in the ram has the value 1
for(i <- 0 until dut.ram.wordCount) {
assumeInitial(dut.ram(i) =/= 1)
}
// Allow the write anything but value 1 in the ram
anyseq(dut.write)
clockDomain.withoutReset() { // As the memory write can occur during reset, we need to ensure the assume apply there too
assume(dut.write.data =/= 1)
}
// Check that no word in the ram is set to 1
anyseq(dut.read.address)
assert(dut.read.data =/= 1)
})
}
实用工具和原语
断言/时钟/复位
断言(assert)是一直被时钟驱动的,但在复位期间被禁用。这也适用于假设(assume)和覆盖(cover)。
如果您想在复位期间保持断言被检查,您可以执行以下操作:
ClockDomain.current.withoutReset() {
assert(wuff === 0)
}
指定信号的初始值
For instance, for the reset signal of the current clockdomain (useful at the top)
ClockDomain.current.readResetWire initial(False)
指定初始假设
assumeInitial(clockDomain.isResetActive)
内存内容(Mem)检查
如果您的设计中有 Mem,并且想要检查其内容,可以通过以下方式进行:
// Manual access
for(i <- 0 until dut.ram.wordCount) {
assumeInitial(dut.ram(i) =/= X) // No occurrence of the word X
}
assumeInitial(!dut.ram.formalContains(X)) // No occurrence of the word X
assumeInitial(dut.ram.formalCount(X) === 1) // only one occurrence of the word X
在复位的时候进行断言检查,可以这样做
ClockDomain.current.duringReset {
assume(rawrrr === 0)
assume(wuff === 3)
}
形式化验证的原语
语法
返回类型
描述
assert(Bool)
assume(Bool)
cover(Bool)
past(that : T, delay : Int)
past(that : T)
T
返回
delay
周期以前的 ``that``值 。 (默认1个周期)
rose(that : Bool)
Bool
当
that
从 False 变为 True 时返回 True
fell(that : Bool)
Bool
当
that
从 True 变为 False 时返回 True
changed(that : Bool)
Bool
当
that
当前值与上一个周期相比发生变化时返回 True
stable(that : Bool)
Bool
当
that
当前值与上一个周期相比没有改变时返回 True
initstate()
Bool
第一个周期时返回 True
pastValid()
Bool
当过去的值有效时返回 True(第一个周期为 False)。建议在
past
,rose
,fell
,changed
和stable
每次使用的地方均使用它。
pastValidAfterReset()
Bool
Similar to
pastValid
, where only difference is that this would take reset into account. Can be understood aspastValid & past(!reset)
.
请注意,您可以对past的返回值使用 init 语句:
when(past(enable) init(False)) { ... }
局限性
不支持非时钟驱动的断言。但它们在第三方形式化验证示例中有这样使用,似乎主要与代码风格相关。
命名策略
所有与形式验证相关的函数都返回 Area 或 Composite(首选),并命名为formalXXXX。 formalContext
可用于创建形式相关逻辑,还有可能是 formalAsserts
、 formalAssumes
和 formalCovers
。
对于组件
证明模式中需要的, Component
内部所需的最少断言可以命名为 formalAsserts
。
对于实现 IMasterSlave的接口
There could be functions in name formalAssertsMaster
, formalAssertsSlave
, formalAssumesMaster
, formalAssumesSlave
or formalCovers
.
Master/Slave are target interface type, so that formalAssertsMaster
can be understand as “formal verification assertions for master interface”.