// SPDX-License-Identifier: Apache-2.0 package chisel3 import chisel3.internal.firrtl.Component 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"..."`. Printable string interpolation is * similar to [[https://docs.scala-lang.org/overviews/core/string-interpolation.html String * interpolation in Scala]] For example: * {{{ * printf(p"The value of wire = \$wire\n") * }}} * This is equivalent to writing: * {{{ * printf("The value of wire = %d\n", wire) * }}} * All Chisel data types have a method `.toPrintable` that gives a default pretty print that can be * accessed via `p"..."`. This works even for aggregate types, for example: * {{{ * val myVec = VecInit(5.U, 10.U, 13.U) * printf(p"myVec = \$myVec\n") * // myVec = Vec(5, 10, 13) * * val myBundle = Wire(new Bundle { * val foo = UInt() * val bar = UInt() * }) * myBundle.foo := 3.U * myBundle.bar := 11.U * printf(p"myBundle = \$myBundle\n") * // myBundle = Bundle(a -> 3, b -> 11) * }}} * Users can override the default behavior of `.toPrintable` in custom [[Bundle]] and [[Record]] * types. */ // 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 = Printables(List(this, that)) /** Allow for appending Strings to Printables */ final def +(that: String): Printables = 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 checkArg(i: Int): Unit = { if (!args.hasNext) { val msg = "has no matching argument!\n" + errorMsg(i) // Exception wraps msg in s"Format Specifier '$msg'" throw new MissingFormatArgumentException(msg) } val _ = args.next() } var iter = 0 var curr_start = 0 val buf = mutable.ListBuffer.empty[String] while (iter < fmt.size) { // Encountered % which is either // 1. Describing a format specifier. // 2. Literal Percent // 3. Dangling percent - most likely due to a typo - intended literal percent or forgot the specifier. // Try to give meaningful error reports if (fmt(iter) == '%') { if (iter != fmt.size - 1 && (fmt(iter + 1) != '%' && !fmt(iter + 1).isWhitespace)) { checkArg(iter) buf += fmt.substring(curr_start, iter) curr_start = iter iter += 1 } // Last character is %. else if (iter == fmt.size - 1) { val msg = s"Trailing %\n" + errorMsg(fmt.size - 1) throw new UnknownFormatConversionException(msg) } // A lone % else if (fmt(iter + 1).isWhitespace) { val msg = s"Unescaped % - add % if literal or add proper specifier if not\n" + errorMsg(iter + 1) throw new UnknownFormatConversionException(msg) } // A literal percent - hence increment by 2. else { iter += 2 } } // Normal progression else { iter += 1 } } require( !args.hasNext, s"Too many arguments! More format specifier(s) expected!\n" + errorMsg(fmt.size) ) buf += fmt.substring(curr_start, iter) // The string received as an input to pack is already // treated i.e. escape sequences are processed. // Since StringContext API assumes the parts are un-treated // treatEscapes is called within the implemented custom interpolators. // The literal \ needs to be escaped before sending to the custom cf interpolator. val bufEscapeBackSlash = buf.map(_.replace("\\", "\\\\")) StringContext(bufEscapeBackSlash.toSeq: _*).cf(data: _*) } } 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(private[chisel3] val 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) }