Chapter 3. Type System Changes

VHDL is a strongly-typed language, which means that every object has a specified type, specification of types is explicitly stated, and correct use of types is required. One rationale for strong typing is that it helps tools detect errors early in the design process, usually during analysis, rather than later during elaboration or execution. This helps designers avoid the escape of bugs into products. Another rationale is that it provides extra information to an analyzer, so it can generate code optimized for a particular use of data. There is a trade-off in supporting these benefits. The type rules can seem somewhat restrictive or burdensome to the designer. In particular, rules that make it easier for a tool to implement language constructs can make it harder for a designer to write code expressing their intent.

In this chapter, we describe two ways in which VHDL-2008 changes the type system to relax some of the type rules. Both changes deal with rules relating to elements of composite types. The changes imply that tools must do more work to check correctness of designs and generate corresponding code for simulation. However, they remove restrictions that designers have found burdensome in earlier versions of VHDL.

Unconstrained Element Types

VHDL provides two kinds of composite types, namely, arrays and records. Each contains elements: all of the same type, in the case of array elements; and of heterogeneous types, in the case of record elements. In earlier versions of VHDL, the element types for arrays and records all had to be constrained, meaning the size of any elements that were arrays had to be fixed. So, for example, if we had a type that was an array of arrays, we could leave the outer array size unspecified, but the element array size had to be fixed. This restriction has long been an impediment, but it was not a simple matter to change. Hence, successive revisions of the VHDL standard left the restriction in place. In VHDL-2008, however, considerable effort has been invested in revising the type rules to lift the restriction. We describe the new rules in this section.

Composite Types

In order to fully understand the rules, we first review some terminology used in the VHDL standard. A type in VHDL just specifies a set of values. A subtype is a subset of values from a type, determined by a constraint. Those values that meet the constraint are in the subtype, and those that don’t meet the constraint are not in the subtype. The type from which values of a subtype are drawn is called the base type of the subtype. Note that a subtype need not be a proper subset of a type. The constraint may be vacuous, allowing any value from the type to be in the subtype. Thus, a type is considered to be a subtype of itself.

For an array type, each value is an indexed collection of elements, each of the same subtype, called the element subtype. An array value has one or more indices, the number of which determine the dimensionality of the array. Each index comes from an index subtype, which must contain only discrete scalar values, such as integers or values of an enumeration type. Thus, a one-dimensional array type, commonly called a vector, has a single index subtype; a two-dimensional array type, commonly called a matrix, has two distinct index subtypes; and so on.

A particular value or object of an array type has an index range for each index position. The index range has a left bound, a right bound, and a direction; it also determines the number of elements in the array value. Note that an index range is a distinct concept from an index subtype; it is a property of an array value or an array object, whereas an index subtype is a property of an array type. Keeping these concepts distinct in our minds will help us make sense of the type rules. The connection between the two is that, if an array value or object is of an array type, the bounds of each index range must belong to the corresponding index subtype of the array type.

When we declare an array type, we are effectively declaring an array subtype. There are two forms of array type declaration. One form, called a constrained array declaration, specifies index ranges, for example:

type A1 is array (natural range 0 to 7) of bit;

In this example, we are declaring an anonymous array base type that has natural as its index subtype and bit as its element subtype. The name A1 denotes a subtype of that anonymous base type, with the index constraint that the index range for values of the subtype must have a left bound of 0, a right bound of 7, and an ascending direction.

The other form of array type declaration was called an unconstrained array declaration in earlier versions of VHDL, but in VHDL-2008, it is called an unbounded array declaration. An example is:

type A2 is array (natural range <>) of bit;

Here, we are declaring A2 to be a base type that has natural as its index subtype and bit as its element subtype. We can treat A2 as a subtype with vacuous constraint; that is, values of the subtype can have any index range, of either direction, provided the bounds are values of subtype natural.

VHDL-2008 defines the terms “unconstrained” and “constrained” somewhat differently from earlier versions of VHDL, since the situation is somewhat more involved. In VHDL-2008, an array subtype is unconstrained if it has no index constraints, and the element subtype is either not a composite type or is an unconstrained type. An unconstrained type has no constraints anywhere in its structure where a constraint could apply. An array subtype is fully constrained if it has index constraints for all of its indices, and the element subtype is either not a composite type or is a fully constrained type. A fully constrained type has constraints everywhere in its structure where a constraint could apply. Together, these two categories do not cover all array subtypes, as there are those with index constraints but not fully constrained element subtypes, and those with no index constraints but full constrained element subtypes. These “in between” subtypes are called partially constrained, and have constraints in some places but not in others where a constraint could apply.

Here are some examples to illustrate the categories of array types. We will also use these types in further examples in Section 3.1.2. First, array types with non-composite elements are either unconstrained or fully constrained. Thus

type M-unconstrained is
  array (natural range <>, natural range <>) of bit;

is unconstrained, and

type M_fully_constrained is
  array (natural range 0 to 7, integer range -1 to 1) of bit;

is fully constrained. Note that every index of any array (but not necessarily of its elements) must be constrained or not. Thus, we could not legally write

type M_illegal is
  array (natural range <>, integer range -1 to 1) of bit;

Now, if we use M_unconstrained as the element subtype in an unbounded array definition:

type A_unconstrained is
  array (character range <>) of M_unconstrained;

the subtype defined is unconstrained. This was illegal in earlier versions of VHDL, but is now legal in VHDL-2008. If we use M_fully_constrained in a constrained array definition:

type A_fully_constrained is
  array (character range 'A' to 'Z') of M_fully_constrained;

the subtype defined is fully constrained. This was legal in earlier versions of VHDL, and remains legal in VHDL-2008. We can define partially constrained array subtypes as follows:

