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--- .../scala/chisel3/internal/firrtl/Emitter.scala | 6 +- src/main/scala/chisel3/package.scala | 51 +++++++ src/test/scala/chiselTests/PrintableSpec.scala | 162 +++++++++++++++++++++ src/test/scala/chiselTests/Printf.scala | 9 ++ 4 files changed, 227 insertions(+), 1 deletion(-) create mode 100644 src/test/scala/chiselTests/PrintableSpec.scala (limited to 'src') diff --git a/src/main/scala/chisel3/internal/firrtl/Emitter.scala b/src/main/scala/chisel3/internal/firrtl/Emitter.scala index 8b94c68f..8ace27f9 100644 --- a/src/main/scala/chisel3/internal/firrtl/Emitter.scala +++ b/src/main/scala/chisel3/internal/firrtl/Emitter.scala @@ -25,7 +25,11 @@ private class Emitter(circuit: Circuit) { case e: Connect => s"${e.loc.fullName(ctx)} <= ${e.exp.fullName(ctx)}" case e: BulkConnect => s"${e.loc1.fullName(ctx)} <- ${e.loc2.fullName(ctx)}" case e: Stop => s"stop(${e.clk.fullName(ctx)}, UInt<1>(1), ${e.ret})" - case e: Printf => s"""printf(${e.clk.fullName(ctx)}, UInt<1>(1), "${e.format}"${e.ids.map(_.fullName(ctx)).fold(""){_ + ", " + _}})""" + case e: Printf => + val (fmt, args) = e.pable.unpack + val printfArgs = Seq(e.clk.fullName(ctx), "UInt<1>(1)", + "\"" + printf.format(fmt) + "\"") ++ args + printfArgs mkString ("printf(", ", ", ")") case e: DefInvalid => s"${e.arg.fullName(ctx)} is invalid" case e: DefInstance => s"inst ${e.name} of ${e.id.modName}" case w: WhenBegin => diff --git a/src/main/scala/chisel3/package.scala b/src/main/scala/chisel3/package.scala index 0b548683..30e2b5c3 100644 --- a/src/main/scala/chisel3/package.scala +++ b/src/main/scala/chisel3/package.scala @@ -54,6 +54,27 @@ package object chisel3 { val when = chisel3.core.when type WhenContext = chisel3.core.WhenContext + type Printable = chisel3.core.Printable + val Printable = chisel3.core.Printable + type Printables = chisel3.core.Printables + val Printables = chisel3.core.Printables + type PString = chisel3.core.PString + val PString = chisel3.core.PString + type FirrtlFormat = chisel3.core.FirrtlFormat + val FirrtlFormat = chisel3.core.FirrtlFormat + type Decimal = chisel3.core.Decimal + val Decimal = chisel3.core.Decimal + type Hexadecimal = chisel3.core.Hexadecimal + val Hexadecimal = chisel3.core.Hexadecimal + type Binary = chisel3.core.Binary + val Binary = chisel3.core.Binary + type Character = chisel3.core.Character + val Character = chisel3.core.Character + type Name = chisel3.core.Name + val Name = chisel3.core.Name + type FullName = chisel3.core.FullName + val FullName = chisel3.core.FullName + val Percent = chisel3.core.Percent implicit class fromBigIntToLiteral(val x: BigInt) extends AnyVal { def U: UInt = UInt(x, Width()) @@ -79,4 +100,34 @@ package object chisel3 { def do_!= (that: BitPat)(implicit sourceInfo: SourceInfo): Bool = that != x def do_=/= (that: BitPat)(implicit sourceInfo: SourceInfo): Bool = that =/= x } + + /** Implicit for custom Printable string interpolator */ + implicit class PrintableHelper(val sc: StringContext) extends AnyVal { + /** Custom string interpolator for generating Printables: p"..." + * Will call .toString on any non-Printable arguments (mimicking s"...") + */ + def p(args: Any*): Printable = { + sc.checkLengths(args) // Enforce sc.parts.size == pargs.size + 1 + val pargs: Seq[Option[Printable]] = args map { + case p: Printable => Some(p) + case d: Data => Some(d.toPrintable) + case any => for { + v <- Option(any) // Handle null inputs + str = v.toString + if !str.isEmpty // Handle empty Strings + } yield PString(str) + } + val parts = sc.parts map StringContext.treatEscapes + // Zip sc.parts and pargs together ito flat Seq + // eg. Seq(sc.parts(0), pargs(0), sc.parts(1), pargs(1), ...) + val seq = for { // append None because sc.parts.size == pargs.size + 1 + (literal, arg) <- parts zip (pargs :+ None) + optPable <- Seq(Some(PString(literal)), arg) + pable <- optPable // Remove Option[_] + } yield pable + Printables(seq) + } + } + + implicit def string2Printable(str: String): Printable = PString(str) } 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 --- .../scala/chisel3/internal/firrtl/Emitter.scala | 2 +- src/test/scala/chiselTests/PrintableSpec.scala | 31 +++++++++++++++++++--- 2 files changed, 29 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/main/scala/chisel3/internal/firrtl/Emitter.scala b/src/main/scala/chisel3/internal/firrtl/Emitter.scala index 8ace27f9..8849077d 100644 --- a/src/main/scala/chisel3/internal/firrtl/Emitter.scala +++ b/src/main/scala/chisel3/internal/firrtl/Emitter.scala @@ -26,7 +26,7 @@ private class Emitter(circuit: Circuit) { case e: BulkConnect => s"${e.loc1.fullName(ctx)} <- ${e.loc2.fullName(ctx)}" case e: Stop => s"stop(${e.clk.fullName(ctx)}, UInt<1>(1), ${e.ret})" case e: Printf => - val (fmt, args) = e.pable.unpack + val (fmt, args) = e.pable.unpack(ctx) val printfArgs = Seq(e.clk.fullName(ctx), "UInt<1>(1)", "\"" + printf.format(fmt) + "\"") ++ args printfArgs mkString ("printf(", ", ", ")") 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 From bb240453abf96b4c2d75ebb2cdc7e3159068431d Mon Sep 17 00:00:00 2001 From: Henry Cook Date: Thu, 8 Sep 2016 13:02:22 -0700 Subject: Add IrrevocableIO alternative to DecoupledIO (#274) Add IrrevocableIO subclass of DecoupledIO that promises not to change .bits on a cycle after .valid is high and .ready is low--- src/main/scala/chisel3/util/Decoupled.scala | 99 ++++++++++++++++++++++++----- 1 file changed, 83 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/src/main/scala/chisel3/util/Decoupled.scala b/src/main/scala/chisel3/util/Decoupled.scala index f37a5c31..51f5bd6a 100644 --- a/src/main/scala/chisel3/util/Decoupled.scala +++ b/src/main/scala/chisel3/util/Decoupled.scala @@ -7,23 +7,68 @@ package chisel3.util import chisel3._ -/** An I/O Bundle with simple handshaking using valid and ready signals for data 'bits'*/ -class DecoupledIO[+T <: Data](gen: T) extends Bundle +/** An I/O Bundle containing 'valid' and 'ready' signals that handshake + * the transfer of data stored in the 'bits' subfield. + * The base protocol implied by the directionality is that the consumer + * uses the flipped interface. Actual semantics of ready/valid are + * enforced via use of concrete subclasses. + */ +abstract class ReadyValidIO[+T <: Data](gen: T) extends Bundle { val ready = Bool(INPUT) val valid = Bool(OUTPUT) val bits = gen.cloneType.asOutput def fire(dummy: Int = 0): Bool = ready && valid - override def cloneType: this.type = new DecoupledIO(gen).asInstanceOf[this.type] } -/** Adds a ready-valid handshaking protocol to any interface. - * The standard used is that the consumer uses the flipped interface. +/** A concrete subclass of ReadyValidIO signalling that the user expects a + * "decoupled" interface: 'valid' indicates that the producer has + * put valid data in 'bits', and 'ready' indicates that the consumer is ready + * to accept the data this cycle. No requirements are placed on the signalling + * of ready or valid. */ -object Decoupled { +class DecoupledIO[+T <: Data](gen: T) extends ReadyValidIO[T](gen) +{ + override def cloneType: this.type = new DecoupledIO(gen).asInstanceOf[this.type] +} + +/** This factory adds a decoupled handshaking protocol to a data bundle. */ +object Decoupled +{ + /** Take any Data and wrap it in a DecoupledIO interface */ def apply[T <: Data](gen: T): DecoupledIO[T] = new DecoupledIO(gen) + + /** Take an IrrevocableIO and cast it to a DecoupledIO. + * This cast is only safe to do in cases where the IrrevocableIO + * is being produced as an output. + */ + def apply[T <: Data](irr: IrrevocableIO[T]): DecoupledIO[T] = { + require(irr.bits.dir == OUTPUT, "Only safe to cast produced Irrevocable bits to Decoupled.") + val d = Wire(new DecoupledIO(irr.bits)) + d.bits := irr.bits + d.valid := irr.valid + irr.ready := d.ready + d + } +} + +/** A concrete subclass of ReadyValidIO that promises to not change + * the value of 'bits' after a cycle where 'valid' is high and 'ready' is low. + * Additionally, once 'valid' is raised it will never be lowered until after + * 'ready' has also been raised. + */ +class IrrevocableIO[+T <: Data](gen: T) extends ReadyValidIO[T](gen) +{ + override def cloneType: this.type = new IrrevocableIO(gen).asInstanceOf[this.type] +} + +/** Factory adds an irrevocable handshaking protocol to a data bundle. */ +object Irrevocable +{ + def apply[T <: Data](gen: T): IrrevocableIO[T] = new IrrevocableIO(gen) } + /** An I/O bundle for enqueuing data with valid/ready handshaking * Initialization must be handled, if necessary, by the parent circuit */ @@ -103,7 +148,8 @@ class QueueIO[T <: Data](gen: T, entries: Int) extends Bundle * q.io.enq <> producer.io.out * consumer.io.in <> q.io.deq }}} */ -class Queue[T <: Data](gen: T, val entries: Int, +class Queue[T <: Data](gen: T, + val entries: Int, pipe: Boolean = false, flow: Boolean = false, override_reset: Option[Bool] = None) @@ -164,22 +210,43 @@ extends Module(override_reset=override_reset) { } } -/** Generic hardware queue. Required parameter entries controls - the depth of the queues. The width of the queue is determined - from the inputs. - - Example usage: - {{{ val q = Queue(Decoupled(UInt()), 16) - q.io.enq <> producer.io.out - consumer.io.in <> q.io.deq }}} +/** Factory for a generic hardware queue. Required parameter 'entries' controls + * the depth of the queues. The width of the queue is determined + * from the input 'enq'. + * + * Example usage: + * {{{ consumer.io.in <> Queue(producer.io.out, 16) }}} */ object Queue { - def apply[T <: Data](enq: DecoupledIO[T], entries: Int = 2, pipe: Boolean = false): DecoupledIO[T] = { + /** Create a queue and supply a DecoupledIO containing the product. */ + def apply[T <: Data]( + enq: ReadyValidIO[T], + entries: Int = 2, + pipe: Boolean = false, + flow: Boolean = false): DecoupledIO[T] = { val q = Module(new Queue(enq.bits.cloneType, entries, pipe)) q.io.enq.valid := enq.valid // not using <> so that override is allowed q.io.enq.bits := enq.bits enq.ready := q.io.enq.ready TransitName(q.io.deq, q) } + + /** Create a queue and supply a IrrevocableIO containing the product. + * Casting from Decoupled is safe here because we know the Queue has + * Irrevocable semantics; we didn't want to change the return type of + * apply() for backwards compatibility reasons. + */ + def irrevocable[T <: Data]( + enq: ReadyValidIO[T], + entries: Int = 2, + pipe: Boolean = false, + flow: Boolean = false): IrrevocableIO[T] = { + val deq = apply(enq, entries, pipe) + val irr = Wire(new IrrevocableIO(deq.bits)) + irr.bits := deq.bits + irr.valid := deq.valid + deq.ready := irr.ready + irr + } } -- cgit v1.2.3 From 2ff229dac5f915e7f583cbf9cc8118674a4e52a5 Mon Sep 17 00:00:00 2001 From: Henry Cook Date: Tue, 13 Sep 2016 15:51:18 -0700 Subject: Bugfix: actually pass flow parameter from Queue factory to Queue module constructor --- src/main/scala/chisel3/util/Decoupled.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/main/scala/chisel3/util/Decoupled.scala b/src/main/scala/chisel3/util/Decoupled.scala index 51f5bd6a..b2634bec 100644 --- a/src/main/scala/chisel3/util/Decoupled.scala +++ b/src/main/scala/chisel3/util/Decoupled.scala @@ -225,7 +225,7 @@ object Queue entries: Int = 2, pipe: Boolean = false, flow: Boolean = false): DecoupledIO[T] = { - val q = Module(new Queue(enq.bits.cloneType, entries, pipe)) + val q = Module(new Queue(enq.bits.cloneType, entries, pipe, flow)) q.io.enq.valid := enq.valid // not using <> so that override is allowed q.io.enq.bits := enq.bits enq.ready := q.io.enq.ready @@ -242,7 +242,7 @@ object Queue entries: Int = 2, pipe: Boolean = false, flow: Boolean = false): IrrevocableIO[T] = { - val deq = apply(enq, entries, pipe) + val deq = apply(enq, entries, pipe, flow) val irr = Wire(new IrrevocableIO(deq.bits)) irr.bits := deq.bits irr.valid := deq.valid -- cgit v1.2.3