Skip to content

Commit

Permalink
Parse the HGLDD file and collect signals.
Browse files Browse the repository at this point in the history
- Emit both hgldd with and without `-g` option (maybe are both needed for mapping)
- Add dump file function for the `DebugIRParser`
- Collect all the signals in the `DebugIRParser`
- Map verilog names to variables in the `DebugIRParser` (`updateSignal()` method)
- Add opcode check in `DebugIRParser`
- Fix `dumpFile()` method in `UniqueHashMap`
  • Loading branch information
rameloni committed Mar 13, 2024
1 parent 9fe8913 commit 5b480dd
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 50 deletions.
11 changes: 9 additions & 2 deletions src/main/tywaves/circuitmapper/MapChiselToVcd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,12 @@ class MapChiselToVcd[T <: RawModule](generateModule: () => T, private val workin
chiselIRParser.parseCircuit(circuitChiselIR)

// Step 4. Parse the debug information
val debugIRParser = new DebugIRParser
debugIRParser.parse(logSubDir, TypedConverter.getDebugIRFile)
val gDebugIRParser = new DebugIRParser(workingDir, TypedConverter.getDebugIRFile(gOpt = true))
val debugIRParser =
new DebugIRParser(workingDir, TypedConverter.getDebugIRFile(gOpt = false)) // TODO: check if this is needed or not

gDebugIRParser.parse()
debugIRParser.parse()

def printDebug(): Unit = {
println("Chisel Stage Annotations:")
Expand All @@ -59,6 +63,9 @@ class MapChiselToVcd[T <: RawModule](generateModule: () => T, private val workin
def dumpLog(): Unit = {
firrtlIRParser.dumpMaps(s"$logSubDir/FirrtlIRParsing.log")
chiselIRParser.dumpMaps(s"$logSubDir/ChiselIRParsing.log")

gDebugIRParser.dump(s"$logSubDir/gDebugIRParser.log")
debugIRParser.dump(s"$logSubDir/DebugIRParser.log")
}

/**
Expand Down
43 changes: 30 additions & 13 deletions src/main/tywaves/circuitmapper/TypedConverter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,17 @@ private object TypedConverter {

private val args = Array("--target", "systemverilog", "--split-verilog")

private var hglddDebugDir = "hgldd/debug"
private var hglddWithOptDir = "hgldd/opt"

// In the default annotations, emit also the debug hgldd file (a json file containing info from the debug dialect)
private val defaultAnnotations =
createFirtoolOptions(Seq("-disable-annotation-unknown", "-g", "--emit-hgldd" /*,"--output-final-mlir=WORK.mlir"*/ ))
createFirtoolOptions(Seq(
// "-disable-annotation-unknown",
"--emit-hgldd",
"--hgldd-output-prefix=<path>",
/*,"--output-final-mlir=WORK.mlir"*/
))

private var workingDir: Option[String] = None

Expand All @@ -31,27 +39,36 @@ private object TypedConverter {
/** This function is used to elaborate the circuit and get the ChiselIR */
def getChiselStageAnno[T <: RawModule](generateModule: () => T, workingDir: String = "workingDir"): AnnotationSeq = {
this.workingDir = Some(workingDir)
hglddWithOptDir = workingDir + "/" + hglddWithOptDir
hglddDebugDir = workingDir + "/" + hglddDebugDir

val annotations = Seq(ChiselGeneratorAnnotation(generateModule)) ++ defaultAnnotations
chiselStage.execute(
args ++ Array("--target-dir", workingDir),
args ++ Array("--target-dir", hglddWithOptDir),
annotations,
) // execute returns the passThrough annotations in CIRCT transform stage

chiselStage.execute(
args ++ Array("--target-dir", hglddDebugDir),
annotations ++ Seq(circt.stage.FirtoolOption("-g")),
// execute returns the passThrough annotations in CIRCT transform stage
)
}

/** Get the name of the debug file */
def getDebugIRFile: String = {
val workingDir = this.workingDir match {
case None =>
throw new Exception("This function should be called after getChiselStageAnno. WorkingDir is not set.")
case Some(wd) => new java.io.File(wd)
}
def getDebugIRFile(gOpt: Boolean): String = {

// Open the HGLDD file and extract the information
if (workingDir.exists() && workingDir.isDirectory) {
workingDir.listFiles().filter(_.getName.endsWith(debugFileExt)).head.getAbsolutePath
} else
throw new Exception(s"WorkingDir: $workingDir does not exist or is not a directory.")
def getFile(_workingDir: String): String = {
val workingDir = new java.io.File(_workingDir)
// Open the HGLDD file and extract the information
if (workingDir.exists() && workingDir.isDirectory) {
workingDir.listFiles().filter(_.getName.endsWith(debugFileExt)).head.getAbsolutePath
} else
throw new Exception(s"WorkingDir: $workingDir does not exist or is not a directory.")
}

if (gOpt) getFile(this.hglddDebugDir)
else getFile(this.hglddWithOptDir)
}
}

Expand Down
110 changes: 83 additions & 27 deletions src/main/tywaves/hglddparser/DebugIRParser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,27 @@ import tywaves.utils.UniqueHashMap

import scala.io.Source
case class VerilogSignals(names: Seq[String])
class DebugIRParser {
class DebugIRParser(val workingDir: String, ddFilePath: String) {

def this() = this("workingDir", "ddFilePath")

lazy val modules = new UniqueHashMap[ElId, Name]()
lazy val ports = new UniqueHashMap[ElId, (Name, Direction, Type /*, chiselIR.Port*/ )]()
lazy val flattenedPorts = new UniqueHashMap[ElId, (Name, Direction, HardwareType, Type)]()
lazy val allElements = new UniqueHashMap[ElId, (Name, Direction, Type)]()
lazy val signals = new UniqueHashMap[ElId, (Name, Direction, HardwareType, Type, VerilogSignals)]()

private var nameUpdate = false
def dump(fileDump: String): Unit = {
if (!nameUpdate) throw new Exception("The name of the file has been updated. Please use the new name.")
modules.dumpFile(fileDump, "Modules:", append = false)
ports.dumpFile(fileDump, "Ports:")
flattenedPorts.dumpFile(fileDump, "Flattened Ports:")
allElements.dumpFile(fileDump, "Internal Elements:")
signals.dumpFile(fileDump, "Signals:")

}

/**
* Parse a given string in `hgldd` format.
*
Expand All @@ -38,7 +51,6 @@ class DebugIRParser {
*/
def parseFile(ddFilePath: String): hglddparser.HglddTopInterface = {
// Imp: for the future the "file_info" property is a relative path from the working directory
println("DebugIRParser: parse. ddFilePath: " + ddFilePath)

// Open the file HglDD file and convert it to a string
val sourceHgldd = Source.fromFile(ddFilePath)
Expand All @@ -56,7 +68,7 @@ class DebugIRParser {
* @param ddFilePath
* the input file to parse
*/
def parse(workingDir: String, ddFilePath: String): Unit = {
def parse(): Unit = {
val hgldd = parseFile(ddFilePath)
println("DebugIRParser: parse. hgldd: " + hgldd)
val (fileInfo, hdlFileActualIndex) = (hgldd.HGLDD.file_info, hgldd.HGLDD.hdl_file_index - 1)
Expand All @@ -70,19 +82,8 @@ class DebugIRParser {
_,
)
)
println("======================")
println("Modules")
modules.foreach(println)
println("======================")
println("Flattened ports")
flattenedPorts.foreach(println)
println("======================")
println("All elements")
allElements.foreach(println)
println("======================")
println("Module Signals")
signals.foreach(println)

updateSignals()
}

/** Parse an object from the HGLDD representation */
Expand All @@ -95,12 +96,14 @@ class DebugIRParser {
// Drop the scope from the object name
val obj_name =
scope match { case "root" => hglddObject.obj_name; case _ => hglddObject.obj_name.substring(scope.length + 1) }
val elId = createId(fileInfo, hglddObject.hgl_loc, obj_name)
val elId =
createId(fileInfo, hglddObject.hgl_loc, obj_name)

// Step 2: Parse the kind of the object
hglddObject.kind match {
case s @ "struct" =>
allElements.put(elId, (Name(obj_name, scope), Direction("Unknown"), Type(s)))
hglddObject.port_vars.foreach(parsePortVarFromModule(fileInfo, _, hglddObject.obj_name))
case "module" =>
modules.put(elId, Name(obj_name, scope))
hglddObject.port_vars.foreach(parsePortVarFromModule(fileInfo, _, hglddObject.obj_name))
Expand All @@ -125,12 +128,63 @@ class DebugIRParser {
val typ = Type(portVar.type_name)
val hwTyp = HardwareType(portVar.type_name)

val sigNames = portVar.value match {
case None => VerilogSignals(Seq(portVar.var_name))
case Some(value) => VerilogSignals(collectSigNames(value))
val sigNames =
portVar.value match {
case None => VerilogSignals(Seq(portVar.var_name))
case Some(value) => VerilogSignals(collectSigNames(value))
}
// val sigNames = VerilogSignals(Seq(portVar.var_name))
signals.put(elId, (name, dir, hwTyp, typ, sigNames))
}

/**
* Update the [[signals]] in order to associate their actual system verilog
* name. Some signals are part of a complex type, for example a bundle/struct.
*
* The [[signals]] list contains all the signals in the HGLDD file associated
* to their system verilog name which should be contained in the
* [[Value.sig_name]] variable. However, when they are children of a complex
* type (no "logic", "wire", or "register") their [[Value.sig_name]] does not
* match the actual system verilog name.
*
* A signal that is complex type has a [[Type.name]] that is not "logic", and
* it contains a list of [[Value.sig_name]]s which should be the actual system
* verilog name. This function associates these names to their respective
* signals in [[signals]].
*
* It updates that name by doing the following steps:
*
* 1. Find all the complex types elements (i.e. not logic, wire, or
* register) and assign the `scope` as its type.
* 1. Get the list of actual system verilog names (children signals of
* `scope`) from the [[Value.sig_name]].
* 1. From [[signals]] search those signals within the `scope` and update
* with their new name.
*/
// Find all the elements that are not logic, wire, or register
// if a signal is not a logic, wire, or register, it has a complex type,
// find and update the name of that signals
private def updateSignals(): Unit = {
val (logic, wire, register) = ("logic", "wire", "register")

signals.foreach {
case (_, (_, _, _, typ, sigNames)) =>
typ.name match {
case "logic" | "wire" | "register" => ()
case scope => // The scope to update
val newNames = sigNames.names // This contains the verilog signal names withing the scope

// Find all the signals that are within the scope to be updated
signals.filter(_._2._1.scope == scope)
.zip(newNames).map {
case ((elId, (name, dir, hwType, typ, _)), newSigName) =>
(elId, (name, dir, hwType, typ, VerilogSignals(Seq(newSigName))))
}.foreach(kv => signals.update(kv._1, kv._2))

}
}
signals.put(elId, (name, dir, hwTyp, typ, sigNames))
nameUpdate = true

}

/**
Expand All @@ -143,13 +197,15 @@ class DebugIRParser {
case Some(sigName) => names = names :+ sigName
case None => ()
}
value.operands match {
case Some(operands) =>
operands.foreach { op =>
names = names ++ collectSigNames(op)
}
case None => ()
}

if (value.opcode.getOrElse("") == "'{")
value.operands match {
case Some(operands) =>
operands.foreach { op =>
names = names ++ collectSigNames(op)
}
case None => ()
}
names
}

Expand Down
4 changes: 3 additions & 1 deletion src/main/tywaves/hglddparser/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ package object hglddparser {
case class HglddHeader(version: String, file_info: Seq[String], hdl_file_index: Int)

/**
* An object containing information about the hgl and hdl.
* An object containing information about the hgl and hdl. This object stores
* somehow a high level information like the hierarchy and the name used in
* Chisel. While the sig_name of value contains the hierarchy and verilog name.
*
* @param kind
* The kind of object (e.g. struct, module, etc.)
Expand Down
8 changes: 4 additions & 4 deletions src/main/tywaves/utils/UniqueHashMap.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,16 @@ class UniqueHashMap[K, V] extends mutable.HashMap[K, V] {
val bw = new java.io.BufferedWriter(new java.io.FileWriter(file, append))
bw.write(s"\n$header\n")

debugList.foreach { case (key, value, count) =>
bw.write(s"$count: $key: $value\n")
this.foreach { case (key, value) =>
bw.write(s"$key: $value\n")
}
bw.close()
}

def log(header: String = ""): Unit = {
println(s"\n$header\n")
debugList.foreach { case (key, value, count) =>
println(s"$count: $key: $value")
this.foreach { case (key, value) =>
println(s"$key: $value")
}
}
def debugLog(): Unit =
Expand Down
2 changes: 1 addition & 1 deletion src/test/foo/Foo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class Bar extends Module {
val b = Input(Bool())
val out = Output(Bool())
})

io.out := io.a & io.b
}

Expand Down
10 changes: 10 additions & 0 deletions src/test/foo/FooTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,13 @@ class FooTest extends AnyFlatSpec {
}

}

class SimpleTest extends AnyFlatSpec {
behavior of "SimpleTest"
it should "trace simple bar" in {
simulate(new Bar, Seq(simSettings.EnableTraceWithUnderscore), simName = "trace_simple_bar") { c =>
c.io.a.poke(true)
c.io.b.poke(false)
}
}
}
4 changes: 2 additions & 2 deletions src/test/tywaves/parsers/debuginfo/HglddParseTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,9 @@ class HglddParseTest extends AnyFlatSpec {
it should "parse hgldd with DebugIR" in {
// Generate the Hgldd file
val ddFile = GenerateHgldd(() => new Foo, tmpDir.toString) + "/Foo.dd"
val debugParser = new DebugIRParser
debugParser.parse(tmpDir.toString, ddFile)
val debugParser = new DebugIRParser(tmpDir.toString, ddFile)

println("Done")
}

}

0 comments on commit 5b480dd

Please sign in to comment.