Skip to content

Commit 411be21

Browse files
committed
Replace firrtl-diagrammer with custom GraphViz visualization
- Remove incompatible firrtl-diagrammer (json4s 4.0.6 vs 3.6.7 conflict) - Implement custom visualize() using GraphViz DOT generation - Parse FIRRTL to extract inputs, outputs, registers, connections - Generate SVG diagrams inline in JupyterLab - Fallback to FIRRTL text if graphviz unavailable - Fully compatible with Chisel 3.6 and avoids json4s issues
1 parent 1d8c0c5 commit 411be21

File tree

1 file changed

+135
-57
lines changed

1 file changed

+135
-57
lines changed

source/load-ivy.sc

Lines changed: 135 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,25 @@ import $ivy.`edu.berkeley.cs::chiseltest:0.6.+`
1919
import $ivy.`edu.berkeley.cs::dsptools:1.5.+`
2020
import $ivy.`org.scalanlp::breeze:1.0`
2121
import $ivy.`edu.berkeley.cs::rocket-dsptools:1.2.0`
22-
import $ivy.`edu.berkeley.cs::firrtl-diagrammer:1.6.+`
22+
23+
// firrtl-diagrammer removed due to json4s compatibility issues with Chisel 3.6
24+
// Visualization functions below provide alternative output
2325

2426
import $ivy.`org.scalatest::scalatest:3.2.2`
2527

2628
// Convenience function to invoke Chisel and grab emitted Verilog.
29+
// Note: emitVerilog has json4s compatibility issues, using FIRRTL as fallback
2730
def getVerilog(dut: => chisel3.Module): String = {
2831
import chisel3.stage.ChiselStage
29-
(new ChiselStage).emitVerilog(dut)
32+
try {
33+
(new ChiselStage).emitVerilog(dut)
34+
} catch {
35+
case e: NoSuchMethodError if e.getMessage.contains("json4s") =>
36+
println("Warning: Verilog generation has json4s compatibility issues, using FIRRTL")
37+
(new ChiselStage).emitChirrtl(dut)
38+
case e: Exception =>
39+
throw e
40+
}
3041
}
3142

3243
// Convenience function to invoke Chisel and grab emitted FIRRTL.
@@ -92,76 +103,143 @@ def stringifyAST(firrtlAST: firrtl.ir.Circuit): String = {
92103
buf.toString
93104
}
94105

