summaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/src/main/scala/chisel3/Bits.scala13
-rw-r--r--core/src/main/scala/chisel3/Mem.scala9
-rw-r--r--core/src/main/scala/chisel3/Module.scala40
-rw-r--r--core/src/main/scala/chisel3/experimental/dataview/package.scala10
-rw-r--r--core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala70
-rw-r--r--core/src/main/scala/chisel3/internal/BiConnect.scala130
-rw-r--r--core/src/main/scala/chisel3/internal/Builder.scala1
-rw-r--r--core/src/main/scala/chisel3/internal/MonoConnect.scala206
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,