Beispiel #1
0
 def visit_Attribute( s, node ):
   if isinstance( node.value.Type, rt.InterfaceView ):
     if not node.value.Type.has_property( node.attr ):
       raise PyMTLTypeError( s.blk, node.ast,
         f'{dtype.get_name()} does not have field {node.attr}!' )
     node.Type = node.value.Type.get_property( node.attr )
   else:
     super().visit_Attribute( node )
 def visit_If( s, node ):
   # Can the type of condition be cast into bool?
   dtype = node.cond.Type.get_dtype()
   if not rdt.Bool()( dtype ):
     raise PyMTLTypeError( s.blk, node.ast,
       'the condition of "if" cannot be converted to bool!'
     )
   node.Type = None
    def visit_Attribute(s, node):
        # Attribute supported at L1: CurCompAttr
        if isinstance(node.value, bir.Base):
            if not node.value.Type.has_property(node.attr):
                raise PyMTLTypeError(
                    s.blk, node.ast,
                    f'type {node.value.Type} does not have attribute {node.attr}!'
                )
            s.accessed.add(node.attr)

        else:
            raise PyMTLTypeError(
                s.blk, node.ast,
                f'non-component attribute {node.attr} of type {node.value.Type} is not supported at L1!'
            )
        # value.attr has the type that is specified by the base
        node.Type = node.value.Type.get_property(node.attr)
 def visit_Base(s, node):
     # Mark this node as having type rt.Component
     # In L1 the `s` top component is the only possible base
     node.Type = s.rtlir_getter.get_rtlir(node.base)
     node._is_explicit = True
     if not isinstance(node.Type, rt.Component):
         raise PyMTLTypeError(s.blk, node.ast,
                              f'{node} is not a rt.Component!')
    def _visit_Assign_single_target(s, node, target, i):
        rhs_type = node.value.Type.get_dtype()
        lhs_type = target.Type.get_dtype()

        # Weak type checking (agreeable types)
        if not lhs_type(rhs_type):
            raise PyMTLTypeError(
                s.blk, node.ast,
                f'Unagreeable types between LHS and RHS (LHS target#{i} of {lhs_type} vs {rhs_type})!'
            )

        # Strong type checking (same type)
        if rhs_type != lhs_type:
            raise PyMTLTypeError(
                s.blk, node.ast,
                f'LHS and RHS of assignment should have the same type (LHS target#{i} of {lhs_type} vs {rhs_type})!'
            )
Beispiel #6
0
 def visit_BoolOp(s, node):
     for value in node.values:
         if not isinstance(value.Type, rt.Signal) or not rdt.Bool()(
                 value.Type.get_dtype()):
             raise PyMTLTypeError(
                 s.blk, node.ast,
                 f"{value} of {value.Type} cannot be cast into bool!")
     node.Type = rt.NetWire(rdt.Bool())
 def visit_Concat(s, node):
     nbits = 0
     for child in node.values:
         if not isinstance(child.Type, rt.Signal):
             raise PyMTLTypeError(s.blk, node.ast,
                                  f'{child} is not a signal!')
         nbits += child.Type.get_dtype().get_length()
     node.Type = rt.NetWire(rdt.Vector(nbits))
 def visit_Compare(s, node):
     l_type = node.left.Type.get_dtype()
     r_type = node.right.Type.get_dtype()
     if l_type != r_type:
         raise PyMTLTypeError(
             s.blk, node.ast,
             f"LHS and RHS of {node.op.__class__.__name__} have different types ({l_type} vs {r_type})!"
         )
     node.Type = rt.NetWire(rdt.Bool())
