形式化验证

介绍

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,您有几种选择。您可以在以下位置获取预编译包:

或者你可以从头开始编译:

示例

外部断言

这是一个简单计数器和相应的形式化测试代码的示例。

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 有输入,您需要从测试代码中驱动它们。您可以使用所有常规硬件语句来执行此操作,但您也可以使用形式化验证中的 anyseqanyconstallseqallconst 语句:

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, changedstable 每次使用的地方均使用它。

pastValidAfterReset()

Bool

Similar to pastValid, where only difference is that this would take reset into account. Can be understood as pastValid & past(!reset).

请注意,您可以对past的返回值使用 init 语句:

when(past(enable) init(False)) { ... }

局限性

不支持非时钟驱动的断言。但它们在第三方形式化验证示例中有这样使用,似乎主要与代码风格相关。

命名策略

所有与形式验证相关的函数都返回 Area 或 Composite(首选),并命名为formalXXXX。 formalContext 可用于创建形式相关逻辑,还有可能是 formalAssertsformalAssumesformalCovers

对于组件

证明模式中需要的, 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”.