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