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

try to use virtual threads #677

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
204 changes: 3 additions & 201 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,10 @@ jobs:
strategy:
matrix:
scala: [2.13.10]
jvm: [8, 11]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Scala
uses: actions/setup-java@v3
with:
distribution: 'adopt'
java-version: ${{ matrix.jvm }}
- name: Test
run: sbt ++${{ matrix.scala }} "testOnly chiseltest.** -- -l RequiresVcs -l RequiresVerilator -l Formal -l RequiresIcarus"
jvm: [20]

integration-test:
name: Integration Tests
runs-on: ubuntu-20.04
strategy:
matrix:
scala: [ 2.13.10 ]
jvm: [ 8, 11, 20 ]
env:
JAVA_OPTS: "--enable-preview"
steps:
- name: Checkout
uses: actions/checkout@v3
Expand All @@ -44,186 +29,3 @@ jobs:
java-version: ${{ matrix.jvm }}
- name: Test
run: sbt ++${{ matrix.scala }} "testOnly integration.**"


test-mac:
name: sbt test on mac
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Test
run: sbt "testOnly chiseltest.** -- -l RequiresVcs -l RequiresVerilator -l Formal -l RequiresIcarus"

icarus:
name: icarus verilog
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ ubuntu-20.04, macos-latest ]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install Icarus Verilog for Ubuntu
if: runner.os == 'Linux'
run: |
sudo apt-get install -y iverilog
iverilog -v || true
- name: Install Icarus Verilog for MacOS
if: runner.os == 'macOS'
run: |
brew install icarus-verilog
iverilog -v || true
- name: Test
run: sbt ++${{ matrix.scala }} "testOnly chiseltest.** -- -n RequiresIcarus"

verilator:
name: verilator regressions
runs-on: ubuntu-20.04
strategy:
matrix:
# 4.028: Ubuntu 20.04, Fedora 32
# 4.032: Fedora 33
# 4.034: Chipyard
# 4.038: Ubuntu 20.10
# 4.108: Fedora 34
# 4.200: currently the latest version on brew (MacOS)
# 4.202: added "forcePerInstance" to support our coverage flow
version: ["4.028", "4.032", "4.034", "4.038", "4.108", "4.200", "4.202"]

steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install Verilator Build Dependencies
run: sudo apt-get install -y git make autoconf g++ flex bison libfl2 libfl-dev
- name: Cache Verilator ${{ matrix.version }}
uses: actions/cache@v3
id: cache-verilator
with:
path: verilator-${{ matrix.version }}
key: ${{ runner.os }}-verilator-${{ matrix.version }}
- name: Compile Verilator ${{ matrix.version }}
if: steps.cache-verilator.outputs.cache-hit != 'true'
run: |
wget https://github.com/verilator/verilator/archive/refs/tags/v${{ matrix.version }}.zip
unzip v${{ matrix.version }}.zip
cd verilator-${{ matrix.version }}
autoconf
./configure
make
- name: Install Verilator ${{ matrix.version }}
run: |
cd verilator-${{ matrix.version }}
sudo make install
verilator --version
- name: Test
run: sbt "testOnly chiseltest.** -- -n RequiresVerilator"

formal:
name: formal verification tests
runs-on: ubuntu-20.04
strategy:
matrix:
backend: [z3, cvc4, btormc, bitwuzla]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install Z3 and CVC4
if: runner.os == 'Linux'
run: |
sudo apt-get install -y z3 cvc4
z3 --version
cvc4 --version
- name: Install Tabby OSS Cad Suite (from YosysHQ)
uses: ./.github/workflows/setup-oss-cad-suite
with:
osscadsuite-version: '2023-01-09'
- name: Test
run: sbt "testOnly chiseltest.** -- -n Formal -Dformal_engine=${{ matrix.backend }}"

formal-mac:
name: formal verification tests on mac
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install Z3 for MacOS
run: |
brew install z3
z3 --version
- name: Test
run: sbt "testOnly chiseltest.** -- -n Formal -Dformal_engine=z3"

