Chapter 7. Packages and Use Clauses

Packages in VHDL provide an important way of organizing the data and subprograms declared in a model. In this chapter, we describe the basics of packages and show how they may be used. We will return to packages in Chapter 12, where we will see how they can be extended to make them more reusable than the basic form we discuss here.

Package Declarations

A VHDL package is simply a way of grouping a collection of related declarations that serve a common purpose. They might be a set of subprograms that provide operations on a particular type of data, or they might just be the set of declarations needed to model a particular design. The important thing is that they can be collected together into a separate design unit that can be worked on independently and reused in different parts of a model.

Another important aspect of packages is that they separate the external view of the items they declare from the implementation of those items. The external view is specified in a package declaration, whereas the implementation is defined in a separate package body. We will look at package declaration first and return to the package body shortly.

The syntax rule for writing a package declaration is

   package_declaration  ⇐
      package identifier is
          { package_declarative_item }
      end [ package ] [ identifier ]

The identifier provides a name for the package, which we can use elsewhere in a model to refer to the package. Inside the package declaration we write a collection of declarations, including type, subtype, constant, signal and subprogram declarations, as well as several other kinds of declarations that we see in later chapters. These are the declarations that are provided to the users of the package. The advantage of placing them in a package is that they do not clutter up other parts of a model, and they can be shared within and between models without having to rewrite them.

Example 7.1. A package as part of a CPU model

The following package declares constants and types that we can use in a model of a CPU:

   package cpu_types is

    constant word_size : positive := 16;
    constant address_size : positive := 24;

    subtype word is bit_vector(word_size - 1 downto 0);
    subtype address is bit_vector(address_size - 1 downto 0);

    type status_value is ( halted, idle, fetch,
                           mem_read, mem_write,
                           io_read, io_write, int_ack );

   end package cpu_types;

VHDL-87

The keyword package may not be included at the end of a package declaration in VHDL-87.

Most of the time, we write a package as another form of design unit, along with entity declarations and architecture bodies. It is separately analyzed and is placed into the working library as a library unit by the analyzer. From there, other library units can refer to an item declared in the package using the selected name of the item. The selected name is formed by writing the library name, then the package name and then the name of the item, all separated by dots; for example:

   work.cpu_types.status_value

Example 7.2. An address decoder using the cpu_types package

Suppose the cpu_types package, shown in Example 7.1, has been analyzed and placed into the work library. We might make use of the declared items when modeling an address decoder to go with a CPU. The entity declaration and architecture body of the decoder are:

   entity address_decoder is
     port ( addr : in work.cpu_types.address;
            status : in work.cpu_types.status_value;
            mem_sel, int_sel, io_sel : out bit );
   end entity address_decoder;
   --------------------------------------------------
   
   architecture functional of address_decoder is

    constant mem_low : work.cpu_types.address := X"000000";
    constant mem_high : work.cpu_types.address := X"EFFFFF";
    constant io_low : work.cpu_types.address := X"F00000";
    constant io_high : work.cpu_types.address := X"FFFFFF";
   begin

    mem_decoder :
      mem_sel <=
        '1' when ( work.cpu_types."="
                     (status, work.cpu_types.fetch)
                   or work.cpu_types."="(
                        status, work.cpu_types.mem_read)
                   or work.cpu_types."="
                        (status, work.cpu_types.mem_write) )
                 and addr >= mem_low
                 and addr <= mem_high else
        '0';
    int_decoder :
      int_sel <=
        '1' when work.cpu_types."="
                   (status, work.cpu_types.int_ack) else
        '0';
    io_decoder :
      io_sel <=
        '1' when ( work.cpu_types."="
                     (status, work.cpu_types.io_read)
                   or work.cpu_types."="
                        (status, work.cpu_types.io_write) )
                 and addr >= io_low
                 and addr <= io_high else
        '0';
   end architecture functional;

Note that we have to use selected names to refer to the subtype address, the type status_value, the enumeration literals of status_value and the implicitly declared “=” operator, defined in the package cpu_types. This is because they are not directly visible within the entity declaration and architecture body. We will see later in this chapter how a use clause can help us avoid long selected names. If we needed to type-qualify the enumeration literals, we would use selected names for both the type name and the literal name; for example:

   work.cpu_types.status_value'(work.cpu_types.fetch)

