diff --git a/README.md b/README.md
index d02012d..8974e12 100644
--- a/README.md
+++ b/README.md
@@ -60,5 +60,5 @@ for a good introduction to writing Firrtl transforms
## TODO
- This used to work by annotating a circuit, consider re-adding that
- Setting to allow the graphs to go deeper into sub-module logic
-- Big firrtl modules that take more than 7 seconds to render are stopped, try and fix this.
+- Provide links to the chisel source
diff --git a/src/main/scala/dotvisualizer/FirrtlDiagrammer.scala b/src/main/scala/dotvisualizer/FirrtlDiagrammer.scala
index 098e609..83d93a1 100644
--- a/src/main/scala/dotvisualizer/FirrtlDiagrammer.scala
+++ b/src/main/scala/dotvisualizer/FirrtlDiagrammer.scala
@@ -39,6 +39,8 @@ case class RankDirAnnotation(rankDir: String) extends OptionAnnotation
case object UseRankAnnotation extends OptionAnnotation
+case object ShowPrintfsAnnotation extends OptionAnnotation
+
object FirrtlDiagrammer {
var dotTimeOut = 7
@@ -156,7 +158,7 @@ object FirrtlDiagrammer {
config.firrtlSource
}
else {
- io.Source.fromFile(config.firrtlSourceFile).getLines().mkString("\n")
+ FileUtils.getText(config.firrtlSourceFile)
}
}
@@ -224,9 +226,13 @@ object FirrtlDiagrammer {
.action { (_, c) => c.copy(useRanking = true) }
.text("tries to rank elements by depth from inputs")
+ opt[Unit]('p', "show-printfs")
+ .action { (_, c) => c.copy(showPrintfs = true) }
+ .text("render printfs showing arguments")
+
opt[Int]('s', "dot-timeout-seconds")
.action { (x, c) => c.copy(dotTimeOut = x) }
- .text("use this to only see the top level view")
+ .text("gives up trying to diagram a module after 7 seconds, this increases that time")
}
parser.parse(args, Config()) match {
@@ -247,7 +253,8 @@ case class Config(
justTopLevel: Boolean = false,
dotTimeOut: Int = 7,
useRanking: Boolean = false,
- rankDir: String = "LR"
+ rankDir: String = "LR",
+ showPrintfs: Boolean = false
) {
def toAnnotations: Seq[Annotation] = {
val dir = {
@@ -269,7 +276,8 @@ case class Config(
RankDirAnnotation(rankDir)
) ++
(if(startModuleName.nonEmpty) Seq(StartModule(startModuleName)) else Seq.empty) ++
- (if(useRanking) Seq(UseRankAnnotation) else Seq.empty)
+ (if(useRanking) Seq(UseRankAnnotation) else Seq.empty) ++
+ (if(showPrintfs) Seq(ShowPrintfsAnnotation) else Seq.empty)
}
}
diff --git a/src/main/scala/dotvisualizer/dotnodes/PrintfNode.scala b/src/main/scala/dotvisualizer/dotnodes/PrintfNode.scala
new file mode 100644
index 0000000..cfe1481
--- /dev/null
+++ b/src/main/scala/dotvisualizer/dotnodes/PrintfNode.scala
@@ -0,0 +1,48 @@
+// See LICENSE for license details.
+
+package dotvisualizer.dotnodes
+
+import firrtl.WRef
+import firrtl.ir.Print
+
+import scala.collection.mutable
+
+case class PrintfNode(name: String, formatString: String, parentOpt: Option[DotNode]) extends DotNode {
+
+ val text = new mutable.StringBuilder()
+
+ override def absoluteName: String = "struct_" + super.absoluteName
+
+ text.append(
+ s"""
+ |$absoluteName [shape="plaintext" label=<
+ |
+ |
+ | printf("$formatString") |
+ |
+ """.stripMargin)
+
+ def addArgument(displayName: String, connectTarget: String, connect: String): PrintfArgument = {
+ val port = PrintfArgument(displayName, connect, connectTarget)
+ text.append(s" ${port.render}")
+ port
+ }
+
+ def finish() {
+ text.append(
+ """
+ |
>];
+ """.stripMargin)
+ }
+
+ def render: String = text.toString()
+}
+
+case class PrintfArgument(name: String, override val absoluteName: String, connectTarget: String) extends DotNode {
+ val parentOpt : Option[DotNode] = None // doesn't need to know parent
+ def render: String = {
+ s"""
+ |$name |
+ """.stripMargin
+ }
+}
diff --git a/src/main/scala/dotvisualizer/transforms/MakeOneDiagram.scala b/src/main/scala/dotvisualizer/transforms/MakeOneDiagram.scala
index 8dfa197..f6a1892 100644
--- a/src/main/scala/dotvisualizer/transforms/MakeOneDiagram.scala
+++ b/src/main/scala/dotvisualizer/transforms/MakeOneDiagram.scala
@@ -42,6 +42,8 @@ class MakeOneDiagram extends Transform {
val rankDir = state.annotations.collectFirst { case RankDirAnnotation(dir) => dir}.getOrElse("LR")
+ val showPrintfs = state.annotations.collectFirst { case ShowPrintfsAnnotation => ShowPrintfsAnnotation}.isDefined
+
val printFileName = s"$targetDir$startModuleName.dot"
println(s"creating dot file $printFileName")
val printFile = new PrintWriter(new java.io.File(printFileName))
@@ -106,10 +108,14 @@ class MakeOneDiagram extends Transform {
s"${moduleNode.absoluteName}_$name".replaceAll("""\.""", "_")
}
+ def reducedLongLiteral(s: String): String = {
+ if(s.length > 32) { s.take(16) + "..." + s.takeRight(16) } else { s }
+ }
+
def getLiteralValue(expression: Expression): Option[String] = {
expression match {
- case UIntLiteral(x, _) => Some(x.toString)
- case SIntLiteral(x, _) => Some(x.toString)
+ case UIntLiteral(x, _) => Some(reducedLongLiteral(x.toString))
+ case SIntLiteral(x, _) => Some(reducedLongLiteral(x.toString))
case _ => None
}
}
@@ -275,6 +281,26 @@ class MakeOneDiagram extends Transform {
moduleNode += memNode
}
+ def processPrintf(printf: Print): Unit = {
+ val nodeName = s"printf_${printf.hashCode().abs}"
+ val printfNode = PrintfNode(nodeName, printf.string.serialize, Some(moduleNode))
+
+ printf.args.foreach { arg =>
+ val displayName = arg.serialize
+ val connectTarget = s"${printfNode.absoluteName}:$displayName"
+ val processedARg = processExpression(arg)
+
+ val port = printfNode.addArgument(displayName, connectTarget, processedARg)
+ nameToNode(connectTarget) = port
+
+ moduleNode.connect(connectTarget, processedARg)
+ }
+ printfNode.finish()
+
+
+ moduleNode += printfNode
+ }
+
def getConnectInfo(expression: Expression): String = {
val (fName, dotName) = expression match {
case WRef(name, _, _, _) => (getFirrtlName(name), expand(name))
@@ -349,6 +375,8 @@ class MakeOneDiagram extends Transform {
val regNode = RegisterNode(reg.name, Some(moduleNode))
nameToNode(getFirrtlName(reg.name)) = regNode
moduleNode += regNode
+ case printf: Print if showPrintfs =>
+ processPrintf(printf)
case memory: DefMemory if scope.doComponents() =>
processMemory(memory)
case _ =>
diff --git a/src/test/scala/dotvisualizer/PrintfSpec.scala b/src/test/scala/dotvisualizer/PrintfSpec.scala
new file mode 100644
index 0000000..4c5aa2f
--- /dev/null
+++ b/src/test/scala/dotvisualizer/PrintfSpec.scala
@@ -0,0 +1,50 @@
+// See README.md for license details.
+
+package dotvisualizer
+
+import org.scalatest.{FreeSpec, Matchers}
+import chisel3._
+import firrtl.FileUtils
+
+class HasPrintf extends MultiIOModule {
+ val in = IO(Input(Bool()))
+ val out = IO(Output(Bool()))
+ out := in
+ printf("in %d, out %d\n", in, out)
+}
+class PrintfSpec extends FreeSpec with Matchers {
+
+ "printfs can now be rendered" - {
+ val dirName = "test_run_dir/has_printf_on/"
+
+ def makeDotFile(showPrintfs: Boolean): String = {
+ val circuit = chisel3.Driver.elaborate(() => new HasPrintf)
+ val firrtl = chisel3.Driver.emit(circuit)
+ val config = Config(
+ targetDir = dirName,
+ firrtlSource = firrtl,
+ rankDir = "TB",
+ useRanking = true,
+ showPrintfs = showPrintfs,
+ openProgram = ""
+ )
+ FirrtlDiagrammer.run(config)
+
+ FileUtils.getText(s"${dirName}HasPrintf.dot")
+ }
+
+ "showPrintfs=true will render printfs in dot file" in {
+ val dotText = makeDotFile(showPrintfs = true)
+
+ dotText.contains("struct_cluster_HasPrintf_printf_1") should be(true)
+ dotText.contains("""printf("in %d, out %d\n")""") should be(true)
+ }
+
+ "default behavior will not render printfs in dot file" in {
+ val dotText = makeDotFile(showPrintfs = false)
+
+ dotText.contains("struct_cluster_HasPrintf_printf_1") should be(false)
+ dotText.contains("""printf("in %d, out %d\n")""") should be(false)
+ }
+ }
+}