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