Beispiel #9
0
    def visit_Assign(s, node):
        # RHS should have the same type as LHS
        rhs_type = node.value.Type.get_dtype()
        lhs_type = node.target.Type.get_dtype()

        # Weak type checking (agreeable types)
        if not lhs_type(rhs_type):
            raise PyMTLTypeError(
                s.blk, node.ast,
                'Unagreeable types {} and {}!'.format(lhs_type, rhs_type))

        # Strong type checking (same type)
        if rhs_type != lhs_type:
            raise PyMTLTypeError( s.blk, node.ast,
              'LHS and RHS of assignment should have the same type ({} vs {})!'. \
                  format( lhs_type, rhs_type ) )

        node.Type = None
    def visit_BinOp(s, node):
        op = node.op
        l_type = node.left.Type.get_dtype()
        r_type = node.right.Type.get_dtype()
        if not (rdt.Vector(1)(l_type) and rdt.Vector(1)(r_type)):
            raise PyMTLTypeError(
                s.blk, node.ast,
                f"both sides of {op.__class__.__name__} should be of vector type!"
            )

        if not isinstance(op, s.BinOp_left_nbits) and l_type != r_type:
            raise PyMTLTypeError(
                s.blk, node.ast,
                f"LHS and RHS of {op.__class__.__name__} should have the same type ({l_type} vs {r_type})!"
            )

        l_nbits = l_type.get_length()
        r_nbits = r_type.get_length()

        # Enforcing Verilog bitwidth inference rules
        res_nbits = 0
        if isinstance(op, s.BinOp_max_nbits):
            res_nbits = max(l_nbits, r_nbits)
        elif isinstance(op, s.BinOp_left_nbits):
            res_nbits = l_nbits
        else:
            raise Exception('RTLIRTypeCheck internal error: unrecognized op!')

        try:
            # Both sides are constant expressions
            l_val = node.left._value
            r_val = node.rigth._value
            node._value = s.eval_const_binop(l_val, op, r_val)
            node.Type = rt.Const(rdt.Vector(res_nbits))
        except AttributeError:
            # Both sides are constant but the value cannot be determined statically
            if isinstance(node.left.Type, rt.Const) and isinstance(
                    node.right.Type, rt.Const):
                node.Type = rt.Const(rdt.Vector(res_nbits), None)

            # Variable
            else:
                node.Type = rt.NetWire(rdt.Vector(res_nbits))
  def visit_Attribute( s, node ):
    """Type check an attribute.

    Since only the ports of a subcomponent can be accessed, no explicit
    cross-hierarchy access detection is needed.
    """
    # Attributes of subcomponent can only access ports
    if isinstance( node.value.Type, rt.Component ) and \
       node.value.Type.get_name() != s.component.__class__.__name__:
      if not node.value.Type.has_property( node.attr ):
        raise PyMTLTypeError( s.blk, node.ast,
          f'rt.Component {node.value.Type.get_name()} does not have attribute {node.attr}!' )
      prop = node.value.Type.get_property( node.attr )
      if not rt._is_of_type( prop, ( rt.Port, rt.InterfaceView ) ):
        raise PyMTLTypeError( s.blk, node.ast,
          f'{node.attr} is not a port of {node.value.Type.get_name()} subcomponent!' )
      node.Type = prop
    else:
      super().visit_Attribute( node )
  def visit_For( s, node ):
    try:
      if node.start._value < 0:
        raise PyMTLTypeError( s.blk, node.ast,
          'the start of for-loop must be non-negative!' )
      if node.end._value < 0:
        raise PyMTLTypeError( s.blk, node.ast,
          'the end of for-loop must be non-negative!' )
    except AttributeError:
      pass

    try:
      step = node.step._value
      if step == 0:
        raise PyMTLTypeError( s.blk, node.ast,
          'the step of for-loop cannot be zero!' )
    except AttributeError:
      raise PyMTLTypeError( s.blk, node.ast,
        'the step of for-loop must be a constant!' )

    if hasattr(node.start, '_value') and hasattr(node.end, '_value') and \
       hasattr(node.step, '_value'):
      lvar_nbits = s._get_nbits_from_value(
          max(range(node.start._value, node.end._value, node.step._value)) )
      s.loopvar_is_explicit[node.var.name] = False
    else:
      lvar_nbits = max([x.Type.get_dtype().get_length() for x in [node.start, node.end, node.step]])
      s.loopvar_is_explicit[node.var.name] = True

    # context_type = rt.NetWire(rdt.Vector(lvar_nbits))
    s.loopvar_nbits[node.var.name] = lvar_nbits
    # s.enforcer.enter(s.blk, context_type, node.start)
    # s.enforcer.enter(s.blk, context_type, node.end)
    # s.enforcer.enter(s.blk, context_type, node.step)

    for stmt in node.body:
      s.visit( stmt )

    del s.loopvar_nbits[node.var.name]

    node.Type = None
    node._is_explicit = True