type A1_partially_constrained is
  array (character range 'A' to 'Z') of M_unconstrained;
type A2-partially_constrained is
  array (character range <>) of M_fully_constrained;

An object of subtype A1_partially_constrained must have ‘A’ to ‘Z’ as its index range, but the index ranges of each element are not specified. This was illegal in earlier versions of VHDL. An object of subtype A2_partially_constrained does not have its index range specified, but the index ranges of each element must be 0 to 7 and -1 to 1, respectively. This was previously legal, and remains legal in VHDL-2008.

VHDL-2008 also makes similar extensions to the type rules for records. For a record type, a value is a collection of elements, each identified by an element name and each of a specified element subtype. The element subtypes for different elements need not be the same. Moreover, in VHDL-2008, the element subtypes need not be constrained, as they were in earlier versions of VHDL. Just as we did for array subtypes, we say that a record subtype is unconstrained if each element subtype is either not a composite type or is an unconstrained type. A record subtype is fully constrained if each element subtype is either not a composite type or is a fully constrained type. A record subtype that is neither unconstrained nor fully constrained is partially constrained.

Again, here are some examples illustrating the categories for record types. First, a record type with non-composite elements is fully constrained:

type R_non_composite_elements is record
  e1  : bit;
  e2  : integer;
end record R_non_composite_elements;

It cannot be unconstrained, since there is no place in the record structure for a constraint to apply. An example of a record type declaration defining an unconstrained record subtype is:

type R_unconstrained  is record
  e1  : A_unconstrained;
  e2  : M_unconstrained;
  e3  : bit;
end record R_unconstrained;

In this case, the first two element subtypes are unconstrained and the third is non-composite, so the subtype is unconstrained. A record type declaration with fully constrained elements is:

type R_fully_constrained is record
  e1  : A_fully_constrained;
  e2  : M_fully_constrained;
  e3  : bit;
end record R_full_constrained;

This defines a fully constrained record subtype. The declaration:

type R1_partially_constrained is record
  e1  : A_unconstrained;
  e2  : M_fully_constrained;
  e3  : bit;
end record R1_partially_constrained;

defines a partially constrained record subtype, since the composite element subtypes are neither all unconstrained nor all fully constrained. Similarly, the declarations:

type R2_partially_constrained is record
  e1  : A1_partially_constrained;
  e2  : M_fully_constrained;
  e3  : bit;
end record R1_partially_constrained;

type R3_partially_constrained is record
  e1  : A2_partially_constrained;
  e2  : M_unconstrained;
  e3  : bit;
end record R3_partially_constrained;

both define partially constrained record subtypes, for the same reason.

Subtype Indications and Constraints

Now that we have seen how to declare unconstrained, partially constrained, and fully constrained subtypes using type declarations, we can turn to the way in which we specify constraints in subtype declarations and other places. In earlier versions of VHDL, the only kind of constraint we could apply to a composite subtype was an index constraint to specify index ranges for an otherwise unconstrained array type. All of the array elements and subelements of the array type had to be constrained. The situation is different in VHDL-2008, since we can have array and record types in which some element or sub-element subtype is an array subtype without an index constraint. We may want to specify index ranges at various levels in a composite subtype’s nested structure. We will show how the rules for specifying constraints are extended in VHDL-2008 for that purpose.

There are numerous places in VHDL where we can specify a subtype using a subtype indication, including in subtype declarations; declarations of elements of composite and other types; declarations of constants, signals, variables and files; declarations of generics, ports and parameters; and so on. In each case, the subtype indication takes the form of the name of a type or subtype followed by a constraint that limits the values that are in the subtype. We will just use subtype declarations to illustrate the various forms of subtype indication, but the same rules apply in other places. The types that we refer to in the following examples are all defined in Section 3.1.1.

If the type we are constraining is an array subtype with unspecified index ranges, we can include an index constraint, as we did in earlier versions of VHDL, for example:

subtype S1 is A_unconstrained('x' to 'z'),

In this case, since the element subtype for A_unconstrained was also unconstrained, it remains unconstrained in SI. Thus, SI is a partially constrained subtype. If we write

subtype S2 is A2_partia11y_constrained('c' downto 'a'),

the subtype S2 is fully constrained, since the element subtype of A2_partially_constrained is fully constrained.

Now suppose we want to define a subtype of A_unconstrained specifying index ranges for the top-level array and also for the elements. We can do this as follows:

subtype S3 is A_unconstrained('x' to 'z')(0 to 7, 31 downto 16);

The subtype S3 is fully constrained. Values or objects of this subtype must have three elements indexed from ‘x’ to ‘z’, and each element must be a matrix indexed from 0 to 7 in one dimension and from 31 down to 16 in the other. If we want to define a partially constrained subtype, specifying index ranges for the elements but leaving the index range at the top level unspecified, we can use the reserved word open in place of an index range, for example:

subtype S4 is A_unconstrained(open)(0 to 7, 31 downto 16);

Values or objects of this subtype can have any index bounds and direction, provided each element is a matrix indexed from 0 to 7 in one dimension and from 31 down to 16 in the other. We can also use this notation to specify index ranges for elements of a subtype that already has a constraint on the top-level indices, for example:

subtype S5 is
  A1_partially_constrained (open) (0 to 7, 31 downto 16);

In this case, the subtype A1_partially_constrained specifies an index range of ‘A’ to ‘Z’, but leaves the index ranges for the elements unconstrained. In the subtype declaration for S5, the use of open for the top-level constraint indicates that we leave the existing constraint, and skip over to the elements, for which we do specify index ranges.

As in earlier versions of VHDL, we can’t specify index ranges in a subtype indication if the subtype already has an index constraint at the specified position. So the following would be illegal:

