diff options
| author | Richard Lin | 2017-06-26 18:35:46 -0700 |
|---|---|---|
| committer | GitHub | 2017-06-26 18:35:46 -0700 |
| commit | aaee58d374dc3f3a088856da13a6a59ecffb2cac (patch) | |
| tree | 790828de9a9fbb1dfd26635eebaf27668a4cc8df /chiselFrontend/src/main/scala/chisel3/core/Binding.scala | |
| parent | 91850ec917a432e9e3db588e7711c8a82801eb49 (diff) | |
Directions internals mega-refactor (#617)
Part 1 of mega-change in #578
Major notes:
- Input(...) and Output(...) now (effectively) recursively override their elements' directions
- Nodes given userDirection (Input, Output, Flip - what the user assigned to _that_ node) and actualDirection (Input, Output, None, but also Bidirectional and BidirectionalFlip for mostly Aggregates), because of the above (since a higher-level Input(...) can override the locally specified user direction).
- DataMirror (node reflection APIs) added to chisel3.experimental. This provides ways to query the user given direction of a node as well as the actual direction.
- checkSynthesizable replaced with requireIsHardware and requireIsChiselType and made available in chisel3.experimental.
Internal changes notes:
- toType moved into Emitter, this makes the implementation cleaner especially considering that Vec types can't be flipped in FIRRTL. This also more clearly separates Chisel frontend from FIRRTL emission.
- Direction separated from Bindings, both are now fields in Data, and all nodes are given hierarchical directions (Aggregates may be Bidirectional). The actualDirection at the Element (leaf) level should be the same as binding directions previously.
- Bindings are hierarchical, children (of a, for example, Bundle) have a ChildBinding that points to their parent. This is different than the previous scheme where Bindings only applied at the Element (leaf) level.
- Lots of small misc clean up.
Future PRs will address other parts of #578, including stricter direction checks that aren't a side-effect of this internal refactor, stricter checks and splitting of binding operations (Wire vs. WireInit), and node operations not introduced here (getType and deprecation of chiselCloneType). Since those shouldn't mess with internals, those should be much smaller.
Diffstat (limited to 'chiselFrontend/src/main/scala/chisel3/core/Binding.scala')
| -rw-r--r-- | chiselFrontend/src/main/scala/chisel3/core/Binding.scala | 239 |
1 files changed, 69 insertions, 170 deletions
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 +} |
