diff options
| author | Adam Izraelevitz | 2019-08-12 15:49:42 -0700 |
|---|---|---|
| committer | GitHub | 2019-08-12 15:49:42 -0700 |
| commit | fddb5943b1d36925a5435d327c3312572e98ca58 (patch) | |
| tree | b22e3a544dbb265dead955544c75bf7abddb7c69 /src/test | |
| parent | 466ffbc9ca4fcca73d56f849df9e2753f68c53a8 (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.scala | 36 | ||||
| -rw-r--r-- | src/test/scala/chiselTests/aop/InjectionSpec.scala | 58 | ||||
| -rw-r--r-- | src/test/scala/chiselTests/aop/SelectSpec.scala | 144 | ||||
| -rw-r--r-- | src/test/scala/chiselTests/stage/ChiselAnnotationsSpec.scala | 12 |
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 { |
