From fa395b4c22f75d66047c1e835413beb612bc31d3 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Wed, 25 Jan 2017 21:02:04 -0800 Subject: Better name propagation by macros (#327) * Name propagation * chiselName everywhere at best-effort level * Better collision handling * Allow recursing into inner anonymous functions * Add for loop and anonymous inner function tests --- src/main/scala/chisel3/compatibility.scala | 27 +++ src/main/scala/chisel3/package.scala | 17 ++ .../scala/chiselTests/NamingAnnotationTest.scala | 200 +++++++++++++++++++++ 3 files changed, 244 insertions(+) create mode 100644 src/test/scala/chiselTests/NamingAnnotationTest.scala (limited to 'src') diff --git a/src/main/scala/chisel3/compatibility.scala b/src/main/scala/chisel3/compatibility.scala index abbe8ffe..a919e3b9 100644 --- a/src/main/scala/chisel3/compatibility.scala +++ b/src/main/scala/chisel3/compatibility.scala @@ -6,6 +6,10 @@ package object Chisel { // scalastyle:ignore package.object.name import chisel3.internal.firrtl.Width + import scala.language.experimental.macros + import scala.annotation.StaticAnnotation + import scala.annotation.compileTimeOnly + implicit val defaultCompileOptions = chisel3.core.ExplicitCompileOptions.NotStrict type Direction = chisel3.core.Direction @@ -333,4 +337,27 @@ package object Chisel { // scalastyle:ignore package.object.name val Pipe = chisel3.util.Pipe type Pipe[T <: Data] = chisel3.util.Pipe[T] + + /** Package for experimental features, which may have their API changed, be removed, etc. + * + * Because its contents won't necessarily have the same level of stability and support as + * non-experimental, you must explicitly import this package to use its contents. + */ + object experimental { + import scala.annotation.StaticAnnotation + import scala.annotation.compileTimeOnly + + @compileTimeOnly("enable macro paradise to expand macro annotations") + class dump extends StaticAnnotation { + def macroTransform(annottees: Any*): Any = macro chisel3.internal.naming.DebugTransforms.dump + } + @compileTimeOnly("enable macro paradise to expand macro annotations") + class treedump extends StaticAnnotation { + def macroTransform(annottees: Any*): Any = macro chisel3.internal.naming.DebugTransforms.treedump + } + @compileTimeOnly("enable macro paradise to expand macro annotations") + class chiselName extends StaticAnnotation { + def macroTransform(annottees: Any*): Any = macro chisel3.internal.naming.NamingTransforms.chiselName + } + } } diff --git a/src/main/scala/chisel3/package.scala b/src/main/scala/chisel3/package.scala index cba8dffe..109bd14e 100644 --- a/src/main/scala/chisel3/package.scala +++ b/src/main/scala/chisel3/package.scala @@ -285,5 +285,22 @@ package object chisel3 { // scalastyle:ignore package.object.name */ def range(args: Any*): (NumericBound[Int], NumericBound[Int]) = macro chisel3.internal.RangeTransform.apply } + + import scala.language.experimental.macros + import scala.annotation.StaticAnnotation + import scala.annotation.compileTimeOnly + + @compileTimeOnly("enable macro paradise to expand macro annotations") + class dump extends StaticAnnotation { + def macroTransform(annottees: Any*): Any = macro chisel3.internal.naming.DebugTransforms.dump + } + @compileTimeOnly("enable macro paradise to expand macro annotations") + class treedump extends StaticAnnotation { + def macroTransform(annottees: Any*): Any = macro chisel3.internal.naming.DebugTransforms.treedump + } + @compileTimeOnly("enable macro paradise to expand macro annotations") + class chiselName extends StaticAnnotation { + def macroTransform(annottees: Any*): Any = macro chisel3.internal.naming.NamingTransforms.chiselName + } } } diff --git a/src/test/scala/chiselTests/NamingAnnotationTest.scala b/src/test/scala/chiselTests/NamingAnnotationTest.scala new file mode 100644 index 00000000..7b05d338 --- /dev/null +++ b/src/test/scala/chiselTests/NamingAnnotationTest.scala @@ -0,0 +1,200 @@ +// See LICENSE for license details. + +package chiselTests + +import chisel3._ +import chisel3.experimental.{chiselName, dump} +import org.scalatest._ +import org.scalatest.prop._ +import chisel3.testers.BasicTester + +import scala.collection.mutable.ListBuffer + +trait NamedModuleTester extends Module { + val io = IO(new Bundle()) // Named module testers don't need IO + + val expectedNameMap = ListBuffer[(Data, String)]() + + /** Expects some name for a node that is propagated to FIRRTL. + * The node is returned allowing this to be called inline. + */ + def expectName[T <: Data](node: T, fullName: String): T = { + expectedNameMap += ((node, fullName)) + node + } + + /** After this module has been elaborated, returns a list of (node, expected name, actual name) + * that did not match expectations. + * Returns an empty list if everything was fine. + */ + def getNameFailures(): List[(Data, String, String)] = { + val failures = ListBuffer[(Data, String, String)]() + for ((ref, expectedName) <- expectedNameMap) { + if (ref.instanceName != expectedName) { + failures += ((ref, expectedName, ref.instanceName)) + } + } + failures.toList + } +} + +@chiselName +class NamedModule extends NamedModuleTester { + @chiselName + def FunctionMockupInner(): UInt = { + val my2A = 1.U + val my2B = expectName(my2A +& 2.U, "test_myNested_my2B") + val my2C = my2B +& 3.U // should get named at enclosing scope + my2C + } + + @chiselName + def FunctionMockup(): UInt = { + val myNested = expectName(FunctionMockupInner(), "test_myNested") + val myA = expectName(1.U + myNested, "test_myA") + val myB = expectName(myA +& 2.U, "test_myB") + val myC = expectName(myB +& 3.U, "test_myC") + myC +& 4.U // named at enclosing scope + } + + // chiselName "implicitly" applied + def ImplicitlyNamed(): UInt = { + val implicitA = expectName(1.U + 2.U, "test3_implicitA") + val implicitB = expectName(implicitA + 3.U, "test3_implicitB") + implicitB + 2.U // named at enclosing scope + } + + // Ensure this applies a partial name if there is no return value + def NoReturnFunction() { + val noreturn = expectName(1.U + 2.U, "noreturn") + } + + + val test = expectName(FunctionMockup(), "test") + val test2 = expectName(test +& 2.U, "test2") + val test3 = expectName(ImplicitlyNamed(), "test3") + + // Test that contents of for loops are named + for (i <- 0 until 1) { + val forInner = expectName(test3 + i.U, "forInner") + } + + // Test that contents of anonymous functions are named + Seq((0, "anonInner"), (1, "anonInner_1"), (2, "anonInner_2")).foreach { case (in, name) => + val anonInner = expectName(test3 + in.U, name) + } + + NoReturnFunction() +} + +@chiselName +class NameCollisionModule extends NamedModuleTester { + @chiselName + def repeatedCalls(id: Int): UInt = { + val test = expectName(1.U + 3.U, s"test_$id") // should disambiguate by invocation order + test + 2.U + } + + // chiselName applied by default to this + def innerNamedFunction() { + // ... but not this inner function + def innerUnnamedFunction() { + val a = repeatedCalls(1) + val b = repeatedCalls(2) + } + + innerUnnamedFunction() + } + + val test = expectName(1.U + 2.U, "test") + innerNamedFunction() +} + +/** Ensure no crash happens if a named function is enclosed in a non-named module + */ +class NonNamedModule extends NamedModuleTester { + @chiselName + def NamedFunction(): UInt = { + val myVal = 1.U + 2.U + myVal + } + + val test = NamedFunction() +} + +/** Ensure no crash happens if a named function is enclosed in a non-named function in a named + * module. + */ +object NonNamedHelper { + @chiselName + def NamedFunction(): UInt = { + val myVal = 1.U + 2.U + myVal + } + + def NonNamedFunction() : UInt = { + val myVal = NamedFunction() + myVal + } +} + +@chiselName +class NonNamedFunction extends NamedModuleTester { + val test = NonNamedHelper.NamedFunction() +} + +/** Ensure broken links in the chain are simply dropped + */ +@chiselName +class PartialNamedModule extends NamedModuleTester { + // Create an inner function that is the extent of the implicit naming + def innerNamedFunction(): UInt = { + def innerUnnamedFunction(): UInt = { + @chiselName + def disconnectedNamedFunction(): UInt = { + val a = expectName(1.U + 2.U, "test_a") + val b = expectName(a + 2.U, "test_b") + b + } + disconnectedNamedFunction() + } + innerUnnamedFunction() + 1.U + } + + val test = innerNamedFunction() +} + + +/** A simple test that checks the recursive function val naming annotation both compiles and + * generates the expected names. + */ +class NamingAnnotationSpec extends ChiselPropSpec { + property("NamedModule should have function hierarchical names") { + // TODO: clean up test style + var module: NamedModule = null + elaborate { module = new NamedModule; module } + assert(module.getNameFailures() == Nil) + } + + property("NameCollisionModule should disambiguate collisions") { + // TODO: clean up test style + var module: NameCollisionModule = null + elaborate { module = new NameCollisionModule; module } + assert(module.getNameFailures() == Nil) + } + + property("PartialNamedModule should have partial names") { + // TODO: clean up test style + var module: PartialNamedModule = null + elaborate { module = new PartialNamedModule; module } + assert(module.getNameFailures() == Nil) + } + + property("NonNamedModule should elaborate") { + elaborate { new NonNamedModule } + } + + property("NonNamedFunction should elaborate") { + elaborate { new NonNamedFunction } + } +} -- cgit v1.2.3