We have seen that a package, when analyzed, is placed into the working library. Items in the package can be accessed by other library units using selected names starting with work. However, if we are writing a package of generally useful declarations, we may wish to place them into a different library, such as a project library, where they can be accessed by other designers. Different VHDL tool suites provide different ways of specifying the library into which a library unit is placed. We must consult the documentation for a particular product to find out what to do. However, once the package has been included in a resource library, we can refer to items declared in it using selected names, starting with the resource library name. As an example, we might consider the IEEE standard-logic package, which must be placed in a resource library called ieee. We can refer to the types declared in that package, for example:

   variable stored_state : ieee.std_logic_1164.std_ulogic;

One kind of declaration we can include in a package declaration is a signal declaration. This gives us a way of defining a signal, such as a master clock or reset signal, that is global to a whole design, instead of being restricted to a single architecture body. Any module that needs to refer to the global signal simply names it using the selected name as described above. This avoids the clutter of having to specify the signal as a port in each entity that uses it, making the model a little less complex. However, it does mean that a module can affect the overall behavior of a system by means other than through its ports, namely, by assigning to global signals. This effectively means that part of the module’s interface is implicit, rather than being specified in the port map of the entity. As a matter of style, global signals declared in packages should be used sparingly, and their use should be clearly documented with comments in the model.

Example 7.3. A package containing clock signal declarations

The following package declares two clock signals for use within an integrated circuit design for an input/output interface controller.

   library ieee;  use ieee.std_logic_1164.all;

   package clock_pkg is

    constant Tpw : delay_length := 4 ns;

    signal clock_phase1, clock_phase2 : std_ulogic;

   end package clock_pkg;

The top-level architecture of the controller circuit is outlined below. The instance of the phase_locked_clock_gen entity uses the ref_clock port of the circuit to generate the two-phase clock waveforms on the global clock signals. The architecture also includes an instance of an entity that sequences bus operations using the bus control signals and generates internal register control signals.

   library ieee;  use ieee.std_logic_1164.all;
   
   entity io_controller is
    port ( ref_clock : in std_ulogic;  ... );
   end entity io_controller;
   --------------------------------------------------
   architecture top_level of io_controller is
    ...
   begin
    internal_clock_gen :
      entity work.phase_locked_clock_gen(std_cell)
        port map ( reference => ref_clock,
                   phi1 => work.clock_pkg.clock_phase1,
                   phi2 => work.clock_pkg.clock_phase2 );
    the_bus_sequencer :
      entity work.bus_sequencer(fsm)
        port map ( rd, wr, sel, width, burst,
                   addr(1 downto 0), ready,
                   control_reg_wr, status_reg_rd,
                   data_fifo_wr, data_fifo_rd,
                   ... );
    ...
   end architecture top_level;

The architecture body for the sequencer is outlined next. It creates an instance of a register entity and connects the global clock signals to its clock input ports.

   architecture fsm of bus_sequencer is
    -- This architecture implements the sequencer as
    -- a finite-state machine. NOTE: it uses the clock signals
    -- from clock_pkg to synchronize the fsm.
    signal next_state_vector : ...;
   begin
    bus_sequencer_state_register :
      entity work.state_register(std_cell)
        port map ( phi1 => work.clock_pkg.clock_phase1,
                   phi2 => work.clock_pkg.clock_phase2,
                   next_state => next_state_vector,
                   ... );
    ...
   end architecture fsm;

Subprograms in Package Declarations

Another kind of declaration that may be included in a package declaration is a subprogram declaration—either a procedure or a function declaration. This ability allows us to write subprograms that implement useful operations and to call them from a number of different modules. An important use of this feature is to declare subprograms that operate on values of a type declared by the package. This gives us a way of conceptually extending VHDL with new types and operations, so-called abstract data types, a topic we return to in Chapter 12.

An important aspect of declaring a subprogram in a package declaration is that we only write the header of the subprogram, that is, the part that includes the name and the interface list defining the parameters (and result type for functions). We leave out the body of the subprogram. The reason for this is that the package declaration, as we mentioned earlier, provides only the external view of the items it declares, leaving the implementation of the items to the package body. For items such as types and signals, the complete definition is needed in the external view. However, for subprograms, we need only know the information contained in the header to be able to call the subprogram. As users of a subprogram, we need not be concerned with how it achieves its effect or calculates its result. This is an example of a general principle called information hiding: making an interface visible but hiding the details of implementation. To illustrate this idea, suppose we have a package declaration that defines a bit-vector subtype:

   subtype word32 is bit_vector(31 downto 0);