Beispiel #13
0
 def visit_Attribute(s, node):
     if isinstance(node.value.Type, rt.InterfaceView):
         if not node.value.Type.has_property(node.attr):
             raise PyMTLTypeError(
                 s.blk, node.ast,
                 f'{dtype.get_name()} does not have field {node.attr}!')
         node.Type = node.value.Type.get_property(node.attr)
         # The attribute of an interface is always non-constant
         node._is_explicit = True
     else:
         super().visit_Attribute(node)
    def visit_Slice(s, node):
        lower_val = None if not hasattr(node.lower,
                                        "_value") else node.lower._value
        upper_val = None if not hasattr(node.upper,
                                        "_value") else node.upper._value
        dtype = node.value.Type.get_dtype()

        if not isinstance(dtype, rdt.Vector):
            raise PyMTLTypeError(s.blk, node.ast,
                                 f'cannot perform slicing on type {dtype}!')

        if not lower_val is None and not upper_val is None:
            signal_nbits = dtype.get_length()
            # upper bound must be strictly larger than the lower bound
            if (lower_val >= upper_val):
                raise PyMTLTypeError(
                    s.blk, node.ast,
                    'the upper bound of a slice must be larger than the lower bound!'
                )
            # upper & lower bound should be less than the bit width of the signal
            if not (0 <= lower_val < upper_val <= signal_nbits):
                raise PyMTLTypeError(
                    s.blk, node.ast,
                    'upper/lower bound of slice out of width of signal!')
            node.Type = rt.NetWire(rdt.Vector(int(upper_val - lower_val)))

        else:
            # Try to special case the constant-stride part selection
            try:
                assert isinstance(node.upper, bir.BinOp)
                assert isinstance(node.upper.op, bir.Add)
                nbits = node.upper.right
                slice_size = nbits._value
                assert s.is_same(node.lower, node.upper.left)
                node.Type = rt.NetWire(rdt.Vector(slice_size))
                # Add new fields that might help translation
                node.size = slice_size
                node.base = node.lower
            except Exception:
                raise PyMTLTypeError(s.blk, node.ast,
                                     'slice bounds must be constant!')
    def visit(s, node):
        # not node._is_explicit: is the parent node allowed to re-interpret this
        # node's bitwidth without truncation?
        node_name = node.__class__.__name__
        method = 'visit_' + node_name
        func = getattr(s, method, s.generic_visit)

        # First visit (type check) all child nodes
        for field, value in vars(node).items():
            # Special case For because we use context depedent types
            # for the loop index
            if node_name == 'For' and field == 'body': continue
            if isinstance(value, list):
                for item in value:
                    if isinstance(item, BaseBehavioralRTLIR):
                        s.visit(item)
            elif isinstance(value, BaseBehavioralRTLIR):
                s.visit(value)

        # Then verify that all child nodes have desired types
        try:
            node_vars = vars(node)
            # Check the expected types of child nodes
            for field, target_type, error_msg in s.type_expect[node_name]:
                value = node_vars[field]

                if isinstance(value, list):
                    for v in value:
                        if not isinstance(v.Type, target_type):
                            raise PyMTLTypeError(s.blk, node.ast, error_msg)
                else:
                    if not isinstance(value.Type, target_type):
                        raise PyMTLTypeError(s.blk, node.ast, error_msg)
        except PyMTLTypeError:
            raise
        except Exception:
            # This node does not require type checking on child nodes
            pass

        # Finally call the type check function
        func(node)
 def visit_Truncate(s, node):
     new_nbits = node.nbits
     child_type = node.value.Type
     old_nbits = child_type.get_dtype().get_length()
     if new_nbits > old_nbits:
         raise PyMTLTypeError(
             s.blk, node.ast,
             f'the target bitwidth {new_nbits} is larger than the bitwidth of the operand ({old_nbits})!'
         )
     node.Type = copy.copy(child_type)
     node.Type.dtype = rdt.Vector(new_nbits)
     node._is_explicit = True