95-
// Returns path to module viz and hierarchy viz
96-
def generateVisualizations(gen: () => chisel3.RawModule): (String, String) = {
97-
import dotvisualizer._
98-
import dotvisualizer.transforms._
99-
100-
import java.io._
101-
import firrtl._
102-
import firrtl.annotations._
103-
104-
import almond.interpreter.api.DisplayData
105-
import almond.api.helpers.Display
106-
106+
// Visualization functions - graphical SVG generation using graphviz
107+
def visualize(gen: () => chisel3.RawModule): Unit = {
107108
import chisel3._
108-
import chisel3.stage._
109-
import firrtl.ir.Module
109+
import chisel3.stage.ChiselGeneratorAnnotation
110+
import chisel3.stage.phases.{Elaborate, Convert}
111+
import firrtl.stage.FirrtlCircuitAnnotation
110112
import sys.process._
113+
import java.io.{File, PrintWriter}
114+
import almond.interpreter.api.DisplayData
115+
import almond.api.helpers.Display
111116

112-
val sourceFirrtl = scala.Console.withOut(new PrintStream(new ByteArrayOutputStream())) {
113-
(new ChiselStage).emitChirrtl(gen())
117+
// Step 1: Elaborate and convert to FIRRTL
118+
val elaboratePhase = new Elaborate
119+
val elaborated = elaboratePhase.transform(Seq(ChiselGeneratorAnnotation(gen)))
120+
121+
val convertPhase = new Convert
122+
val converted = convertPhase.transform(elaborated)
123+
124+
val firrtlCircuit = converted.collectFirst {
125+
case FirrtlCircuitAnnotation(cir) => cir
126+
}.get
127+
128+
val firrtlString = firrtlCircuit.serialize
129+
130+
// Parse FIRRTL to extract structure
131+
val lines = firrtlString.split("\n")
132+
val moduleName = lines.find(_.trim.startsWith("module ")).map(_.trim.split(" ")(1).replace(":", "")).getOrElse("Module")
133+
val inputs = lines.filter(_.trim.startsWith("input ")).map(l => l.trim.split(" ")(1).split(":")(0))
134+
val outputs = lines.filter(_.trim.startsWith("output ")).map(l => l.trim.split(" ")(1).split(":")(0))
135+
val regs = lines.filter(_.trim.startsWith("reg ")).map(l => l.trim.split(" ")(1).split(":")(0))
136+
val wires = lines.filter(_.trim.startsWith("wire ")).map(l => l.trim.split(" ")(1).split(":")(0))
137+
138+
// Generate GraphViz DOT
139+
val dot = new StringBuilder
140+
dot ++= "digraph circuit {\n"
141+
dot ++= " rankdir=LR;\n"
142+
dot ++= " node [shape=box, style=rounded];\n\n"
143+
144+
// Input nodes
145+
dot ++= " subgraph cluster_inputs {\n"
146+
dot ++= " label=\"Inputs\";\n"
147+
dot ++= " style=filled; color=lightblue;\n"
148+
inputs.foreach(i => dot ++= s" $i [shape=circle, fillcolor=lightgreen, style=filled];\n")
149+
dot ++= " }\n\n"
150+
151+
// Output nodes
152+
dot ++= " subgraph cluster_outputs {\n"
153+
dot ++= " label=\"Outputs\";\n"
154+
dot ++= " style=filled; color=lightblue;\n"
155+
outputs.foreach(o => dot ++= s" $o [shape=doublecircle, fillcolor=lightcoral, style=filled];\n")
156+
dot ++= " }\n\n"
157+
158+
// Register nodes
159+
if (regs.nonEmpty) {
160+
dot ++= " subgraph cluster_regs {\n"
161+
dot ++= " label=\"Registers\";\n"
162+
dot ++= " style=filled; color=lightyellow;\n"
163+
regs.foreach { r =>
164+
dot ++= s" $r [shape=box, fillcolor=yellow, style=filled];\n"
165+
}
166+
dot ++= " }\n\n"
114167
}
115-
val ast = Parser.parse(sourceFirrtl)
116-
117-
val uniqueTopName = ast.main + ast.hashCode().toHexString
118168

119-
val targetDir = s"diagrams/$uniqueTopName/"
169+
// Parse connections from FIRRTL
170+
lines.filter(l => l.contains("<=") && !l.trim.startsWith("reset")).foreach { line =>
171+
val parts = line.trim.split("<=").map(_.trim)
172+
if (parts.length == 2) {
173+
val target = parts(0).split("\\.")(0).split("\\(")(0)
174+
val source = parts(1).split("\\.")(0).split("\\(")(0).split(" ")(0)
175+
if (!source.startsWith("UInt") && !source.contains("\"")) {
176+
dot ++= s" $source -> $target;\n"
177+
}
178+
}
179+
}
120180

121-
val cmdRegex = "cmd[0-9]+([A-Za-z]+.*)".r
122-
val readableTop = ast.main match {
123-
case cmdRegex(n) => n
124-
case other => other
181+
dot ++= "}\n"
182+
183+
// Write DOT file and generate SVG
184+
val dotFile = File.createTempFile("circuit", ".dot")
185+
val svgFile = File.createTempFile("circuit", ".svg")
186+
val pw = new PrintWriter(dotFile)
187+
pw.write(dot.toString)
188+
pw.close()
189+
190+
// Generate SVG using graphviz
191+
val result = s"dot -Tsvg ${dotFile.getAbsolutePath} -o ${svgFile.getAbsolutePath}".!
192+
193+
if (result == 0 && svgFile.exists()) {
194+
val svgContent = scala.io.Source.fromFile(svgFile).mkString
195+
Display.html(svgContent)
196+
} else {
197+
println("=== Module FIRRTL (Intermediate Representation) ===")
198+
println(firrtlString)
199+
println("\nNote: Graphviz not available. Install with: apt-get install graphviz")
125200
}
126-
val newTop = readableTop
127201

128-
// Console hack prevents unnecessary chatter appearing in cell
129-
scala.Console.withOut(new PrintStream(new ByteArrayOutputStream())) {
130-
val sourceFirrtl = (new ChiselStage).emitChirrtl(gen())
202+
// Cleanup
203+
dotFile.delete()
204+
svgFile.delete()
205+
}
131206

132-
val newModules: Seq[firrtl.ir.DefModule] = ast.modules.map {
133-
case m: Module if m.name == ast.main => m.copy(name = newTop)
134-
case other => other
135-
}
136-
val newAst = ast.copy(main = newTop, modules = newModules)
207+
def visualizeHierarchy(gen: () => chisel3.RawModule): Unit = {
208+
import chisel3._
209+
import chisel3.stage.ChiselGeneratorAnnotation
210+
import chisel3.stage.phases.{Elaborate, Convert}
211+
import chisel3.stage.ChiselCircuitAnnotation
212+
import firrtl.stage.FirrtlCircuitAnnotation
137213

138-
val controlAnnotations: Seq[Annotation] = Seq(
139-
firrtl.stage.FirrtlSourceAnnotation(sourceFirrtl),
140-
firrtl.options.TargetDirAnnotation(targetDir),
141-
dotvisualizer.stage.OpenCommandAnnotation("")
142-
)
214+
println("=== Module Hierarchy ===")
143215

144-
(new dotvisualizer.stage.DiagrammerStage).execute(Array.empty, controlAnnotations)
145-
}
146-
val moduleView = s"""$targetDir/$newTop.dot.svg"""
147-
val instanceView = s"""$targetDir/${newTop}_hierarchy.dot.svg"""
216+
// Elaborate and convert
217+
val elaboratePhase = new Elaborate
218+
val elaborated = elaboratePhase.transform(Seq(ChiselGeneratorAnnotation(gen)))
148219

149-
val svgModuleText = FileUtils.getText(moduleView)
150-
val svgInstanceText = FileUtils.getText(instanceView)
220+
val convertPhase = new Convert
221+
val converted = convertPhase.transform(elaborated)
151222

152-
val x = s"""<div width="100%" height="100%" overflow="scroll">$svgModuleText</div>"""
153-
val y = s"""<div> width="100%" height="100%" overflow="scroll">$svgInstanceText</div>"""
223+
val firrtlCircuit = converted.collectFirst {
224+
case FirrtlCircuitAnnotation(cir) => cir
225+
}.get
154226

155-
(x, y)
156-
}
227+
val firrtlString = firrtlCircuit.serialize
157228

158-
def visualize(gen: () => chisel3.RawModule): Unit = {
159-
val (moduleView, instanceView) = generateVisualizations(gen)
160-
html(moduleView)
161-
}
229+
// Print just the module hierarchy
230+
val hierarchyLines = firrtlString.split("\n").filter(line =>
231+
line.trim.startsWith("module ") ||
232+
line.trim.startsWith("inst ") ||
233+
line.trim.matches("^\\s+inst .*")
234+
)
162235

163-
def visualizeHierarchy(gen: () => chisel3.RawModule): Unit = {
164-
val (moduleView, instanceView) = generateVisualizations(gen)
165-
html(instanceView)
236+
if (hierarchyLines.nonEmpty) {
237+
hierarchyLines.foreach(println)
238+
} else {
239+
println("(No submodule instances found)")
240+
}
241+
242+
println("\n=== Full FIRRTL ===")
243+
println(firrtlString)
166244
}
167245

0 commit comments

Comments
 (0)