We can include in the package a procedure to do addition on word32 values that represent signed integers. The procedure declaration in the package declaration is

   procedure add ( a, b : in word32;
                   result : out word32;  overflow : out boolean );

Note that we do not include the keyword is or any of the local declarations or statements needed to perform the addition. These are deferred to the package body. All we include is the description of the formal parameters of the procedure. Similarly, we might include a function to perform an arithmetic comparison of two word32 values:

   function "<" ( a, b : in word32 ) return boolean;

Again, we omit the local declarations and statements, simply specifying the formal parameters and the result type of the function.

Constants in Package Declarations

Just as we can apply the principle of information hiding to subprograms declared in a package, we can also apply it to constants declared in a package. The external view of a constant is just its name and type. We need to know these in order to use it, but we do not actually need to know its value. This may seem strange at first, but if we recall that the idea of introducing constant declarations in the first place was to avoid scattering literal values throughout a model, it makes more sense. We defer specifying the value of a constant declared in a package by omitting the initialization expression; for example:

   constant max_buffer_size : positive;

This defines the constant to be a positive integer value. However, since we cannot see the actual value, we are not tempted to write the value as an integer literal in a model that uses the package. The specification of the actual value is deferred to the package body, where it is not visible to a model that uses the package. Given the above deferred constant in a package declaration, the corresponding package body must include the full constant declaration, for example:

   constant max_buffer_size : positive := 4096;

Note that we do not have to defer the value in a constant declaration—it is optional.

Example 7.4. The CPU package extended with operations

We can extend the package specification from Example 7.1, declaring useful types for a CPU model, by including declarations related to opcode processing. The revised package is shown below. It includes a subtype that represents an opcode value, a function to extract an opcode from an instruction word and a number of constants representing the opcodes for different instructions.

   package cpu_types is

     constant word_size : positive := 16;
     constant address_size : positive := 24;

     subtype word is bit_vector(word_size - 1 downto 0);
     subtype address is bit_vector(address_size - 1 downto 0);

     type status_value is ( halted, idle, fetch,
                            mem_read, mem_write,
                            io_read, io_write, int_ack );

     subtype opcode is bit_vector(5 downto 0);

     function extract_opcode ( instr_word : word ) return opcode;

     constant op_nop : opcode := "000000";
     constant op_breq : opcode := "000001";
     constant op_brne : opcode := "000010";
     constant op_add : opcode := "000011";
     ...

   end package cpu_types;

A behavioral model of a CPU that uses these declarations is

   architecture behavioral of cpu is
   begin
   
    interpreter : process is

      variable instr_reg : work.cpu_types.word;
      variable instr_opcode : work.cpu_types.opcode;

    begin
      ...  -- initialize
      loop
        ...  -- fetch instruction
        instr_opcode := work.cpu_types.extract_opcode(instr_reg);
        case instr_opcode is
          when work.cpu_types.op_nop => null;
          when work.cpu_types.op_breq => ...
          ...
        end case;
      end loop;
    end process interpreter;

   end architecture behavioral;

The instruction set interpreter process declares a variable of the opcode type and uses the extract_opcode function to extract the bits representing the opcode from the fetched instruction word. It then uses the constants from the package as choices in a case statement to decode and execute the instruction specified by the opcode. Note that since the constants are used as choices in the case statement, they must be locally static. If we had deferred the values of the constants to the package body, their value would not be known when the case statement was analyzed. This is why we included the constant values in the package declaration. In general, the value of a deferred constant is not locally static.

Package Bodies

Now that we have seen how to define the interface to a package, we can turn to the package body. Each package declaration that includes subprogram declarations or deferred constant declarations must have a corresponding package body to fill in the missing details. However, if a package declaration only includes other kinds of declarations, such as types, signals or fully specified constants, no package body is necessary. The syntax rule for a package body is similar to that for the interface, but with the inclusion of the keyword body:

   package_body  ⇐
       package body identifier is
            { package_body_declarative_item}
       end [package body ] [ identifier] ;

