diff options
Diffstat (limited to 'chiselFrontend/src/main/scala/chisel3/internal')
6 files changed, 700 insertions, 17 deletions
diff --git a/chiselFrontend/src/main/scala/chisel3/internal/BiConnect.scala b/chiselFrontend/src/main/scala/chisel3/internal/BiConnect.scala new file mode 100644 index 00000000..e122f13a --- /dev/null +++ b/chiselFrontend/src/main/scala/chisel3/internal/BiConnect.scala @@ -0,0 +1,332 @@ +// See LICENSE for license details. + +package chisel3.internal + +import chisel3._ +import chisel3.experimental.{Analog, BaseModule, RawModule, attach} +import chisel3.internal.Builder.pushCommand +import chisel3.internal.firrtl.{Connect, DefInvalid} +import scala.language.experimental.macros +import chisel3.internal.sourceinfo._ + +/** +* BiConnect.connect executes a bidirectional connection element-wise. +* +* Note that the arguments are left and right (not source and sink) so the +* intent is for the operation to be commutative. +* +* The connect operation will recurse down the left Data (with the right Data). +* An exception will be thrown if a movement through the left cannot be matched +* in the right (or if the right side has extra fields). +* +* See elemConnect for details on how the root connections are issued. +* +*/ + +private[chisel3] object BiConnect { + // scalastyle:off method.name public.methods.have.type + // These are all the possible exceptions that can be thrown. + // These are from element-level connection + def BothDriversException = + BiConnectException(": Both Left and Right are drivers") + def NeitherDriverException = + BiConnectException(": Neither Left nor Right is a driver") + def UnknownDriverException = + BiConnectException(": Locally unclear whether Left or Right (both internal)") + def UnknownRelationException = + BiConnectException(": Left or Right unavailable to current module.") + // These are when recursing down aggregate types + def MismatchedVecException = + BiConnectException(": Left and Right are different length Vecs.") + def MissingLeftFieldException(field: String) = + BiConnectException(s".$field: Left Record missing field ($field).") + def MissingRightFieldException(field: String) = + BiConnectException(s": Right Record missing field ($field).") + def MismatchedException(left: String, right: String) = + BiConnectException(s": Left ($left) and Right ($right) have different types.") + def AttachAlreadyBulkConnectedException(sourceInfo: SourceInfo) = + BiConnectException(sourceInfo.makeMessage(": Analog previously bulk connected at " + _)) + def DontCareCantBeSink = + BiConnectException(": DontCare cannot be a connection sink (LHS)") + // scalastyle:on method.name public.methods.have.type + + /** This function is what recursively tries to connect a left and right together + * + * There is some cleverness in the use of internal try-catch to catch exceptions + * during the recursive decent and then rethrow them with extra information added. + * This gives the user a 'path' to where in the connections things went wrong. + */ + def connect(sourceInfo: SourceInfo, connectCompileOptions: CompileOptions, left: Data, right: Data, context_mod: RawModule): Unit = { // scalastyle:ignore line.size.limit cyclomatic.complexity method.length + (left, right) match { + // Handle element case (root case) + case (left_a: Analog, right_a: Analog) => + try { + analogAttach(sourceInfo, left_a, right_a, context_mod) + } catch { + // If attach fails, convert to BiConnectException + case attach.AttachException(message) => throw BiConnectException(message) + } + case (left_e: Element, right_e: Element) => { + elemConnect(sourceInfo, connectCompileOptions, left_e, right_e, context_mod) + // TODO(twigg): Verify the element-level classes are connectable + } + // Handle Vec case + case (left_v: Vec[Data@unchecked], right_v: Vec[Data@unchecked]) => { + 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") + } + } + } + // Handle Vec connected to DontCare + case (left_v: Vec[Data@unchecked], DontCare) => { + for (idx <- 0 until left_v.length) { + try { + implicit val compileOptions = connectCompileOptions + connect(sourceInfo, connectCompileOptions, left_v(idx), right, context_mod) + } catch { + case BiConnectException(message) => throw BiConnectException(s"($idx)$message") + } + } + } + // Handle DontCare connected to Vec + case (DontCare, right_v: Vec[Data@unchecked]) => { + for (idx <- 0 until right_v.length) { + try { + implicit val compileOptions = connectCompileOptions + connect(sourceInfo, connectCompileOptions, left, right_v(idx), context_mod) + } catch { + case BiConnectException(message) => throw BiConnectException(s"($idx)$message") + } + } + } + // Handle Records defined in Chisel._ code (change to NotStrict) + case (left_r: Record, right_r: Record) => (left_r.compileOptions, right_r.compileOptions) match { + case (ExplicitCompileOptions.NotStrict, _) => + left_r.bulkConnect(right_r)(sourceInfo, ExplicitCompileOptions.NotStrict) + case (_, ExplicitCompileOptions.NotStrict) => + left_r.bulkConnect(right_r)(sourceInfo, ExplicitCompileOptions.NotStrict) + case _ => recordConnect(sourceInfo, connectCompileOptions, left_r, right_r, context_mod) + } + + // Handle Records connected to DontCare (change to NotStrict) + case (left_r: Record, DontCare) => + left_r.compileOptions match { + case ExplicitCompileOptions.NotStrict => + left.bulkConnect(right)(sourceInfo, ExplicitCompileOptions.NotStrict) + case _ => + // For each field in left, descend with right + for ((field, left_sub) <- left_r.elements) { + try { + connect(sourceInfo, connectCompileOptions, left_sub, right, context_mod) + } catch { + case BiConnectException(message) => throw BiConnectException(s".$field$message") + } + } + } + case (DontCare, right_r: Record) => + right_r.compileOptions match { + case ExplicitCompileOptions.NotStrict => + left.bulkConnect(right)(sourceInfo, ExplicitCompileOptions.NotStrict) + case _ => + // For each field in left, descend with right + for ((field, right_sub) <- right_r.elements) { + try { + connect(sourceInfo, connectCompileOptions, left, right_sub, context_mod) + } catch { + case BiConnectException(message) => throw BiConnectException(s".$field$message") + } + } + } + + // Left and right are different subtypes of Data so fail + case (left, right) => throw MismatchedException(left.toString, right.toString) + } + } + + // Do connection of two Records + def recordConnect(sourceInfo: SourceInfo, + connectCompileOptions: CompileOptions, + left_r: Record, + right_r: Record, + context_mod: RawModule): Unit = { + // Verify right has no extra fields that left doesn't have + for((field, right_sub) <- right_r.elements) { + if(!left_r.elements.isDefinedAt(field)) { + if (connectCompileOptions.connectFieldsMustMatch) { + throw MissingLeftFieldException(field) + } + } + } + // For each field in left, descend with right + for((field, left_sub) <- left_r.elements) { + try { + right_r.elements.get(field) match { + case Some(right_sub) => connect(sourceInfo, connectCompileOptions, left_sub, right_sub, context_mod) + case None => { + if (connectCompileOptions.connectFieldsMustMatch) { + throw MissingRightFieldException(field) + } + } + } + } catch { + case BiConnectException(message) => throw BiConnectException(s".$field$message") + } + } + } + + + // 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 = { + // Source and sink are ambiguous in the case of a Bi/Bulk Connect (<>). + // If either is a DontCareBinding, just issue a DefInvalid for the other, + // otherwise, issue a Connect. + (left.topBinding, right.topBinding) match { + case (lb: DontCareBinding, _) => pushCommand(DefInvalid(sourceInfo, right.lref)) + case (_, rb: DontCareBinding) => pushCommand(DefInvalid(sourceInfo, left.lref)) + case (_, _) => pushCommand(Connect(sourceInfo, right.lref, left.ref)) + } + } + // Issue with left as sink, right as source + private def issueConnectR2L(left: Element, right: Element)(implicit sourceInfo: SourceInfo): Unit = { + // Source and sink are ambiguous in the case of a Bi/Bulk Connect (<>). + // If either is a DontCareBinding, just issue a DefInvalid for the other, + // otherwise, issue a Connect. + (left.topBinding, right.topBinding) match { + case (lb: DontCareBinding, _) => pushCommand(DefInvalid(sourceInfo, right.lref)) + case (_, rb: DontCareBinding) => pushCommand(DefInvalid(sourceInfo, left.lref)) + case (_, _) => pushCommand(Connect(sourceInfo, left.lref, right.ref)) + } + } + + // This function checks if element-level connection operation allowed. + // Then it either issues it or throws the appropriate exception. + def elemConnect(implicit sourceInfo: SourceInfo, connectCompileOptions: CompileOptions, left: Element, right: Element, context_mod: RawModule): Unit = { // scalastyle:ignore line.size.limit cyclomatic.complexity method.length + import BindingDirection.{Internal, Input, Output} // Using extensively so import these + // If left or right have no location, assume in context module + // This can occur if one of them is a literal, unbound will error previously + val left_mod: BaseModule = left.topBinding.location.getOrElse(context_mod) + val right_mod: BaseModule = right.topBinding.location.getOrElse(context_mod) + + val left_direction = BindingDirection.from(left.topBinding, left.direction) + val right_direction = BindingDirection.from(right.topBinding, right.direction) + + // CASE: Context is same module as left node and right node is in a child module + if( (left_mod == context_mod) && + (right_mod._parent.map(_ == context_mod).getOrElse(false)) ) { + // Thus, right node better be a port node and thus have a direction hint + ((left_direction, right_direction): @unchecked) match { + // CURRENT MOD CHILD MOD + case (Input, Input) => issueConnectL2R(left, right) + case (Internal, Input) => issueConnectL2R(left, right) + + case (Output, Output) => issueConnectR2L(left, right) + case (Internal, Output) => issueConnectR2L(left, right) + + case (Input, Output) => throw BothDriversException + case (Output, Input) => throw NeitherDriverException + case (_, Internal) => throw UnknownRelationException + } + } + + // CASE: Context is same module as right node and left node is in child module + else if( (right_mod == context_mod) && + (left_mod._parent.map(_ == context_mod).getOrElse(false)) ) { + // Thus, left node better be a port node and thus have a direction hint + ((left_direction, right_direction): @unchecked) match { + // CHILD MOD CURRENT MOD + case (Input, Input) => issueConnectR2L(left, right) + case (Input, Internal) => issueConnectR2L(left, right) + + case (Output, Output) => issueConnectL2R(left, right) + case (Output, Internal) => issueConnectL2R(left, right) + + case (Input, Output) => throw NeitherDriverException + case (Output, Input) => throw BothDriversException + case (Internal, _) => throw UnknownRelationException + } + } + + // CASE: Context is same module that both left node and right node are in + else if( (context_mod == left_mod) && (context_mod == right_mod) ) { + ((left_direction, right_direction): @unchecked) match { + // CURRENT MOD CURRENT MOD + case (Input, Output) => issueConnectL2R(left, right) + case (Input, Internal) => issueConnectL2R(left, right) + case (Internal, Output) => issueConnectL2R(left, right) + + case (Output, Input) => issueConnectR2L(left, right) + case (Output, Internal) => issueConnectR2L(left, right) + case (Internal, Input) => issueConnectR2L(left, right) + + case (Input, Input) => throw BothDriversException + case (Output, Output) => throw BothDriversException + case (Internal, Internal) => { + if (connectCompileOptions.dontAssumeDirectionality) { + throw UnknownDriverException + } else { + issueConnectR2L(left, right) + } + } + } + } + + // CASE: Context is the parent module of both the module containing left node + // and the module containing right node + // Note: This includes case when left and right in same module but in parent + else if( (left_mod._parent.map(_ == context_mod).getOrElse(false)) && + (right_mod._parent.map(_ == context_mod).getOrElse(false)) + ) { + // Thus both nodes must be ports and have a direction hint + ((left_direction, right_direction): @unchecked) match { + // CHILD MOD CHILD MOD + case (Input, Output) => issueConnectR2L(left, right) + case (Output, Input) => issueConnectL2R(left, right) + + case (Input, Input) => throw NeitherDriverException + case (Output, Output) => throw BothDriversException + case (_, Internal) => + if (connectCompileOptions.dontAssumeDirectionality) { + throw UnknownRelationException + } else { + issueConnectR2L(left, right) + } + case (Internal, _) => + if (connectCompileOptions.dontAssumeDirectionality) { + throw UnknownRelationException + } else { + issueConnectR2L(left, right) + } + } + } + + // Not quite sure where left and right are compared to current module + // so just error out + else throw UnknownRelationException + } + + // This function checks if analog element-level attaching is allowed + // Then it either issues it or throws the appropriate exception. + def analogAttach(implicit sourceInfo: SourceInfo, left: Analog, right: Analog, contextModule: RawModule): Unit = { + // Error if left or right is BICONNECTED in the current module already + for (elt <- left :: right :: Nil) { + elt.biConnectLocs.get(contextModule) match { + case Some(sl) => throw AttachAlreadyBulkConnectedException(sl) + case None => // Do nothing + } + } + + // Do the attachment + attach.impl(Seq(left, right), contextModule) + // Mark bulk connected + left.biConnectLocs(contextModule) = sourceInfo + right.biConnectLocs(contextModule) = sourceInfo + } +} diff --git a/chiselFrontend/src/main/scala/chisel3/internal/Binding.scala b/chiselFrontend/src/main/scala/chisel3/internal/Binding.scala new file mode 100644 index 00000000..34de36a3 --- /dev/null +++ b/chiselFrontend/src/main/scala/chisel3/internal/Binding.scala @@ -0,0 +1,106 @@ +// See LICENSE for license details. + +package chisel3.internal + +import chisel3._ +import chisel3.experimental.{BaseModule, RawModule} +import chisel3.internal.firrtl.LitArg + +/** Requires that a node is hardware ("bound") + */ +object requireIsHardware { + def apply(node: Data, msg: String = ""): Unit = { + node._parent match { // Compatibility layer hack + case Some(x: BaseModule) => x._compatAutoWrapPorts + case _ => + } + if (!node.isSynthesizable) { + val prefix = if (msg.nonEmpty) s"$msg " else "" + throw ExpectedHardwareException(s"$prefix'$node' must be hardware, " + + "not a bare Chisel type. Perhaps you forgot to wrap it in Wire(_) or IO(_)?") + } + } +} + +/** Requires that a node is a chisel type (not hardware, "unbound") + */ +object requireIsChiselType { + def apply(node: Data, msg: String = ""): Unit = if (node.isSynthesizable) { + val prefix = if (msg.nonEmpty) s"$msg " else "" + throw ExpectedChiselTypeException(s"$prefix'$node' must be a Chisel type, not hardware") + } +} + +// Element only direction used for the Binding system only. +private[chisel3] sealed abstract class BindingDirection +private[chisel3] object BindingDirection { + /** Internal type or wire + */ + case object Internal extends BindingDirection + /** Module port with output direction + */ + case object Output extends BindingDirection + /** Module port with input direction + */ + case object Input extends BindingDirection + + /** Determine the BindingDirection of an Element given its top binding and resolved direction. + */ + def from(binding: TopBinding, direction: ActualDirection): BindingDirection = { + binding match { + case PortBinding(_) => direction match { + case ActualDirection.Output => Output + case ActualDirection.Input => Input + case dir => throw new RuntimeException(s"Unexpected port element direction '$dir'") + } + case _ => Internal + } + } +} + +// Location refers to 'where' in the Module hierarchy this lives +sealed trait Binding { + def location: Option[BaseModule] +} +// Top-level binding representing hardware, not a pointer to another binding (like ChildBinding) +sealed trait TopBinding extends Binding + +// Constrained-ness refers to whether 'bound by Module boundaries' +// An unconstrained binding, like a literal, can be read by everyone +sealed trait UnconstrainedBinding extends TopBinding { + def location: Option[BaseModule] = None +} +// A constrained binding can only be read/written by specific modules +// Location will track where this Module is, and the bound object can be referenced in FIRRTL +sealed trait ConstrainedBinding extends TopBinding { + def enclosure: BaseModule + def location: Option[BaseModule] = Some(enclosure) +} + +// A binding representing a data that cannot be (re)assigned to. +sealed trait ReadOnlyBinding extends TopBinding + +// TODO(twigg): Ops between unenclosed nodes can also be unenclosed +// However, Chisel currently binds all op results to a module +case class OpBinding(enclosure: RawModule) extends ConstrainedBinding with ReadOnlyBinding +case class MemoryPortBinding(enclosure: RawModule) extends ConstrainedBinding +case class PortBinding(enclosure: BaseModule) extends ConstrainedBinding +case class RegBinding(enclosure: RawModule) extends ConstrainedBinding +case class WireBinding(enclosure: RawModule) extends ConstrainedBinding + +case class ChildBinding(parent: Data) extends Binding { + def location: Option[BaseModule] = parent.topBinding.location +} +/** Special binding for Vec.sample_element */ +case class SampleElementBinding[T <: Data](parent: Vec[T]) extends Binding { + def location = parent.topBinding.location +} +// A DontCare element has a specific Binding, somewhat like a literal. +// It is a source (RHS). It may only be connected/applied to sinks. +case class DontCareBinding() extends UnconstrainedBinding + +sealed trait LitBinding extends UnconstrainedBinding with ReadOnlyBinding +// Literal binding attached to a element that is not part of a Bundle. +case class ElementLitBinding(litArg: LitArg) extends LitBinding +// Literal binding attached to the root of a Bundle, containing literal values of its children. +case class BundleLitBinding(litMap: Map[Data, LitArg]) extends LitBinding diff --git a/chiselFrontend/src/main/scala/chisel3/internal/Builder.scala b/chiselFrontend/src/main/scala/chisel3/internal/Builder.scala index d825f39d..664813f7 100644 --- a/chiselFrontend/src/main/scala/chisel3/internal/Builder.scala +++ b/chiselFrontend/src/main/scala/chisel3/internal/Builder.scala @@ -3,10 +3,11 @@ package chisel3.internal import scala.util.DynamicVariable -import scala.collection.mutable.{ArrayBuffer, HashMap} +import scala.collection.mutable.ArrayBuffer import chisel3._ -import core._ -import firrtl._ +import chisel3.experimental._ +import chisel3.internal.firrtl._ +import chisel3.internal.naming._ import _root_.firrtl.annotations.{CircuitName, ComponentName, ModuleName, Named} private[chisel3] class Namespace(keywords: Set[String]) { @@ -186,7 +187,7 @@ private[chisel3] class DynamicContext() { var currentClock: Option[Clock] = None var currentReset: Option[Reset] = None val errors = new ErrorLog - val namingStack = new internal.naming.NamingStack + val namingStack = new NamingStack } //scalastyle:off number.of.methods @@ -204,7 +205,7 @@ private[chisel3] object Builder { private def initializeSingletons(): Unit = { // This used to contain: // val dummy = core.DontCare - // but this would occasionally produce hangs dues to static initialization deadlock + // but this would occasionally produce hangs due to static initialization deadlock // when Builder initialization collided with chisel3.package initialization of the DontCare value. // See: // http://ternarysearch.blogspot.com/2013/07/static-initialization-deadlock.html @@ -212,14 +213,14 @@ private[chisel3] object Builder { // https://stackoverflow.com/questions/28631656/runnable-thread-state-but-in-object-wait } - def namingStackOption: Option[internal.naming.NamingStack] = dynamicContextVar.value.map(_.namingStack) + def namingStackOption: Option[NamingStack] = dynamicContextVar.value.map(_.namingStack) def idGen: IdGen = chiselContext.value.idGen def globalNamespace: Namespace = dynamicContext.globalNamespace def components: ArrayBuffer[Component] = dynamicContext.components def annotations: ArrayBuffer[ChiselAnnotation] = dynamicContext.annotations - def namingStack: internal.naming.NamingStack = dynamicContext.namingStack + def namingStack: NamingStack = dynamicContext.namingStack def currentModule: Option[BaseModule] = dynamicContextVar.value match { case Some(dyanmicContext) => dynamicContext.currentModule @@ -366,19 +367,19 @@ private[chisel3] object Builder { * objects. */ object DynamicNamingStack { - def pushContext(): internal.naming.NamingContextInterface = { + def pushContext(): NamingContextInterface = { Builder.namingStackOption match { case Some(namingStack) => namingStack.pushContext() - case None => internal.naming.DummyNamer + case None => DummyNamer } } - def popReturnContext[T <: Any](prefixRef: T, until: internal.naming.NamingContextInterface): T = { + def popReturnContext[T <: Any](prefixRef: T, until: NamingContextInterface): T = { until match { - case internal.naming.DummyNamer => + case DummyNamer => require(Builder.namingStackOption.isEmpty, "Builder context must remain stable throughout a chiselName-annotated function invocation") - case context: internal.naming.NamingContext => + case context: NamingContext => require(Builder.namingStackOption.isDefined, "Builder context must remain stable throughout a chiselName-annotated function invocation") Builder.namingStackOption.get.popContext(prefixRef, context) diff --git a/chiselFrontend/src/main/scala/chisel3/internal/Error.scala b/chiselFrontend/src/main/scala/chisel3/internal/Error.scala index 59f32542..91d9d7de 100644 --- a/chiselFrontend/src/main/scala/chisel3/internal/Error.scala +++ b/chiselFrontend/src/main/scala/chisel3/internal/Error.scala @@ -4,8 +4,6 @@ package chisel3.internal import scala.collection.mutable.{ArrayBuffer, LinkedHashMap} -import chisel3.core._ - class ChiselException(message: String, cause: Throwable = null) extends Exception(message, cause) { val blacklistPackages = Set("chisel3", "scala", "java", "sun", "sbt") @@ -126,7 +124,7 @@ private[chisel3] class ErrorLog { "java.", "scala.", "chisel3.internal.", - "chisel3.core.", + "chisel3.experimental.", "chisel3.package$" // for some compatibility / deprecated types ) !chiselPrefixes.filter(className.startsWith(_)).isEmpty diff --git a/chiselFrontend/src/main/scala/chisel3/internal/MonoConnect.scala b/chiselFrontend/src/main/scala/chisel3/internal/MonoConnect.scala new file mode 100644 index 00000000..91f1bfd8 --- /dev/null +++ b/chiselFrontend/src/main/scala/chisel3/internal/MonoConnect.scala @@ -0,0 +1,246 @@ +// See LICENSE for license details. + +package chisel3.internal + +import chisel3._ +import chisel3.experimental.{BaseModule, EnumType, FixedPoint, RawModule, UnsafeEnum} +import chisel3.internal.Builder.pushCommand +import chisel3.internal.firrtl.{Connect, DefInvalid} +import scala.language.experimental.macros +import chisel3.internal.sourceinfo.SourceInfo + +/** +* MonoConnect.connect executes a mono-directional connection element-wise. +* +* Note that this isn't commutative. There is an explicit source and sink +* already determined before this function is called. +* +* The connect operation will recurse down the left Data (with the right Data). +* An exception will be thrown if a movement through the left cannot be matched +* in the right. The right side is allowed to have extra Record fields. +* Vecs must still be exactly the same size. +* +* See elemConnect for details on how the root connections are issued. +* +* Note that a valid sink must be writable so, one of these must hold: +* - Is an internal writable node (Reg or Wire) +* - Is an output of the current module +* - Is an input of a submodule of the current module +* +* Note that a valid source must be readable so, one of these must hold: +* - Is an internal readable node (Reg, Wire, Op) +* - Is a literal +* - Is a port of the current module or submodule of the current module +*/ + +private[chisel3] object MonoConnect { + // scalastyle:off method.name public.methods.have.type + // These are all the possible exceptions that can be thrown. + // These are from element-level connection + def UnreadableSourceException = + MonoConnectException(": Source is unreadable from current module.") + def UnwritableSinkException = + MonoConnectException(": Sink is unwriteable by current module.") + def UnknownRelationException = + MonoConnectException(": Sink or source unavailable to current module.") + // These are when recursing down aggregate types + def MismatchedVecException = + MonoConnectException(": Sink and Source are different length Vecs.") + def MissingFieldException(field: String) = + MonoConnectException(s": Source Record missing field ($field).") + def MismatchedException(sink: String, source: String) = + MonoConnectException(s": Sink ($sink) and Source ($source) have different types.") + def DontCareCantBeSink = + MonoConnectException(": DontCare cannot be a connection sink (LHS)") + // scalastyle:on method.name public.methods.have.type + + /** This function is what recursively tries to connect a sink and source together + * + * There is some cleverness in the use of internal try-catch to catch exceptions + * during the recursive decent and then rethrow them with extra information added. + * This gives the user a 'path' to where in the connections things went wrong. + */ + def connect( //scalastyle:off cyclomatic.complexity method.length + sourceInfo: SourceInfo, + connectCompileOptions: CompileOptions, + sink: Data, + source: Data, + context_mod: RawModule): Unit = + (sink, source) match { + + // Handle legal element cases, note (Bool, Bool) is caught by the first two, as Bool is a UInt + case (sink_e: Bool, source_e: UInt) => + elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod) + case (sink_e: UInt, source_e: Bool) => + elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod) + case (sink_e: UInt, source_e: UInt) => + elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod) + case (sink_e: SInt, source_e: SInt) => + elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod) + case (sink_e: FixedPoint, source_e: FixedPoint) => + elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod) + case (sink_e: Clock, source_e: Clock) => + elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod) + case (sink_e: EnumType, source_e: UnsafeEnum) => + elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod) + case (sink_e: EnumType, source_e: EnumType) if sink_e.typeEquivalent(source_e) => + elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod) + case (sink_e: UnsafeEnum, source_e: UInt) => + elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod) + + // 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") + } + } + // Handle Vec connected to DontCare. Apply the DontCare to individual elements. + case (sink_v: Vec[Data @unchecked], DontCare) => + for(idx <- 0 until sink_v.length) { + try { + implicit val compileOptions = connectCompileOptions + connect(sourceInfo, connectCompileOptions, sink_v(idx), source, context_mod) + } catch { + case MonoConnectException(message) => throw MonoConnectException(s"($idx)$message") + } + } + + // 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) + } + } + } + } catch { + case MonoConnectException(message) => throw MonoConnectException(s".$field$message") + } + } + // Handle Record connected to DontCare. Apply the DontCare to individual elements. + case (sink_r: Record, DontCare) => + // For each field, descend with right + for((field, sink_sub) <- sink_r.elements) { + try { + connect(sourceInfo, connectCompileOptions, sink_sub, source, context_mod) + } catch { + case MonoConnectException(message) => throw MonoConnectException(s".$field$message") + } + } + + // Source is DontCare - it may be connected to anything. It generates a defInvalid for the sink. + case (sink, DontCare) => pushCommand(DefInvalid(sourceInfo, sink.lref)) + // DontCare as a sink is illegal. + case (DontCare, _) => throw DontCareCantBeSink + // Sink and source are different subtypes of data so fail + case (sink, source) => throw MismatchedException(sink.toString, source.toString) + } + + // 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, + // otherwise, issue a Connect. + source.topBinding match { + case b: DontCareBinding => pushCommand(DefInvalid(sourceInfo, sink.lref)) + case _ => pushCommand(Connect(sourceInfo, sink.lref, source.ref)) + } + } + + // This function checks if element-level connection operation allowed. + // Then it either issues it or throws the appropriate exception. + def elemConnect(implicit sourceInfo: SourceInfo, connectCompileOptions: CompileOptions, sink: Element, source: Element, context_mod: RawModule): Unit = { // scalastyle:ignore line.size.limit + import BindingDirection.{Internal, Input, Output} // Using extensively so import these + // 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) + val source_mod: BaseModule = source.topBinding.location.getOrElse(context_mod) + + val sink_direction = BindingDirection.from(sink.topBinding, sink.direction) + val source_direction = BindingDirection.from(source.topBinding, source.direction) + + // CASE: Context is same module that both left node and right node are in + if( (context_mod == sink_mod) && (context_mod == source_mod) ) { + ((sink_direction, source_direction): @unchecked) match { + // SINK SOURCE + // CURRENT MOD CURRENT MOD + case (Output, _) => issueConnect(sink, source) + case (Internal, _) => issueConnect(sink, source) + case (Input, _) => throw UnwritableSinkException + } + } + + // CASE: Context is same module as sink node and right node is in a child module + else if( (sink_mod == context_mod) && + (source_mod._parent.map(_ == context_mod).getOrElse(false)) ) { + // Thus, right node better be a port node and thus have a direction + ((sink_direction, source_direction): @unchecked) match { + // SINK SOURCE + // CURRENT MOD CHILD MOD + case (Internal, Output) => issueConnect(sink, source) + case (Internal, Input) => issueConnect(sink, source) + case (Output, Output) => issueConnect(sink, source) + case (Output, Input) => issueConnect(sink, source) + case (_, Internal) => { + if (!(connectCompileOptions.dontAssumeDirectionality)) { + issueConnect(sink, source) + } else { + throw UnreadableSourceException + } + } + case (Input, Output) if (!(connectCompileOptions.dontTryConnectionsSwapped)) => issueConnect(source, sink) // scalastyle:ignore line.size.limit + case (Input, _) => throw UnwritableSinkException + } + } + + // CASE: Context is same module as source node and sink node is in child module + else if( (source_mod == context_mod) && + (sink_mod._parent.map(_ == context_mod).getOrElse(false)) ) { + // Thus, left node better be a port node and thus have a direction + ((sink_direction, source_direction): @unchecked) match { + // SINK SOURCE + // CHILD MOD CURRENT MOD + case (Input, _) => issueConnect(sink, source) + case (Output, _) => throw UnwritableSinkException + case (Internal, _) => throw UnwritableSinkException + } + } + + // 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_mod._parent.map(_ == context_mod).getOrElse(false)) && + (source_mod._parent.map(_ == context_mod).getOrElse(false)) + ) { + // Thus both nodes must be ports and have a direction + ((sink_direction, source_direction): @unchecked) match { + // SINK SOURCE + // CHILD MOD CHILD MOD + case (Input, Input) => issueConnect(sink, source) + case (Input, Output) => issueConnect(sink, source) + case (Output, _) => throw UnwritableSinkException + case (_, Internal) => { + if (!(connectCompileOptions.dontAssumeDirectionality)) { + issueConnect(sink, source) + } else { + throw UnreadableSourceException + } + } + case (Internal, _) => throw UnwritableSinkException + } + } + + // Not quite sure where left and right are compared to current module + // so just error out + else throw UnknownRelationException + } +} diff --git a/chiselFrontend/src/main/scala/chisel3/internal/firrtl/IR.scala b/chiselFrontend/src/main/scala/chisel3/internal/firrtl/IR.scala index 7854efdb..2cb4d092 100644 --- a/chiselFrontend/src/main/scala/chisel3/internal/firrtl/IR.scala +++ b/chiselFrontend/src/main/scala/chisel3/internal/firrtl/IR.scala @@ -3,9 +3,9 @@ package chisel3.internal.firrtl import chisel3._ -import core._ import chisel3.internal._ -import chisel3.internal.sourceinfo.{SourceInfo, NoSourceInfo} +import chisel3.internal.sourceinfo.SourceInfo +import chisel3.experimental.{BaseModule, ChiselAnnotation, Param, RawModule} // scalastyle:off number.of.types |
