summaryrefslogtreecommitdiff
path: root/chiselFrontend/src/main/scala/chisel3/core/Binding.scala
blob: 467cb4eb7f6bb600cda2690023a0ff39709115db (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
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 Bundle 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")

  // 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 (bundle: Bundle) => {
      for((field, subelem) <- bundle.elements) {
        try walkToBinding(subelem, checker)
        catch {
          case BindingException(message) => throw BindingException(s".$field$message")
        }
      }
    }
  }

  // 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)
        }
          // If autoIOWrap is enabled and we're rebinding a PortBinding, just ignore the rebinding.
        case portBound @ PortBinding(_, _) if (!(forcedModule.compileOptions.requireIOWrap) && binder.isInstanceOf[PortBinder]) =>
        case binding => throw AlreadyBoundException(binding.toString)
      }
    )
    catch {
      case BindingException(message) => throw BindingException(s"$error_prelude$message")
    }
    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")
    }
  }

  // Excepts if any root element is unbound and thus not on the hardware graph
  def checkSynthesizable(target: Data, error_prelude: String): Unit = {
    // This is called if we support autoIOWrap
    def elementOfIO(element: Element): Boolean = {
      element._parent match {
        case None => false
        case Some(x: Module) => {
          // Have we defined the IO ports for this module? If not, do so now.
          if (!x.ioDefined) {
            x.computePorts
            element.binding match {
              case SynthesizableBinding() => true
              case _ => false
            }
          } else {
            // io.flatten eliminates Clock elements, so we need to use io.allElements
            val ports = x.io.allElements
            val isIOElement = ports.contains(element) || element == x.clock || element == x.reset
            isIOElement
          }
        }
      }
    }

    /** Diagnose a binding error caused by a missing IO() wrapper.
      * @param element the element triggering the binding error.
      * @return true if the element is a member of the module's io but ioDefined is false.
      */
    def isMissingIOWrapper(element: Element): Boolean = {
      element._parent match {
        case None => false
        case Some(x: Module) => {
          // If the IO() wrapper has been executed, it isn't missing.
          if (x.ioDefined) {
            false
          } else {
            // TODO: We should issue the message only once, and if we get here,
            //  we know the wrapper is missing, whether or not the element is a member of io.
            //  But if it's not an io element, we want to issue the complementary "unbound" error.
            //  Revisit this when we collect error messages instead of throwing exceptions.
            x.io.flatten.contains(element)
          }
        }
      }
    }

    try walkToBinding(
      target,
      element => element.binding match {
        case SynthesizableBinding() => {} // OK
        case binding =>
          // The following kludge is an attempt to provide backward compatibility
          // It should be done at at higher level.
          if ((forcedModule.compileOptions.requireIOWrap || !elementOfIO(element))) {
            // Generate a better error message if this is a result of a missing IO() wrapper.
            if (isMissingIOWrapper(element)) {
              throw MissingIOWrapperException
            } else {
              throw NotSynthesizableException
            }
          } else {
            Binding.bind(element, PortBinder(element._parent.get), "Error: IO")
          }
      }
    )
    catch {
      case BindingException(message) => throw BindingException(s"$error_prelude$message")
    }
  }
}

// Location refers to 'where' in the Module hierarchy this lives
sealed trait Binding {
  def location: Option[Module]
  def direction: Option[Direction]
}

// 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 {
  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 {
  def enclosure: Module
  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: Module)
    extends SynthesizableBinding with ConstrainedBinding with UndirectionedBinding

// TODO(twigg): Ops between unenclosed nodes can also be unenclosed
// However, Chisel currently binds all op results to a module
case class OpBinding(enclosure: Module)
    extends SynthesizableBinding with ConstrainedBinding with UndirectionedBinding

case class PortBinding(enclosure: Module, direction: Option[Direction])
    extends SynthesizableBinding with ConstrainedBinding

case class RegBinding(enclosure: Module)
    extends SynthesizableBinding with ConstrainedBinding with UndirectionedBinding

case class WireBinding(enclosure: Module)
    extends SynthesizableBinding with ConstrainedBinding with UndirectionedBinding