Materialized bridge FCCAs in BridgeExtraction (#1225)

This patch moves the materialization of FCCAs connected to bridges to the BridgeExtraction stage.
Up to that point, information about channels connected to bridges is carried in the bridgeChannels field of the SerializableBridgeAnnotation. Bridges with IO towards the target must construct BridgeChannel decriptors
for the channels they want materialized instead of eagerly creating FCCAs.
This commit is contained in:
Nandor Licker 2022-10-05 22:56:38 +03:00 committed by GitHub
parent f9afbcceb8
commit 760411bc93
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 221 additions and 152 deletions

View File

@ -2,8 +2,10 @@
package midas.passes
import midas.widgets.{BridgeAnnotation, ClockBridgeModule}
import midas.passes.fame.{PromoteSubmodule, PromoteSubmoduleAnnotation, FAMEChannelConnectionAnnotation}
import midas.widgets.{BridgeAnnotation, ClockBridgeModule, FindScaledPeriodGCD}
import midas.widgets.{PipeBridgeChannel, ClockBridgeChannel, ReadyValidBridgeChannel}
import midas.passes.fame.{PromoteSubmodule, PromoteSubmoduleAnnotation, FAMEChannelConnectionAnnotation, RTRenamer}
import midas.passes.fame.{PipeChannel, TargetClockChannel, DecoupledReverseChannel, DecoupledForwardChannel}
import firrtl._
import firrtl.ir._
@ -84,16 +86,13 @@ private[passes] class BridgeExtraction extends firrtl.Transform {
val topModule = c.modules.find(_.name == c.main).get
// Collect all bridge modules
val bridgeAnnos = mutable.ArrayBuffer[BridgeAnnotation]()
val fcaAnnos = mutable.ArrayBuffer[FAMEChannelConnectionAnnotation]()
val otherAnnos = state.annotations.flatMap({
case anno: BridgeAnnotation => bridgeAnnos += anno; None
case fca: FAMEChannelConnectionAnnotation => fcaAnnos += fca; None
case otherAnno => Some(otherAnno)
})
val bridgeAnnoMap = bridgeAnnos.map(anno => anno.target.module -> anno ).toMap
val fcaMap = fcaAnnos.groupBy(_.getBridgeModule).toMap
val portInstPairs = new mutable.ArrayBuffer[(String, String)]()
val instList = new mutable.ArrayBuffer[(String, String)]()
@ -110,9 +109,68 @@ private[passes] class BridgeExtraction extends firrtl.Transform {
s"Multiple ClockBridge instances found: ${clockBridgeInsts.mkString("\n")} ${bridgeInstMessage}")
val ioAnnotations = portInstPairs.flatMap({ case (port, inst) =>
val updatedBridgeAnno = bridgeAnnoMap(instMap(inst)).toIOAnnotation(port)
val updatedFCAAnnos = fcaMap(instMap(inst)).map(_.moveFromBridge(port))
Seq(updatedBridgeAnno) ++ updatedFCAAnnos
val bridge = bridgeAnnoMap(instMap(inst))
val updatedBridgeAnno = bridge.toIOAnnotation(port)
def buildRenamer(rTs: Seq[ReferenceTarget]) = {
def updateRT(rT: ReferenceTarget): ReferenceTarget =
ModuleTarget(rT.circuit, rT.circuit).ref(port).field(rT.ref)
RTRenamer.exact(RenameMap(Map((rTs.map(rT => rT -> Seq(updateRT(rT)))):_*)))
}
val bridgeFCCAAnnos : AnnotationSeq = bridge.bridgeChannels.flatMap({
case PipeBridgeChannel(name, clock, sinks, sources, latency) => {
val renamer = buildRenamer(sinks ++ sources ++ Seq(clock))
Seq(FAMEChannelConnectionAnnotation(
s"${port}_${name}",
PipeChannel(latency),
Some(renamer(clock)),
if (sources.isEmpty) { None } else { Some(sources.map(renamer(_))) },
if (sinks.isEmpty) { None } else { Some(sinks.map(renamer(_))) }
))
}
case ClockBridgeChannel(name, sinks, clocks, clockMFMRs) => {
val renamer = buildRenamer(sinks)
Seq(FAMEChannelConnectionAnnotation(
s"${port}_${name}",
channelInfo = TargetClockChannel(clocks, clockMFMRs),
clock = None,
sinks = Some(sinks.map(renamer(_))),
sources = None
))
}
case ReadyValidBridgeChannel(fwdName, revName, clock, sinks, sources, valid, ready) => {
val renamer = buildRenamer(sinks ++ sources ++ Seq(clock, valid, ready))
val clockRT = renamer(clock)
val validRT = renamer(valid)
val readyRT = renamer(ready)
Seq(
FAMEChannelConnectionAnnotation(
s"${port}_${fwdName}",
if (sinks.isEmpty) {
DecoupledForwardChannel.source(validRT, readyRT)
} else {
DecoupledForwardChannel.sink(validRT, readyRT)
},
Some(clockRT),
if (sources.isEmpty) { None } else { Some(sources.map(renamer(_))) },
if (sinks.isEmpty) { None } else { Some(sinks.map(renamer(_))) }
),
FAMEChannelConnectionAnnotation(
s"${port}_${revName}",
DecoupledReverseChannel,
Some(clockRT),
if (sources.isEmpty) { Some(Seq(readyRT)) } else { None },
if (sinks.isEmpty) { Some(Seq(readyRT)) } else { None }
)
)
}
})
Seq(updatedBridgeAnno) ++ bridgeFCCAAnnos
})
state.copy(annotations = otherAnnos ++ ioAnnotations)

View File

@ -65,19 +65,6 @@ case class FAMEChannelConnectionAnnotation(
def getBridgeModule(): String = sources.getOrElse(sinks.get).head.module
def moveFromBridge(portName: String): FAMEChannelConnectionAnnotation = {
def updateRT(rT: ReferenceTarget): ReferenceTarget = ModuleTarget(rT.circuit, rT.circuit).ref(portName).field(rT.ref)
require(sources == None || sinks == None, "Bridge-connected channels cannot loopback")
val rTs = sources.getOrElse(sinks.get) ++ clock ++ (channelInfo match {
case i: DecoupledForwardChannel => Seq(i.readySink.getOrElse(i.readySource.get))
case other => Seq()
})
val localRenames = RenameMap(Map((rTs.map(rT => rT -> Seq(updateRT(rT)))):_*))
copy(globalName = s"${portName}_${globalName}").update(localRenames).head.asInstanceOf[this.type]
}
override def getTargets: Seq[ReferenceTarget] = clock ++: (sources.toSeq.flatten ++ sinks.toSeq.flatten)
}

View File

@ -63,27 +63,19 @@ trait Bridge[HPType <: Record with HasChannels, WidgetType <: BridgeModule[HPTyp
annotate(new ChiselAnnotation { def toFirrtl = {
BridgeAnnotation(
self.toNamed.toTarget,
bridgeIO.allChannelNames,
bridgeIO.bridgeChannels,
widgetClass = widgetClassSymbol.fullName,
widgetConstructorKey = constructorArg)
}
})
// Emit annotations to capture channel information
bridgeIO.generateAnnotations()
}
}
trait HasChannels {
/**
* Called to emit FCCAs in the target RTL in order to assign the target
* port's fields to channels.
* Returns a list of channel descriptors.
*/
def generateAnnotations(): Unit
/**
* Returns a list of channel names for which FAMEChannelConnectionAnnotations have been generated
*/
def allChannelNames(): Seq[String]
def bridgeChannels(): Seq[BridgeChannel]
// Called in FPGATop to connect the instantiated bridge to channel ports on the wrapper
private[midas] def connectChannels2Port(bridgeAnno: BridgeIOAnnotation, channels: TargetChannelIO): Unit

View File

@ -3,20 +3,83 @@
package midas.widgets
import chisel3._
import firrtl.{RenameMap}
import firrtl.annotations.{SingleTargetAnnotation} // Deprecated
import firrtl.annotations.{ReferenceTarget, ModuleTarget, HasSerializationHints}
import firrtl.annotations.{Annotation, ReferenceTarget, ModuleTarget, HasSerializationHints}
import freechips.rocketchip.config.Parameters
import midas.passes.fame.{RTRenamer}
import midas.targetutils.FAMEAnnotation
sealed trait BridgeChannel {
def update(renames: RenameMap): BridgeChannel
}
/**
* Descriptor for a pipe channel ending at the bridge.
*/
case class PipeBridgeChannel(
name: String,
clock: ReferenceTarget,
sinks: Seq[ReferenceTarget],
sources: Seq[ReferenceTarget],
latency: Int
) extends BridgeChannel {
def update(renames: RenameMap): BridgeChannel = {
val renamer = RTRenamer.exact(renames)
PipeBridgeChannel(name, renamer(clock), sinks.map(renamer), sources.map(renamer), latency)
}
}
/**
* Descriptor for a clock channel originating from a clock bridge.
*/
case class ClockBridgeChannel(
name: String,
sinks: Seq[ReferenceTarget],
clocks: Seq[RationalClock],
clockMFMRs: Seq[Int]
) extends BridgeChannel {
def update(renames: RenameMap): BridgeChannel = {
val renamer = RTRenamer.exact(renames)
ClockBridgeChannel(name, sinks.map(renamer), clocks, clockMFMRs)
}
}
/**
* Descriptor for a Ready-Valid channel originating from a bridge.
*/
case class ReadyValidBridgeChannel(
fwdName: String,
revName: String,
clock: ReferenceTarget,
sinks: Seq[ReferenceTarget],
sources: Seq[ReferenceTarget],
valid: ReferenceTarget,
ready: ReferenceTarget,
) extends BridgeChannel {
def update(renames: RenameMap): BridgeChannel = {
val renamer = RTRenamer.exact(renames)
ReadyValidBridgeChannel(
fwdName,
revName,
renamer(clock),
sinks.map(renamer),
sources.map(renamer),
renamer(valid),
renamer(ready)
)
}
}
/**
* A serializable annotation emitted by Chisel Modules that extend Bridge
*
* @param target The module representing an Bridge. Typically a black box
*
* @param channelNames A list of channel names that match the globalName emitted in the FCCAs
* associated with this bridge. We use these strings to look up those annotations
* @param bridgeChannels A list of descriptors for the channels attached to the
* bridge. FCCAs are materialized from these descriptors.
*
* @param widgetClass The full class name of the BridgeModule generator
*
@ -31,7 +94,7 @@ import midas.targetutils.FAMEAnnotation
case class BridgeAnnotation(
target: ModuleTarget,
channelNames: Seq[String],
bridgeChannels: Seq[BridgeChannel],
widgetClass: String,
widgetConstructorKey: Option[_ <: AnyRef])
extends SingleTargetAnnotation[ModuleTarget] with FAMEAnnotation with HasSerializationHints {
@ -41,21 +104,32 @@ case class BridgeAnnotation(
* a ReferenceTarget based one that can be attached to newly created IO on the top-level
*/
def toIOAnnotation(port: String): BridgeIOAnnotation = {
val channelNames = bridgeChannels.flatMap({
case ch: PipeBridgeChannel => Seq(ch.name)
case ch: ClockBridgeChannel => Seq(ch.name)
case ch: ReadyValidBridgeChannel => Seq(ch.fwdName, ch.revName)
})
val channelMapping = channelNames.map(oldName => oldName -> s"${port}_$oldName")
BridgeIOAnnotation(target.copy(module = target.circuit).ref(port),
channelMapping.toMap,
widgetClass = widgetClass,
widgetConstructorKey = widgetConstructorKey)
widgetConstructorKey = widgetConstructorKey
)
}
def typeHints() = widgetConstructorKey match {
def typeHints() = bridgeChannels.map(_.getClass) ++ (widgetConstructorKey match {
// If the key has extra type hints too, grab them as well
case Some(key: HasSerializationHints) => key.getClass +: key.typeHints
case Some(key) => Seq(key.getClass)
case None => Seq()
}
})
def duplicate(n: ModuleTarget) = this.copy(target)
override def update(renames: RenameMap): Seq[Annotation] = {
val renamer = RTRenamer.exact(renames)
Seq(BridgeAnnotation(target, bridgeChannels.map(_.update(renames)), widgetClass, widgetConstructorKey))
}
}
/**

View File

@ -20,27 +20,9 @@ import scala.collection.mutable
* This becomes more useful when there are channel types.
*
*/
sealed trait ChannelMetadata {
def clockRT(): Option[ReferenceTarget]
def chInfo(): FAMEChannelInfo
def fieldRTs(): Seq[ReferenceTarget]
def bridgeSunk: Boolean
// Channel name is passed as an argument here because it is determined reflexively after the record
// is constructed -- it's not avaiable when the metadata instance is created
def generateAnnotations(chName: String): Seq[ChiselAnnotation] = {
Seq(new ChiselAnnotation { def toFirrtl =
if (bridgeSunk) {
FAMEChannelConnectionAnnotation.source(chName, chInfo, clockRT, fieldRTs)
} else {
FAMEChannelConnectionAnnotation.sink(chName, chInfo, clockRT, fieldRTs)
}
})
}
}
case class PipeChannelMetadata(field: Data, clock: Clock, bridgeSunk: Boolean, latency: Int = 0) extends ChannelMetadata {
case class PipeChannelMetadata(field: Data, clock: Clock, bridgeSunk: Boolean, latency: Int = 0) {
def fieldRTs = Seq(field.toTarget)
def clockRT = Some(clock.toTarget)
def clockRT = clock.toTarget
def chInfo = midas.passes.fame.PipeChannel(latency)
}
@ -63,7 +45,7 @@ trait ChannelizedHostPortIO extends HasChannels { this: Record =>
// of the target-side of the bridge)
// _2 -> The associated host-channel (an actual element in this aggregate, unlike above)
// _3 -> Associated metadate which will encode for the FCCA
private val channels = mutable.ArrayBuffer[(Data, ChannelType[_ <: Data], ChannelMetadata)]()
private val channels = mutable.ArrayBuffer[(Data, ChannelType[_ <: Data], PipeChannelMetadata)]()
// These will only be called after the record has been finalized.
lazy private val fieldToChannelMap = Map((channels.map(t => t._1 -> t._2)):_*)
@ -123,14 +105,6 @@ trait ChannelizedHostPortIO extends HasChannels { this: Record =>
ch
}
// Implement methods of HasChannels
def generateAnnotations(): Unit = {
for ((targetField, channelElement, metadata) <- channels) {
checkFieldDirection(targetField, metadata.bridgeSunk)
metadata.generateAnnotations(reverseElementMap(channelElement)).map(a => annotate(a))
}
}
def connectChannels2Port(bridgeAnno: BridgeIOAnnotation, targetIO: TargetChannelIO): Unit = {
val local2globalName = bridgeAnno.channelMapping.toMap
for ((_, channel, metadata) <- channels) {
@ -143,5 +117,12 @@ trait ChannelizedHostPortIO extends HasChannels { this: Record =>
}
}
def allChannelNames() = channels.map(ch => reverseElementMap(ch._2))
def bridgeChannels = channels.map({ case (_, ch, meta) =>
val name = reverseElementMap(ch)
if (meta.bridgeSunk) {
PipeBridgeChannel(name, meta.clockRT, Seq(), meta.fieldRTs, 0)
} else {
PipeBridgeChannel(name, meta.clockRT, meta.fieldRTs, Seq(), 0)
}
})
}

View File

@ -4,7 +4,7 @@ package midas.widgets
import midas.core.{TargetChannelIO, SimUtils}
import midas.core.SimUtils.{RVChTuple}
import midas.passes.fame.{FAMEChannelConnectionAnnotation, TargetClockChannel}
import midas.passes.fame.{FAMEChannelConnectionAnnotation, TargetClockChannel, RTRenamer}
import freechips.rocketchip.config.Parameters
import freechips.rocketchip.util.DensePrefixSum
@ -12,7 +12,9 @@ import freechips.rocketchip.util.DensePrefixSum
import chisel3._
import chisel3.util._
import chisel3.experimental.{BaseModule, Direction, ChiselAnnotation, annotate}
import firrtl.annotations.{ModuleTarget, ReferenceTarget}
import firrtl.{RenameMap}
import firrtl.annotations.{Annotation, ModuleTarget, ReferenceTarget}
/**
* Defines a generated clock as a rational multiple of some reference clock. The generated
@ -87,20 +89,16 @@ class RationalClockBridge(val allClocks: Seq[RationalClock]) extends BlackBox wi
annotate(new ChiselAnnotation { def toFirrtl =
BridgeAnnotation(
target = outer.toTarget,
channelNames = Seq(clockChannelName),
bridgeChannels = Seq(
ClockBridgeChannel(
name = clockChannelName,
sinks = io.clocks.map(_.toTarget),
clocks = allClocks,
clockMFMRs)),
widgetClass = classOf[ClockBridgeModule].getName,
widgetConstructorKey = Some(ClockParameters(allClocks))
)
})
annotate(new ChiselAnnotation { def toFirrtl =
FAMEChannelConnectionAnnotation(
clockChannelName,
channelInfo = TargetClockChannel(allClocks, clockMFMRs),
clock = None, // Clock channels do not have a reference clock
sinks = Some(io.clocks.map(_.toTarget)),
sources = None
)
})
}
object RationalClockBridge {
@ -127,9 +125,11 @@ object RationalClockBridge {
class ClockTokenVector(numClocks: Int) extends Bundle with HasChannels with ClockBridgeConsts {
val clocks = new DecoupledIO(Vec(numClocks, Bool()))
def allChannelNames = Seq(clockChannelName)
def bridgeChannels = Seq()
def connectChannels2Port(bridgeAnno: BridgeIOAnnotation, targetIO: TargetChannelIO): Unit =
targetIO.clockElement._2 <> clocks
def generateAnnotations(): Unit = {}
}

View File

@ -46,66 +46,6 @@ class HostPortIO[+T <: Data](private val targetPortProto: T) extends Record with
allTargetClocks.head
}
private def getRVChannelNames(channels: Seq[SimUtils.RVChTuple]): Seq[String] =
channels.flatMap({ channel =>
val (fwd, rev) = SimUtils.rvChannelNamePair(channel)
Seq(fwd, rev)
})
// Create a wire channel annotation
protected def generateWireChannelFCCAs(channels: Seq[(Data, String)], bridgeSunk: Boolean = false, latency: Int = 0): Unit = {
for ((field, chName) <- channels) {
annotate(new ChiselAnnotation { def toFirrtl =
if (bridgeSunk) {
FAMEChannelConnectionAnnotation.source(chName, PipeChannel(latency), Some(getClock.toNamed.toTarget), Seq(field.toNamed.toTarget))
} else {
FAMEChannelConnectionAnnotation.sink(chName, PipeChannel(latency), Some(getClock.toNamed.toTarget), Seq(field.toNamed.toTarget))
}
})
}
}
// Create Ready Valid channel annotations assuming bridge-sourced directions
protected def generateRVChannelFCCAs(channels: Seq[(ReadyValidIO[Data], String)], bridgeSunk: Boolean = false): Unit = {
for ((field, chName) <- channels) yield {
// Generate the forward channel annotation
val (fwdChName, revChName) = SimUtils.rvChannelNamePair(chName)
annotate(new ChiselAnnotation { def toFirrtl = {
val clockTarget = Some(getClock.toNamed.toTarget)
val validTarget = field.valid.toNamed.toTarget
val readyTarget = field.ready.toNamed.toTarget
val leafTargets = Seq(validTarget) ++ SimUtils.lowerAggregateIntoLeafTargets(field.bits)
// Bridge is the sink; it applies target backpressure
if (bridgeSunk) {
FAMEChannelConnectionAnnotation.source(
fwdChName,
DecoupledForwardChannel.source(validTarget, readyTarget),
clockTarget,
leafTargets
)
} else {
// Bridge is the source; it asserts target-valid and receives target-backpressure
FAMEChannelConnectionAnnotation.sink(
fwdChName,
DecoupledForwardChannel.sink(validTarget, readyTarget),
clockTarget,
leafTargets
)
}
}})
annotate(new ChiselAnnotation { def toFirrtl = {
val clockTarget = Some(getClock.toNamed.toTarget)
val readyTarget = Seq(field.ready.toNamed.toTarget)
if (bridgeSunk) {
FAMEChannelConnectionAnnotation.sink(revChName, DecoupledReverseChannel, clockTarget, readyTarget)
} else {
FAMEChannelConnectionAnnotation.source(revChName, DecoupledReverseChannel, clockTarget, readyTarget)
}
}})
}
}
// These are lazy because parsePorts needs a directioned gen; these can be called once
// this Record has been bound to Hardware
//private lazy val (ins, outs, rvIns, rvOuts) = SimUtils.parsePorts(targetPortProto, alsoFlattenRVPorts = false)
@ -180,16 +120,53 @@ class HostPortIO[+T <: Data](private val targetPortProto: T) extends Record with
SimUtils.findClocks(hBits).map(_ := false.B.asClock)
}
def generateAnnotations(): Unit = {
generateWireChannelFCCAs(inputWireChannels, bridgeSunk = true, latency = 1)
generateWireChannelFCCAs(outputWireChannels, bridgeSunk = false, latency = 1)
generateRVChannelFCCAs(inputRVChannels, bridgeSunk = true)
generateRVChannelFCCAs(outputRVChannels, bridgeSunk = false)
}
def allChannelNames: Seq[String] = ins.unzip._2 ++ outs.unzip._2 ++
(rvIns.unzip._2 ++ rvOuts.unzip._2).flatMap { ch =>
val (fwd, rev) = SimUtils.rvChannelNamePair(ch)
Seq(fwd, rev)
def bridgeChannels: Seq[BridgeChannel] = {
val clockRT = getClock.toNamed.toTarget
inputWireChannels.map({ case (field, chName) =>
PipeBridgeChannel(
chName,
clock = clockRT,
sinks = Seq(),
sources = Seq(field.toNamed.toTarget),
latency = 1
)
}) ++
outputWireChannels.map({ case (field, chName) =>
PipeBridgeChannel(
chName,
clock = clockRT,
sinks = Seq(field.toNamed.toTarget),
sources = Seq(),
latency = 1
)
}) ++
rvIns.map({ case (field, chName) =>
val (fwdChName, revChName) = SimUtils.rvChannelNamePair(chName)
val validTarget = field.valid.toNamed.toTarget
ReadyValidBridgeChannel(
fwdChName,
revChName,
clock = getClock.toNamed.toTarget,
sinks = Seq(),
sources = Seq(validTarget) ++ SimUtils.lowerAggregateIntoLeafTargets(field.bits),
valid = validTarget,
ready = field.ready.toNamed.toTarget
)
}) ++
rvOuts.map({ case (field, chName) =>
val (fwdChName, revChName) = SimUtils.rvChannelNamePair(chName)
val validTarget = field.valid.toNamed.toTarget
ReadyValidBridgeChannel(
fwdChName,
revChName,
clock = getClock.toNamed.toTarget,
sinks = Seq(validTarget) ++ SimUtils.lowerAggregateIntoLeafTargets(field.bits),
sources = Seq(),
valid = validTarget,
ready = field.ready.toNamed.toTarget
)
})
}
}