From 5e81249d88dfb3b71823026e4c381f399c300c9f Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 5 Dec 2022 23:51:16 +0000 Subject: [PATCH 1/7] Fix issues in IPCSimulatorContext (VCS) simulations (#570) (#571) * Fix issues in VCS simulations * IPCSimulatorContext: better synchronization on the logs * TestApplicationException: changes for more useful assertion failure messages * scalafmt Co-authored-by: Jack Koenig (cherry picked from commit 5956488be7b78522df43bb5686001a1837409b8b) Co-authored-by: Megan Wachs --- .../chiseltest/internal/GenericBackend.scala | 12 +++++++++-- .../simulator/ipc/IPCSimulatorContext.scala | 20 ++++++++++++++----- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/main/scala/chiseltest/internal/GenericBackend.scala b/src/main/scala/chiseltest/internal/GenericBackend.scala index 82ca11186..bcf063442 100644 --- a/src/main/scala/chiseltest/internal/GenericBackend.scala +++ b/src/main/scala/chiseltest/internal/GenericBackend.scala @@ -8,6 +8,8 @@ import chiseltest.coverage.TestCoverage import chiseltest.simulator.{SimulatorContext, StepInterrupted, StepOk} import firrtl.AnnotationSeq +import chiseltest.simulator.ipc.TestApplicationException + import scala.collection.mutable /** Chiseltest threaded backend using the generic SimulatorContext abstraction from [[chiseltest.simulator]] */ @@ -202,8 +204,14 @@ class GenericBackend[T <: Module]( thread.thread.interrupt() } } - - tester.finish() // needed to dump VCDs + terminate any external process + try { + tester.finish() // needed to dump VCDs + terminate any external process + } catch { + case e: TestApplicationException => + throw new ChiselAssertionError( + s"Simulator exited sooner than expected. See logs for more information about what is assumed to be a Chisel Assertion which failed." + ) + } } if (tester.sim.supportsCoverage) { generateTestCoverageAnnotation() +: coverageAnnotations diff --git a/src/main/scala/chiseltest/simulator/ipc/IPCSimulatorContext.scala b/src/main/scala/chiseltest/simulator/ipc/IPCSimulatorContext.scala index 6cc868b49..2de6c1155 100644 --- a/src/main/scala/chiseltest/simulator/ipc/IPCSimulatorContext.scala +++ b/src/main/scala/chiseltest/simulator/ipc/IPCSimulatorContext.scala @@ -57,7 +57,14 @@ private[chiseltest] class IPCSimulatorContext( private def startProcess(cmd: Seq[String], logs: ArrayBuffer[String], cwd: os.Path): Process = { val processBuilder = Process(cmd, cwd = cwd.toIO) // This makes everything written to stderr get added as lines to logs - val processLogger = ProcessLogger(println, logs += _) // don't log stdout + val processLogger = ProcessLogger( + println, + { str => + logs.synchronized { + logs += str + } + } + ) // don't log stdout processBuilder.run(processLogger) } @@ -108,8 +115,10 @@ private[chiseltest] class IPCSimulatorContext( } private def dumpLogs(): Unit = { - _logs.foreach(x => println(x)) - _logs.clear() + _logs.synchronized { + _logs.foreach(x => println(x)) + _logs.clear() + } } private def throwExceptionIfDead(exitValue: Future[Int]): Unit = { @@ -380,10 +389,10 @@ private[chiseltest] class IPCSimulatorContext( mwhile(!sendCmd(SIM_CMD.FIN)) {} val exit = Await.result(exitValue, Duration.Inf) println("Exit Code: %d".format(exit)) - dumpLogs() inChannel.close() outChannel.close() cmdChannel.close() + dumpLogs() isRunning = false } @@ -436,4 +445,5 @@ private class Channel(cwd: os.Path, name: String) { os.remove(cwd / name) } -private case class TestApplicationException(exitVal: Int, lastMessage: String) extends RuntimeException(lastMessage) +private[chiseltest] case class TestApplicationException(exitVal: Int, lastMessage: String) + extends RuntimeException(lastMessage) From bb2fd485e310be478c643322713cb9774fbf7908 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 7 Dec 2022 00:07:34 +0000 Subject: [PATCH 2/7] VCS Q-2020.03 (at least) fails because it can't find nullptr (#572) (#573) (cherry picked from commit 9c6b387ad49f66e43d8016d07567e6bf300f7398) Co-authored-by: Steve Burns --- src/main/resources/simulator/vpi.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/simulator/vpi.h b/src/main/resources/simulator/vpi.h index b7f13be59..468487f4d 100644 --- a/src/main/resources/simulator/vpi.h +++ b/src/main/resources/simulator/vpi.h @@ -32,7 +32,7 @@ class vpi_api_t: public sim_api_t { vpiHandle syscall_handle = vpi_handle(vpiSysTfCall, NULL); vpiHandle arg_iter = vpi_iterate(vpiArgument, syscall_handle); // Cache Inputs - if(arg_iter != nullptr) { + if(arg_iter != NULL) { while (vpiHandle arg_handle = vpi_scan(arg_iter)) { sim_data.inputs.push_back(arg_handle); } @@ -43,7 +43,7 @@ class vpi_api_t: public sim_api_t { vpiHandle syscall_handle = vpi_handle(vpiSysTfCall, NULL); vpiHandle arg_iter = vpi_iterate(vpiArgument, syscall_handle); // Cache Outputs - if(arg_iter != nullptr) { + if(arg_iter != NULL) { while (vpiHandle arg_handle = vpi_scan(arg_iter)) { sim_data.outputs.push_back(arg_handle); } From cef6079ca0191f8dd5af0b0babc81bc98b1a481d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 6 Dec 2022 21:34:26 -0500 Subject: [PATCH 3/7] Remove quotes around VcsCFlags (#574) (#575) * Remove quotes around VcsCFlags * Revert back to using nullptr * Remove added blank line (cherry picked from commit 24e4ec41c47ab9c343b923fc5eb1075c15208ad6) Co-authored-by: Steve Burns Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- src/main/scala/chiseltest/simulator/VcsSimulator.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/chiseltest/simulator/VcsSimulator.scala b/src/main/scala/chiseltest/simulator/VcsSimulator.scala index 1efc38799..c3975bfc5 100644 --- a/src/main/scala/chiseltest/simulator/VcsSimulator.scala +++ b/src/main/scala/chiseltest/simulator/VcsSimulator.scala @@ -193,7 +193,7 @@ private object VcsSimulator extends Simulator { "-LDFLAGS", "-lstdc++", "-CFLAGS", - "\"" + cFlags.mkString(" ") + "\"" + cFlags.mkString(" ") // Don't wrap in double quotes ) ++ verdiFlags private def generateHarness( From 2e610f46017a625d3cc2e8e0ee86fa0259dc72a0 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 7 Dec 2022 10:49:25 -0500 Subject: [PATCH 4/7] update 0.5.x CI (#577) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kevin Läufer --- .github/workflows/test.yml | 60 ++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 35 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 19f3dfd8f..4a7426bcc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,7 +4,7 @@ on: pull_request: push: branches: - - master + - main - 0.5.x - 0.3.x @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-20.04 strategy: matrix: - scala: [2.12.15, 2.13.7] + scala: [2.12.16, 2.13.10] jvm: [adopt@1.8, adopt@1.11] steps: - name: Checkout @@ -65,25 +65,6 @@ jobs: - name: Test run: sbt ++${{ matrix.scala }} "testOnly -- -n RequiresIcarus" - verilator-mac: - name: verilator regression on mac - runs-on: macos-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Install Tabby OSS Cad Suite (from YosysHQ) - uses: YosysHQ/setup-oss-cad-suite@v1 - with: - osscadsuite-version: '2021-11-09' - - name: Print Verilator Version - run: verilator --version - - name: Setup Scala - uses: olafurpg/setup-scala@v10 - with: - java-version: adopt@1.11 - - name: Test - run: sbt "testOnly -- -n RequiresVerilator" - verilator: name: verilator regressions runs-on: ubuntu-20.04 @@ -132,33 +113,23 @@ 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] 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-18' - name: Setup Scala uses: olafurpg/setup-scala@v10 with: @@ -166,6 +137,23 @@ jobs: - 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: adopt@1.11 + - name: Test + run: sbt "testOnly -- -n Formal -Dformal_engine=z3" + doc: name: Documentation and Formatting runs-on: ubuntu-20.04 @@ -186,7 +174,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, formal, formal-mac, icarus, test-mac] runs-on: ubuntu-latest steps: - run: echo Success! @@ -214,3 +202,5 @@ jobs: PGP_SECRET: ${{ secrets.PGP_SECRET }} SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} + + From bccede10e4791914ada45b5a5f38f241e66c3dbb Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 7 Dec 2022 16:03:38 -0500 Subject: [PATCH 5/7] ci: update github actions (#579) (#584) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ci: update github actions the old versions of these actions use a deprecated version of NodeJS * remove deprecated setup-scala action * change java version numbers Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> (cherry picked from commit f3571c569fa784b7dba138cd23d19c7ff3ef0718) Co-authored-by: Kevin Läufer --- .github/workflows/test.yml | 52 ++++++++++---------------------------- 1 file changed, 14 insertions(+), 38 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4a7426bcc..e9d32f8d7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,15 +14,17 @@ jobs: runs-on: ubuntu-20.04 strategy: matrix: - scala: [2.12.16, 2.13.10] - jvm: [adopt@1.8, adopt@1.11] + scala: [2.12.17, 2.13.10] + jvm: [8, 11] steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup Scala - uses: olafurpg/setup-scala@v10 + uses: actions/setup-java@v3 with: + distribution: 'adopt' java-version: ${{ matrix.jvm }} + cache: 'sbt' - name: Test run: sbt ++${{ matrix.scala }} "testOnly -- -l RequiresVcs -l RequiresVerilator -l Formal -l RequiresIcarus" @@ -31,11 +33,7 @@ jobs: runs-on: macos-latest steps: - name: Checkout - uses: actions/checkout@v2 - - name: Setup Scala - uses: olafurpg/setup-scala@v10 - with: - java-version: adopt@1.11 + uses: actions/checkout@v3 - name: Test run: sbt "testOnly -- -l RequiresVcs -l RequiresVerilator -l Formal -l RequiresIcarus" @@ -47,7 +45,7 @@ jobs: os: [ ubuntu-20.04, macos-latest ] steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install Icarus Verilog for Ubuntu if: runner.os == 'Linux' run: | @@ -58,10 +56,6 @@ jobs: run: | brew install icarus-verilog iverilog -v || true - - name: Setup Scala - uses: olafurpg/setup-scala@v10 - with: - java-version: adopt@1.11 - name: Test run: sbt ++${{ matrix.scala }} "testOnly -- -n RequiresIcarus" @@ -81,11 +75,11 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + 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@v2 + uses: actions/cache@v3 id: cache-verilator with: path: verilator-${{ matrix.version }} @@ -104,10 +98,6 @@ jobs: cd verilator-${{ matrix.version }} sudo make install verilator --version - - name: Setup Scala - uses: olafurpg/setup-scala@v10 - with: - java-version: adopt@1.8 - name: Test run: sbt "testOnly -- -n RequiresVerilator" @@ -119,7 +109,7 @@ jobs: backend: [z3, cvc4, btormc] steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install Z3 and CVC4 if: runner.os == 'Linux' run: | @@ -130,10 +120,6 @@ jobs: uses: YosysHQ/setup-oss-cad-suite@v1 with: osscadsuite-version: '2022-08-18' - - name: Setup Scala - uses: olafurpg/setup-scala@v10 - with: - java-version: adopt@1.11 - name: Test run: sbt "testOnly -- -n Formal -Dformal_engine=${{ matrix.backend }}" @@ -142,15 +128,11 @@ jobs: runs-on: macos-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install Z3 for MacOS run: | brew install z3 z3 --version - - name: Setup Scala - uses: olafurpg/setup-scala@v10 - with: - java-version: adopt@1.11 - name: Test run: sbt "testOnly -- -n Formal -Dformal_engine=z3" @@ -159,9 +141,7 @@ jobs: runs-on: ubuntu-20.04 steps: - name: Checkout - uses: actions/checkout@v2 - - name: Setup Scala - uses: olafurpg/setup-scala@v10 + uses: actions/checkout@v3 - name: Documentation id: doc run: sbt doc @@ -188,11 +168,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 - - name: Setup Scala - uses: olafurpg/setup-scala@v10 - with: - java-version: adopt@1.8 + uses: actions/checkout@v3 - name: Setup GPG (for Publish) uses: olafurpg/setup-gpg@v3 - name: Publish From 02f70948a582706041a25bdc80cb54e38536d25c Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 7 Dec 2022 21:25:37 +0000 Subject: [PATCH 6/7] Allow patched versions of Verilator (#582) (#585) (cherry picked from commit 9a9d34775f88b7c8806ad114d7532948c53a8c62) Co-authored-by: Steve Burns --- .../simulator/VerilatorSimulator.scala | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/main/scala/chiseltest/simulator/VerilatorSimulator.scala b/src/main/scala/chiseltest/simulator/VerilatorSimulator.scala index be8358b5b..1fe28a023 100644 --- a/src/main/scala/chiseltest/simulator/VerilatorSimulator.scala +++ b/src/main/scala/chiseltest/simulator/VerilatorSimulator.scala @@ -43,17 +43,26 @@ private object VerilatorSimulator extends Simulator { } } - // example version string: Verilator 4.038 2020-07-11 rev v4.038 + // example version string1: Verilator 4.038 2020-07-11 rev v4.038 + // example version string2: Verilator 5.002.p 2022-11-15 rev v5.002-12-g58821e0eb private lazy val version: (Int, Int) = { // (major, minor) val versionSplitted = os.proc("verilator", "--version").call().out.trim.split(' ') assert( versionSplitted.length > 1 && versionSplitted.head == "Verilator", s"Unknown verilator version string: ${versionSplitted.mkString(" ")}" ) - val Array(majStr, minStr) = versionSplitted(1).split('.').map(_.trim) - assert(majStr.length == 1 && minStr.length == 3, s"$majStr.$minStr is not of the expected format: D.DDD") - val (maj, min) = (majStr.toInt, minStr.toInt) - // println(s"Detected Verilator version $maj.$min") + + val (maj, min) = versionSplitted(1).split('.').map(_.trim) match { + case Array(majStr, minStr) => + assert(majStr.length == 1 && minStr.length == 3, s"${majStr}.${minStr} is not of the expected format: D.DDD") + (majStr.toInt, minStr.toInt) + case Array(majStr, minStr, s) => + assert(majStr.length == 1 && minStr.length == 3, s"${majStr}.${minStr} is not of the expected format: D.DDD.s+") + (majStr.toInt, minStr.toInt) + case s => + assert(false, s"${s} is not of the expected format: D.DDD or D.DDD.s+") + (0, 0) + } (maj, min) } From 0e81af7d4c88b0d1ac33b42b561801c402ecd355 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 20 Dec 2022 13:02:25 +0000 Subject: [PATCH 7/7] partialPoke/Expect: add more tests, improve implementation (#589) (#590) * partialPoke/Expect: add more tests, improve implementation * fix error reporting for partialExpect (cherry picked from commit 8f692f2f7102c4983adfac615a6080cf146fb7a4) Co-authored-by: Kevin Laeufer --- src/main/scala/chiseltest/exceptions.scala | 11 + .../internal/TestEnvInterface.scala | 10 +- src/main/scala/chiseltest/package.scala | 169 ++++++------ .../tests/PokeAndExpectPartialTests.scala | 250 ++++++++++++++++++ 4 files changed, 345 insertions(+), 95 deletions(-) create mode 100644 src/test/scala/chiseltest/tests/PokeAndExpectPartialTests.scala diff --git a/src/main/scala/chiseltest/exceptions.scala b/src/main/scala/chiseltest/exceptions.scala index b4c09d8d1..fff4f58c3 100644 --- a/src/main/scala/chiseltest/exceptions.scala +++ b/src/main/scala/chiseltest/exceptions.scala @@ -24,3 +24,14 @@ class StopException(message: String) extends Exception(message) /** Indicates that a Chisel `assert(...)` or `assume(...)` statement has failed. */ class ChiselAssertionError(message: String) extends Exception(message) + +/** Indicates that a value used in a poke/expect is not a literal. + * It could be hardware or a DontCare which is only allowed when using pokePartial/expectPartial. + */ +class NonLiteralValueError(val value: chisel3.Data, val signal: chisel3.Data, op: String) + extends Exception( + s"""Value $value for entry $signal is not a literal value! + |You need to fully specify all fields/entries when using $op. + |Maybe try using `${op}Partial` if you only want to use incomplete Vec/Bundle literals. + |""".stripMargin + ) diff --git a/src/main/scala/chiseltest/internal/TestEnvInterface.scala b/src/main/scala/chiseltest/internal/TestEnvInterface.scala index fcc1fa1d9..3b472993b 100644 --- a/src/main/scala/chiseltest/internal/TestEnvInterface.scala +++ b/src/main/scala/chiseltest/internal/TestEnvInterface.scala @@ -45,17 +45,19 @@ trait TestEnvInterface { def signalExpectFailure(message: String): Unit = { val trace = new Throwable val expectStackDepth = trace.getStackTrace.indexWhere(ste => - ste.getClassName.startsWith("chiseltest.package$") && ste.getMethodName == "expect" + ste.getClassName.startsWith( + "chiseltest.package$" + ) && (ste.getMethodName == "expect" || ste.getMethodName == "expectPartial") ) require( expectStackDepth != -1, s"Failed to find expect in stack trace:\r\n${trace.getStackTrace.mkString("\r\n")}" ) - val trimmedTrace = trace.getStackTrace.drop(expectStackDepth + 2) - val detailedTrace = topFileName.map(getExpectDetailedTrace(trimmedTrace.toSeq, _)).getOrElse("") + val trimmedTrace = trace.getStackTrace.drop(expectStackDepth) + val failureLocation: String = topFileName.map(getExpectDetailedTrace(trimmedTrace.toSeq, _)).getOrElse("") val stackIndex = expectStackDepth + 1 - batchedFailures += new FailedExpectException(message + detailedTrace, stackIndex) + batchedFailures += new FailedExpectException(message + failureLocation, stackIndex) } /** If there are any failures, reports them and end the test now. diff --git a/src/main/scala/chiseltest/package.scala b/src/main/scala/chiseltest/package.scala index 2d4fcd626..9617f52c3 100644 --- a/src/main/scala/chiseltest/package.scala +++ b/src/main/scala/chiseltest/package.scala @@ -196,19 +196,7 @@ package object chiseltest { */ def pokePartial(value: T): Unit = { require(DataMirror.checkTypeEquivalence(x, value), s"Record type mismatch") - x.elements.filter { case (k, v) => - DataMirror.directionOf(v) != ActualDirection.Output && { - value.elements(k) match { - case _: Record => true - case data: Data => data.isLit - } - } - }.foreach { case (k, v) => - v match { - case record: Record => record.pokePartial(value.elements(k).asInstanceOf[Record]) - case data: Data => data.poke(value.elements(k)) - } - } + x.pokeInternal(value, allowPartial = true) } /** Check the given signal with a [[Record.litValue()]]; @@ -216,17 +204,7 @@ package object chiseltest { */ def expectPartial(value: T): Unit = { require(DataMirror.checkTypeEquivalence(x, value), s"Record type mismatch") - x.elements.filter { case (k, _) => - value.elements(k) match { - case _: Record => true - case d: Data => d.isLit - } - }.foreach { case (k, v) => - v match { - case record: Record => record.expectPartial(value.elements(k).asInstanceOf[Record]) - case data: Data => data.expect(value.elements(k)) - } - } + x.expectInternal(value, None, allowPartial = true) } } @@ -242,19 +220,7 @@ package object chiseltest { */ def pokePartial(value: T): Unit = { require(DataMirror.checkTypeEquivalence(x, value), s"Vec type mismatch") - x.getElements.zipWithIndex.filter { case (v, index) => - DataMirror.directionOf(v) != ActualDirection.Output && { - value.getElements(index) match { - case _: T => true - case data: Data => data.isLit - } - } - }.foreach { case (v, index) => - v match { - case vec: T => vec.pokePartial(value.getElements(index).asInstanceOf[T]) - case data: Data => data.poke(value.getElements(index)) - } - } + x.pokeInternal(value, allowPartial = true) } /** Check the given signal with a [[Vec.litValue()]]; @@ -262,44 +228,55 @@ package object chiseltest { */ def expectPartial(value: T): Unit = { require(DataMirror.checkTypeEquivalence(x, value), s"Vec type mismatch") - x.getElements.zipWithIndex.filter { case (_, index) => - value.getElements(index) match { - case _: T => true - case d: Data => d.isLit - } - }.foreach { case (v, index) => - v match { - case vec: T => vec.expectPartial(value.getElements(index).asInstanceOf[T]) - case data: Data => data.expect(value.getElements(index)) - } - } + x.expectInternal(value, None, allowPartial = true) } } implicit class testableData[T <: Data](x: T) { import Utils._ - def poke(value: T): Unit = (x, value) match { - case (x: Bool, value: Bool) => x.poke(value) - case (x: UInt, value: UInt) => x.poke(value) - case (x: SInt, value: SInt) => x.poke(value) - case (x: FixedPoint, value: FixedPoint) => x.poke(value) - case (x: Interval, value: Interval) => x.poke(value) - case (x: Record, value: Record) => - require(DataMirror.checkTypeEquivalence(x, value), s"Record type mismatch") - x.elements.zip(value.elements).foreach { case ((_, x), (_, value)) => - x.poke(value) - } - case (x: Vec[_], value: Vec[_]) => - require(DataMirror.checkTypeEquivalence(x, value), s"Vec type mismatch") - x.getElements.zip(value.getElements).foreach { case (x, value) => - x.poke(value) + def poke(value: T): Unit = pokeInternal(value, allowPartial = false) + + private def isAllowedNonLitGround(value: T, allowPartial: Boolean, op: String): Boolean = { + val isGroundType = value match { + case _: Vec[_] | _: Record => false + case _ => true + } + // if we are dealing with a ground type non-literal, this is only allowed if we are doing a partial poke/expect + if (isGroundType && !value.isLit) { + if (allowPartial) { true } + else { + throw new NonLiteralValueError(value, x, op) } - case (x: EnumType, value: EnumType) => - require(DataMirror.checkTypeEquivalence(x, value), s"EnumType mismatch") - pokeBits(x, value.litValue) - case x => throw new LiteralTypeException(s"don't know how to poke $x") - // TODO: aggregate types + } else { + false + } + } + + private[chiseltest] def pokeInternal(value: T, allowPartial: Boolean): Unit = { + if (isAllowedNonLitGround(value, allowPartial, "poke")) return + (x, value) match { + case (x: Bool, value: Bool) => x.poke(value) + case (x: UInt, value: UInt) => x.poke(value) + case (x: SInt, value: SInt) => x.poke(value) + case (x: FixedPoint, value: FixedPoint) => x.poke(value) + case (x: Interval, value: Interval) => x.poke(value) + case (x: Record, value: Record) => + require(DataMirror.checkTypeEquivalence(x, value), s"Record type mismatch") + x.elements.zip(value.elements).foreach { case ((_, x), (_, value)) => + x.pokeInternal(value, allowPartial) + } + case (x: Vec[_], value: Vec[_]) => + require(DataMirror.checkTypeEquivalence(x, value), s"Vec type mismatch") + x.getElements.zip(value.getElements).foreach { case (x, value) => + x.pokeInternal(value, allowPartial) + } + case (x: EnumType, value: EnumType) => + require(DataMirror.checkTypeEquivalence(x, value), s"EnumType mismatch") + pokeBits(x, value.litValue) + case x => throw new LiteralTypeException(s"don't know how to poke $x") + // TODO: aggregate types + } } def peek(): T = x match { @@ -322,31 +299,41 @@ package object chiseltest { case x => throw new LiteralTypeException(s"don't know how to peek $x") } - protected def expectInternal(value: T, message: Option[() => String]): Unit = (x, value) match { - case (x: Bool, value: Bool) => x.expectInternal(value.litValue, message) - case (x: UInt, value: UInt) => x.expectInternal(value.litValue, message) - case (x: SInt, value: SInt) => x.expectInternal(value.litValue, message) - case (x: FixedPoint, value: FixedPoint) => x.expectInternal(value, message) - case (x: Interval, value: Interval) => x.expectInternal(value, message) - case (x: Record, value: Record) => - require(DataMirror.checkTypeEquivalence(x, value), s"Record type mismatch") - x.elements.zip(value.elements).foreach { case ((_, x), (_, value)) => - x.expectInternal(value, message) - } - case (x: Vec[_], value: Vec[_]) => - require(DataMirror.checkTypeEquivalence(x, value), s"Vec type mismatch") - x.getElements.zip(value.getElements).foreach { case (x, value) => - x.expectInternal(value, message) - } - case (x: EnumType, value: EnumType) => - require(DataMirror.checkTypeEquivalence(x, value), s"EnumType mismatch") - Utils.expectBits(x, value.litValue, message, Some(enumToString(x))) - case x => throw new LiteralTypeException(s"don't know how to expect $x") - // TODO: aggregate types + private[chiseltest] def expectInternal(value: T, message: Option[() => String], allowPartial: Boolean): Unit = { + if (isAllowedNonLitGround(value, allowPartial, "expect")) return + (x, value) match { + case (x: Bool, value: Bool) => x.expectInternal(value.litValue, message) + case (x: UInt, value: UInt) => x.expectInternal(value.litValue, message) + case (x: SInt, value: SInt) => x.expectInternal(value.litValue, message) + case (x: FixedPoint, value: FixedPoint) => x.expectInternal(value, message) + case (x: Interval, value: Interval) => x.expectInternal(value, message) + case (x: Record, value: Record) => + require(DataMirror.checkTypeEquivalence(x, value), s"Record type mismatch") + x.elements.zip(value.elements).foreach { case ((_, x), (_, value)) => + x.expectInternal(value, message, allowPartial) + } + case (x: Vec[_], value: Vec[_]) => + require(DataMirror.checkTypeEquivalence(x, value), s"Vec type mismatch") + x.getElements.zip(value.getElements).zipWithIndex.foreach { case ((subX, value), index) => + value match { + case DontCare => + throw new RuntimeException( + s"Vec $x needs to be fully specified when using expect. Index $index is missing." + + "Maybe try using `expectPartial` if you only want to check for some elements." + ) + case other => subX.expectInternal(other, message, allowPartial) + } + } + case (x: EnumType, value: EnumType) => + require(DataMirror.checkTypeEquivalence(x, value), s"EnumType mismatch") + Utils.expectBits(x, value.litValue, message, Some(enumToString(x))) + case x => throw new LiteralTypeException(s"don't know how to expect $x") + // TODO: aggregate types + } } - def expect(value: T): Unit = expectInternal(value, None) - def expect(value: T, message: => String): Unit = expectInternal(value, Some(() => message)) + def expect(value: T): Unit = expectInternal(value, None, allowPartial = false) + def expect(value: T, message: => String): Unit = expectInternal(value, Some(() => message), allowPartial = false) /** @return the single clock that drives the source of this signal. * @throws ClockResolutionException if sources of this signal have more than one, or zero clocks diff --git a/src/test/scala/chiseltest/tests/PokeAndExpectPartialTests.scala b/src/test/scala/chiseltest/tests/PokeAndExpectPartialTests.scala new file mode 100644 index 000000000..a8de08588 --- /dev/null +++ b/src/test/scala/chiseltest/tests/PokeAndExpectPartialTests.scala @@ -0,0 +1,250 @@ +package chiseltest.tests + +import chisel3._ +import chisel3.experimental.BundleLiterals.AddBundleLiteralConstructor +import chisel3.experimental.VecLiterals.AddVecLiteralConstructor +import chiseltest._ +import org.scalatest.flatspec.AnyFlatSpec + +class PokeAndExpectPartialTests extends AnyFlatSpec with ChiselScalatestTester{ + behavior of "pokePartial" + + it should "work with a bundle of uint" in { + val typ = new CustomBundle("foo" -> UInt(32.W), "bar" -> UInt(32.W)) + test(new PassthroughModule(typ)) { c => + c.in.pokePartial(typ.Lit( + _.elements("foo") -> 4.U + )) + c.out.expectPartial(typ.Lit( + _.elements("foo") -> 4.U + )) + c.clock.step() + c.in.pokePartial(typ.Lit( + _.elements("bar") -> 5.U + )) + c.out.expect(typ.Lit( + _.elements("foo") -> 4.U, + _.elements("bar") -> 5.U + )) + } + } + + it should "work with a bundle of bundle" in { + val innerTyp = new CustomBundle("0" -> UInt(8.W), "1" -> UInt(17.W), "2" -> UInt(100.W)) + val typ = new CustomBundle("0" -> innerTyp, "1" -> innerTyp) + test(new PassthroughModule(typ)) { c => + c.in.pokePartial(typ.Lit( + _.elements("0") -> innerTyp.Lit( + // full inner bundle + _.elements("0") -> 4.U, + _.elements("1") -> 4.U, + _.elements("2") -> 4.U, + ))) + c.out.expectPartial(typ.Lit( + _.elements("0") -> innerTyp.Lit( + // full inner bundle + _.elements("0") -> 4.U, + _.elements("1") -> 4.U, + _.elements("2") -> 4.U, + ))) + c.clock.step() + c.in.pokePartial(typ.Lit( + _.elements("1") -> innerTyp.Lit( + // partial inner bundle + _.elements("0") -> 3.U, + _.elements("2") -> 3.U, + ))) + c.out.expectPartial(typ.Lit( + _.elements("1") -> innerTyp.Lit( + // partial inner bundle + _.elements("0") -> 3.U, + _.elements("2") -> 3.U, + ))) + c.clock.step() + c.in.pokePartial(typ.Lit( + _.elements("1") -> innerTyp.Lit( + // partial inner bundle + _.elements("0") -> 7.U, // partial overwrite! + _.elements("1") -> 7.U, + ))) + c.out.expect(typ.Lit( + _.elements("0") -> innerTyp.Lit( + _.elements("0") -> 4.U, + _.elements("1") -> 4.U, + _.elements("2") -> 4.U, + ), + _.elements("1") -> innerTyp.Lit( + _.elements("0") -> 7.U, + _.elements("1") -> 7.U, + _.elements("2") -> 3.U, + ), + )) + } + } + + it should "work with a vector of uint" in { + val typ = Vec(4, UInt(32.W)) + test(new PassthroughModule(typ)) { c => + c.in.pokePartial(typ.Lit( + 0 -> 4.U, + )) + c.out.expectPartial(typ.Lit( + 0 -> 4.U, + )) + c.clock.step() + c.in.pokePartial(typ.Lit( + 3 -> 5.U, + 2 -> 321.U, + 1 -> 123.U, + )) + c.out.expect(typ.Lit( + 0 -> 4.U, + 1 -> 123.U, + 2 -> 321.U, + 3 -> 5.U, + )) + c.clock.step() + c.in.pokePartial(typ.Lit( + 2 -> 444.U, + )) + c.out.expect(typ.Lit( + 0 -> 4.U, + 1 -> 123.U, + 2 -> 444.U, + 3 -> 5.U, + )) + } + } + + it should "work with a vector of vector" in { + val innerTyp = Vec(3, UInt(32.W)) + val typ = Vec(2, innerTyp) + test(new PassthroughModule(typ)) { c => + c.in.pokePartial(typ.Lit( + 0 -> innerTyp.Lit( + // full inner vector + 0 -> 4.U, + 1 -> 4.U, + 2 -> 4.U, + ))) + c.out.expectPartial(typ.Lit( + 0 -> innerTyp.Lit( + // full inner vector + 0 -> 4.U, + 1 -> 4.U, + 2 -> 4.U, + ))) + c.clock.step() + c.in.pokePartial(typ.Lit( + 1 -> innerTyp.Lit( + // partial inner vector + 0 -> 3.U, + 2 -> 3.U, + ))) + c.out.expectPartial(typ.Lit( + 1 -> innerTyp.Lit( + // partial inner vector + 0 -> 3.U, + 2 -> 3.U, + ))) + c.clock.step() + c.in.pokePartial(typ.Lit( + 1 -> innerTyp.Lit( + // partial inner vector + 0 -> 7.U, // partial overwrite! + 1 -> 7.U, + ))) + c.out.expect(typ.Lit( + 0 -> innerTyp.Lit( + 0 -> 4.U, + 1 -> 4.U, + 2 -> 4.U, + ), + 1 -> innerTyp.Lit( + 0 -> 7.U, + 1 -> 7.U, + 2 -> 3.U, + ), + )) + } + } + + it should "work with a vector of bundle" in { + val innerTyp = new CustomBundle("0" -> UInt(8.W), "1" -> UInt(17.W), "2" -> UInt(100.W)) + val typ = Vec(2, innerTyp) + test(new PassthroughModule(typ)) { c => + c.in.pokePartial(typ.Lit( + 0 -> innerTyp.Lit( + // full inner bundle + _.elements("0") -> 4.U, + _.elements("1") -> 4.U, + _.elements("2") -> 4.U, + ))) + c.out.expectPartial(typ.Lit( + 0 -> innerTyp.Lit( + // full inner bundle + _.elements("0") -> 4.U, + _.elements("1") -> 4.U, + _.elements("2") -> 4.U, + ))) + c.clock.step() + c.in.pokePartial(typ.Lit( + 1 -> innerTyp.Lit( + // partial inner bundle + _.elements("0") -> 3.U, + _.elements("2") -> 3.U, + ))) + c.out.expectPartial(typ.Lit( + 1 -> innerTyp.Lit( + // partial inner bundle + _.elements("0") -> 3.U, + _.elements("2") -> 3.U, + ))) + c.clock.step() + c.in.pokePartial(typ.Lit( + 1 -> innerTyp.Lit( + // partial inner bundle + _.elements("0") -> 7.U, // partial overwrite! + _.elements("1") -> 7.U, + ))) + c.out.expect(typ.Lit( + 0 -> innerTyp.Lit( + _.elements("0") -> 4.U, + _.elements("1") -> 4.U, + _.elements("2") -> 4.U, + ), + 1 -> innerTyp.Lit( + _.elements("0") -> 7.U, + _.elements("1") -> 7.U, + _.elements("2") -> 3.U, + ), + )) + } + } + + behavior of "poke" + + it should "provide a good error message when used with partial bundle literals" in { + val typ = new CustomBundle("foo" -> UInt(32.W), "bar" -> UInt(32.W)) + assertThrows[NonLiteralValueError] { + test(new PassthroughModule(typ)) { c => + c.in.poke(typ.Lit( + _.elements("foo") -> 123.U + )) + } + } + } + + it should "provide a good error message when used with partial vector literals" in { + val typ = Vec(4, UInt(32.W)) + assertThrows[NonLiteralValueError] { + test(new PassthroughModule(typ)) { c => + c.in.poke(typ.Lit( + 0 -> 4.U, + 3 -> 5.U + )) + } + } + } + +}