doc:
name: Documentation and Formatting
runs-on: ubuntu-20.04
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Documentation
id: doc
run: sbt doc
- name: Check Formatting
run: sbt scalafmtCheckAll

no-warn:
name: No Warnings with Scala 2.13 for PRs
if: github.event_name == 'pull_request'
runs-on: ubuntu-20.04
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Check for Warnings
run: sbt "set ThisBuild / scalacOptions ++= Seq(\"-Xfatal-warnings\") ; compile"
- name: Check for Warnings in Tests
run: sbt "set ThisBuild / scalacOptions ++= Seq(\"-Xfatal-warnings\") ; Test / compile"

test-treadle:
name: sbt test for treadle on ubuntu
runs-on: ubuntu-20.04
strategy:
matrix:
scala: [2.13.10]
jvm: [8, 11]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Scala
uses: actions/setup-java@v3
with:
distribution: 'adopt'
java-version: ${{ matrix.jvm }}
- name: Test
run: sbt ++${{ matrix.scala }} "testOnly treadle2.**"

# Sentinel job to simplify how we specify which checks need to pass in branch
# protection and in Mergify
#
# When adding new jobs, please add them to `needs` below
all_tests_passed:
name: "all tests passed"
needs: [test, doc, verilator, formal, formal-mac, icarus, test-mac, no-warn, integration-test]
runs-on: ubuntu-latest
steps:
- run: echo Success!

# sbt ci-release publishes all cross versions so this job needs to be
# separate from a Scala versions build matrix to avoid duplicate publishing
publish:
# note: we do not require a warning check for publishing!
needs: [test, doc, verilator, formal, formal-mac, icarus, test-mac, test-treadle, integration-test]
runs-on: ubuntu-20.04
if: github.event_name == 'push'

steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup GPG (for Publish)
uses: olafurpg/setup-gpg@v3
- name: Publish
run: sbt ci-release
env:
PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }}
PGP_SECRET: ${{ secrets.PGP_SECRET }}
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
2 changes: 2 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,5 @@ libraryDependencies ++= Seq(
)
}
}

javacOptions ++= Seq("--source", "20", "--target", "20", "--enable-preview")
145 changes: 145 additions & 0 deletions src/main/scala/chiseltest/internal/NewThreadedBackend.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package chiseltest.internal

import chisel3.{Clock, Data, Module}
import chiseltest._
import chiseltest.coverage.TestCoverage
import chiseltest.simulator.{SimulatorContext, StepInterrupted, StepOk}
import firrtl2.AnnotationSeq
import scala.collection.mutable