The items declared in a package body must include the full declarations of all subprograms defined in the corresponding package declaration. These full declarations must include the subprogram headers exactly as they are written in the package declaration, to ensure that the implementation conforms with the interface. This means that the names, types, modes and default values of each of the formal parameters must be repeated exactly. There are only two variations allowed. First, a numeric literal may be written differently, for example, in a different base, provided it has the same value. Second, a simple name consisting just of an identifier may be replaced by a selected name, provided it refers to the same item. While this conformance requirement might seem an imposition at first, in practice it is not. Any reasonable text editor used to create a VHDL model allows the header to be copied from the package declaration with little difficulty. Similarly, a deferred constant defined in a package declaration must have its value specified by repeating the declaration in the package body, this time filling in the initialization expression as in a full constant declaration.

In addition to the full declarations of items deferred from the package declaration, a package body may include declarations of additional types, subtypes, constants and subprograms. These items are used to implement the subprograms defined in the package declaration. Note that the items declared in the package declaration cannot be declared again in the body (apart from subprograms and deferred constants, as described above), since they are automatically visible in the body. Furthermore, the package body cannot include declarations of additional signals. Signal declarations may only be included in the interface declaration of a package.

Example 7.5. A package of overloaded arithmetic operators

The package declaration outlined below declares overloaded versions of arithmetic operators for bit-vector values. The functions treat bit vectors as representing signed integers in binary form. Only the function headers are included in the package declaration.

   package bit_vector_signed_arithmetic is

    function "+" ( bv1, bv2 : bit_vector )
      return bit_vector;

    function "-" ( bv : bit_vector )
      return bit_vector;

    function "*" ( bv1, bv2 : bit_vector )
      return bit_vector;


   ...
   end package bit_vector_signed_arithmetic;

The package body contains the full function bodies. It also includes a function, mult_unsigned, not defined in the package declaration. It is used internally in the package body to implement the signed multiplication operator.

   package body bit_vector_signed_arithmetic is

    function "+" ( bv1, bv2 : bit_vector )
      return bit_vector is ...
   
    function "-" ( bv : bit_vector )
      return bit_vector is ...

    function mult_unsigned ( bv1, bv2 : bit_vector )
      return bit_vector is

      ...
    begin
      ...
    end function mult_unsigned;

    function "*" ( bv1, bv2 : bit_vector )
      return bit_vector is
    begin
      if not bv1(bv1'left) and not bv2(bv2'left) then
        return mult_unsigned(bv1, bv2);
      elsif not bv1(bv1'left) and bv2(bv2'left) then
        return -mult_unsigned(bv1, -bv2);
      elsif bv1(bv1'left) and not bv2(bv2'left) then
        return -mult_unsigned(-bv1, bv2);
      else
        return mult_unsigned(-bv1, -bv2);
      end if;
    end function "*";

     ...
   end package body bit_vector_signed_arithmetic;

One further point to mention on the topic of packages relates to the order of analysis. We mentioned before that a package is usually a separate design unit that is analyzed separately from other design units, such as entity declarations and architecture bodies. (We will return to the case of a package not being a design unit shortly.) In most cases, a package declaration and its corresponding package body are each separate design units; hence they may be analyzed separately. A package declaration is a primary design unit, and a package body is a secondary design unit. The package body depends on information defined in the package declaration, so the declaration must be analyzed first. Furthermore, the declaration must be analyzed before any other design unit that refers to an item defined by the package. Once the declaration has been analyzed, it does not matter when the body is analyzed in relation to units that use the package, provided it is analyzed before the model is elaborated. In a large suite of models, the dependency relationships can get quite complex, and a correct order of analysis can be difficult to find. A good VHDL tool suite will provide some degree of automating this process by working out the dependency relationships and analyzing those units needed to build a particular target unit to simulate or synthesize.

VHDL-87

The keywords package body may not be included at the end of a package body in VHDL-87.

Local Packages

We have mentioned that package declarations and package bodies are usually separate design units, but that there are cases where that is not so. VHDL allows us to declare a package locally within the declarative part of an entity, architecture body, process, or subprogram. This allows the visibility of the package to be contained to just the enclosing declarative part and the corresponding statement part. If the package declaration requires a package body, we must write the package body in the same declarative part after the package declaration. Since declarations written in a package body are not visible outside the package, we can use a local package to provide controlled access to locally declared items.

Example 7.6. A package to manage unique identification numbers

