Skip to content
This repository has been archived by the owner on Aug 19, 2024. It is now read-only.

Commit

Permalink
ChiselEnum: implement peek and better error message (#542)
Browse files Browse the repository at this point in the history
  • Loading branch information
ekiwi committed Aug 3, 2022
1 parent 1fad53f commit f3b356b
Show file tree
Hide file tree
Showing 14 changed files with 441 additions and 115 deletions.
37 changes: 22 additions & 15 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -132,40 +132,47 @@ jobs:

formal:
name: formal verification tests
runs-on: ${{ matrix.os }}
runs-on: ubuntu-20.04
strategy:
matrix:
os: [ubuntu-20.04, macos-latest]
backend: [z3, cvc4, btormc]
backend: [z3, cvc4, btormc, yices2, boolector, bitwuzla]
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Install Z3 and CVC4 for Ubuntu
- name: Install Z3 and CVC4
if: runner.os == 'Linux'
run: |
sudo apt-get install -y z3 cvc4
z3 --version
cvc4 --version
- name: Install Z3 and CVC4 for MacOS
if: runner.os == 'macOS'
run: |
brew install z3
brew tap cvc4/cvc4
brew install cvc4/cvc4/cvc4
z3 --version
cvc4 --version
- name: Install Tabby OSS Cad Suite (from YosysHQ)
if: matrix.backend == 'btormc'
uses: YosysHQ/setup-oss-cad-suite@v1
with:
osscadsuite-version: '2021-11-09'
osscadsuite-version: '2022-08-02'
- name: Setup Scala
uses: olafurpg/setup-scala@v10
with:
java-version: [email protected]
- name: Test
run: sbt "testOnly -- -n Formal -Dformal_engine=${{ matrix.backend }}"

formal-mac:
name: formal verification tests on mac
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Install Z3 for MacOS
run: |
brew install z3
z3 --version
- name: Setup Scala
uses: olafurpg/setup-scala@v10
with:
java-version: [email protected]
- name: Test
run: sbt "testOnly -- -n Formal -Dformal_engine=z3"

doc:
name: Documentation and Formatting
runs-on: ubuntu-20.04
Expand All @@ -186,7 +193,7 @@ jobs:
# When adding new jobs, please add them to `needs` below
all_tests_passed:
name: "all tests passed"
needs: [test, doc, verilator, verilator-mac, formal, icarus, test-mac]
needs: [test, doc, verilator, verilator-mac, formal, formal-mac, icarus, test-mac]
runs-on: ubuntu-latest
steps:
- run: echo Success!
Expand Down
21 changes: 21 additions & 0 deletions src/main/scala/chisel3/internaltest/EnumHelpers.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: Apache-2.0

package chisel3.internaltest

import chisel3.experimental.EnumType

/** Helper functions to allow for peeks and better debugging of ChiselEnums.
* This needs to be in a `chisel3` package in order to access the package private
* `factory` field of EnumType.
*/
object EnumHelpers {
def fromBits[T <: EnumType](tpe: T, bits: BigInt): T = {
val all = tpe.factory.all
all.find(_.litValue == bits).get.asInstanceOf[T]
}
def valueToName(tpe: EnumType, bits: BigInt): Option[String] = {
val name = tpe.factory.nameOfValue(bits)
val tpeName = tpe.factory.enumTypeName
name.map(n => s"$tpeName.$n")
}
}
24 changes: 21 additions & 3 deletions src/main/scala/chiseltest/formal/backends/Maltese.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,21 @@ case object Z3EngineAnnotation extends FormalEngineAnnotation
*/
case object BtormcEngineAnnotation extends FormalEngineAnnotation

/** Use a SMTLib based model checker with the yices2 SMT solver.
* @note yices2 often performs better than Z3 or CVC4.
*/
case object Yices2EngineAnnotation extends FormalEngineAnnotation

/** Use a SMTLib based model checker with the boolector SMT solver.
* @note boolector often performs better than Z3 or CVC4.
*/
case object BoolectorEngineAnnotation extends FormalEngineAnnotation

/** Use a SMTLib based model checker with the bitwuzla SMT solver.
* @note bitwuzla often performs better than Z3 or CVC4.
*/
case object BitwuzlaEngineAnnotation extends FormalEngineAnnotation

/** Formal Verification based on the firrtl compiler's SMT backend and the maltese SMT libraries solver bindings. */
private[chiseltest] object Maltese {
def bmc(circuit: ir.Circuit, annos: AnnotationSeq, kMax: Int, resetLength: Int = 0): Unit = {
Expand Down Expand Up @@ -138,9 +153,12 @@ private[chiseltest] object Maltese {
val engines = annos.collect { case a: FormalEngineAnnotation => a }
assert(engines.nonEmpty, "You need to provide at least one formal engine annotation!")
engines.map {
case CVC4EngineAnnotation => new SMTModelChecker(CVC4SMTLib)
case Z3EngineAnnotation => new SMTModelChecker(Z3SMTLib)
case BtormcEngineAnnotation => new BtormcModelChecker(targetDir)
case CVC4EngineAnnotation => new SMTModelChecker(CVC4SMTLib)
case Z3EngineAnnotation => new SMTModelChecker(Z3SMTLib)
case BtormcEngineAnnotation => new BtormcModelChecker(targetDir)
case Yices2EngineAnnotation => new SMTModelChecker(Yices2SMTLib)
case BoolectorEngineAnnotation => new SMTModelChecker(BoolectorSMTLib)
case BitwuzlaEngineAnnotation => new SMTModelChecker(BitwuzlaSMTLib)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// SPDX-License-Identifier: Apache-2.0
// Author: Kevin Laeufer <[email protected]>

package chiseltest.formal.backends.smt

import firrtl.backends.experimental.smt._
import scala.collection.mutable

class CompactSmtEncoding(sys: TransitionSystem) extends TransitionSystemSmtEncoding {
import SMTTransitionSystemEncoder._
private def id(s: String): String = SMTLibSerializer.escapeIdentifier(s)
private val stateType = id(sys.name + "_s")
private val stateInitFun = id(sys.name + "_i")
private val stateTransitionFun = id(sys.name + "_t")

private val states = mutable.ArrayBuffer[UTSymbol]()

def defineHeader(ctx: SolverContext): Unit = encode(sys).foreach(ctx.runCommand)

private def appendState(ctx: SolverContext): UTSymbol = {
val s = UTSymbol(s"s${states.length}", stateType)
ctx.runCommand(DeclareUninterpretedSymbol(s.name, s.tpe))
states.append(s)
s
}

def init(ctx: SolverContext): Unit = {
assert(states.isEmpty)
val s0 = appendState(ctx)
ctx.assert(BVFunctionCall(stateInitFun, List(s0), 1))
}

def unroll(ctx: SolverContext): Unit = {
assert(states.nonEmpty)
appendState(ctx)
val tStates = states.takeRight(2).toList
ctx.assert(BVFunctionCall(stateTransitionFun, tStates, 1))
}

/** returns an expression representing the constraint in the current state */
def getConstraint(name: String): BVExpr = {
assert(states.nonEmpty)
val foo = id(name + "_f")
BVFunctionCall(foo, List(states.last), 1)
}

/** returns an expression representing the assertion in the current state */
def getAssertion(name: String): BVExpr = {
assert(states.nonEmpty)
val foo = id(name + "_f")
BVFunctionCall(foo, List(states.last), 1)
}

def getSignalAt(sym: BVSymbol, k: Int): BVExpr = {
assert(states.length > k, s"no state s$k")
val state = states(k)
val foo = id(sym.name + "_f")
BVFunctionCall(foo, List(state), sym.width)
}

def getSignalAt(sym: ArraySymbol, k: Int): ArrayExpr = {
assert(states.length > k, s"no state s$k")
val state = states(k)
val foo = id(sym.name + "_f")
ArrayFunctionCall(foo, List(state), sym.indexWidth, sym.dataWidth)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,33 +72,56 @@ private object SMTLibResponseParser {
}
}

sealed trait SExpr
sealed trait SExpr {
def isEmpty: Boolean
}
case class SExprNode(children: List[SExpr]) extends SExpr {
override def toString = children.mkString("(", " ", ")")
override def isEmpty: Boolean = children.isEmpty || children.forall(_.isEmpty)
}
case class SExprLeaf(value: String) extends SExpr {
override def toString = value
override def isEmpty: Boolean = value.trim.isEmpty
}

/** simple S-Expression parser to make sense of SMTLib solver output */
object SExprParser {
def parse(line: String): SExpr = {
val tokens = line
.replace("(", " ( ")
.replace(")", " ) ")
.split("\\s+")
.filterNot(_.isEmpty)
.toList
val tokens = tokenize(line)

assert(tokens.nonEmpty)
if (tokens.head == "(") {
if (tokens.isEmpty) {
SExprLeaf("")
} else if (tokens.head == "(") {
parseSExpr(tokens.tail)._1
} else {
assert(tokens.tail.isEmpty)
assert(tokens.tail.isEmpty, s"multiple tokens, but not starting with a `(`:\n${tokens.mkString(" ")}")
SExprLeaf(tokens.head)
}
}

// tokenization with | as escape character
private def tokenize(line: String): List[String] = {
var tokens: List[String] = List()
var inEscape = false
var tmp = ""
def finish(): Unit = {
if (tmp.nonEmpty) {
tokens = tokens :+ tmp
tmp = ""
}
}
line.foreach {
case '(' => finish(); tokens = tokens :+ "("
case ')' => finish(); tokens = tokens :+ ")"
case '|' if inEscape => tmp += '|'; finish(); inEscape = false
case '|' if !inEscape => finish(); inEscape = true; tmp = "|"
case ' ' | '\t' | '\r' | '\n' if !inEscape => finish()
case other => tmp += other
}
finish()
tokens
}

private def parseSExpr(tokens: List[String]): (SExpr, List[String]) = {
var t = tokens
var elements = List[SExpr]()
Expand Down
Loading

0 comments on commit f3b356b

Please sign in to comment.