// SPDX-License-Identifier: Apache-2.0 package firrtl.analyses import firrtl.Kind import firrtl.analyses.InstanceKeyGraph.InstanceKey import firrtl.annotations.TargetToken.{Instance, OfModule} import firrtl.annotations._ import firrtl.ir.{Circuit, DefInstance} /** Use to construct [[CircuitGraph]] * Also contains useful related functions */ object CircuitGraph { /** Build a CircuitGraph * [[firrtl.ir.Circuit]] must be of MiddleForm or lower * @param circuit * @return */ def apply(circuit: Circuit): CircuitGraph = new CircuitGraph(ConnectionGraph(circuit)) /** Return a nicely-formatted string of a path of [[firrtl.annotations.ReferenceTarget]] * @param connectionPath * @param tab * @return */ def prettyToString(connectionPath: Seq[ReferenceTarget], tab: String = ""): String = { tab + connectionPath.mkString(s"\n$tab") } } /** Graph-representation of a FIRRTL Circuit * * Requires Middle FIRRTL * Useful for writing design-specific custom-transforms that require connectivity information * * @param connectionGraph Source-to-sink connectivity graph */ class CircuitGraph private[analyses] (connectionGraph: ConnectionGraph) { // Reverse (sink-to-source) connectivity graph private lazy val reverseConnectionGraph = connectionGraph.reverseConnectionGraph // AST Circuit private val circuit = connectionGraph.circuit // AST Information private val irLookup = connectionGraph.irLookup // Module/Instance Hierarchy information private lazy val instanceGraph = InstanceKeyGraph(circuit) // Per module, which modules does it instantiate private lazy val moduleChildren = instanceGraph.getChildInstances.toMap // Top-level module target private val main = ModuleTarget(circuit.main, circuit.main) /** Given a signal, return the signals that it drives * @param source * @return */ def fanOutSignals(source: ReferenceTarget): Set[ReferenceTarget] = connectionGraph.getEdges(source).toSet /** Given a signal, return the signals that drive it * @param sink * @return */ def fanInSignals(sink: ReferenceTarget): Set[ReferenceTarget] = reverseConnectionGraph.getEdges(sink).toSet /** Return the absolute paths of all instances of this module. * * For example: * - Top instantiates a1 of A and a2 of A * - A instantiates b1 of B and b2 of B * Then, absolutePaths of B will return: * - Seq(~Top|Top/a1:A/b1:B, ~Top|Top/a1:A/b2:B, ~Top|Top/a2:A/b1:B, ~Top|Top/a2:A/b2:B) * @param mt * @return */ def absolutePaths(mt: ModuleTarget): Seq[IsModule] = instanceGraph.findInstancesInHierarchy(mt.module).map { case seq if seq.nonEmpty => seq.foldLeft(CircuitTarget(circuit.main).module(circuit.main): IsModule) { case (it, InstanceKey(instance, ofModule)) => it.instOf(instance, ofModule) } } /** Return the sequence of nodes from source to sink, inclusive * @param source * @param sink * @return */ def connectionPath(source: ReferenceTarget, sink: ReferenceTarget): Seq[ReferenceTarget] = connectionGraph.path(source, sink) /** Return a reference to all nodes of given kind, directly contained in the referenced module/instance * Path can be either a module, or an instance * @param path * @param kind * @return */ def localReferences(path: IsModule, kind: Kind): Seq[ReferenceTarget] = { val leafModule = path.leafModule irLookup.kindFinder(ModuleTarget(circuit.main, leafModule), kind).map(_.setPathTarget(path)) } /** Return a reference to all nodes of given kind, contained in the referenced module/instance or any child instance * Path can be either a module, or an instance * @param kind * @param path * @return */ def deepReferences(kind: Kind, path: IsModule = ModuleTarget(circuit.main, circuit.main)): Seq[ReferenceTarget] = { val leafModule = path.leafModule val children = moduleChildren(leafModule) val localRefs = localReferences(path, kind) localRefs ++ children.flatMap { child => deepReferences(kind, path.instOf(child.name, child.module)) } } /** Return all absolute references to signals of the given kind directly contained in the module * @param moduleTarget * @param kind * @return */ def absoluteReferences(moduleTarget: ModuleTarget, kind: Kind): Seq[ReferenceTarget] = { localReferences(moduleTarget, kind).flatMap(makeAbsolute) } /** Given a reference, return all instances of that reference (i.e. with absolute paths) * @param reference * @return */ def makeAbsolute(reference: ReferenceTarget): Seq[ReferenceTarget] = { absolutePaths(reference.moduleTarget).map(abs => reference.setPathTarget(abs)) } }