One of the main purposes of VHDL is modeling the behavior of hardware. This chapter describes a number of features in VHDL-2008 that make the modeling task easier. All of the modeling tasks described here can be expressed in earlier versions of VHDL, but not as succinctly.
When we instantiate a component in VHDL, we write a port map to specify the signals connected to the ports of the instance. If an input port is to be tied to a fixed value, we can write an expression in the port map in place of a signal name. In earlier versions of VHDL, the expression was required to be static; that is, the expression’s value could not change during execution of the model. A common example is a simple literal value representing tying an unused input high or low:
inst : component adder32 port map ( . . ., carry_in => '0', . . . );
In VHDL-2008, the rules for writing an expression in a port map are generalized to include nonstatic expressions involving the values of signals. This allows us to include a small amount of functional logic in a port map, and avoids the need to express the logic with a separate assignment statement and an intermediate signal. If the expression is not static, the port association is defined to be equivalent to association with an anonymous signal that is the target of a signal assignment with the expression on the right-hand side.
Example 6.1. Select logic in a port map
Suppose an I/O controller connected to a CPU bus is to be enabled when bus control signals indicate a read from I/O address space and the bus address matches the controller’s address. We can include the select logic in the port map for the controller instance:
io_ctrl_1 : entity work.io_controller(rtl) port map ( en => rd_en and io_sel and addr ?= io_base, . . . );
This is a much more succinct way of expressing the model than the equivalent:
signal en_tmp : std_ulogic; . . . en_tmp <= rd_en and io_sel and addr ?= io_base; io_ctrl_1 : entity work.io_controller(rtl) port map ( en => en_tmp, . . . );
Most of the time, it is a straightforward matter to determine whether an expression in a port map is static, denoting a fixed value for the port, or nonstatic, implying connection to additional logic. However, if the expression is in the form of a function call applied to a signal name, there are two possible interpretations, one as a nonstatic expression implying connection to logic, and the other as a conversion function implying a change of representation of a value. An example of the latter is:
signal s : signed; component abstract_ALU is port ( a : in integer; . . . ); end component; . . . ALU : component abstract_ALU port map ( a => to_integer(s), . . . );
The reason for making the distinction is that the interpretation as a nonstatic expression introduces a delta delay between the input signal changing value and the port changing value, whereas the interpretation as application of a conversion function does not. If we were to write a function with one parameter representing some computational logic, for example:
function increment ( x : unsigned ) return unsigned;
and use it in a port map:
op_counter : component reg16 port map ( d_in => increment(op_count), . . . );
it would not be clear how to interpret the expression. Under the rules of earlier versions of VHDL, the expression would be interpreted as a conversion function, which is not what we want. To make the intention explicit, we can include the reserved work inertial in the port association to imply an inertial signal assignment of the expression to the anonymous intermediate signal. Thus, we would write the port map as
op_counter : component reg16 port map ( d_in => inertial increment(op_count), . . . );
Under the VHDL-2008 rules, if we omit the reserved word and the expression can be interpreted as application of a conversion function or a type conversion, then that interpretation takes precedence.
The sensitivity list of a process statement specifies the signals that cause the process to resume execution. For a process representing combinational logic, we should include all of the input signals of the logic in the sensitivity list. If we omit an input signal, a synthesis tool would infer latch-based storage, since the output would remain unchanged when the omitted signal changed.
Using earlier versions of VHDL, ensuring that all input signals are included in a sensitivity list is cumbersome and error prone. We must carefully check the body of the process and any subprograms called by the process to assemble the list of signals that are read. If we subsequently revise the model code, we must remember to update each process’s sensitivity list if we update the process’s statements. In VHDL-2008, writing and maintaining processes representing combinational logic is much simpler. We can replace the list of signals in the sensitivity list with the reserved word all, indicating that the process is sensitive to all signals read by the process.
Example 6.2. Combinational logic for a finite-state machine
One place where assembling the sensitivity list for a combinational process often causes problems is the next-state and output logic for a finite-state machine. The logic has, as inputs, the current state signal and signals whose values determine the next state and the output values. An example is:
next_state_logic : process (all ) is begin out1 <= '0'; out2 <= '0'; . . . case current_state is when idle => out1 <= '1'; if in1 and not in2 then out2 <= '1'; next_state <= busy1; elsif in1 and in2 then next_state <= busy2; else next_state <= idle; end if; . . . end case; end process next_state;
As we revise the finite-state machine, we might include more signals as inputs. Using the reserved word all instead of explicitly listing the input signals makes the process easier to write and maintain.
The list of signals to which the process is made sensitive also includes signals read in subprograms called directly or indirectly by the process. A proviso, however, is that a subprogram declared in a separate design unit from the process must only read its signal parameters. It cannot read signals declared globally in packages or signals identified using external names (see Section 2.1). The rationale for this restriction is to ensure the analyzer can assemble the sensitivity list for the process just by analyzing the enclosing design unit. It also means that we can perform a similar analysis to understand a process. We don’t get any surprises from unexpected external signals becoming inputs to the logic implied by a process.
In many designs, we compute a value and assign it to an output port, and then use the value in further computations within the design. There are two ways in which we might do this. On one hand, the value might be used to implement further behavior of the design. An example is a circuit with both active-high and active-low outputs. We derive a value for the active-high output, and then negate it for the active-low output. One the other hand, the value might be used in verification of the design’s functionality. The computation using the value is passive and does not imply additional hardware.
VHDL provides two modes for output ports, out and buffer. An out-mode port is intended to be used for cases where there is no internal use of the port’s value. Prior to VHDL-2008, we could not read the value of an out-mode port. A buffer-mode port is intended to be used for cases where the port’s driving value is used internally. As the name suggests, a hardware buffer is implied between the driver assigning values to the port and the external connections. The value used internally is taken from the inside of the buffer.
Prior to VHDL-2002, there were somewhat restrictive rules governing connection of buffer-mode ports of a component instance to the buffer-mode and out-mode ports of an enclosing entity. These restrictions made it very difficult to use buffer-mode ports effectively, so designers largely ignored them. It became common practice to declare an internal signal in a design, to use that signal internally, and to include a separate signal assignment to drive the signal’s value onto an out-mode port.
In VHDL-2002, the rules for buffer-mode ports were relaxed, allowing them to be connected to external out-mode and buffer-mode ports. It is no longer necessary to resort to an internal signal when a port’s driving value is needed internally as an input to further logic. Nonetheless, the practice persists, both for backward compatibility with previous versions of VHDL, and because many designers are not aware of the change.
Example 6.3. True and complement outputs for a flip-flop
A flip-flop having both true (active-high) and complement (active-low) outputs can be modeled using out-mode ports and an internal signal as follows:
entity Dff is port ( clk, d : in bit; q, q_n : out bit ); end entity Dff; architecture rtl of Dff is signal q_int : bit begin ff : process (clk) is begin if clk = '1' then q_int <= d; end if ; end process ff; q <= q_int; q_n <= not q_int; end architecture rtl;
By deriving the active-low output from the active-high internal signal, we avoid having a synthesis tool infer two separate flip-flops. Under the relaxed rules for buffer-mode ports in VHDL-2002, we can rewrite this model as follows:
entity Dff is port (clk, d : in bit; q : buffer bit; q_n : out bit ); end entity Dff; architecture rtl of Dff is begin ff : process (clk) is begin if clk = '1' then q <= d; end if ; end process ff; q_n <= not q; end architecture rtl;
In VHDL-2008, the restriction on reading out-mode ports is also removed. The value of an out-mode port seen internally is the same as the value being driven onto the port. Thus, an out-mode port has the same behavior in VHDL-2008 as a buffer-mode port. However, the rationale for allowing reading of an out-mode port is to support verification of a design’s behavior, as opposed to implying a hardware buffer. We should choose between out and buffer modes for a port depending on whether we intend to imply hardware buffering or passive verification, respectively. The choice of port mode documents our intention. Note that this choice would be made as a convention, rather than being enforced by the language definition. A tool has no way of distinguishing between the two ways of using a port.
Example 6.4. Reading an out-mode Port for verification
Suppose we wish to verify that the outputs of a device are all ’2’ within a required interval of the device being disabled and remain all ’2’ 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 generic ( T_z : delay_length ); 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 begin d_out <= . . . when to_x01(en) = '1' else . . . when to_x01(en) = '0' else "XXXXXXXX"; assert (to_x01(en'delayed(T_z)) = '0' and d_out = "ZZZZZZZZ") or to_x01(en) = '1'; end architecture verifying;
VHDL-2008 also removes the restriction on reading the value of an out-mode parameter of a procedure. Out-mode parameters can be either signal parameters or variable parameters. In the case of an out-mode signal parameter, when we call the procedure and pass an actual signal, the procedure is passed a reference to the actual signal. The value we get when we read the parameter within the procedure is the current value of the actual signal.
In the case of an out-mode variable parameter, the rules are slightly different. The formal parameter of the procedure is treated like a local variable, and is initialized when the procedure is called. It does not take on the value of the actual parameter variable at that stage. As we make assignments to the parameter within the procedure, the local value is updated. When we read the parameter, we get that local value. Eventually, when the procedure returns, the final value of the parameter is copied to the actual variable.
To illustrate this behavior, consider the following procedure:
procedure do_funny ( p : out positive ) is variable tmp : positive; begin tmp := p; p := tmp * 2; end procedure do_funny;
Suppose we call the procedure as follows:
variable v : positive := 10;
. . .
do_funny(v)
When the procedure is called, the parameter p
is initialized, not with the value of v, but with the default initial value for positive
, namely, 1. Thus, the value read and assigned to tmp
is 1, and the value assigned to p
in the last statement is 2. When the procedure returns, the final value of p
, namely, 2, is copied out to v.
Example 6.5. Checking an out-mode status parameter
A procedure to read multiple items from a line of input might have an out-mode parameter to indicate whether items were read successfully. We can use the parameter as the actual to textio read
procedures, and check that each call succeeded before continuing, as follows:
procedure read_time_and_int ( L : inout line; T : out time; I : out integer; good : out boolean ) is begin read(L, T, good); if not good then report "Read of time failed" severity failure; return; end if; read(L, I, good) ; if not good then report "Read of integer failed" severity failure; end if; end procedure read_time_and_int;
If the procedure could not read the value of the out-mode parameter good
, it would have to declare an internal variable to pass to the read
procedures, and copy the value to good
upon returning. The ability to read the out-mode parameter makes the procedure simpler and easier to maintain.
An array aggregate provides a way of forming an array from a collection of elements. In earlier versions of VHDL, we could only form an aggregate from individual elements. In VHDL-2008, the rules are extended to allow us to form an aggregate from a mixture of individual elements and slices of the array. For example, a bit_vector
array aggregate could be written as follows:
('0', "1001", '1')
This forms a 6-element vector from the single element ‘O’, the vector value “1001” and the single element ‘1’. The vector value forms a slice of the final aggregate value. The effect is similar to concatenating the element and array values. While this illustrates the idea, a more powerful use of the feature involves writing an aggregate as the target of an assignment statement. We can write a signal assignment target in the form of an aggregate of signal names. In VHDL-2008, the names can be a mixture of element-typed signals and array-typed signals. Elements of the right-hand-side value are assigned to the matching signals or signal elements.
Example 6.6. Binary addition with carry out
We can add two 16-bit unsigned numbers to get a 17-bit result, with the most-significant bit being the carry out of the addition, and the least-significant 16 bits being the sum. We can assign to separate signals representing the carry out and the sum as follows:
signal a, b, sum : unsigned(l5 downto 0); signal c_out : std_ulogi c; . . . (c_out, sum) <= ('0' & a) + ('0' & b);
The operands a
and b
are zero-extended and added to produce a 17-bit result. This is assigned to the aggregate target comprising the scalar signal c out
and the 16-bit vector sum.
Since we use positional association in the aggregate, c out
matches the leftmost element and sum
matches the rightmost 16 elements.
We can also use named association in array aggregates. If we want to include an association for a slice of the aggregate, we specify a range of index values for the slice. For example, given signals declared as follows:
variable status_reg : bit_vector(7 downto 0); variable int_priority, cpu_priority : bit_vector(2 downto 0); variable int_enable, cpu_mode : bit;
We can write the assignment:
( 2 downto 0 => int_priority, 6 downto 4 => cpu_priority, 3 => int_en, 7 => cpu_mode ) := status_reg;
This specifies that the bits of the status_reg
value are assigned in left-to-right order to cpu_mode, cpu_priority, int_en
, and int_priority
, respectively. When we include slices in an aggregate, the direction of the aggregate’s index range is taken from the direction of ranges used to specify the slices. Thus, in this above example, the direction is descending, since “2 downto 0"
and "6
downto 4” are both descending ranges. All the ranges used in this way must have the same direction. Thus, it would be illegal to write the example using “2 downto 0"
for one range and “4 to 6” for the other.
VHDL allows us to use a bit-string literal to specify a value for a vector of ‘0’ and ‘1’ elements in binary, octal, or hexadecimal form. The rules for bit-string literals in earlier versions of VHDL were somewhat restrictive. In particular, the resulting vector value had to be a multiple of three in length for octal literals or a multiple of four in length for hexadecimal literals. Moreover, every bit had to be stated explicitly, as there was no provision for zero extension or sign extension. Finally, only ‘0’ and ‘1’ elements could be specified. There was no provision for “metalogical” values, such as ‘XI or ‘Z’.
VHDL-2008 enhances bit-string literals considerably. First, we can specify elements other than just ‘0’ and ‘1’. In an octal literal, any non-octal-digit character is expanded to three occurrences of that character in the vector value. Thus, the literal O"3XZ4"
represents the vector value "011XXXZZZ100".
Similarly, in a hexadecimal literal any non-hexadecimal-digit character is expanded to four occurrences of the character. Thus, the literal X"A3-"
represents the vector value '10100011———'.
In a binary literal, any non-bit character just represents itself in the vector. Thus, B"00UU"
represents the vector "OOUU".
While this may seem vacuous at first, the benefit of allowing this in binary literals will become clear when we look at other enhancements.
Note that expansion of non-digit characters does not extend to embedded underscores, which we might add for readability. Thus, O"3_X"
represents "01lXXX"
, not "011XXX".
Also, expansion on non-digit characters is not limited to those defined in std_ulogic
, though that is the most common use case. We could write the literal XMO#?F"
to represent the string value "0000####????1111"
The second enhancement is provision for specifying the exact length of the vector represented by a literal. This allows us to specify vectors whose length is not a multiple or three (for octal) or four (for hexadecimal). We do so by writing the length immediately before the base specifier, with no intervening space. For example, the literal 7X"3CW
represents the 7-element vector "0111100", 80'S"
represents "00000101"
, and 10B"X"
represents "000000000X".
If the final length of the vector is longer than that implied by the digits, the vector is padded on the left with ‘0’ elements. If the final length is less than that implied by the digits, the left-most elements of the vector are truncated, provided they are all ‘0’. An error occurs if any non-‘0’ elements are truncated, as they would be in the literal 8X"90F".
The third enhancement is provision for specifying whether the literal represents an unsigned or signed number. We represent an signed number using one of the base specifiers UB, UO, or UX. These are the same as the ordinary base specifiers B, 0, and X. When a sized unsigned literal is extended, it is padded with ‘0’ elements, and when elements are truncated, they must be all ‘0’.
We represent a signed number using one of the base specifiers SB, SO, or SX. The rules for extension and truncation are based on those for sign extension and truncation of 2s-complement binary numbers. When a sized signed literal is extended, each element of padding on the left is a replication of the leftmost element prior to padding. For example, the literal 1OSX"71"
is extended to "0001110001", 1 0SX"88"
is extended to "1110001000"
, and 10SX"WO"
is extended to "WWWWWW0000".
When a sized signed literal is truncated, all of the elements removed at the left must be the same as the leftmost remaining element. For example, the literal 6SXn16"
is truncated to "01011O", 6SXWE8"
is truncated to "101000"
, and 6SXNH3"
is truncated to "HH0011"
However, 6SX"28"
is invalid, since, prior to truncation, the vector would be "00101000".
The two leftmost elements removed are each ‘O’, which differs from the leftmost remaining ‘1’ element. The literal would have to be written as 6SX"E8"
for this reason. The rationale for this rule is that it prevents the signed numeric value represented by the literal being inadvertently changed by the truncation.
The remaining enhancement is provision for specifying a vector value in decimal. For this, we use the base specifier D. All of the characters in the literal must then be decimal digits (or underscores); we cannot specify other characters, such as ’2’ or ‘XI, since it would not be clear which elements of the vector would correspond to those characters. If we omit a size specification in a decimal bit-string literal, the number of elements represented is the smallest number that can encode the value. For example, the literal D"23"
represents the vector "10111"
, D"64"
represents " 1000000"
, and D"0003"
represents "1 1"
. A decimal bit-string literal is treated as representing an unsigned number. If the literal must be extended, the vector is padded on the left with ‘0’ elements. For example, 1 2DM10"
represents "000000001010".
It can never be legal to specify a size requiring truncation, since the leftmost element prior to truncation is always ‘1’.
13.58.150.59