1"""
2========================================================================
3ComponentLevel3.py
4========================================================================
5We add value wire/interface connections. Basically, all connected
6value signal in the whole graph should have the same value of the unique
7"net writer" written in an update block.
8Then, the update block for a net is basically one writer writes to those
9readers. Interface connections are handled separately, and they should
10be revamped when adding method-based interfaces.
11
12Author : Shunning Jiang
13Date   : Jan 29, 2020
14"""
15import ast
16import inspect
17import linecache
18from collections import defaultdict
19
20from pymtl3.datatypes import Bits, is_bitstruct_inst
21from pymtl3.extra.pypy import custom_exec
22
23from .ComponentLevel1 import ComponentLevel1
24from .ComponentLevel2 import ComponentLevel2, compiled_re
25from .Connectable import (
26    Connectable,
27    Const,
28    InPort,
29    Interface,
30    OutPort,
31    Signal,
32    Wire,
33    _connect_check,
34)
35from .errors import (
36    InvalidConnectionError,
37    InvalidPlaceholderError,
38    MultiWriterError,
39    NotElaboratedError,
40    NoWriterError,
41    PyMTLDeprecationError,
42    SignalTypeError,
43)
44from .NamedObject import NamedObject
45from .Placeholder import Placeholder
46
47
48def connect( o1, o2 ):
49  host, o1_connectable, o2_connectable = _connect_check( o1, o2, internal=False )
50  host._connect_dispatch( o1, o2, o1_connectable, o2_connectable )
51
52class ComponentLevel3( ComponentLevel2 ):
53
54  #-----------------------------------------------------------------------
55  # Private methods
56  #-----------------------------------------------------------------------
57
58  def __new__( cls, *args, **kwargs ):
59    inst = super().__new__( cls, *args, **kwargs )
60    inst._dsl.adjacency     = defaultdict(set)
61    inst._dsl.connect_order = []
62    inst._dsl.consts        = set()
63
64    return inst
65
66  # Override
67  def _collect_vars( s, m ):
68    super()._collect_vars( m )
69    if isinstance( m, ComponentLevel3 ):
70      all_ajd = s._dsl.all_adjacency
71      for k, v in m._dsl.adjacency.items():
72        all_ajd[k] |= v
73
74  # The following three methods should only be called when types are
75  # already checked
76  def _create_assign_lambda( s, o, lamb ):
77    assert isinstance( o, Signal ), "You can only assign(//=) a lambda function to a Wire/InPort/OutPort."
78
79    srcs, line = inspect.getsourcelines( lamb )
80
81    src  = compiled_re.sub( r'\2', ''.join(srcs) ).lstrip(' ')
82    root = ast.parse(src)
83    assert isinstance( root, ast.Module ) and len(root.body) == 1, "We only support single-statement lambda."
84
85    root = root.body[0]
86    assert isinstance( root, ast.AugAssign ) and isinstance( root.op, ast.FloorDiv )
87
88    # lhs, rhs = root.target, root.value
89    # Shunning: here we need to use ast from repr(o), because root.target
90    # can be "m.in_" in some cases where we actually know what m is but the
91    # source code still captures "m"
92    lhs, rhs = ast.parse( f"s{repr(o)[len(repr(s)):]}" ).body[0].value, root.value
93    lhs.ctx = ast.Store()
94    # We expect the lambda to have no argument:
95    # {'args': [], 'vararg': None, 'kwonlyargs': [], 'kw_defaults': [], 'kwarg': None, 'defaults': []}
96    assert isinstance( rhs, ast.Lambda ) and not rhs.args.args and rhs.args.vararg is None, \
97      "The lambda shouldn't contain any argument."
98
99    rhs = rhs.body
100
101    # Compose a new and valid function based on the lambda's lhs and rhs
102    # Note that we don't need to add those source code of closure var
103    # assignment to linecache. To get the matching line number in the
104    # error message, we set the line number of update block
105    # Shunning: bugfix:
106
107    blk_name = "_lambda__{}".format( repr(o).replace(".","_").replace("[", "_").replace("]", "_").replace(":", "_") )
108    lambda_upblk = ast.FunctionDef(
109      name=blk_name,
110      args=ast.arguments(args=[], vararg=None, kwonlyargs=[], kw_defaults=[], posonlyargs=[], kwarg=None, defaults=[]),
111      body=[ast.AugAssign(target=lhs, op=ast.MatMult(), value=rhs, lineno=2, col_offset=6)],
112      decorator_list=[],
113      returns=None,
114      lineno=1, col_offset=4,
115    )
116    lambda_upblk_module = ast.Module(body=[ lambda_upblk ], type_ignores=[])
117
118    # Manually wrap the lambda upblk with a closure function that adds the
119    # desired variables to the closure of `_lambda__*`
120    # We construct AST for the following function to add free variables in the
121    # closure of the lambda function to the closure of the generated lambda
122    # update block.
123    #
124    # def closure( lambda_closure ):
125    #   <FreeVarName1> = lambda_closure[<Idx1>].cell_contents
126    #   <FreeVarName2> = lambda_closure[<Idx2>].cell_contents
127    #   ...
128    #   <FreeVarNameN> = lambda_closure[<IdxN>].cell_contents
129    #   def _lambda__<lambda_blk_name>():
130    #     # the assignment statement appears here
131    #   return _lambda__<lambda_blk_name>
132
133    new_root = ast.Module( body=[
134      ast.FunctionDef(
135          name="closure",
136          args=ast.arguments(args=[ast.arg(arg="lambda_closure", annotation=None, lineno=1, col_offset=12)],
137                             vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, posonlyargs=[], defaults=[]),
138          body=[
139            ast.Assign(
140              targets=[ast.Name(id=var, ctx=ast.Store(), lineno=1+idx, col_offset=2)],
141              value=ast.Attribute(
142                value=ast.Subscript(
143                  value=ast.Name(
144                    id='lambda_closure',
145                    ctx=ast.Load(),
146                    lineno=1+idx, col_offset=5+len(var),
147                  ),
148                  slice=ast.Index(
149                    value=ast.Num(
150                      n=idx,
151                      lineno=1+idx, col_offset=19+len(var),
152                    ),
153                  ),
154                  ctx=ast.Load(),
155                  lineno=1+idx, col_offset=5+len(var),
156                ),
157                attr='cell_contents',
158                ctx=ast.Load(),
159                lineno=1+idx, col_offset=5+len(var),
160              ),
161              lineno=1+idx, col_offset=2,
162            ) for idx, var in enumerate(lamb.__code__.co_freevars)
163          ] + [ lambda_upblk ] + [
164            ast.Return(
165              value=ast.Name(
166                id=blk_name,
167                ctx=ast.Load(),
168                lineno=4+len(lamb.__code__.co_freevars), col_offset=9,
169              ),
170              lineno=4+len(lamb.__code__.co_freevars), col_offset=2,
171            )
172          ],
173          decorator_list=[],
174          returns=None,
175          lineno=1, col_offset=0,
176        )
177    ], type_ignores=[] )
178
179    # In Python 3 we need to supply a dict as local to get the newly
180    # compiled function from closure.
181    # Then `closure(lamb.__closure__)` returns the lambda update block with
182    # the correct free variables in its closure.
183
184    dict_local = {}
185    custom_exec( compile(new_root, blk_name, "exec"), lamb.__globals__, dict_local )
186    blk = dict_local[ 'closure' ]( lamb.__closure__ )
187
188    # Add the source code to linecache for the compiled function
189
190    new_src = "def {}():\n {}\n".format( blk_name, src.replace("//=", "@=") )
191    linecache.cache[ blk_name ] = (len(new_src), None, new_src.splitlines(), blk_name)
192
193    ComponentLevel1._update( s, blk )
194
195    # This caching here does no caching because the block name contains
196    # the signal name intentionally to avoid conflicts. With //= it is
197    # more possible than normal update block to have conflicts:
198    # if param == 1:  s.out //= s.in_ + 1
199    # else:           s.out //= s.out + 100
200    # Here these two blocks will implicity have the same name but they
201    # have different contents based on different param.
202    # So the cache call here is just to reuse the existing interface to
203    # register the AST/src of the generated block for elaborate or passes
204    # to use.
205    s._cache_func_meta( blk, is_update_ff=False,
206      given=("".join(srcs), lambda_upblk_module, line, inspect.getsourcefile( lamb )) )
207    return blk
208
209  def _connect_signal_const( s, o1, o2 ):
210    Type = o1._dsl.Type
211    if isinstance( o2, int ):
212      if not issubclass( Type, (int, Bits) ):
213        raise InvalidConnectionError( f"We don't support connecting an integer constant "
214                                      f"to non-int/Bits type {Type}" )
215      o2 = Const( Type, Type(o2), s )
216    elif isinstance( o2, Bits ):
217      Type2 = type(o2)
218      if not issubclass( Type, Bits ):
219        raise InvalidConnectionError( f"We don't support connecting a {Type2} constant "
220                                      f"to non-Bits type {Type}" )
221      # Both bits, check bitwidth
222      # use object.nbits instead of Type.nbits to handle both cases: Bits32(2) and Bits(32,2)
223      if Type.nbits != o2.nbits:
224        raise InvalidConnectionError( f"Bitwidth mismatch when connecting a {Type2} constant "
225                                      f"to signal {o1} with type {Type}." )
226      o2 = Const( Type, o2, s )
227    elif is_bitstruct_inst( o2 ):
228      Type2 = type(o2)
229      if Type is not Type2:
230        raise InvalidConnectionError( f"We don't support connecting a {Type2} constant bitstruct"
231                                      f"to non-bitstruct type {Type}" )
232      o2 = Const( Type, o2, s )
233    else:
234      raise InvalidConnectionError(f"\n>>> {o2} of type {type(o2)} is not a const! \n"
235                                   f">>> It cannot be connected to signal {o1} of type {o1._dsl.Type}!\n"
236                                   f"Suggestion: fix the RHS of connection to be an instance of {o1._dsl.Type}.")
237
238    # TODO implement connecting a const struct
239
240    host = o1.get_host_component()
241
242    if isinstance( o1, InPort ):
243      # connecting constant to inport should be at the parent level
244      host = host.get_parent_object()
245
246    o2._dsl.parent_obj = s
247    s._dsl.consts.add( o2 )
248
249    # o2 should be a new object
250
251    s._dsl.adjacency[o1].add( o2 )
252    s._dsl.adjacency[o2].add( o1 )
253
254    s._dsl.connect_order.append( (o1, o2) )
255
256  def _connect_signal_signal( s, o1, o2 ):
257    if not (o1._dsl.Type is o2._dsl.Type):
258      raise InvalidConnectionError( f"Bitwidth mismatch {o1._dsl.Type.__name__} != {o2._dsl.Type.__name__}\n"
259                                    f"- In class {type(s)}\n- When connecting {o1} <-> {o2}\n"
260                                    f"Suggestion: make sure both sides of connection have matching bitwidth")
261
262    if o1 not in s._dsl.adjacency[o2]:
263      assert o2 not in s._dsl.adjacency[o1]
264      s._dsl.adjacency[o1].add( o2 )
265      s._dsl.adjacency[o2].add( o1 )
266
267      s._dsl.connect_order.append( (o1, o2) )
268
269  def _connect_interfaces( s, o1, o2 ):
270    # When we connect two interfaces, we first try to use o1's and o2's
271    # connect. If failed, we fall back to by-name connection
272
273    def connect_by_name( this, other ):
274      def recursive_connect( this_obj, other_obj ):
275        if isinstance( this_obj, list ):
276          for i in range(len(this_obj)):
277            # TODO add error message if other_obj is not a list
278            recursive_connect( this_obj[i], other_obj[i] )
279        else:
280          s._connect( other_obj, this_obj, internal=True )
281
282      # Sort the keys to always connect in a unique order
283      for name in sorted(this.__dict__):
284        if name[0] != '_': # filter private variables
285          obj = this.__dict__[ name ]
286          if hasattr( other, name ):
287            # other has the corresponding field, connect recursively
288            recursive_connect( obj, getattr( other, name ) )
289
290          else:
291            # other doesn't have the corresponding field, raise error
292            # if obj is connectable.
293            if isinstance( obj, Connectable ):
294              raise InvalidConnectionError("There is no \"{}\" field in {} "
295              "to connect to {} during by-name connection\n"
296              "Suggestion: check the implementation of \n"
297              "          - {} (class {})\n"
298              "          - {} (class {})".format( name, other, obj,
299                repr(this), type(this), repr(other), type(other) ) )
300
301    if hasattr( o1, "connect" ):
302      if not o1.connect( o2, s ): # o1.connect fail
303        if hasattr( o2, "connect" ):
304          if not o2.connect( o1, s ):
305            connect_by_name( o1, o2 )
306        else:
307          connect_by_name( o1, o2 )
308
309    else: # o1 has no "connect"
310      if hasattr( o2, "connect" ):
311        if not o2.connect( o1, s ):
312          connect_by_name( o1, o2 )
313      else:
314        connect_by_name( o1, o2 ) # capture s
315
316  def _connect( s, o1, o2, internal ):
317    """ Top level private method for connecting two objects. """
318    host, o1_connectable, o2_connectable = _connect_check( o1, o2, internal )
319
320    if host is None:
321      # host is None only happens when internal = True
322      assert internal
323
324    else:
325      assert s is host, "Please contact pymtl3 developer -- s:{} host:{}".format(s, host)
326      s._connect_dispatch( o1, o2, o1_connectable, o2_connectable )
327
328  def _connect_dispatch( s, o1, o2, o1_connectable, o2_connectable ):
329
330    if o1_connectable and o2_connectable:
331      # if both connectable, dispatch signal-signal and interface-interface
332      if isinstance( o1, Signal ) and isinstance( o2, Signal ):
333        s._connect_signal_signal( o1, o2 )
334      elif isinstance( o1, Interface ) and isinstance( o2, Interface ):
335        s._connect_interfaces( o1, o2 )
336      else:
337        raise InvalidConnectionError("{} cannot be connected to {}: {} != {}" \
338              .format(repr(o1), repr(o2), type(o1), type(o2)) )
339    else:
340      # One is connectable, we make sure it's o1
341      if o2_connectable:
342        o1, o2 = o2, o1
343      assert isinstance( o1, Signal ), f"Cannot connect {o1!r} to {o2!r}."
344
345      s._connect_signal_const( o1, o2 )
346
347  @staticmethod
348  def _floodfill_nets( signal_list, adjacency ):
349    """ Floodfill to find out connected nets. Return a list of sets. """
350
351    nets = []
352    visited = set()
353    pred    = {} # detect cycle that has >=3 nodes
354    for obj in signal_list:
355      # If obj has adjacent signals
356      if obj in adjacency and obj not in visited:
357        net = set()
358        Q   = [ obj ]
359        while Q:
360          u = Q.pop()
361          visited.add( u )
362          net.add( u )
363          for v in adjacency[u]:
364            if v not in visited:
365              pred[v] = u
366              Q.append( v )
367            elif v is not pred[u]:
368              raise InvalidConnectionError(repr(v)+" is in a connection loop.")
369        if len(net) == 1:
370          continue
371        nets.append( net )
372    return nets
373
374  def _resolve_value_connections( s ):
375    """ The case of nested data struct: the writer of a net can be one of
376    the three: signal itself (s.x.a), ancestor (s.x), descendant (s.x.b)
377
378    An iterative algorithm is required to mark the writers. The example
379    is the following. Net 1's writer is s.x and one reader is s.y.
380    Net 2's writer is s.y.a (known ONLY after Net 1's writer is clear),
381    one reader is s.z. Net 3's writer is s.z.a (known ...), and so forth
382
383    Note that s.x becomes writer when WR s.x.a or WR s.x.b, but s.x then
384    cannot propagate back to s.x.b or s.x.a.
385
386    The original state is all the writers from all update blocks.
387    writer_prop is a dict {x:y} that stores potential writers and
388    whether the writer can propagate to other nets. After a net is
389    resolved from headless condition, its readers become writers.
390
391    The case of slicing: slices of the same wire are only one level
392    deeper, so all of those parent/child relationship work easily.
393    However, unlike different fields of a data struct, different slices
394    may _intersect_, so they need to check sibling slices' write/read
395    status as well. """
396
397    # First of all, bfs the "forest" to find out all nets
398
399    nets = s._floodfill_nets( s._dsl.all_signals, s._dsl.all_adjacency )
400
401    # Then figure out writers: all writes in upblks and their nest objects
402
403    writer_prop = {}
404
405    for blk, writes in s._dsl.all_upblk_writes.items():
406      for obj in writes:
407        writer_prop[ obj ] = True # propagatable
408
409        obj = obj.get_parent_object()
410        while obj.is_signal():
411          writer_prop[ obj ] = False
412          obj = obj.get_parent_object()
413
414    # Find the host object of every net signal
415    # and then leverage the information to find out top level input port
416
417    for net in nets:
418      for member in net:
419        host = member.get_host_component()
420
421        # Specialize two cases:
422        # 1. A top-level input port is writer.
423        # 2. An output port of a placeholder module is a writer
424        if ( isinstance( member, InPort ) and host == s ) or \
425           ( isinstance( member, OutPort ) and isinstance( host, Placeholder ) ):
426          writer_prop[ member ] = True
427
428    headless = nets
429    headed   = []
430
431    # Convention: we store a net in a tuple ( writer, set([readers]) )
432    # The first element is writer; it should be None if there is no
433    # writer. The second element is a set of signals including the writer.
434
435    while headless:
436      new_headless = []
437      wcount = len(writer_prop)
438
439      # For each net, figure out the writer among all vars and their
440      # ancestors. Moreover, if x's ancestor has a writer in another net,
441      # x should be the writer of this net.
442      #
443      # If there is a writer, propagate writer information to all readers
444      # and readers' ancestors. The propagation is tricky: assume s.x.a
445      # is in net, and s.x.b is written in upblk, s.x.b will mark s.x as
446      # an unpropagatable writer because later s.x.a shouldn't be marked
447      # as writer by s.x.
448      #
449      # Similarly, if x[0:10] is written in update block, x[5:15] can
450      # be a unpropagatable writer because we don't want x[5:15] to
451      # propagate to x[12:17] later.
452
453      for net in headless:
454        has_writer = False
455
456        for v in net:
457          obj = None
458          try:
459            # Check if itself is a writer or a constant
460            if v in writer_prop or isinstance( v, Const ):
461              assert not has_writer
462              has_writer, writer = True, v
463
464            else:
465              # Check if an ancestor is a propagatable writer
466              obj = v.get_parent_object()
467              while obj.is_signal():
468                if obj in writer_prop and writer_prop[ obj ]:
469                  assert not has_writer
470                  has_writer, writer = True, v
471                  break
472                obj = obj.get_parent_object()
473
474              # Check sibling slices
475              for obj in v.get_sibling_slices():
476                if obj.slice_overlap( v ):
477                  if obj in writer_prop and writer_prop[ obj ]:
478                    assert not has_writer
479                    has_writer, writer = True, v
480                    # Shunning: is breaking out of here enough? If we
481                    # don't break the loop, we might a list here storing
482                    # "why the writer became writer" and do some sibling
483                    # overlap checks when we enter the loop body later
484                    break
485
486          except AssertionError:
487            raise MultiWriterError( \
488            "Two-writer conflict \"{}\"{}, \"{}\" in the following net:\n - {}".format(
489              repr(v), "" if not obj else "(as \"{}\" is written somewhere else)".format( repr(obj) ),
490              repr(writer), "\n - ".join([repr(x) for x in net])) )
491
492        if not has_writer:
493          new_headless.append( net )
494          continue
495
496        # Child s.x.y of some propagatable s.x, or sibling of some
497        # propagatable s[a:b].
498        # This means that at least other variables are able to see s.x/s[a:b]
499        # so it doesn't matter if s.x.y is not in writer_prop
500        if writer not in writer_prop:
501          pass
502
503        for v in net:
504          if v != writer:
505            writer_prop[ v ] = True # The reader becomes new writer
506
507            obj = v.get_parent_object()
508            while obj.is_signal():
509              if obj not in writer_prop:
510                writer_prop[ obj ] = False
511              obj = obj.get_parent_object()
512
513        headed.append( (writer, net) )
514
515      if wcount == len(writer_prop): # no more new writers
516        break
517      headless = new_headless
518
519    return headed + [ (None, x) for x in headless ]
520
521  def _check_port_in_nets( s ):
522    nets = s._dsl.all_value_nets
523
524    # The case of connection is very tricky because we put a single upblk
525    # in the lowest common ancestor node and the "output port" chain is
526    # inverted. So we need to deal with it here ...
527    #
528    # The gist is that the data flows from deeper level writer to upper
529    # level readers via output port, to the same level via wire, and from
530    # upper level to deeper level via input port
531
532    headless = [ signals for writer, signals in nets if writer is None ] # remove None
533    if headless:
534      raise NoWriterError( headless )
535
536    for writer, _ in nets:
537
538      # We need to do DFS to check all connected port types
539      # Each node is a writer when we expand it to other nodes
540
541      S = [ writer ]
542      visited = { writer }
543
544      while S:
545        u = S.pop() # u is the writer
546        whost = u.get_host_component()
547
548        for v in s._dsl.all_adjacency[u]: # v is the reader
549          if v not in visited:
550            visited.add( v )
551            S.append( v )
552            rhost = v.get_host_component()
553
554            # 1. have the same host: writer_host(x)/reader_host(x):
555            # Hence, writer is anything, reader is wire or outport
556            if   whost == rhost:
557              valid = isinstance( u, (Signal, Const) ) and \
558                      isinstance( v, (OutPort, Wire) )
559              if not valid:
560                # Check if it's an outport driving inport. If it is
561                # connected at parent level, we permit this loopback from
562                # parent level.
563                if isinstance( u, OutPort ) and isinstance( v, InPort ):
564                  u_connected_in_whost = v in whost._dsl.adjacency and u in whost._dsl.adjacency[v]
565                  v_connected_in_whost = u in whost._dsl.adjacency and v in whost._dsl.adjacency[u]
566                  assert u_connected_in_whost == v_connected_in_whost, "Please contact pymtl3 developers."
567
568                  parent = whost.get_parent_object()
569                  u_connected_in_parent = v in parent._dsl.adjacency and u in parent._dsl.adjacency[v]
570                  v_connected_in_parent = u in parent._dsl.adjacency and v in parent._dsl.adjacency[u]
571                  assert u_connected_in_parent == v_connected_in_parent, "Please contact pymtl3 developers."
572
573                  assert u_connected_in_whost != u_connected_in_parent, "Please contact pymtl3 developers."
574
575                  # We permit this loopback from parent level. Otherwise
576                  # we throw an error
577                  if not u_connected_in_parent:
578                    raise InvalidConnectionError( \
579"""InPort and OutPort loopback connection is only allowed at parent level:
580
581- Unless the connection is fulfilled in parent "{}",
582  {} "{}" of {} (class {}) cannot be driven by {} "{}" of {} (class {}).
583
584  Note: Looks like the connection is fulfilled in "{}".""" \
585          .format(  parent,
586                    type(v).__name__, repr(v), repr(rhost), type(rhost).__name__,
587                    type(u).__name__, repr(u), repr(whost), type(whost).__name__,
588                    repr(whost) ) )
589
590                else:
591                  raise SignalTypeError( \
592"""[Type 5] Invalid port type detected at the same host component "{}" (class {})
593
594- {} "{}" cannot be driven by {} "{}".
595
596  Note: InPort x.y cannot be driven by x.z""" \
597          .format(  repr(rhost), type(rhost).__name__,
598                    type(v).__name__, repr(v), type(u).__name__, repr(u) ) )
599
600            # 2. reader_host(x) is writer_host(x.y)'s parent:
601            # Hence, writer is outport, reader is wire or outport
602            # writer cannot be constant
603            elif rhost == whost.get_parent_object():
604              valid = isinstance( u, OutPort ) and \
605                      isinstance( v, (OutPort, Wire) )
606
607              if not valid:
608                raise SignalTypeError( \
609"""[Type 6] Invalid port type detected when the driver lies deeper than drivee:
610
611- {} "{}" of {} (class {}) cannot be driven by {} "{}" of {} (class {}).
612
613  Note: InPort x.y cannot be driven by x.z.a""" \
614          .format(  type(v).__name__, repr(v), repr(rhost), type(rhost).__name__,
615                    type(u).__name__, repr(u), repr(whost), type(whost).__name__ ) )
616
617            # 3. writer_host(x) is reader_host(x.y)'s parent:
618            # Hence, writer is inport or wire, reader is inport
619            # writer can be constant
620            elif whost == rhost.get_parent_object():
621              # valid = ( isinstance( u, InPort ) or isinstance( u, Wire ) \
622                                                 # or isinstance( u, Const)) and \
623                         # isinstance( v, InPort )
624
625              # if not valid:
626                # raise SignalTypeError( \
627# """[Type 7] Invalid port type detected when the driver lies shallower than drivee:
628
629# - {} "{}" of {} (class {}) cannot be driven by {} "{}" of {} (class {}).
630
631  # Note: OutPort/Wire x.y.z cannot be driven by x.a""" \
632          # .format(  type(v).__name__, repr(v), repr(rhost), type(rhost).__name__,
633                    # type(u).__name__, repr(u), repr(whost), type(whost).__name__ ) )
634
635            # Shunning 9/12/2017: Actually in this case writer can be outport
636              valid = isinstance( u, (Signal, Const) ) and isinstance( v, InPort )
637
638              if not valid:
639                raise SignalTypeError( \
640"""[Type 7] Invalid port type detected when the driver lies shallower than drivee:
641
642- {} "{}" of {} (class {}) cannot be driven by {} "{}" of {} (class {}).
643
644  Note: OutPort/Wire x.y.z cannot be driven by x.a""" \
645          .format(  type(v).__name__, repr(v), repr(rhost), type(rhost).__name__,
646                    type(u).__name__, repr(u), repr(whost), type(whost).__name__ ) )
647
648            # 4. hosts have the same parent: writer_host(x.y)/reader_host(x.z)
649            # This means that the connection is fulfilled in x
650            # Hence, writer is outport and reader is inport
651            # writer cannot be constant
652            elif whost.get_parent_object() == rhost.get_parent_object():
653              valid = isinstance( u, OutPort ) and isinstance( v, InPort )
654
655              if not valid:
656                raise SignalTypeError( \
657"""[Type 8] Invalid port type detected when the drivers is the sibling of drivee:
658
659- {} "{}" of {} (class {}) cannot be driven by {} "{}" of {} (class {}).
660
661  Note: Looks like the connection is fulfilled in "{}".
662        OutPort/Wire x.y.z cannot be driven by x.a.b""" \
663          .format(  type(v).__name__, repr(v), repr(rhost), type(rhost).__name__,
664                    type(u).__name__, repr(u), repr(whost), type(whost).__name__,
665                    repr(whost.get_parent_object()) ) )
666            # 5. neither host is the other's parent nor the same.
667            else:
668              raise SignalTypeError("""[Type 9] "{}" and "{}" cannot be connected:
669
670- host objects "{}" and "{}" are too far in the hierarchy.""" \
671              .format( repr(u), repr(v), repr(whost), repr(rhost) ) )
672
673  def _disconnect_signal_int( s, o1, o2 ):
674
675    nets = s.get_all_value_nets()
676
677    for i, net in enumerate( nets ):
678      writer, signals = net
679
680      if o1 in signals: # Find the net that contains o1
681        # If we're disconnecting a constant from a port, the constant
682        # should be the only writer in this net and is equal to o2
683        assert isinstance( writer, Const ), "what the hell?"
684        assert writer._dsl.const == o2, "Disconnecting the wrong const {} " \
685                                        "-- should be {}.".format( o2, writer.const )
686        o2 = writer
687
688        # I don't remove it from m._adjacency since they are not used later
689        assert o1 in s._dsl.all_adjacency[o2] and o2 in s._dsl.all_adjacency[o1]
690        s._dsl.all_adjacency[o2].remove( o1 )
691        s._dsl.all_adjacency[o1].remove( o2 )
692
693        # Disconnect a const from a signal just removes the writer in the net
694        signals.remove( writer )
695        nets[i] = ( None, signals )
696        return
697
698  def _disconnect_signal_signal( s, o1, o2 ):
699
700    nets = s.get_all_value_nets()
701
702    assert o1 in s._dsl.all_adjacency[o2] and o2 in s._dsl.all_adjacency[o1]
703    # I don't remove it from m._adjacency since they are not used later
704    s._dsl.all_adjacency[o2].remove( o1 )
705    s._dsl.all_adjacency[o1].remove( o2 )
706
707    for i, net in enumerate( nets ):
708      writer, signals = net
709
710      if o1 in signals: # Find the net that contains o1
711        assert o2 in signals, signals
712
713        broken_nets = s._floodfill_nets( signals, s._dsl.all_adjacency )
714
715        # disconnect the only two vertices in the net
716        if len(broken_nets) == 0:
717          nets[i] = nets.pop() # squeeze in the last net
718
719        # the removed edge results in an isolated vertex and a connected component
720        elif len(broken_nets) == 1:
721          net0 = broken_nets[0]
722          if writer in net0:
723            nets[i] = ( writer, net0 )
724          else:
725            assert writer is o1 or writer is o2
726            nets[i] = ( None, net0 )
727
728        elif len(broken_nets) == 2:
729          net0, net1 = broken_nets[0], broken_nets[1]
730          if writer in net0:
731            nets[i] = ( writer, net0 ) # replace in-place
732            nets.append( (None, net1) )
733          else:
734            assert writer in net1
735            nets[i] = ( None, net0 ) # replace in-place
736            nets.append( (writer, net1) )
737
738        else:
739          assert False, "what the hell?"
740
741        return
742
743  # Override
744  def _check_valid_dsl_code( s ):
745    s._check_upblk_writes()
746    s._check_port_in_upblk()
747    s._check_port_in_nets()
748
749  #-----------------------------------------------------------------------
750  # Construction-time APIs
751  #-----------------------------------------------------------------------
752
753  def __call__( s, *args, **kwargs ):
754    """ This syntactic sugar supports the following one-liner:
755      >>> s.x = SomeReg(Bits1)( in_ = s.in_ )
756    It connects s.in_ to s.x.in_ in the same line as model construction.
757    """
758    raise PyMTLDeprecationError("\n__call__ connection has been deprecated! "
759                                "\n- Please use free function connect(s.x,s.y) or "
760                                "syntactic sugar s.x//=s.y instead.")
761
762  def connect( s, *args, **kwargs ):
763    raise PyMTLDeprecationError("\ns.connect method has been deprecated! "
764                                "\n- Please use free function connect(s.x,s.y) or "
765                                "syntactic sugar s.x//=s.y instead.")
766
767  #-----------------------------------------------------------------------
768  # elaborate
769  #-----------------------------------------------------------------------
770  # Since the spawned signals are handled by the updated elaborate
771  # template in ComponentLevel2, we just need to add a bit more
772  # functionalities to handle nets.
773
774  # Override
775  def _elaborate_declare_vars( s ):
776    super()._elaborate_declare_vars()
777    s._dsl.all_adjacency = defaultdict(set)
778
779  # Override
780  def _elaborate_collect_all_vars( s ):
781    super()._elaborate_collect_all_vars()
782    s._dsl.all_value_nets = s._resolve_value_connections()
783    s._dsl._has_pending_value_connections = False
784
785    s._check_valid_dsl_code()
786
787  #-----------------------------------------------------------------------
788  # Post-elaborate public APIs (can only be called after elaboration)
789  #-----------------------------------------------------------------------
790  # We have moved these implementations to Component.py because the
791  # outside world should only use Component.py
792
793  def get_all_value_nets( s ):
794
795    if s._dsl._has_pending_value_connections:
796      s._dsl.all_value_nets = s._resolve_value_connections()
797      s._dsl._has_pending_value_connections = False
798
799    return s._dsl.all_value_nets
800