def test_pymtl3_list_interface_views(): a = CaseBits32MsgRdyIfcOnly.DUT() a.elaborate() assert rt.is_rtlir_convertible( a.in_ ) assert rtlir_getter.get_rtlir( a.in_ ) == \ rt.Array([5], rt.InterfaceView('Bits32MsgRdyIfc', {'msg':rt.Port('output', rdt.Vector(32)), 'rdy':rt.Port('input', rdt.Vector(1))}))
def _handle_index_extension(s, node, value, idx, dscp, inclusive=True): expected_nbits = value.Type.get_index_width() idx_nbits = idx.Type.get_dtype().get_length() is_idx_reinterpretable = not idx._is_explicit if not inclusive and hasattr(idx, '_value'): idx_nbits = s._get_nbits_from_value(idx._value - 1) if idx_nbits > expected_nbits: # Either bitwidth do not match or requires implicit truncation raise PyMTLTypeError( s.blk, node.ast, f'expects a {expected_nbits}-bit index but the given {dscp} has more ({idx_nbits}) bits!' ) elif idx_nbits < expected_nbits: if is_idx_reinterpretable: # Implicit zero-extension s.enforcer.enter(s.blk, rt.NetWire(rdt.Vector(expected_nbits)), idx) else: # Bitwidth mismatch raise PyMTLTypeError( s.blk, node.ast, f'expects a {expected_nbits}-bit index but the given {dscp} has {idx_nbits} bits!' ) else: if idx_nbits != idx.Type.get_dtype().get_length(): # If we used a different bitwidth then enforce it s.enforcer.enter(s.blk, rt.NetWire(rdt.Vector(idx_nbits)), idx)
def visit_Attribute(s, node): if isinstance(node.value.Type, rt.Signal): dtype = node.value.Type.get_dtype() if not isinstance(dtype, rdt.Struct): raise PyMTLTypeError( s.blk, node.ast, 'attribute base should be a struct signal!') if not dtype.has_property(node.attr): raise PyMTLTypeError( s.blk, node.ast, f'{dtype.get_name()} does not have field {node.attr}!') dtype = dtype.get_property(node.attr) if isinstance(node.value.Type, rt.Port): rtype = rt.Port(node.value.Type.get_direction(), dtype) elif isinstance(node.value.Type, rt.Wire): rtype = rt.Wire(dtype) elif isinstance(node.value.Type, rt.Const): obj = node.value.Type.get_object() if obj is None: rtype = rt.Const(dtype) else: try: rtype = rt.Const(dtype, getattr(obj, node.attr)) except AttributeError: rtype = rt.Const(dtype) else: raise PyMTLTypeError( s.blk, node.ast, f'unrecognized signal type {node.value.Type}!') node.Type = rtype 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 test_L1_const_numbers(): a = CaseConstBits32AttrComp.DUT() a.elaborate() a.apply(StructuralRTLIRGenL1Pass(gen_connections(a))) ns = a._pass_structural_rtlir_gen assert ns.consts == [('const', rt.Array([5], rt.Const(rdt.Vector(32))), a.const)]
def test_L1_const_numbers(): a = CaseConstBits32AttrComp.DUT() a.elaborate() a.apply(StructuralRTLIRGenL1Pass(gen_connections(a))) consts = a.get_metadata(StructuralRTLIRGenL1Pass.consts) assert consts == [('const', rt.Array([5], rt.Const(rdt.Vector(32))), a.const)]
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
def test_pymtl3_list_consts(): class A(dsl.Component): def construct(s): s.in_ = [Bits32(42) for _ in range(5)] a = A() a.elaborate() assert rt.is_rtlir_convertible(a.in_) assert rt.Array([5], rt.Const(rdt.Vector(32))) == rt.get_rtlir(a.in_)
def __init__( s, attr_base, attr ): base_rtype = attr_base.get_rtype() dtype = base_rtype.get_dtype() if isinstance( base_rtype, rt.Port ): rtype = rt.Port( base_rtype.get_direction(), dtype.get_property( attr ) ) elif isinstance( base_rtype, rt.Wire ): rtype = rt.Wire( dtype.get_property( attr ) ) else: assert False, f"unrecognized signal type {base_rtype} for field selection" super().__init__( attr_base, attr, rtype )
def __init__(s, index_base, index): base_rtype = index_base.get_rtype() dtype = base_rtype.get_dtype() if isinstance(base_rtype, rt.Port): rtype = rt.Port(base_rtype.get_direction(), rdt.Vector(1)) elif isinstance(base_rtype, rt.Wire): rtype = rt.Wire(rdt.Vector(1)) else: assert False, f"unrecognized signal type {base_rtype} for indexing" super().__init__(index_base, index, rtype)
def test_pymtl_list_multi_dimension(): class A(dsl.Component): def construct(s): s.out = [[[dsl.OutPort(Bits32) for _ in range(1)] \ for _ in range(2)] for _ in range(3)] a = A() a.elaborate() assert rt.is_rtlir_convertible(a.out) assert rt.Array([3, 2, 1], rt.Port('output', rdt.Vector(32))) == rt.get_rtlir(a.out)
def test_L1_const_numbers(): class A(dsl.Component): def construct(s): s.const = [Bits32(42) for _ in range(5)] a = A() a.elaborate() a.apply(StructuralRTLIRGenL1Pass(*gen_connections(a))) ns = a._pass_structural_rtlir_gen assert ns.consts == [('const', rt.Array([5], rt.Const(rdt.Vector(32))), a.const)]
def __init__(s, index_base, index): base_rtype = index_base.get_rtype() dtype = base_rtype.get_dtype() if isinstance(base_rtype, rt.Port): rtype = rt.Port(base_rtype.get_direction(), dtype.get_next_dim_type()) elif isinstance(base_rtype, rt.Wire): rtype = rt.Wire(dtype.get_next_dim_type()) else: assert False, \ "unrecognized signal type {} for indexing".format( base_rtype ) super().__init__(index_base, index, rtype)
def __init__( s, slice_base, start, stop ): base_rtype = slice_base.get_rtype() dtype = base_rtype.get_dtype() if isinstance( base_rtype, rt.Port ): rtype = rt.Port( base_rtype.get_direction(), rdt.Vector( stop-start ) ) elif isinstance( base_rtype, rt.Wire ): rtype = rt.Wire( rdt.Vector( stop-start ) ) else: assert False, f"unrecognized signal type {base_rtype} for slicing" super().__init__( rtype ) s.base = slice_base s.slice = ( start, stop )
def test_pymtl3_interface_wire(): class Ifc(dsl.Interface): def construct(s): s.foo = dsl.Wire(Bits32) s.bar = dsl.InPort(Bits32) class A(dsl.Component): def construct(s): s.in_ = Ifc() a = A() a.elaborate() # in_.foo will be silently dropped! rt.get_rtlir(a.in_)
def test_pymtl_Bits_closure_construct(do_test): a = CaseBits32ClosureConstruct.DUT() a.elaborate() a._rtlir_freevar_ref = { 'foo_at_upblk': (a.fvar_ref, rt.Const(rdt.Vector(32), a.fvar_ref)) } do_test(a)
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 if isinstance(node.Type, rt.Const) and isinstance( node.Type.get_dtype(), rdt.Vector): node._is_explicit = node.Type.get_dtype().is_explicit() else: node._is_explicit = True else: super().visit_Attribute(node)
def visit_Index(s, node): idx = None if not hasattr(node.idx, "_value") else node.idx._value 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, f'cannot perform index on {dtype}!') else: # Should be unreachable raise PyMTLTypeError( s.blk, node.ast, f'cannot perform index on {node.value.Type}!')
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 = rt.get_rtlir(node.base) if not isinstance(node.Type, rt.Component): raise PyMTLTypeError(s.blk, node.ast, f'{node} is not a rt.Component!')
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() )
def visit_UnaryOp( s, node ): if isinstance( node.op, bir.Not ): dtype = node.operand.Type.get_dtype() if not rdt.Bool()( dtype ): raise PyMTLTypeError( s.blk, node.ast, 'the operand of "Logic-not" cannot be cast to bool!' ) if dtype.get_length() != 1: raise PyMTLTypeError( s.blk, node.ast, 'the operand of "Logic-not" is not a single bit!' ) node.Type = rt.NetWire( rdt.Bool() ) else: node.Type = node.operand.Type if hasattr( node.operand, '_value' ): opmap = { bir.Invert : '~', bir.Not : 'not', bir.UAdd : '+', bir.USub : '-', } try: op = opmap[node.op.__class__] operand = node.operand._value.uint() node._value = eval(f"Bits32({op}{operand})") except: pass
def visit_StructInst(s, node): cls = node.struct 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, f"BitStruct {cls.__name__} has {len(all_properties)} fields but only {len(node.values)} arguments are given!" ) all_types = zip(node.values, list(all_properties.items())) 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, f"argument #{idx} has type {value.Type} but not a signal!") 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, f"Expected argument#{idx} ( field {name} ) to have type {field}, but got {v_dtype}." ) node.Type = rt.Const(dtype)
def visit_IfExp(s, node): # Can the type of condition be cast into bool? if not rdt.Bool()(node.cond.Type.get_dtype()): raise PyMTLTypeError( s.blk, node.ast, 'the condition of "if-exp" cannot be converted to bool!') # body and orelse must have the same type # if node.body.Type != node.orelse.Type: if not node.body.Type.get_dtype()(node.orelse.Type.get_dtype()): raise PyMTLTypeError( s.blk, node.ast, 'the body and orelse of "if-exp" must have the same type!') lhs_dtype, rhs_dtype = node.body.Type.get_dtype( ), node.orelse.Type.get_dtype() lhs_is_vector = isinstance(lhs_dtype, rdt.Vector) rhs_is_vector = isinstance(rhs_dtype, rdt.Vector) lhs_nbits, rhs_nbits = lhs_dtype.get_length(), rhs_dtype.get_length() # Unify body and orelse if both are rdt.Vector if lhs_is_vector and rhs_is_vector and lhs_nbits != rhs_nbits: is_lhs_inferred = not node.body._is_explicit is_rhs_inferred = not node.orelse._is_explicit # Both sides are explicit if not is_lhs_inferred and not is_rhs_inferred: raise PyMTLTypeError( s.blk, node.ast, f'the body and orelse of "if-exp" have different bitwidth {lhs_nbits} vs {rhs_nbits}!' ) # Both sides are implicit elif is_lhs_inferred and is_rhs_inferred: if lhs_nbits >= rhs_nbits: target_nbits = lhs_nbits op = node.body else: target_nbits = rhs_nbits op = node.orelse context = rt.NetWire(rdt.Vector(target_nbits)) s.enforcer.enter(s.blk, context, op) else: # One side is explicit and the other implicit if is_lhs_inferred: exp_str, imp_str = "or-else", "body" context, op, explicit, implicit = node.orelse.Type, node.body, rhs_nbits, lhs_nbits else: exp_str, imp_str = "body", "or-else" context, op, explicit, implicit = node.body.Type, node.orelse, lhs_nbits, rhs_nbits if explicit < implicit: raise PyMTLTypeError( s.blk, node.ast, f"The {exp_str} side of if-exp has {explicit} bits but " f"the {imp_str} side requires more bits ({implicit})!") s.enforcer.enter(s.blk, context, op) node.Type = node.body.Type node._is_explicit = node.body._is_explicit or node.orelse._is_explicit
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_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 test_pymtl_struct_closure(do_test): a = CaseStructClosureGlobal.DUT() a.elaborate() a._rtlir_freevar_ref = { 'foo_at_upblk': (a._foo, rt.Port("input", rdt.Struct("Bits32Foo", {"foo": rdt.Vector(32)}))) } do_test(a)
def visit_TmpVar(s, node): tmpvar_id = (node.name, node.upblk_name) if tmpvar_id not in s.tmpvars: # This tmpvar is being created. Later when it is used, its type can # be read from the tmpvar type environment. node.Type = rt.NoneType() else: node.Type = s.tmpvars[tmpvar_id]
def test_pymtl_list_components(): a = CaseBits32InOutx5CompOnly.DUT() a.elaborate() assert rt.is_rtlir_convertible(a.b) assert rt.get_rtlir( a.b ) == \ rt.Array([5], rt.Component( a.b[0], { 'clk':rt.Port('input', rdt.Vector(1)), 'reset':rt.Port('input', rdt.Vector(1)), 'in_':rt.Port('input', rdt.Vector(32)), 'out':rt.Port('output', rdt.Vector(32)), }))
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_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 )