Suppose we need to generate test cases in a design, with each test case having a unique identification number. We can declare a package locally within a stimulus-generator process. The package encapsulates a variable that tracks the next identification number to be assigned, and provides an operation to yield the next number. The process outline is:

   Stim_gen : process is

    package ID_manager is
      impure function get_ID return natural;
    end package ID_manager;

    package body ID_manager is
      variable next_ID : natural := 0;
      impure function get_ID return natural is
        variable result : natural;
      begin
        result := next_ID;
        next_ID := next_ID + 1;
        return result;
      end function get_ID;
    end package body ID_manager;
    ...
    type test_case is record

        ...
        ID : natural;
   
      end record test_case;
    variable next_test_case : test_case;

   begin
      ...
      next_test_case.ID := ID_manager.get_ID;
      ID_manager.next_ID := 0;  -- Illegal
      ...
   end process stim_gen;

The variable next_ID is declared in the package body, and so is not visible outside the package. The only way to access it is using the get_ID function provided by the package declaration. This is shown in the first assignment statement within the process body. The package name is used as a prefix in the selected name for the function. The second assignment statement is illegal, since the variable is not visible at that point. The package provides a measure of safety against inadvertent corruption of the data state.

By writing the package locally within the process, it is only available in the process. Thus, we have achieved greater separation of concerns than if we had written the package as a design unit, making it globally visible. Moreover, since the package is local to a process, there can be no concurrent access by multiple processes. Thus, the encapsulated variable can be an ordinary non-shared variable. If the package were declared as a global design unit, there could be concurrent calls to the get_ID function. As a consequence, the variable would have to be declared as a shared variable, which would significantly complicate the design. (We describe shared variables in Chapter 23.)

We can also declare a local package within an enclosing package declaration, whether that enclosing package be a design unit or itself a local package. If the nested package is to be accessible outside the enclosing package, the nested package declaration must occur within the enclosing package declaration. The nested package body, if required, must then occur within the body of the enclosing package. As an example, the following outline shows an enclosing package outer with a nested package inner that declares a function f:

   package outer is
    ...
    package inner is
      impure function f ( ... ) return natural;
    end package inner;
    ...
   end package outer;
   package body outer is
    ...
    package body inner is
      ...
      impure function f ( ... ) return natural is

          ...
   
      end function f;
    end package body inner;
    ...
   end package body outer;

Outside the packages, we can refer to the function f with the name outer.inner.f.

If the enclosing package only requires a local package for private use, we can write both the nested package declaration and nested package body in the body of the enclosing package. The outline is:

   package outer is
    ...
   end package outer;

   package body outer is
    ...
    package inner is
      ...
    end package inner;
    package body inner is
      ...
    end package body inner;
    ...
   end package body outer;

In this case, items in the nested package are only accessible within the enclosing package body. While this scheme would not commonly arise in practice, it does serve to illustrate the consistency and general applicability of the visibility rules we introduced in Section 6.6.

VHDL-87, -93, and -2002

These versions of VHDL do not allow declaration of local packages. A package can only be declared as a design unit.

Use Clauses

We have seen how we can refer to an item provided by a package by writing its selected name, for example, work.cpu_types.status_value. This name refers to the item status_value in the package cpu_types stored in the library work. If we need to refer to this object in many places in a model, having to write the library name and package name becomes tedious and can obscure the intent of the model. We saw in Chapter 5 that we can write a use clause to make a library unit directly visible in a model, allowing us to omit the library name when referring to the library unit. Since an analyzed design-unit package is a library unit, use clauses also apply to making such packages directly visible. So we could precede a model with a use clause referring to the package defined in the example in Example 7.1:

   use work.cpu_types;

This use clause allows us to write declarations in our model more simply; for example:

   variable data_word : cpu_types.word;
   variable next_address : cpu_types.address;

In fact, the use clause is more general than this usage indicates and allows us to make any name from a library or package directly visible. Let us look at the full syntax rule for a use clause, then discuss some of the possibilities.

   use selected_name {, ...};
   selected_name  ⇐ name . ( identifier | character_literal | operator_symbol |all )

The syntax rule for names, shown in Appendix B, includes the ossibility of a name itself being either a selected name or a simple identifier. If we make these substitutions in the above syntax rule, we see that a selected name can be of the form

   identifier . identifier . ( identifier | character_literal | operator_symbol | all )

One possibility is that the first identifier is a library name, and the second is the name of a package within the library. This form allows us to refer directly to items within a package without having to use the full selected name. For example, we can simplify the above declarations even further by rewriting the use clause as

   use work.cpu_types.word, work.cpu_types.address;