subtype S6 is A1_partially_constrained('è' to 'ë '), -- illegal

since the constraint ‘è’ to ‘ë’ conflicts with the constraint A’ to ‘2’ specified in the subtype Al _partially_constrained.

If the type we are constraining is a record type or subtype, we can specify index constraints for the element subtypes. We need to indicate which record element is being constrained. An example showing the notation we use is:

subtype S7 is R_unconstrained(e1('A' to 'F'));

The subtype S7 is partially constrained with an index range of ‘A’ to ‘F’ specified for the element e1, but no index ranges specified for the elements of e1 or for the element e2. We could further constrain S7 as follows:

subtype S8 is S7(e1(open)(15 downto 0, 7 downto 0));

In this case, since e1 already includes an index constraint, we skip over it and specify index ranges for the elements of e1. Again, we leave the element subtype for e2 unconstrained. We can specify constraints for multiple elements as follows:

subtype S9 is S7(e1(open)(l5 downto 0, 7 downto 0),
                 e2(1 to 3, 1 to 3) );

Now, having specified constraints in all places where a constraint can apply, the subtype S9 is fully constrained.

This notation for specifying constraints for subelements of composite types has sufficient generality to work for any arbitrary nesting. We just need to ensure that the way we list constraints follows the hierarchy of nesting. At any given level, if the element subtype is an array subtype, we either specify one or more index ranges in parentheses or use the reserved word open to skip the level without specifying any index ranges. If the element subtype at the given level is a record subtype, we write one or more element names, and for each, we write constraints for the named element subtype. For example, given the following type declarations:

type T1 is array (integer range <>) of T;
type T2 is array (integer range <>, integer range <>) of T;
type T3 is record
     e1 : T1;
     e2 : T2;
end record T3;
type T4 is array (integer range <>) of T3;
type T5 is array (integer range <>, integer range <>)of T4;

We can write a fully constrained subtype declaration as follows:

subtype S10 is
  T5(1 to 4, 0 to 9)
      (3 downto 0)
        (e1(9 to 99),
         e2(-1 to 1, -1 to 1) );

Use of Composite Subtypes

In earlier versions of VHDL, there were rules specifying where we had to use a constrained subtype and where we could use an unconstrained subtype. These rules have been modified in VHDL-2008 to reflect the new category of partially constrained subtypes. In general, where previously we had to use a constrained subtype, we must now use a fully constrained subtype. Where previously we could use an unconstrained or a constrained subtype, we can now use an unconstrained, partially constrained, or fully constrained subtype. In earlier versions of VHDL, the rules for determining the index ranges for array objects were somewhat unclear. VHDL-2008 clarifies the rules, and extends them to deal with determining the index ranges for arrays that are elements or subelements of larger composite objects. We will go through the cases covered by these rules and use examples to show how they apply. In each case, it is important to keep in mind the distinction between an index subtype for an array type and an index range of a value or object. The cases we are discussing here deal with the way the index ranges for values or objects are determined from various subtypes in different ways.

Variable and Signal Declarations

The first case deals with variable and signal declarations, and includes arrays that are whole objects and arrays that are elements or subelements of larger objects. In this case, the subtype of the variable or signal must be a fully constrained subtype, since a tool must be able to determine the size of the object. For example, if we have a type declared as:

type signed_matrix is array (1 to 3, 1 to 4) of signed;

we must constrain the element type in order to declare a variable:

variable v : signed_matrix(open)(7 downto 0);

This gives a fully constrained subtype for the variable. A tool can then determine that the variable needs 3 × 4 × 8 = 96 scalar subelements. The index ranges for the array variable are taken from the fully constrained subtype, and are 1 to 3 for the first dimension and 1 to 4 for the second dimension. The index range for each element array are also taken from the fully constrained subtype. So each element has the index range 7 down to 0.

Constant Declarations

The second case deals with constant declarations, in which we can use unconstrained, partially constrained, and fully constrained subtypes. We must provide a value for a declared constant, and the value must belong to the subtype of the constant. The actual index ranges for the constant are determined jointly from the subtype and from the index ranges of the value. If the subtype includes a constraint that specifies index ranges at any given position, those index ranges are used. If the subtype leaves the index ranges undefined at any given position, the index range for that position is the corresponding index range from the value. In either case, whether the index range be specified by a constraint or determined from the value, each element of the constant is the element in the same left-to-right position in the initial value.

To illustrate the rule for constants, suppose we have the following declarations:

type A is array (1 to 3) of bit_vector;
constant C : A := ("0100", "1101", "0010");

The subtype A specifies an index range of 1 to 3 for the top-level array, so that is the index range used. However, the element subtype of A is unconstrained, so the index range for each element comes from the initial value’s elements. Those elements are all bit strings of type bit_vector, with index range 0 to 3. The subelements of the constant value are thus:

C(1)(0) = '0' C(1)(1) = '1' C(1)(2) = '0' C(1)(3) = '0'
C(2)(0) = '1' C(2)(1) = '1' C(2)(2) = '0' C(2)(3) = '1'
C(3)(0) = '0' C(3)(1) = '0' C(3)(2) = '1' C(3)(3) = '0'

Now suppose we declare a further constant as:

constant C1 : A(open)(7 downto 4) := C;

In this case, the subtype for the constant CI specifies index ranges at both levels: 1 to 3 for the top level and 7 down to 4 for the element level. Since the elements and sub-elements are assigned from the initial value in left-to-right order, the values are:

C1(1)(7) = '0' C1(1)(6) = '1' C1(1)(5) = '0' C1(1)(4) = '0'
C1(2)(7) = '1' C1(2)(6) = '1' C1(2)(5) = '0' C1(2)(4) = '1'
C1(3)(7) = '0' C1(3)(6) = '0' C1(3)(5) = '1' C1(3)(4) = '0'