Beispiel #17
0
  def visit_FreeVar( s, node ):
    if node.name not in s.freevars:
      s.freevars[ node.name ] = node.obj

    try:
      t = rt.get_rtlir( node.obj )
    except RTLIRConversionError as e:
      raise PyMTLTypeError(s.blk, node.ast,
        f'{node.name} cannot be converted into a valid RTLIR object!' )

    if isinstance( t, rt.Const ) and isinstance( t.get_dtype(), rdt.Vector ):
      node._value = mk_bits( t.get_dtype().get_length() )( node.obj )
    node.Type = t
    def visit_Compare(s, node):
        l_type = node.left.Type.get_dtype()
        r_type = node.right.Type.get_dtype()
        l_explicit, r_explicit = node.left._is_explicit, node.right._is_explicit
        l_nbits, r_nbits = l_type.get_length(), r_type.get_length()

        if l_explicit and r_explicit:
            if l_type != r_type:
                raise PyMTLTypeError(
                    s.blk, node.ast,
                    f"LHS and RHS of {node.op.__class__.__name__} have different types ({l_type} vs {r_type})!"
                )

        elif not l_explicit and not r_explicit:
            if l_nbits >= r_nbits:
                target_nbits = l_nbits
                op = node.right
            else:
                target_nbits = r_nbits
                op = node.left
            context = rt.NetWire(rdt.Vector(target_nbits))
            s.enforcer.enter(s.blk, context, op)

        else:
            context, op, explicit, implicit = node.left.Type, node.right, l_nbits, r_nbits
            if not l_explicit:
                context, op, explicit, implicit = node.right.Type, node.left, r_nbits, l_nbits
            # Check if any implicit truncation happens
            if explicit < implicit:
                raise PyMTLTypeError(
                    s.blk, node.ast,
                    f"The explicitly sized side of comparison has {explicit} bits but "
                    f"the integer literal requires more bits ({implicit}) to hold!"
                )
            s.enforcer.enter(s.blk, context, op)

        node.Type = rt.NetWire(rdt.Bool())
        node._is_explicit = True
Beispiel #19
0
    def visit_Index(s, node):
        idx = getattr(node.idx, '_value', None)
        if isinstance(node.value.Type, rt.Array):
            if idx is not None and not (0 <= idx <
                                        node.value.Type.get_dim_sizes()[0]):
                raise PyMTLTypeError(s.blk, node.ast,
                                     'array index out of range!')
            node.Type = node.value.Type.get_next_dim_type()
            obj = node.value.Type.get_obj()
            if idx is not None and obj is not None:
                if isinstance(node.Type, rt.Array):
                    node.Type.obj = obj[int(idx)]
                else:
                    node._value = obj[int(idx)]

        elif isinstance(node.value.Type, rt.Signal):
            dtype = node.value.Type.get_dtype()
            if node.value.Type.is_packed_indexable():
                if idx is not None and not (0 <= idx < dtype.get_length()):
                    raise PyMTLTypeError(s.blk, node.ast,
                                         'bit selection index out of range!')
                node.Type = node.value.Type.get_next_dim_type()
            elif isinstance(dtype, rdt.Vector):
                if idx is not None and not (0 <= idx < dtype.get_length()):
                    raise PyMTLTypeError(s.blk, node.ast,
                                         'bit selection index out of range!')
                node.Type = rt.NetWire(rdt.Vector(1))
            else:
                raise PyMTLTypeError(
                    s.blk, node.ast,
                    'cannot perform index on {}!'.format(dtype))

        else:
            # Should be unreachable
            raise PyMTLTypeError(
                s.blk, node.ast,
                'cannot perform index on {}!'.format(node.value.Type))
    def visit(s, node):
        node_name = node.__class__.__name__
        method = 'visit_' + node_name
        func = getattr(s, method, s.generic_visit)

        # First visit (type check) all child nodes
        for field, value in vars(node).items():
            if isinstance(value, list):
                for item in value:
                    if isinstance(item, bir.BaseBehavioralRTLIR):
                        s.visit(item)
            elif isinstance(value, bir.BaseBehavioralRTLIR):
                s.visit(value)

        # Then verify that all child nodes have desired types
        try:
            # Check the expected types of child nodes
            for field, type_rule in s.type_expect[node_name].items():
                value = vars(node)[field]
                target_type = type_rule[0]
                exception_msg = type_rule[1]
                if isinstance(value, list):
                    for v in value:
                        if eval('not isinstance( v.Type, target_type )'):
                            raise PyMTLTypeError(s.blk, node.ast,
                                                 exception_msg)
                else:
                    if eval('not isinstance( value.Type, target_type )'):
                        raise PyMTLTypeError(s.blk, node.ast, exception_msg)
        except PyMTLTypeError:
            raise
        except Exception:
            # This node does not require type checking on child nodes
            pass

        # Finally call the type check function
        func(node)
