1""" 2======================================================================== 3ComponentLevel2.py 4======================================================================== 5At level two we introduce implicit variable constraints. 6By default we assume combinational semantics: 7- upA reads Wire x while upB writes Wire x ==> upB = WR(x) < RD(x) = upA 8When upA is marked as update_ff ==> for all upblks upX that 9write/read variables in upA, upA < upX: 10- upA = RD(x) < WR(x) = upB and upA = WR(x) < RD(x) = upB 11 12Author : Shunning Jiang 13Date : Apr 16, 2018 14""" 15import ast 16import gc 17import inspect 18import re 19from collections import defaultdict 20 21from pymtl3.datatypes import Bits, is_bitstruct_class 22 23from . import AstHelper 24from .ComponentLevel1 import ComponentLevel1 25from .Connectable import Connectable, Const, InPort, Interface, OutPort, Signal, Wire 26from .ConstraintTypes import RD, WR, U, ValueConstraint 27from .errors import ( 28 InvalidConstraintError, 29 InvalidFuncCallError, 30 InvalidIndexError, 31 InvalidPlaceholderError, 32 MultiWriterError, 33 NotElaboratedError, 34 PyMTLDeprecationError, 35 SignalTypeError, 36 UpblkFuncSameNameError, 37 UpdateBlockWriteError, 38 UpdateFFBlockWriteError, 39 UpdateFFNonTopLevelSignalError, 40 VarNotDeclaredError, 41 WriteNonSignalError, 42) 43from .NamedObject import NamedObject 44from .Placeholder import Placeholder 45 46compiled_re = re.compile('( *(@|def))') 47 48def update_ff( blk ): 49 NamedObject._elaborate_stack[-1]._update_ff( blk ) 50 return blk 51 52class ComponentLevel2( ComponentLevel1 ): 53 54 #----------------------------------------------------------------------- 55 # Private methods 56 #----------------------------------------------------------------------- 57 58 def __new__( cls, *args, **kwargs ): 59 inst = super().__new__( cls, *args, **kwargs ) 60 61 inst._dsl.update_ff = set() 62 63 # constraint[var] = (sign, func) 64 inst._dsl.RD_U_constraints = defaultdict(set) 65 inst._dsl.WR_U_constraints = defaultdict(set) 66 inst._dsl.name_func = {} 67 68 return inst 69 70 def _cache_func_meta( s, func, is_update_ff, given=None ): 71 """ Convention: the source of a function/update block across different 72 instances should be the same. You can construct different functions 73 based on the condition, but please use different names. This not only 74 keeps the caching valid, but also make the code more readable. 75 76 According to the convention, we can cache the information of a 77 function in the *class object* to avoid redundant parsing. """ 78 cls = s.__class__ 79 try: 80 name_info = cls._name_info 81 name_rd = cls._name_rd 82 name_wr = cls._name_wr 83 name_fc = cls._name_fc 84 except Exception: 85 name_info = cls._name_info = {} 86 name_rd = cls._name_rd = {} 87 name_wr = cls._name_wr = {} 88 name_fc = cls._name_fc = {} 89 90 name = func.__name__ 91 92 # Always override lambda 93 if given is not None: 94 _src, _ast, _line, _file = given 95 96 name_info[ name ] = ( True, _src, _line, _file, _ast ) 97 name_rd[ name ] = _rd = [] 98 name_wr[ name ] = _wr = [] 99 name_fc[ name ] = _fc = [] 100 AstHelper.extract_reads_writes_calls( s, func, _ast, _rd, _wr, _fc ) 101 102 elif name not in name_info: 103 _src, _line = inspect.getsourcelines( func ) 104 _src = "".join( _src ) 105 _ast = ast.parse( compiled_re.sub( r'\2', _src ) ) 106 107 name_info[ name ] = (False, _src, _line, inspect.getsourcefile( func ), _ast ) 108 name_rd[ name ] = _rd = [] 109 name_wr[ name ] = _wr = [] 110 name_fc[ name ] = _fc = [] 111 AstHelper.extract_reads_writes_calls( s, func, _ast, _rd, _wr, _fc ) 112 113 def _elaborate_read_write_func( s ): 114 115 # We have parsed AST to extract every read/write variable name. 116 # I refactor the process of materializing objects in this function 117 # Pass in the func as well for error message 118 119 def extract_obj_from_names( func, names, update_ff=False, is_write=False ): 120 121 def expand_array_index( obj, name_depth, node_depth, idx_depth, idx ): 122 """ Find s.x[0][*][2], if index is exhausted, jump back to lookup_variable """ 123 if obj is None: 124 return 125 126 if idx_depth >= len(idx): # exhausted, go to next level of name 127 lookup_variable( obj, name_depth+1, node_depth+1 ) 128 return 129 130 current_idx = idx[ idx_depth ] 131 132 if current_idx == "*": # special case, materialize all objects 133 if isinstance( obj, NamedObject ): # Signal[*] is the signal itself 134 objs.add( obj ) 135 else: 136 for i, child in enumerate( obj ): 137 expand_array_index( child, name_depth, node_depth, idx_depth+1, idx ) 138 139 # Here we try to find the value of free variables in the current 140 # component scope. 141 # tuple: [x] where x is a closure/global variable 142 # slice: [x:y] where x and y are either normal integers or 143 # closure/global variable. 144 else: 145 if isinstance( current_idx, tuple ): 146 is_closure, name = current_idx 147 current_idx = _closure[ name ] if is_closure else _globals[ name ] 148 elif isinstance( current_idx, slice ): 149 start = current_idx.start 150 if isinstance( start, tuple ): 151 is_closure, name = start 152 start = _closure[ name ] if is_closure else _globals[ name ] 153 stop = current_idx.stop 154 if isinstance( stop, tuple ): 155 is_closure, name = stop 156 stop = _closure[ name ] if is_closure else _globals[ name ] 157 current_idx = slice(start, stop) 158 159 try: 160 child = obj[ current_idx ] 161 except TypeError: # cannot convert to integer 162 raise VarNotDeclaredError( obj, current_idx, func, s, nodelist[node_depth].lineno ) 163 except IndexError: 164 return 165 except AssertionError: 166 raise InvalidIndexError( obj, current_idx, func, s, nodelist[node_depth].lineno ) 167 168 expand_array_index( child, name_depth, node_depth, idx_depth+1, idx ) 169 170 def lookup_variable( obj, name_depth, node_depth ): 171 """ Look up the object s.a.b.c in s. Jump to expand_array_index if c[] """ 172 if obj is None: 173 return 174 175 if name_depth >= len(obj_name): # exhausted 176 if isinstance( obj, NamedObject ): 177 objs.add( obj ) 178 elif isinstance( obj, list ) and obj: # Exhaust all the elements in the high-d array 179 Q = [ *obj ] # PEP 448 -- see https://stackoverflow.com/a/43220129/6470797 180 while Q: 181 m = Q.pop() 182 if isinstance( m, NamedObject ): 183 objs.add( m ) 184 elif isinstance( m, list ): 185 Q.extend( m ) 186 return 187 188 # still have names 189 field, idx = obj_name[ name_depth ] 190 try: 191 child = getattr( obj, field ) 192 except AttributeError as e: 193 print(e) 194 raise VarNotDeclaredError( obj, field, func, s, nodelist[node_depth].lineno ) 195 196 if not idx: lookup_variable ( child, name_depth+1, node_depth+1 ) 197 else: expand_array_index( child, name_depth, node_depth+1, 0, idx ) 198 199 """ extract_obj_from_names: 200 Here we enumerate names and use the above functions to turn names 201 into objects """ 202 203 _globals = func.__globals__ 204 205 _closure = {} 206 for i, var in enumerate( func.__code__.co_freevars ): 207 try: _closure[ var ] = func.__closure__[i].cell_contents 208 except ValueError: pass 209 210 all_objs = set() 211 212 # Now we turn names into actual objects 213 for obj_name, nodelist, op in names: 214 if obj_name[0][0] == "s": 215 objs = set() 216 lookup_variable( s, 1, 1 ) 217 218 if not is_write or not objs: 219 all_objs |= objs 220 continue 221 222 # Now we perform write checks 223 224 for obj in objs: # The objects in objs are all NamedObject 225 if not isinstance( obj, Signal ): 226 raise WriteNonSignalError( s, func, nodelist[0].lineno, obj ) 227 all_objs.add( obj ) 228 229 # Check all assignments in update_ff and update 230 # - <<= in update_ff 231 # - @= in update 232 # - = in update/update_ff 233 234 if update_ff: 235 # - signals can only be at LHS of <<= 236 # * only top level signals 237 # - signals cannot be at LHS of @= or = 238 239 if op is None: 240 raise UpdateFFBlockWriteError( s, func, '=', nodelist[0].lineno, 241 "Fix the '=' assignment with '<<='") 242 elif op == 'for': 243 raise UpdateFFBlockWriteError( s, func, op, nodelist[0].lineno, 244 "Fix the loop variable in for-loop assignment") 245 elif not isinstance( op, ast.LShift ): 246 if isinstance( op, ast.MatMult ): 247 raise UpdateFFBlockWriteError( s, func, '@=', nodelist[0].lineno, 248 "Fix the '@=' assignment with '<<='") 249 250 raise UpdateFFBlockWriteError( s, func, op+'=', nodelist[0].lineno, 251 "Fix the signal assignment with '<<='") 252 253 254 for x in objs: 255 if not x.is_top_level_signal(): 256 raise UpdateFFNonTopLevelSignalError( s, func, nodelist[0].lineno ) 257 258 x._dsl.needs_double_buffer = True 259 260 else: # update 261 # - signals can only be at LHS of @= 262 # - signals cannot be at LHS of <<= or = 263 if op is None: 264 raise UpdateBlockWriteError( s, func, '=', nodelist[0].lineno, 265 "Fix the '=' assignment with '@='") 266 elif op == 'for': 267 raise UpdateBlockWriteError( s, func, op, nodelist[0].lineno, 268 "Fix the loop variable in for-loop assignment") 269 elif not isinstance( op, ast.MatMult ): 270 if isinstance( op, ast.LShift ): 271 raise UpdateBlockWriteError( s, func, '<<=', nodelist[0].lineno, 272 "Fix the '<<=' assignment with '@='") 273 raise UpdateBlockWriteError( s, func, op+'=', nodelist[0].lineno, 274 "Fix the signal assignment with '@='") 275 276 # This is a function call without "s." prefix, check func list 277 elif obj_name[0][0] in s._dsl.name_func: 278 call = s._dsl.name_func[ obj_name[0][0] ] 279 all_objs.add( call ) 280 281 return all_objs 282 283 """ elaborate_read_write_func """ 284 285 # Access cached data in this component 286 287 cls = s.__class__ 288 try: 289 name_rd, name_wr, name_fc = cls._name_rd, cls._name_wr, cls._name_fc 290 except AttributeError: # This component doesn't have update block 291 pass 292 293 # what object each astnode corresponds to. You can't have two update 294 # blocks in one component that have the same ast. 295 s._dsl.func_reads = {} 296 s._dsl.func_writes = {} 297 s._dsl.func_calls = {} 298 for name, func in s._dsl.name_func.items(): 299 s._dsl.func_reads [ func ] = extract_obj_from_names( func, name_rd[ name ] ) 300 s._dsl.func_writes[ func ] = extract_obj_from_names( func, name_wr[ name ] ) 301 s._dsl.func_calls [ func ] = extract_obj_from_names( func, name_fc[ name ] ) 302 303 s._dsl.upblk_reads = {} 304 s._dsl.upblk_writes = {} 305 s._dsl.upblk_calls = {} 306 for name, blk in s._dsl.name_upblk.items(): 307 s._dsl.upblk_reads [ blk ] = extract_obj_from_names( blk, name_rd[ name ] ) 308 s._dsl.upblk_writes[ blk ] = extract_obj_from_names( blk, name_wr[ name ], 309 update_ff = blk in s._dsl.update_ff, is_write=True ) 310 s._dsl.upblk_calls [ blk ] = extract_obj_from_names( blk, name_fc[ name ] ) 311 312 # Override 313 def _collect_vars( s, m ): 314 super()._collect_vars( m ) 315 316 if isinstance( m, ComponentLevel2 ): 317 s._dsl.all_update_ff |= m._dsl.update_ff 318 319 for k, k_cons in m._dsl.RD_U_constraints.items(): 320 s._dsl.all_RD_U_constraints[k] |= k_cons 321 322 for k, k_cons in m._dsl.WR_U_constraints.items(): 323 s._dsl.all_WR_U_constraints[k] |= k_cons 324 325 # I assume different update blocks will always have different ids 326 s._dsl.all_upblk_reads.update( m._dsl.upblk_reads ) 327 s._dsl.all_upblk_writes.update( m._dsl.upblk_writes ) 328 329 for blk, calls in m._dsl.upblk_calls.items(): 330 s._dsl.all_upblk_calls[ blk ] = calls 331 332 for call in calls: 333 334 # Expand function calls. E.g. upA calls fx, fx calls fy and fz 335 # This is invalid: fx calls fy but fy also calls fx 336 # To detect this, we need to use dfs and see if the current node 337 # has an edge to a previously marked ancestor 338 339 def dfs( u, stk ): 340 if u not in m._dsl.func_reads: 341 return 342 343 # Add all read/write of funcs to the outermost upblk 344 s._dsl.all_upblk_reads [ blk ] |= m._dsl.func_reads[u] 345 s._dsl.all_upblk_writes[ blk ] |= m._dsl.func_writes[u] 346 347 for v in m._dsl.func_calls[ u ]: 348 if v in caller: # v calls someone else there is a cycle 349 raise InvalidFuncCallError( \ 350 "In class {}\nThe full call hierarchy:\n - {}{}\nThese function calls form a cycle:\n {}\n{}".format( 351 type(m).__name__, # function's hostobj must be m 352 "\n - ".join( [ "{} calls {}".format( caller[x][0].__name__, x.__name__ ) 353 for x in stk ] ), 354 "\n - {} calls {}".format( u.__name__, v.__name__ ), 355 "\n ".join( [ ">>> {} calls {}".format( caller[x][0].__name__, x.__name__) 356 for x in stk[caller[v][1]+1: ] ] ), 357 " >>> {} calls {}".format( u.__name__, v.__name__ ) ) ) 358 359 caller[ v ] = ( u, len(stk) ) 360 stk.append( v ) 361 dfs( v, stk ) 362 del caller[ v ] 363 stk.pop() 364 365 # callee's id: (func, the caller's idx in stk) 366 caller = { call: ( blk, 0 ) } 367 stk = [ call ] # for error message 368 dfs( call, stk ) 369 370 def _uncollect_vars( s, m ): 371 super()._uncollect_vars( m ) 372 373 if isinstance( m, ComponentLevel2 ): 374 s._dsl.all_update_ff -= m._dsl.update_ff 375 376 for k in m._dsl.RD_U_constraints: 377 s._dsl.all_RD_U_constraints[k] -= m._dsl.RD_U_constraints[k] 378 for k in m._dsl.WR_U_constraints: 379 s._dsl.all_WR_U_constraints[k] -= m._dsl.RD_U_constraints[k] 380 381 for k in m._dsl.upblks: 382 del s._dsl.all_upblk_reads[k] 383 del s._dsl.all_upblk_writes[k] 384 del s._dsl.all_upblk_calls[k] 385 386 def _check_upblk_writes( s ): 387 388 write_upblks = defaultdict(set) 389 for blk, writes in s._dsl.all_upblk_writes.items(): 390 for wr in writes: 391 write_upblks[ wr ].add( blk ) 392 393 for obj, wr_blks in write_upblks.items(): 394 wr_blks = list(wr_blks) 395 396 if len(wr_blks) > 1: 397 raise MultiWriterError( \ 398 "Multiple update blocks write {}.\n - {}".format( repr(obj), 399 "\n - ".join([ x.__name__+" at "+repr(s._dsl.all_upblk_hostobj[x]) \ 400 for x in wr_blks ]) ) ) 401 402 # See VarConstraintPass.py for full information 403 # 1) WR A.b.b.b, A.b.b, A.b, A (detect 2-writer conflict) 404 405 x = obj 406 while x.is_signal(): 407 if x is not obj and x in write_upblks: 408 wrx_blks = list(write_upblks[x]) 409 410 if wrx_blks[0] != wr_blks[0]: 411 raise MultiWriterError( \ 412 "Two-writer conflict in nested struct/slice. \n - {} (in {})\n - {} (in {})".format( 413 repr(x), wrx_blks[0].__name__, 414 repr(obj), wr_blks[0].__name__ ) ) 415 x = x.get_parent_object() 416 417 # 4) WR A.b[1:10], A.b[0:5], A.b[6] (detect 2-writer conflict) 418 419 for x in obj.get_sibling_slices(): 420 # Recognize overlapped slices 421 if x.slice_overlap( obj ) and x in write_upblks: 422 wrx_blks = list(write_upblks[x]) 423 raise MultiWriterError( \ 424 "Two-writer conflict between sibling slices. \n - {} (in {})\n - {} (in {})".format( 425 repr(x), wrx_blks[0].__name__, 426 repr(obj), wr_blks[0].__name__ ) ) 427 428 def _check_port_in_upblk( s ): 429 430 # Check read first 431 for blk, reads in s._dsl.all_upblk_reads.items(): 432 433 blk_hostobj = s._dsl.all_upblk_hostobj[ blk ] 434 435 for obj in reads: 436 host = obj 437 while not isinstance( host, ComponentLevel2 ): 438 host = host.get_parent_object() # go to the component 439 440 if isinstance( obj, (InPort, OutPort) ): pass 441 elif isinstance( obj, Wire ): 442 if blk_hostobj != host: 443 raise SignalTypeError("""[Type 1] Invalid read to Wire: 444 445- Wire "{}" of {} (class {}) is read in update block 446 "{}" of {} (class {}). 447 448 Note: Please only read Wire "x.wire" in x's update block. 449 (Or did you intend to declare it as an OutPort?)""" \ 450 .format( repr(obj), repr(host), type(host).__name__, 451 blk.__name__, repr(blk_hostobj), type(blk_hostobj).__name__ ) ) 452 453 # Then check write 454 for blk, writes in s._dsl.all_upblk_writes.items(): 455 456 blk_hostobj = s._dsl.all_upblk_hostobj[ blk ] 457 458 for obj in writes: 459 host = obj 460 while not isinstance( host, ComponentLevel2 ): 461 host = host.get_parent_object() # go to the component 462 463 # A continuous assignment is implied when a variable is connected to 464 # an input port declaration. This makes assignments to a variable 465 # declared as an input port illegal. -- IEEE 466 467 if isinstance( obj, InPort ): 468 if host.get_parent_object() != blk_hostobj: 469 raise SignalTypeError("""[Type 2] Invalid write to an input port: 470 471- InPort "{}" of {} (class {}) is written in update block 472 "{}" of {} (class {}). 473 474 Note: Please only write to children's InPort "x.y.in", not "x.in", in x's update block.""" \ 475 .format( repr(obj), repr(host), type(host).__name__, 476 blk.__name__, repr(host), type(host).__name__ ) ) 477 478 # A continuous assignment is implied when a variable is connected to 479 # the output port of an instance. This makes procedural or 480 # continuous assignments to a variable connected to the output port 481 # of an instance illegal. -- IEEE 482 483 elif isinstance( obj, OutPort ): 484 if blk_hostobj != host: 485 raise SignalTypeError("""[Type 3] Invalid write to output port: 486 487- OutPort \"{}\" of {} (class {}) is written in update block 488 \"{}\" of {} (class {}). 489 490 Note: Please only write to OutPort "x.out", not "x.y.out", in x's update block.""" \ 491 .format( repr(obj), repr(host), type(host).__name__, 492 blk.__name__, repr(blk_hostobj), type(blk_hostobj).__name__, ) ) 493 494 # The case of wire is special. We only allow Wire to be written in 495 # the same object. One cannot write this from outside 496 497 elif isinstance( obj, Wire ): 498 if blk_hostobj != host: 499 raise SignalTypeError("""[Type 4] Invalid write to Wire: 500 501- Wire "{}" of {} (class {}) is written in update block 502 "{}" of {} (class {}). 503 504 Note: Please only write to Wire "x.wire" in x's update block. 505 (Or did you intend to declare it as an InPort?)""" \ 506 .format( repr(obj), repr(host), type(host).__name__, 507 blk.__name__, repr(blk_hostobj), type(blk_hostobj).__name__ ) ) 508 509 # TODO rename 510 def _check_valid_dsl_code( s ): 511 s._check_upblk_writes() 512 s._check_port_in_upblk() 513 514 #----------------------------------------------------------------------- 515 # Construction-time APIs 516 #----------------------------------------------------------------------- 517 518 def func( s, func ): # @s.func is for those functions 519 if isinstance( s, Placeholder ): 520 raise InvalidPlaceholderError( "Cannot define function {}" 521 "in a placeholder component.".format( func.__name__ ) ) 522 name = func.__name__ 523 if name in s._dsl.name_func or name in s._dsl.name_upblk: 524 raise UpblkFuncSameNameError( name ) 525 526 s._dsl.name_func[ name ] = func 527 s._cache_func_meta( func, is_update_ff=False ) 528 return func 529 530 # Override 531 def _update( s, blk ): 532 super()._update( blk ) 533 534 s._cache_func_meta( blk, is_update_ff=False ) # add caching of src/ast 535 536 def update_on_edge( s, blk ): 537 raise PyMTLDeprecationError("\ns.update_on_edge decorator has been deprecated! " 538 "\n- Please use @update_ff instead.") 539 540 def _update_ff( s, blk ): 541 super()._update( blk ) 542 543 s._dsl.update_ff.add( blk ) 544 s._cache_func_meta( blk, is_update_ff=True ) # add caching of src/ast 545 546 # Override 547 def add_constraints( s, *args ): # add RD-U/WR-U constraints 548 if isinstance( s, Placeholder ): 549 raise InvalidPlaceholderError( "Cannot define constraints " 550 "in a placeholder component." ) 551 552 for (x0, x1, is_equal) in args: 553 554 if isinstance( x0, U ) and isinstance( x1, U ): # U & U, same 555 assert is_equal == False 556 assert (x0.func, x1.func) not in s._dsl.U_U_constraints, \ 557 "Duplicated constraint" 558 s._dsl.U_U_constraints.add( (x0.func, x1.func) ) 559 560 elif isinstance( x0, ValueConstraint ) and isinstance( x1, ValueConstraint ): 561 raise InvalidConstraintError 562 563 elif isinstance( x0, ValueConstraint ) or isinstance( x1, ValueConstraint ): 564 assert is_equal == False 565 sign = 1 # RD(x) < U is 1, RD(x) > U is -1 566 if isinstance( x1, ValueConstraint ): 567 sign = -1 568 x0, x1 = x1, x0 # Make sure x0 is RD/WR(...) and x1 is U(...) 569 570 if isinstance( x0, RD ): 571 assert (sign, x1.func) not in s._dsl.RD_U_constraints[ x0.var ], \ 572 "Duplicated constraint" 573 s._dsl.RD_U_constraints[ x0.var ].add( (sign, x1.func) ) 574 else: 575 assert (sign, x1.func ) not in s._dsl.WR_U_constraints[ x0.var ], \ 576 "Duplicated constraint" 577 s._dsl.WR_U_constraints[ x0.var ].add( (sign, x1.func) ) 578 579 #----------------------------------------------------------------------- 580 # elaborate 581 #----------------------------------------------------------------------- 582 583 # Override 584 def _elaborate_declare_vars( s ): 585 super()._elaborate_declare_vars() 586 587 s._dsl.all_update_ff = set() 588 589 s._dsl.all_RD_U_constraints = defaultdict(set) 590 s._dsl.all_WR_U_constraints = defaultdict(set) 591 592 # We don't collect func's metadata 593 # because every func is local to the component 594 s._dsl.all_upblk_reads = {} 595 s._dsl.all_upblk_writes = {} 596 s._dsl.all_upblk_calls = {} 597 598 # Like all_components in level1, although this all_signals is a subset 599 # of all_named_objects in NamedObject class, I still maintain it here 600 # because we want to avoid redundant isinstance check. I'm going to pay the 601 # extra cost of removing from both all_named_objects and all_signals 602 # when I delete a signal 603 s._dsl.all_signals = set() 604 605 # Override 606 def _elaborate_collect_all_vars( s ): 607 for c in s._dsl.all_named_objects: 608 if isinstance( c, Signal ): 609 s._dsl.all_signals.add( c ) 610 elif isinstance( c, ComponentLevel1 ): 611 s._dsl.all_components.add( c ) 612 s._collect_vars( c ) 613 614 # Override 615 def elaborate( s ): 616 # Don't directly use the base class elaborate anymore 617 s._elaborate_construct() 618 619 # First elaborate all functions to spawn more named objects 620 for c in s._collect_all_single( lambda s: isinstance( s, ComponentLevel2 ) ): 621 c._elaborate_read_write_func() 622 623 s._elaborate_collect_all_named_objects() 624 625 s._elaborate_declare_vars() 626 s._elaborate_collect_all_vars() 627 628 s._check_valid_dsl_code() 629 630 #----------------------------------------------------------------------- 631 # Post-elaborate public APIs (can only be called after elaboration) 632 #----------------------------------------------------------------------- 633 # We have moved these implementations to Component.py because the 634 # outside world should only use Component.py 635