Giter VIP home page Giter VIP logo

chiselverify's Introduction

ChiselVerify: A Hardware Verification Library for Chisel

In this repository, we proprose ChiselVerify, which is the beginning of a verification library within Scala for digital hardware described in Chisel, but also supporting legacy components in VHDL, Verilog, or SystemVerilog. The library runs off of ChiselTest for all of the DUT interfacing. An early technical report describing the initial version of the library in detail is available online: Open-Source Verification with Chisel and Scala. For the most up-to-date writing on topic, please refer to Verification of Chisel Hardware Designs with ChiselVerify.

When you use this library in a research project, please cite it as:

@article{dobis2023verification,
  title={{Verification of Chisel Hardware Designs with ChiselVerify}},
  author={Dobis, Amelia and Laeufer, Kevin and Damsgaard, Hans Jakob and Petersen, Tjark and Rasmussen, Kasper Juul Hesse and Tolotto, Enrico and Andersen, Simon Thye and Lin, Richard and Schoeberl, Martin},
  journal={Microprocessors and Microsystems},
  pages={104737},
  year={2023},
  publisher={Elsevier}}

ChiselVerify is published on Maven. To use it, add following line to your build.sbt:

libraryDependencies += "io.github.chiselverify" % "chiselverify" % "0.3.0"

Run tests with

make

This README contains a brief overview of the library and its functionalities. For a more in-depth tutorial, please check-out the ChiselVerify Wiki. Other general documentation, such as technical reports, journal, and conference papers related to the chiselverify project can be found in the documentation repository.


Verification Library for Chisel

The library can be divided into 4 main parts:

  1. Functional Coverage: Enabling Functional Coverage features like Cover Points, Cross Coverage, Timed Coverage and Conditional Coverage.
  2. Constrained Random Verification: Allowing for constraints and random variables to be defined and used directly in Scala.
  3. Bus Functional Models: Enabling Transactional modeling for standardized Buses like AXI4.
  4. Approximate Design Verification: Provides comparative port samplers, as well as several error metrics that simplify the verification of approximate deisgns.

THE API PRESENTED IN THIS README MIGHT BE OUT-DATED, AS IT IS ONLY UPDATED OCCASIONALLY. THE MAIN SOURCE OF INFORMATION THAT SHOULD BE USED TO LEARN HOW TO USE THE LIBRARY IS THE WIKI.

Functional Coverage in Chisel

The idea is to implement functional coverage features directly in Chisel. The structure of the system can be seen in the diagram below.

Structure of the Coverage system

Coverage Reporter

This is the heart of the system. It handles everything from registering the Cover Points to managing the Coverage DataBase. It will also generate the final coverage report. Registering Cover Points together will group them into a same Cover Group.

Coverage DataBase

This DataBase handles the maintenance of the values that were sampled for each of the Cover Point bins. This allows us to know how much of the verification plan was tested. The DataBase also keeps mappings linking Cover Groups to their contained Cover Points.

How to use it

The Functional coverage system is compatible with the ChiselTest framework.

  1. The CoverageReporter must first be instanciated within a test.
  2. CoverGroups can then be created by using the register method of the coverage reporter. This takes as parameter a List[Cover]. Cover represents either a CoverPoint or a CoverCondition that contains a port that will be sampled, a portName that will be shown in the report and either a List[Bins], created from a name and a scala range, or a List[Condition], created from a name and an arbitrary condition function.
  3. CoverGroups may also contain a List[Cross] which represents a set of hit relations between two ports.
  4. The port must then be manually sampled by calling the sample method of the coverage reporter.
  5. Once the test is done, a coverage report can be generated by calling the printReport or report methods of the coverage reporter.

An example of this can be found here.

Timed Cross Coverage

Idea: We want to check the relationship between two ports with a delay of a certain amount of cycles. Example: We have the following situation, imagine we have a device that breaks if:

  • dut.io.a takes the value of 1.U at cycle 1
  • dut.io.b takes the value of 1.U the following cycle

We want to verify that the above case was tested. This can be done by defining a TimedCross between the two points:

val cr = new CoverageReporter(dut)
cr.register(
  // Declare CoverPoints
  cover("a", dut.io.a)(DefaultBin(dut.io.a)),
  cover("b", dut.io.b)(DefaultBin(dut.io.b)),
  // Declare timed cross point with a delay of 1 cycle
  cover("timedAB", dut.io.a, dut.io.b)(Exactly(1))(
    cross("both1", Seq(1 to 1, 1 to 1))
  )
)