The declarations can then be written as

   variable data_word : word;
   variable next_address : address;

We can place a use clause in any declarative part in a model. One way to think of a use clause is that it “imports” the names of the listed items into the part of the model containing the use clause, so that they can be used without writing the library or package name. The names become directly visible after the use clause, according to visibility rules similar to those we discussed in Chapter 6.

The syntax rule for a use clause shows that we can write the keyword all instead of the name of a particular item to import from a package. This form is very useful, as it is a shorthand way of importing all of the names defined in the interface of a package. For example, if we are using the IEEE standard-logic package as the basis for the data types in a design, it is often convenient to import everything from the standard-logic package, including all of the overloaded operator definitions. We can do this with a use clause as follows:

   use ieee.std_logic_1164.all;

This use clause means that the model imports all of the names defined in the package std_logic_1164 residing in the library ieee. This explains the “magic” that we have used in previous chapters when we needed to model data using the standard-logic types. The keyword all can be included for any package where we want to import all of the declarations from the package into a model.

We can also write use clauses for locally declared packages. Thus, in Example 7.6, we could follow the package declaration with the use clause

   use ID_manager.all;

and then rewrite the assignment in the process as

   next_test_case.ID := get_ID;

Example 7.7. A use clause for the CPU types package

Following is a revised version of the CPU architecture body outlined in Example 7.4. It includes a use clause referring to items declared in the cpu_types package. This makes the rest of the model considerably less cluttered and easier to read. The use clause is included within the declarative part of the instruction set interpreter process. Thus the names “imported” from the package are directly visible in the rest of the declarative part and in the body of the process.

   architecture behavioral of cpu is
   begin

    interpreter : process is

      use work.cpu_types.all;

      variable instr_reg : word;
      variable instr_opcode : opcode;

    begin
      ...  -- initialize
      loop
        ...  -- fetch instruction
        instr_opcode := extract_opcode ( instr_reg );
        case instr_opcode is
          when op_nop => null;
          when op_breq => ...
          ...
        end case;
      end loop;
    end process interpreter;

   end architecture behavioral;

While using all to import all declaration from a package is a common case, there are occasions when we would prefer to use just a type declared in a package. This might occur if we need to draw upon several packages, each of which declares numerous other items that could conflict with our own declarations and with items from other packages. However, just importing a type is not particularly useful, since we typically need to operate on values of the type. Fortunately, VHDL helps us by importing more than just the type name when we refer to a type in a use clause. In addition, the following are imported:

  • All of the predefined operations on the type, provided they are not hidden by overloaded versions also declared in the package.

  • Overloaded versions of predefined operations on the type declared in the package.

  • For an enumeration type or subtype, all of the enumeration literals. This includes any character literals of the type.

  • For a physical type or subtype, all of the unit names for the type.

For example, suppose we declare the following package:

   package stuff_pkg is

    type color_type is (red, orange, yellow, green, blue, violet);
    subtype warm_color is color_type range red to yellow;

    function "<" ( c1, c2 : color_type ) return boolean;
    function pretty ( c : color_type ) return boolean;

    type resistance is range 0 to 1E9 units
      Ohm;
      kOhm = 1000 Ohm;
      MOhm = 1000 kOhm;
    end units;

    subtype weak_logic is
      IEEE.std_logic_1164.std_ulogic range 'W' to 'H';

   end package stuff_pkg;

Then the use clause

   use stuff_pkg.color_type;

makes not only the type color_type visible, but also the enumeration literals red through violet, the predefined operations on color_type other than “<”, and the overloaded “<” operator declared in the package. It does not make the function pretty visible, since it is not an overloaded version of a predefined operation. If we write the use clause

   use stuff_pkg.warm_color;

it makes the subtype warm_color visible, along with all of the enumeration literals for color_type (not just those in the subtype) and the operations for color_type.

If we write the use clause

   use stuff_pkg.resistance;

it makes the type resistance visible, along with the unit names Ohm, kOhm, and MOhm, and the predefined operations on resistance.

Finally, if we write the use clause

   use stuff_pkg.weak_logic;

all we get is the subtype name weak_logic made visible, since none of the enumeration literals or operations are declared in the package stuff_pkg.

VHDL-87, -93, and -2002

