diff options
| author | Jack | 2022-03-15 19:37:37 +0000 |
|---|---|---|
| committer | Jack | 2022-03-15 19:37:37 +0000 |
| commit | 2f21943ff772da2171df866d4cee71dfa8127bf8 (patch) | |
| tree | d00c9059c9361920036e784425641288782515d5 /core/src/main | |
| parent | 1876e740a48be2e5ff5bd4fd6c2018927f1dcec2 (diff) | |
| parent | f26df23bbe0ae9b7162ed70369f24b01d75a1493 (diff) | |
Merge branch '3.5.x' into 3.5-release
Diffstat (limited to 'core/src/main')
| -rw-r--r-- | core/src/main/scala/chisel3/Bits.scala | 13 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/Mem.scala | 9 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/Module.scala | 40 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/experimental/dataview/package.scala | 10 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala | 70 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/internal/BiConnect.scala | 130 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/internal/Builder.scala | 1 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/internal/MonoConnect.scala | 206 |
8 files changed, 418 insertions, 61 deletions
diff --git a/core/src/main/scala/chisel3/Bits.scala b/core/src/main/scala/chisel3/Bits.scala index 8a616d02..4133592f 100644 --- a/core/src/main/scala/chisel3/Bits.scala +++ b/core/src/main/scala/chisel3/Bits.scala @@ -107,6 +107,12 @@ sealed abstract class Bits(private[chisel3] val width: Width) extends Element wi (((value >> castToInt(x, "Index")) & 1) == 1).asBool }.getOrElse { requireIsHardware(this, "bits to be indexed") + + widthOption match { + case Some(w) if x >= w => Builder.error(s"High index $x is out of range [0, ${w - 1}]") + case _ => + } + pushOp(DefPrim(sourceInfo, Bool(), BitsExtractOp, this.ref, ILit(x), ILit(x))) } } @@ -160,6 +166,13 @@ sealed abstract class Bits(private[chisel3] val width: Width) extends Element wi ((value >> y) & ((BigInt(1) << w) - 1)).asUInt(w.W) }.getOrElse { requireIsHardware(this, "bits to be sliced") + + widthOption match { + case Some(w) if y >= w => Builder.error(s"High and low indices $x and $y are both out of range [0, ${w - 1}]") + case Some(w) if x >= w => Builder.error(s"High index $x is out of range [0, ${w - 1}]") + case _ => + } + pushOp(DefPrim(sourceInfo, UInt(Width(w)), BitsExtractOp, this.ref, ILit(x), ILit(y))) } } diff --git a/core/src/main/scala/chisel3/Mem.scala b/core/src/main/scala/chisel3/Mem.scala index 3f37308c..36984a3a 100644 --- a/core/src/main/scala/chisel3/Mem.scala +++ b/core/src/main/scala/chisel3/Mem.scala @@ -246,6 +246,11 @@ sealed abstract class MemBase[T <: Data](val t: T, val length: BigInt) )( implicit compileOptions: CompileOptions ): T = { + if (Builder.currentModule != _parent) { + throwException( + s"Cannot create a memory port in a different module (${Builder.currentModule.get.name}) than where the memory is (${_parent.get.name})." + ) + } requireIsHardware(idx, "memory port index") val i = Vec.truncateIndex(idx, length)(sourceInfo, compileOptions) @@ -267,7 +272,7 @@ sealed abstract class MemBase[T <: Data](val t: T, val length: BigInt) * @note when multiple conflicting writes are performed on a Mem element, the * result is undefined (unlike Vec, where the last assignment wins) */ -sealed class Mem[T <: Data] private (t: T, length: BigInt) extends MemBase(t, length) +sealed class Mem[T <: Data] private[chisel3] (t: T, length: BigInt) extends MemBase(t, length) object SyncReadMem { @@ -345,7 +350,7 @@ object SyncReadMem { * @note when multiple conflicting writes are performed on a Mem element, the * result is undefined (unlike Vec, where the last assignment wins) */ -sealed class SyncReadMem[T <: Data] private (t: T, n: BigInt, val readUnderWrite: SyncReadMem.ReadUnderWrite) +sealed class SyncReadMem[T <: Data] private[chisel3] (t: T, n: BigInt, val readUnderWrite: SyncReadMem.ReadUnderWrite) extends MemBase[T](t, n) { override def read(x: UInt): T = macro SourceInfoTransform.xArg diff --git a/core/src/main/scala/chisel3/Module.scala b/core/src/main/scala/chisel3/Module.scala index 3611f5dd..84139630 100644 --- a/core/src/main/scala/chisel3/Module.scala +++ b/core/src/main/scala/chisel3/Module.scala @@ -101,8 +101,8 @@ object Module extends SourceInfoDoc { compileOptions: CompileOptions ): T = { val parent = Builder.currentModule - val module: T = bc // bc is actually evaluated here + if (!parent.isEmpty) { Builder.currentModule = parent } module } @@ -229,6 +229,8 @@ package internal { // Private internal class to serve as a _parent for Data in cloned ports private[chisel3] class ModuleClone[T <: BaseModule](val getProto: T) extends PseudoModule with IsClone[T] { override def toString = s"ModuleClone(${getProto})" + // Do not call default addId function, which may modify a module that is already "closed" + override def addId(d: HasId): Unit = () def getPorts = _portsRecord // ClonePorts that hold the bound ports for this module // Used for setting the refs of both this module and the Record @@ -246,8 +248,14 @@ package internal { } // Maps proto ports to module clone's ports private[chisel3] lazy val ioMap: Map[Data, Data] = { - val name2Port = getPorts.elements - getProto.getChiselPorts.map { case (name, data) => data -> name2Port(name) }.toMap + getProto match { + // BlackBox needs special handling for its pseduo-io Bundle + case protoBB: BlackBox => + Map(protoBB._io.get -> getPorts.elements("io")) + case _ => + val name2Port = getPorts.elements + getProto.getChiselPorts.map { case (name, data) => data -> name2Port(name) }.toMap + } } // This module doesn't actually exist in the FIRRTL so no initialization to do private[chisel3] def initializeInParent(parentCompileOptions: CompileOptions): Unit = () @@ -265,7 +273,17 @@ package internal { case bad => throwException(s"Internal Error! Cloned-module Record $record has unexpected ref $bad") } // Set both the record and the module to have the same instance name - record.setRef(ModuleCloneIO(getProto, instName), force = true) // force because we did .forceName first + val ref = ModuleCloneIO(getProto, instName) + record.setRef(ref, force = true) // force because we did .forceName first + getProto match { + // BlackBox needs special handling for its pseduo-io Bundle + case _: BlackBox => + // Override the io Bundle's ref so that it thinks it is the top for purposes of + // generating FIRRTL + record.elements("io").setRef(ref, force = true) + case _ => // Do nothing + } + this.setRef(Ref(instName)) } } @@ -307,6 +325,8 @@ package internal { override def toString = s"DefinitionClone(${getProto})" // No addition components are generated private[chisel3] def generateComponent(): Option[Component] = None + // Do not call default addId function, which may modify a module that is already "closed" + override def addId(d: HasId): Unit = () // Necessary for toTarget to work private[chisel3] def initializeInParent(parentCompileOptions: CompileOptions): Unit = () // Module name is the same as proto's module name @@ -325,8 +345,8 @@ package internal { * @note These are not true Data (the Record doesn't correspond to anything in the emitted * FIRRTL yet its elements *do*) so have some very specialized behavior. */ - private[chisel3] class ClonePorts(elts: Data*)(implicit compileOptions: CompileOptions) extends Record { - val elements = ListMap(elts.map(d => d.instanceName -> d.cloneTypeFull): _*) + private[chisel3] class ClonePorts(elts: (String, Data)*)(implicit compileOptions: CompileOptions) extends Record { + val elements = ListMap(elts.map { case (name, d) => name -> d.cloneTypeFull }: _*) def apply(field: String) = elements(field) override def cloneType = (new ClonePorts(elts: _*)).asInstanceOf[this.type] } @@ -347,12 +367,18 @@ package internal { // Fake Module to serve as the _parent of the cloned ports // We don't create this inside the ModuleClone because we need the ref to be set by the // currentModule (and not clonePorts) - val clonePorts = new ClonePorts(proto.getModulePorts: _*) + val clonePorts = proto match { + // BlackBox needs special handling for its pseduo-io Bundle + case b: BlackBox => + new ClonePorts(proto.getChiselPorts :+ ("io" -> b._io.get): _*) + case _ => new ClonePorts(proto.getChiselPorts: _*) + } clonePorts.bind(PortBinding(cloneParent)) clonePorts.setAllParents(Some(cloneParent)) cloneParent._portsRecord = clonePorts // Normally handled during Module construction but ClonePorts really lives in its parent's parent if (!compileOptions.explicitInvalidate) { + // FIXME This almost certainly doesn't work since clonePorts is not a real thing... pushCommand(DefInvalid(sourceInfo, clonePorts.ref)) } if (proto.isInstanceOf[Module]) { diff --git a/core/src/main/scala/chisel3/experimental/dataview/package.scala b/core/src/main/scala/chisel3/experimental/dataview/package.scala index 3278d82c..891ecb81 100644 --- a/core/src/main/scala/chisel3/experimental/dataview/package.scala +++ b/core/src/main/scala/chisel3/experimental/dataview/package.scala @@ -262,4 +262,14 @@ package object dataview { } } + /** Determine the target of a View if it is a single Target + * + * @note An Aggregate may be a view of unrelated [[Data]] (eg. like a Seq or tuple) and thus this + * there is no single Data representing the Target and this function will return None + * @return The single Data target of this view or None if a single Data doesn't exist + */ + private[chisel3] def reifyToAggregate(data: Data): Option[Aggregate] = reifySingleData(data) match { + case Some(a: Aggregate) => Some(a) + case other => None + } } diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala b/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala index 8552267a..60290f83 100644 --- a/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala +++ b/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala @@ -10,7 +10,7 @@ import scala.annotation.implicitNotFound import scala.collection.mutable.HashMap import chisel3._ import chisel3.experimental.dataview.{isView, reify, reifySingleData} -import chisel3.internal.firrtl.{Arg, ILit, Index, Slot, ULit} +import chisel3.internal.firrtl.{Arg, ILit, Index, ModuleIO, Slot, ULit} import chisel3.internal.{throwException, AggregateViewBinding, Builder, ChildBinding, ViewBinding, ViewParent} /** Represents lookup typeclass to determine how a value accessed from an original IsInstantiable @@ -19,7 +19,7 @@ import chisel3.internal.{throwException, AggregateViewBinding, Builder, ChildBin */ @implicitNotFound( "@public is only legal within a class or trait marked @instantiable, and only on vals of type" + - " Data, BaseModule, IsInstantiable, IsLookupable, or Instance[_], or in an Iterable, Option, or Either" + " Data, BaseModule, MemBase, IsInstantiable, IsLookupable, or Instance[_], or in an Iterable, Option, Either, or Tuple2" ) trait Lookupable[-B] { type C // Return type of the lookup @@ -123,8 +123,8 @@ object Lookupable { def unrollCoordinates(res: List[Arg], d: Data): (List[Arg], Data) = d.binding.get match { case ChildBinding(parent) => d.getRef match { - case arg @ (_: Slot | _: Index) => unrollCoordinates(arg :: res, parent) - case other => err(s"Unroll coordinates failed for '$arg'! Unexpected arg '$other'") + case arg @ (_: Slot | _: Index | _: ModuleIO) => unrollCoordinates(arg :: res, parent) + case other => err(s"unrollCoordinates failed for '$arg'! Unexpected arg '$other'") } case _ => (res, d) } @@ -135,6 +135,7 @@ object Lookupable { val next = (coor.head, d) match { case (Slot(_, name), rec: Record) => rec.elements(name) case (Index(_, ILit(n)), vec: Vec[_]) => vec.apply(n.toInt) + case (ModuleIO(_, name), rec: Record) => rec.elements(name) case (arg, _) => err(s"Unexpected Arg '$arg' applied to '$d'! Root was '$start'.") } applyCoordinates(coor.tail, next) @@ -348,6 +349,45 @@ object Lookupable { } } + private[chisel3] def cloneMemToContext[T <: MemBase[_]]( + mem: T, + context: BaseModule + )( + implicit sourceInfo: SourceInfo, + compileOptions: CompileOptions + ): T = { + mem._parent match { + case None => mem + case Some(parent) => + val newParent = cloneModuleToContext(Proto(parent), context) + newParent match { + case Proto(p) if p == parent => mem + case Clone(mod: BaseModule) => + val existingMod = Builder.currentModule + Builder.currentModule = Some(mod) + val newChild: T = mem match { + case m: Mem[_] => new Mem(m.t.asInstanceOf[Data].cloneTypeFull, m.length).asInstanceOf[T] + case m: SyncReadMem[_] => + new SyncReadMem(m.t.asInstanceOf[Data].cloneTypeFull, m.length, m.readUnderWrite).asInstanceOf[T] + } + Builder.currentModule = existingMod + newChild.setRef(mem.getRef, true) + newChild + } + } + } + + implicit def lookupMem[B <: MemBase[_]](implicit sourceInfo: SourceInfo, compileOptions: CompileOptions) = + new Lookupable[B] { + type C = B + def definitionLookup[A](that: A => B, definition: Definition[A]): C = { + cloneMemToContext(that(definition.proto), definition.getInnerDataContext.get) + } + def instanceLookup[A](that: A => B, instance: Instance[A]): C = { + cloneMemToContext(that(instance.proto), instance.getInnerDataContext.get) + } + } + import scala.language.higherKinds // Required to avoid warning for lookupIterable type parameter implicit def lookupIterable[B, F[_] <: Iterable[_]]( implicit sourceInfo: SourceInfo, @@ -402,6 +442,28 @@ object Lookupable { } } } + + implicit def lookupTuple2[X, Y]( + implicit sourceInfo: SourceInfo, + compileOptions: CompileOptions, + lookupableX: Lookupable[X], + lookupableY: Lookupable[Y] + ) = new Lookupable[(X, Y)] { + type C = (lookupableX.C, lookupableY.C) + def definitionLookup[A](that: A => (X, Y), definition: Definition[A]): C = { + val ret = that(definition.proto) + ( + lookupableX.definitionLookup[A](_ => ret._1, definition), + lookupableY.definitionLookup[A](_ => ret._2, definition) + ) + } + def instanceLookup[A](that: A => (X, Y), instance: Instance[A]): C = { + import instance._ + val ret = that(proto) + (lookupableX.instanceLookup[A](_ => ret._1, instance), lookupableY.instanceLookup[A](_ => ret._2, instance)) + } + } + implicit def lookupIsInstantiable[B <: IsInstantiable]( implicit sourceInfo: SourceInfo, compileOptions: CompileOptions diff --git a/core/src/main/scala/chisel3/internal/BiConnect.scala b/core/src/main/scala/chisel3/internal/BiConnect.scala index 5b4ad1b9..a8b425f5 100644 --- a/core/src/main/scala/chisel3/internal/BiConnect.scala +++ b/core/src/main/scala/chisel3/internal/BiConnect.scala @@ -3,15 +3,14 @@ package chisel3.internal import chisel3._ -import chisel3.experimental.dataview.reify +import chisel3.experimental.dataview.{isView, reify, reifyToAggregate} import chisel3.experimental.{attach, Analog, BaseModule} import chisel3.internal.Builder.pushCommand -import chisel3.internal.firrtl.{Connect, DefInvalid} +import chisel3.internal.firrtl.{Connect, Converter, DefInvalid} import scala.language.experimental.macros import chisel3.internal.sourceinfo._ - -import scala.annotation.tailrec +import _root_.firrtl.passes.CheckTypes /** * BiConnect.connect executes a bidirectional connection element-wise. @@ -91,12 +90,28 @@ private[chisel3] object BiConnect { if (left_v.length != right_v.length) { throw MismatchedVecException } - for (idx <- 0 until left_v.length) { - try { - implicit val compileOptions = connectCompileOptions - connect(sourceInfo, connectCompileOptions, left_v(idx), right_v(idx), context_mod) - } catch { - case BiConnectException(message) => throw BiConnectException(s"($idx)$message") + + val leftReified: Option[Aggregate] = if (isView(left_v)) reifyToAggregate(left_v) else Some(left_v) + val rightReified: Option[Aggregate] = if (isView(right_v)) reifyToAggregate(right_v) else Some(right_v) + + if ( + leftReified.nonEmpty && rightReified.nonEmpty && canBulkConnectAggregates( + leftReified.get, + rightReified.get, + sourceInfo, + connectCompileOptions, + context_mod + ) + ) { + pushCommand(Connect(sourceInfo, leftReified.get.lref, rightReified.get.lref)) + } else { + for (idx <- 0 until left_v.length) { + try { + implicit val compileOptions = connectCompileOptions + connect(sourceInfo, connectCompileOptions, left_v(idx), right_v(idx), context_mod) + } catch { + case BiConnectException(message) => throw BiConnectException(s"($idx)$message") + } } } } @@ -122,29 +137,31 @@ private[chisel3] object BiConnect { } } } - // Handle Records defined in Chisel._ code by emitting a FIRRTL partial connect + // Handle Records defined in Chisel._ code by emitting a FIRRTL bulk + // connect when possible and a partial connect otherwise case pair @ (left_r: Record, right_r: Record) => val notStrict = Seq(left_r.compileOptions, right_r.compileOptions).contains(ExplicitCompileOptions.NotStrict) - if (notStrict) { - // Traces flow from a child Data to its parent - @tailrec def traceFlow(currentlyFlipped: Boolean, data: Data): Boolean = { - import SpecifiedDirection.{Input => SInput, Flip => SFlip} - val sdir = data.specifiedDirection - val flipped = sdir == SInput || sdir == SFlip - data.binding.get match { - case ChildBinding(parent) => traceFlow(flipped ^ currentlyFlipped, parent) - case PortBinding(enclosure) => - val childPort = enclosure != context_mod - childPort ^ flipped ^ currentlyFlipped - case _ => true - } - } - def canBeSink(data: Data): Boolean = traceFlow(true, data) - def canBeSource(data: Data): Boolean = traceFlow(false, data) - // chisel3 <> is commutative but FIRRTL <- is not - val flipConnection = !canBeSink(left_r) || !canBeSource(right_r) - val (newLeft, newRight) = if (flipConnection) pair.swap else pair + + // chisel3 <> is commutative but FIRRTL <- is not + val flipConnection = + !MonoConnect.canBeSink(left_r, context_mod) || !MonoConnect.canBeSource(right_r, context_mod) + val (newLeft, newRight) = if (flipConnection) (right_r, left_r) else (left_r, right_r) + + val leftReified: Option[Aggregate] = if (isView(newLeft)) reifyToAggregate(newLeft) else Some(newLeft) + val rightReified: Option[Aggregate] = if (isView(newRight)) reifyToAggregate(newRight) else Some(newRight) + + if ( + leftReified.nonEmpty && rightReified.nonEmpty && canBulkConnectAggregates( + leftReified.get, + rightReified.get, + sourceInfo, + connectCompileOptions, + context_mod + ) + ) { + pushCommand(Connect(sourceInfo, leftReified.get.lref, rightReified.get.lref)) + } else if (notStrict) { newLeft.bulkConnect(newRight)(sourceInfo, ExplicitCompileOptions.NotStrict) } else { recordConnect(sourceInfo, connectCompileOptions, left_r, right_r, context_mod) @@ -218,6 +235,59 @@ private[chisel3] object BiConnect { } } + /** Check whether two aggregates can be bulk connected (<=) in FIRRTL. From the + * FIRRTL specification, the following must hold for bulk connection: + * + * 1. The types of the left-hand and right-hand side expressions must be + * equivalent. + * 2. The bit widths of the two expressions must allow for data to always + * flow from a smaller bit width to an equal size or larger bit width. + * 3. The flow of the left-hand side expression must be sink or duplex + * 4. Either the flow of the right-hand side expression is source or duplex, + * or the right-hand side expression has a passive type. + */ + private[chisel3] def canBulkConnectAggregates( + sink: Aggregate, + source: Aggregate, + sourceInfo: SourceInfo, + connectCompileOptions: CompileOptions, + context_mod: RawModule + ): Boolean = { + + // check that the aggregates have the same types + def typeCheck = CheckTypes.validConnect( + Converter.extractType(sink, sourceInfo), + Converter.extractType(source, sourceInfo) + ) + + // check records live in appropriate contexts + def contextCheck = + MonoConnect.aggregateConnectContextCheck( + sourceInfo, + connectCompileOptions, + sink, + source, + context_mod + ) + + // sink must be writable and must also not be a literal + def bindingCheck = sink.topBinding match { + case _: ReadOnlyBinding => false + case _ => true + } + + // check data can flow between provided aggregates + def flow_check = MonoConnect.canBeSink(sink, context_mod) && MonoConnect.canBeSource(source, context_mod) + + // do not bulk connect source literals (results in infinite recursion from calling .ref) + def sourceNotLiteralCheck = source.topBinding match { + case _: LitBinding => false + case _ => true + } + + typeCheck && contextCheck && bindingCheck && flow_check && sourceNotLiteralCheck + } + // These functions (finally) issue the connection operation // Issue with right as sink, left as source private def issueConnectL2R(left: Element, right: Element)(implicit sourceInfo: SourceInfo): Unit = { diff --git a/core/src/main/scala/chisel3/internal/Builder.scala b/core/src/main/scala/chisel3/internal/Builder.scala index 247be57a..1ffe54ab 100644 --- a/core/src/main/scala/chisel3/internal/Builder.scala +++ b/core/src/main/scala/chisel3/internal/Builder.scala @@ -258,6 +258,7 @@ private[chisel3] trait HasId extends InstanceId { (p._component, this) match { case (Some(c), _) => refName(c) case (None, d: Data) if d.topBindingOpt == Some(CrossModuleBinding) => _ref.get.localName + case (None, _: MemBase[Data]) => _ref.get.localName case (None, _) => throwException(s"signalName/pathName should be called after circuit elaboration: $this, ${_parent}") } diff --git a/core/src/main/scala/chisel3/internal/MonoConnect.scala b/core/src/main/scala/chisel3/internal/MonoConnect.scala index b4d9aeff..40056c89 100644 --- a/core/src/main/scala/chisel3/internal/MonoConnect.scala +++ b/core/src/main/scala/chisel3/internal/MonoConnect.scala @@ -5,11 +5,13 @@ package chisel3.internal import chisel3._ import chisel3.experimental.{Analog, BaseModule, EnumType, FixedPoint, Interval, UnsafeEnum} import chisel3.internal.Builder.pushCommand -import chisel3.experimental.dataview.reify -import chisel3.internal.firrtl.{Connect, DefInvalid} +import chisel3.internal.firrtl.{Connect, Converter, DefInvalid} +import chisel3.experimental.dataview.{isView, reify, reifyToAggregate} import scala.language.experimental.macros import chisel3.internal.sourceinfo.SourceInfo +import _root_.firrtl.passes.CheckTypes +import scala.annotation.tailrec /** * MonoConnect.connect executes a mono-directional connection element-wise. @@ -129,12 +131,28 @@ private[chisel3] object MonoConnect { // Handle Vec case case (sink_v: Vec[Data @unchecked], source_v: Vec[Data @unchecked]) => if (sink_v.length != source_v.length) { throw MismatchedVecException } - for (idx <- 0 until sink_v.length) { - try { - implicit val compileOptions = connectCompileOptions - connect(sourceInfo, connectCompileOptions, sink_v(idx), source_v(idx), context_mod) - } catch { - case MonoConnectException(message) => throw MonoConnectException(s"($idx)$message") + + val sinkReified: Option[Aggregate] = if (isView(sink_v)) reifyToAggregate(sink_v) else Some(sink_v) + val sourceReified: Option[Aggregate] = if (isView(source_v)) reifyToAggregate(source_v) else Some(source_v) + + if ( + sinkReified.nonEmpty && sourceReified.nonEmpty && canBulkConnectAggregates( + sinkReified.get, + sourceReified.get, + sourceInfo, + connectCompileOptions, + context_mod + ) + ) { + pushCommand(Connect(sourceInfo, sinkReified.get.lref, sourceReified.get.ref)) + } else { + for (idx <- 0 until sink_v.length) { + try { + implicit val compileOptions = connectCompileOptions + connect(sourceInfo, connectCompileOptions, sink_v(idx), source_v(idx), context_mod) + } catch { + case MonoConnectException(message) => throw MonoConnectException(s"($idx)$message") + } } } // Handle Vec connected to DontCare. Apply the DontCare to individual elements. @@ -150,19 +168,34 @@ private[chisel3] object MonoConnect { // Handle Record case case (sink_r: Record, source_r: Record) => - // For each field, descend with right - for ((field, sink_sub) <- sink_r.elements) { - try { - source_r.elements.get(field) match { - case Some(source_sub) => connect(sourceInfo, connectCompileOptions, sink_sub, source_sub, context_mod) - case None => { - if (connectCompileOptions.connectFieldsMustMatch) { - throw MissingFieldException(field) + val sinkReified: Option[Aggregate] = if (isView(sink_r)) reifyToAggregate(sink_r) else Some(sink_r) + val sourceReified: Option[Aggregate] = if (isView(source_r)) reifyToAggregate(source_r) else Some(source_r) + + if ( + sinkReified.nonEmpty && sourceReified.nonEmpty && canBulkConnectAggregates( + sinkReified.get, + sourceReified.get, + sourceInfo, + connectCompileOptions, + context_mod + ) + ) { + pushCommand(Connect(sourceInfo, sinkReified.get.lref, sourceReified.get.ref)) + } else { + // For each field, descend with right + for ((field, sink_sub) <- sink_r.elements) { + try { + source_r.elements.get(field) match { + case Some(source_sub) => connect(sourceInfo, connectCompileOptions, sink_sub, source_sub, context_mod) + case None => { + if (connectCompileOptions.connectFieldsMustMatch) { + throw MissingFieldException(field) + } } } + } catch { + case MonoConnectException(message) => throw MonoConnectException(s".$field$message") } - } catch { - case MonoConnectException(message) => throw MonoConnectException(s".$field$message") } } // Handle Record connected to DontCare. Apply the DontCare to individual elements. @@ -190,6 +223,143 @@ private[chisel3] object MonoConnect { case (sink, source) => throw MismatchedException(sink, source) } + /** Determine if a valid connection can be made between a source [[Aggregate]] and sink + * [[Aggregate]] given their parent module and directionality context + * + * @return whether the source and sink exist in an appropriate context to be connected + */ + private[chisel3] def aggregateConnectContextCheck( + implicit sourceInfo: SourceInfo, + connectCompileOptions: CompileOptions, + sink: Aggregate, + source: Aggregate, + context_mod: RawModule + ): Boolean = { + import ActualDirection.{Bidirectional, Input, Output} + // If source has no location, assume in context module + // This can occur if is a literal, unbound will error previously + val sink_mod: BaseModule = sink.topBinding.location.getOrElse(throw UnwritableSinkException(sink, source)) + val source_mod: BaseModule = source.topBinding.location.getOrElse(context_mod) + + val sink_parent = Builder.retrieveParent(sink_mod, context_mod).getOrElse(None) + val source_parent = Builder.retrieveParent(source_mod, context_mod).getOrElse(None) + + val sink_is_port = sink.topBinding match { + case PortBinding(_) => true + case _ => false + } + val source_is_port = source.topBinding match { + case PortBinding(_) => true + case _ => false + } + + if (!checkWhenVisibility(sink)) { + throw SinkEscapedWhenScopeException(sink) + } + + if (!checkWhenVisibility(source)) { + throw SourceEscapedWhenScopeException(source) + } + + // CASE: Context is same module that both sink node and source node are in + if ((context_mod == sink_mod) && (context_mod == source_mod)) { + sink.direction != Input + } + + // CASE: Context is same module as sink node and source node is in a child module + else if ((sink_mod == context_mod) && (source_parent == context_mod)) { + // NOTE: Workaround for bulk connecting non-agnostified FIRRTL ports + // See: https://github.com/freechipsproject/firrtl/issues/1703 + // Original behavior should just check if the sink direction is an Input + val sinkCanBeInput = sink.direction match { + case Input => true + case Bidirectional(_) => true + case _ => false + } + // Thus, right node better be a port node and thus have a direction + if (!source_is_port) { !connectCompileOptions.dontAssumeDirectionality } + else if (sinkCanBeInput) { + if (source.direction == Output) { + !connectCompileOptions.dontTryConnectionsSwapped + } else { false } + } else { true } + } + + // CASE: Context is same module as source node and sink node is in child module + else if ((source_mod == context_mod) && (sink_parent == context_mod)) { + // NOTE: Workaround for bulk connecting non-agnostified FIRRTL ports + // See: https://github.com/freechipsproject/firrtl/issues/1703 + // Original behavior should just check if the sink direction is an Input + sink.direction match { + case Input => true + case Bidirectional(_) => true + case _ => false + } + } + + // CASE: Context is the parent module of both the module containing sink node + // and the module containing source node + // Note: This includes case when sink and source in same module but in parent + else if ((sink_parent == context_mod) && (source_parent == context_mod)) { + // Thus both nodes must be ports and have a direction + if (!source_is_port) { !connectCompileOptions.dontAssumeDirectionality } + else if (sink_is_port) { + // NOTE: Workaround for bulk connecting non-agnostified FIRRTL ports + // See: https://github.com/freechipsproject/firrtl/issues/1703 + // Original behavior should just check if the sink direction is an Input + sink.direction match { + case Input => true + case Bidirectional(_) => true // NOTE: Workaround for non-agnostified ports + case _ => false + } + } else { false } + } + + // Not quite sure where left and right are compared to current module + // so just error out + else false + } + + /** Trace flow from child Data to its parent. */ + @tailrec private[chisel3] def traceFlow(currentlyFlipped: Boolean, data: Data, context_mod: RawModule): Boolean = { + import SpecifiedDirection.{Input => SInput, Flip => SFlip} + val sdir = data.specifiedDirection + val flipped = sdir == SInput || sdir == SFlip + data.binding.get match { + case ChildBinding(parent) => traceFlow(flipped ^ currentlyFlipped, parent, context_mod) + case PortBinding(enclosure) => + val childPort = enclosure != context_mod + childPort ^ flipped ^ currentlyFlipped + case _ => true + } + } + def canBeSink(data: Data, context_mod: RawModule): Boolean = traceFlow(true, data, context_mod) + def canBeSource(data: Data, context_mod: RawModule): Boolean = traceFlow(false, data, context_mod) + + /** Check whether two aggregates can be bulk connected (<=) in FIRRTL. (MonoConnect case) + * + * Mono-directional bulk connects only work if all signals of the sink are unidirectional + * In the case of a sink aggregate with bidirectional signals, e.g. `Decoupled`, + * a `BiConnect` is necessary. + */ + private[chisel3] def canBulkConnectAggregates( + sink: Aggregate, + source: Aggregate, + sourceInfo: SourceInfo, + connectCompileOptions: CompileOptions, + context_mod: RawModule + ): Boolean = { + // Assuming we're using a <>, check if a bulk connect is valid in that case + def biConnectCheck = + BiConnect.canBulkConnectAggregates(sink, source, sourceInfo, connectCompileOptions, context_mod) + + // Check that the Aggregate can be driven (not bidirectional or an input) to match Chisel semantics + def sinkCanBeDrivenCheck: Boolean = + sink.direction == ActualDirection.Output || sink.direction == ActualDirection.Unspecified + + biConnectCheck && sinkCanBeDrivenCheck + } + // This function (finally) issues the connection operation private def issueConnect(sink: Element, source: Element)(implicit sourceInfo: SourceInfo): Unit = { // If the source is a DontCare, generate a DefInvalid for the sink, |