Using that, we can check that we tested the above case in our test suite. This construct can be used to check delay between two cover points.

Use

To be able to use the timed coverage, stepping the clock must be done through the coverage reporter:

dut.clock.step(nCycles) // Will trigger an exception if used with Timed Cross Coverage
cr.step(nCycles)        // This works and updates the DataBase

This is done in order ensure that the coverage DataBase will always remain synchronized with the DUT's internal clock.

Delay Types

The current implementation allows for the following special types of timing:

  • Eventually: This sees if a cross hit was detected at any point in the next given amount of cycles.
  • Always: This only considers a hit if the it was detected every cycle in the next given amount of cycles.
  • Exactly: This only considers a hit if it was detected exactly after a given amount of cycles.

Timed Assertions

Delay types can also be used in order to used Timed Assertions or Timed Expect. These can be used in order to check an assertion, in the form of an arbitrary function, with an added timing argument. We could thus check, for example, that two ports are equal two cycles appart. For example:

AssertTimed(dut, dut.io.a.peek() === dut.io.b.peek(), "aEqb expected timing is wrong")(Exactly(2)).join()

This can also be done more naturally with the Expect interface:

ExpectTimed(dut,dut.io.a, dut.io.b.peek().litValue(), "aEqb expected timing is wrong")(Exactly(2)).join()

These can also be used with a simplyfied syntax, inspired by ScalaTest syntax:

// For Timed Assertions
eventually(2, "aEqb expected timing is wrong") { dut.io.a.peek() === dut.io.b.peek() }
exact(2, "aEqb expected timing is wrong") { dut.io.a.peek() === dut.io.b.peek() }
always(2, "aEqb expected timing is wrong") { dut.io.a.peek() === dut.io.b.peek() }
never(2, "aEqb expected timing is wrong") { dut.io.a.peek() === dut.io.b.peek() }

Example use case

Here is a toy example of how to use the assertion:

always(9, "a isn't always less than or equal to one") { LtEq(dut.io.outB, dut.io.outA) }
always(9, "a isn't always greater than or equal to one") { GtEq(dut.io.outB, dut.io.outA) }

Cover Conditions

Idea: A type of coverpoint that can apply arbitrary hit conditions to an arbitrary number of ports.

cover(readableName: String, ports: Data*)(conditions: Condition*)
// where a condition is declared using the bin function without a range
bin(name: String, func : Seq[BigInt] => Boolean)

Example:

val cr = new CoverageReporter(dut)
cr.register(
  // Declare CoverPoints
  cover("aAndB", dut.io.outA, dut.io.outB)(
    bin("aeqb", { case Seq(a, b) => a == b })
  )
)

Bins are thus defined using arbitrary functions of the type List[BigInt] => Boolean which represent different hit conditions. No coverage percentage is given due to cartesian product complexity. Instead, we offer the possibility to use a user-defined "expected number of hits" to get a coverage percentage. This looks like the following:

val cr = new CoverageReporter(dut)
cr.register(
  // Declare CoverPoints
  cover("aAndB", dut.io.outA, dut.io.outB)(
    bin("asuptobAtLeast100Times", condition = { case Seq(a, b) => a > b }, expectedHits = 100)
  )
)

The above example results in the following coverage report:

============ COVERAGE REPORT ============
============== GROUP ID: 1 ==============
COVER_CONDITION NAME: aAndB
CONDITION aeqb HAS 4 HITS
CONDITION asuptobAtLeast100 HAS 95 HITS EXPECTED 100 = 95.0%
=========================================
=========================================

Constrained Random Verification (CRV)

The CRV package inside this project aims to mimic the functionality of SystemVerilog constraint programming and integrates them into ChiselTest. It combines a Constraint Satisfactory Problem (CSP) solver with some helper classes to create and use random objects inside your tests. Currently, only the jacop backend is supported, but in the future other backends can be added.

Comparison

The following is a comparison of how to describe a randomizable packet with SystemVerilog and our proposed framework.

SystemVerilog

class frame_t;
rand pkt_type ptype;
rand integer len;
randc bit [1:0] no_repeat;
// Constrain the members
constraint legal {
  len >= 2;
  len <= 5;
}

CRV w/ jacop backend

class Frame extends RandObj(new Model) {
  val pkType: RandVar = rand(0, 3)
  val len: RandVar = rand(0, 10)
  val noRepeat: RandVar = rand(0, 1, Cyclic)

  val legal = new ConstraintGroup {
    len >= 2
    len <= 5
  }
}

Random Objects

