def _type_to_ir(self, concrete_type: ConcreteType) -> type_mod.Type: """Converts a concrete type to its corresponding IR representation.""" assert isinstance(concrete_type, ConcreteType), concrete_type logging.vlog(4, 'Converting concrete type to IR: %s', concrete_type) if isinstance(concrete_type, ArrayType): element_type = self._type_to_ir(concrete_type.get_element_type()) element_count = concrete_type.size if not isinstance(element_count, int): raise ValueError( 'Expect array element count to be integer; got {!r}'.format( element_count)) result = self.package.get_array_type(element_count, element_type) logging.vlog( 4, 'Converted type to IR; concrete type: %s ir: %s element_count: %d', concrete_type, result, concrete_type.size) return result elif isinstance(concrete_type, BitsType) or isinstance( concrete_type, EnumType): return self.package.get_bits_type(concrete_type.get_total_bit_count()) else: if not isinstance(concrete_type, TupleType): raise ValueError( 'Expect type to be bits/enum, array, or tuple; got: ' f'{concrete_type} ({concrete_type!r})') members = tuple( self._type_to_ir(m) for m in concrete_type.get_unnamed_members()) return self.package.get_tuple_type(members)
def _bind_names(name_def_tree: ast.NameDefTree, type_: ConcreteType, ctx: DeduceCtx) -> None: """Binds names in name_def_tree to corresponding type given in type_.""" if name_def_tree.is_leaf(): name_def = name_def_tree.get_leaf() ctx.type_info[name_def] = type_ return if not isinstance(type_, TupleType): raise XlsTypeError( name_def_tree.span, type_, rhs_type=None, suffix='Expected a tuple type for these names, but got {}.'.format( type_)) if len(name_def_tree.tree) != type_.get_tuple_length(): raise TypeInferenceError( name_def_tree.span, type_, 'Could not bind names, names are mismatched in number vs type; at ' 'this level of the tuple: {} names, {} types.'.format( len(name_def_tree.tree), type_.get_tuple_length())) for subtree, subtype in zip(name_def_tree.tree, type_.get_unnamed_members()): ctx.type_info[subtree] = subtype _bind_names(subtree, subtype, ctx)
def _generate_unbiased_argument(concrete_type: ConcreteType, rng: Random) -> Value: if isinstance(concrete_type, BitsType): bit_count = concrete_type.get_total_bit_count() return _generate_bit_value(bit_count, rng, concrete_type.get_signedness()) else: raise NotImplementedError( 'Generate argument for type: {}'.format(concrete_type))
def _symbolic_bind_array(self, param_type: ConcreteType, arg_type: ConcreteType): """Binds any parametric symbols in the "array" param_type.""" assert isinstance(param_type, ArrayType) and isinstance( arg_type, ArrayType) self._symbolic_bind(param_type.get_element_type(), arg_type.get_element_type()) self._symbolic_bind_dims(param_type, arg_type)
def _symbolic_bind_tuple(self, param_type: ConcreteType, arg_type: ConcreteType): """Binds any parametric symbols in the "tuple" param_type.""" assert isinstance(param_type, TupleType) and isinstance( arg_type, TupleType) for param_member, arg_member in zip(param_type.get_unnamed_members(), arg_type.get_unnamed_members()): self._symbolic_bind(param_member, arg_member)
def _cast_to_array(self, node: ast.Cast, output_type: ConcreteType) -> None: bits = self._use(node.expr) slices = [] element_bit_count = output_type.get_element_type().get_total_bit_count() # pytype: disable=attribute-error # MSb becomes lowest-indexed array element. for i in range(0, output_type.get_total_bit_count(), element_bit_count): slices.append(self.fb.add_bit_slice(bits, i, element_bit_count)) slices.reverse() element_type = self.package.get_bits_type(element_bit_count) self._def(node, self.fb.add_array, slices, element_type)
def _symbolic_bind_function(self, param_type: ConcreteType, arg_type: ConcreteType): """Binds any parametric symbols in the "function" param_type.""" assert isinstance(param_type, FunctionType) and isinstance( arg_type, FunctionType) for param_param, arg_param in zip(param_type.get_function_params(), arg_type.get_function_params()): self._symbolic_bind(param_param, arg_param) self._symbolic_bind(param_type.get_function_return_type(), arg_type.get_function_return_type())
def concrete_type_to_annotation( concrete_type: concrete_type_mod.ConcreteType) -> ast.TypeAnnotation: if isinstance(concrete_type, concrete_type_mod.BitsType): keyword = SN_KEYWORD if concrete_type.get_signedness() else UN_KEYWORD num_tok = scanner.Token(scanner.TokenKind.NUMBER, FAKE_SPAN, concrete_type.get_total_bit_count()) return ast.make_builtin_type_annotation( FAKE_SPAN, keyword, dims=(ast.Number(num_tok),)) raise NotImplementedError(concrete_type)
def concrete_type_accepts_value(type_: ConcreteType, value: Value) -> bool: """Returns whether 'value' conforms to this concrete type.""" if value.tag == Tag.UBITS: return (isinstance(type_, BitsType) and not type_.signed and value.bits_payload.bit_count == type_.get_total_bit_count()) if value.tag == Tag.SBITS: return (isinstance(type_, BitsType) and type_.signed and value.bits_payload.bit_count == type_.get_total_bit_count()) if value.tag in (Tag.ARRAY, Tag.TUPLE, Tag.ENUM): return _value_compatible_with_type(type_, value) raise NotImplementedError(type_, value)
def check_bitwidth(self, concrete_type: ConcreteType) -> None: # TODO(leary): 2019-04-19 Because we punt on typechecking symbolic concrete # types here we'll need to do another pass to check whether numerical values # fit once the parametric symbols are instantiated as integers. if (isinstance(concrete_type, BitsType) and isinstance(concrete_type.get_total_bit_count(), int) and not bit_helpers.fits_in_bits(self.get_value_as_int(), concrete_type.get_total_bit_count())): msg = 'value {!r} does not fit in the bitwidth of a {} ({})'.format( self.value, concrete_type, concrete_type.get_total_bit_count()) raise TypeInferenceError(span=self.span, type_=concrete_type, suffix=msg)
def _visit_matcher(self, matcher: ast.NameDefTree, index: Tuple[int, ...], matched_value: BValue, matched_type: ConcreteType) -> BValue: if matcher.is_leaf(): leaf = matcher.get_leaf() if isinstance(leaf, ast.WildcardPattern): return self._def(matcher, self.fb.add_literal_bits, bits_mod.UBits(1, 1)) elif isinstance(leaf, (ast.Number, ast.EnumRef)): leaf.accept(self) return self._def(matcher, self.fb.add_eq, self._use(leaf), matched_value) elif isinstance(leaf, ast.NameRef): result = self._def(matcher, self.fb.add_eq, self._use(leaf.name_def), matched_value) self._def_alias(leaf.name_def, to=leaf) return result else: assert isinstance( leaf, ast.NameDef ), 'Expected leaf to be wildcard, number, or name; got: {!r}'.format( leaf) ok = self._def(leaf, self.fb.add_literal_bits, bits_mod.UBits(1, 1)) self.node_to_ir[matcher] = self.node_to_ir[leaf] = matched_value return ok else: ok = self.fb.add_literal_bits(bits_mod.UBits(value=1, bit_count=1)) for i, (element, element_type) in enumerate( zip(matcher.tree, matched_type.get_unnamed_members())): # pytype: disable=attribute-error # Extract the element. member = self.fb.add_tuple_index(matched_value, i) cond = self._visit_matcher(element, index + (i,), member, element_type) ok = self.fb.add_and(ok, cond) return ok
def sign_convert_value(concrete_type: ConcreteType, value: Value) -> Value: """Converts the values to matched the signedness of the concrete type. Converts bits-typed Values contained within the given Value to match the signedness of the ConcreteType. Examples: invocation: sign_convert_value(s8, u8:64) returns: s8:64 invocation: sign_convert_value(s3, u8:7) returns: s3:-1 invocation: sign_convert_value((s8, u8), (u8:42, u8:10)) returns: (s8:42, u8:10) This conversion functionality is required because the Values used in the DSLX may be signed while Values in IR interpretation and Verilog simulation are always unsigned. This function is idempotent. Args: concrete_type: ConcreteType to match. value: Input value. Returns: Sign-converted value. """ if isinstance(concrete_type, concrete_type_mod.TupleType): assert value.is_tuple() assert len(value.tuple_members) == concrete_type.get_tuple_length() return Value.make_tuple( tuple( sign_convert_value(t, a) for t, a in zip( concrete_type.get_unnamed_members(), value.tuple_members))) elif isinstance(concrete_type, concrete_type_mod.ArrayType): assert value.is_array() assert len(value.array_payload.elements) == concrete_type.size return Value.make_array( tuple( sign_convert_value(concrete_type.get_element_type(), v) for v in value.array_payload.elements)) elif concrete_type_mod.is_sbits(concrete_type): return Value.make_sbits(value.get_bit_count(), value.get_bits_value()) else: assert concrete_type_mod.is_ubits(concrete_type) return value
def _instantiate_one_arg(self, i: int, param_type: ConcreteType, arg_type: ConcreteType) -> ConcreteType: """Binds param_type via arg_type, updating symbolic bindings.""" assert isinstance(param_type, ConcreteType), repr(param_type) assert isinstance(arg_type, ConcreteType), repr(arg_type) # Check parameter and arg types are the same kind. if type(param_type) != type(arg_type): # pylint: disable=unidiomatic-typecheck raise XlsTypeError( self.span, param_type, arg_type, suffix='Parameter {} and argument types are different kinds ' '({} vs {}).'.format(i, param_type.get_debug_type_name(), arg_type.get_debug_type_name())) logging.vlog( 3, 'Symbolically binding param_type %d %s against arg_type %s', i, param_type, arg_type) self._symbolic_bind(param_type, arg_type) resolved = self._resolve(param_type) logging.vlog(3, 'Resolved param_type: %s', resolved) return resolved
def _resolve(self, annotated: ConcreteType) -> ConcreteType: """Resolves a parametric type via symbolic_bindings.""" if self.constraints: self._verify_constraints() def resolver(dim): if isinstance(dim, parametric_expression.ParametricExpression): return dim.evaluate(self.symbolic_bindings) return dim return annotated.map_size(resolver)
def generate_argument(arg_type: ConcreteType, rng: Random, prior: Sequence[Value]) -> Value: """Generates an argument value of the same type as the concrete type.""" if isinstance(arg_type, TupleType): return Value.make_tuple( tuple( generate_argument(t, rng, prior) for t in arg_type.get_unnamed_members())) elif isinstance(arg_type, ArrayType): return Value.make_array( tuple( generate_argument(arg_type.get_element_type(), rng, prior) for _ in range(arg_type.size))) else: assert isinstance(arg_type, BitsType) if not prior or rng.random() < 0.5: return _generate_unbiased_argument(arg_type, rng) to_mutate = rng.choice(prior) bit_count = arg_type.get_total_bit_count() if bit_count > to_mutate.get_bit_count(): to_mutate = to_mutate.bits_payload.concat( _generate_bit_value(bit_count - to_mutate.get_bit_count(), rng, signed=False).bits_payload) else: to_mutate = to_mutate.bits_payload.slice(0, bit_count, lsb_is_0=False) assert to_mutate.bit_count == bit_count value = to_mutate.value mutation_count = randrange_biased_towards_zero(bit_count, rng) for _ in range(mutation_count): # Pick a random bit and flip it. bitno = rng.randrange(bit_count) value ^= 1 << bitno signed = arg_type.get_signedness() constructor = Value.make_sbits if signed else Value.make_ubits return constructor(value=value, bit_count=bit_count)
def _unify_NameDefTree(self: ast.NameDefTree, type_: ConcreteType, ctx: DeduceCtx) -> None: """Unifies the NameDefTree AST node with the observed RHS type type_.""" resolved_rhs_type = resolve(type_, ctx) if self.is_leaf(): leaf = self.get_leaf() if isinstance(leaf, ast.NameDef): ctx.type_info[leaf] = resolved_rhs_type elif isinstance(leaf, ast.WildcardPattern): pass elif isinstance(leaf, (ast.Number, ast.EnumRef)): resolved_leaf_type = resolve(deduce(leaf, ctx), ctx) if resolved_leaf_type != resolved_rhs_type: raise TypeInferenceError( span=self.span, type_=resolved_rhs_type, suffix= 'Conflicting types; pattern expects {} but got {} from value' .format(resolved_rhs_type, resolved_leaf_type)) else: assert isinstance(leaf, ast.NameRef), repr(leaf) ref_type = ctx.type_info[leaf.name_def] resolved_ref_type = resolve(ref_type, ctx) if resolved_ref_type != resolved_rhs_type: raise TypeInferenceError( span=self.span, type_=resolved_rhs_type, suffix= 'Conflicting types; pattern expects {} but got {} from reference' .format(resolved_rhs_type, resolved_ref_type)) else: assert isinstance(self.tree, tuple) if isinstance(type_, TupleType) and type_.get_tuple_length() == len( self.tree): for subtype, subtree in zip(type_.get_unnamed_members(), self.tree): _unify(subtree, subtype, ctx)
def _value_compatible_with_type(type_: ConcreteType, value: Value) -> bool: """Returns whether value is compatible with type_ (recursively).""" assert isinstance(value, Value), value if isinstance(type_, TupleType) and value.is_tuple(): return all( _value_compatible_with_type(ct, m) for ct, m in zip(type_.get_unnamed_members(), value.tuple_members)) if isinstance(type_, ArrayType) and value.is_array(): et = type_.get_element_type() return all( _value_compatible_with_type(et, m) for m in value.array_payload.elements) if isinstance(type_, EnumType) and value.tag == Tag.ENUM: return type_.nominal_type == value.type_ if isinstance(type_, BitsType) and not type_.signed and value.tag == Tag.UBITS: return value.bits_payload.bit_count == type_.get_total_bit_count() if isinstance(type_, BitsType) and type_.signed and value.tag == Tag.SBITS: return value.bits_payload.bit_count == type_.get_total_bit_count() if value.tag == Tag.ENUM and isinstance(type_, BitsType): return (value.type_.get_signedness() == type_.get_signedness() and value.bits_payload.bit_count == type_.get_total_bit_count()) if value.tag == Tag.ARRAY and is_ubits(type_): flat_bit_count = value.array_payload.flatten().bits_payload.bit_count return flat_bit_count == type_.get_total_bit_count() if isinstance(type_, EnumType) and value.is_bits(): return (type_.get_signedness() == (value.tag == Tag.SBITS) and type_.get_total_bit_count() == value.get_bit_count()) raise NotImplementedError(type_, value)
def _validate_struct_members_subset( members: ast.StructInstanceMembers, struct_type: ConcreteType, struct_text: str, ctx: DeduceCtx ) -> Tuple[Set[str], Tuple[ConcreteType], Tuple[ConcreteType]]: """Validates a struct instantiation is a subset of members with no dups. Args: members: Sequence of members used in instantiation. Note this may be a subset; e.g. in the case of splat instantiation. struct_type: The deduced type for the struct (instantiation). struct_text: Display name to use for the struct in case of an error. ctx: Wrapper containing node to type mapping context. Returns: A tuple containing the set of struct member names that were instantiated, the ConcreteTypes of the provided arguments, and the ConcreteTypes of the corresponding struct member definition. """ seen_names = set() arg_types = [] member_types = [] for k, v in members: if k in seen_names: raise TypeInferenceError( v.span, type_=None, suffix= 'Duplicate value seen for {!r} in this {!r} struct instance.'. format(k, struct_text)) seen_names.add(k) expr_type = resolve(deduce(v, ctx), ctx) arg_types.append(expr_type) try: member_type = struct_type.get_member_type_by_name(k) # pytype: disable=attribute-error member_types.append(member_type) except KeyError: raise TypeInferenceError( v.span, None, suffix= 'Struct {!r} has no member {!r}, but it was provided by this instance.' .format(struct_text, k)) return seen_names, tuple(arg_types), tuple(member_types)
def resolve(type_: ConcreteType, ctx: DeduceCtx) -> ConcreteType: """Resolves "type_" via provided symbolic bindings. Uses the symbolic bindings of the function we're currently inside of to resolve parametric types. Args: type_: Type to resolve any contained dims for. ctx: Deduction context to use in resolving the dims. Returns: "type_" with dimensions resolved according to bindings in "ctx". """ _, fn_symbolic_bindings = ctx.fn_stack[-1] def resolver(dim): if isinstance(dim, ParametricExpression): return dim.evaluate(fn_symbolic_bindings) return dim return type_.map_size(resolver)
def _concretize_struct_annotation(type_annotation: ast.TypeRefTypeAnnotation, struct: ast.Struct, base_type: ConcreteType) -> ConcreteType: """Returns concretized struct type using the provided bindings. For example, if we have a struct defined as `struct [N: u32, M: u32] Foo`, the default TupleType will be (N, M). If a type annotation provides bindings, (e.g. Foo[A, 16]), we will replace N, M with those values. In the case above, we will return (A, 16) instead. Args: type_annotation: The provided type annotation for this parametric struct. struct: The corresponding struct AST node. base_type: The TupleType of the struct, based only on the struct definition. """ assert len(struct.parametric_bindings) == len(type_annotation.parametrics) defined_to_annotated = {} for defined_parametric, annotated_parametric in zip( struct.parametric_bindings, type_annotation.parametrics): assert isinstance(defined_parametric, ast.ParametricBinding), defined_parametric if isinstance(annotated_parametric, ast.Number): defined_to_annotated[defined_parametric.name.identifier] = \ int(annotated_parametric.value) else: assert isinstance(annotated_parametric, ast.NameRef), repr(annotated_parametric) defined_to_annotated[defined_parametric.name.identifier] = \ ParametricSymbol(annotated_parametric.identifier, annotated_parametric.span) def resolver(dim): if isinstance(dim, ParametricExpression): return dim.evaluate(defined_to_annotated) return dim return base_type.map_size(resolver)
def ir_value_to_interpreter_value(value: ir_value.Value, dslx_type: ConcreteType) -> dslx_value.Value: """Converts an IR Value to an interpreter Value.""" if value.is_bits(): assert isinstance(dslx_type, BitsType), dslx_type ir_bits_val = value.get_bits() if dslx_type.get_signedness(): return dslx_value.Value.make_sbits( ir_bits_val.bit_count(), bits_to_int(ir_bits_val, signed=True)) return dslx_value.Value.make_ubits( ir_bits_val.bit_count(), bits_to_int(ir_bits_val, signed=False)) elif value.is_array(): assert isinstance(dslx_type, ArrayType), dslx_type return dslx_value.Value.make_array( tuple( ir_value_to_interpreter_value(e, dslx_type.element_type) for e in value.get_elements())) else: assert value.is_tuple() assert isinstance(dslx_type, TupleType), dslx_type return dslx_value.Value.make_tuple( tuple( ir_value_to_interpreter_value(e, t) for e, t in zip( value.get_elements(), t.get_unnamed_members())))
def concrete_type_convert_value(type_: ConcreteType, value: Value, span: Span, enum_values: Optional[Tuple[Value, ...]], enum_signed: Optional[bool]) -> Value: """Converts 'value' into a value of this concrete type.""" logging.vlog(3, 'Converting value %s to type %s', value, type_) if value.tag == Tag.UBITS and isinstance(type_, ArrayType): bits_per_element = type_.get_element_type().get_total_bit_count() bits = value.bits_payload def bit_slice_value_at_index(i): return Value( Tag.UBITS, bits.slice(i * bits_per_element, (i + 1) * bits_per_element, lsb_is_0=False)) return Value.make_array( tuple(bit_slice_value_at_index(i) for i in range(type_.size))) if (isinstance(type_, EnumType) and value.tag in (Tag.UBITS, Tag.SBITS, Tag.ENUM) and value.get_bit_count() == type_.get_total_bit_count()): # Check that the bits we're converting from are present in the enum type # we're converting to. for enum_value in enum_values: if value.bits_payload == enum_value.bits_payload: break else: raise FailureError( span, 'Value is not valid for enum {}: {}'.format( type_.nominal_type.identifier, value)) return Value.make_enum(value.bits_payload, type_.nominal_type) if (value.tag == Tag.ENUM and isinstance(type_, BitsType) and type_.get_total_bit_count() == value.get_bit_count()): constructor = Value.make_sbits if type_.signed else Value.make_ubits # pytype: disable=attribute-error bit_count = type_.get_total_bit_count() return constructor(bit_count, value.bits_payload.value) def zero_ext() -> Value: assert isinstance(type_, BitsType) constructor = Value.make_sbits if type_.signed else Value.make_ubits bit_count = type_.get_total_bit_count() return constructor( bit_count, value.get_bits_value() & bit_helpers.to_mask(bit_count)) def sign_ext() -> Value: assert isinstance(type_, BitsType) constructor = Value.make_sbits if type_.signed else Value.make_ubits bit_count = type_.get_total_bit_count() logging.vlog(3, 'Sign extending %s to %s', value, bit_count) return constructor(bit_count, value.bits_payload.sign_ext(bit_count).value) if value.tag == Tag.UBITS: return zero_ext() if value.tag == Tag.SBITS: return sign_ext() if value.tag == Tag.ENUM: assert enum_signed is not None return sign_ext() if enum_signed else zero_ext() # If we're converting an array into bits, flatten the array payload. if value.tag == Tag.ARRAY and isinstance(type_, BitsType): return value.array_payload.flatten() if concrete_type_accepts_value(type_, value): # Vacuous conversion. return value raise FailureError( span, 'Interpreter failure: cannot convert value %s (of type %s) to type %s' % (value, concrete_type_from_value(value), type_))
def _is_acceptable_cast(from_: ConcreteType, to: ConcreteType) -> bool: if {type(from_), type(to)} == {ArrayType, BitsType}: return from_.get_total_bit_count() == to.get_total_bit_count() return True
def _deduce_slice_type(self: ast.Index, ctx: DeduceCtx, lhs_type: ConcreteType) -> ConcreteType: """Deduces the concrete type of an Index AST node with a slice spec.""" index_slice = self.index assert isinstance(index_slice, (ast.Slice, ast.WidthSlice)), index_slice # TODO(leary): 2019-10-28 Only slicing bits types for now, and only with # number ast nodes, generalize to arrays and constant expressions. if not isinstance(lhs_type, BitsType): raise XlsTypeError(self.span, lhs_type, None, 'Value to slice is not of "bits" type.') bit_count = lhs_type.get_total_bit_count() if isinstance(index_slice, ast.WidthSlice): start = index_slice.start if isinstance(start, ast.Number) and start.type_ is None: start_type = lhs_type.to_ubits() resolved_start_type = resolve(start_type, ctx) if not bit_helpers.fits_in_bits( start.get_value_as_int(), resolved_start_type.get_total_bit_count()): raise TypeInferenceError( start.span, resolved_start_type, 'Cannot fit {} in {} bits (inferred from bits to slice).'. format(start.get_value_as_int(), resolved_start_type.get_total_bit_count())) ctx.type_info[start] = start_type else: start_type = deduce(start, ctx) # Check the start is unsigned. if start_type.signed: raise TypeInferenceError( start.span, type_=start_type, suffix='Start index for width-based slice must be unsigned.') width_type = deduce(index_slice.width, ctx) if isinstance(width_type.get_total_bit_count(), int) and isinstance( lhs_type.get_total_bit_count(), int) and width_type.get_total_bit_count( ) > lhs_type.get_total_bit_count(): raise XlsTypeError( start.span, lhs_type, width_type, 'Slice type must have <= original number of bits; attempted slice from {} to {} bits.' .format(lhs_type.get_total_bit_count(), width_type.get_total_bit_count())) # Check the width type is bits-based (no enums, since value could be out # of range of the enum values). if not isinstance(width_type, BitsType): raise TypeInferenceError( self.span, type_=width_type, suffix='Require a bits-based type for width-based slice.') # The width type is the thing returned from the width-slice. return width_type assert isinstance(index_slice, ast.Slice), index_slice limit = index_slice.limit.get_value_as_int() if index_slice.limit else None # PyType has trouble figuring out that start is definitely an Number at this # point. start = index_slice.start assert isinstance(start, (ast.Number, type(None))) start = start.get_value_as_int() if start else None _, fn_symbolic_bindings = ctx.fn_stack[-1] if isinstance(bit_count, ParametricExpression): bit_count = bit_count.evaluate(fn_symbolic_bindings) start, width = bit_helpers.resolve_bit_slice_indices( bit_count, start, limit) key = tuple(fn_symbolic_bindings.items()) ctx.type_info.add_slice_start_width(index_slice, key, (start, width)) return BitsType(signed=False, size=width)