Skip to content

Commit

Permalink
Merge branch '3.6.x' into 3.6-release
Browse files Browse the repository at this point in the history
  • Loading branch information
jackkoenig committed Apr 14, 2023
2 parents 8a44b25 + 9ebf28d commit 0600e1e
Show file tree
Hide file tree
Showing 14 changed files with 427 additions and 131 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install CIRCT
uses: ./.github/workflows/install-circt
- name: Setup Scala
uses: actions/setup-java@v3
with:
Expand Down
24 changes: 23 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ enablePlugins(SiteScaladocPlugin)
addCommandAlias("fmt", "; scalafmtAll ; scalafmtSbt")
addCommandAlias("fmtCheck", "; scalafmtCheckAll ; scalafmtSbtCheck")

lazy val firtoolVersion = settingKey[Option[String]]("Determine the version of firtool on the PATH")
ThisBuild / firtoolVersion := {
import scala.sys.process._
val Version = """^CIRCT firtool-(\S+)$""".r
try {
val lines = Process(Seq("firtool", "--version")).lineStream
lines.collectFirst { case Version(v) => v }
} catch {
case e: java.io.IOException => None
}
}

val defaultVersions = Map(
"firrtl" -> "edu.berkeley.cs" %% "firrtl" % "1.6.0",
"treadle" -> "edu.berkeley.cs" %% "treadle" % "1.6.0",
Expand Down Expand Up @@ -70,6 +82,16 @@ lazy val publishSettings = Seq(
versionScheme := Some("pvp"),
publishMavenStyle := true,
Test / publishArtifact := false,
// We are just using 'publish / skip' as a hook to run checks required for publishing,
// but that are not necessarily required for local development or running testing in CI
publish / skip := {
// Check that firtool exists on the PATH so Chisel can use the version it was tested against
// in error messages
if (firtoolVersion.value.isEmpty) {
sys.error(s"Failed to determine firtool version. Make sure firtool is found on the PATH.")
}
(publish / skip).value
},
pomIncludeRepository := { x => false },
pomExtra := <url>http://chisel.eecs.berkeley.edu/</url>
<licenses>
Expand Down Expand Up @@ -198,7 +220,7 @@ lazy val core = (project in file("core"))
.settings(
buildInfoPackage := "chisel3",
buildInfoUsePackageAsPath := true,
buildInfoKeys := Seq[BuildInfoKey](buildInfoPackage, version, scalaVersion, sbtVersion)
buildInfoKeys := Seq[BuildInfoKey](buildInfoPackage, version, scalaVersion, sbtVersion, firtoolVersion)
)
.settings(publishSettings: _*)
.settings(mimaPreviousArtifacts := Set())
Expand Down
87 changes: 44 additions & 43 deletions core/src/main/scala/chisel3/Data.scala
Original file line number Diff line number Diff line change
Expand Up @@ -830,51 +830,52 @@ object Data {
*
* Only zips immediate children (vs members, which are all children/grandchildren etc.)
*/
implicit private[chisel3] val DataMatchingZipOfChildren = new DataMirror.HasMatchingZipOfChildren[Data] {
implicit val dataMatchingZipOfChildren: DataMirror.HasMatchingZipOfChildren[Data] =
new DataMirror.HasMatchingZipOfChildren[Data] {

implicit class VecOptOps(vOpt: Option[Vec[Data]]) {
// Like .get, but its already defined on Option
def grab(i: Int): Option[Data] = vOpt.flatMap { _.lift(i) }
def size = vOpt.map(_.size).getOrElse(0)
}
implicit class RecordOptGet(rOpt: Option[Record]) {
// Like .get, but its already defined on Option
def grab(k: String): Option[Data] = rOpt.flatMap { _._elements.get(k) }
def keys: Iterable[String] = rOpt.map { r => r._elements.map(_._1) }.getOrElse(Seq.empty[String])
}
//TODO(azidar): Rewrite this to be more clear, probably not the cleanest way to express this
private def isDifferent(l: Option[Data], r: Option[Data]): Boolean =
l.nonEmpty && r.nonEmpty && !isRecord(l, r) && !isVec(l, r) && !isElement(l, r)
private def isRecord(l: Option[Data], r: Option[Data]): Boolean =
l.orElse(r).map { _.isInstanceOf[Record] }.getOrElse(false)
private def isVec(l: Option[Data], r: Option[Data]): Boolean =
l.orElse(r).map { _.isInstanceOf[Vec[_]] }.getOrElse(false)
private def isElement(l: Option[Data], r: Option[Data]): Boolean =
l.orElse(r).map { _.isInstanceOf[Element] }.getOrElse(false)

/** Zips matching children of `left` and `right`; returns Nil if both are empty
*
* The canonical API to iterate through two Chisel types or components, where
* matching children are provided together, while non-matching members are provided
* separately
*
* Only zips immediate children (vs members, which are all children/grandchildren etc.)
*
* Returns Nil if both are different types
*/
def matchingZipOfChildren(left: Option[Data], right: Option[Data]): Seq[(Option[Data], Option[Data])] =
(left, right) match {
case (None, None) => Nil
case (lOpt, rOpt) if isDifferent(lOpt, rOpt) => Nil
case (lOpt: Option[Vec[Data] @unchecked], rOpt: Option[Vec[Data] @unchecked]) if isVec(lOpt, rOpt) =>
(0 until (lOpt.size.max(rOpt.size))).map { i => (lOpt.grab(i), rOpt.grab(i)) }
case (lOpt: Option[Record @unchecked], rOpt: Option[Record @unchecked]) if isRecord(lOpt, rOpt) =>
(lOpt.keys ++ rOpt.keys).toList.distinct.map { k => (lOpt.grab(k), rOpt.grab(k)) }
case (lOpt: Option[Element @unchecked], rOpt: Option[Element @unchecked]) if isElement(lOpt, rOpt) => Nil
case _ =>
throw new InternalErrorException(s"Match Error: left=$left, right=$right")
implicit class VecOptOps(vOpt: Option[Vec[Data]]) {
// Like .get, but its already defined on Option
def grab(i: Int): Option[Data] = vOpt.flatMap { _.lift(i) }
def size = vOpt.map(_.size).getOrElse(0)
}
}
implicit class RecordOptGet(rOpt: Option[Record]) {
// Like .get, but its already defined on Option
def grab(k: String): Option[Data] = rOpt.flatMap { _._elements.get(k) }
def keys: Iterable[String] = rOpt.map { r => r._elements.map(_._1) }.getOrElse(Seq.empty[String])
}
//TODO(azidar): Rewrite this to be more clear, probably not the cleanest way to express this
private def isDifferent(l: Option[Data], r: Option[Data]): Boolean =
l.nonEmpty && r.nonEmpty && !isRecord(l, r) && !isVec(l, r) && !isElement(l, r)
private def isRecord(l: Option[Data], r: Option[Data]): Boolean =
l.orElse(r).map { _.isInstanceOf[Record] }.getOrElse(false)
private def isVec(l: Option[Data], r: Option[Data]): Boolean =
l.orElse(r).map { _.isInstanceOf[Vec[_]] }.getOrElse(false)
private def isElement(l: Option[Data], r: Option[Data]): Boolean =
l.orElse(r).map { _.isInstanceOf[Element] }.getOrElse(false)

/** Zips matching children of `left` and `right`; returns Nil if both are empty
*
* The canonical API to iterate through two Chisel types or components, where
* matching children are provided together, while non-matching members are provided
* separately
*
* Only zips immediate children (vs members, which are all children/grandchildren etc.)
*
* Returns Nil if both are different types
*/
def matchingZipOfChildren(left: Option[Data], right: Option[Data]): Seq[(Option[Data], Option[Data])] =
(left, right) match {
case (None, None) => Nil
case (lOpt, rOpt) if isDifferent(lOpt, rOpt) => Nil
case (lOpt: Option[Vec[Data] @unchecked], rOpt: Option[Vec[Data] @unchecked]) if isVec(lOpt, rOpt) =>
(0 until (lOpt.size.max(rOpt.size))).map { i => (lOpt.grab(i), rOpt.grab(i)) }
case (lOpt: Option[Record @unchecked], rOpt: Option[Record @unchecked]) if isRecord(lOpt, rOpt) =>
(lOpt.keys ++ rOpt.keys).toList.distinct.map { k => (lOpt.grab(k), rOpt.grab(k)) }
case (lOpt: Option[Element @unchecked], rOpt: Option[Element @unchecked]) if isElement(lOpt, rOpt) => Nil
case _ =>
throw new InternalErrorException(s"Match Error: left=$left, right=$right")
}
}

/**
* Provides generic, recursive equality for [[Bundle]] and [[Vec]] hardware. This avoids the
Expand Down
5 changes: 4 additions & 1 deletion core/src/main/scala/chisel3/connectable/Alignment.scala
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ private[chisel3] sealed trait Alignment {

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

final def isExcluded: Boolean = base.excluded.contains(member)

// Whether the current member is an aggregate
final def isAgg: Boolean = member.isInstanceOf[Aggregate]

Expand All @@ -94,6 +96,7 @@ private[chisel3] case class EmptyAlignment(base: Connectable[Data], isConsumer:
def member = DontCare
def waived = Set.empty
def squeezed = Set.empty
def excluded = Set.empty
def invert = this
def coerced = false
def coerce = this
Expand Down Expand Up @@ -167,7 +170,7 @@ object Alignment {
left: Option[Alignment],
right: Option[Alignment]
): Seq[(Option[Alignment], Option[Alignment])] = {
Data.DataMatchingZipOfChildren.matchingZipOfChildren(left.map(_.member), right.map(_.member)).map {
Data.dataMatchingZipOfChildren.matchingZipOfChildren(left.map(_.member), right.map(_.member)).map {
case (l, r) => l.map(deriveChildAlignment(_, left.get)) -> r.map(deriveChildAlignment(_, right.get))
}
}
Expand Down
53 changes: 44 additions & 9 deletions core/src/main/scala/chisel3/connectable/Connectable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,19 @@ import experimental.{prefix, requireIsHardware}
final class Connectable[+T <: Data] private (
val base: T,
private[chisel3] val waived: Set[Data],
private[chisel3] val squeezed: Set[Data]) {
private[chisel3] val squeezed: Set[Data],
private[chisel3] val excluded: Set[Data]) {
requireIsHardware(base, s"Can only created Connectable of components, not unbound Chisel types")

/** True if no members are waived or squeezed */
def notSpecial = waived.isEmpty && squeezed.isEmpty
/** True if no members are waived or squeezed or excluded */
def notWaivedOrSqueezedOrExcluded = waived.isEmpty && squeezed.isEmpty && excluded.isEmpty

private[chisel3] def copy(waived: Set[Data] = this.waived, squeezed: Set[Data] = this.squeezed): Connectable[T] =
new Connectable(base, waived, squeezed)
private[chisel3] def copy(
waived: Set[Data] = this.waived,
squeezed: Set[Data] = this.squeezed,
excluded: Set[Data] = this.excluded
): Connectable[T] =
new Connectable(base, waived, squeezed, excluded)

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

/** Adds base to excludes */
def exclude: Connectable[T] = this.copy(excluded = excluded ++ addOpaque(Seq(base)))

/** Select members of base to exclude
*
* @param members functions given the base return a member to exclude
*/
def exclude(members: (T => Data)*): Connectable[T] = this.copy(excluded = excluded ++ members.map(f => f(base)).toSet)

/** Select members of base to exclude and static cast to a new type
*
* @param members functions given the base return a member to exclude
*/
def excludeAs[S <: Data](members: (T => Data)*)(implicit ev: T <:< S): Connectable[S] =
this.copy(excluded = excluded ++ members.map(f => f(base)).toSet).asInstanceOf[Connectable[S]]

/** Programmatically select members of base to exclude and static cast to a new type
*
* @param members partial function applied to all recursive members of base, if match, can return a member to exclude
*/
def excludeEach[S <: Data](pf: PartialFunction[Data, Seq[Data]])(implicit ev: T <:< S): Connectable[S] = {
val excludedMembers = DataMirror.collectMembers(base)(pf).flatten
this.copy(excluded = excluded ++ excludedMembers.toSet).asInstanceOf[Connectable[S]]
}

/** Add any elements of members that are OpaqueType */
private def addOpaque(members: Seq[Data]): Seq[Data] = {
members.flatMap {
Expand All @@ -98,13 +128,18 @@ object Connectable {
def apply[T <: Data](
base: T,
waiveSelection: Data => Boolean = { _ => false },
squeezeSelection: Data => Boolean = { _ => false }
squeezeSelection: Data => Boolean = { _ => false },
excludeSelection: Data => Boolean = { _ => false }
): Connectable[T] = {
val (waived, squeezed) = {
val (waived, squeezed, excluded) = {
val members = DataMirror.collectMembers(base) { case x => x }
(members.filter(waiveSelection).toSet, members.filter(squeezeSelection).toSet)
(
members.filter(waiveSelection).toSet,
members.filter(squeezeSelection).toSet,
members.filter(excludeSelection).toSet
)
}
new Connectable(base, waived, squeezed)
new Connectable(base, waived, squeezed, excluded)
}

/** The default connection operators for Chisel hardware components
Expand Down
82 changes: 45 additions & 37 deletions core/src/main/scala/chisel3/connectable/Connection.scala
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ private[chisel3] case object ColonLessGreaterEq extends Connection {
// when calling DirectionConnection.connect. Hence, we can just default to false to take the non-optimized emission path
case e: Throwable => false
}
(typeEquivalent && consumer.notSpecial && producer.notSpecial)
(typeEquivalent && consumer.notWaivedOrSqueezedOrExcluded && producer.notWaivedOrSqueezedOrExcluded)
}
}

Expand Down Expand Up @@ -173,76 +173,84 @@ private[chisel3] object Connection {
import Alignment.deriveChildAlignment

def doConnection(
consumerAlignment: Alignment,
producerAlignment: Alignment
conAlign: Alignment,
proAlign: Alignment
)(
implicit sourceInfo: SourceInfo
): Unit = {
(consumerAlignment, producerAlignment) match {
(conAlign, proAlign) match {
// Base Case 0: should probably never happen
case (_: EmptyAlignment, _: EmptyAlignment) => ()

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

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

// Base Case 3: early exit if operator requires matching widths, but they aren't the same
case (consumerAlignment: NonEmptyAlignment, producerAlignment: NonEmptyAlignment)
if (consumerAlignment
.truncationRequired(producerAlignment, connectionOp)
.nonEmpty) && (connectionOp.noMismatchedWidths) =>
val mustBeTruncated = consumerAlignment.truncationRequired(producerAlignment, connectionOp).get
// Base Case 3: early exit if dangling/unconnected is wavied
case (conAlign: NonEmptyAlignment, proAlign: NonEmptyAlignment) if conAlign.isExcluded || proAlign.isExcluded =>
val (excluded, included) =
if (conAlign.isExcluded) (conAlign, proAlign)
else (proAlign, conAlign)
errors = (s"excluded field ${excluded.member} has matching non-excluded field ${included.member}") +: errors

// Base Case 4: early exit if operator requires matching orientations, but they don't align
case (conAlign: NonEmptyAlignment, proAlign: NonEmptyAlignment)
if (!conAlign.alignsWith(proAlign)) && (connectionOp.noWrongOrientations) =>
errors = (s"inversely oriented fields ${conAlign.member} and ${proAlign.member}") +: errors

// Base Case 5: early exit if operator requires matching widths, but they aren't the same
case (conAlign: NonEmptyAlignment, proAlign: NonEmptyAlignment)
if (conAlign.truncationRequired(proAlign, connectionOp).nonEmpty) && (connectionOp.noMismatchedWidths) =>
val mustBeTruncated = conAlign.truncationRequired(proAlign, connectionOp).get
errors =
(s"mismatched widths of ${consumerAlignment.member} and ${producerAlignment.member} might require truncation of $mustBeTruncated") +: errors
(s"mismatched widths of ${conAlign.member} and ${proAlign.member} might require truncation of $mustBeTruncated") +: errors

// Base Case 3: operator error on dangling/unconnected fields
// Base Case 6: operator error on dangling/unconnected fields
case (consumer: NonEmptyAlignment, _: EmptyAlignment) =>
errors = (s"${consumer.errorWord(connectionOp)} consumer field ${consumerAlignment.member}") +: errors
errors = (s"${consumer.errorWord(connectionOp)} consumer field ${conAlign.member}") +: errors
case (_: EmptyAlignment, producer: NonEmptyAlignment) =>
errors = (s"${producer.errorWord(connectionOp)} producer field ${producerAlignment.member}") +: errors
errors = (s"${producer.errorWord(connectionOp)} producer field ${proAlign.member}") +: errors

// Recursive Case 4: non-empty orientations
case (consumerAlignment: NonEmptyAlignment, producerAlignment: NonEmptyAlignment) =>
(consumerAlignment.member, producerAlignment.member) match {
case (conAlign: NonEmptyAlignment, proAlign: NonEmptyAlignment) =>
(conAlign.member, proAlign.member) match {
case (consumer: Aggregate, producer: Aggregate) =>
matchingZipOfChildren(Some(consumerAlignment), Some(producerAlignment)).foreach {
matchingZipOfChildren(Some(conAlign), Some(proAlign)).foreach {
case (ceo, peo) =>
doConnection(ceo.getOrElse(consumerAlignment.empty), peo.getOrElse(producerAlignment.empty))
doConnection(ceo.getOrElse(conAlign.empty), peo.getOrElse(proAlign.empty))
}
case (consumer: Aggregate, DontCare) =>
consumer.getElements.foreach {
case f =>
doConnection(
deriveChildAlignment(f, consumerAlignment),
deriveChildAlignment(f, consumerAlignment).swap(DontCare)
deriveChildAlignment(f, conAlign),
deriveChildAlignment(f, conAlign).swap(DontCare)
)
}
case (DontCare, producer: Aggregate) =>
producer.getElements.foreach {
case f =>
doConnection(
deriveChildAlignment(f, producerAlignment).swap(DontCare),
deriveChildAlignment(f, producerAlignment)
deriveChildAlignment(f, proAlign).swap(DontCare),
deriveChildAlignment(f, proAlign)
)
}
case (consumer, producer) =>
val alignment = (
consumerAlignment.alignsWith(producerAlignment),
(!consumerAlignment.alignsWith(
producerAlignment
conAlign.alignsWith(proAlign),
(!conAlign.alignsWith(
proAlign
) && connectionOp.connectToConsumer && !connectionOp.connectToProducer),
(!consumerAlignment.alignsWith(
producerAlignment
(!conAlign.alignsWith(
proAlign
) && !connectionOp.connectToConsumer && connectionOp.connectToProducer)
) match {
case (true, _, _) => consumerAlignment
case (_, true, _) => consumerAlignment
case (_, _, true) => producerAlignment
case (true, _, _) => conAlign
case (_, true, _) => conAlign
case (_, _, true) => proAlign
case other => throw new Exception(other.toString)
}
val lAndROpt = alignment.computeLandR(consumer, producer, connectionOp)
Expand Down
Loading

0 comments on commit 0600e1e

Please sign in to comment.