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=< + | + | + | + | + """.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( + """ + |
printf("$formatString")
>]; + """.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) + } + } +}