/** Experiment to create a more performant threaded backedn */
class NewThreadedBackend[T <: Module](
val dut: T,
val dataNames: Map[Data, String],
val combinationalPaths: Map[Data, Set[Data]],
tester: SimulatorContext,
coverageAnnotations: AnnotationSeq)
extends BackendInstance[T] {

override def resolveName(signal: Data): String = {
dataNames.getOrElse(signal, signal.toString)
}

// Circuit introspection functionality
override def getSourceClocks(signal: Data): Set[Clock] = {
throw new ClockResolutionException("ICR not available on chisel-testers2 / firrtl master")
}

override def getSinkClocks(signal: Data): Set[Clock] = {
throw new ClockResolutionException("ICR not available on chisel-testers2 / firrtl master")
}

override def pokeClock(signal: Clock, value: Boolean): Unit = {
throw new NotImplementedError("Poking clocks is currently no supported!")
}

override def peekClock(signal: Clock): Boolean = {
val a = tester.peek(dataNames(signal))
a > 0
}

private val previousPokes = mutable.HashMap[String, BigInt]()
override def pokeBits(signal: Data, value: BigInt): Unit = {
val name = dataNames(signal)
previousPokes.get(name) match {
case Some(oldValue) if oldValue == value => // ignore
case _ =>
tester.poke(name, value)
idleCycles = 0
previousPokes(name) = value
}
}

override def peekBits(signal: Data): BigInt = {
val name = dataNames.getOrElse(
signal,
throw new UnpeekableException(
s"Signal $signal not found. Perhaps you're peeking a non-IO signal.\n If so, consider using the chiseltest.experimental.expose API."
)
)
tester.peek(name)
}

override def doTimescope(contents: () => Unit): Unit = {
throw new NotImplementedError("This backend does not support timescopes!")
}

override def doFork(runnable: () => Unit, name: Option[String], region: Option[Region]): Nothing = {
throw new NotImplementedError("This backend does not support threads!")
}

override def doJoin(threads: Seq[AbstractTesterThread], stepAfter: Option[Clock]): Unit = {
throw new NotImplementedError("This backend does not support threads!")
}

private var timeout = 1000
private var idleCycles = 0

override def step(signal: Clock, cycles: Int): Unit = {
require(signal == dut.clock)
// throw any available exceptions before stepping
Context().env.checkpoint()
val delta = if (timeout == 0) cycles else Seq(cycles, timeout - idleCycles).min
tester.step(delta) match {
case StepOk =>
// update and check timeout
idleCycles += delta
stepCount += delta
if (timeout > 0 && idleCycles == timeout) {
throw new TimeoutException(s"timeout on $signal at $timeout idle cycles")
}
case StepInterrupted(after, true, _) =>
val msg = s"An assertion in ${dut.name} failed.\n" +
"Please consult the standard output for more details."
throw new ChiselAssertionError(msg, cycles + after)
case StepInterrupted(after, false, _) =>
val msg = s"A stop() statement was triggered in ${dut.name}."
throw new StopException(msg, cycles + after)
}
}

private var stepCount: Long = 0

override def getStepCount(signal: Clock): Long = {
require(signal == dut.clock)
stepCount
}

override def setTimeout(signal: Clock, cycles: Int): Unit = {
require(signal == dut.clock, "timeout currently only supports master clock")
require(cycles >= 0, s"Negative timeout $cycles is not supported! Use 0 to disable the timeout.")
timeout = cycles
idleCycles = 0
}

override def run(testFn: T => Unit): AnnotationSeq = {
try {
// default reset
tester.poke("reset", 1)
tester.step(1)
tester.poke("reset", 0)

// we only count the user steps
stepCount = 0

// execute use code
testFn(dut)

// throw any exceptions that might be left over
Context().env.checkpoint()
} finally {
tester.finish() // needed to dump VCDs + terminate any external process
}

if (tester.sim.supportsCoverage) {
generateTestCoverageAnnotation() +: coverageAnnotations
} else { Seq() }
}

/** Generates an annotation containing the map from coverage point names to coverage counts. */
private def generateTestCoverageAnnotation(): TestCoverage = {
TestCoverage(tester.getCoverage())
}

}
2 changes: 1 addition & 1 deletion src/main/scala/chiseltest/internal/ThreadedBackend.scala
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,7 @@ trait ThreadedBackend[T <: Module] extends BackendInterface {

// noinspection ConvertExpressionToSAM
// TODO: code analysis suggests "Convert expression to Single Abstract Method", will that work?
val thread = new Thread(new Runnable {
val thread = new Thread (new Runnable {
def run(): Unit = {
try {
waiting.acquire()
Expand Down
4 changes: 2 additions & 2 deletions src/test/scala/integration/UartSendAndReceiveTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import org.scalatest.freespec.AnyFreeSpec
class UartPassThrough extends Module {
val rx = IO(Input(UInt(1.W)))
val tx = IO(Output(UInt(1.W)))
val Depth = 50
val Depth = 1
val AllOnes = (BigInt(1) << Depth) - 1
tx := ShiftRegister(rx, Depth, AllOnes.U, true.B)
}
Expand Down Expand Up @@ -80,7 +80,7 @@ class UartSendAndReceiveTest extends AnyFreeSpec with ChiselScalatestTester {
}

"run 100 uart transactions in software" in {
test(new UartPassThrough)(runTimedTest(_, 100, bitDelay = 50))
test(new UartPassThrough)(runTimedTest(_, 10000, bitDelay = 1))
}

}