Skip to content

Commit 9ebf28d

Browse files
mergify[bot]azidar
andauthored
Added .exclude to Connectable (#3172) (#3178)
(cherry picked from commit 4b891ec) Co-authored-by: Adam Izraelevitz <adam.izraelevitz@sifive.com>
1 parent f446fff commit 9ebf28d

File tree

6 files changed

+316
-95
lines changed

6 files changed

+316
-95
lines changed

core/src/main/scala/chisel3/Data.scala

Lines changed: 44 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -830,51 +830,52 @@ object Data {
830830
*
831831
* Only zips immediate children (vs members, which are all children/grandchildren etc.)
832832
*/
833-
implicit private[chisel3] val DataMatchingZipOfChildren = new DataMirror.HasMatchingZipOfChildren[Data] {
833+
implicit val dataMatchingZipOfChildren: DataMirror.HasMatchingZipOfChildren[Data] =
834+
new DataMirror.HasMatchingZipOfChildren[Data] {
834835

835-
implicit class VecOptOps(vOpt: Option[Vec[Data]]) {
836-
// Like .get, but its already defined on Option
837-
def grab(i: Int): Option[Data] = vOpt.flatMap { _.lift(i) }
838-
def size = vOpt.map(_.size).getOrElse(0)
839-
}
840-
implicit class RecordOptGet(rOpt: Option[Record]) {
841-
// Like .get, but its already defined on Option
842-
def grab(k: String): Option[Data] = rOpt.flatMap { _._elements.get(k) }
843-
def keys: Iterable[String] = rOpt.map { r => r._elements.map(_._1) }.getOrElse(Seq.empty[String])
844-
}
845-
//TODO(azidar): Rewrite this to be more clear, probably not the cleanest way to express this
846-
private def isDifferent(l: Option[Data], r: Option[Data]): Boolean =
847-
l.nonEmpty && r.nonEmpty && !isRecord(l, r) && !isVec(l, r) && !isElement(l, r)
848-
private def isRecord(l: Option[Data], r: Option[Data]): Boolean =
849-
l.orElse(r).map { _.isInstanceOf[Record] }.getOrElse(false)
850-
private def isVec(l: Option[Data], r: Option[Data]): Boolean =
851-
l.orElse(r).map { _.isInstanceOf[Vec[_]] }.getOrElse(false)
852-
private def isElement(l: Option[Data], r: Option[Data]): Boolean =
853-
l.orElse(r).map { _.isInstanceOf[Element] }.getOrElse(false)
854-
855-
/** Zips matching children of `left` and `right`; returns Nil if both are empty
856-
*
857-
* The canonical API to iterate through two Chisel types or components, where
858-
* matching children are provided together, while non-matching members are provided
859-
* separately
860-
*
861-
* Only zips immediate children (vs members, which are all children/grandchildren etc.)
862-
*
863-
* Returns Nil if both are different types
864-
*/
865-
def matchingZipOfChildren(left: Option[Data], right: Option[Data]): Seq[(Option[Data], Option[Data])] =
866-
(left, right) match {
867-
case (None, None) => Nil
868-
case (lOpt, rOpt) if isDifferent(lOpt, rOpt) => Nil
869-
case (lOpt: Option[Vec[Data] @unchecked], rOpt: Option[Vec[Data] @unchecked]) if isVec(lOpt, rOpt) =>
870-
(0 until (lOpt.size.max(rOpt.size))).map { i => (lOpt.grab(i), rOpt.grab(i)) }
871-
case (lOpt: Option[Record @unchecked], rOpt: Option[Record @unchecked]) if isRecord(lOpt, rOpt) =>
872-
(lOpt.keys ++ rOpt.keys).toList.distinct.map { k => (lOpt.grab(k), rOpt.grab(k)) }
873-
case (lOpt: Option[Element @unchecked], rOpt: Option[Element @unchecked]) if isElement(lOpt, rOpt) => Nil
874-
case _ =>
875-
throw new InternalErrorException(s"Match Error: left=$left, right=$right")
836+
implicit class VecOptOps(vOpt: Option[Vec[Data]]) {
837+
// Like .get, but its already defined on Option
838+
def grab(i: Int): Option[Data] = vOpt.flatMap { _.lift(i) }
839+
def size = vOpt.map(_.size).getOrElse(0)
876840
}
877-
}
841+
implicit class RecordOptGet(rOpt: Option[Record]) {
842+
// Like .get, but its already defined on Option
843+
def grab(k: String): Option[Data] = rOpt.flatMap { _._elements.get(k) }
844+
def keys: Iterable[String] = rOpt.map { r => r._elements.map(_._1) }.getOrElse(Seq.empty[String])
845+
}
846+
//TODO(azidar): Rewrite this to be more clear, probably not the cleanest way to express this
847+
private def isDifferent(l: Option[Data], r: Option[Data]): Boolean =
848+
l.nonEmpty && r.nonEmpty && !isRecord(l, r) && !isVec(l, r) && !isElement(l, r)
849+
private def isRecord(l: Option[Data], r: Option[Data]): Boolean =
850+
l.orElse(r).map { _.isInstanceOf[Record] }.getOrElse(false)
851+
private def isVec(l: Option[Data], r: Option[Data]): Boolean =
852+
l.orElse(r).map { _.isInstanceOf[Vec[_]] }.getOrElse(false)
853+
private def isElement(l: Option[Data], r: Option[Data]): Boolean =
854+
l.orElse(r).map { _.isInstanceOf[Element] }.getOrElse(false)
855+
856+
/** Zips matching children of `left` and `right`; returns Nil if both are empty
857+
*
858+
* The canonical API to iterate through two Chisel types or components, where
859+
* matching children are provided together, while non-matching members are provided
860+
* separately
861+
*
862+
* Only zips immediate children (vs members, which are all children/grandchildren etc.)
863+
*
864+
* Returns Nil if both are different types
865+
*/
866+
def matchingZipOfChildren(left: Option[Data], right: Option[Data]): Seq[(Option[Data], Option[Data])] =
867+
(left, right) match {
868+
case (None, None) => Nil
869+
case (lOpt, rOpt) if isDifferent(lOpt, rOpt) => Nil
870+
case (lOpt: Option[Vec[Data] @unchecked], rOpt: Option[Vec[Data] @unchecked]) if isVec(lOpt, rOpt) =>
871+
(0 until (lOpt.size.max(rOpt.size))).map { i => (lOpt.grab(i), rOpt.grab(i)) }
872+
case (lOpt: Option[Record @unchecked], rOpt: Option[Record @unchecked]) if isRecord(lOpt, rOpt) =>
873+
(lOpt.keys ++ rOpt.keys).toList.distinct.map { k => (lOpt.grab(k), rOpt.grab(k)) }
874+
case (lOpt: Option[Element @unchecked], rOpt: Option[Element @unchecked]) if isElement(lOpt, rOpt) => Nil
875+
case _ =>
876+
throw new InternalErrorException(s"Match Error: left=$left, right=$right")
877+
}
878+
}
878879

879880
/**
880881
* Provides generic, recursive equality for [[Bundle]] and [[Vec]] hardware. This avoids the

core/src/main/scala/chisel3/connectable/Alignment.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ private[chisel3] sealed trait Alignment {
7373

7474
final def isSqueezed: Boolean = base.squeezed.contains(member)
7575

76+
final def isExcluded: Boolean = base.excluded.contains(member)
77+
7678
// Whether the current member is an aggregate
7779
final def isAgg: Boolean = member.isInstanceOf[Aggregate]
7880

@@ -94,6 +96,7 @@ private[chisel3] case class EmptyAlignment(base: Connectable[Data], isConsumer:
9496
def member = DontCare
9597
def waived = Set.empty
9698
def squeezed = Set.empty
99+
def excluded = Set.empty
97100
def invert = this
98101
def coerced = false
99102
def coerce = this
@@ -167,7 +170,7 @@ object Alignment {
167170
left: Option[Alignment],
168171
right: Option[Alignment]
169172
): Seq[(Option[Alignment], Option[Alignment])] = {
170-
Data.DataMatchingZipOfChildren.matchingZipOfChildren(left.map(_.member), right.map(_.member)).map {
173+
Data.dataMatchingZipOfChildren.matchingZipOfChildren(left.map(_.member), right.map(_.member)).map {
171174
case (l, r) => l.map(deriveChildAlignment(_, left.get)) -> r.map(deriveChildAlignment(_, right.get))
172175
}
173176
}

core/src/main/scala/chisel3/connectable/Connectable.scala

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,19 @@ import experimental.{prefix, requireIsHardware}
1717
final class Connectable[+T <: Data] private (
1818
val base: T,
1919
private[chisel3] val waived: Set[Data],
20-
private[chisel3] val squeezed: Set[Data]) {
20+
private[chisel3] val squeezed: Set[Data],
21+
private[chisel3] val excluded: Set[Data]) {
2122
requireIsHardware(base, s"Can only created Connectable of components, not unbound Chisel types")
2223

23-
/** True if no members are waived or squeezed */
24-
def notSpecial = waived.isEmpty && squeezed.isEmpty
24+
/** True if no members are waived or squeezed or excluded */
25+
def notWaivedOrSqueezedOrExcluded = waived.isEmpty && squeezed.isEmpty && excluded.isEmpty
2526

26-
private[chisel3] def copy(waived: Set[Data] = this.waived, squeezed: Set[Data] = this.squeezed): Connectable[T] =
27-
new Connectable(base, waived, squeezed)
27+
private[chisel3] def copy(
28+
waived: Set[Data] = this.waived,
29+
squeezed: Set[Data] = this.squeezed,
30+
excluded: Set[Data] = this.excluded
31+
): Connectable[T] =
32+
new Connectable(base, waived, squeezed, excluded)
2833

2934
/** Select members of base to waive
3035
*
@@ -83,6 +88,31 @@ final class Connectable[+T <: Data] private (
8388
this.copy(squeezed = squeezedMembers.toSet) // not appending squeezed because we are collecting all members
8489
}
8590

91+
/** Adds base to excludes */
92+
def exclude: Connectable[T] = this.copy(excluded = excluded ++ addOpaque(Seq(base)))
93+
94+
/** Select members of base to exclude
95+
*
96+
* @param members functions given the base return a member to exclude
97+
*/
98+
def exclude(members: (T => Data)*): Connectable[T] = this.copy(excluded = excluded ++ members.map(f => f(base)).toSet)
99+
100+
/** Select members of base to exclude and static cast to a new type
101+
*
102+
* @param members functions given the base return a member to exclude
103+
*/
104+
def excludeAs[S <: Data](members: (T => Data)*)(implicit ev: T <:< S): Connectable[S] =
105+
this.copy(excluded = excluded ++ members.map(f => f(base)).toSet).asInstanceOf[Connectable[S]]
106+
107+
/** Programmatically select members of base to exclude and static cast to a new type
108+
*
109+
* @param members partial function applied to all recursive members of base, if match, can return a member to exclude
110+
*/
111+
def excludeEach[S <: Data](pf: PartialFunction[Data, Seq[Data]])(implicit ev: T <:< S): Connectable[S] = {
112+
val excludedMembers = DataMirror.collectMembers(base)(pf).flatten
113+
this.copy(excluded = excluded ++ excludedMembers.toSet).asInstanceOf[Connectable[S]]
114+
}
115+
86116
/** Add any elements of members that are OpaqueType */
87117
private def addOpaque(members: Seq[Data]): Seq[Data] = {
88118
members.flatMap {
@@ -98,13 +128,18 @@ object Connectable {
98128
def apply[T <: Data](
99129
base: T,
100130
waiveSelection: Data => Boolean = { _ => false },
101-
squeezeSelection: Data => Boolean = { _ => false }
131+
squeezeSelection: Data => Boolean = { _ => false },
132+
excludeSelection: Data => Boolean = { _ => false }
102133
): Connectable[T] = {
103-
val (waived, squeezed) = {
134+
val (waived, squeezed, excluded) = {
104135
val members = DataMirror.collectMembers(base) { case x => x }
105-
(members.filter(waiveSelection).toSet, members.filter(squeezeSelection).toSet)
136+
(
137+
members.filter(waiveSelection).toSet,
138+
members.filter(squeezeSelection).toSet,
139+
members.filter(excludeSelection).toSet
140+
)
106141
}
107-
new Connectable(base, waived, squeezed)
142+
new Connectable(base, waived, squeezed, excluded)
108143
}
109144

110145
/** The default connection operators for Chisel hardware components

core/src/main/scala/chisel3/connectable/Connection.scala

Lines changed: 45 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ private[chisel3] case object ColonLessGreaterEq extends Connection {
7070
// when calling DirectionConnection.connect. Hence, we can just default to false to take the non-optimized emission path
7171
case e: Throwable => false
7272
}
73-
(typeEquivalent && consumer.notSpecial && producer.notSpecial)
73+
(typeEquivalent && consumer.notWaivedOrSqueezedOrExcluded && producer.notWaivedOrSqueezedOrExcluded)
7474
}
7575
}
7676

@@ -173,76 +173,84 @@ private[chisel3] object Connection {
173173
import Alignment.deriveChildAlignment
174174

175175
def doConnection(
176-
consumerAlignment: Alignment,
177-
producerAlignment: Alignment
176+
conAlign: Alignment,
177+
proAlign: Alignment
178178
)(
179179
implicit sourceInfo: SourceInfo
180180
): Unit = {
181-
(consumerAlignment, producerAlignment) match {
181+
(conAlign, proAlign) match {
182182
// Base Case 0: should probably never happen
183183
case (_: EmptyAlignment, _: EmptyAlignment) => ()
184184

185-
// Base Case 1: early exit if dangling/unconnected is wavied
186-
case (consumerAlignment: NonEmptyAlignment, _: EmptyAlignment) if consumerAlignment.isWaived => ()
187-
case (_: EmptyAlignment, producerAlignment: NonEmptyAlignment) if producerAlignment.isWaived => ()
185+
// Base Case 1: early exit if dangling/unconnected is excluded
186+
case (conAlign: Alignment, proAlign: Alignment) if conAlign.isExcluded && proAlign.isExcluded => ()
188187

189-
// Base Case 2: early exit if operator requires matching orientations, but they don't align
190-
case (consumerAlignment: NonEmptyAlignment, producerAlignment: NonEmptyAlignment)
191-
if (!consumerAlignment.alignsWith(producerAlignment)) && (connectionOp.noWrongOrientations) =>
192-
errors = (s"inversely oriented fields ${consumerAlignment.member} and ${producerAlignment.member}") +: errors
188+
// Base Case 2(A,B): early exit if dangling/unconnected is wavied or excluded
189+
case (conAlign: NonEmptyAlignment, _: EmptyAlignment) if conAlign.isWaived || conAlign.isExcluded => ()
190+
case (_: EmptyAlignment, proAlign: NonEmptyAlignment) if proAlign.isWaived || proAlign.isExcluded => ()
193191

194-
// Base Case 3: early exit if operator requires matching widths, but they aren't the same
195-
case (consumerAlignment: NonEmptyAlignment, producerAlignment: NonEmptyAlignment)
196-
if (consumerAlignment
197-
.truncationRequired(producerAlignment, connectionOp)
198-
.nonEmpty) && (connectionOp.noMismatchedWidths) =>
199-
val mustBeTruncated = consumerAlignment.truncationRequired(producerAlignment, connectionOp).get
192+
// Base Case 3: early exit if dangling/unconnected is wavied
193+
case (conAlign: NonEmptyAlignment, proAlign: NonEmptyAlignment) if conAlign.isExcluded || proAlign.isExcluded =>
194+
val (excluded, included) =
195+
if (conAlign.isExcluded) (conAlign, proAlign)
196+
else (proAlign, conAlign)
197+
errors = (s"excluded field ${excluded.member} has matching non-excluded field ${included.member}") +: errors
198+
199+
// Base Case 4: early exit if operator requires matching orientations, but they don't align
200+
case (conAlign: NonEmptyAlignment, proAlign: NonEmptyAlignment)
201+
if (!conAlign.alignsWith(proAlign)) && (connectionOp.noWrongOrientations) =>
202+
errors = (s"inversely oriented fields ${conAlign.member} and ${proAlign.member}") +: errors
203+
204+
// Base Case 5: early exit if operator requires matching widths, but they aren't the same
205+
case (conAlign: NonEmptyAlignment, proAlign: NonEmptyAlignment)
206+
if (conAlign.truncationRequired(proAlign, connectionOp).nonEmpty) && (connectionOp.noMismatchedWidths) =>
207+
val mustBeTruncated = conAlign.truncationRequired(proAlign, connectionOp).get
200208
errors =
201-
(s"mismatched widths of ${consumerAlignment.member} and ${producerAlignment.member} might require truncation of $mustBeTruncated") +: errors
209+
(s"mismatched widths of ${conAlign.member} and ${proAlign.member} might require truncation of $mustBeTruncated") +: errors
202210

203-
// Base Case 3: operator error on dangling/unconnected fields
211+
// Base Case 6: operator error on dangling/unconnected fields
204212
case (consumer: NonEmptyAlignment, _: EmptyAlignment) =>
205-
errors = (s"${consumer.errorWord(connectionOp)} consumer field ${consumerAlignment.member}") +: errors
213+
errors = (s"${consumer.errorWord(connectionOp)} consumer field ${conAlign.member}") +: errors
206214
case (_: EmptyAlignment, producer: NonEmptyAlignment) =>
207-
errors = (s"${producer.errorWord(connectionOp)} producer field ${producerAlignment.member}") +: errors
215+
errors = (s"${producer.errorWord(connectionOp)} producer field ${proAlign.member}") +: errors
208216

209217
// Recursive Case 4: non-empty orientations
210-
case (consumerAlignment: NonEmptyAlignment, producerAlignment: NonEmptyAlignment) =>
211-
(consumerAlignment.member, producerAlignment.member) match {
218+
case (conAlign: NonEmptyAlignment, proAlign: NonEmptyAlignment) =>
219+
(conAlign.member, proAlign.member) match {
212220
case (consumer: Aggregate, producer: Aggregate) =>
213-
matchingZipOfChildren(Some(consumerAlignment), Some(producerAlignment)).foreach {
221+
matchingZipOfChildren(Some(conAlign), Some(proAlign)).foreach {
214222
case (ceo, peo) =>
215-
doConnection(ceo.getOrElse(consumerAlignment.empty), peo.getOrElse(producerAlignment.empty))
223+
doConnection(ceo.getOrElse(conAlign.empty), peo.getOrElse(proAlign.empty))
216224
}
217225
case (consumer: Aggregate, DontCare) =>
218226
consumer.getElements.foreach {
219227
case f =>
220228
doConnection(
221-
deriveChildAlignment(f, consumerAlignment),
222-
deriveChildAlignment(f, consumerAlignment).swap(DontCare)
229+
deriveChildAlignment(f, conAlign),
230+
deriveChildAlignment(f, conAlign).swap(DontCare)
223231
)
224232
}
225233
case (DontCare, producer: Aggregate) =>
226234
producer.getElements.foreach {
227235
case f =>
228236
doConnection(
229-
deriveChildAlignment(f, producerAlignment).swap(DontCare),
230-
deriveChildAlignment(f, producerAlignment)
237+
deriveChildAlignment(f, proAlign).swap(DontCare),
238+
deriveChildAlignment(f, proAlign)
231239
)
232240
}
233241
case (consumer, producer) =>
234242
val alignment = (
235-
consumerAlignment.alignsWith(producerAlignment),
236-
(!consumerAlignment.alignsWith(
237-
producerAlignment
243+
conAlign.alignsWith(proAlign),
244+
(!conAlign.alignsWith(
245+
proAlign
238246
) && connectionOp.connectToConsumer && !connectionOp.connectToProducer),
239-
(!consumerAlignment.alignsWith(
240-
producerAlignment
247+
(!conAlign.alignsWith(
248+
proAlign
241249
) && !connectionOp.connectToConsumer && connectionOp.connectToProducer)
242250
) match {
243-
case (true, _, _) => consumerAlignment
244-
case (_, true, _) => consumerAlignment
245-
case (_, _, true) => producerAlignment
251+
case (true, _, _) => conAlign
252+
case (_, true, _) => conAlign
253+
case (_, _, true) => proAlign
246254
case other => throw new Exception(other.toString)
247255
}
248256
val lAndROpt = alignment.computeLandR(consumer, producer, connectionOp)

0 commit comments

Comments
 (0)