diff options
| -rw-r--r-- | chiselFrontend/src/main/scala/chisel3/core/Aggregate.scala | 42 | ||||
| -rw-r--r-- | chiselFrontend/src/main/scala/chisel3/core/Bits.scala | 17 | ||||
| -rw-r--r-- | chiselFrontend/src/main/scala/chisel3/core/Data.scala | 7 | ||||
| -rw-r--r-- | chiselFrontend/src/main/scala/chisel3/core/Printable.scala | 152 | ||||
| -rw-r--r-- | chiselFrontend/src/main/scala/chisel3/core/Printf.scala | 42 | ||||
| -rw-r--r-- | chiselFrontend/src/main/scala/chisel3/internal/firrtl/IR.scala | 18 | ||||
| -rw-r--r-- | src/main/scala/chisel3/internal/firrtl/Emitter.scala | 6 | ||||
| -rw-r--r-- | src/main/scala/chisel3/package.scala | 53 | ||||
| -rw-r--r-- | src/main/scala/chisel3/util/Decoupled.scala | 94 | ||||
| -rw-r--r-- | src/test/scala/chiselTests/PrintableSpec.scala | 188 | ||||
| -rw-r--r-- | src/test/scala/chiselTests/Printf.scala | 9 |
11 files changed, 584 insertions, 44 deletions
diff --git a/chiselFrontend/src/main/scala/chisel3/core/Aggregate.scala b/chiselFrontend/src/main/scala/chisel3/core/Aggregate.scala index 596a7244..6baf5202 100644 --- a/chiselFrontend/src/main/scala/chisel3/core/Aggregate.scala +++ b/chiselFrontend/src/main/scala/chisel3/core/Aggregate.scala @@ -48,10 +48,20 @@ object Vec { // changing apply(elt0, elts*) to apply(elts*) causes a function collision // with apply(Seq) after type erasure. Workarounds by either introducing a // DummyImplicit or additional type parameter will break some code. + + // Check that types are homogeneous. Width mismatch for Elements is safe. require(!elts.isEmpty) - val width = elts.map(_.width).reduce(_ max _) - // If an element has a direction associated with it, use the bulk connect operator. - val vec = Wire(new Vec(elts.head.cloneTypeWidth(width), elts.length)) + def eltsCompatible(a: Data, b: Data) = a match { + case _: Element => a.getClass == b.getClass + case _: Aggregate => Mux.typesCompatible(a, b) + } + + val t = elts.head + for (e <- elts.tail) + require(eltsCompatible(t, e), s"can't create Vec of heterogeneous types ${t.getClass} and ${e.getClass}") + + val maxWidth = elts.map(_.width).reduce(_ max _) + val vec = Wire(new Vec(t.cloneTypeWidth(maxWidth), elts.length)) def doConnect(sink: T, source: T) = { if (elts.head.flatten.exists(_.firrtlDirection != Direction.Unspecified)) { sink bulkConnect source @@ -202,6 +212,17 @@ sealed class Vec[T <: Data] private (gen: T, val length: Int) for ((elt, i) <- self zipWithIndex) elt.setRef(this, i) + + /** Default "pretty-print" implementation + * Analogous to printing a Seq + * Results in "Vec(elt0, elt1, ...)" + */ + def toPrintable: Printable = { + val elts = + if (length == 0) List.empty[Printable] + else self flatMap (e => List(e.toPrintable, PString(", "))) dropRight 1 + PString("Vec(") + Printables(elts) + PString(")") + } } /** A trait for [[Vec]]s containing common hardware generators for collection @@ -398,6 +419,21 @@ class Bundle extends Aggregate { this } } + + /** Default "pretty-print" implementation + * Analogous to printing a Map + * Results in "Bundle(elt0.name -> elt0.value, ...)" + */ + def toPrintable: Printable = { + val elts = + if (elements.isEmpty) List.empty[Printable] + else { + elements.toList.reverse flatMap { case (name, data) => + List(PString(s"$name -> "), data.toPrintable, PString(", ")) + } dropRight 1 // Remove trailing ", " + } + PString("Bundle(") + Printables(elts) + PString(")") + } } private[core] object Bundle { diff --git a/chiselFrontend/src/main/scala/chisel3/core/Bits.scala b/chiselFrontend/src/main/scala/chisel3/core/Bits.scala index 8a8721f9..8b3723f5 100644 --- a/chiselFrontend/src/main/scala/chisel3/core/Bits.scala +++ b/chiselFrontend/src/main/scala/chisel3/core/Bits.scala @@ -247,6 +247,7 @@ sealed abstract class Bits(width: Width, override val litArg: Option[LitArg]) def do_asSInt(implicit sourceInfo: SourceInfo): SInt /** Reinterpret cast to Bits. */ + @deprecated("Use asUInt, which does the same thing but returns a more concrete type", "chisel3") final def asBits(): Bits = macro SourceInfoTransform.noArg def do_asBits(implicit sourceInfo: SourceInfo): Bits = asUInt() @@ -277,7 +278,7 @@ sealed abstract class Bits(width: Width, override val litArg: Option[LitArg]) pushOp(DefPrim(sourceInfo, UInt(w), ConcatOp, this.ref, that.ref)) } - @deprecated("Use asBits, which makes the reinterpret cast more explicit and actually returns Bits", "chisel3") + @deprecated("Use asUInt, which does the same thing but makes the reinterpret cast more explicit", "chisel3") override def toBits: UInt = do_asUInt(DeprecatedSourceInfo) override def do_fromBits(that: Bits)(implicit sourceInfo: SourceInfo): this.type = { @@ -285,6 +286,9 @@ sealed abstract class Bits(width: Width, override val litArg: Option[LitArg]) res := that res } + + /** Default print as [[Decimal]] */ + final def toPrintable: Printable = Decimal(this) } /** Provides a set of operations to create UInt types and literals. @@ -827,10 +831,15 @@ object Mux { pushOp(DefPrim(sourceInfo, d, MultiplexOp, cond.ref, con.ref, alt.ref)) } + private[core] def typesCompatible[T <: Data](x: T, y: T): Boolean = { + val sameTypes = x.getClass == y.getClass + val sameElements = x.flatten zip y.flatten forall { case (a, b) => a.getClass == b.getClass && a.width == b.width } + val sameNumElements = x.flatten.size == y.flatten.size + sameTypes && sameElements && sameNumElements + } + private def doAggregateMux[T <: Data](cond: Bool, con: T, alt: T)(implicit sourceInfo: SourceInfo): T = { - require(con.getClass == alt.getClass, s"can't Mux between ${con.getClass} and ${alt.getClass}") - for ((c, a) <- con.flatten zip alt.flatten) - require(c.width == a.width, "can't Mux between aggregates of different width") + require(typesCompatible(con, alt), s"can't Mux between heterogeneous types ${con.getClass} and ${alt.getClass}") doMux(cond, con, alt) } } diff --git a/chiselFrontend/src/main/scala/chisel3/core/Data.scala b/chiselFrontend/src/main/scala/chisel3/core/Data.scala index 991b1898..69c375f1 100644 --- a/chiselFrontend/src/main/scala/chisel3/core/Data.scala +++ b/chiselFrontend/src/main/scala/chisel3/core/Data.scala @@ -223,7 +223,7 @@ abstract class Data extends HasId { * * This performs the inverse operation of fromBits(Bits). */ - @deprecated("Use asBits, which makes the reinterpret cast more explicit and actually returns Bits", "chisel3") + @deprecated("Use asUInt, which does the same thing but makes the reinterpret cast more explicit", "chisel3") def toBits(): UInt = SeqUtils.do_asUInt(this.flatten)(DeprecatedSourceInfo) /** Reinterpret cast to UInt. @@ -241,6 +241,8 @@ abstract class Data extends HasId { // firrtlDirection is the direction we report to firrtl. // It maintains the user-specified value (as opposed to the "actual" or applied/propagated value). var firrtlDirection: Direction = Direction.Unspecified + /** Default pretty printing */ + def toPrintable: Printable } object Wire { @@ -293,4 +295,7 @@ sealed class Clock extends Element(Width(1)) { case _: Clock => super.connect(that)(sourceInfo, connectCompileOptions) case _ => super.badConnect(that)(sourceInfo) } + + /** Not really supported */ + def toPrintable: Printable = PString("CLOCK") } diff --git a/chiselFrontend/src/main/scala/chisel3/core/Printable.scala b/chiselFrontend/src/main/scala/chisel3/core/Printable.scala new file mode 100644 index 00000000..f6e63936 --- /dev/null +++ b/chiselFrontend/src/main/scala/chisel3/core/Printable.scala @@ -0,0 +1,152 @@ +// See LICENSE for license details. + +package chisel3.core + +import chisel3.internal.firrtl.Component +import chisel3.internal.HasId + +import scala.collection.mutable + +import java.util.{ + MissingFormatArgumentException, + UnknownFormatConversionException +} + +/** Superclass of things that can be printed in the resulting circuit + * + * Usually created using the custom string interpolator p"..." + * TODO Add support for names of Modules + * Currently impossible because unpack is called before the name is selected + * Could be implemented by adding a new format specifier to Firrtl (eg. %m) + * TODO Should we provide more functions like map and mkPrintable? + */ +sealed abstract class Printable { + /** Unpack into format String and a List of String arguments (identifiers) + * @note This must be called after elaboration when Chisel nodes actually + * have names + */ + def unpack(ctx: Component): (String, Iterable[String]) + /** Allow for appending Printables like Strings */ + final def +(that: Printable) = Printables(List(this, that)) + /** Allow for appending Strings to Printables */ + final def +(that: String) = Printables(List(this, PString(that))) +} +object Printable { + /** Pack standard printf fmt, args* style into Printable + */ + def pack(fmt: String, data: Data*): Printable = { + val args = data.toIterator + + // Error handling + def carrotAt(index: Int) = (" " * index) + "^" + def errorMsg(index: Int) = + s"""| fmt = "$fmt" + | ${carrotAt(index)} + | data = ${data mkString ", "}""".stripMargin + def getArg(i: Int): Data = { + if (!args.hasNext) { + val msg = "has no matching argument!\n" + errorMsg(i) + // Exception wraps msg in s"Format Specifier '$msg'" + throw new MissingFormatArgumentException(msg) + } + args.next() + } + + val pables = mutable.ListBuffer.empty[Printable] + var str = "" + var percent = false + for ((c, i) <- fmt.zipWithIndex) { + if (percent) { + val arg = c match { + case FirrtlFormat(x) => FirrtlFormat(x.toString, getArg(i)) + case 'n' => Name(getArg(i)) + case 'N' => FullName(getArg(i)) + case '%' => Percent + case x => + val msg = s"Illegal format specifier '$x'!\n" + errorMsg(i) + throw new UnknownFormatConversionException(msg) + } + pables += PString(str dropRight 1) // remove format % + pables += arg + str = "" + percent = false + } else { + str += c + percent = c == '%' + } + } + if (percent) { + val msg = s"Trailing %\n" + errorMsg(fmt.size - 1) + throw new UnknownFormatConversionException(msg) + } + require(!args.hasNext, + s"Too many arguments! More format specifier(s) expected!\n" + + errorMsg(fmt.size)) + + pables += PString(str) + Printables(pables) + } +} + +case class Printables(pables: Iterable[Printable]) extends Printable { + require(pables.hasDefiniteSize, "Infinite-sized iterables are not supported!") + final def unpack(ctx: Component): (String, Iterable[String]) = { + val (fmts, args) = pables.map(_ unpack ctx).unzip + (fmts.mkString, args.flatten) + } +} +/** Wrapper for printing Scala Strings */ +case class PString(str: String) extends Printable { + final def unpack(ctx: Component): (String, Iterable[String]) = + (str replaceAll ("%", "%%"), List.empty) +} +/** Superclass for Firrtl format specifiers for Bits */ +sealed abstract class FirrtlFormat(specifier: Char) extends Printable { + def bits: Bits + def unpack(ctx: Component): (String, Iterable[String]) = { + (s"%$specifier", List(bits.ref.fullName(ctx))) + } +} +object FirrtlFormat { + final val legalSpecifiers = List('d', 'x', 'b', 'c') + + def unapply(x: Char): Option[Char] = + Option(x) filter (x => legalSpecifiers contains x) + + /** Helper for constructing Firrtl Formats + * Accepts data to simplify pack + */ + def apply(specifier: String, data: Data): FirrtlFormat = { + val bits = data match { + case b: Bits => b + case d => throw new Exception(s"Trying to construct FirrtlFormat with non-bits $d!") + } + specifier match { + case "d" => Decimal(bits) + case "x" => Hexadecimal(bits) + case "b" => Binary(bits) + case "c" => Character(bits) + case c => throw new Exception(s"Illegal format specifier '$c'!") + } + } +} +/** Format bits as Decimal */ +case class Decimal(bits: Bits) extends FirrtlFormat('d') +/** Format bits as Hexidecimal */ +case class Hexadecimal(bits: Bits) extends FirrtlFormat('x') +/** Format bits as Binary */ +case class Binary(bits: Bits) extends FirrtlFormat('b') +/** Format bits as Character */ +case class Character(bits: Bits) extends FirrtlFormat('c') +/** Put innermost name (eg. field of bundle) */ +case class Name(data: Data) extends Printable { + final def unpack(ctx: Component): (String, Iterable[String]) = (data.ref.name, List.empty) +} +/** Put full name within parent namespace (eg. bundleName.field) */ +case class FullName(data: Data) extends Printable { + final def unpack(ctx: Component): (String, Iterable[String]) = (data.ref.fullName(ctx), List.empty) +} +/** Represents escaped percents */ +case object Percent extends Printable { + final def unpack(ctx: Component): (String, Iterable[String]) = ("%%", List.empty) +} diff --git a/chiselFrontend/src/main/scala/chisel3/core/Printf.scala b/chiselFrontend/src/main/scala/chisel3/core/Printf.scala index 400c144d..4ec13751 100644 --- a/chiselFrontend/src/main/scala/chisel3/core/Printf.scala +++ b/chiselFrontend/src/main/scala/chisel3/core/Printf.scala @@ -10,6 +10,24 @@ import chisel3.internal.firrtl._ import chisel3.internal.sourceinfo.SourceInfo object printf { // scalastyle:ignore object.name + /** Helper for packing escape characters */ + private[chisel3] def format(formatIn: String): String = { + require(formatIn forall (c => c.toInt > 0 && c.toInt < 128), + "format strings must comprise non-null ASCII values") + def escaped(x: Char) = { + require(x.toInt >= 0) + if (x == '"' || x == '\\') { + s"\\${x}" + } else if (x == '\n') { + "\\n" + } else { + require(x.toInt >= 32) // TODO \xNN once FIRRTL issue #59 is resolved + x + } + } + formatIn map escaped mkString "" + } + /** Prints a message in simulation. * * Does not fire when in reset (defined as the encapsulating Module's @@ -23,14 +41,30 @@ object printf { // scalastyle:ignore object.name * @param fmt printf format string * @param data format string varargs containing data to print */ - def apply(fmt: String, data: Bits*)(implicit sourceInfo: SourceInfo) { + def apply(fmt: String, data: Bits*)(implicit sourceInfo: SourceInfo): Unit = + apply(Printable.pack(fmt, data:_*)) + /** Prints a message in simulation. + * + * Does not fire when in reset (defined as the encapsulating Module's + * reset). If your definition of reset is not the encapsulating Module's + * reset, you will need to gate this externally. + * + * May be called outside of a Module (like defined in a function), so + * functions using printf make the standard Module assumptions (single clock + * and single reset). + * + * @param pable [[Printable]] to print + */ + def apply(pable: Printable)(implicit sourceInfo: SourceInfo): Unit = { when (!Builder.forcedModule.reset) { - printfWithoutReset(fmt, data:_*) + printfWithoutReset(pable) } } - private[core] def printfWithoutReset(fmt: String, data: Bits*)(implicit sourceInfo: SourceInfo) { + private[chisel3] def printfWithoutReset(pable: Printable)(implicit sourceInfo: SourceInfo): Unit = { val clock = Builder.forcedModule.clock - pushCommand(Printf(sourceInfo, Node(clock), fmt, data.map((d: Bits) => d.ref))) + pushCommand(Printf(sourceInfo, Node(clock), pable)) } + private[chisel3] def printfWithoutReset(fmt: String, data: Bits*)(implicit sourceInfo: SourceInfo): Unit = + printfWithoutReset(Printable.pack(fmt, data:_*)) } diff --git a/chiselFrontend/src/main/scala/chisel3/internal/firrtl/IR.scala b/chiselFrontend/src/main/scala/chisel3/internal/firrtl/IR.scala index 64d7d5fd..1f05b6e2 100644 --- a/chiselFrontend/src/main/scala/chisel3/internal/firrtl/IR.scala +++ b/chiselFrontend/src/main/scala/chisel3/internal/firrtl/IR.scala @@ -169,22 +169,6 @@ case class ConnectInit(sourceInfo: SourceInfo, loc: Node, exp: Arg) extends Comm case class Stop(sourceInfo: SourceInfo, clk: Arg, ret: Int) extends Command case class Component(id: Module, name: String, ports: Seq[Port], commands: Seq[Command]) extends Arg case class Port(id: Data, dir: Direction) -case class Printf(sourceInfo: SourceInfo, clk: Arg, formatIn: String, ids: Seq[Arg]) extends Command { - require(formatIn.forall(c => c.toInt > 0 && c.toInt < 128), "format strings must comprise non-null ASCII values") - def format: String = { - def escaped(x: Char) = { - require(x.toInt >= 0) - if (x == '"' || x == '\\') { - s"\\${x}" - } else if (x == '\n') { - "\\n" - } else { - require(x.toInt >= 32) // TODO \xNN once FIRRTL issue #59 is resolved - x - } - } - formatIn.map(escaped _).mkString - } -} +case class Printf(sourceInfo: SourceInfo, clk: Arg, pable: Printable) extends Command case class Circuit(name: String, components: Seq[Component]) diff --git a/src/main/scala/chisel3/internal/firrtl/Emitter.scala b/src/main/scala/chisel3/internal/firrtl/Emitter.scala index 8b94c68f..8849077d 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(ctx) + 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 7911cb04..8f01779f 100644 --- a/src/main/scala/chisel3/package.scala +++ b/src/main/scala/chisel3/package.scala @@ -59,6 +59,58 @@ package object chisel3 { // scalastyle:ignore package.object.name 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 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) + /** * These implicit classes allow one to convert scala.Int|scala.BigInt to * Chisel.UInt|Chisel.SInt by calling .asUInt|.asSInt on them, respectively. @@ -144,4 +196,5 @@ package object chisel3 { // scalastyle:ignore package.object.name a.allElements } def getModulePorts(m: Module): Seq[Port] = m.getPorts + def getFirrtlDirection(d: Data): Direction = chisel3.core.Data.getFirrtlDirection(d) } diff --git a/src/main/scala/chisel3/util/Decoupled.scala b/src/main/scala/chisel3/util/Decoupled.scala index 5ce2583c..d178cec5 100644 --- a/src/main/scala/chisel3/util/Decoupled.scala +++ b/src/main/scala/chisel3/util/Decoupled.scala @@ -8,13 +8,17 @@ package chisel3.util import chisel3._ import chisel3.NotStrict.CompileOptions -/** 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 = Input(Bool()) val valid = Output(Bool()) val bits = Output(gen.chiselCloneType) - override def cloneType: this.type = DecoupledIO(gen).asInstanceOf[this.type] } object DecoupledIO { @@ -23,6 +27,19 @@ object DecoupledIO { */ 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(getFirrtlDirection(irr.bits) == 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 + } + implicit class AddMethodsToDecoupled[T<:Data](val target: DecoupledIO[T]) extends AnyVal { def fire(): Bool = target.ready && target.valid @@ -65,6 +82,33 @@ object DecoupledIO { // } } +/** 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. + */ +class DecoupledIO[+T <: Data](gen: T) extends ReadyValidIO[T](gen) +{ + override def cloneType: this.type = new DecoupledIO(gen).asInstanceOf[this.type] +} + +/** 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) +} + object EnqIO { def apply[T<:Data](gen: T): DecoupledIO[T] = DecoupledIO(gen) } @@ -98,7 +142,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) @@ -159,22 +204,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(DecoupledIO(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] = { - val q = Module(new Queue(enq.bits.chiselCloneType, entries, pipe)) + /** 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, 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 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, flow) + val irr = Wire(new IrrevocableIO(deq.bits)) + irr.bits := deq.bits + irr.valid := deq.valid + deq.ready := irr.ready + irr + } } diff --git a/src/test/scala/chiselTests/PrintableSpec.scala b/src/test/scala/chiselTests/PrintableSpec.scala new file mode 100644 index 00000000..afef3c54 --- /dev/null +++ b/src/test/scala/chiselTests/PrintableSpec.scala @@ -0,0 +1,188 @@ +package chiselTests + +import org.scalatest.{FlatSpec, Matchers} +import scala.collection.mutable + +import chisel3._ +import chisel3.testers.BasicTester +import chisel3.Strict.CompileOptions + +/* 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 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] + } + 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(myInst.io.fizz)}") + } + val firrtl = Driver.emit(() => new MyModule) + println(firrtl) + getPrintfs(firrtl) match { + case Seq(Printf("foo", Seq()), + Printf("myWire.foo", 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() + } + } + 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 } + } } |
