aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAlbert Magyar2019-10-23 15:44:58 -0700
committerAlbert Magyar2019-10-24 12:21:03 -0700
commit0a9a3241fca1a9cf020025fbfabba76e6fe17366 (patch)
tree77d6729d6e0835c64da0eaf0ccc21d106ee74805 /src
parent746e281453f18926b175a58e8a32c45640fc9d28 (diff)
Enhance CheckCombLoops errors with connection info
* Closes #1203
Diffstat (limited to 'src')
-rw-r--r--src/main/scala/firrtl/transforms/CheckCombLoops.scala168
1 files changed, 92 insertions, 76 deletions
diff --git a/src/main/scala/firrtl/transforms/CheckCombLoops.scala b/src/main/scala/firrtl/transforms/CheckCombLoops.scala
index d1d98872..bb5d88e7 100644
--- a/src/main/scala/firrtl/transforms/CheckCombLoops.scala
+++ b/src/main/scala/firrtl/transforms/CheckCombLoops.scala
@@ -11,14 +11,51 @@ import firrtl.passes.{Errors, PassException}
import firrtl.traversals.Foreachers._
import firrtl.annotations._
import firrtl.Utils.throwInternalError
-import firrtl.graph.{MutableDiGraph,DiGraph}
+import firrtl.graph._
import firrtl.analyses.InstanceGraph
import firrtl.options.{RegisteredTransform, ShellOption}
+/*
+ * A case class that represents a net in the circuit. This is
+ * necessary since combinational loop checking is an analysis on the
+ * netlist of the circuit; the fields are specialized for low
+ * FIRRTL. Since all wires are ground types, a given ground type net
+ * may only be a subfield of an instance or a memory
+ * port. Therefore, it is uniquely specified within its module
+ * context by its name, its optional parent instance (a WDefInstance
+ * or WDefMemory), and its optional memory port name.
+ */
+case class LogicNode(name: String, inst: Option[String] = None, memport: Option[String] = None)
+
+object LogicNode {
+ def apply(e: Expression): LogicNode = e match {
+ case idx: WSubIndex =>
+ LogicNode(idx.expr)
+ case r: WRef =>
+ LogicNode(r.name)
+ case s: WSubField =>
+ s.expr match {
+ case modref: WRef =>
+ LogicNode(s.name,Some(modref.name))
+ case memport: WSubField =>
+ memport.expr match {
+ case memref: WRef =>
+ LogicNode(s.name,Some(memref.name),Some(memport.name))
+ case _ => throwInternalError(s"LogicNode: unrecognized subsubfield expression - $memport")
+ }
+ case _ => throwInternalError(s"LogicNode: unrecognized subfield expression - $s")
+ }
+ }
+}
+
object CheckCombLoops {
+ type AbstractConnMap = DiGraph[LogicNode]
+ type ConnMap = DiGraph[LogicNode] with EdgeData[LogicNode, Info]
+ type MutableConnMap = MutableDiGraph[LogicNode] with MutableEdgeData[LogicNode, Info]
+
+
class CombLoopException(info: Info, mname: String, cycle: Seq[String]) extends PassException(
s"$info: [module $mname] Combinational loop detected:\n" + cycle.mkString("\n"))
-
}
case object DontCheckCombLoopsAnnotation extends NoTargetAnnotation
@@ -70,63 +107,33 @@ class CheckCombLoops extends Transform with RegisteredTransform {
toAnnotationSeq = (_: Unit) => Seq(DontCheckCombLoopsAnnotation),
helpText = "Disable combinational loop checking" ) )
- /*
- * A case class that represents a net in the circuit. This is
- * necessary since combinational loop checking is an analysis on the
- * netlist of the circuit; the fields are specialized for low
- * FIRRTL. Since all wires are ground types, a given ground type net
- * may only be a subfield of an instance or a memory
- * port. Therefore, it is uniquely specified within its module
- * context by its name, its optional parent instance (a WDefInstance
- * or WDefMemory), and its optional memory port name.
- */
- private case class LogicNode(name: String, inst: Option[String] = None, memport: Option[String] = None)
-
- private def toLogicNode(e: Expression): LogicNode = e match {
- case idx: WSubIndex =>
- toLogicNode(idx.expr)
- case r: WRef =>
- LogicNode(r.name)
- case s: WSubField =>
- s.expr match {
- case modref: WRef =>
- LogicNode(s.name,Some(modref.name))
- case memport: WSubField =>
- memport.expr match {
- case memref: WRef =>
- LogicNode(s.name,Some(memref.name),Some(memport.name))
- case _ => throwInternalError(s"toLogicNode: unrecognized subsubfield expression - $memport")
- }
- case _ => throwInternalError(s"toLogicNode: unrecognized subfield expression - $s")
- }
- }
-
-
- private def getExprDeps(deps: MutableDiGraph[LogicNode], v: LogicNode)(e: Expression): Unit = e match {
- case r: WRef => deps.addEdgeIfValid(v, toLogicNode(r))
- case s: WSubField => deps.addEdgeIfValid(v, toLogicNode(s))
- case _ => e.foreach(getExprDeps(deps, v))
+ private def getExprDeps(deps: MutableConnMap, v: LogicNode, info: Info)(e: Expression): Unit = e match {
+ case r: WRef => deps.addEdgeIfValid(v, LogicNode(r), info)
+ case s: WSubField => deps.addEdgeIfValid(v, LogicNode(s), info)
+ case _ => e.foreach(getExprDeps(deps, v, info))
}
private def getStmtDeps(
- simplifiedModules: mutable.Map[String,DiGraph[LogicNode]],
- deps: MutableDiGraph[LogicNode])(s: Statement): Unit = s match {
- case Connect(_,loc,expr) =>
- val lhs = toLogicNode(loc)
+ simplifiedModules: mutable.Map[String, AbstractConnMap],
+ deps: MutableConnMap)(s: Statement): Unit = s match {
+ case Connect(info, loc, expr) =>
+ val lhs = LogicNode(loc)
if (deps.contains(lhs)) {
- getExprDeps(deps, lhs)(expr)
+ getExprDeps(deps, lhs, info)(expr)
}
case w: DefWire =>
deps.addVertex(LogicNode(w.name))
- case n: DefNode =>
- val lhs = LogicNode(n.name)
+ case DefNode(info, name, value) =>
+ val lhs = LogicNode(name)
deps.addVertex(lhs)
- getExprDeps(deps, lhs)(n.value)
+ getExprDeps(deps, lhs, info)(value)
case m: DefMemory if (m.readLatency == 0) =>
for (rp <- m.readers) {
- val dataNode = deps.addVertex(LogicNode("data",Some(m.name),Some(rp)))
- deps.addEdge(dataNode, deps.addVertex(LogicNode("addr",Some(m.name),Some(rp))))
- deps.addEdge(dataNode, deps.addVertex(LogicNode("en",Some(m.name),Some(rp))))
+ val dataNode = deps.addVertex(LogicNode("data", Some(m.name), Some(rp)))
+ val addr = LogicNode("addr", Some(m.name), Some(rp))
+ val en = LogicNode("en", Some(m.name), Some(rp))
+ deps.addEdge(dataNode, deps.addVertex(addr), m.info)
+ deps.addEdge(dataNode, deps.addVertex(en), m.info)
}
case i: WDefInstance =>
val iGraph = simplifiedModules(i.module).transformNodes(n => n.copy(inst = Some(i.name)))
@@ -136,6 +143,11 @@ class CheckCombLoops extends Transform with RegisteredTransform {
s.foreach(getStmtDeps(simplifiedModules,deps))
}
+ // Pretty-print a LogicNode with a prepended hierarchical path
+ private def prettyPrintAbsoluteRef(hierPrefix: Seq[String], node: LogicNode): String = {
+ (hierPrefix ++ node.inst ++ node.memport :+ node.name).mkString(".")
+ }
+
/*
* Recover the full path from a path passing through simplified
* instances. Since edges may pass through simplified instances, the
@@ -144,23 +156,25 @@ class CheckCombLoops extends Transform with RegisteredTransform {
*/
private def expandInstancePaths(
m: String,
- moduleGraphs: mutable.Map[String,DiGraph[LogicNode]],
- moduleDeps: Map[String, Map[String,String]],
- prefix: Seq[String],
+ moduleGraphs: mutable.Map[String, ConnMap],
+ moduleDeps: Map[String, Map[String, String]],
+ hierPrefix: Seq[String],
path: Seq[LogicNode]): Seq[String] = {
- def absNodeName(prefix: Seq[String], n: LogicNode) =
- (prefix ++ n.inst ++ n.memport :+ n.name).mkString(".")
- val pathNodes = (path zip path.tail) map { case (a, b) =>
- if (a.inst.isDefined && !a.memport.isDefined && a.inst == b.inst) {
- val child = moduleDeps(m)(a.inst.get)
- val newprefix = prefix :+ a.inst.get
- val subpath = moduleGraphs(child).path(b.copy(inst=None),a.copy(inst=None)).tail.reverse
- expandInstancePaths(child,moduleGraphs,moduleDeps,newprefix,subpath)
+ // Recover info from edge data, add to error string
+ def info(u: LogicNode, v: LogicNode): String =
+ moduleGraphs(m).getEdgeData(u, v).map(_.toString).mkString("\t", "", "")
+ // lhs comes after rhs
+ val pathNodes = (path zip path.tail) map { case (rhs, lhs) =>
+ if (lhs.inst.isDefined && !lhs.memport.isDefined && lhs.inst == rhs.inst) {
+ val child = moduleDeps(m)(lhs.inst.get)
+ val newHierPrefix = hierPrefix :+ lhs.inst.get
+ val subpath = moduleGraphs(child).path(lhs.copy(inst=None),rhs.copy(inst=None)).reverse
+ expandInstancePaths(child, moduleGraphs, moduleDeps, newHierPrefix, subpath)
} else {
- Seq(absNodeName(prefix,a))
+ Seq(prettyPrintAbsoluteRef(hierPrefix, lhs) ++ info(lhs, rhs))
}
}
- pathNodes.flatten :+ absNodeName(prefix, path.last)
+ pathNodes.flatten
}
/*
@@ -216,36 +230,38 @@ class CheckCombLoops extends Transform with RegisteredTransform {
val iGraph = new InstanceGraph(c).graph
val moduleDeps = iGraph.getEdgeMap.map({ case (k,v) => (k.module, (v map { i => (i.name, i.module) }).toMap) }).toMap
val topoSortedModules = iGraph.transformNodes(_.module).linearize.reverse map { moduleMap(_) }
- val moduleGraphs = new mutable.HashMap[String,DiGraph[LogicNode]]
- val simplifiedModuleGraphs = new mutable.HashMap[String,DiGraph[LogicNode]]
+ val moduleGraphs = new mutable.HashMap[String, ConnMap]
+ val simplifiedModuleGraphs = new mutable.HashMap[String, AbstractConnMap]
topoSortedModules.foreach {
case em: ExtModule =>
val portSet = em.ports.map(p => LogicNode(p.name)).toSet
- val extModuleDeps = new MutableDiGraph[LogicNode]
+ val extModuleDeps = new MutableDiGraph[LogicNode] with MutableEdgeData[LogicNode, Info]
portSet.foreach(extModuleDeps.addVertex(_))
extModulePaths.getOrElse(ModuleTarget(c.main, em.name), Nil).collect {
case a: ExtModulePathAnnotation => extModuleDeps.addPairWithEdge(LogicNode(a.sink.ref), LogicNode(a.source.ref))
}
- moduleGraphs(em.name) = DiGraph(extModuleDeps).simplify(portSet)
- simplifiedModuleGraphs(em.name) = moduleGraphs(em.name)
+ moduleGraphs(em.name) = extModuleDeps
+ simplifiedModuleGraphs(em.name) = extModuleDeps.simplify(portSet)
case m: Module =>
val portSet = m.ports.map(p => LogicNode(p.name)).toSet
- val internalDeps = new MutableDiGraph[LogicNode]
+ val internalDeps = new MutableDiGraph[LogicNode] with MutableEdgeData[LogicNode, Info]
portSet.foreach(internalDeps.addVertex(_))
m.foreach(getStmtDeps(simplifiedModuleGraphs, internalDeps))
- val moduleGraph = DiGraph(internalDeps)
- moduleGraphs(m.name) = moduleGraph
+ moduleGraphs(m.name) = internalDeps
simplifiedModuleGraphs(m.name) = moduleGraphs(m.name).simplify(portSet)
// Find combinational nodes with self-edges; this is *NOT* the same as length-1 SCCs!
- for (unitLoopNode <- moduleGraph.getVertices.filter(v => moduleGraph.getEdges(v).contains(v))) {
+ for (unitLoopNode <- internalDeps.getVertices.filter(v => internalDeps.getEdges(v).contains(v))) {
errors.append(new CombLoopException(m.info, m.name, Seq(unitLoopNode.name)))
}
- for (scc <- moduleGraph.findSCCs.filter(_.length > 1)) {
- val sccSubgraph = moduleGraph.subgraph(scc.toSet)
+ for (scc <- internalDeps.findSCCs.filter(_.length > 1)) {
+ val sccSubgraph = internalDeps.subgraph(scc.toSet)
val cycle = findCycleInSCC(sccSubgraph)
- (cycle zip cycle.tail).foreach({ case (a,b) => require(moduleGraph.getEdges(a).contains(b)) })
- val expandedCycle = expandInstancePaths(m.name, moduleGraphs, moduleDeps, Seq(m.name), cycle.reverse)
- errors.append(new CombLoopException(m.info, m.name, expandedCycle))
+ (cycle zip cycle.tail).foreach({ case (a,b) => require(internalDeps.getEdges(a).contains(b)) })
+ // Reverse to make sure LHS comes after RHS, print repeated vertex at start for legibility
+ val intuitiveCycle = cycle.reverse
+ val repeatedInitial = prettyPrintAbsoluteRef(Seq(m.name), intuitiveCycle.head)
+ val expandedCycle = expandInstancePaths(m.name, moduleGraphs, moduleDeps, Seq(m.name), intuitiveCycle)
+ errors.append(new CombLoopException(m.info, m.name, repeatedInitial +: expandedCycle))
}
case m => throwInternalError(s"Module ${m.name} has unrecognized type")
}