diff options
7 files changed, 202 insertions, 3 deletions
diff --git a/chiselFrontend/src/main/scala/chisel3/core/Aggregate.scala b/chiselFrontend/src/main/scala/chisel3/core/Aggregate.scala index 5563092e..e85b7158 100644 --- a/chiselFrontend/src/main/scala/chisel3/core/Aggregate.scala +++ b/chiselFrontend/src/main/scala/chisel3/core/Aggregate.scala @@ -150,6 +150,10 @@ object Vec extends VecFactory */ sealed class Vec[T <: Data] private[core] (gen: => T, val length: Int) extends Aggregate with VecLike[T] { + override def toString: String = { + s"$sample_element[$length]$bindingToString" + } + private[core] override def typeEquivalent(that: Data): Boolean = that match { case that: Vec[T] => this.length == that.length && @@ -448,6 +452,18 @@ abstract class Record(private[chisel3] implicit val compileOptions: CompileOptio * assert(uint === "hbeefdead".U) // This will pass * }}} */ + override def toString: String = { + val bindingString = topBindingOpt match { + case Some(BundleLitBinding(_)) => + val contents = elements.toList.reverse.map { case (name, data) => + s"$name=$data" + }.mkString(", ") + s"($contents)" + case _ => bindingToString + } + s"$className$bindingString" + } + val elements: ListMap[String, Data] /** Name for Pretty Printing */ @@ -543,8 +559,11 @@ class AutoClonetypeException(message: String) extends ChiselException(message) * }}} */ abstract class Bundle(implicit compileOptions: CompileOptions) extends Record { - override def className = "Bundle" - + override def className: String = this.getClass.getSimpleName match { + case name if name.startsWith("$anon$") => "AnonymousBundle" // fallback for anonymous Bundle case + case "" => "AnonymousBundle" // ditto, but on other platforms + case name => name + } /** The collection of [[Data]] * * Elements defined earlier in the Bundle are higher order upon diff --git a/chiselFrontend/src/main/scala/chisel3/core/Bits.scala b/chiselFrontend/src/main/scala/chisel3/core/Bits.scala index 83c8637a..db2562a5 100644 --- a/chiselFrontend/src/main/scala/chisel3/core/Bits.scala +++ b/chiselFrontend/src/main/scala/chisel3/core/Bits.scala @@ -644,6 +644,13 @@ abstract trait Num[T <: Data] { * @define constantWidth @note The width of the returned $coll is unchanged, i.e., `width of this`. */ sealed class UInt private[core] (width: Width) extends Bits(width) with Num[UInt] { + override def toString: String = { + val bindingString = litOption match { + case Some(value) => s"($value)" + case _ => bindingToString + } + s"UInt$width$bindingString" + } private[core] override def typeEquivalent(that: Data): Boolean = that.isInstanceOf[UInt] && this.width == that.width @@ -959,6 +966,13 @@ object Bits extends UIntFactory * @define constantWidth @note The width of the returned $coll is unchanged, i.e., `width of this`. */ sealed class SInt private[core] (width: Width) extends Bits(width) with Num[SInt] { + override def toString: String = { + val bindingString = litOption match { + case Some(value) => s"($value)" + case _ => bindingToString + } + s"SInt$width$bindingString" + } private[core] override def typeEquivalent(that: Data): Boolean = this.getClass == that.getClass && this.width == that.width // TODO: should this be true for unspecified widths? @@ -1207,6 +1221,14 @@ sealed trait Reset extends Element with ToBoolable * @define numType $coll */ sealed class Bool() extends UInt(1.W) with Reset { + override def toString: String = { + val bindingString = litToBooleanOption match { + case Some(value) => s"($value)" + case _ => bindingToString + } + s"Bool$bindingString" + } + private[core] override def cloneTypeWidth(w: Width): this.type = { require(!w.known || w.get == 1) new Bool().asInstanceOf[this.type] @@ -1328,6 +1350,14 @@ object Bool extends BoolFactory */ sealed class FixedPoint private (width: Width, val binaryPoint: BinaryPoint) extends Bits(width) with Num[FixedPoint] { + override def toString: String = { + val bindingString = litToDoubleOption match { + case Some(value) => s"($value)" + case _ => bindingToString + } + s"FixedPoint$width$binaryPoint$bindingString" + } + private[core] override def typeEquivalent(that: Data): Boolean = that match { case that: FixedPoint => this.width == that.width && this.binaryPoint == that.binaryPoint // TODO: should this be true for unspecified widths? case _ => false @@ -1724,6 +1754,10 @@ object FixedPoint { final class Analog private (private[chisel3] val width: Width) extends Element { require(width.known, "Since Analog is only for use in BlackBoxes, width must be known") + override def toString: String = { + s"Analog$width$bindingToString" + } + private[core] override def typeEquivalent(that: Data): Boolean = that.isInstanceOf[Analog] && this.width == that.width diff --git a/chiselFrontend/src/main/scala/chisel3/core/Clock.scala b/chiselFrontend/src/main/scala/chisel3/core/Clock.scala index 88208d9a..1bc2260c 100644 --- a/chiselFrontend/src/main/scala/chisel3/core/Clock.scala +++ b/chiselFrontend/src/main/scala/chisel3/core/Clock.scala @@ -13,6 +13,8 @@ object Clock { // TODO: Document this. sealed class Clock(private[chisel3] val width: Width = Width(1)) extends Element { + override def toString: String = s"Clock$bindingToString" + def cloneType: this.type = Clock().asInstanceOf[this.type] private[core] def typeEquivalent(that: Data): Boolean = diff --git a/chiselFrontend/src/main/scala/chisel3/core/Data.scala b/chiselFrontend/src/main/scala/chisel3/core/Data.scala index cf6dabdd..bffd84df 100644 --- a/chiselFrontend/src/main/scala/chisel3/core/Data.scala +++ b/chiselFrontend/src/main/scala/chisel3/core/Data.scala @@ -87,10 +87,23 @@ object DataMirror { target.direction } + // Returns the top-level module ports // TODO: maybe move to something like Driver or DriverUtils, since this is mainly for interacting // with compiled artifacts (vs. elaboration-time reflection)? def modulePorts(target: BaseModule): Seq[(String, Data)] = target.getChiselPorts + // Returns all module ports with underscore-qualified names + def fullModulePorts(target: BaseModule): Seq[(String, Data)] = { + def getPortNames(name: String, data: Data): Seq[(String, Data)] = Seq(name -> data) ++ (data match { + case _: Element => Seq() + case r: Record => r.elements.toSeq flatMap { case (eltName, elt) => getPortNames(s"${name}_${eltName}", elt) } + case v: Vec[_] => v.zipWithIndex flatMap { case (elt, index) => getPortNames(s"${name}_${index}", elt) } + }) + modulePorts(target).flatMap { case (name, data) => + getPortNames(name, data).toList + } + } + // Internal reflection-style APIs, subject to change and removal whenever. object internal { def isSynthesizable(target: Data) = target.topBindingOpt.isDefined @@ -292,6 +305,26 @@ abstract class Data extends HasId with NamedComponent with SourceInfoDoc { _direction = Some(actualDirection) } + // User-friendly representation of the binding as a helper function for toString. + // Provides a unhelpful fallback for literals, which should have custom rendering per + // Data-subtype. + protected def bindingToString: String = topBindingOpt match { + case None => "" + case Some(OpBinding(enclosure)) => s"(OpResult in ${enclosure.desiredName})" + case Some(MemoryPortBinding(enclosure)) => s"(MemPort in ${enclosure.desiredName})" + case Some(PortBinding(enclosure)) if !enclosure.isClosed => s"(IO in unelaborated ${enclosure.desiredName})" + case Some(PortBinding(enclosure)) if enclosure.isClosed => + DataMirror.fullModulePorts(enclosure).find(_._2 == this) match { + case Some((name, _)) => s"(IO $name in ${enclosure.desiredName})" + case None => s"(IO (unknown) in ${enclosure.desiredName})" + } + case Some(RegBinding(enclosure)) => s"(Reg in ${enclosure.desiredName})" + case Some(WireBinding(enclosure)) => s"(Wire in ${enclosure.desiredName})" + case Some(DontCareBinding()) => s"(DontCare)" + case Some(ElementLitBinding(litArg)) => s"(unhandled literal)" + case Some(BundleLitBinding(litMap)) => s"(unhandled bundle literal)" + } + // Return ALL elements at root of this type. // Contasts with flatten, which returns just Bits // TODO: refactor away this, this is outside the scope of Data @@ -646,6 +679,8 @@ object DontCare extends Element { bind(DontCareBinding(), SpecifiedDirection.Output) override def cloneType = DontCare + override def toString: String = "DontCare()" + override def litOption = None def toPrintable: Printable = PString("DONTCARE") diff --git a/chiselFrontend/src/main/scala/chisel3/core/StrongEnum.scala b/chiselFrontend/src/main/scala/chisel3/core/StrongEnum.scala index 889e9b11..8feb0541 100644 --- a/chiselFrontend/src/main/scala/chisel3/core/StrongEnum.scala +++ b/chiselFrontend/src/main/scala/chisel3/core/StrongEnum.scala @@ -33,6 +33,19 @@ import EnumAnnotations._ abstract class EnumType(private val factory: EnumFactory, selfAnnotating: Boolean = false) extends Element { + override def toString: String = { + val bindingString = litOption match { + case Some(value) => factory.nameOfValue(value) match { + case Some(name) => s"($value=$name)" + case None => s"($value=(invalid))" + } + case _ => bindingToString + } + // Use getSimpleName instead of enumTypeName because for debugging purposes the fully qualified name isn't + // necessary (compared to for the Enum annotation), and it's more consistent with Bundle printing. + s"${factory.getClass.getSimpleName.init}$bindingString" + } + override def cloneType: this.type = factory().asInstanceOf[this.type] private[core] def compop(sourceInfo: SourceInfo, op: PrimOp, other: EnumType): Bool = { @@ -151,6 +164,10 @@ abstract class EnumFactory { def all: Seq[Type] = enumInstances + private[chisel3] def nameOfValue(id: BigInt): Option[String] = { + enum_records.find(_.inst.litValue() == id).map(_.name) + } + protected def Value: Type = macro EnumMacros.ValImpl protected def Value(id: UInt): Type = macro EnumMacros.ValCustomImpl diff --git a/src/test/scala/chiselTests/DataPrint.scala b/src/test/scala/chiselTests/DataPrint.scala new file mode 100644 index 00000000..662d8e1a --- /dev/null +++ b/src/test/scala/chiselTests/DataPrint.scala @@ -0,0 +1,92 @@ +// See LICENSE for license details. + +package chiselTests + +import org.scalatest._ + +import chisel3._ +import chisel3.experimental.{ChiselEnum, FixedPoint, RawModule, MultiIOModule} + +class DataPrintSpec extends ChiselFlatSpec with Matchers { + object EnumTest extends ChiselEnum { + val sNone, sOne, sTwo = Value + } + + // TODO: dedup w/ BundleLiteralSpec + class BundleTest extends Bundle { + val a = UInt(8.W) + val b = Bool() + + // Bundle literal constructor code, which will be auto-generated using macro annotations in + // the future. + import chisel3.core.BundleLitBinding + import chisel3.internal.firrtl.{ULit, Width} + // Full bundle literal constructor + def Lit(aVal: UInt, bVal: Bool): BundleTest = { + val clone = cloneType + clone.selfBind(BundleLitBinding(Map( + clone.a -> litArgOfBits(aVal), + clone.b -> litArgOfBits(bVal) + ))) + clone + } + } + + "Data types" should "have a meaningful string representation" in { + elaborate { new RawModule { + UInt().toString should be ("UInt") + UInt(8.W).toString should be ("UInt<8>") + SInt(15.W).toString should be ("SInt<15>") + Bool().toString should be ("Bool") + Clock().toString should be ("Clock") + FixedPoint(5.W, 3.BP).toString should be ("FixedPoint<5><<3>>") + Vec(3, UInt(2.W)).toString should be ("UInt<2>[3]") + EnumTest.Type().toString should be ("EnumTest") + (new BundleTest).toString should be ("BundleTest") + } } + } + + class BoundDataModule extends MultiIOModule { // not in the test to avoid anon naming suffixes + Wire(UInt()).toString should be("UInt(Wire in DataPrintSpec$BoundDataModule)") + Reg(SInt()).toString should be("SInt(Reg in DataPrintSpec$BoundDataModule)") + val io = IO(Output(Bool())) // needs a name so elaboration doesn't fail + io.toString should be("Bool(IO in unelaborated DataPrintSpec$BoundDataModule)") + val m = Mem(4, UInt(2.W)) + m(2).toString should be("UInt<2>(MemPort in DataPrintSpec$BoundDataModule)") + (2.U + 2.U).toString should be("UInt<2>(OpResult in DataPrintSpec$BoundDataModule)") + Wire(Vec(3, UInt(2.W))).toString should be ("UInt<2>[3](Wire in DataPrintSpec$BoundDataModule)") + + class InnerModule extends MultiIOModule { + val io = IO(Output(new Bundle { + val a = UInt(4.W) + })) + } + val inner = Module(new InnerModule) + inner.clock.toString should be ("Clock(IO clock in DataPrintSpec$BoundDataModule$InnerModule)") + inner.io.a.toString should be ("UInt<4>(IO io_a in DataPrintSpec$BoundDataModule$InnerModule)") + } + + "Bound data types" should "have a meaningful string representation" in { + elaborate { new BoundDataModule } + } + + "Literals" should "have a meaningful string representation" in { + elaborate { new RawModule { + 3.U.toString should be ("UInt<2>(3)") + 3.U(5.W).toString should be ("UInt<5>(3)") + -1.S.toString should be ("SInt<1>(-1)") + false.B.toString should be ("Bool(false)") + true.B.toString should be ("Bool(true)") + 2.25.F(6.W, 2.BP).toString should be ("FixedPoint<6><<2>>(2.25)") + -2.25.F(6.W, 2.BP).toString should be ("FixedPoint<6><<2>>(-2.25)") + EnumTest.sNone.toString should be ("EnumTest(0=sNone)") + EnumTest.sTwo.toString should be ("EnumTest(2=sTwo)") + EnumTest(1.U).toString should be ("EnumTest(1=sOne)") + (new BundleTest).Lit(2.U, false.B).toString should be ("BundleTest(a=UInt<8>(2), b=Bool(false))") + new Bundle { + val a = UInt(8.W) + }.toString should be ("AnonymousBundle") + DontCare.toString should be ("DontCare()") + } } + } +} diff --git a/src/test/scala/chiselTests/PrintableSpec.scala b/src/test/scala/chiselTests/PrintableSpec.scala index c6ba0a1d..6d74fbc0 100644 --- a/src/test/scala/chiselTests/PrintableSpec.scala +++ b/src/test/scala/chiselTests/PrintableSpec.scala @@ -177,7 +177,7 @@ class PrintableSpec extends FlatSpec with Matchers { } val firrtl = Driver.emit(() => new MyModule) getPrintfs(firrtl) match { - case Seq(Printf("Bundle(foo -> %d, bar -> %d)", + case Seq(Printf("AnonymousBundle(foo -> %d, bar -> %d)", Seq("myBun.foo", "myBun.bar"))) => case e => fail() } |