Random objects can be created by extending the RandObj trait. This class accepts one parameter which is a Model. A model correspond to a database in which all the random variables and constraints declared inside the RandObj are stored.

class Frame extends RandObj(new Model)

A model can be initialized with a seed new Model(42), which allows the user to create reproducible tests.

Random Fields

Random fields are defined using the following function:

def rand(min: Int, max: Int, randType: RandType = Normal)(implicit model: Model)

A random field can be added to a RandObj by declaring a Rand variable.

  val len = rand(0, 10)

Random-cyclic variable can be added by declaring a Randc field inside a RandObj. This is done using the Cyclic RandType parameter.

  val noRepeat = rand(0, 1, Cyclic)

Constraints

Each variable can have one or multiple constraints. These are defined using constraint operators.

len >= 2

In the previous block of code we are specifying that the variable len can only take values that are grater then 2. Each constraint can be assigned to a variable and enabled or disabled at any time during the test.

val lenConstraint = len > 2
[....]
lenConstraint.disable()
[....]
lenConstraint.enable()

Constraints can also be grouped together in a ConstraintGroup and the group itself can be enabled or disabled.

val legal = new ConstraintGroup {
  len >= 2
  len <= 5
  payload.size == len
}
[...]
legal.disable()
[...]
legal.enable()

By default, constraints and constraint groups are enabled when they are declared.

The list of operator used to construct constraints is the following: <, <=, >, >=,==, div, *, mod, +, -, \=, ^, in, inside.

It is also possible to declare conditional constraints with constructors like IfCon and IfElseCon.

val constraint1 = IfCon(len == 1) {
    payload.size == 3
  } ElseC {
    payload.size == 10
  }

Usage

As in SystemVerilog, each random class exposes a method called randomize() to solve the constraint problem specified in the class and assign to each random field a random value. The method returns true only if the CSP found a set of values that satisfy the current constraints.

val myPacket = new Frame(new Model)
assert(myPacket.randomize)

Other usage examples can be found in our backend tests.

Verification of Approximate Designs

The approximation package within this library serves to simplify the process of verifying that approximate hardware designs satisfy commonly applied error metrics relative to exact counterparts.

Metric

Error metrics contained within the library follow a hierarchy of two different characteristics that describe the values they return and which kind of inputs they take. Specifically, Instantaneous metrics may be computed on a single sample from two ports, whereas HistoryBased metrics require sequences of samples. Absolute metrics return non-normalized results, while Relative metrics return normalized or somehow relativized results. Inheriting (case) classes must extend one of Instantaneous or HistoryBased and mixin either Absolute or Relative.

Watcher

These elements implement distributed sampling and storage for keeping track of a pair of an approximate port and its related exact port (or an exact reference value) and any number of metrics to track for them. While the main class is abstract and not meant to be used, these elements come in two types:

  • Trackers that can simply sample port values and report on given metrics but lack verification features, thus ignoring any maximum values for the metrics provided.
  • Constraints that extend upon the functionality of the Tracker by also allowing for verification, requiring that all provided metrics have maximum values.

In practice, these two are created using the simple API end-points track and constrain. They come in two variants: reference-based and port-based. The former are to be used with software models and require passing in expected values when sampled. Contrarily, the latter are to be used with combined DUTs that provide both approximate and exact outputs, thus not requiring but nonetheless accepting reference values when sampled.

Error Reporter

This is the core of the system. It handles any number of Trackers and/or Constraints, sampling of their related ports, reporting on their results, and verifying that their constraints are met.

How to use it

The approximate verification system is closely modeled after our functional coverage tools and remains fully compatible with the ChiselTest framework. Tests may be written in one of two ways depending on whether the exact DUT is implemented as a Chisel Module or as a software model.

  1. As ChiselTest only allows for testing one Chisel module at a time, it is necessary to first define a top-level module that contains both the approximate and exact DUTs, should the exact DUT be implemented as a Chisel Module. Otherwise, skip ahead to step 2.
  2. The ErrorReporter must then be instantiated within a test and provided any number of Trackers and/or Constraints watching ports of the DUT.
  3. When instantiated, its watchers must be sampled manually by calling the sample method of the error reporter. If the exact DUT is a software model, this method must be passed a map of port to reference value pairs with the ports corresponding to ones registered in the watchers. It is not possible to sample a reference-based watcher without passing it a reference value. It is valid to use a combination of reference-based and port-based watchers and selectively override port-based watchers' exact values by passing in expected values.
  4. Once the test is done, an error report can be generated by calling the report methods of the error reporter and printing its return value.

An example of this can be found here.

Example Use Cases

We will explore a handful of use cases to explore verification.