One point to note is that, since the intial value is converted to the subtype of the constant, the initial value doesn’t need to have exactly the same index range as the constant, providing the length matches. For example, given the following declarations

type A2 is array (2 downto 0) of bit_vector;
constant C2 : A2 := C;

the index range of C2 is 2 down to 0, but the index range of the initial value C is 1 to 3. Elements of C are used to initialize C2 from left to right.

Attribute Specifications

The third case in the rules for determining index ranges deals with attribute values in attribute specifications, and is similar to the case of constant declarations. Thus, if the subtype in an attribute specification is an array subtype or includes array elements, the attribute value must belong to the subtype of the constant, and the actual index ranges are determined jointly from the subtype and from the index ranges of the specified value. In a sense, an attribute specification defines a constant value that decorates the named item. As an example, if we declare an attribute of a composite type:

type string_vector is array (positive range <>) of string;
attribute key_vector : string_vector;

we can decorate an item with the attribute as follows:

attribute key_vector of e : entity is
  ("66A6D 7DF3A 88CE1 8DEEB", "012BD 2BEE9 98634 93FE1");

Since the subtype for the attribute specifies index ranges in neither the top-level nor the element position, the corresponding index subtypes are used to determine the index ranges for the attribute value, giving the ranges 1 to 2 for the top level and 1 to 23 for each element.

Allocated Objects

The fourth case deals with allocation of objects using new. In this case, the allocator determines the index ranges of the allocated object. If we write an allocator with just a subtype indication, it must specify a fully constrained subtype, and the index ranges are taken from that subtype. For example, given the following declarations:

type RV is record
  v1 : bit_vector;
  v2 : time_vector;
end record RV;
type RV_ptr is access RV;
variable p : RV_ptr;

we can write an allocator using a subtype indication:

p := new RV_record(v1(0 to 23), v2(0 to 23));

The subtype indication specifies index ranges of 0 to 23 for both elements, so they are used for the allocated object. The object is then initialized with the default initial value.

On the other hand, if we write an allocator with a qualified expression, the value in the expression is converted to the named subtype (see Sections 9.3 and 9.4), and that determines the index ranges for the allocated object. Where that subtype specifies index ranges, they are used; and where no index range is specified, an index range is determined from the corresponding index subtype. For example, given the preceding declarations, we can write an allocator:

p := new RV_record'(v1 => "010", v2 => (2 ns, 4 ns, 6 ns));

Since the subtype RV_record does not specify any index ranges, the index subtypes for the record elements are used to determine index ranges for the allocated value. For each element, the index subtype is natural, so the index ranges are 0 to 2.

Interface Objects

The fifth and final case deals with interface objects, namely, generic constants, ports, and parameters. The declaration of a formal interface object includes a subtype indication, which may define an unconstrained, partially constrained, or fully constrained subtype. For each index position in the formal, whether it be at the top level of an array or a sub-element within the composite structure of the formal, there may or may not be a corresponding index range defined by an index constraint in the subtype. We call the index range specified in the subtype, if defined, the subtype index range corresponding to a given index position in the formal. We will now see how to determine the index range for each index position of the formal. There are several subcases, depending on the declaration of the formal, the actual value or object associated with the formal, and the way in which the association is written in the relevant generic map, port map, or parameter list.

In the first subcase, the subtype index range is defined by an index constraint in the subtype. The index range for the formal is taken from that index constraint. As an example, suppose we declare a port of an entity as follows:

entity ent1 is
  port ( p : out std_logic_vector(0 to 31) );
end entity ent1;

Regardless of the actual signal associated with the port in an instance of the entity, the formal port takes its index range from the subtype, since the subtype defines the index range. Thus, in an architecture, we can reference the index range as follows:

architecture a of ent1 is
begin
  process is
  begin
     ...
     for i in 0 to 31 loop
        p(i)  <=  ...
     end for;
     . . .
  end process;
end architecture a:

We can declare a signal and associate it with the port of an instance of ent1:

signal s : std_logic_vector(63 downto 32);
...

inst1 : entity work.e(a)
  port map ( p => s );

The fact that the actual signal has a different index range does not affect the index range for the formal port. All it means is that s(63) is associated with p(0), s(62) with p(1), and so on.

This subcase also applies to subelements of interface objects. For example, suppose we have a type declared as:

type byte_vector is
  array (natural range <>) of bit_vector(7 downto 0);

We might define a function as follows:

function reduce ( v : byte_vector ) return bit is
  ...
begin
  for i in v'range loop
     for j in 7 downto 0 loop
         ...
     end loop;
  end loop;
  return ...;
end function reduce;

In any call to reduce, the index range for each element of ν is 7 down to 0, taken from the subtype byte_vector, regardless of the index range at the top level of v.

In the second subcase, the subtype index range is undefined, but the array is associated using subelement association. That means the association list uses named association to divide the formal into separate elements or slices and associates each element or slice with a separate actual value or object. The index range for the formal is then determined from the index values used in those formal element names or slices. The smallest index value used is the low bound of the index range of the formal, and the largest index value used is the high bound. The direction of the index range is the direction of the corresponding index subtype taken from the subtype of the formal. To illustrate, we can revise our earlier example of an entity’s port:

entity ent2 is
  port ( p : out std_logic_vector );
end entity ent2;

In this example, we don’t know the index range for p within the architecture body, so we would have to refer to it using attributes, such as p'range. If we write an instance of the entity as follows:

inst2 : entity work.ent2(a)
  port map ( p(l1) => s1, p(12 to 15) => sv );

