diff options
Diffstat (limited to 'chiselFrontend/src/main/scala/chisel3')
18 files changed, 487 insertions, 594 deletions
diff --git a/chiselFrontend/src/main/scala/chisel3/core/Aggregate.scala b/chiselFrontend/src/main/scala/chisel3/core/Aggregate.scala index 3f81de9f..701cf892 100644 --- a/chiselFrontend/src/main/scala/chisel3/core/Aggregate.scala +++ b/chiselFrontend/src/main/scala/chisel3/core/Aggregate.scala @@ -15,11 +15,58 @@ import chisel3.internal.sourceinfo._ * of) other Data objects. */ sealed abstract class Aggregate extends Data { + private[chisel3] override def bind(target: Binding, parentDirection: UserDirection) { + binding = target + + val resolvedDirection = UserDirection.fromParent(parentDirection, userDirection) + for (child <- getElements) { + child.bind(ChildBinding(this), resolvedDirection) + } + + // Check that children obey the directionality rules. + val childDirections = getElements.map(_.direction).toSet + direction = if (childDirections == Set()) { // Sadly, Scala can't do set matching + // If empty, use my assigned direction + resolvedDirection match { + case UserDirection.Unspecified | UserDirection.Flip => ActualDirection.Unspecified + case UserDirection.Output => ActualDirection.Output + case UserDirection.Input => ActualDirection.Input + } + } else if (childDirections == Set(ActualDirection.Unspecified)) { + ActualDirection.Unspecified + } else if (childDirections == Set(ActualDirection.Input)) { + ActualDirection.Input + } else if (childDirections == Set(ActualDirection.Output)) { + ActualDirection.Output + } else if (childDirections subsetOf + Set(ActualDirection.Output, ActualDirection.Input, + ActualDirection.Bidirectional(ActualDirection.Default), + ActualDirection.Bidirectional(ActualDirection.Flipped))) { + resolvedDirection match { + case UserDirection.Unspecified => ActualDirection.Bidirectional(ActualDirection.Default) + case UserDirection.Flip => ActualDirection.Bidirectional(ActualDirection.Flipped) + case _ => throw new RuntimeException("Unexpected forced Input / Output") + } + } else { + this match { + // Anything flies in compatibility mode + case t: Record if !t.compileOptions.dontAssumeDirectionality => resolvedDirection match { + case UserDirection.Unspecified => ActualDirection.Bidirectional(ActualDirection.Default) + case UserDirection.Flip => ActualDirection.Bidirectional(ActualDirection.Flipped) + case _ => ActualDirection.Bidirectional(ActualDirection.Default) + } + case _ => + val childWithDirections = getElements zip getElements.map(_.direction) + throw Binding.MixedDirectionAggregateException(s"Aggregate '$this' can't have elements that are both directioned and undirectioned: $childWithDirections") + } + } + } + /** Returns a Seq of the immediate contents of this Aggregate, in order. */ def getElements: Seq[Data] - private[core] def width: Width = getElements.map(_.width).foldLeft(0.W)(_ + _) + private[chisel3] def width: Width = getElements.map(_.width).foldLeft(0.W)(_ + _) private[core] def legacyConnect(that: Data)(implicit sourceInfo: SourceInfo): Unit = pushCommand(BulkConnect(sourceInfo, this.lref, that.lref)) @@ -68,25 +115,21 @@ object Vec { // Check that types are homogeneous. Width mismatch for Elements is safe. require(!elts.isEmpty) + elts.foreach(requireIsHardware(_, "vec element")) val vec = Wire(new Vec(cloneSupertype(elts, "Vec"), elts.length)) - def doConnect(sink: T, source: T) = { - // TODO: this looks bad, and should feel bad. Replace with a better abstraction. - // NOTE: Must use elts.head instead of vec.sample_element because vec.sample_element has - // WireBinding which does not have a direction - val hasDirectioned = elts.head match { - case t: Aggregate => t.flatten.exists(_.dir != Direction.Unspecified) - case t: Element => t.dir != Direction.Unspecified - } - if (hasDirectioned) { - sink bulkConnect source - } else { - sink connect source - } - } - for ((v, e) <- vec zip elts) { - doConnect(v, e) + // TODO: try to remove the logic for this mess + elts.head.direction match { + case ActualDirection.Input | ActualDirection.Output | ActualDirection.Unspecified => + // When internal wires are involved, driver / sink must be specified explicitly, otherwise + // the system is unable to infer which is driver / sink + (vec zip elts).foreach(x => x._1 := x._2) + case ActualDirection.Bidirectional(_) => + // For bidirectional, must issue a bulk connect so subelements are resolved correctly. + // Bulk connecting two wires may not succeed because Chisel frontend does not infer + // directions. + (vec zip elts).foreach(x => x._1 <> x._2) } vec } @@ -186,7 +229,7 @@ sealed class Vec[T <: Data] private (gen: => T, val length: Int) * * Needed specifically for the case when the Vec is length 0. */ - private[core] val sample_element: T = gen + private[chisel3] val sample_element: T = gen // allElements current includes sample_element // This is somewhat weird although I think the best course of action here is @@ -226,16 +269,25 @@ sealed class Vec[T <: Data] private (gen: => T, val length: Int) override def apply(p: UInt): T = macro CompileOptionsTransform.pArg def do_apply(p: UInt)(implicit compileOptions: CompileOptions): T = { - Binding.checkSynthesizable(p ,s"'p' ($p)") + requireIsHardware(p, "vec index") val port = gen - val i = Vec.truncateIndex(p, length)(UnlocatableSourceInfo, compileOptions) - port.setRef(this, i) - // Bind each element of port to being whatever the base type is - // Using the head element as the sample_element - for((port_elem, model_elem) <- port.allElements zip sample_element.allElements) { - port_elem.binding = model_elem.binding + // Reconstruct the resolvedDirection (in Aggregate.bind), since it's not stored. + // It may not be exactly equal to that value, but the results are the same. + val reconstructedResolvedDirection = direction match { + case ActualDirection.Input => UserDirection.Input + case ActualDirection.Output => UserDirection.Output + case ActualDirection.Bidirectional(ActualDirection.Default) | ActualDirection.Unspecified => + UserDirection.Unspecified + case ActualDirection.Bidirectional(ActualDirection.Flipped) => UserDirection.Flip } + // TODO port technically isn't directly child of this data structure, but the result of some + // muxes / demuxes. However, this does make access consistent with the top-level bindings. + // Perhaps there's a cleaner way of accomplishing this... + port.bind(ChildBinding(this), reconstructedResolvedDirection) + + val i = Vec.truncateIndex(p, length)(UnlocatableSourceInfo, compileOptions) + port.setRef(this, i) port } @@ -256,7 +308,6 @@ sealed class Vec[T <: Data] private (gen: => T, val length: Int) new Vec(gen.cloneType, length).asInstanceOf[this.type] } - private[chisel3] def toType: String = s"${sample_element.toType}[$length]" override def getElements: Seq[Data] = (0 until length).map(apply(_)) @@ -384,14 +435,6 @@ abstract class Record(private[chisel3] implicit val compileOptions: CompileOptio /** Name for Pretty Printing */ def className: String = this.getClass.getSimpleName - private[chisel3] def toType = { - def eltPort(elt: Data): String = { - val flipStr: String = if(Data.isFirrtlFlipped(elt)) "flip " else "" - s"${flipStr}${elt.getRef.name} : ${elt.toType}" - } - elements.toIndexedSeq.reverse.map(e => eltPort(e._2)).mkString("{", ", ", "}") - } - private[core] override def typeEquivalent(that: Data): Boolean = that match { case that: Record => this.getClass == that.getClass && diff --git a/chiselFrontend/src/main/scala/chisel3/core/BiConnect.scala b/chiselFrontend/src/main/scala/chisel3/core/BiConnect.scala index 0e5d4e8b..f2b2b7e1 100644 --- a/chiselFrontend/src/main/scala/chisel3/core/BiConnect.scala +++ b/chiselFrontend/src/main/scala/chisel3/core/BiConnect.scala @@ -138,15 +138,14 @@ object BiConnect { // 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: UserModule): Unit = { - import Direction.{Input, Output} // Using extensively so import these + 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.binding.location.getOrElse(context_mod) - val right_mod: BaseModule = right.binding.location.getOrElse(context_mod) + val left_mod: BaseModule = left.topBinding.location.getOrElse(context_mod) + val right_mod: BaseModule = right.topBinding.location.getOrElse(context_mod) - val left_direction: Option[Direction] = left.binding.direction - val right_direction: Option[Direction] = right.binding.direction - // None means internal + 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) && @@ -154,15 +153,15 @@ object BiConnect { // 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 (Some(Input), Some(Input)) => issueConnectL2R(left, right) - case (None, Some(Input)) => issueConnectL2R(left, right) + case (Input, Input) => issueConnectL2R(left, right) + case (Internal, Input) => issueConnectL2R(left, right) - case (Some(Output), Some(Output)) => issueConnectR2L(left, right) - case (None, Some(Output)) => issueConnectR2L(left, right) + case (Output, Output) => issueConnectR2L(left, right) + case (Internal, Output) => issueConnectR2L(left, right) - case (Some(Input), Some(Output)) => throw BothDriversException - case (Some(Output), Some(Input)) => throw NeitherDriverException - case (_, None) => throw UnknownRelationException + case (Input, Output) => throw BothDriversException + case (Output, Input) => throw NeitherDriverException + case (_, Internal) => throw UnknownRelationException } } @@ -172,15 +171,15 @@ object BiConnect { // 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 (Some(Input), Some(Input)) => issueConnectR2L(left, right) - case (Some(Input), None) => issueConnectR2L(left, right) + case (Input, Input) => issueConnectR2L(left, right) + case (Input, Internal) => issueConnectR2L(left, right) - case (Some(Output), Some(Output)) => issueConnectL2R(left, right) - case (Some(Output), None) => issueConnectL2R(left, right) + case (Output, Output) => issueConnectL2R(left, right) + case (Output, Internal) => issueConnectL2R(left, right) - case (Some(Input), Some(Output)) => throw NeitherDriverException - case (Some(Output), Some(Input)) => throw BothDriversException - case (None, _) => throw UnknownRelationException + case (Input, Output) => throw NeitherDriverException + case (Output, Input) => throw BothDriversException + case (Internal, _) => throw UnknownRelationException } } @@ -188,39 +187,17 @@ object BiConnect { else if( (context_mod == left_mod) && (context_mod == right_mod) ) { ((left_direction, right_direction): @unchecked) match { // CURRENT MOD CURRENT MOD - case (Some(Input), Some(Output)) => issueConnectL2R(left, right) - case (Some(Input), None) => issueConnectL2R(left, right) - case (None, Some(Output)) => issueConnectL2R(left, right) + case (Input, Output) => issueConnectL2R(left, right) + case (Input, Internal) => issueConnectL2R(left, right) + case (Internal, Output) => issueConnectL2R(left, right) - case (Some(Output), Some(Input)) => issueConnectR2L(left, right) - case (Some(Output), None) => issueConnectR2L(left, right) - case (None, Some(Input)) => issueConnectR2L(left, right) + case (Output, Input) => issueConnectR2L(left, right) + case (Output, Internal) => issueConnectR2L(left, right) + case (Internal, Input) => issueConnectR2L(left, right) - case (Some(Input), Some(Input)) => { - if (connectCompileOptions.dontAssumeDirectionality) { - throw BothDriversException - } else { - (left.binding, right.binding) match { - case (PortBinding(_, _), PortBinding(_, _)) => throw BothDriversException - case (PortBinding(_, _), _) => issueConnectL2R(left, right) - case (_, PortBinding(_, _)) => issueConnectR2L(left, right) - case _ => throw BothDriversException - } - } - } - case (Some(Output), Some(Output)) => { - if (connectCompileOptions.dontAssumeDirectionality) { - throw BothDriversException - } else { - (left.binding, right.binding) match { - case (PortBinding(_, _), PortBinding(_, _)) => throw BothDriversException - case (PortBinding(_, _), _) => issueConnectR2L(left, right) - case (_, PortBinding(_, _)) => issueConnectL2R(left, right) - case _ => throw BothDriversException - } - } - } - case (None, None) => { + case (Input, Input) => throw BothDriversException + case (Output, Output) => throw BothDriversException + case (Internal, Internal) => { if (connectCompileOptions.dontAssumeDirectionality) { throw UnknownDriverException } else { @@ -239,18 +216,18 @@ object BiConnect { // Thus both nodes must be ports and have a direction hint ((left_direction, right_direction): @unchecked) match { // CHILD MOD CHILD MOD - case (Some(Input), Some(Output)) => issueConnectR2L(left, right) - case (Some(Output), Some(Input)) => issueConnectL2R(left, right) + case (Input, Output) => issueConnectR2L(left, right) + case (Output, Input) => issueConnectL2R(left, right) - case (Some(Input), Some(Input)) => throw NeitherDriverException - case (Some(Output), Some(Output)) => throw BothDriversException - case (_, None) => + case (Input, Input) => throw NeitherDriverException + case (Output, Output) => throw BothDriversException + case (_, Internal) => if (connectCompileOptions.dontAssumeDirectionality) { throw UnknownRelationException } else { issueConnectR2L(left, right) } - case (None, _) => + case (Internal, _) => if (connectCompileOptions.dontAssumeDirectionality) { throw UnknownRelationException } else { diff --git a/chiselFrontend/src/main/scala/chisel3/core/Binder.scala b/chiselFrontend/src/main/scala/chisel3/core/Binder.scala deleted file mode 100644 index d872d7c6..00000000 --- a/chiselFrontend/src/main/scala/chisel3/core/Binder.scala +++ /dev/null @@ -1,64 +0,0 @@ -package chisel3.core - -/** -* A Binder is a function from UnboundBinding to some Binding. -* -* These are used exclusively by Binding.bind and sealed in order to keep -* all of them in one place. There are two flavors of Binders: -* Non-terminal (returns another UnboundBinding): These are used to reformat an -* UnboundBinding (like setting direction) before it is terminally bound. -* Terminal (returns any other Binding): Due to the nature of Bindings, once a -* Data is bound to anything but an UnboundBinding, it is forever locked to -* being that type (as it now represents something in the hardware graph). -* -* Note that some Binders require extra arguments to be constructed, like the -* enclosing Module. -*/ - -sealed trait Binder[Out <: Binding] extends Function1[UnboundBinding, Out]{ - def apply(in: UnboundBinding): Out -} - -// THE NON-TERMINAL BINDERS -// These 'rebind' to another unbound node of different direction! -case object InputBinder extends Binder[UnboundBinding] { - def apply(in: UnboundBinding) = UnboundBinding(Some(Direction.Input)) -} -case object OutputBinder extends Binder[UnboundBinding] { - def apply(in: UnboundBinding) = UnboundBinding(Some(Direction.Output)) -} -case object FlippedBinder extends Binder[UnboundBinding] { - def apply(in: UnboundBinding) = UnboundBinding(in.direction.map(_.flip)) - // TODO(twigg): flipping a None should probably be a warning/error -} -// The need for this should be transient. -case object NoDirectionBinder extends Binder[UnboundBinding] { - def apply(in: UnboundBinding) = UnboundBinding(None) -} - -// THE TERMINAL BINDERS -case object LitBinder extends Binder[LitBinding] { - def apply(in: UnboundBinding) = LitBinding() -} - -case class MemoryPortBinder(enclosure: UserModule) extends Binder[MemoryPortBinding] { - def apply(in: UnboundBinding) = MemoryPortBinding(enclosure) -} - -case class OpBinder(enclosure: UserModule) extends Binder[OpBinding] { - def apply(in: UnboundBinding) = OpBinding(enclosure) -} - -// Notice how PortBinder uses the direction of the UnboundNode -case class PortBinder(enclosure: BaseModule) extends Binder[PortBinding] { - def apply(in: UnboundBinding) = PortBinding(enclosure, in.direction) -} - -case class RegBinder(enclosure: UserModule) extends Binder[RegBinding] { - def apply(in: UnboundBinding) = RegBinding(enclosure) -} - -case class WireBinder(enclosure: UserModule) extends Binder[WireBinding] { - def apply(in: UnboundBinding) = WireBinding(enclosure) -} - diff --git a/chiselFrontend/src/main/scala/chisel3/core/Binding.scala b/chiselFrontend/src/main/scala/chisel3/core/Binding.scala index 87e706b7..2b6f10f6 100644 --- a/chiselFrontend/src/main/scala/chisel3/core/Binding.scala +++ b/chiselFrontend/src/main/scala/chisel3/core/Binding.scala @@ -2,150 +2,69 @@ package chisel3.core import chisel3.internal.Builder.{forcedModule} -/** - * The purpose of a Binding is to indicate what type of hardware 'entity' a - * specific Data's leaf Elements is actually bound to. All Data starts as being - * Unbound (and the whole point of cloneType is to return an unbound version). - * Then, specific API calls take a Data, and return a bound version (either by - * binding the original model or cloneType then binding the clone). For example, - * Reg[T<:Data](...) returns a T bound to RegBinding. - * - * It is considered invariant that all Elements of a single Data are bound to - * the same concrete type of Binding. - * - * These bindings can be checked (e.g. checkSynthesizable) to make sure certain - * operations are valid. For example, arithemetic operations or connections can - * only be executed between synthesizable nodes. These checks are to avoid - * undefined reference errors. - * - * Bindings can carry information about the particular element in the graph it - * represents like: - * - For ports (and unbound), the 'direction' - * - For (relevant) synthesizable nodes, the enclosing Module - * - * TODO(twigg): Enrich the bindings to carry more information like the hosting - * module (when applicable), direction (when applicable), literal info (when - * applicable). Can ensure applicable data only stored on relevant nodes. e.g. - * literal info on LitBinding, direction info on UnboundBinding and PortBinding, - * etc. - * - * TODO(twigg): Currently, bindings only apply at the Element level and an - * Aggregate is considered bound via its elements. May be appropriate to allow - * Aggregates to be bound along with the Elements. However, certain literal and - * port direction information doesn't quite make sense in aggregates. This would - * elegantly handle the empty Vec or Record problem though. - * - * TODO(twigg): Binding is currently done via allElements. It may be more - * elegant if this was instead done as a more explicit tree walk as that allows - * for better errors. - */ - object Binding { - // Two bindings are 'compatible' if they are the same type. - // Check currently kind of weird: just ensures same class - private def compatible(a: Binding, b: Binding): Boolean = a.getClass == b.getClass - private def compatible(nodes: Seq[Binding]): Boolean = - if(nodes.size > 1) - (for((a,b) <- nodes zip nodes.tail) yield compatible(a,b)) - .fold(true)(_&&_) - else true - - case class BindingException(message: String) extends Exception(message) - def AlreadyBoundException(binding: String) = BindingException(s": Already bound to $binding") - def NotSynthesizableException = BindingException(s": Not bound to synthesizable node, currently only Type description") - def MissingIOWrapperException = BindingException(": Missing IO() wrapper") + class BindingException(message: String) extends Exception(message) + /** A function expected a Chisel type but got a hardware object + */ + case class ExpectedChiselTypeException(message: String) extends BindingException(message) + /**A function expected a hardware object but got a Chisel type + */ + case class ExpectedHardwareException(message: String) extends BindingException(message) + /** An aggregate had a mix of specified and unspecified directionality children + */ + case class MixedDirectionAggregateException(message: String) extends BindingException(message) + /** Attempted to re-bind an already bound (directionality or hardware) object + */ + case class RebindingException(message: String) extends BindingException(message) +} - // This recursively walks down the Data tree to look at all the leaf 'Element's - // Will build up an error string in case something goes wrong - // TODO(twigg): Make member function of Data. - // Allows oddities like sample_element to be better hidden - private def walkToBinding(target: Data, checker: Element=>Unit): Unit = target match { - case (element: Element) => checker(element) - case (vec: Vec[Data @unchecked]) => { - try walkToBinding(vec.sample_element, checker) - catch { - case BindingException(message) => throw BindingException(s"(*)$message") - } - for(idx <- 0 until vec.length) { - try walkToBinding(vec(idx), checker) - catch { - case BindingException(message) => throw BindingException(s"($idx)$message") - } - } - } - case (record: Record) => { - for((field, subelem) <- record.elements) { - try walkToBinding(subelem, checker) - catch { - case BindingException(message) => throw BindingException(s".$field$message") - } - } +/** Requires that a node is hardware ("bound") + */ +object requireIsHardware { + def apply(node: Data, msg: String = "") = { + node._parent match { // Compatibility layer hack + case Some(x: BaseModule) => x._autoWrapPorts + case _ => } - } - - // Use walkToBinding to actually rebind the node type - def bind[T<:Data](target: T, binder: Binder[_<:Binding], error_prelude: String): target.type = { - try walkToBinding( - target, - element => element.binding match { - case unbound @ UnboundBinding(_) => { - element.binding = binder(unbound) - } - case binding => throw AlreadyBoundException(binding.toString) - } - ) - catch { - case BindingException(message) => throw BindingException(s"$error_prelude$message") + if (!node.hasBinding) { + val prefix = if (msg.nonEmpty) s"$msg " else "" + throw Binding.ExpectedHardwareException(s"$prefix'$node' must be hardware, not a bare Chisel type") } - target } +} - // Excepts if any root element is already bound - def checkUnbound(target: Data, error_prelude: String): Unit = { - try walkToBinding( - target, - element => element.binding match { - case unbound @ UnboundBinding(_) => {} - case binding => throw AlreadyBoundException(binding.toString) - } - ) - catch { - case BindingException(message) => throw BindingException(s"$error_prelude$message") - } +/** Requires that a node is a chisel type (not hardware, "unbound") + */ +object requireIsChiselType { + def apply(node: Data, msg: String = "") = if (node.hasBinding) { + val prefix = if (msg.nonEmpty) s"$msg " else "" + throw Binding.ExpectedChiselTypeException(s"$prefix'$node' must be a Chisel type, not hardware") } +} - // Excepts if any root element is unbound and thus not on the hardware graph - def checkSynthesizable(target: Data, error_prelude: String): Unit = { - try walkToBinding( - target, - element => { - // Compatibility mode to automatically wrap ports in IO - // TODO: remove me, perhaps by removing Bindings checks from compatibility mode - element._parent match { - case Some(x: BaseModule) => x._autoWrapPorts - case _ => - } - // Actual binding check - element.binding match { - case SynthesizableBinding() => // OK - case binding => { - // Attempt to diagnose common bindings issues, like forgot to wrap IO(...) - element._parent match { - case Some(x: LegacyModule) => - // null check in case we try to access io before it is defined - if ((x.io != null) && (x.io.flatten contains element)) { - throw MissingIOWrapperException - } - case _ => - } - // Fallback generic exception - throw NotSynthesizableException - } - } +// Element only direction used for the Binding system only. +sealed abstract class BindingDirection +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) = { + 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'") } - ) - catch { - case BindingException(message) => throw BindingException(s"$error_prelude$message") + case _ => Internal } } } @@ -153,52 +72,32 @@ object Binding { // Location refers to 'where' in the Module hierarchy this lives sealed trait Binding { def location: Option[BaseModule] - def direction: Option[Direction] } +// 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 Binding { +sealed trait UnconstrainedBinding extends TopBinding { def location = None } // A constrained binding can only be read/written by specific modules // Location will track where this Module is -sealed trait ConstrainedBinding extends Binding { +sealed trait ConstrainedBinding extends TopBinding { def enclosure: BaseModule def location = Some(enclosure) } -// An undirectioned binding means the element represents an internal node -// with no meaningful concept of a direction -sealed trait UndirectionedBinding extends Binding { def direction = None } - -// This is the default binding, represents data not yet positioned in the graph -case class UnboundBinding(direction: Option[Direction]) - extends Binding with UnconstrainedBinding - - -// A synthesizable binding is 'bound into' the hardware graph -object SynthesizableBinding { - def unapply(target: Binding): Boolean = target.isInstanceOf[SynthesizableBinding] - // Type check OK because Binding and SynthesizableBinding is sealed -} -sealed trait SynthesizableBinding extends Binding -case class LitBinding() // will eventually have literal info - extends SynthesizableBinding with UnconstrainedBinding with UndirectionedBinding - -case class MemoryPortBinding(enclosure: UserModule) - extends SynthesizableBinding with ConstrainedBinding with UndirectionedBinding - +// TODO literal info here +case class LitBinding() extends UnconstrainedBinding // TODO(twigg): Ops between unenclosed nodes can also be unenclosed // However, Chisel currently binds all op results to a module -case class OpBinding(enclosure: UserModule) - extends SynthesizableBinding with ConstrainedBinding with UndirectionedBinding - -case class PortBinding(enclosure: BaseModule, direction: Option[Direction]) - extends SynthesizableBinding with ConstrainedBinding - -case class RegBinding(enclosure: UserModule) - extends SynthesizableBinding with ConstrainedBinding with UndirectionedBinding - -case class WireBinding(enclosure: UserModule) - extends SynthesizableBinding with ConstrainedBinding with UndirectionedBinding +case class OpBinding(enclosure: UserModule) extends ConstrainedBinding +case class MemoryPortBinding(enclosure: UserModule) extends ConstrainedBinding +case class PortBinding(enclosure: BaseModule) extends ConstrainedBinding +case class RegBinding(enclosure: UserModule) extends ConstrainedBinding +case class WireBinding(enclosure: UserModule) extends ConstrainedBinding + +case class ChildBinding(parent: Data) extends Binding { + def location = parent.binding.location +} diff --git a/chiselFrontend/src/main/scala/chisel3/core/Bits.scala b/chiselFrontend/src/main/scala/chisel3/core/Bits.scala index 7de7be09..7f660188 100644 --- a/chiselFrontend/src/main/scala/chisel3/core/Bits.scala +++ b/chiselFrontend/src/main/scala/chisel3/core/Bits.scala @@ -17,26 +17,16 @@ import chisel3.internal.firrtl.PrimOp._ /** Element is a leaf data type: it cannot contain other Data objects. Example * uses are for representing primitive data types, like integers and bits. */ -abstract class Element(private[core] val width: Width) extends Data { - /** - * Elements can actually be bound to the hardware graph and thus must store - * that binding information. - */ - private[this] var _binding: Binding = UnboundBinding(None) - // Define setter/getter pairing - // Can only bind something that has not yet been bound. - private[core] def binding_=(target: Binding): Unit = _binding match { - case UnboundBinding(_) => { - _binding = target - _binding +abstract class Element(private[chisel3] val width: Width) extends Data { + private[chisel3] override def bind(target: Binding, parentDirection: UserDirection) { + binding = target + val resolvedDirection = UserDirection.fromParent(parentDirection, userDirection) + direction = resolvedDirection match { + case UserDirection.Unspecified | UserDirection.Flip => ActualDirection.Unspecified + case UserDirection.Output => ActualDirection.Output + case UserDirection.Input => ActualDirection.Input } - case _ => throw Binding.AlreadyBoundException(_binding.toString) - // Other checks should have caught this. } - private[core] def binding = _binding - - /** Return the binding for some bits. */ - def dir: Direction = binding.direction.getOrElse(Direction.Unspecified) private[chisel3] final def allElements: Seq[Element] = Seq(this) def widthKnown: Boolean = width.known @@ -95,7 +85,8 @@ sealed abstract class Bits(width: Width, override val litArg: Option[LitArg]) if (isLit()) { (((litValue() >> x.toInt) & 1) == 1).asBool } else { - Binding.checkSynthesizable(this, s"'this' ($this)") + + requireIsHardware(this, "bits to be indexed") pushOp(DefPrim(sourceInfo, Bool(), BitsExtractOp, this.ref, ILit(x), ILit(x))) } } @@ -138,7 +129,7 @@ sealed abstract class Bits(width: Width, override val litArg: Option[LitArg]) if (isLit()) { ((litValue >> y) & ((BigInt(1) << w) - 1)).asUInt(w.W) } else { - Binding.checkSynthesizable(this, s"'this' ($this)") + requireIsHardware(this, "bits to be sliced") pushOp(DefPrim(sourceInfo, UInt(Width(w)), BitsExtractOp, this.ref, ILit(x), ILit(y))) } } @@ -150,25 +141,25 @@ sealed abstract class Bits(width: Width, override val litArg: Option[LitArg]) apply(x.toInt, y.toInt) private[core] def unop[T <: Data](sourceInfo: SourceInfo, dest: T, op: PrimOp): T = { - Binding.checkSynthesizable(this, s"'this' ($this)") + requireIsHardware(this, "bits operated on") pushOp(DefPrim(sourceInfo, dest, op, this.ref)) } private[core] def binop[T <: Data](sourceInfo: SourceInfo, dest: T, op: PrimOp, other: BigInt): T = { - Binding.checkSynthesizable(this, s"'this' ($this)") + requireIsHardware(this, "bits operated on") pushOp(DefPrim(sourceInfo, dest, op, this.ref, ILit(other))) } private[core] def binop[T <: Data](sourceInfo: SourceInfo, dest: T, op: PrimOp, other: Bits): T = { - Binding.checkSynthesizable(this, s"'this' ($this)") - Binding.checkSynthesizable(other, s"'other' ($other)") + requireIsHardware(this, "bits operated on") + requireIsHardware(other, "bits operated on") pushOp(DefPrim(sourceInfo, dest, op, this.ref, other.ref)) } private[core] def compop(sourceInfo: SourceInfo, op: PrimOp, other: Bits): Bool = { - Binding.checkSynthesizable(this, s"'this' ($this)") - Binding.checkSynthesizable(other, s"'other' ($other)") + requireIsHardware(this, "bits operated on") + requireIsHardware(other, "bits operated on") pushOp(DefPrim(sourceInfo, Bool(), op, this.ref, other.ref)) } private[core] def redop(sourceInfo: SourceInfo, op: PrimOp): Bool = { - Binding.checkSynthesizable(this, s"'this' ($this)") + requireIsHardware(this, "bits operated on") pushOp(DefPrim(sourceInfo, Bool(), op, this.ref)) } @@ -404,7 +395,6 @@ sealed class UInt private[core] (width: Width, lit: Option[ULit] = None) private[core] override def cloneTypeWidth(w: Width): this.type = new UInt(w).asInstanceOf[this.type] - private[chisel3] def toType = s"UInt$width" // TODO: refactor to share documentation with Num or add independent scaladoc final def unary_- (): UInt = macro SourceInfoTransform.noArg @@ -547,7 +537,7 @@ trait UIntFactory { val lit = ULit(value, width) val result = new UInt(lit.width, Some(lit)) // Bind result to being an Literal - result.binding = LitBinding() + result.bind(LitBinding()) result } @@ -572,7 +562,6 @@ sealed class SInt private[core] (width: Width, lit: Option[SLit] = None) private[core] override def cloneTypeWidth(w: Width): this.type = new SInt(w).asInstanceOf[this.type] - private[chisel3] def toType = s"SInt$width" final def unary_- (): SInt = macro SourceInfoTransform.noArg final def unary_-% (): SInt = macro SourceInfoTransform.noArg @@ -703,7 +692,7 @@ trait SIntFactory { val lit = SLit(value, width) val result = new SInt(lit.width, Some(lit)) // Bind result to being an Literal - result.binding = LitBinding() + result.bind(LitBinding()) result } } @@ -765,7 +754,7 @@ trait BoolFactory { protected[chisel3] def Lit(x: Boolean): Bool = { val result = new Bool(Some(ULit(if (x) 1 else 0, Width(1)))) // Bind result to being an Literal - result.binding = LitBinding() + result.bind(LitBinding()) result } } @@ -794,7 +783,6 @@ sealed class FixedPoint private (width: Width, val binaryPoint: BinaryPoint, lit private[core] override def cloneTypeWidth(w: Width): this.type = new FixedPoint(w, binaryPoint).asInstanceOf[this.type] - private[chisel3] def toType = s"Fixed$width$binaryPoint" override def connect (that: Data)(implicit sourceInfo: SourceInfo, connectCompileOptions: CompileOptions): Unit = that match { case _: FixedPoint => super.connect(that) @@ -946,9 +934,6 @@ object FixedPoint { /** Create an FixedPoint type or port with fixed width. */ def apply(width: Width, binaryPoint: BinaryPoint): FixedPoint = new FixedPoint(width, binaryPoint) - /** Create an FixedPoint port with inferred width. */ - def apply(dir: Direction): FixedPoint = apply(dir, Width(), BinaryPoint()) - /** Create an FixedPoint literal with inferred width from BigInt. * Use PrivateObject to force users to specify width and binaryPoint by name */ @@ -991,11 +976,10 @@ object FixedPoint { } /** Create an FixedPoint port with specified width and binary position. */ - def apply(dir: Direction, width: Width, binaryPoint: BinaryPoint): FixedPoint = new FixedPoint(width, binaryPoint) def apply(value: BigInt, width: Width, binaryPoint: BinaryPoint): FixedPoint = { val lit = FPLit(value, width, binaryPoint) val newLiteral = new FixedPoint(lit.width, lit.binaryPoint, Some(lit)) - newLiteral.binding = LitBinding() + newLiteral.bind(LitBinding()) newLiteral } @@ -1043,8 +1027,6 @@ object FixedPoint { final class Analog private (width: Width) extends Element(width) { require(width.known, "Since Analog is only for use in BlackBoxes, width must be known") - private[chisel3] def toType = s"Analog$width" - private[core] override def typeEquivalent(that: Data): Boolean = that.isInstanceOf[Analog] && this.width == that.width @@ -1057,9 +1039,23 @@ final class Analog private (width: Width) extends Element(width) { // Define setter/getter pairing // Analog can only be bound to Ports and Wires (and Unbound) - private[core] override def binding_=(target: Binding): Unit = target match { - case (_: UnboundBinding | _: WireBinding | PortBinding(_, None)) => super.binding_=(target) - case _ => throwException("Only Wires and Ports can be of type Analog") + private[chisel3] override def bind(target: Binding, parentDirection: UserDirection) { + UserDirection.fromParent(parentDirection, userDirection) match { + case UserDirection.Unspecified | UserDirection.Flip => + case x => throwException(s"Analog may not have explicit direction, got '$x'") + } + val targetTopBinding = target match { + case target: TopBinding => target + case ChildBinding(parent) => parent.topBinding + } + + // Analog counts as different directions based on binding context + targetTopBinding match { + case WireBinding(_) => direction = ActualDirection.Unspecified // internal wire + case PortBinding(_) => direction = ActualDirection.Bidirectional(ActualDirection.Default) + case x => throwException(s"Analog can only be Ports and Wires, not '$x'") + } + binding = target } override def do_asUInt(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): UInt = diff --git a/chiselFrontend/src/main/scala/chisel3/core/BlackBox.scala b/chiselFrontend/src/main/scala/chisel3/core/BlackBox.scala index cd072ba9..6d8e85a4 100644 --- a/chiselFrontend/src/main/scala/chisel3/core/BlackBox.scala +++ b/chiselFrontend/src/main/scala/chisel3/core/BlackBox.scala @@ -74,13 +74,7 @@ abstract class ExtModule(val params: Map[String, Param] = Map.empty[String, Para id._onModuleClose } - val firrtlPorts = for (port <- getModulePorts) yield { - // Port definitions need to know input or output at top-level. - // By FIRRTL semantics, 'flipped' becomes an Input - val direction = if(Data.isFirrtlFlipped(port)) Direction.Input else Direction.Output - Port(port, direction) - } - + val firrtlPorts = getModulePorts map {port => Port(port, port.userDirection)} val component = DefBlackBox(this, name, firrtlPorts, params) _component = Some(component) component @@ -132,7 +126,7 @@ abstract class ExtModule(val params: Map[String, Param] = Map.empty[String, Para */ abstract class BlackBox(val params: Map[String, Param] = Map.empty[String, Param])(implicit compileOptions: CompileOptions) extends BaseBlackBox { def io: Record - + // Allow access to bindings from the compatibility package protected def _ioPortBound() = portsContains(io) @@ -165,19 +159,13 @@ abstract class BlackBox(val params: Map[String, Param] = Map.empty[String, Param id._onModuleClose } - val firrtlPorts = for ((_, port) <- namedPorts) yield { - // Port definitions need to know input or output at top-level. - // By FIRRTL semantics, 'flipped' becomes an Input - val direction = if(Data.isFirrtlFlipped(port)) Direction.Input else Direction.Output - Port(port, direction) - } - + val firrtlPorts = namedPorts map {namedPort => Port(namedPort._2, namedPort._2.userDirection)} val component = DefBlackBox(this, name, firrtlPorts, params) _component = Some(component) component } - private[core] def initializeInParent() { + private[core] def initializeInParent() { for ((_, port) <- io.elements) { pushCommand(DefInvalid(UnlocatableSourceInfo, port.ref)) } diff --git a/chiselFrontend/src/main/scala/chisel3/core/ChiselAnnotation.scala b/chiselFrontend/src/main/scala/chisel3/core/ChiselAnnotation.scala index ad4050f3..07546406 100644 --- a/chiselFrontend/src/main/scala/chisel3/core/ChiselAnnotation.scala +++ b/chiselFrontend/src/main/scala/chisel3/core/ChiselAnnotation.scala @@ -2,6 +2,8 @@ package chisel3.core +import scala.language.existentials + import chisel3.internal.{Builder, InstanceId} import firrtl.Transform import firrtl.annotations.{Annotation, CircuitName, ComponentName, ModuleName} @@ -56,7 +58,7 @@ object dontTouch { // scalastyle:ignore object.name */ def apply[T <: Data](data: T)(implicit compileOptions: CompileOptions): T = { if (compileOptions.checkSynthesizable) { - Binding.checkSynthesizable(data, s"$data") + requireIsHardware(data, "Data marked dontTouch") } // TODO unify with firrtl.transforms.DontTouchAnnotation val anno = ChiselAnnotation(data, classOf[firrtl.Transform], "DONTtouch!") diff --git a/chiselFrontend/src/main/scala/chisel3/core/Clock.scala b/chiselFrontend/src/main/scala/chisel3/core/Clock.scala new file mode 100644 index 00000000..f682310b --- /dev/null +++ b/chiselFrontend/src/main/scala/chisel3/core/Clock.scala @@ -0,0 +1,34 @@ +// See LICENSE for license details. + +package chisel3.core + +import chisel3.internal.Builder.{pushOp} +import chisel3.internal.firrtl._ +import chisel3.internal.sourceinfo._ +import chisel3.internal.firrtl.PrimOp.AsUIntOp + +object Clock { + def apply(): Clock = new Clock +} + +// TODO: Document this. +sealed class Clock extends Element(Width(1)) { + def cloneType: this.type = Clock().asInstanceOf[this.type] + + private[core] def typeEquivalent(that: Data): Boolean = + this.getClass == that.getClass + + override def connect(that: Data)(implicit sourceInfo: SourceInfo, connectCompileOptions: CompileOptions): Unit = that match { + case _: Clock => super.connect(that)(sourceInfo, connectCompileOptions) + case _ => super.badConnect(that)(sourceInfo) + } + + /** Not really supported */ + def toPrintable: Printable = PString("CLOCK") + + override def do_asUInt(implicit sourceInfo: SourceInfo, connectCompileOptions: CompileOptions): UInt = pushOp(DefPrim(sourceInfo, UInt(this.width), AsUIntOp, ref)) + private[core] override def connectFromBits(that: Bits)(implicit sourceInfo: SourceInfo, + compileOptions: CompileOptions): Unit = { + this := that + } +} diff --git a/chiselFrontend/src/main/scala/chisel3/core/CompileOptions.scala b/chiselFrontend/src/main/scala/chisel3/core/CompileOptions.scala index 803e6c0f..55e6d18c 100644 --- a/chiselFrontend/src/main/scala/chisel3/core/CompileOptions.scala +++ b/chiselFrontend/src/main/scala/chisel3/core/CompileOptions.scala @@ -16,9 +16,6 @@ trait CompileOptions { val dontTryConnectionsSwapped: Boolean // If connection directionality is not explicit, do not use heuristics to attempt to determine it. val dontAssumeDirectionality: Boolean - // Issue a deprecation warning if Data.{flip, asInput,asOutput} is used - // instead of Flipped, Input, or Output. - val deprecateOldDirectionMethods: Boolean // Check that referenced Data have actually been declared. val checkSynthesizable: Boolean } @@ -46,9 +43,6 @@ object ExplicitCompileOptions { val dontTryConnectionsSwapped: Boolean, // If connection directionality is not explicit, do not use heuristics to attempt to determine it. val dontAssumeDirectionality: Boolean, - // Issue a deprecation warning if Data.{flip, asInput,asOutput} is used - // instead of Flipped, Input, or Output. - val deprecateOldDirectionMethods: Boolean, // Check that referenced Data have actually been declared. val checkSynthesizable: Boolean ) extends CompileOptions @@ -61,7 +55,6 @@ object ExplicitCompileOptions { declaredTypeMustBeUnbound = false, dontTryConnectionsSwapped = false, dontAssumeDirectionality = false, - deprecateOldDirectionMethods = false, checkSynthesizable = false ) @@ -72,7 +65,6 @@ object ExplicitCompileOptions { declaredTypeMustBeUnbound = true, dontTryConnectionsSwapped = true, dontAssumeDirectionality = true, - deprecateOldDirectionMethods = true, checkSynthesizable = true ) } diff --git a/chiselFrontend/src/main/scala/chisel3/core/Data.scala b/chiselFrontend/src/main/scala/chisel3/core/Data.scala index 580dabe0..41e09a5b 100644 --- a/chiselFrontend/src/main/scala/chisel3/core/Data.scala +++ b/chiselFrontend/src/main/scala/chisel3/core/Data.scala @@ -8,16 +8,65 @@ import chisel3.internal._ import chisel3.internal.Builder.{pushCommand, pushOp} import chisel3.internal.firrtl._ import chisel3.internal.sourceinfo._ -import chisel3.internal.firrtl.PrimOp.AsUIntOp -sealed abstract class Direction(name: String) { - override def toString: String = name - def flip: Direction +/** User-specified directions. + */ +sealed abstract class UserDirection +object UserDirection { + /** Default user direction, also meaning 'not-flipped' + */ + case object Unspecified extends UserDirection + /** Node and its children are forced as output + */ + case object Output extends UserDirection + /** Node and ites children are forced as inputs + */ + case object Input extends UserDirection + /** Mainly for containers, children are flipped. + */ + case object Flip extends UserDirection + + def flip(dir: UserDirection) = dir match { + case Unspecified => Flip + case Flip => Unspecified + case Output => Input + case Input => Output + } + + /** Returns the effective UserDirection of this node given the parent's effective UserDirection + * and the user-specified UserDirection of this node. + */ + def fromParent(parentDirection: UserDirection, thisDirection: UserDirection) = + (parentDirection, thisDirection) match { + case (UserDirection.Output, _) => UserDirection.Output + case (UserDirection.Input, _) => UserDirection.Input + case (UserDirection.Unspecified, thisDirection) => thisDirection + case (UserDirection.Flip, thisDirection) => UserDirection.flip(thisDirection) + } } -object Direction { - object Input extends Direction("input") { override def flip: Direction = Output } - object Output extends Direction("output") { override def flip: Direction = Input } - object Unspecified extends Direction("unspecified") { override def flip: Direction = Input } + +/** Resolved directions for both leaf and container nodes, only visible after + * a node is bound (since higher-level specifications like Input and Output + * can override directions). + */ +sealed abstract class ActualDirection + +object ActualDirection { + /** Undirectioned, struct-like + */ + case object Unspecified extends ActualDirection + /** Output element, or container with all outputs (even if forced) + */ + case object Output extends ActualDirection + /** Input element, or container with all inputs (even if forced) + */ + case object Input extends ActualDirection + + sealed abstract class BidirectionalDirection + case object Default extends BidirectionalDirection + case object Flipped extends BidirectionalDirection + + case class Bidirectional(dir: BidirectionalDirection) extends ActualDirection } @deprecated("debug doesn't do anything in Chisel3 as no pruning happens in the frontend", "chisel3") @@ -25,8 +74,17 @@ object debug { // scalastyle:ignore object.name def apply (arg: Data): Data = arg } +/** Experimental hardware construction reflection API + */ object DataMirror { def widthOf(target: Data): Width = target.width + def userDirectionOf(target: Data): UserDirection = target.userDirection + def directionOf(target: Data): ActualDirection = { + requireIsHardware(target, "node requested directionality on") + target.direction + } + // TODO: really not a reflection-style API, but a workaround for dir in the compatibility package + def isSynthesizable(target: Data) = target.hasBinding } /** Creates a clone of the super-type of the input elements. Super-type is defined as: @@ -87,88 +145,24 @@ private[core] object cloneSupertype { * Thus, an error will be thrown if these are used on bound Data */ object Input { - def apply[T<:Data](source: T)(implicit compileOptions: CompileOptions): T = { - val target = source.chiselCloneType - Data.setFirrtlDirection(target, Direction.Input) - Binding.bind(target, InputBinder, "Error: Cannot set as input ") + def apply[T<:Data](source: T): T = { + val out = source.cloneType + out.userDirection = UserDirection.Input + out } } object Output { - def apply[T<:Data](source: T)(implicit compileOptions: CompileOptions): T = { - val target = source.chiselCloneType - Data.setFirrtlDirection(target, Direction.Output) - Binding.bind(target, OutputBinder, "Error: Cannot set as output ") + def apply[T<:Data](source: T): T = { + val out = source.cloneType + out.userDirection = UserDirection.Output + out } } object Flipped { - def apply[T<:Data](source: T)(implicit compileOptions: CompileOptions): T = { - val target = source.chiselCloneType - Data.setFirrtlDirection(target, Data.getFirrtlDirection(source).flip) - Binding.bind(target, FlippedBinder, "Error: Cannot flip ") - } -} - -object Data { - /** - * This function returns true if the FIRRTL type of this Data should be flipped - * relative to other nodes. - * - * Note that the current scheme only applies Flip to Elements or Vec chains of - * Elements. - * - * A Record is never marked flip, instead preferring its root fields to be marked - * - * The Vec check is due to the fact that flip must be factored out of the vec, ie: - * must have flip field: Vec(UInt) instead of field: Vec(flip UInt) - */ - private[chisel3] def isFlipped(target: Data): Boolean = target match { - case (element: Element) => element.binding.direction == Some(Direction.Input) - case (vec: Vec[Data @unchecked]) => isFlipped(vec.sample_element) - case (record: Record) => false - } - - /** This function returns the "firrtl" flipped-ness for the specified object. - * - * @param target the object for which we want the "firrtl" flipped-ness. - */ - private[chisel3] def isFirrtlFlipped(target: Data): Boolean = { - Data.getFirrtlDirection(target) == Direction.Input - } - - /** This function gets the "firrtl" direction for the specified object. - * - * @param target the object for which we want to get the "firrtl" direction. - */ - private[chisel3] def getFirrtlDirection(target: Data): Direction = target match { - case (vec: Vec[Data @unchecked]) => vec.sample_element.firrtlDirection - case _ => target.firrtlDirection - } - - /** This function sets the "firrtl" direction for the specified object. - * - * @param target the object for which we want to set the "firrtl" direction. - */ - private[chisel3] def setFirrtlDirection(target: Data, direction: Direction): Unit = target match { - case (vec: Vec[Data @unchecked]) => vec.sample_element.firrtlDirection = direction - case _ => target.firrtlDirection = direction - } - - implicit class AddDirectionToData[T<:Data](val target: T) extends AnyVal { - def asInput(implicit opts: CompileOptions): T = { - if (opts.deprecateOldDirectionMethods) - Builder.deprecated("Input(Data) should be used over Data.asInput") - Input(target) - } - def asOutput(implicit opts: CompileOptions): T = { - if (opts.deprecateOldDirectionMethods) - Builder.deprecated("Output(Data) should be used over Data.asOutput") - Output(target) - } - def flip()(implicit opts: CompileOptions): T = { - if (opts.deprecateOldDirectionMethods) - Builder.deprecated("Flipped(Data) should be used over Data.flip") - Flipped(target) - } + def apply[T<:Data](source: T): T = { + val out = source.cloneType + out.userDirection = UserDirection.flip(source.userDirection) + out } } @@ -188,6 +182,76 @@ abstract class Data extends HasId { } } + // User-specified direction, local at this node only. + // Note that the actual direction of this node can differ from child and parent userDirection. + private var _userDirection: UserDirection = UserDirection.Unspecified + private[chisel3] def userDirection: UserDirection = _userDirection + private[core] def userDirection_=(direction: UserDirection) = { + if (_userDirection != UserDirection.Unspecified) { + this match { + // Anything flies in compatibility mode + case t: Record if !t.compileOptions.dontAssumeDirectionality => + case _ => throw Binding.RebindingException(s"Attempted reassignment of user direction to $this") + } + } + _userDirection = direction + } + + /** This overwrites a relative UserDirection with an explicit one, and is used to implement + * the compatibility layer where, at the elements, Flip is Input and unspecified is Output. + * DO NOT USE OUTSIDE THIS PURPOSE. THIS OPERATION IS DANGEROUS! + */ + private[core] def _assignCompatibilityExplicitDirection: Unit = { + _userDirection match { + case UserDirection.Unspecified => _userDirection = UserDirection.Output + case UserDirection.Flip => _userDirection = UserDirection.Input + case UserDirection.Input | UserDirection.Output => // nothing to do + } + } + + // Binding stores information about this node's position in the hardware graph. + // This information is supplemental (more than is necessary to generate FIRRTL) and is used to + // perform checks in Chisel, where more informative error messages are possible. + private var _binding: Option[Binding] = None + private[core] def hasBinding = _binding.isDefined + // Only valid after node is bound (synthesizable), crashes otherwise + private[core] def binding = _binding.get + protected def binding_=(target: Binding) { + if (_binding.isDefined) { + throw Binding.RebindingException(s"Attempted reassignment of binding to $this") + } + _binding = Some(target) + } + + private[core] def topBinding: TopBinding = { + binding match { + case ChildBinding(parent) => parent.topBinding + case topBinding: TopBinding => topBinding + } + } + + /** Binds this node to the hardware graph. + * parentDirection is the direction of the parent node, or Unspecified (default) if the target + * node is the top-level. + * binding and direction are valid after this call completes. + */ + private[chisel3] def bind(target: Binding, parentDirection: UserDirection = UserDirection.Unspecified) + + // Both _direction and _resolvedUserDirection are saved versions of computed variables (for + // efficiency, avoid expensive recomputation of frequent operations). + // Both are only valid after binding is set. + + // Direction of this node, accounting for parents (force Input / Output) and children. + private var _direction: Option[ActualDirection] = None + + private[chisel3] def direction: ActualDirection = _direction.get + private[core] def direction_=(actualDirection: ActualDirection) { + if (_direction.isDefined) { + throw Binding.RebindingException(s"Attempted reassignment of resolved direction to $this") + } + _direction = Some(actualDirection) + } + // Return ALL elements at root of this type. // Contasts with flatten, which returns just Bits // TODO: refactor away this, this is outside the scope of Data @@ -197,8 +261,8 @@ abstract class Data extends HasId { throwException(s"cannot connect ${this} and ${that}") private[chisel3] def connect(that: Data)(implicit sourceInfo: SourceInfo, connectCompileOptions: CompileOptions): Unit = { if (connectCompileOptions.checkSynthesizable) { - Binding.checkSynthesizable(this, s"'this' ($this)") - Binding.checkSynthesizable(that, s"'that' ($that)") + requireIsHardware(this, "data to be connected") + requireIsHardware(that, "data to be connected") try { MonoConnect.connect(sourceInfo, connectCompileOptions, this, that, Builder.forcedUserModule) } catch { @@ -213,8 +277,8 @@ abstract class Data extends HasId { } private[chisel3] def bulkConnect(that: Data)(implicit sourceInfo: SourceInfo, connectCompileOptions: CompileOptions): Unit = { if (connectCompileOptions.checkSynthesizable) { - Binding.checkSynthesizable(this, s"'this' ($this)") - Binding.checkSynthesizable(that, s"'that' ($that)") + requireIsHardware(this, s"data to be bulk-connected") + requireIsHardware(that, s"data to be bulk-connected") try { BiConnect.connect(sourceInfo, connectCompileOptions, this, that, Builder.forcedUserModule) } catch { @@ -235,8 +299,7 @@ abstract class Data extends HasId { private[chisel3] def lref: Node = Node(this) private[chisel3] def ref: Arg = if (isLit) litArg.get else lref - private[chisel3] def toType: String - private[core] def width: Width + private[chisel3] def width: Width private[core] def legacyConnect(that: Data)(implicit sourceInfo: SourceInfo): Unit /** cloneType must be defined for any Chisel object extending Data. @@ -252,21 +315,10 @@ abstract class Data extends HasId { * then performs any fixups required to reconstruct the appropriate core state of the cloned object. * @return a copy of the object with appropriate core state. */ - def chiselCloneType(implicit compileOptions: CompileOptions): this.type = { - // TODO: refactor away allElements, handle this with Aggregate/Element match inside Bindings - - // Call the user-supplied cloneType method - val clone = this.cloneType - // In compatibility mode, simply return cloneType; otherwise, propagate - // direction and flippedness. - if (compileOptions.checkSynthesizable) { - Data.setFirrtlDirection(clone, Data.getFirrtlDirection(this)) - //TODO(twigg): Do recursively for better error messages - for((clone_elem, source_elem) <- clone.allElements zip this.allElements) { - clone_elem.binding = UnboundBinding(source_elem.binding.direction) - Data.setFirrtlDirection(clone_elem, Data.getFirrtlDirection(source_elem)) - } - } + def chiselCloneType: this.type = { + val clone = this.cloneType // get a fresh object, without bindings + // Only the top-level direction needs to be fixed up, cloneType should do the rest + clone.userDirection = userDirection clone } final def := (that: Data)(implicit sourceInfo: SourceInfo, connectionCompileOptions: CompileOptions): Unit = this.connect(that)(sourceInfo, connectionCompileOptions) @@ -322,11 +374,6 @@ abstract class Data extends HasId { def do_asUInt(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): UInt - // firrtlDirection is the direction we report to firrtl. - // It maintains the user-specified value (as opposed to the "actual" or applied/propagated value). - // NOTE: This should only be used for emitting acceptable firrtl. - // The Element.dir should be used for any tests involving direction. - private var firrtlDirection: Direction = Direction.Unspecified /** Default pretty printing */ def toPrintable: Printable } @@ -349,7 +396,7 @@ object Wire { def apply[T <: Data](t: T, init: T)(implicit compileOptions: CompileOptions): T = { implicit val noSourceInfo = UnlocatableSourceInfo val x = apply(t) - Binding.checkSynthesizable(init, s"'init' ($init)") + requireIsHardware(init, "wire initializer") x := init x } @@ -358,7 +405,7 @@ object Wire { val x = t.chiselCloneType // Bind each element of x to being a Wire - Binding.bind(x, WireBinder(Builder.forcedUserModule), "Error: t") + x.bind(WireBinding(Builder.forcedUserModule)) pushCommand(DefWire(sourceInfo, x)) pushCommand(DefInvalid(sourceInfo, x.ref)) @@ -366,38 +413,3 @@ object Wire { x } } - -object Clock { - def apply(): Clock = new Clock - def apply(dir: Direction)(implicit compileOptions: CompileOptions): Clock = { - val result = apply() - dir match { - case Direction.Input => Input(result) - case Direction.Output => Output(result) - case Direction.Unspecified => result - } - } -} - -// TODO: Document this. -sealed class Clock extends Element(Width(1)) { - def cloneType: this.type = Clock().asInstanceOf[this.type] - private[chisel3] def toType = "Clock" - - private[core] def typeEquivalent(that: Data): Boolean = - this.getClass == that.getClass - - override def connect (that: Data)(implicit sourceInfo: SourceInfo, connectCompileOptions: CompileOptions): Unit = that match { - case _: Clock => super.connect(that)(sourceInfo, connectCompileOptions) - case _ => super.badConnect(that)(sourceInfo) - } - - /** Not really supported */ - def toPrintable: Printable = PString("CLOCK") - - override def do_asUInt(implicit sourceInfo: SourceInfo, connectCompileOptions: CompileOptions): UInt = pushOp(DefPrim(sourceInfo, UInt(this.width), AsUIntOp, ref)) - private[core] override def connectFromBits(that: Bits)(implicit sourceInfo: SourceInfo, - compileOptions: CompileOptions): Unit = { - this := that - } -} diff --git a/chiselFrontend/src/main/scala/chisel3/core/Mem.scala b/chiselFrontend/src/main/scala/chisel3/core/Mem.scala index 03c484b0..47d48061 100644 --- a/chiselFrontend/src/main/scala/chisel3/core/Mem.scala +++ b/chiselFrontend/src/main/scala/chisel3/core/Mem.scala @@ -21,8 +21,6 @@ object Mem { def apply[T <: Data](size: Int, t: T): Mem[T] = macro MemTransform.apply[T] def do_apply[T <: Data](size: Int, t: T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Mem[T] = { val mt = t.chiselCloneType - Binding.bind(mt, NoDirectionBinder, "Error: fresh t") - // TODO(twigg): Remove need for this Binding val mem = new Mem(mt, size) pushCommand(DefMemory(sourceInfo, mem, mt, size)) @@ -87,7 +85,7 @@ sealed abstract class MemBase[T <: Data](t: T, val length: Int) extends HasId { } private def makePort(sourceInfo: SourceInfo, idx: UInt, dir: MemPortDirection)(implicit compileOptions: CompileOptions): T = { - Binding.checkSynthesizable(idx, s"'idx' ($idx)") + requireIsHardware(idx, "memory port index") val i = Vec.truncateIndex(idx, length)(sourceInfo, compileOptions) val port = pushCommand( @@ -95,7 +93,7 @@ sealed abstract class MemBase[T <: Data](t: T, val length: Int) extends HasId { t.chiselCloneType, Node(this), dir, i.ref, Node(Builder.forcedClock)) ).id // Bind each element of port to being a MemoryPort - Binding.bind(port, MemoryPortBinder(Builder.forcedUserModule), "Error: Fresh t") + port.bind(MemoryPortBinding(Builder.forcedUserModule)) port } } @@ -124,9 +122,6 @@ object SyncReadMem { def do_apply[T <: Data](size: Int, t: T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): SyncReadMem[T] = { val mt = t.chiselCloneType - Binding.bind(mt, NoDirectionBinder, "Error: fresh t") - // TODO(twigg): Remove need for this Binding - val mem = new SyncReadMem(mt, size) pushCommand(DefSeqMemory(sourceInfo, mem, mt, size)) mem diff --git a/chiselFrontend/src/main/scala/chisel3/core/Module.scala b/chiselFrontend/src/main/scala/chisel3/core/Module.scala index b512ed75..0f081daf 100644 --- a/chiselFrontend/src/main/scala/chisel3/core/Module.scala +++ b/chiselFrontend/src/main/scala/chisel3/core/Module.scala @@ -219,8 +219,34 @@ abstract class BaseModule extends HasId { */ protected def IO[T<:Data](iodef: T): iodef.type = { require(!_closed, "Can't add more ports after module close") + requireIsChiselType(iodef, "io type") + + // Compatibility code: Chisel2 did not require explicit direction on nodes + // (unspecified treated as output, and flip on nothing was input). + // This sets assigns the explicit directions required by newer semantics on + // Bundles defined in compatibility mode. + // This recursively walks the tree, and assigns directions if no explicit + // direction given by upper-levels (override Input / Output) AND element is + // directly inside a compatibility Bundle determined by compile options. + def assignCompatDir(data: Data, insideCompat: Boolean): Unit = { + data match { + case data: Element if insideCompat => data._assignCompatibilityExplicitDirection + case data: Element => // Not inside a compatibility Bundle, nothing to be done + case data: Aggregate => data.userDirection match { + // Recurse into children to ensure explicit direction set somewhere + case UserDirection.Unspecified | UserDirection.Flip => data match { + case data: Record if (!data.compileOptions.dontAssumeDirectionality) => + data.getElements.foreach(assignCompatDir(_, true)) + case _ => data.getElements.foreach(assignCompatDir(_, false)) + } + case UserDirection.Input | UserDirection.Output => // forced assign, nothing to do + } + } + } + assignCompatDir(iodef, false) + // Bind each element of the iodef to being a Port - Binding.bind(iodef, PortBinder(this), "Error: iodef") + iodef.bind(PortBinding(this)) _ports += iodef iodef } diff --git a/chiselFrontend/src/main/scala/chisel3/core/MonoConnect.scala b/chiselFrontend/src/main/scala/chisel3/core/MonoConnect.scala index 80e96ce7..3c34785f 100644 --- a/chiselFrontend/src/main/scala/chisel3/core/MonoConnect.scala +++ b/chiselFrontend/src/main/scala/chisel3/core/MonoConnect.scala @@ -120,24 +120,23 @@ object MonoConnect { // 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: UserModule): Unit = { - import Direction.{Input, Output} // Using extensively so import these + 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.binding.location.getOrElse(throw UnwritableSinkException) val source_mod: BaseModule = source.binding.location.getOrElse(context_mod) - val sink_direction: Option[Direction] = sink.binding.direction - val source_direction: Option[Direction] = source.binding.direction - // None means internal + 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 (Some(Output), _) => issueConnect(sink, source) - case (None, _) => issueConnect(sink, source) - case (Some(Input), _) => throw UnwritableSinkException + case (Output, _) => issueConnect(sink, source) + case (Internal, _) => issueConnect(sink, source) + case (Input, _) => throw UnwritableSinkException } } @@ -148,19 +147,19 @@ object MonoConnect { ((sink_direction, source_direction): @unchecked) match { // SINK SOURCE // CURRENT MOD CHILD MOD - case (None, Some(Output)) => issueConnect(sink, source) - case (None, Some(Input)) => issueConnect(sink, source) - case (Some(Output), Some(Output)) => issueConnect(sink, source) - case (Some(Output), Some(Input)) => issueConnect(sink, source) - case (_, None) => { + 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 (Some(Input), Some(Output)) if (!(connectCompileOptions.dontTryConnectionsSwapped)) => issueConnect(source, sink) - case (Some(Input), _) => throw UnwritableSinkException + case (Input, Output) if (!(connectCompileOptions.dontTryConnectionsSwapped)) => issueConnect(source, sink) + case (Input, _) => throw UnwritableSinkException } } @@ -171,9 +170,9 @@ object MonoConnect { ((sink_direction, source_direction): @unchecked) match { // SINK SOURCE // CHILD MOD CURRENT MOD - case (Some(Input), _) => issueConnect(sink, source) - case (Some(Output), _) => throw UnwritableSinkException - case (None, _) => throw UnwritableSinkException + case (Input, _) => issueConnect(sink, source) + case (Output, _) => throw UnwritableSinkException + case (Internal, _) => throw UnwritableSinkException } } @@ -187,17 +186,17 @@ object MonoConnect { ((sink_direction, source_direction): @unchecked) match { // SINK SOURCE // CHILD MOD CHILD MOD - case (Some(Input), Some(Input)) => issueConnect(sink, source) - case (Some(Input), Some(Output)) => issueConnect(sink, source) - case (Some(Output), _) => throw UnwritableSinkException - case (_, None) => { + 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 (None, _) => throw UnwritableSinkException + case (Internal, _) => throw UnwritableSinkException } } diff --git a/chiselFrontend/src/main/scala/chisel3/core/Mux.scala b/chiselFrontend/src/main/scala/chisel3/core/Mux.scala index f01c59ca..e4ef001f 100644 --- a/chiselFrontend/src/main/scala/chisel3/core/Mux.scala +++ b/chiselFrontend/src/main/scala/chisel3/core/Mux.scala @@ -26,10 +26,10 @@ object Mux { def do_apply[T <: Data](cond: Bool, con: T, alt: T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): T = { - Binding.checkSynthesizable(cond, s"'cond' ($cond)") - Binding.checkSynthesizable(con, s"'con' ($con)") - Binding.checkSynthesizable(alt, s"'alt' ($alt)") + requireIsHardware(cond, "mux condition") + requireIsHardware(con, "mux true value") + requireIsHardware(alt, "mux false value") val d = cloneSupertype(Seq(con, alt), "Mux") pushOp(DefPrim(sourceInfo, d, MultiplexOp, cond.ref, con.ref, alt.ref)) } -}
\ No newline at end of file +} diff --git a/chiselFrontend/src/main/scala/chisel3/core/Reg.scala b/chiselFrontend/src/main/scala/chisel3/core/Reg.scala index 12d0a939..3fdb3398 100644 --- a/chiselFrontend/src/main/scala/chisel3/core/Reg.scala +++ b/chiselFrontend/src/main/scala/chisel3/core/Reg.scala @@ -17,12 +17,12 @@ object Reg { */ def apply[T <: Data](t: T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): T = { if (compileOptions.declaredTypeMustBeUnbound) { - Binding.checkUnbound(t, s"t ($t) must be unbound Type. Try using cloneType?") + requireIsChiselType(t, "reg type") } val reg = t.chiselCloneType val clock = Node(Builder.forcedClock) - Binding.bind(reg, RegBinder(Builder.forcedUserModule), "Error: t") + reg.bind(RegBinding(Builder.forcedUserModule)) pushCommand(DefReg(sourceInfo, reg, clock)) reg } @@ -40,7 +40,6 @@ object RegNext { }).asInstanceOf[T] val reg = Reg(model) - Binding.checkSynthesizable(next, s"'next' ($next)") // TODO: move into connect? reg := next reg @@ -57,7 +56,6 @@ object RegNext { }).asInstanceOf[T] val reg = RegInit(model, init) // TODO: this makes NO sense - Binding.checkSynthesizable(next, s"'next' ($next)") // TODO: move into connect? reg := next reg @@ -84,14 +82,14 @@ object RegInit { */ def apply[T <: Data](t: T, init: T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): T = { if (compileOptions.declaredTypeMustBeUnbound) { - Binding.checkUnbound(t, s"t ($t) must be unbound Type. Try using cloneType?") + requireIsChiselType(t, "reg type") } val reg = t.chiselCloneType val clock = Node(Builder.forcedClock) val reset = Node(Builder.forcedReset) - Binding.bind(reg, RegBinder(Builder.forcedUserModule), "Error: t") - Binding.checkSynthesizable(init, s"'init' ($init)") + reg.bind(RegBinding(Builder.forcedUserModule)) + requireIsHardware(init, "reg initializer") pushCommand(DefRegInit(sourceInfo, reg, clock, reset, init.ref)) reg } diff --git a/chiselFrontend/src/main/scala/chisel3/core/UserModule.scala b/chiselFrontend/src/main/scala/chisel3/core/UserModule.scala index 666be4d0..5207ef04 100644 --- a/chiselFrontend/src/main/scala/chisel3/core/UserModule.scala +++ b/chiselFrontend/src/main/scala/chisel3/core/UserModule.scala @@ -64,21 +64,17 @@ abstract class UserModule(implicit moduleCompileOptions: CompileOptions) id._onModuleClose } - val firrtlPorts = for (port <- getModulePorts) yield { - // Port definitions need to know input or output at top-level. 'flipped' means Input. - val direction = if(Data.isFirrtlFlipped(port)) Direction.Input else Direction.Output - firrtl.Port(port, direction) - } + val firrtlPorts = getModulePorts map {port => Port(port, port.userDirection)} _firrtlPorts = Some(firrtlPorts) // Generate IO invalidation commands to initialize outputs as unused val invalidateCommands = getModulePorts map {port => DefInvalid(UnlocatableSourceInfo, port.ref)} - + val component = DefModule(this, name, firrtlPorts, invalidateCommands ++ getCommands) _component = Some(component) component } - + // There is no initialization to be done by default. private[core] def initializeInParent() {} } @@ -100,7 +96,7 @@ abstract class ImplicitModule(implicit moduleCompileOptions: CompileOptions) private[core] override def initializeInParent() { implicit val sourceInfo = UnlocatableSourceInfo - + for (port <- getModulePorts) { pushCommand(DefInvalid(sourceInfo, port.ref)) } @@ -122,7 +118,7 @@ abstract class LegacyModule(implicit moduleCompileOptions: CompileOptions) // These are to be phased out protected var override_clock: Option[Clock] = None protected var override_reset: Option[Bool] = None - + // _clock and _reset can be clock and reset in these 2ary constructors // once chisel2 compatibility issues are resolved @deprecated("Module constructor with override_clock and override_reset deprecated, use withClockAndReset", "chisel3") @@ -132,7 +128,7 @@ abstract class LegacyModule(implicit moduleCompileOptions: CompileOptions) this.override_clock = override_clock this.override_reset = override_reset } - + @deprecated("Module constructor with override _clock deprecated, use withClock", "chisel3") def this(_clock: Clock)(implicit moduleCompileOptions: CompileOptions) = this(Option(_clock), None)(moduleCompileOptions) @deprecated("Module constructor with override _reset deprecated, use withReset", "chisel3") @@ -174,7 +170,7 @@ abstract class LegacyModule(implicit moduleCompileOptions: CompileOptions) // Don't generate source info referencing parents inside a module, since this interferes with // module de-duplication in FIRRTL emission. implicit val sourceInfo = UnlocatableSourceInfo - + pushCommand(DefInvalid(sourceInfo, io.ref)) override_clock match { diff --git a/chiselFrontend/src/main/scala/chisel3/internal/Builder.scala b/chiselFrontend/src/main/scala/chisel3/internal/Builder.scala index 73556750..1d7a45e0 100644 --- a/chiselFrontend/src/main/scala/chisel3/internal/Builder.scala +++ b/chiselFrontend/src/main/scala/chisel3/internal/Builder.scala @@ -215,7 +215,7 @@ private[chisel3] object Builder { } def pushOp[T <: Data](cmd: DefPrim[T]): T = { // Bind each element of the returned Data to being a Op - Binding.bind(cmd.id, OpBinder(forcedUserModule), "Error: During op creation, fresh result") + cmd.id.bind(OpBinding(forcedUserModule)) pushCommand(cmd).id } diff --git a/chiselFrontend/src/main/scala/chisel3/internal/firrtl/IR.scala b/chiselFrontend/src/main/scala/chisel3/internal/firrtl/IR.scala index 18df7f51..cca368ef 100644 --- a/chiselFrontend/src/main/scala/chisel3/internal/firrtl/IR.scala +++ b/chiselFrontend/src/main/scala/chisel3/internal/firrtl/IR.scala @@ -266,7 +266,7 @@ case class BulkConnect(sourceInfo: SourceInfo, loc1: Node, loc2: Node) extends C case class Attach(sourceInfo: SourceInfo, locs: Seq[Node]) extends Command case class ConnectInit(sourceInfo: SourceInfo, loc: Node, exp: Arg) extends Command case class Stop(sourceInfo: SourceInfo, clock: Arg, ret: Int) extends Command -case class Port(id: Data, dir: Direction) +case class Port(id: Data, dir: UserDirection) case class Printf(sourceInfo: SourceInfo, clock: Arg, pable: Printable) extends Command abstract class Component extends Arg { def id: BaseModule |