Beispiel #21
0
    def visit_Slice(s, node):
        lower_val = getattr(node.lower, '_value', None)
        upper_val = getattr(node.upper, '_value', None)
        dtype = node.value.Type.get_dtype()

        if not isinstance(dtype, rdt.Vector):
            raise PyMTLTypeError(
                s.blk, node.ast,
                'cannot perform slicing on type {}!'.format(dtype))

        if not lower_val is None and not upper_val is None:
            signal_nbits = dtype.get_length()
            # upper bound must be strictly larger than the lower bound
            if (lower_val >= upper_val):
                raise PyMTLTypeError(
                    s.blk, node.ast,
                    'the upper bound of a slice must be larger than the lower bound!'
                )
            # upper & lower bound should be less than the bit width of the signal
            if not (0 <= lower_val < upper_val <= signal_nbits):
                raise PyMTLTypeError(
                    s.blk, node.ast,
                    'upper/lower bound of slice out of width of signal!')
            node.Type = rt.NetWire(rdt.Vector(int(upper_val - lower_val)))

        else:
            # Try to special case the constant-stride part selection
            try:
                assert isinstance(node.upper, bir.BinOp)
                assert isinstance(node.upper.op, bir.Add)
                nbits = node.upper.right
                assert s.is_same(node.lower, node.upper.left)
                node.Type = rt.NetWire(rdt.Vector(nbits))
            except Exception:
                raise PyMTLTypeError(s.blk, node.ast,
                                     'slice bounds must be constant!')
    def _visit_Assign_single_target(s, node, target, i):
        lhs_type = target.Type.get_dtype()
        rhs_type = node.value.Type.get_dtype()

        # At L1 it's always signal assignment
        is_rhs_reinterpretable = not node.value._is_explicit
        if is_rhs_reinterpretable and ((not lhs_type(rhs_type)) or
                                       (rhs_type != lhs_type)):
            s.enforcer.enter(s.blk, target.Type, node.value)

        rhs_type = node.value.Type.get_dtype()
        # Weak type checking (agreeable types)
        if not lhs_type(rhs_type):
            raise PyMTLTypeError(
                s.blk, node.ast,
                f'Unagreeable types between LHS and RHS (LHS target#{i+1} of {lhs_type} vs {rhs_type})!'
            )

        # Strong type checking (same type)
        if rhs_type != lhs_type:
            raise PyMTLTypeError(
                s.blk, node.ast,
                f'LHS and RHS of assignment should have the same type (LHS target#{i+1} of {lhs_type} vs {rhs_type})!'
            )
    def visit_FreeVar(s, node):
        try:
            t = s.rtlir_getter.get_rtlir(node.obj)
        except RTLIRConversionError as e:
            raise PyMTLTypeError(
                s.blk, node.ast,
                f'{node.name} cannot be converted into a valid RTLIR object!')

        if isinstance(t, rt.Const) and isinstance(t.get_dtype(), rdt.Vector):
            node._value = int(node.obj)
        node.Type = t
        node._is_explicit = not isinstance(node.obj, int)

        if node.name not in s.freevars:
            s.freevars[node.name] = (node.obj, t)