the index values in the formal element and slice names are used to determine the index range for the formal p for this instance. The smallest value is 11, and the largest value is 15. The direction for the index range is ascending, since the index subtype for std_logic_vector is natural, which is ascending. Thus, the index range for p is 11 to 15. Note that the direction for the index range for the port is determined by the index subtype of the port, not by the direction of the range in a slice name in the port map. Had we written the above instantiation as:

inst2 : entity work.ent2(a)
  port map ( p(15) => s1, p(14 downto 11) => sv ); -- illegal

the slice name in the port map would be in error. VHDL requires that the direction of the range in a slice name match the direction of the index range of the array being sliced.

As before, this subcase also applies to subelements of interface objects. For example, given a type

type bv_pair is array (1 to 2) of bit_vector;

and a port declared in an entity as:

entity ent3 is
  port ( p : in bv_pair );
end entity ent3;

we can write an instance of the entity:

signal s1, s2 : bit;
signal sv1, sv2 : bit_vector(4 to 7);

inst3 : entity work.ent3
  port map ( p(1)(0) => s1, p(1)(1 to 4 ) => sv1,
             p(2)(0) => s2, p(2)(1 to 4 ) => sv2 );

Here, the index range for the top level of p is determined from the subtype, as in the first subcase. However, for the elements, the subtype index range is not defined, so the index range for p comes from the formal element and slice names. Combining these effects, the index ranges for p are 1 to 2 for the top level, and 0 to 4 for the elements. Note that the index range determined for the two elements p(1) and p(2) must be the same. It would be illegal to write the instance as:

signal s1, s2 : bit;
signal sv1, sv2 : bit_vector(4 to 7);

inst3 : entity work.ent3
 port map ( p(1)(0) => s1, p(1)(1 to 4) => sv1,
           p(2)(15) => s2, p(2)(11 to 14) => sv2 ); -- illegal

since that would imply two different index ranges: 0 to 4 for p(1) and 11 to 15 for p(2). An array must have the same index ranges for all elements.

In the third subcase, the subtype index range is undefined, but the array is associated as a whole. There are no index values or slice values to identify the index bounds for the formal. Instead, the index range is determined from the corresponding index range of the actual or from any conversion functions or type conversions that appear in the association between the actual and the formal. We need to consider the various sub-subcases.

The first sub-subcase is a simple association involving no type conversions or conversion functions in the association between actual and formal. In this sub-subcase, the index range for the formal is taken from the corresponding index range of the actual. For example, given our entity declaration with an unconstrained port, as before:

entity ent2 is
  port ( p : out std_logic_vector );
end entity ent2 ;

we might write an instance as follows:

signal s12 : std_logic_vector(15 downto 4);
...

inst4 : entity work.ent2(a)
  port map (p => s12 );

In this example, the index range of the formal is not defined, and the association with the actual provides no index values to use. So the formal takes its index range, 15 down to 4, from the associated actual signal s12.

Again, this rule applies to arrays that are subelements of interface objects. Returning to our entity ent3 with a port of type bv_pair, we can instantiate it as:

signal sv1, sv2 : bit_vector(0 to 7);

inst5 : entity work.ent3
  port map ( p(1) => sv1,p(2) => sv2 );

As before, the index range for the top level of p is determined from the subtype. However, for the elements, the subtype index range is not defined and the associations do not provide index values. Thus, the index range for the elements come from the actuals. Combining these effects, the index ranges for p are 1 to 2 for the top level, and 0 to 7 for the elements.

The second sub-subcase arises for an interface object of mode in, inout, or linkage, when the association with an actual includes a type conversion or conversion function applied to the actual. In this sub-subcase, the index range for the formal comes from the result of the conversion. This requires that the conversion define the corresponding index ranges. For a type conversion, the named type must be a subtype that includes a constraint defining the relevant index ranges, and for a conversion function, the result subtype must similarly define the relevant index ranges. To illustrate, suppose we have an entity with an unconstrained in-mode port, declared as follows:

type signed_vector is (natural range <>) of signed;
...

entity ent4 is
  port ( x : in signed_vector );
end entity ent4;

We can associate a signal of type integer_vector with the port, provided we apply a conversion function. However, the function must specify index ranges in its result subtype, since the port subtype has no index ranges specified. A legal example is:

subtype iv3 is integer_vector(1 to 3);
subtype sv3 is signed_vector (1 to 3)(31 downto 0);
signal iv : iv3;
function cvt3 ( v : iv3 ) return sv3;
...

inst6 : entity work.ent4
  port map ( x => cvt3(iv) );

Since the result subtype of the conversion function specifies the index ranges 1 to 3 at the top level and 31 down to 0 at the element level, those are the ranges used for the formal in the instance. Had we written a conversion function:

function cvt ( v : integer_vector ) return signed_vector;

we would not be able to use it in the same way, since it does not specify the index ranges to be used for the formal port in the instance. Similar arguments apply to type conversions. For example, with the following declarations:

type unsigned_vector is (natural range <>) of unsigned;
subtype uv3 is unsigned_vector(1 to 3)(31 downto 0);
signal uv : uv3;

we could write the following instance of entity ent4:

inst7 : entity work.ent4
  port map ( x => sv3(uv) );

since the subtype named in the type conversion specifies the index ranges 1 to 3 at the top level and 31 down to 0 at the element level, whereas the following would be illegal:

inst8 : entity work.ent4
  port map ( x => signed_vector(uv) );

The third sub-subcase is similar to the second. It arises for an interface object of mode out, buffer, inout, or linkage, when the association with an actual includes a type conversion or conversion function applied to the formal. For a type conversion, the named type must be a subtype that includes a constraint defining the relevant index ranges, and these are used for the formal. For a conversion function, the parameter subtype must similarly define the relevant index ranges, and these are used for the formal.