According to a strict reading of the VHDL Language Reference Manual, these earlier versions do not import additional operations and other items if a use clause refers to a type name. Only the type name itself is imported. However, since that is not useful in practice, many implementations import at least the predefined operations, enumeration literals, and unit names. Implementations differ on whether they do this and whether they import any additional overloaded operations.

Visibility of Used Declarations

In general, a use clause makes a name directly visible in the enclosing declarative part and in the corresponding statement part. One of the most common places in which we write a use clause is at the beginning of a design unit. We saw in Section 5.4 how we may include library clauses, use clauses, and context references at the head of a design unit, such as an entity interface or architecture body. This area of a design unit is called its context clause. The names imported here are made directly visible throughout the design unit. For example, if we want to use the IEEE standard-logic type std_ulogic in the declaration of an entity, we might write the design unit as follows:

   library ieee;  use ieee.std_logic_1164.std_ulogic;

   entity logic_block is
     port ( a, b : in std_ulogic;
            y, z : out std_ulogic );
   end entity logic_block;

The library clause and the use clause together form the context clause for the entity declaration in this example. The library clause makes the contents of the library accessible to the model, and the use clause imports the type name std_ulogic declared in the package std_logic_1164 in the library ieee. By including the use clause in the context clause of the entity declaration, the std_ulogic type name is available when declaring the ports of the entity.

The names imported by a use clause in this way are made directly visible in the entire design unit after the use clause. In addition, if the design unit is a primary unit (such as an entity declaration or a package declaration), the visibility is extended to any corresponding secondary unit. Thus, if we include a use clause in the primary unit, we do not need to repeat it in the secondary unit, as the names are automatically visible there.

When we write use clauses in a model, there is potential to introduce conflicting names. The rules for dealing with such conflicts are related to those for dealing with overloading of subprograms and enumeration literals.

One form of conflict arises when we use an item declared in a package, and the declarative part containing the use clause declares another item with the same name. For example, our cpu_types package in earlier examples declares the subtype address. We might use this package in an architecture body as follows:

   use work.cpu_types.all;
   architecture behavior of cpu is
     signal address : bit_vector(15 downto 0);
     ...
   end architecture behavior;

The subtype name address identified by the use clause conflicts with the signal name address declared within the architecture body. VHDL resolves this conflict by not making the subtype name directly visible. Any reference to the simple name address in the architecture body refers to the signal. Of course, we can still use the selected name work.cpu_types.address within the architecture body to refer to the subtype if necessary. In general, this form of conflict arises when an item identified by a use clause has the same name as a locally declared item and either or both are not overloadable (that is, not a subprogram or an enumeration literal). The conflict is resolved by not making the item identified by the use clause directly visible. If both of the items are overloadable, then there is no conflict, since the normal rules for disambiguating subprogram calls apply.

Another form of conflict arises when we use two items of the same name declared in different packages. The rules for dealing with this form of conflict are more involved, and depend on whether the items are overloadable and whether either is a predefined operation. We’ll consider the cases in turn.

First, if one item is a predefined operation and the other is explicitly declared, only the explicitly declared item is made directly visible. Explicitly declared items in this case include overloaded versions of predefined operations, as well as other non-overloadable items. For example, suppose we declare two packages as follows:

   package short_int_types is
     type short_int is range 0 to 255;
   end package short_int_types

   use work.short_int_types.all;
   package short_int_ops is
     function "+" ( L, R : short_int ) return short_int;
     constant maximum : short_int := 255;
     ...
   end package short_int_ops;

The package short_int_types declares the integer type short_int. The predefined operations “+” and maximum are implicitly declared within this package also. The package short_int_ops explicitly declares an overloaded version of the “+” operation to perform modulo addition. It also declares the constant maximum, which is a non-overloadable item. Now suppose we include a use clause for both of these packages in an architecture body:

   use work.short_int_types.all, work.short_int_ops.all;
   architecture behavior of alu is
     ...
   end architecture behavior;

Within the architecture body, the explicitly declared “+” operation and maximum constant from the short_int_ops package are made directly visible. If we wanted to refer to the original predefined operations, we would have to write work.short_int_types.“+”and work.short_int.maximum.

The second case of conflict from two items of the same name used from different packages arises if both items are explicitly declared and either or both are not overloadable. In this case, neither item is made directly visible. For example, if we declare the following package:

   package controller_types is
     subtype address is bit_vector(2 downto 0);
     constant int_ack : address := "100";
   end package controller_types;

