summaryrefslogtreecommitdiff
path: root/chiselFrontend/src/main/scala/chisel3/core/Binding.scala
diff options
context:
space:
mode:
Diffstat (limited to 'chiselFrontend/src/main/scala/chisel3/core/Binding.scala')
-rw-r--r--chiselFrontend/src/main/scala/chisel3/core/Binding.scala239
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
+}