Note that both the second and the third sub-subcases deal with interface objects of modes inout and linkage. This mirrors the possibility of including type conversions or conversion functions in both the formal and actual parts of the association between formal and actual. If that occurs, both sub-subcases apply, and the index ranges determined must agree.

Summary: Determining Array Index Ranges

Since this case analysis of the way in which index ranges are determined is complex and multi-level, we’ll summarize it here. For an array object, including a subelement array of a larger composite object, we determine each index range as follows:

  1. For a declared signal or variable: The index range comes from the object’s subtype, which must be fully constrained.

  2. For a declared constant: If the constant’s subtype defines the index range, that index range is used; otherwise, the index range comes from the corresponding index range of the constant’s initial value.

  3. For an attribute value: The index range comes from the attribute’s subtype and the specified value, in same way as case 2.

  4. For an allocated object: If the allocator is in the form of a subtype indication, the index range comes from the specified subtype, which must be fully constrained. Otherwise, the allocator is in the form of a qualified expression, and the index range comes from the index range in the qualified expression’s subtype, if defined, or from the index subtype in the qualified expression’s subtype otherwise.

  5. For a formal interface object, there are three subcases:

    1. If the formal’s subtype defines the index ranges, they are used.

    2. If the formal’s subtype does not define the index ranges, and subelement association is used to specify index values for the formal, then the index range uses the smallest and largest index values as the bounds, and gets the direction from the index subtype of the formal’s subtype.

    3. If the formal’s subtype does not define the index ranges, and the association with the actual does not specify index values for the formal, then there are three sub-subcases:

      • If there are no conversions involved in the association, then the index range comes from the actual object.

      • If the formal is of mode in, inout, or linkage, and there is a conversion in the actual part of the association, then the index range comes from the conversion’s result subtype, which must define a corresponding index range.

      • If the formal is of mode out, buffer, inout, or linkage, and there is a conversion in the formal part of the association, then the index range comes from the type conversion’s subtype or conversion function’s parameter subtype, as appropriate, and that subtype must define a corresponding index range.

For a formal of mode inout or linkage, if conversions are used in both formal and actual parts, they must both define the same index ranges.

Type Conversions

Now that we’ve covered the rules dealing with the way in which index ranges are determined for composite objects, we can turn to some further uses of composite subtypes. One use is as the target of a type conversion. VHDL-2008 makes further changes to the rules for type conversions (see Section 9.4), but we will focus on the rules relating to index ranges here. When we convert the type of an array object to a target array subtype, we produce an array with the same element values, but with different index ranges. If the target subtype defines index ranges at a given index position, we use those index ranges. On the other hand, if the target subtype leaves the index ranges undefined, we determine index ranges for the result based on the index subtype at that position. The index range starts at the leftmost value of the index subtype and has the same direction, ascending or descending, as the index subtype. The right bound is then determined by the required size for the index range.

Example 3.1. Type conversions between signed and unsigned vectors

Suppose we have two unconstrained types declared as:

type unsigned_vector is (natural range <>) of unsigned;
type   signed_vector is (natural range <>) of   signed;

and subtypes declared as:

subtype unsigned_vector3 is
          unsigned_vector(1 to 3);
subtype unsigned_byte_vector is
          unsigned_vector(open )(7 downto 0);
subtype unsigned_byte_vector3 is
          unsigned_vector(1 to 3)(7 downto 0);

Given a signal:

signal s : signed_vector(1 to 3)(7 downto 0);

the conversion:

unsigned_vector3(s)

yields an array indexed from 1 to 3 at the top level and from 0 to 7 at the element level. The top-level index range is specified in the target subtype. The element-level index range is determined from the index subtype natural, starting from 0 and ascending for eight elements. Alternatively, the conversion:

unsigned_byte_vector(s)

yields an array indexed from 0 to 2 at the top level and from 7 down to 0 at the element level. In this case, the target subtype does not specify an index range and the top level, so the top-level index range is determined from the top-level index subtype. The element-level index range comes from that specified in the target subtype. Finally, the conversion:

unsigned_byte_vector3(s)

yields an array indexed from 1 to 3 at the top level and from 7 down to 0 at the element level, since both index ranges are specified in the target subtype.

Alias Declarations and Subtype Attributes

Another place where we can use a composite subtype is in an alias declaration, to get an alternative view of an array object. The rules for determining the index ranges in the view are slightly different from those of type conversions. To start with, the subtype in an alias declaration must have the same base type as that of the object being aliased. This means that the bounds of index ranges of the aliased object are guaranteed to belong to the index subtypes of the alias. If the alias subtype defines index ranges at any given index position, then those index ranges are used for the alias. On the other hand, if index ranges are not defined, then the corresponding index ranges of the aliased object are used for the alias also.

Example 3-2. Alias of a Register File Signal

We can declare a register file as follows:

type register_array is array (natural range <>) of bit_vector;
signal register_file : register_array(0 to 15)(31 downto 0);

We can then declare aliases for

alias bigendian_register_file : register_array(open)(0 to 31) is
        register_file;

This alias views the register file as an array with the same index range as the original, 0 to 15, since the subtype indication does not specify a top-level index range. Each element, however, is viewed with the index range 0 to 31 specified in the subtype indication.

One common use of aliases is to provide a normalized view of an unconstrained port or parameter so that we can write for loops that iterate over corresponding elements of two potentially different index ranges. In earlier versions of VHDL, using the ’length attribute was sufficient, since any elements of an unconstrained array type had to be constrained with a specific index range. In VHDL-2008, that is no longer the case. We may have to deal with two ports (or two parameters) that have different index ranges for their elements as well as for the top-level arrays. To help us with such situations, VHDL-2008 predefines a new attribute, ’element, that gives the element subtype of an array object, complete with constraints defining the index ranges for the array object. The attribute is also defined for array subtypes, in which case it just gives the element subtype.

