Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Define Data .toString #985

Merged
merged 12 commits into from
Jan 22, 2019
22 changes: 20 additions & 2 deletions chiselFrontend/src/main/scala/chisel3/core/Aggregate.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 &&
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -543,8 +559,10 @@ class AutoClonetypeException(message: String) extends ChiselException(message)
* }}}
*/
abstract class Bundle(implicit compileOptions: CompileOptions) extends Record {
override def className = "Bundle"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

className is used by Printable for hardware pretty printing. I think just using the default Aggregate .getClass.getSimpleName is better for obvious reasons. I can't recall my original thinking, but I think it had to do with making this pretty printing look like printing a Map in Scala (since that's sort of what Records/Bundles are), but I think giving the name of the Bundle class is generally better.

My only question is what about anonymous or inner bundles? You'll get some pretty weird names but perhaps that's still desirable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the intention of this is for human debugging, I think providing better naming on a best-effort basis makes sense. So anonymous Bundles will probably give some nasty name. Inner bundles should work fine, it's one of the test cases (though not intentionally).

Random thought: maybe we could regex match anonymous bundle names and replace it with a more friendly AnonymousBundle? Might be more trouble than it's worth though, so I'd prefer to wait for user feedback first.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think that's sensible, at least MyModule$anon$1 tells you more than just Bundle!

override def className: String = this.getClass.getSimpleName match {
case "" => "Bundle" // fallback for anonymous Bundle case
case name => name
}
/** The collection of [[Data]]
*
* Elements defined earlier in the Bundle are higher order upon
Expand Down
34 changes: 34 additions & 0 deletions chiselFrontend/src/main/scala/chisel3/core/Bits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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?
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions chiselFrontend/src/main/scala/chisel3/core/Clock.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
35 changes: 35 additions & 0 deletions chiselFrontend/src/main/scala/chisel3/core/Data.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -289,6 +302,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
Expand Down Expand Up @@ -643,6 +676,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")
Expand Down
17 changes: 17 additions & 0 deletions chiselFrontend/src/main/scala/chisel3/core/StrongEnum.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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

Expand Down
92 changes: 92 additions & 0 deletions src/test/scala/chiselTests/DataPrint.scala
Original file line number Diff line number Diff line change
@@ -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)")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think it might be worth having a source locator for Data? Let people know where the Wire was constructed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly. This is new enough that I don't know what the use case for this will be (other than printing literals), so I'd like to wait and see first.

There would also need to be infrastructure built for it, mainly IO needs to have a source locator attached to it, and all Bindings needs a copy of the source locator (currently is only part of the FIRRTL command). Adding source locators for literals would be more difficult because of the possibility for a chained apply, so we might have to build more source info transform macros.

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 ("Bundle")
DontCare.toString should be ("DontCare()")
} }
}
}