summaryrefslogtreecommitdiff
path: root/src/test
diff options
context:
space:
mode:
authorAdam Izraelevitz2019-08-12 15:49:42 -0700
committerGitHub2019-08-12 15:49:42 -0700
commitfddb5943b1d36925a5435d327c3312572e98ca58 (patch)
treeb22e3a544dbb265dead955544c75bf7abddb7c69 /src/test
parent466ffbc9ca4fcca73d56f849df9e2753f68c53a8 (diff)
Aspect-Oriented Programming for Chisel (#1077)
Added Aspects to Chisel, enabling a mechanism for dependency injection to hardware modules.
Diffstat (limited to 'src/test')
-rw-r--r--src/test/scala/chiselTests/ChiselSpec.scala36
-rw-r--r--src/test/scala/chiselTests/aop/InjectionSpec.scala58
-rw-r--r--src/test/scala/chiselTests/aop/SelectSpec.scala144
-rw-r--r--src/test/scala/chiselTests/stage/ChiselAnnotationsSpec.scala12
4 files changed, 230 insertions, 20 deletions
diff --git a/src/test/scala/chiselTests/ChiselSpec.scala b/src/test/scala/chiselTests/ChiselSpec.scala
index 5973cb63..75fa68dd 100644
--- a/src/test/scala/chiselTests/ChiselSpec.scala
+++ b/src/test/scala/chiselTests/ChiselSpec.scala
@@ -8,25 +8,29 @@ import org.scalacheck._
import chisel3._
import chisel3.experimental.RawModule
import chisel3.testers._
-import firrtl.{
- CommonOptions,
- ExecutionOptionsManager,
- HasFirrtlOptions,
- FirrtlExecutionSuccess,
- FirrtlExecutionFailure
-}
+import firrtl.options.OptionsException
+import firrtl.{AnnotationSeq, CommonOptions, ExecutionOptionsManager, FirrtlExecutionFailure, FirrtlExecutionSuccess, HasFirrtlOptions}
import firrtl.util.BackendCompilationUtilities
/** Common utility functions for Chisel unit tests. */
trait ChiselRunners extends Assertions with BackendCompilationUtilities {
- def runTester(t: => BasicTester, additionalVResources: Seq[String] = Seq()): Boolean = {
- TesterDriver.execute(() => t, additionalVResources)
+ def runTester(t: => BasicTester,
+ additionalVResources: Seq[String] = Seq(),
+ annotations: AnnotationSeq = Seq()
+ ): Boolean = {
+ TesterDriver.execute(() => t, additionalVResources, annotations)
}
- def assertTesterPasses(t: => BasicTester, additionalVResources: Seq[String] = Seq()): Unit = {
- assert(runTester(t, additionalVResources))
+ def assertTesterPasses(t: => BasicTester,
+ additionalVResources: Seq[String] = Seq(),
+ annotations: AnnotationSeq = Seq()
+ ): Unit = {
+ assert(runTester(t, additionalVResources, annotations))
}
- def assertTesterFails(t: => BasicTester, additionalVResources: Seq[String] = Seq()): Unit = {
- assert(!runTester(t, additionalVResources))
+ def assertTesterFails(t: => BasicTester,
+ additionalVResources: Seq[String] = Seq(),
+ annotations: Seq[chisel3.aop.Aspect[_]] = Seq()
+ ): Unit = {
+ assert(!runTester(t, additionalVResources, annotations))
}
def elaborate(t: => RawModule): Unit = Driver.elaborate(() => t)
@@ -95,11 +99,12 @@ class ChiselTestUtilitiesSpec extends ChiselFlatSpec {
import org.scalatest.exceptions.TestFailedException
// Who tests the testers?
"assertKnownWidth" should "error when the expected width is wrong" in {
- a [TestFailedException] shouldBe thrownBy {
+ val caught = intercept[OptionsException] {
assertKnownWidth(7) {
Wire(UInt(8.W))
}
}
+ assert(caught.getCause.isInstanceOf[TestFailedException])
}
it should "error when the width is unknown" in {
@@ -117,11 +122,12 @@ class ChiselTestUtilitiesSpec extends ChiselFlatSpec {
}
"assertInferredWidth" should "error if the width is known" in {
- a [TestFailedException] shouldBe thrownBy {
+ val caught = intercept[OptionsException] {
assertInferredWidth(8) {
Wire(UInt(8.W))
}
}
+ assert(caught.getCause.isInstanceOf[TestFailedException])
}
it should "error if the expected width is wrong" in {
diff --git a/src/test/scala/chiselTests/aop/InjectionSpec.scala b/src/test/scala/chiselTests/aop/InjectionSpec.scala
new file mode 100644
index 00000000..6c022d60
--- /dev/null
+++ b/src/test/scala/chiselTests/aop/InjectionSpec.scala
@@ -0,0 +1,58 @@
+// See LICENSE for license details.
+
+package chiselTests.aop
+
+import chisel3.testers.BasicTester
+import chiselTests.ChiselFlatSpec
+import chisel3._
+import chisel3.aop.injecting.InjectingAspect
+
+class AspectTester(results: Seq[Int]) extends BasicTester {
+ val values = VecInit(results.map(_.U))
+ val counter = RegInit(0.U(results.length.W))
+ counter := counter + 1.U
+ when(counter >= values.length.U) {
+ stop()
+ }.otherwise {
+ when(reset.asBool() === false.B) {
+ printf("values(%d) = %d\n", counter, values(counter))
+ assert(counter === values(counter))
+ }
+ }
+}
+
+class InjectionSpec extends ChiselFlatSpec {
+ val correctValueAspect = InjectingAspect(
+ {dut: AspectTester => Seq(dut)},
+ {dut: AspectTester =>
+ for(i <- 0 until dut.values.length) {
+ dut.values(i) := i.U
+ }
+ }
+ )
+
+ val wrongValueAspect = InjectingAspect(
+ {dut: AspectTester => Seq(dut)},
+ {dut: AspectTester =>
+ for(i <- 0 until dut.values.length) {
+ dut.values(i) := (i + 1).U
+ }
+ }
+ )
+
+ "Test" should "pass if inserted the correct values" in {
+ assertTesterPasses{ new AspectTester(Seq(0, 1, 2)) }
+ }
+ "Test" should "fail if inserted the wrong values" in {
+ assertTesterFails{ new AspectTester(Seq(9, 9, 9)) }
+ }
+ "Test" should "pass if pass wrong values, but correct with aspect" in {
+ assertTesterPasses({ new AspectTester(Seq(9, 9, 9))} , Nil, Seq(correctValueAspect))
+ }
+ "Test" should "pass if pass wrong values, then wrong aspect, then correct aspect" in {
+ assertTesterPasses({ new AspectTester(Seq(9, 9, 9))} , Nil, Seq(wrongValueAspect, correctValueAspect))
+ }
+ "Test" should "fail if pass wrong values, then correct aspect, then wrong aspect" in {
+ assertTesterFails({ new AspectTester(Seq(9, 9, 9))} , Nil, Seq(correctValueAspect, wrongValueAspect))
+ }
+}
diff --git a/src/test/scala/chiselTests/aop/SelectSpec.scala b/src/test/scala/chiselTests/aop/SelectSpec.scala
new file mode 100644
index 00000000..d3f72551
--- /dev/null
+++ b/src/test/scala/chiselTests/aop/SelectSpec.scala
@@ -0,0 +1,144 @@
+// See LICENSE for license details.
+
+package chiselTests.aop
+
+import chisel3.testers.BasicTester
+import chiselTests.ChiselFlatSpec
+import chisel3._
+import chisel3.aop.Select.{PredicatedConnect, When, WhenNot}
+import chisel3.aop.{Aspect, Select}
+import chisel3.experimental.RawModule
+import firrtl.{AnnotationSeq}
+
+import scala.reflect.runtime.universe.TypeTag
+
+class SelectTester(results: Seq[Int]) extends BasicTester {
+ val values = VecInit(results.map(_.U))
+ val counter = RegInit(0.U(results.length.W))
+ val added = counter + 1.U
+ counter := added
+ val overflow = counter >= values.length.U
+ val nreset = reset.asBool() === false.B
+ val selected = values(counter)
+ val zero = 0.U + 0.U
+ when(overflow) {
+ counter := zero
+ stop()
+ }.otherwise {
+ when(nreset) {
+ assert(counter === values(counter))
+ printf("values(%d) = %d\n", counter, selected)
+ }
+ }
+}
+
+case class SelectAspect[T <: RawModule, X](selector: T => Seq[X], desired: T => Seq[X])(implicit tTag: TypeTag[T]) extends Aspect[T] {
+ override def toAnnotation(top: T): AnnotationSeq = {
+ val results = selector(top)
+ val desiredSeq = desired(top)
+ assert(results.length == desiredSeq.length, s"Failure! Results $results have different length than desired $desiredSeq!")
+ val mismatches = results.zip(desiredSeq).flatMap {
+ case (res, des) if res != des => Seq((res, des))
+ case other => Nil
+ }
+ assert(mismatches.isEmpty,s"Failure! The following selected items do not match their desired item:\n" + mismatches.map{
+ case (res: Select.Serializeable, des: Select.Serializeable) => s" ${res.serialize} does not match:\n ${des.serialize}"
+ case (res, des) => s" $res does not match:\n $des"
+ }.mkString("\n"))
+ Nil
+ }
+}
+
+class SelectSpec extends ChiselFlatSpec {
+
+ def execute[T <: RawModule, X](dut: () => T, selector: T => Seq[X], desired: T => Seq[X])(implicit tTag: TypeTag[T]): Unit = {
+ val ret = new chisel3.stage.ChiselStage().run(
+ Seq(
+ new chisel3.stage.ChiselGeneratorAnnotation(dut),
+ SelectAspect(selector, desired),
+ new chisel3.stage.ChiselOutputFileAnnotation("test_run_dir/Select.fir")
+ )
+ )
+ }
+
+ "Test" should "pass if selecting correct registers" in {
+ execute(
+ () => new SelectTester(Seq(0, 1, 2)),
+ { dut: SelectTester => Select.registers(dut) },
+ { dut: SelectTester => Seq(dut.counter) }
+ )
+ }
+
+ "Test" should "pass if selecting correct wires" in {
+ execute(
+ () => new SelectTester(Seq(0, 1, 2)),
+ { dut: SelectTester => Select.wires(dut) },
+ { dut: SelectTester => Seq(dut.values) }
+ )
+ }
+
+ "Test" should "pass if selecting correct printfs" in {
+ execute(
+ () => new SelectTester(Seq(0, 1, 2)),
+ { dut: SelectTester => Seq(Select.printfs(dut).last) },
+ { dut: SelectTester =>
+ Seq(Select.Printf(
+ Seq(
+ When(Select.ops("eq")(dut).last.asInstanceOf[Bool]),
+ When(dut.nreset),
+ WhenNot(dut.overflow)
+ ),
+ Printable.pack("values(%d) = %d\n", dut.counter, dut.selected),
+ dut.clock
+ ))
+ }
+ )
+ }
+
+ "Test" should "pass if selecting correct connections" in {
+ execute(
+ () => new SelectTester(Seq(0, 1, 2)),
+ { dut: SelectTester => Select.connectionsTo(dut)(dut.counter) },
+ { dut: SelectTester =>
+ Seq(PredicatedConnect(Nil, dut.counter, dut.added, false),
+ PredicatedConnect(Seq(When(dut.overflow)), dut.counter, dut.zero, false))
+ }
+ )
+ }
+
+ "Test" should "pass if selecting ops by kind" in {
+ execute(
+ () => new SelectTester(Seq(0, 1, 2)),
+ { dut: SelectTester => Select.ops("tail")(dut) },
+ { dut: SelectTester => Seq(dut.added, dut.zero) }
+ )
+ }
+
+ "Test" should "pass if selecting ops" in {
+ execute(
+ () => new SelectTester(Seq(0, 1, 2)),
+ { dut: SelectTester => Select.ops(dut).collect { case ("tail", d) => d} },
+ { dut: SelectTester => Seq(dut.added, dut.zero) }
+ )
+ }
+
+ "Test" should "pass if selecting correct stops" in {
+ execute(
+ () => new SelectTester(Seq(0, 1, 2)),
+ { dut: SelectTester => Seq(Select.stops(dut).last) },
+ { dut: SelectTester =>
+ Seq(Select.Stop(
+ Seq(
+ When(Select.ops("eq")(dut).dropRight(1).last.asInstanceOf[Bool]),
+ When(dut.nreset),
+ WhenNot(dut.overflow)
+ ),
+ 1,
+ dut.clock
+ ))
+ }
+ )
+ }
+
+}
+
diff --git a/src/test/scala/chiselTests/stage/ChiselAnnotationsSpec.scala b/src/test/scala/chiselTests/stage/ChiselAnnotationsSpec.scala
index c89955f2..63b1001f 100644
--- a/src/test/scala/chiselTests/stage/ChiselAnnotationsSpec.scala
+++ b/src/test/scala/chiselTests/stage/ChiselAnnotationsSpec.scala
@@ -3,11 +3,9 @@
package chiselTests.stage
import org.scalatest.{FlatSpec, Matchers}
-
import chisel3._
-import chisel3.stage.{ChiselCircuitAnnotation, ChiselGeneratorAnnotation}
+import chisel3.stage.{ChiselCircuitAnnotation, ChiselGeneratorAnnotation, DesignAnnotation}
import chisel3.experimental.RawModule
-
import firrtl.options.OptionsException
class ChiselAnnotationsSpecFoo extends RawModule {
@@ -33,7 +31,9 @@ class ChiselAnnotationsSpec extends FlatSpec with Matchers {
it should "elaborate to a ChiselCircuitAnnotation" in {
val annotation = ChiselGeneratorAnnotation(() => new ChiselAnnotationsSpecFoo)
- annotation.elaborate shouldBe a [ChiselCircuitAnnotation]
+ val res = annotation.elaborate
+ res(0) shouldBe a [ChiselCircuitAnnotation]
+ res(1) shouldBe a [DesignAnnotation[ChiselAnnotationsSpecFoo]]
}
it should "throw an exception if elaboration fails" in {
@@ -45,7 +45,9 @@ class ChiselAnnotationsSpec extends FlatSpec with Matchers {
it should "elaborate from a String" in {
val annotation = ChiselGeneratorAnnotation("chiselTests.stage.ChiselAnnotationsSpecFoo")
- annotation.elaborate shouldBe a [ChiselCircuitAnnotation]
+ val res = annotation.elaborate
+ res(0) shouldBe a [ChiselCircuitAnnotation]
+ res(1) shouldBe a [DesignAnnotation[ChiselAnnotationsSpecFoo]]
}
it should "throw an exception if elaboration from a String refers to nonexistant class" in {