Example 3.3. Aliases for normalizing subelements

We can write a function that locates the first bit difference between two arrays of bit vectors as follows:

type bv_vector is array (natural range <>) of bit_vector;

function find_first_difference ( s1, s2 : in bv_vector)
                       return natural is
  alias s1_norm : bv_vector(0 to s1'length - 1)
                        (0 to s1'element'length - 1) is s1;
  alias s2_norm : bv_vector(0 to s2'length - 1)
                        (0 to s2'element'1ength - 1) is s2;
  variable count : natural := 0;
begin
   assert s1'length = s2 'length and
            s1'element'length = s2 'element'length;
   for i in s1_norm'range loop
       for j in s1_norm'element 'range loop
           exit when s1_norm(i)(j)/= s2_norm(i)(j);
                   count := count + 1;
     end loop;
   end loop;
  return count;
end function find_first_difference;

The two parameters are of an unconstrained type, allowing the function to operate on arrays of various lengths and on arrays with various bit-vector element lengths. The function only requires that, on each call, the two actual parameters have the same shape. In order to deal with the differences, the function declares aliases for the parameters. It views each parameter with an index range starting at 0 and ascending to one less than the length. It views the elements similarly, with an index range starting at 0 and ascending to one less than length of each bit-vector element. The alias declaration uses the ’element attribute to get the constrained subtype for the actual parameter’s elements. Within the function body, the inner for loop also uses the ’element attribute to get the index range for the elements of the aliases.

VHDL-2008 also predefines the ’subtype attribute for objects. It provides the subtype of the object, complete with constraints defining index ranges if the object is an array or has any array subelements. Since the subtype is fully constrained, we can use it to declare an object with the same index ranges as the actual associated with an unconstrained or partially constrained formal.

Example 3-4. Swapping variables with unconstrained subelements

Given the type bv_array as defined in Example 3.3, we can declare a procedure to swap two variables of the type:

procedure swap_bv_arrays ( a1, a2 : inout bv_array ) is
  variable temp : al' subtype;
begin
  assert a1'length = a2'1ength and
                   a1'element'1ength = a2'element'length;
  temp := a1; a1 := a2; a2 := temp;
end procedure swap;

Since the type bv_array is not fully constrained, we cannot use it as the type of the variable temp. Instead, we use the 'subtype attribute to get a fully constrained subtype with the same shape as al. Once we’ve verified that a1 and a2 are the same shape, we can then swap their values in the usual way using temp as the intermediate variable.

Resolved Composite Subtypes

The final place to consider for use of composite subtypes is declaration of resolution functions. To declare a resolution function for signals of a given type, we write a function that takes as a parameter an array with elements of that type and that returns a value of the type. The parameter array type must have an undefined index range, so that signals with different numbers of drivers can be resolved. In earlier versions of VHDL, an unconstrained array type had to have a constrained element subtype. As a consequence, if we wanted to use resolved signals of a composite type, the signal type had to be constrained. We could not, for example, specify a resolved subtype for signals of type bit_vector, and use it for a mixture of 8-bit, 16-bit, and other length signals. There was no way for us to express the resolution function. In VHDL-2008, since we can leave the element subtype of an array unconstrained, we can develop resolved composite subtypes that are unconstrained. The only requirement on the subtype for the resolution function parameter is that it be an array with unconstrained index range. The element subtype can be fully constrained, partially constrained, or unconstrained.

Example 3-5. Resolved unconstrained composite signals

Suppose, in the interest of simulation performance for a particular application, we want to use signals of type bit_vector, resolved using a bit-wise wired-or operation. The declarations we need are:

type bit_vector_vector is array (integer range <>) of bit_vector;

function resolve_vectors( v : bit_vector_vector )
                        return bit_vector is
  variable result : bit_vector(v'element'range)
                   := (others => '0'),
begin
  for i in v'range loop
     result := result or v(i);
  end loop;
  return result;
end function resolve_vectors;

subtype resolved_bit_vector is resolve_vectors bit_vector;

In the design, if we declare a signal as follows:

signal data_bus : resolved_bit_vector(31 downto 0);

and drive it with four sources, the resolution function will be passed an array of four 32-bit elements, and will be expected to return a 32-bit result.

Resolved Elements

In earlier versions of VHDL, we could declare resolved subtypes and resolved signals to model signals with multiple sources. This feature is preserved in VHDL-2008. We can associate a resolution function with a subtype or signal. The purpose of the resolution function is to determine the resolved value of a signal from the values of the contributing sources. The source values are passed to the function as an array whose element type is the same as that of the signal, and the result type of the function is also that of the signal. Ideally, we declare a signal intended to have only one source with an unresolved subtype, and we declare a signal intended to have multiple sources with a resolved subtype. That way, tools can detect inadvertent connection of multiple sources to signal intended to have only one source.

While this approach works well for scalar subtype and signals, in earlier versions of VHDL it led to problems with array signals. We can illustrate the problem using the types std_ulogic_vector and std_logic_vector, declared in earlier versions as:

type std_ulogic is ( ... );
type std_ulogic_vector is  array (natural range <>)of std_ulogic;
subtype std_logic is resolved std_ulogic;
type std_logic_vector is array (natural range <>) of std_logic;

We would like to be able to use type std_ulogic_vector for signals with only one source per element, and type std_logic_vector for signal with multiple sources per element, for example:

signal s1 : std_ulogic_vector(31 downto 0);
signal s2 : std_logic_vector(31 downto 0);

However, we could not assign the value of one of these signals to the other, unless we included a type conversion:

s1 <= std_ulogic_vector(s2);

even though we could assign respective elements:

s1(0) <= s2(0);

The reason was that for the element types, std_logic was a subtype of std_ulogic, whereas std_logic_vector and std_ulogic_vector were, in earlier versions of the language, two distinct base types declared by distinct type declarations.

A similar problem arose when we connected signals to ports of components. If a component had a port of type std_ulogic_vector, because it had only one source for the port internally, we could not simply connect the port to a signal of type std_ulogic_ vector, even if the port was the only source for the signal. Instead, we needed to include a type conversion in the port map:

signal s : std_ulogic_vector(0 to 7);
component c is
  port ( p : out std_ulogic_vector(0 to 7); ... );

end component c;
...

inst : component c
  port map ( std_ulogic_vector(p) = > s, ... );

While these problems have been part of VHDL since the first version of the standard, devising a way to fix them has proved to be difficult. Nonetheless, an approach has been found and incorporated in VHDL-2008. It involves a way of associating a resolution function with an element type of a composite subtype as part of declaring a new subtype. For example, in VHDL-2008, the type std_logic_vector is now defined to be a subtype of std_ulogic_vector, declared as:

subtype std_logic_vector is (resolved) std_ulogic_vector;

The parentheses around the resolution function name, resolved, indicates that the resolution function is associated with each element of the array type, rather than with the array type as a whole. Since std_logic_vector is now a subtype of std_ulogic_vector, not a distinct type, we can freely assign and associate signals and ports of the two types.

The change made in VHDL-2008 is to allow a more general form of resolution indication to be included in a subtype indication or signal declaration, rather than just naming a resolution function by itself. The change is backward compatible. If we want to associate a resolution function with an entire subtype, the resolution indication just consists of the resolution function name, as in previous version of VHDL. For example, in the declaration of std_logic:

subtype std_logic is resolved std_ulogic;

The resolution indication is just the resolution function name, resolved. In the case of an array whose elements are to be resolved, we write the resolution function name in parentheses, as in the declaration of std_logic_vector. We can also resolve the elements of an array type that is itself an array element type. For example, given the following declaration:

type unresolved_RAM_content_type is
  array (natural range <>) of std_ulogic_vector;

we can declare a subtype with resolved nested elements:

subtype RAM_content_type is
  ((resolved)) un resolved_RAM_content_type;

The degree of nesting of parentheses indicates how deeply nested in the type structure the resolution function is associated. Two levels indicate that the resolution function is associated with the elements of the elements of the type.

If we have a record type, one of whose elements is to be resolved, we include the element name in the resolution indication. For example, given the following record type with no associated resolution information:

type unresolved_status_type is record
  valid :  std_ulogic;
  dirty :  std_ulogic;
  tag   :  std_ulogic_-vector;
end record unresolved_status_type;

we can declare a subtype with a resolved valid element as follows:

subtype status_resolved_valid is
  (valid wand) unresolved_status_type;

We can include resolution functions with multiple elements of the record type by listing the element names and the resolution function associated with each, for example:

subtype status_resolved_flags is
  (valid wand, dirty wor) unresolved_status_type;

For a record element that is itself of a composite type, we can associate a resolution function with subelements of the record element by writing a parenthesized resolution indication for the element. Thus, to resolve the elements of the tag element of the above record type, we would declare a subtype as follows:

subtype status_resolved_tag is
  (tag(reso1ved)) unresolved_status_type;

We could combine all of these examples together, resolving all of the scalar sub-elements, as follows:

subtype resolved_status_type is
  (tag(reso1ved), valid wand, dirty wor) unresolved_status_type;

This declaration illustrates that we do not have to write the resolution indications for the record elements in the same order as the declaration of elements in the record types. The record element names in the resolution indication determine the element with which the resolution function is associated.

Example 3.6. Memory system with tristate bus

We can write a model for a memory system composed of multiple memory devices with tristate data buses. The entity declaration for the memory system is:

library ieee; context ieee.ieee_std_context;
entity memory_1Mx8    is
  port ( ce_n, oe_n, we_n : in std_ulogic;
          a : in unsigned(l9 downto 0);
          d : inout std_logic_vector(7 downto 0) );
end entity memory_1Mx8;

The d port is of type std_logic_vector, since internally there are multiple sources, one per memory device. The structural architecture is:

architecture struct of memory_1Mx8 is
  component memory_256Kx8 is
     port ( ce_n, oe_n, we_n : in std_ulogic;
               a : in unsigned(l7 downto 0);
               d : inout std_ulogic_vector(7 downto 0) );
  end component memory_256Kx8;
  signal ce_decoded_n : std_ulogic_vector(3 downto 0);
begin
  with to_x01(a(l9 downto 18)) select
     ce_decoded_n <= "1110" when "00",
                        "1101" when "01",
                        "1011"    when     "10",
                        "0111" when "11",
                        "XXXX" when others;
  chip_gen : for i in 3 downto 0 generate
     chip : component memory_256Kx8
       port map ( ce_n => ce_decoded_n(i),
                 oe_n => oe_n, we_n => we_n,
                 a => a(17 downto 0), d => d );
  end generate chip_gen;
end architecture struct;

The d port of the component representing the memory devices is of type std_ulogic_vector, since each device has only one internal source. Nonetheless, we can connect the d port of each instance directly to the d port of the memory system entity without type conversion. Had we inadvertently declared the d port of the entity to be of type std_ulogic_vector, the analyzer would detect the error arising from multiple sources connected to the unresolved elements.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset