diff options
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 +} |