and use it together with the cpu_types package from earlier examples:

   use work.cpu_types.all, work.controller_types.all;
   architecture rtl of system is
    ...
   end architecture rtl;

the names address and int_ack are not made directly visible in the architecture. Both packages explicitly declare address as a subtype, which is a non-overloadable item. The package cpu_types declares int_ack as an enumeration type, which is overloadable, but controller_types declares int_ack as a constant which is not overloadable.

There is one case where what we might consider a conflict arises, but in fact a different rule comes into play. Suppose we write two packages, each of which explicitly declares overloaded subprograms, as follows:

   package int_ops is
     function increment ( a : inout integer; n : in integer := 1 );
   end package int_ops;

   package counter_ops is
     function increment ( c : inout integer; n : in integer := 1 );
   end package counter_ops;

If we use these two packages in a given design unit, as follows:

   use work.int_ops.all, work.counter_ops.all;

we might be tempted to say that, since the names and the types of the parameters are the same, the declarations conflict, and so neither version of increment would be made directly visible. However, the rules for disambiguating calls to overloaded procedures allow parameter names used in named association to be taken into account. Thus, the following two calls are unambiguous:

   increment ( a => count_value, n => -1 );
   increment ( c => count_value, n => -1 );

The first call refers to the increment procedure from package int_ops, whereas the second call refers to the increment procedure from package counter_ops. In order for this rule to come into play, the two subprogram names must be directly visible. Rather than there being a conflict, there may be an ambiguity in the call if positional association is used.

VHDL-87, -93, and -2002

According to a strict reading of the VHDL Language Reference Manual, these earlier versions did not resolve a conflict between an implicitly declared predefined operation in one package and an explicitly declared overloaded version in another package in the same way as VHDL-2008. Instead, both are made directly visible, and the rules for overload resolution are required to disambiguate calls.

Complicating this is the widely held, but incorrect, view that versions of a subprogram with the same names and parameter types used from different packages do conflict and are not made directly visible. Some tools implement this view, rather than making both subprograms directly visible, as described for the increment procedures above.

Exercises

1.

[Exercises7.1] Write a package declaration for use in a model of an engine management system. The package contains declarations of a physical type, engine_speed, expressed in units of revolutions per minute (RPM); a constant, peak_rpm, with a value of 6000 RPM; and an enumeration type, gear, with values representing first, second, third, fourth and reverse gears. Assuming the package is analyzed and stored in the current working library, write selected names for each of the items declared in the package.

2.

[Exercises7.1] Write a declaration for a procedure that increments an integer, as the procedure declaration would appear in a package declaration.

3.

[Exercises7.1] Write a declaration for a function that tests whether an integer is odd, as the function declaration would appear in a package declaration.

4.

[Exercises7.1] Write a deferred constant declaration for the real constant e = 2.71828.

5.

[Exercises7.2] Is a package body required for the package declaration described in Exercise 1?

6.

[Exercises7.3] Write a use clause that makes the engine_speed type from the package described in Exercise 1 directly visible.

7.

[Exercises7.3] Write a context clause that makes a library DSP_lib accessible and that makes an entity systolic_FFT and all items declared in a package DSP_types in the library directly visible.

8.

[Exercises7.1/7.2] Develop a package declaration and body that provide operations for dealing with time-of-day values. The package defines a time-of-day value as a record containing hours, minutes and seconds since midnight and provides deferred constants representing midnight and midday. The operations provided by the package are

  • comparison (“<”, “>”, “<=” and “>=”),

  • addition of a time-of-day value and a number of seconds to yield a time-of-day result and

  • subtraction of two time-of-day values to yield a number-of-seconds result.

9.

[Exercises7.1/7.2] Develop a package declaration and body to provide operations on character strings representing identifiers. An outline of the package declaration is

   package identifier_pkg is

     subtype identifier is string(1 to 15);

     constant max_table_size : integer := 50;
     subtype table_index is integer range 1 to max_table_size;
     type table is array (table_index) of identifier;
      ...

   end package identifier_pkg;

The package also declares a procedure to convert alphabetic characters in a string to lowercase and a procedure to search for an occurrence of a given identifier in a table. The search procedure has two out-mode parameters: a Boolean value indicating whether the sought string is in the table and a table_index value indicating its position, if present.

 

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

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