Throughout the previous chapters we have studiously avoided considering the case of multiple output ports connecting one signal. The problem that arises in such a case is determining the final value of the signal when multiple sources drive it. In this chapter we discuss resolved signals, the mechanism provided by VHDL for modeling such cases.
If we consider a real digital system with two outputs driving one signal, we can fairly readily determine the resulting value based on some analog circuit theory. The signal is driven to some intermediate state, depending on the drive capacities of the conflicting drivers. This intermediate state may or may not represent a valid logic state. Usually we only connect outputs in a design if at most one is active at a time, and the rest are in some high-impedance state. In this case, the resulting value should be the driving value of the single active output. In addition, we include some form of “pull-up” that determines the value of the signal when all outputs are inactive.
While this simple approach is satisfactory for some models, there are other cases where we need to go further. One of the reasons for simulating a model of a design is to detect errors such as multiple simultaneously active connected outputs. In this case, we need to extend the simple approach to detect such errors. Another problem arises when we are modeling at a higher level of abstraction and are using more complex types. We need to specify what, if anything, it means to connect multiple outputs of an enumeration type together.
The approach taken by VHDL is a very general one: the language requires the designer to specify precisely what value results from connecting multiple outputs. It does this through resolved signals, which are an extension of the basic signals we have used in previous chapters. A resolved signal includes in its definition a function, called the resolution function, that is used to calculate the final signal value from the values of all of its sources.
Let us see how this works by developing an example. We can model the values driven by a tristate output using a simple extension to the predefined type bit
, for example:
type tri_state_logic is ('0', '1', 'Z'),
The extra value, ‘Z’, is used by an output to indicate that it is in the high-impedance state. Next, we need to write a function that takes a collection of values of this type, representing the values driven by a number of outputs, and return the resulting value to be applied to the connected signal. For this example, we assume that at most one driver is active (‘0’ or ‘1’) at a time and that the rest are all driving ‘Z’. The difficulty with writing the function is that we should not restrict it to a fixed number of input values. We can avoid this by giving it a single parameter that is an unconstrained array of tri_state_logic
values, defined by the type declaration
type tri_state_logic_array is array (integer range <>) of tri_state_logic;
The declaration of the resolution function is
function resolve_tri_state_logic ( values : in tri_state_logic_array ) return tri_state_logic is variable result : tri_state_logic := 'Z'; begin for index in values'range loop if values(index) /= 'Z' then result := values(index); end if; end loop; return result; end function resolve_tri_state_logic;
The final step to making a resolved signal is to declare the signal, as follows:
signal s1 : resolve_tri_state_logic tri_state_logic;
This declaration is almost identical to a normal signal declaration, but with the addition of the resolution function name before the signal type. The signal still takes on values from the type tri_state_logic
, but inclusion of a function name indicates that the signal is a resolved signal, with the named function acting as the resolution function. The fact that s1
is resolved means that we are allowed to have more than one source for it in the design. (Sources include drivers within processes and output ports of components associated with the signal.) When a transaction is scheduled for the signal, the value is not applied to the signal directly. Instead, the values of all sources connected to the signal, including the new value from the transaction, are formed into an array and passed to the resolution function. The result returned by the function is then applied to the signal as its new value.
Let us look at the syntax rule that describes the VHDL mechanism we have used in the above example. It is an extension of the rules for the subtype indication, which we first introduced in Chapter 2 and 4. The combined rule is
subtype_indication ⇐ [ resolution_function_name ] type_mark [ range ( range_attribute_name | simple_expression ( to | downto ) simple_expression ) | ( discrete_range { , ... } ) ]
This rule shows that a subtype indication can optionally include the name of a function to be used as a resolution function. Given this new rule, we can include a resolution function name anywhere that we specify a type to be used for a signal. For example, we could write a separate subtype declaration that includes a resolution function name, defining a resolved subtype, then use this subtype to declare a number of resolved signals, as follows:
subtype resolved_logic is resolve_tri_state_logic tri_state_logic; signal s2, s3 : resolved_logic;
The subtype resolved_logic
is a resolved subtype of tri_state_logic
, with resolve_tri_state_logic
acting as the resolution function. The signals s2
and s3
are resolved signals of this subtype. Where a design makes extensive use of resolved signals, it is good practice to define resolved subtypes and use them to declare the signals and ports in the design.
The resolution function for a resolved signal is also invoked to initialize the signal. At the start of a simulation, the drivers for the signal are initialized to the expression included in the signal declaration, or to the default initial value for the signal type if no initialization expression is given. The resolution function is then invoked using these driver values to determine the initial value for the signal. In this way, the signal always has a properly resolved value, right from the start of the simulation.
Let us now return to the tristate logic type we introduced earlier. In the previous example, we assumed that at most one driver is ‘0’ or ‘1’ at a time. In a more realistic model, we need to deal with the possibility of driver conflicts, in which one source drives a resolved signal with the value ‘0’ and another drives it with the value ‘1’. In some logic families, such driver conflicts cause an indeterminate signal value. We can represent this indeterminate state with a fourth value of the logic type, ‘X’, often called an unknown value. This gives us a complete and consistent multivalued logic type, which we can use to describe signal values in a design in more detail than we can using just bit values.
Example 8.1. A four-state multivalued logic type and its use
The following package declaration and body define a four-state multivalued logic type.
package MVL4 is -- unresolved logic type type MVL4_ulogic is ('X', '0', '1', 'Z'), type MVL4_ulogic_vector is array (natural range <>) of MVL4_ulogic; function resolve_MVL4 ( contribution : MVL4_ulogic_vector ) return MVL4_ulogic; subtype MVL4_logic is resolve_MVL4 MVL4_ulogic; function "not" ( r : MVL4_ulogic ) return MVL4_ulogic; function "and" ( l, r : MVL4_ulogic ) return MVL4_ulogic; function "or" ( l, r : MVL4_ulogic ) return MVL4_ulogic; ... function to_X01 ( a : MVL4_ulogic ) return MVL4_ulogic; function "??" ( r : MVL4_ulogic ) return boolean; end package MVL4; -------------------------------------------------- package body MVL4 is type table is array (MVL4_ulogic, MVL4_ulogic) of MVL4_ulogic; constant resolution_table : table := -- 'X' '0' '1' 'Z' -- ------------------ ( ( 'X', 'X', 'X', 'X' ), -- 'X' ( 'X', '0', 'X', '0' ), -- '0' ( 'X', 'X', '1', '1' ), -- '1' ( 'X', '0', '1', 'Z' ) ); -- 'Z' function resolve_MVL4 ( contribution : MVL4_ulogic_vector ) return MVL4_ulogic is variable result : MVL4_ulogic := 'Z'; begin for index in contribution'range loop result := resolution_table(result, contribution(index)); end loop; return result; end function resolve_MVL4; function "not" ( r : MVL4_ulogic ) return MVL4_ulogic is begin case r is when '1' => return '0'; when '0' => return '1'; when others => return 'X'; end case; end function "not"; function "and" ( l, r : MVL4_ulogic ) return MVL4_ulogic is ... function "or" ( l, r : MVL4_ulogic ) return MVL4_ulogic is ... ... function to_X01 ( a : MVL4_ulogic ) return MVL4_ulogic is begin case a is when '0' | '1' => return a; when 'X' | 'Z' => return 'X'; end case; end function to_X01; function "??" ( r : MVL4_ulogic ) return boolean is begin return r = '1'; end function "??"; end package body MVL4;
The constant resolution_table
is a lookup table used to determine the value resulting from two source contributions to a signal of the resolved logic type. The resolution function uses this table, indexing it with each element of the array passed to the function. If any source contributes ‘X’, or if there are two sources with conflicting ‘0’ and ‘1’ contributions, the result is ‘X’. If one or more sources are ‘0’ and the remainder ‘Z’, the result is ‘0’. Similarly, if one or more sources are ‘1’ and the remainder ‘Z’, the result is ‘1’. If all sources are ‘Z’, the result is ‘Z’. The lookup table is a compact way of representing this set of rules. The package also declares overloaded versions of the logical operators and other functions. The to_X01
function ensures that a value is either a proper logic value (‘0’ or ‘1’) or an unknown value. The overloaded “??
” operator allows us to use values of the logic type in conditions with implicit conversion to boolean
.
We can use this package in a design for a tristate buffer. The entity declaration and a behavioral architecture body are
use work.MVL4.all; entity tri_state_buffer is port ( a, enable : in MVL4_ulogic; y : out MVL4_ulogic ); end entity tri_state_buffer; -------------------------------------------------- architecture behavioral of tri_state_buffer is begin y <= to_X01(a) when enable else 'Z' when not enable else 'X'; end architecture behavioral;
When the buffer is enabled, the buffer copies the input to the output, but with a ‘Z’ input value changed to ‘X’ by the to_X01
function. When the buffer is not enabled, it drives the value ‘Z’ on its output. If the enable
port is not a proper logic level, both conditions are false, so the buffer drives the unknown value on its output.
An architecture body for a logic block that uses the tristate buffer is:
use work.MVL4.all; architecture gate_level of misc_logic is signal src1, src1_enable : MVL4_ulogic; signal src2, src2_enable : MVL4_ulogic; signal selected_val : MVL4_logic; ... begin src1_buffer : entity work.tri_state_buffer(behavioral) port map ( a => src1, enable => src1_enable, y => selected_val ); src2_buffer : entity work.tri_state_buffer(behavioral) port map ( a => src2, enable => src2_enable, y => selected_val ); ... end architecture gate_level;
The signal selected_val
is a resolved signal of the multivalued logic type. It is driven by the two buffer output ports. The resolution function for the signal is used to determine the final value of the signal whenever a new transaction is applied to either of the buffer outputs.
The above examples have all shown resolved subtypes of scalar enumeration types. In fact, VHDL’s resolution mechanism is more general. We can use it to define a resolved subtype of any type that we can legally use as the type of a signal. Thus, we can define resolved integer subtypes, resolved composite subtypes and others. In the latter case, the resolution function is passed an array of composite values and must determine the final composite value to be applied to the signal.
Example 8.2. A package for a resolved array subtype
The package declaration and body below define a resolved array subtype. Each element of an array value of this subtype can be ‘X’, ‘0’, ‘1’ or ‘Z’. The unresolved type uword
is an unconstrained array of these values. The resolution function has an unconstrained array parameter consisting of elements of type uword
. The function uses the lookup table to resolve corresponding elements from each of the contributing sources and produces an array result. The subtype word
is the final resolved array subtype.
package words is type X01Z is ('X', '0', '1', 'Z'), type uword is array (natural range <>) of X01Z; type uword_vector is array (natural range <>) of uword; function resolve_word ( contribution : uword_vector ) return uword; subtype word is resolve_word uword; end package words; -------------------------------------------------- package body words is type table is array (X01Z, X01Z) of X01Z; constant resolution_table : table := -- 'X' '0' '1' 'Z' -- ------------------ ( ( 'X', 'X', 'X', 'X' ), -- 'X' ( 'X', '0', 'X', '0' ), -- '0' ( 'X', 'X', '1', '1' ), -- '1' ( 'X', '0', '1', 'Z' ) ); -- 'Z' function resolve_word ( contribution : uword_vector ) return uword is variable result : uword(contribution'element'range) := (others => 'Z'), begin for index in contribution'range loop for element in result'range loop result(element) := resolution_table( result(element), contribution(index)(element) ); end loop; end loop; return result; end function resolve_word; end package body words;
We can use these types to declare array ports in entity declarations and resolved array signals with multiple sources. For example, the following CPU entity and memory entity have bidirectional data ports of the unresolved array type.
use work.words.all; entity cpu is port ( address : out uword(23 downto 0); data : inout uword(31 downto 0); ... ); end entity cpu; -------------------------------------------------- use work.words.all; entity memory is port ( address : in uword(23 downto 0); data : inout uword(31 downto 0); ... ); end entity memory;
The architecture body for a computer system declares a signal of the resolved subtype and connects it to the data ports of the instances of the CPU and memory:
architecture top_level of computer_system is use work.words.all; signal address : uword(23 downto 0); signal data : word(31 downto 0); ... begin the_cpu : entity work.cpu(behavioral) port map ( address, data, ... ); the_memory : entity work.memory(behavioral) port map ( address, data, ... ); ... end architecture top_level;
A resolved composite subtype works well provided every source for a resolved signal of the subtype is connected to every element of the signal. For the data
signal shown in the example, every source must be a 32-element array and must connect to all 32 elements of the data signal. However, in a realistic computer system, sources are not always connected in this way. For example, we may wish to connect an 8-bit-wide device to the low-order eight bits of a 32-bit-wide data bus. We might attempt to express such a connection in a component instantiation statement, as follows:
boot_rom : entity work.ROM(behavioral) port map ( a => address, d => data(24 to 31), ... ); -- illegal
If we add this statement to the architecture body in Example 8.2, we have two sources for elements 0 to 23 of the data signal and three for elements 24 to 31. A problem arises when resolving the signal, since we are unable to construct an array containing the contributions from the sources. For this reason, VHDL does not allow us to write such a description; it is illegal.
The solution to this problem is to describe the data signal as an array of resolved elements, rather than as a resolved array of elements. One way of doing this is to declare an array type whose elements are values of the MVL4_logic
type, shown in Example 8.2. The array type declaration is
type MVL4_logic_vector is array (natural range <>) of MVL4_logic;
This approach has the advantage that the array type is unconstrained, so we can use it to create signals of different widths, each element of which is resolved. The problem, however, is that the type MVL4_logic_vector
is distinct from the type MVL4_ulogic_vector
, since they are defined by separate type declarations. Neither is a subtype of the other. Hence we cannot legally associate a signal of type MVL4_logic_vector
with a port of type MVL4_ulogic_vector
, or a signal of type MVL4_ulogic_vector
with a port of type MVL4_logic_vector
.
A better way to describe arrays of resolved elements is to declare an array subtype in which we associate the resolution function with the elements. In our example, the array base type is MVL4_ulogic_vector, which has unresolved elements. We declare the subtype as:
subtype MVL4_logic_vector is (resolve_MVL4) std_ulogic_vector;
The parentheses around the resolution function name, resolve_MVL4
, indicates that the resolution function is associated with each element of the array type, rather than with the array type as a whole. Since MVL4_logic_vector
is now a subtype of MVL4_ulogic_vector
, not a distinct type, we can freely assign and associate signals and ports of the two types.
This example illustrates 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 syntax rule is:
resolution_indication ⇐ resolution_function_name | ( resolution_indication | (record_element_identifier resolution_indication ) , {., ... ...})
If we want to associate a resolution function with an entire subtype, the resolution indication just consists of the resolution function name, as in the declaration of MVL4_logic
:
subtype MVL4_logic is resolve_MVL4 MVL4_ulogic;
The resolution indication here is just the resolution function name, resolve_MVL4
. 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 MVL4_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 MVL4_ulogic_vector;
we can declare a subtype with resolved nested elements:
subtype RAM_content_type is ((resolve_MVL4)) unresolved_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 : MVL4_ulogic; dirty : MVL4_ulogic; tag : MVL4_ulogic_vector; end record unresolved_status_type;
we can declare a subtype with the valid
element resolved by the function wired_and
as follows:
subtype status_resolved_valid is (valid wired_and) 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 wired_and, dirty wired_or) 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(resolve_MVL4)) unresolved_status_type;
We could combine all of these examples together, resolving all of the scalar subelements, as follows:
subtype resolved_status_type is ( tag(resolve_MVL4), valid wired_and, dirty wired_or ) 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 8.3. Connection to parts of a bus signal
Let us assume that the type MVL4_logic_vector
described above has been added to the package MVL4
. Below are entity declarations for a ROM entity and a single in-line memory module (SIMM), using the MVL4_ulogic_vector
type for their data ports. The data port of the SIMM is 32 bits wide, whereas the data port of the ROM is only 8 bits wide.
use work.MVL4.all; entity ROM is port ( a : in MVL4_ulogic_vector(15 downto 0); d : out MVL4_ulogic_vector(7 downto 0); rd : in MVL4_ulogic ); end entity ROM; -------------------------------------------------- use work.MVL4.all; entity SIMM is port ( a : in MVL4_ulogic_vector(9 downto 0); d : inout MVL4_ulogic_vector(31 downto 0); ras, cas, we, cs : in MVL4_ulogic ); end entity SIMM;
The following architecture body uses these two entities. It declares a signal, internal_data
, of the MVL4_logic_vector
type, representing 32 individually resolved elements. The SIMM entity is instantiated with its data port connected to all 32 internal data elements. The ROM entity is instantiated with its data port connected to the rightmost eight elements of the internal data signal. When any of these elements is resolved, the resolution function is passed contributions from the corresponding elements of the SIMM and ROM data ports. When any of the remaining elements of the internal data signal are resolved, they have one less contribution, since they are not connected to any element of the ROM data port.
architecture detailed of memory_subsystem is signal internal_data : MVL4_logic_vector(31 downto 0); ... begin boot_ROM : entity work.ROM(behavioral) port map ( a => internal_addr(15 downto 0), d => internal_data(7 downto 0), rd => ROM_select ); main_mem : entity work.SIMM(behavioral) port map ( a => main_mem_addr, d => internal_data, ... ); ... end architecture detailed;
At this point, let us summarize the important points about resolved signals and their resolution functions. Resolved signals of resolved subtypes are the only means by which we may connect a number of sources together, since we need a resolution function to determine the final value of the signal or port from the contributing values. The resolution function must take a single parameter that is a one-dimensional unconstrained array of values of the signal type, and must return a value of the signal type. The index type of the array does not matter, so long as it contains enough index values for the largest possible collection of sources connected together. For example, an array type declared as follows is inadequate if the resolved signal has five sources:
type small_int is range 1 to 4; type small_array is array (small_int range <>) of ... ;
The resolution function must be a pure function; that is, it must not have any side effects. This requirement is a safety measure to ensure that the function always returns a predictable value for a given set of source values. Furthermore, since the source values may be passed in any order within the array, the function should be commutative; that is, its result should be independent of the order of the values. When the design is simulated, the resolution function is called whenever any of the resolved signal’s sources is active. The function is passed an array of all of the current source values, and the result it returns is used to update the signal value. When the design is synthesized, the resolution function specifies the way in which the synthesized hardware should combine values from multiple sources for a resolved signal.
In previous chapters we have used the IEEE standard multivalued logic package, std_logic_1164
. We are now in a position to describe all of the types and subtypes provided by the package. We defer a full description of the operations provided by the package to Chapter 9, in which we also describe other standard packages based on the standard logic types. First, recall that the package provides the basic type std_ulogic
, defined as
type std_ulogic is ('U', 'X', '0', '1', 'Z', 'W', 'L', 'H', '-'),
and an array type std_ulogic_vector
, defined as
type std_ulogic_vector is array ( natural range <> ) of std_ulogic;
We have not mentioned it before, but the “u” in “ulogic” stands for unresolved. These types serve as the basis for the declaration of the resolved subtype std_logic
, defined as follows:
function resolved ( s : std_ulogic_vector ) return std_ulogic; subtype std_logic is resolved std_ulogic;
The standard-logic package also declares an array subtype of standard-logic elements, analogous to the bit_vector
type, for use in declaring array signals:
subtype std_logic_vector (resolved) std_ulogic_vector;
The standard defines the resolution function resolved
as follows:
type stdlogic_table is array (std_ulogic, std_ulogic) of std_ulogic; constant resolution_table : stdlogic_table := -- --------------------------------------------- -- 'U', 'X', '0', '1', 'Z', 'W', 'L', 'H', '-' -- --------------------------------------------- ( ( 'U', 'U', 'U', 'U', 'U', 'U', 'U', 'U', 'U' ), -- 'U' ( 'U', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X' ), -- 'X' ( 'U', 'X', '0', 'X', '0', '0', '0', '0', 'X' ), -- '0' ( 'U', 'X', 'X', '1', '1', '1', '1', '1', 'X' ), -- '1' ( 'U', 'X', '0', '1', 'Z', 'W', 'L', 'H', 'X' ), -- 'Z' ( 'U', 'X', '0', '1', 'W', 'W', 'W', 'W', 'X' ), -- 'W' ( 'U', 'X', '0', '1', 'L', 'W', 'L', 'W', 'X' ), -- 'L' ( 'U', 'X', '0', '1', 'H', 'W', 'W', 'H', 'X' ), -- 'H' ( 'U', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X' ) -- '-' ); function resolved ( s : std_ulogic_vector ) return std_ulogic is variable result : std_ulogic := 'Z'; -- weakest state default begin if s'length = 1 then return s(s'low); else for i in s'range loop result := resolution_table(result, s(i)); end loop; end if; return result; end function resolved;
VHDL tools are allowed to provide built-in implementations of this function to improve performance. The function uses the constant resolution_table
to resolve the driving values. If there is only one driving value, the function returns that value unchanged. If the function is passed an empty array, it returns the value ‘Z’. (The circumstances under which a resolution function may be invoked with an empty array will be covered in Section 23.1.) The value of resolution_table
shows exactly what is meant by “forcing” driving values (‘X’, ‘0’ and ‘1’) and “weak” driving values (‘W’, ‘L’ and ‘H’). If one driver of a resolved signal drives a forcing value and another drives a weak value, the forcing value dominates. On the other hand, if both drivers drive different values with the same strength, the result is the unknown value of that strength (‘X’ or ‘W’). The high-impedance value, ‘Z’, is dominated by forcing and weak values. If a “don’t care” value (‘–’) is to be resolved with any other value, the result is the unknown value ‘X’. The interpretation of the “don’t care” value is that the model has not made a choice about its output state. Finally, if an “uninitialized” value (‘U’) is to be resolved with any other value, the result is ‘U’, indicating that the model has not properly initialized all outputs.
In addition to this multivalued logic subtype, the package std_logic_1164
declares a number of subtypes for more restricted multivalued logic modeling. The subtype declarations are
subtype X01 is resolved std_ulogic range 'X' to '1'; -- ('X','0','1') subtype X01Z is resolved std_ulogic range 'X' to 'Z'; -- ('X','0','1','Z') subtype UX01 is resolved std_ulogic range 'U' to '1'; -- ('U','X','0','1') subtype UX01Z is resolved std_ulogic range 'U' to 'Z'; -- ('U','X','0','1','Z')
Each of these is a closed subtype; that is, the result of resolving values in each case is a value within the range of the subtype. The subtype X01Z
corresponds to the type MVL4
we introduced in Example 8.1.
In the previous discussion of resolved signals, we have limited ourselves to the simple case where a number of drivers or output ports of component instances drive a signal. Any input port connected to the resolved signal gets the final resolved value as the port value when a transaction is performed. We now look in more detail at the case of ports of mode inout
being connected to a resolved signal. The question to answer here is, What value is seen by the input side of such a port? Is it the value driven by the component instance or the final value of the resolved signal connected to the port? In fact, it is the latter. An inout
port models a connection in which the driver contributes to the associated signal’s value, and the input side of the component senses the actual signal rather than using the driving value.
Example 8.4. Distributed synchronization using a wired-and signal
Some asynchronous bus protocols use a distributed synchronization mechanism based on a wired-and control signal. This is a single signal driven by each module using active-low open-collector or open-drain drivers and pulled up by the bus terminator. If a number of modules on the bus need to wait until all are ready to proceed with some operation, they use the control signal as follows. Initially, all modules drive the signal to the ‘0’ state. When each is ready to proceed, it turns off its driver (‘Z’) and monitors the control signal. So long as any module is not yet ready, the signal remains at ‘0’. When all modules are ready, the bus terminator pulls the signal up to the ‘1’ state. All modules sense this change and proceed with the operation.
Following is an entity declaration for a bus module that has a port of the unresolved type std_ulogic
for connection to such a synchronization control signal.
library ieee; use ieee.std_logic_1164.all; entity bus_module is port ( synch : inout std_ulogic; ... ); end entity bus_module;
The architecture body for a system comprising several such modules is outlined below. The control signal is pulled up by a concurrent signal assignment statement, which acts as a source with a constant driving value of ‘H’. This is a value having a weak strength, which is overridden by any other source that drives ‘0’. It can pull the signal high only when all other sources drive ‘Z’.
architecture top_level of bus_based_system is signal synch_control : std_logic; ... begin synch_control_pull_up : synch_control <= 'H'; bus_module_1 : entity work.bus_module(behavioral) port map ( synch => synch_control, ... ); bus_module_2 : entity work.bus_module(behavioral) port map ( synch => synch_control, ... ); ... end architecture top_level;
An outline of a behavioral architecture body for the bus module is:
architecture behavioral of bus_module is begin behavior : process is ... begin synch <= '0' after Tdelay_synch; ... -- ready to start operation synch <= 'Z' after Tdelay_synch; wait until synch = 'H'; -- proceed with operation ... end process behavior; end architecture behavioral;
Each instance initially drives its synchronization port with ‘0’. This value is passed up through the port and used as the contribution to the resolved signal from the entity instance. When an instance is ready to proceed with its operation, it changes its driving value to ‘Z’, modeling an open-collector or open-drain driver being turned off. The process then suspends until the value seen on the synchronization port changes to ‘H’. If other instances are still driving ‘0’, their contributions dominate, and the value of the signal stays ‘0’. When all other instances eventually change their contributions to ‘Z’, the value ‘H’ contributed by the pull-up statement dominates, and the value of the signal changes to ‘H’. This value is passed back down through the ports of each instance, and the processes all resume.
Just as a signal declared with a signal declaration can be of a resolved subtype, so too can a port declared in an interface list of an entity. This is consistent with all that we have said about ports appearing just like signals to an architecture body. Thus if the architecture body contains a number of processes that must drive a port or a number of component instances that must connect outputs to a port, the port must be resolved. The final value driven by the resolved port is determined by resolving all of the sources within the architecture body. For example, we might declare an entity with a resolved port as follows:
library ieee; use ieee.std_logic_1164.all; entity IO_section is port ( data_ack : inout std_logic; ... ); end entity IO_section;
The architecture body corresponding to this entity might instantiate a number of I/O controller components, each with their data acknowledge ports connected to the data_ack
port of the entity. Each time any of the controllers updates its data acknowledge port, the standard-logic resolution function is invoked. It determines the driving value for the data_ack
port by resolving the driving values from all controllers.
Example 8.5. A memory system with tristate buses
We can write a model for a memory system composed of multiple memory devices with tristate data buses. The entity declaration for the memory devices is
library ieee; use ieee.std_logic_1164.all; entity memory_256Kx8 is port ( ce_n, oe_n, we_n : in std_ulogic; a : in std_ulogic_vector(17 downto 0); d : inout std_ulogic_vector(7 downto 0) ); end entity memory_256Kx8;
The d
port of the entity has unresolved elements because there is only one source for each element within the memory device. The entity declaration for the memory system is
library ieee; use ieee.std_logic_1164.all; entity memory_1Mx8 is port ( ce_n, oe_n, we_n : in std_ulogic; a : in std_ulogic_vector(19 downto 0); d : inout std_logic_vector(7 downto 0) ); end entity memory_1Mx8;
In this case, the d
port is of type std_logic_vector
with resolved elements, since internally there are multiple sources, one per memory device. The structural architecture is
architecture struct of memory_1Mx8 is signal ce_decoded_n : std_ulogic_vector(3 downto 0); begin with a(19 downto 18) select ce_decoded_n <= "1110" when "00", "1101" when "01", "1011" when "10", "0111" when "11", "XXXX" when others; chip0 : component memory_256Kx8 port map ( ce_n => ce_decoded_n(0), oe_n => oe_n, we_n => we_n, a => a(17 downto 0), d => d ); chip1 : component memory_256Kx8 port map ( ce_n => ce_decoded_n(1), oe_n => oe_n, we_n => we_n, a => a(17 downto 0), d => d ); ... end architecture struct;
We can connect the d
port of each memory-device instance directly to the d
port of the memory system. The contributions of all of the instances are resolved to form the driving value of the memory system’s d
port. Had we inadvertently declared the d
port of the memory system entity to be of type std_ulogic_vector
, the analyzer would detect the error arising from multiple sources connected to the unresolved elements.
If it happens that the actual signal associated with a resolved port in an enclosing architecture body is itself a resolved signal, then the signal’s resolution function will be called separately after the port’s resolution function has determined the port’s driving value. Note that the signal in the enclosing architecture body may use a different resolution function from the connected port, although in practice most designs use the one function for resolution of all signals of a given subtype.
An extension of the above scenario is a design in which there are several levels of hierarchy, with a process nested at the deepest level generating a value to be passed out through resolved ports to a signal at the top level. At each level, a resolution function is called to determine the driving value of the port at that level. The value finally determined for the signal at the top level is called the effective value of the signal. It is passed back down the hierarchy of ports as the effective value of each in
-mode or inout
-mode port. This value is used on the input side of each port.
Example 8.6. Hierarchical resolution in a computer system model
Figure 8.1 shows the hierarchical organization for a single-board computer system, consisting of a frame buffer for a video display, an input/output controller section, a CPU/memory section and a bus expansion block. These are all sources for the resolved data bus signal. The CPU/memory section in turn comprises a memory block and a CPU/cache block. Both of these act as sources for the data port, so it must be a resolved port. The cache has two sections, both of which act as sources for the data port of the CPU/cache block. Hence, this port must also be resolved.
Let us consider the case of one of the cache sections updating its data port. The new driving value is resolved with the current driving value from the other cache section to determine the driving value of the CPU/cache block data port. This result is then resolved with the current driving value of the memory block to determine the driving value of the CPU/memory section. Next, this driving value is resolved with the current driving values of the other top-level sections to determine the effective value of the data bus signal. The final step involves propagating this signal value back down the hierarchy for use as the effective value of each of the data ports. Thus, a module that reads the value of its data port will see the final resolved value of the data bus signal. This value is not necessarily the same as the driving value it contributes.
Figure 8.1. A hierarchical block diagram of a single-board computer system, showing the hierarchical connections of the resolved data bus ports to the data bus signal.
In Chapter 5, we indicated that we can read the value of a buffer
-mode or out
-mode port within an architecture of an entity. There is an important distinction between this and reading the value of an inout
-mode port. As we mentioned above, the value seen internally for an inout
-mode port is the effective value of the externally connected signal. Information is passed from the external connection into the architecture, as suggested by the “in
” part of the inout
port mode. In contrast, when we read a buffer
-mode or out
-mode port, the value seen internally is the driving value of the port. There is no transmission of information into the architecture. We use a buffer
-mode or out
-mode port for designs that have buffered internal connections or that read the value of the port for internal verification, for example, using assertions.
Example 8.7. Verification of tristate disconnection timing
Suppose we wish to verify that the outputs of a device are all ‘Z’ within a required interval of the device being disabled and remain all ‘Z’ until the device is enabled. The output values are not required internally to implement any functionality for the device. Hence, we declare the output ports using out
mode, as follows:
entity device is port ( en : in std_ulogic; d_out : out std_ulogic_vector(7 downto 0); ... ); end entity device;
We can read the values driven onto the output ports in verification code in the architecture:
architecture verifying of device is constant T_z : delay_length := 200 ps; begin d_out <= ... when en else ... when not en else "XXXXXXXX"; assert en or (not en'delayed(T_z) and d_out ?= "ZZZZZZZZ"); end architecture verifying;
Since the value seen on a signal or on an inout
-mode port may be different from the value driven by a process, VHDL provides an attribute, ‘driving_value
, that allows the process to read the value it contributes to the prefix signal. For example, if a process has a driver for a resolved signal s
, it may be driving s
with the value ‘Z’ from a previously executed signal assignment statement, but the resolution function for s
may have given it the value ‘0’. The process can refer to s‘driving_value
to retrieve the value ‘Z’. Note that a process can only use this attribute to determine its own contribution to a signal; it cannot directly find out another process’s contribution.
Let us now return to the topic of subprograms with signal parameters and see how they behave in the presence of resolved signals. Recall that when a procedure with an out
-mode signal parameter is called, the procedure is passed a reference to the caller’s driver for the actual signal. Any signal assignment statements performed within the procedure body are actually performed on the caller’s driver. If the actual signal parameter is a resolved signal, the values assigned by the procedure are used to resolve the signal value. No resolution takes place within the procedure. In fact, the procedure need not be aware that the actual signal is resolved.
In the case of reading a signal parameter to a function or procedure, a reference to the actual signal parameter is passed when the subprogram is called, and the subprogram uses the actual value of the signal. If the signal is resolved, the subprogram sees the value determined after resolution. In the case of an inout
signal parameter, a procedure is passed references to both the signal and its driver, and no resolution is performed internally to the procedure.
Example 8.8. Procedures for distributed synchronization
We can encapsulate the distributed synchronization protocol described in Example 8.4 in a set of procedures, each with a single signal parameter, as follows:
procedure init_synchronize ( signal synch : out std_logic ) is begin synch <= '0'; end procedure init_synchronize; procedure begin_synchronize ( signal synch : inout std_logic; Tdelay : in delay_length := 0 fs ) is begin synch <= 'Z' after Tdelay; wait until synch; end procedure begin_synchronize; procedure end_synchronize ( signal synch : inout std_logic; Tdelay : in delay_length := 0 fs ) is begin synch <= '0' after Tdelay; wait until not synch; end procedure end_synchronize;
Suppose a process uses a resolved signal barrier
of subtype std_logic
to synchronize with other processes. The process can use the procedures to implement the protocol as follows:
synchronized_module : process is ... begin init_synchronize(barrier); ... loop ... begin_synchronize(barrier); ... -- perform operation, synchronized with other processes end_synchronize(barrier); ... end loop; end process synchronized_module;
The process has a driver for barrier
, since the procedure calls associate the signal as an actual parameter with formal parameters of mode out
and inout
. A reference to this driver is passed to init_synchronize
, which assigns the value ‘0’ on behalf of the process. This value is used in the resolution of barrier
. When the process is ready to start its synchronized operation, it calls begin_synchronize
, passing references to its driver for barrier
and to the actual signal itself. The procedure uses the driver to assign the value ‘Z’ on behalf of the process and then waits until the actual signal changes to ‘H’. When the transaction on the driver matures, its value is resolved with other contributions from other processes and the result applied to the signal. This final value is used by the wait statement in the procedure to determine whether to resume the calling process. If the value is ‘H’, the process resumes, the procedure returns to the caller and the operation goes ahead. When the process completes the operation, it calls end_synchronize
to reset barrier
back to ‘0’.
[8.1] Suppose there are four drivers connected to a resolved signal that uses the resolution function
| |
[ 8.1] Rewrite the following resolved signal declaration as a subtype declaration followed by a signal declaration using the subtype. signal synch_control : wired_and tri_state_logic := '0'; | |
[ 8.1] What is the initial value of the following signal of the type signal int_req : MVL4_logic; | |
[ 8.1] Does the result of the resolution function defined in Example 8.1 depend on the order of contributions from drivers in the array passed to the function? | |
[ 8.1] Suppose we define a resolved array subtype
| |
[ 8.1] Suppose a signal is declared as signal data_bus : MVL4_logic_vector(0 to 15); where data_bus <= "ZZZZZZZZZZZZZZZZ"; data_bus(0 to 7) <= "XXXXZZZZ"; data_bus(8 to 15) <= "00111100"; What is the resolved signal value after all of the transactions have been performed? | |
[ 8.1] Suppose there are four drivers connected to a signal of type
| |
[ 8.2] Figure 8.2 is a timing diagram for the system with two bus modules using the wired-and synchronization signal described in Example 8.4. The diagram shows the driving values contributed by each of the bus modules to the | |
[8.2] Suppose all of the modules in the hierarchy of Figure 8.1 use resolved ports for their data connections. If the Mem, Cache, Serial and DMA modules all update their data drivers in the same simulation cycle, how many times is the resolution function invoked to determine the final resolved values of the data signals? | |
[ 8.2] Suppose a process in a model drives a bidirectional port | |
11. | [ 8.1] Develop a model that includes two processes, each of which drives a signal of the type |
12. | [ 8.1] Develop a model of an inverter with an open-collector output of type |
13. | [ 8.1] Develop a behavioral model of an 8-bit-wide bidirectional transceiver, such as the 74245 family of components. The transceiver has two bidirectional data ports, |
14. | [ 8.1] Many combinatorial logic functions can be implemented in integrated circuits using pass transistors acting as switches. While a pass transistor is, in principle, a bidirectional device, for many circuits it is sufficient to model it as a unidirectional device. Develop a model of a unidirectional pass transistor switch, with an input port, an output port and an enable port, all of type |
15. | [ 8.1] Develop a behavioral model of a tristate buffer with data input, data output and enable ports, all of type |
16. | [ 8.1] Use the unidirectional pass transistor model of Exercise 14 in a structural model of a four-input multiplexer. The multiplexer has select inputs |
17. | [ 8.1] Develop a model of a distributed priority arbiter for a shared bus in a multiprocessor computer system. Each bus requester has a request priority, R, between 0 and 31, with 0 indicating the most urgent request and 31 indicating no request. Priorities are binary-encoded using 5-bit vectors, with bit 4 being the most-significant bit and bit 0 being the least-significant bit. The standard-logic values ‘H’ and ‘1’ both represent the binary digit 1, and the standard-logic value ‘0’ represents the binary digit 0. All requesters can drive and sense a 5-bit arbitration bus, A, which is pulled up to ‘H’ by the bus terminator. The requesters each use A and their own priority to compute the minimum of all priorities by comparing the binary digits of priorities as follows. For each bit position i: Tpd is the propagation delay between sensing a value on A and driving a resulting value on A. When the value on A has stabilized, it is the minimum of all request priorities. The requester with R = A wins the arbitration. If you are not convinced that the distributed minimization scheme operates as required, trace its execution for various combinations of priority values. |
18. | [] Develop a behavioral model of a telephone keypad controller. The controller has outputs Each key in the keypad is a single-pole switch that shorts the row signal to the column signal when the key is pressed. Due to the mechanical construction of the switch, “switch bounce” occurs when the key is pressed. Several intermittent contacts are made between the signals over a period of up to 5 ms before a sustained contact is made. Bounce also occurs when the key is released. Several intermittent contacts may occur over the same period before sustained release is achieved. The keypad controller scans the keypad by setting each of the column signals to ‘0’ in turn. While a given column signal is ‘0’, the controller examines each of the row inputs. If a row input is ‘H’, the switch between the column and the row is open. If the row input is ‘0’, the switch is closed. The entire keypad is scanned once every millisecond. The controller generates a set of column outputs |
19. | [] The IEEE standard-logic type models two drive strengths: forcing and weak. This is insufficient to model detailed operation of circuits at the switch level. For example, in circuits that store a charge on the gate terminal of a MOS transistor, we need to distinguish the weaker capacitive drive strength of the stored value from the resistive strength of a value transmitted through a pass transistor. Develop a package that defines a resolved type similar to |
20. | [] Exercise 19 describes a logic type that incorporates three drive strengths. If we need to model switch level circuits in finer detail, we can extend the type to deal with an arbitrary number of drive strengths. Each time a signal is transmitted through a pass transistor, its drive strength is diminished. We can model this by representing a logic value as a record containing the bit value (‘0’, ‘1’ or unknown) and an integer representing the strength. We use 0 to represent power supply strength and a positive integer n to represent the strength of a signal after being transmitted through n pass transistors from the power supply. A normal driver has strength 1, to reflect the fact that it derives the driving value by turning on a transistor connected to one or the other power supply rail. (This scheme is described by Smith and Acosta in [14].) Develop a package that defines a resolved type based on this scheme. Include functions for separating the bit value and strength components of a combined value, for constructing a combined value from separate bit value and strength components and for weakening the strength component of a combined value. Use the package to model a pass transistor component. Then use the pass transistor in a model of an eight-input multiplexer similar to the four-input multiplexer of Exercise 16. |
21. | [] Self-timed asynchronous systems use handshaking to synchronize operation of interacting modules. In such systems, it is sometimes necessary to synchronize a number of modules at a rendezvous. Each module waits until all modules are ready to perform an operation. When all are ready, the operation commences. A scheme for rendezvous synchronization of a number of modules using three wired-and control signals was first proposed by Sutherland et al. for the TRIMOSBUS [15] and was subsequently adopted for use in the arbitration protocol of the IEEE Futurebus [11]. Develop a high-level model of a system that uses the three-wire synchronization scheme. You should include a package to support your model. The package should include a type definition for a record containing the three synchronization wires and a pair of procedures, one to wait for a rendezvous and another to leave the rendezvous after completion of the operation. The procedures should have a bidirectional signal parameter for the three-wire record and should determine the state of the synchronization protocol from the parameter value. |
18.119.167.248