summaryrefslogtreecommitdiff
path: root/core/src/main/scala/chisel3/internal/MonoConnect.scala
blob: a0cca4a60c2b604fcab741582a380c6549527364 (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
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
// SPDX-License-Identifier: Apache-2.0

package chisel3.internal

import chisel3._
import chisel3.experimental.{Analog, BaseModule, FixedPoint, Interval, UnsafeEnum}
import chisel3.internal.Builder.pushCommand
import chisel3.internal.firrtl.{Connect, Converter, DefInvalid}
import chisel3.experimental.dataview.{isView, reify, reifyToAggregate}

import scala.language.experimental.macros
import chisel3.internal.sourceinfo.SourceInfo
import _root_.firrtl.passes.CheckTypes
import scala.annotation.tailrec

/**
  * MonoConnect.connect executes a mono-directional connection element-wise.
  *
  * Note that this isn't commutative. There is an explicit source and sink
  * already determined before this function is called.
  *
  * The connect operation will recurse down the left Data (with the right Data).
  * An exception will be thrown if a movement through the left cannot be matched
  * in the right. The right side is allowed to have extra Record fields.
  * Vecs must still be exactly the same size.
  *
  * See elemConnect for details on how the root connections are issued.
  *
  * Note that a valid sink must be writable so, one of these must hold:
  * - Is an internal writable node (Reg or Wire)
  * - Is an output of the current module
  * - Is an input of a submodule of the current module
  *
  * Note that a valid source must be readable so, one of these must hold:
  * - Is an internal readable node (Reg, Wire, Op)
  * - Is a literal
  * - Is a port of the current module or submodule of the current module
  */

private[chisel3] object MonoConnect {
  def formatName(data: Data) = s"""${data.earlyName} in ${data.parentNameOpt.getOrElse("(unknown)")}"""

  // These are all the possible exceptions that can be thrown.
  // These are from element-level connection
  def UnreadableSourceException(sink: Data, source: Data) =
    MonoConnectException(
      s"""${formatName(source)} cannot be read from module ${sink.parentNameOpt.getOrElse("(unknown)")}."""
    )
  def UnwritableSinkException(sink: Data, source: Data) =
    MonoConnectException(
      s"""${formatName(sink)} cannot be written from module ${source.parentNameOpt.getOrElse("(unknown)")}."""
    )
  def SourceEscapedWhenScopeException(source: Data) =
    MonoConnectException(s"Source ${formatName(source)} has escaped the scope of the when in which it was constructed.")
  def SinkEscapedWhenScopeException(sink: Data) =
    MonoConnectException(s"Sink ${formatName(sink)} has escaped the scope of the when in which it was constructed.")
  def UnknownRelationException =
    MonoConnectException("Sink or source unavailable to current module.")
  // These are when recursing down aggregate types
  def MismatchedVecException =
    MonoConnectException("Sink and Source are different length Vecs.")
  def MissingFieldException(field: String) =
    MonoConnectException(s"Source Record missing field ($field).")
  def MismatchedException(sink: Data, source: Data) =
    MonoConnectException(
      s"Sink (${sink.cloneType.toString}) and Source (${source.cloneType.toString}) have different types."
    )
  def DontCareCantBeSink =
    MonoConnectException("DontCare cannot be a connection sink")
  def AnalogCantBeMonoSink(sink: Data) =
    MonoConnectException(s"Sink ${formatName(sink)} of type Analog cannot participate in a mono connection (:=)")
  def AnalogCantBeMonoSource(source: Data) =
    MonoConnectException(s"Source ${formatName(source)} of type Analog cannot participate in a mono connection (:=)")
  def AnalogMonoConnectionException(source: Data, sink: Data) =
    MonoConnectException(
      s"Source ${formatName(source)} and sink ${formatName(sink)} of type Analog cannot participate in a mono connection (:=)"
    )

  def checkWhenVisibility(x: Data): Boolean = {
    x.topBinding match {
      case mp: MemoryPortBinding =>
        true // TODO (albert-magyar): remove this "bridge" for odd enable logic of current CHIRRTL memories
      case cd: ConditionalDeclarable => cd.visibility.map(_.active()).getOrElse(true)
      case _ => true
    }
  }

  /** This function is what recursively tries to connect a sink and source together
    *
    * There is some cleverness in the use of internal try-catch to catch exceptions
    * during the recursive decent and then rethrow them with extra information added.
    * This gives the user a 'path' to where in the connections things went wrong.
    */
  def connect(
    sourceInfo:            SourceInfo,
    connectCompileOptions: CompileOptions,
    sink:                  Data,
    source:                Data,
    context_mod:           RawModule
  ): Unit =
    (sink, source) match {

      // Handle legal element cases, note (Bool, Bool) is caught by the first two, as Bool is a UInt
      case (sink_e: Bool, source_e: UInt) =>
        elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod)
      case (sink_e: UInt, source_e: Bool) =>
        elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod)
      case (sink_e: UInt, source_e: UInt) =>
        elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod)
      case (sink_e: SInt, source_e: SInt) =>
        elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod)
      case (sink_e: FixedPoint, source_e: FixedPoint) =>
        elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod)
      case (sink_e: Interval, source_e: Interval) =>
        elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod)
      case (sink_e: Clock, source_e: Clock) =>
        elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod)
      case (sink_e: AsyncReset, source_e: AsyncReset) =>
        elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod)
      case (sink_e: ResetType, source_e: Reset) =>
        elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod)
      case (sink_e: Reset, source_e: ResetType) =>
        elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod)
      case (sink_e: EnumType, source_e: UnsafeEnum) =>
        elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod)
      case (sink_e: EnumType, source_e: EnumType) if sink_e.typeEquivalent(source_e) =>
        elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod)
      case (sink_e: UnsafeEnum, source_e: UInt) =>
        elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod)

      // Handle Vec case
      case (sink_v: Vec[Data @unchecked], source_v: Vec[Data @unchecked]) =>
        if (sink_v.length != source_v.length) { throw MismatchedVecException }

        val sinkReified:   Option[Aggregate] = if (isView(sink_v)) reifyToAggregate(sink_v) else Some(sink_v)
        val sourceReified: Option[Aggregate] = if (isView(source_v)) reifyToAggregate(source_v) else Some(source_v)

        if (
          sinkReified.nonEmpty && sourceReified.nonEmpty && canBulkConnectAggregates(
            sinkReified.get,
            sourceReified.get,
            sourceInfo,
            connectCompileOptions,
            context_mod
          )
        ) {
          pushCommand(Connect(sourceInfo, sinkReified.get.lref, sourceReified.get.ref))
        } else {
          for (idx <- 0 until sink_v.length) {
            try {
              implicit val compileOptions = connectCompileOptions
              connect(sourceInfo, connectCompileOptions, sink_v(idx), source_v(idx), context_mod)
            } catch {
              case MonoConnectException(message) => throw MonoConnectException(s"($idx)$message")
            }
          }
        }
      // Handle Vec connected to DontCare. Apply the DontCare to individual elements.
      case (sink_v: Vec[Data @unchecked], DontCare) =>
        for (idx <- 0 until sink_v.length) {
          try {
            implicit val compileOptions = connectCompileOptions
            connect(sourceInfo, connectCompileOptions, sink_v(idx), source, context_mod)
          } catch {
            case MonoConnectException(message) => throw MonoConnectException(s"($idx)$message")
          }
        }

      // Handle Record case
      case (sink_r: Record, source_r: Record) =>
        val sinkReified:   Option[Aggregate] = if (isView(sink_r)) reifyToAggregate(sink_r) else Some(sink_r)
        val sourceReified: Option[Aggregate] = if (isView(source_r)) reifyToAggregate(source_r) else Some(source_r)

        if (
          sinkReified.nonEmpty && sourceReified.nonEmpty && canBulkConnectAggregates(
            sinkReified.get,
            sourceReified.get,
            sourceInfo,
            connectCompileOptions,
            context_mod
          )
        ) {
          pushCommand(Connect(sourceInfo, sinkReified.get.lref, sourceReified.get.ref))
        } else {
          // For each field, descend with right
          for ((field, sink_sub) <- sink_r.elements) {
            try {
              source_r.elements.get(field) match {
                case Some(source_sub) => connect(sourceInfo, connectCompileOptions, sink_sub, source_sub, context_mod)
                case None => {
                  if (connectCompileOptions.connectFieldsMustMatch) {
                    throw MissingFieldException(field)
                  }
                }
              }
            } catch {
              case MonoConnectException(message) => throw MonoConnectException(s".$field$message")
            }
          }
        }
      // Handle Record connected to DontCare. Apply the DontCare to individual elements.
      case (sink_r: Record, DontCare) =>
        // For each field, descend with right
        for ((field, sink_sub) <- sink_r.elements) {
          try {
            connect(sourceInfo, connectCompileOptions, sink_sub, source, context_mod)
          } catch {
            case MonoConnectException(message) => throw MonoConnectException(s".$field$message")
          }
        }

      // Source is DontCare - it may be connected to anything. It generates a defInvalid for the sink.
      case (_sink: Element, DontCare) =>
        val sink = reify(_sink) // Handle views
        pushCommand(DefInvalid(sourceInfo, sink.lref))
      // DontCare as a sink is illegal.
      case (DontCare, _) => throw DontCareCantBeSink
      // Analog is illegal in mono connections.
      case (_: Analog, _: Analog) => throw AnalogMonoConnectionException(source, sink)
      // Analog is illegal in mono connections.
      case (_: Analog, _) => throw AnalogCantBeMonoSink(sink)
      // Analog is illegal in mono connections.
      case (_, _: Analog) => throw AnalogCantBeMonoSource(source)
      // Sink and source are different subtypes of data so fail
      case (sink, source) => throw MismatchedException(sink, source)
    }

  /** Determine if a valid connection can be made between a source [[Aggregate]] and sink
    * [[Aggregate]] given their parent module and directionality context
    *
    * @return whether the source and sink exist in an appropriate context to be connected
    */
  private[chisel3] def aggregateConnectContextCheck(
    implicit sourceInfo:   SourceInfo,
    connectCompileOptions: CompileOptions,
    sink:                  Aggregate,
    source:                Aggregate,
    context_mod:           RawModule
  ): Boolean = {
    import ActualDirection.{Bidirectional, Input, Output}
    // 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.topBinding.location.getOrElse(throw UnwritableSinkException(sink, source))
    val source_mod: BaseModule = source.topBinding.location.getOrElse(context_mod)

    val sink_parent = Builder.retrieveParent(sink_mod, context_mod).getOrElse(None)
    val source_parent = Builder.retrieveParent(source_mod, context_mod).getOrElse(None)

    val sink_is_port = sink.topBinding match {
      case PortBinding(_) => true
      case _              => false
    }
    val source_is_port = source.topBinding match {
      case PortBinding(_) => true
      case _              => false
    }

    if (!checkWhenVisibility(sink)) {
      throw SinkEscapedWhenScopeException(sink)
    }

    if (!checkWhenVisibility(source)) {
      throw SourceEscapedWhenScopeException(source)
    }

    // CASE: Context is same module that both sink node and source node are in
    if ((context_mod == sink_mod) && (context_mod == source_mod)) {
      sink.direction != Input
    }

    // CASE: Context is same module as sink node and source node is in a child module
    else if ((sink_mod == context_mod) && (source_parent == context_mod)) {
      // NOTE: Workaround for bulk connecting non-agnostified FIRRTL ports
      // See: https://github.com/freechipsproject/firrtl/issues/1703
      // Original behavior should just check if the sink direction is an Input
      val sinkCanBeInput = sink.direction match {
        case Input            => true
        case Bidirectional(_) => true
        case _                => false
      }
      // Thus, right node better be a port node and thus have a direction
      if (!source_is_port) { !connectCompileOptions.dontAssumeDirectionality }
      else if (sinkCanBeInput) {
        if (source.direction == Output) {
          !connectCompileOptions.dontTryConnectionsSwapped
        } else { false }
      } else { true }
    }

    // CASE: Context is same module as source node and sink node is in child module
    else if ((source_mod == context_mod) && (sink_parent == context_mod)) {
      // NOTE: Workaround for bulk connecting non-agnostified FIRRTL ports
      // See: https://github.com/freechipsproject/firrtl/issues/1703
      // Original behavior should just check if the sink direction is an Input
      sink.direction match {
        case Input            => true
        case Bidirectional(_) => true
        case _                => false
      }
    }

    // CASE: Context is the parent module of both the module containing sink node
    //                                        and the module containing source node
    //   Note: This includes case when sink and source in same module but in parent
    else if ((sink_parent == context_mod) && (source_parent == context_mod)) {
      // Thus both nodes must be ports and have a direction
      if (!source_is_port) { !connectCompileOptions.dontAssumeDirectionality }
      else if (sink_is_port) {
        // NOTE: Workaround for bulk connecting non-agnostified FIRRTL ports
        // See: https://github.com/freechipsproject/firrtl/issues/1703
        // Original behavior should just check if the sink direction is an Input
        sink.direction match {
          case Input            => true
          case Bidirectional(_) => true // NOTE: Workaround for non-agnostified ports
          case _                => false
        }
      } else { false }
    }

    // Not quite sure where left and right are compared to current module
    // so just error out
    else false
  }

  /** Trace flow from child Data to its parent.
    *
    * Returns true if, given the context,
    * this signal can be a sink when wantsToBeSink = true,
    * or if it can be a source when wantsToBeSink = false.
    * Always returns true if the Data does not actually correspond
    * to a Port.
    */
  @tailrec private[chisel3] def traceFlow(
    wantToBeSink:     Boolean,
    currentlyFlipped: Boolean,
    data:             Data,
    context_mod:      RawModule
  ): Boolean = {
    val sdir = data.specifiedDirection
    val coercedFlip = sdir == SpecifiedDirection.Input
    val coercedAlign = sdir == SpecifiedDirection.Output
    val flipped = sdir == SpecifiedDirection.Flip
    val traceFlipped = ((flipped ^ currentlyFlipped) || coercedFlip) && (!coercedAlign)
    data.binding.get match {
      case ChildBinding(parent) => traceFlow(wantToBeSink, traceFlipped, parent, context_mod)
      case PortBinding(enclosure) =>
        val childPort = enclosure != context_mod
        wantToBeSink ^ childPort ^ traceFlipped
      case _ => true
    }
  }
  def canBeSink(data:   Data, context_mod: RawModule): Boolean = traceFlow(true, false, data, context_mod)
  def canBeSource(data: Data, context_mod: RawModule): Boolean = traceFlow(false, false, data, context_mod)

  /** Check whether two aggregates can be bulk connected (<=) in FIRRTL. (MonoConnect case)
    *
    * Mono-directional bulk connects only work if all signals of the sink are unidirectional
    * In the case of a sink aggregate with bidirectional signals, e.g. `Decoupled`,
    * a `BiConnect` is necessary.
    */
  private[chisel3] def canBulkConnectAggregates(
    sink:                  Aggregate,
    source:                Aggregate,
    sourceInfo:            SourceInfo,
    connectCompileOptions: CompileOptions,
    context_mod:           RawModule
  ): Boolean = {
    // Assuming we're using a <>, check if a bulk connect is valid in that case
    def biConnectCheck =
      BiConnect.canBulkConnectAggregates(sink, source, sourceInfo, connectCompileOptions, context_mod)

    // Check that the Aggregate can be driven (not bidirectional or an input) to match Chisel semantics
    def sinkCanBeDrivenCheck: Boolean =
      sink.direction == ActualDirection.Output || sink.direction == ActualDirection.Unspecified

    biConnectCheck && sinkCanBeDrivenCheck
  }

  // This function (finally) issues the connection operation
  private def issueConnect(sink: Element, source: Element)(implicit sourceInfo: SourceInfo): Unit = {
    // If the source is a DontCare, generate a DefInvalid for the sink,
    //  otherwise, issue a Connect.
    source.topBinding match {
      case b: DontCareBinding => pushCommand(DefInvalid(sourceInfo, sink.lref))
      case _ => pushCommand(Connect(sourceInfo, sink.lref, source.ref))
    }
  }

  // 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:           RawModule
  ): Unit = {
    import BindingDirection.{Input, Internal, Output} // Using extensively so import these
    val sink = reify(_sink)
    val source = reify(_source)
    // 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.topBinding.location.getOrElse(throw UnwritableSinkException(sink, source))
    val source_mod: BaseModule = source.topBinding.location.getOrElse(context_mod)

    val sink_parent = Builder.retrieveParent(sink_mod, context_mod).getOrElse(None)
    val source_parent = Builder.retrieveParent(source_mod, context_mod).getOrElse(None)

    val sink_direction = BindingDirection.from(sink.topBinding, sink.direction)
    val source_direction = BindingDirection.from(source.topBinding, source.direction)

    if (!checkWhenVisibility(sink)) {
      throw SinkEscapedWhenScopeException(sink)
    }

    if (!checkWhenVisibility(source)) {
      throw SourceEscapedWhenScopeException(source)
    }

    // 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 (Output, _)   => issueConnect(sink, source)
        case (Internal, _) => issueConnect(sink, source)
        case (Input, _)    => throw UnwritableSinkException(sink, source)
      }
    }

    // CASE: Context is same module as sink node and right node is in a child module
    else if ((sink_mod == context_mod) && (source_parent == context_mod)) {
      // Thus, right node better be a port node and thus have a direction
      ((sink_direction, source_direction): @unchecked) match {
        //    SINK          SOURCE
        //    CURRENT MOD   CHILD MOD
        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(sink, source)
          }
        }
        case (Input, Output) if (!(connectCompileOptions.dontTryConnectionsSwapped)) => issueConnect(source, sink)
        case (Input, _)                                                              => throw UnwritableSinkException(sink, source)
      }
    }

    // CASE: Context is same module as source node and sink node is in child module
    else if ((source_mod == context_mod) && (sink_parent == context_mod)) {
      // Thus, left node better be a port node and thus have a direction
      ((sink_direction, source_direction): @unchecked) match {
        //    SINK          SOURCE
        //    CHILD MOD     CURRENT MOD
        case (Input, _)    => issueConnect(sink, source)
        case (Output, _)   => throw UnwritableSinkException(sink, source)
        case (Internal, _) => throw UnwritableSinkException(sink, source)
      }
    }

    // CASE: Context is the parent module of both the module containing sink node
    //                                        and the module containing source node
    //   Note: This includes case when sink and source in same module but in parent
    else if ((sink_parent == context_mod) && (source_parent == context_mod)) {
      // Thus both nodes must be ports and have a direction
      ((sink_direction, source_direction): @unchecked) match {
        //    SINK          SOURCE
        //    CHILD MOD     CHILD MOD
        case (Input, Input)  => issueConnect(sink, source)
        case (Input, Output) => issueConnect(sink, source)
        case (Output, _)     => throw UnwritableSinkException(sink, source)
        case (_, Internal) => {
          if (!(connectCompileOptions.dontAssumeDirectionality)) {
            issueConnect(sink, source)
          } else {
            throw UnreadableSourceException(sink, source)
          }
        }
        case (Internal, _) => throw UnwritableSinkException(sink, source)
      }
    }

    // Not quite sure where left and right are compared to current module
    // so just error out
    else throw UnknownRelationException
  }
}