Beispiel #24
0
  def visit_StructInst( s, node ):
    cls = node.struct

    try:
      type_instance = cls()
    except TypeError:
      raise PyMTLTypeError( s.blk, node.ast,
""""\
__init__ of BitStruct {} should take 0 arguments! You can achieve this by
adding default values to the arguments.
""".format( cls.__name__ ) )

    dtype = rdt.get_rtlir_dtype( cls() )
    all_properties = dtype.get_all_properties()

    if len( all_properties ) != len( node.values ):
      raise PyMTLTypeError( s.blk, node.ast,
        "BitStruct {} has {} fields but only {} arguments are given!". \
            format(cls.__name__, len(all_properties), len(node.values)) )

    all_types = zip( node.values, all_properties )
    for idx, ( value, ( name, field ) ) in enumerate( all_types ):
      s.visit( value )
      # Expect each argument to be a signal
      if not isinstance( value.Type, rt.Signal ):
        raise PyMTLTypeError( s.blk, node.ast,
          "argument #{} has type {} but not a signal!". \
              format( idx, value.Type ) )
      v_dtype = value.Type.get_dtype()
      # Expect each argument to have data type which corresponds to the field
      if v_dtype != field:
        raise PyMTLTypeError( s.blk, node.ast,
          "Expected argument#{} ( field {} ) to have type {}, but got {}.". \
              format( idx, name, field, v_dtype ) )

    node.Type = rt.Const( dtype )
  def _visit_Assign_single_target( s, node, target, i ):
    rhs_type = node.value.Type
    lhs_type = target.Type

    if isinstance( target, bir.TmpVar ):
      tmpvar_id = (target.name, target.upblk_name)
      if lhs_type != rt.NoneType() and lhs_type.get_dtype() != rhs_type.get_dtype():
        raise PyMTLTypeError( s.blk, node.ast,
          f'conflicting type {rhs_type} for temporary variable {node.targets[i].name}(LHS target#{i} of {lhs_type})!' )

      # Creating a temporaray variable
      # Reminder that a temporary variable is essentially a wire. So we use
      # rt.Wire here instead of rt.NetWire
      target.Type = rt.Wire( rhs_type.get_dtype() )
      s.tmpvars[ tmpvar_id ] = rt.Wire( rhs_type.get_dtype() )

    else:
      # non-temporary assignment is an L1 thing
      super()._visit_Assign_single_target( node, target, i )
    def visit_Assign(s, node):
        # RHS should have the same type as LHS
        rhs_type = node.value.Type
        lhs_type = node.target.Type

        if isinstance(node.target, bir.TmpVar):
            tmpvar_id = (node.target.name, node.target.upblk_name)
            if lhs_type != rt.NoneType() and lhs_type.get_dtype(
            ) != rhs_type.get_dtype():
                raise PyMTLTypeError(
                    s.blk, node.ast,
                    f'conflicting type {rhs_type} for temporary variable {node.target.name}({lhs_type})!'
                )

            # Creating a temporaray variable
            # Reminder that a temporary variable is essentially a wire. So we use
            # rt.Wire here instead of rt.NetWire
            node.target.Type = rt.Wire(rhs_type.get_dtype())
            s.tmpvars[tmpvar_id] = rt.Wire(rhs_type.get_dtype())
            node.Type = None

        else:
            # non-temporary assignment is an L1 thing
            super().visit_Assign(node)
    def visit_BinOp(s, node):
        op = node.op
        l_type = node.left.Type.get_dtype()
        r_type = node.right.Type.get_dtype()
        l_explicit, r_explicit = node.left._is_explicit, node.right._is_explicit
        if not (rdt.Vector(1)(l_type) and rdt.Vector(1)(r_type)):
            raise PyMTLTypeError(
                s.blk, node.ast,
                f"both sides of {op.__class__.__name__} should be of vector type!"
            )

        l_nbits = l_type.get_length()
        r_nbits = r_type.get_length()

        # Enforcing Verilog bitwidth inference rules
        res_nbits = 0
        if isinstance(op, s.BinOp_max_nbits):
            if (not l_explicit and r_explicit) or (l_explicit
                                                   and not r_explicit):

                context, op, explicit, implicit = node.left.Type, node.right, l_nbits, r_nbits
                if not l_explicit:
                    context, op, explicit, implicit = node.right.Type, node.left, r_nbits, l_nbits
                # Check if any implicit truncation happens
                if explicit < implicit:
                    raise PyMTLTypeError(
                        s.blk, node.ast,
                        f"The explicitly sized side of operation has {explicit} bits but "
                        f"the integer literal requires more bits ({implicit})!"
                    )
                s.enforcer.enter(s.blk, context, op)

            elif not l_explicit and not r_explicit:
                # Both sides are implicit
                if l_nbits >= r_nbits:
                    target_nbits = l_nbits
                    op = node.right
                else:
                    target_nbits = r_nbits
                    op = node.left
                context = rt.NetWire(rdt.Vector(target_nbits))
                s.enforcer.enter(s.blk, context, op)

            else:
                # Both sides are explicit
                if not isinstance(op, s.BinOp_left_nbits) and l_type != r_type:
                    raise PyMTLTypeError(
                        s.blk, node.ast,
                        f"LHS and RHS of {op.__class__.__name__} should have the same type ({l_type} vs {r_type})!"
                    )

            res_nbits = max(l_nbits, r_nbits)
            node._is_explicit = l_explicit or r_explicit

        elif isinstance(op, s.BinOp_left_nbits):
            res_nbits = l_nbits
            node._is_explicit = l_explicit

        else:
            raise Exception('RTLIRTypeCheck internal error: unrecognized op!')

        try:
            # Both sides are constant expressions
            l_val = node.left._value
            r_val = node.right._value
            node._value = s.eval_const_binop(l_val, node.op, r_val)
            node.Type = s.rtlir_getter.get_rtlir(node._value)
            assert isinstance(node.Type, rt.Const)
        except AttributeError:
            # Both sides are constant but the value cannot be determined statically
            if isinstance(node.left.Type, rt.Const) and isinstance(
                    node.right.Type, rt.Const):
                node.Type = rt.Const(rdt.Vector(res_nbits), None)
            # Variable
            else:
                node.Type = rt.NetWire(rdt.Vector(res_nbits))