From 0ed5eb48cdb916b644aaf9e5dbf48f6cfb6c60f4 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Wed, 7 Sep 2016 14:18:29 -0700 Subject: Add Printable (#270) Printable is a new type that changes how printing of Chisel types is represented It uses an ordered collection rather than a format string and specifiers Features: - Custom String Interpolator for Scala-like printf - String-like manipulation of "hardware strings" for custom pretty-printing - Default pretty-printing for Chisel data types--- src/test/scala/chiselTests/PrintableSpec.scala | 162 +++++++++++++++++++++++++ src/test/scala/chiselTests/Printf.scala | 9 ++ 2 files changed, 171 insertions(+) create mode 100644 src/test/scala/chiselTests/PrintableSpec.scala (limited to 'src/test') diff --git a/src/test/scala/chiselTests/PrintableSpec.scala b/src/test/scala/chiselTests/PrintableSpec.scala new file mode 100644 index 00000000..a2c8c62a --- /dev/null +++ b/src/test/scala/chiselTests/PrintableSpec.scala @@ -0,0 +1,162 @@ +package chiselTests + +import org.scalatest.{FlatSpec, Matchers} +import scala.collection.mutable + +import chisel3._ +import chisel3.testers.BasicTester + +/* Printable Tests */ +class PrintableSpec extends FlatSpec with Matchers { + private val PrintfRegex = """\s*printf\((.*)\).*""".r + // This regex is brittle, it relies on the first two arguments of the printf + // not containing quotes, problematic if Chisel were to emit UInt<1>("h01") + // instead of the current UInt<1>(1) for the enable signal + private val StringRegex = """([^"]*)"(.*?)"(.*)""".r + private case class Printf(str: String, args: Seq[String]) + private def getPrintfs(firrtl: String): Seq[Printf] = { + def processArgs(str: String): Seq[String] = + str split "," map (_.trim) filter (_.nonEmpty) + def processBody(str: String): (String, Seq[String]) = { + str match { + case StringRegex(_, fmt, args) => + (fmt, processArgs(args)) + case _ => fail(s"Regex to process Printf should work on $str!") + } + } + + firrtl split "\n" collect { + case PrintfRegex(matched) => + val (str, args) = processBody(matched) + Printf(str, args) + } + } + + behavior of "Printable & Custom Interpolator" + + it should "pass exact strings through" in { + class MyModule extends BasicTester { + printf(p"An exact string") + } + val firrtl = Driver.emit(() => new MyModule) + getPrintfs(firrtl) match { + case Seq(Printf("An exact string", Seq())) => + case e => fail() + } + } + it should "handle Printable and String concatination" in { + class MyModule extends BasicTester { + printf(p"First " + PString("Second ") + "Third") + } + val firrtl = Driver.emit(() => new MyModule) + getPrintfs(firrtl) match { + case Seq(Printf("First Second Third", Seq())) => + case e => fail() + } + } + it should "call toString on non-Printable objects" in { + class MyModule extends BasicTester { + val myInt = 1234 + printf(p"myInt = $myInt") + } + val firrtl = Driver.emit(() => new MyModule) + getPrintfs(firrtl) match { + case Seq(Printf("myInt = 1234", Seq())) => + case e => fail() + } + } + it should "generate proper printf for simple Decimal printing" in { + class MyModule extends BasicTester { + val myWire = Wire(init = UInt(1234)) + printf(p"myWire = ${Decimal(myWire)}") + } + val firrtl = Driver.emit(() => new MyModule) + getPrintfs(firrtl) match { + case Seq(Printf("myWire = %d", Seq("myWire"))) => + case e => fail() + } + } + it should "handle printing literals" in { + class MyModule extends BasicTester { + printf(Decimal(UInt(10, 32))) + } + val firrtl = Driver.emit(() => new MyModule) + getPrintfs(firrtl) match { + case Seq(Printf("%d", Seq(lit))) => + assert(lit contains "UInt<32>") + case e => fail() + } + } + it should "correctly escape percent" in { + class MyModule extends BasicTester { + printf(p"%") + } + val firrtl = Driver.emit(() => new MyModule) + getPrintfs(firrtl) match { + case Seq(Printf("%%", Seq())) => + case e => fail() + } + } + it should "support names of circuit elements and the current module" in { + class MyBundle extends Bundle { + val foo = UInt(width = 32) + override def cloneType = (new MyBundle).asInstanceOf[this.type] + } + class MyModule extends BasicTester { + override def desiredName = "MyModule" + val myWire = Wire(new MyBundle) + printf(p"${Name(myWire.foo)}") + printf(p"${FullName(myWire.foo)}") + printf(p"${FullName(this)}") + } + val firrtl = Driver.emit(() => new MyModule) + getPrintfs(firrtl) match { + case Seq(Printf("foo", Seq()), + Printf("myWire.foo", Seq()), + Printf("MyModule", Seq())) => + case e => fail() + } + } + it should "print UInts and SInts as Decimal by default" in { + class MyModule extends BasicTester { + val myUInt = Wire(init = UInt(0)) + val mySInt = Wire(init = SInt(-1)) + printf(p"$myUInt & $mySInt") + } + val firrtl = Driver.emit(() => new MyModule) + getPrintfs(firrtl) match { + case Seq(Printf("%d & %d", Seq("myUInt", "mySInt"))) => + case e => fail() + } + } + it should "print Vecs like Scala Seqs by default" in { + class MyModule extends BasicTester { + val myVec = Wire(Vec(4, UInt(width = 32))) + myVec foreach (_ := UInt(0)) + printf(p"$myVec") + } + val firrtl = Driver.emit(() => new MyModule) + getPrintfs(firrtl) match { + case Seq(Printf("Vec(%d, %d, %d, %d)", + Seq("myVec[0]", "myVec[1]", "myVec[2]", "myVec[3]"))) => + case e => fail() + } + } + it should "print Bundles like Scala Maps by default" in { + class MyModule extends BasicTester { + val myBun = Wire(new Bundle { + val foo = UInt(width = 32) + val bar = UInt(width = 32) + }) + myBun.foo := UInt(0) + myBun.bar := UInt(0) + printf(p"$myBun") + } + val firrtl = Driver.emit(() => new MyModule) + getPrintfs(firrtl) match { + case Seq(Printf("Bundle(foo -> %d, bar -> %d)", + Seq("myBun.foo", "myBun.bar"))) => + case e => fail() + } + } +} diff --git a/src/test/scala/chiselTests/Printf.scala b/src/test/scala/chiselTests/Printf.scala index c872fde4..28b6132b 100644 --- a/src/test/scala/chiselTests/Printf.scala +++ b/src/test/scala/chiselTests/Printf.scala @@ -4,6 +4,7 @@ package chiselTests import org.scalatest._ import chisel3._ +import chisel3.util._ import chisel3.testers.BasicTester class SinglePrintfTester() extends BasicTester { @@ -24,6 +25,11 @@ class MultiPrintfTester() extends BasicTester { stop() } +class ASCIIPrintableTester extends BasicTester { + printf(PString((0x20 to 0x7e) map (_.toChar) mkString "")) + stop() +} + class PrintfSpec extends ChiselFlatSpec { "A printf with a single argument" should "run" in { assertTesterPasses { new SinglePrintfTester } @@ -34,4 +40,7 @@ class PrintfSpec extends ChiselFlatSpec { "A printf with ASCII characters 1-127" should "run" in { assertTesterPasses { new ASCIIPrintfTester } } + "A printf with Printable ASCII characters 1-127" should "run" in { + assertTesterPasses { new ASCIIPrintableTester } + } } -- cgit v1.2.3 From f793453ba6c4c42ef61eda3af8f04f7cadf80b95 Mon Sep 17 00:00:00 2001 From: jackkoenig Date: Wed, 7 Sep 2016 17:51:30 -0700 Subject: Fix bug in Printable FullName of submodule port Printable was using HasId.instanceName to get full names of Chisel nodes. instanceName uses the parent module of the HasId to get the Component to use in calling fullName on the underlying Ref. Unfortunately this means that any reference to a port of a instance will leave off the instance name. Fixing this required the following: - Add Component argument to Printable.unpack so that we can call Arg.fullName directly in the Printable - Pass the currently emitting module as the Component to Printable.unpack in the Emitter - Remove ability to create FullName Printables from Modules since the Module name is not known until after the printf is already emitted This commit also updates the PrintableSpec test to check that FullName and Decimal printing work on ports of instances --- src/test/scala/chiselTests/PrintableSpec.scala | 31 +++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) (limited to 'src/test') diff --git a/src/test/scala/chiselTests/PrintableSpec.scala b/src/test/scala/chiselTests/PrintableSpec.scala index a2c8c62a..12564a40 100644 --- a/src/test/scala/chiselTests/PrintableSpec.scala +++ b/src/test/scala/chiselTests/PrintableSpec.scala @@ -97,7 +97,14 @@ class PrintableSpec extends FlatSpec with Matchers { case e => fail() } } - it should "support names of circuit elements and the current module" in { + it should "support names of circuit elements including submodule IO" in { + // Submodule IO is a subtle issue because the Chisel element has a different + // parent module + class MySubModule extends Module { + val io = new Bundle { + val fizz = UInt(width = 32) + } + } class MyBundle extends Bundle { val foo = UInt(width = 32) override def cloneType = (new MyBundle).asInstanceOf[this.type] @@ -105,15 +112,33 @@ class PrintableSpec extends FlatSpec with Matchers { class MyModule extends BasicTester { override def desiredName = "MyModule" val myWire = Wire(new MyBundle) + val myInst = Module(new MySubModule) printf(p"${Name(myWire.foo)}") printf(p"${FullName(myWire.foo)}") - printf(p"${FullName(this)}") + printf(p"${FullName(myInst.io.fizz)}") } val firrtl = Driver.emit(() => new MyModule) + println(firrtl) getPrintfs(firrtl) match { case Seq(Printf("foo", Seq()), Printf("myWire.foo", Seq()), - Printf("MyModule", Seq())) => + Printf("myInst.io.fizz", Seq())) => + case e => fail() + } + } + it should "handle printing ports of submodules" in { + class MySubModule extends Module { + val io = new Bundle { + val fizz = UInt(width = 32) + } + } + class MyModule extends BasicTester { + val myInst = Module(new MySubModule) + printf(p"${myInst.io.fizz}") + } + val firrtl = Driver.emit(() => new MyModule) + getPrintfs(firrtl) match { + case Seq(Printf("%d", Seq("myInst.io.fizz"))) => case e => fail() } } -- cgit v1.2.3