Universal Verification Method (UVM) Examples

In the early stages of this project, we explored the possibilty of using UVM to verify Chisel designs. Thus, the sv directory in another repository of ours contains a number of UVM examples.

Simple examples

In sv/uvm-simple-examples a number of simple examples are located. These start with a very basic testbench with no DUT attached, and gradually transition into a complete testbench.

Vivado UVM Examples

These examples assume that a copy of Xilinx Vivado is installed and present in the PATH. The examples are currently tested only on Linux. The first example is taken from Vivado Design Suite Tutorial - Logic Simulation.

Leros ALU

In the directory sv/leros, the Leros ALU is tested using UVM, to showcase that Chisel and UVM can work together. This testbench is reused to also test a VHDL implementation of the ALU, to show that UVM is usable on mixed-language designs (when using a mixed-language simulator).

The VHDL implementaion is run by setting the makefile argument TOP=top_vhd.

Using the SystemVerilog Direct Programming Interface (DPI) and Java's Native Interface (JNI)

Using the SystemVerilog DPI to cosimulate with a golden model described in C is explored in the sv/leros/scoreboards/scoreboard_dpi.svh. The C-model is implemented in sv/leros/scoreboards/scoreboard.c, and the checking functionality is called from the SystemVerilog code.

Implementing a similar functionality in Scala/Chisel has been explored via the JNI. In the directory native, the necessary code for a simple Leros tester using the JNI is implemented.

To use the JNI functionality, first run make jni to generate the correct header files and shared libraries. Then, open sbt and type project native to access the native project. Then run sbt test to test the Leros ALU using a C model called from within Scala. To switch back, type project chisel-uvm.


Resources

If you're interested in learning more about the UVM, we recommend that you explore the otherverify repository as well as some of the following links:


Related Work

Fuzzing

Here are a few pointers to some interesting documentation around the topic of mutation-based fuzzing:

CRV

  • Choco-Solver Java library for solving CSP problems
  • QuickCheck Checker for Haskel, used Lava as example, the inspiration for ScalaCheck

Testing Frameworks / Simulation Tools

Cocotb -- coroutine-based cosimulation library for hardware development in Python

Cocotb repository: cocotb is a coroutine based cosimulation library for writing VHDL and Verilog testbenches in Python.

Resources Related to Cocotb

Extension of Cocotb

  • cocotb-coverage: Extension that enables coverage and constrained random verification
  • Publication in IEEE Paper
  • python-uvm: port of SystemVerilog (SV) Universal Verification Methodology (UVM) 1.2 to Python and cocotb

Hwt -- Python library for hardware development

hwt: one of the goals of this library is to implement some simulation features similar to UVM

Not strictly relevant resources

chiselverify's People

Contributors

dobios avatar ekiwi avatar hansemandse avatar kasperhesse avatar nothinn avatar parzival3 avatar schoeberl avatar tjarker avatar yuravg avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

chiselverify's Issues

PriorityQueue tests failing seemingly at random

The functionality tests for the priority queue have each been failing seemingly at random from one run to the next.
Sometimes MemoryTest fails, sometimes PriorityQueueTest fails, and sometimes they both pass...

This should be really made consistent, as it either shows that the functionality of the PriorityQueue is wrong, or that the test is wrong.

@tjarker Please look into this.

One test aborted

One of the tests is aborted. Furthermore, the tests run too long. 23 minutes on my laptop.

Compile error

When running sbt test we get following error:

[error] /Users/martin/source/chisel-uvm/chisel-verify/src/test/scala/coverage/CoverageDB.scala:60:48: value strip is not a member of String
[error]             coverIdPortMap update (dutPortName.strip(), dutPort)
[error]                                                ^
[error] one error found
[error] (Test / compileIncremental) Compilation failed

Leros code generation

The following assumption is not true:

// leros uses the lower part of main memory as a register file

It would be a legal implementation to map the registers into memory. Which would save resources and also gives a fast context switch. However, it is probably inefficient. If so, I would not put it at address 0.

Is this assumption anywhere considered in the test generation?

Coverage report

Could you please tell me how to get code coverage? I have read the paper you published and also studied the examples you gave in the source code.
code coverage
It looks like there is just a picture in the paper, but no specific method is pointed out in the documentation or an example given in the source code?

Maven Online Repository Update Request

Hello, I found that in the latest version of chisel verify, you have commented out require in lines 166 and 292 of chiselverify/coverage/CoverReport, but the version you uploaded to the maven online repository has not been updated subsequently.
Could you please update it, thank you~

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.