VHDL includes few built-in types, but offers a number of additional types through extension packages. Two of the most widely used types are the
std_ulogic. The difference between them is that the former is resolved, while the latter isn’t.
Before we go on to investigate what it means that a type is resolved, let’s first look at the traits that the two types share in common.
boolean are part of the standard package, requiring no imports to use them. But
std_ulogic can only be used after importing the IEEE 1164 package. To import the IEEE 1164 package, simply add these lines to the top of the VHDL file:
library ieee; use ieee.std_logic_1164.all;
The two types
std_ulogic both have in common that they can represent the following values:
|‘W’||Weak signal, can’t tell if 0 or 1|
|‘L’||Weak 0, pulldown|
|‘H’||Weak 1, pullup|
|‘X’||Unknown, multiple drivers|
Most of the time you will use
'0' to indicate a logic high or low value. And
'U' will be used for representing uninitialized values, such as RAM content at startup. Occasionally you will see the
'X' value, indicating some sort of driver conflict.
std_ulogic – The unresolved type
Let’s first have a look at the
std_ulogic type, the unresolved version of the two. Below is an excerpt of the type declaration taken from an implementation of the std_logic_1164 package.
TYPE std_ulogic IS ( 'U', -- Uninitialized 'X', -- Forcing Unknown '0', -- Forcing 0 '1', -- Forcing 1 'Z', -- High Impedance 'W', -- Weak Unknown 'L', -- Weak 0 'H', -- Weak 1 '-' -- Don't care );
std_ulogic is simply an enumerated type which lists the possible values as enumeration literals. When a signal or variable is declared with
std_ulogic as the type, it can represent any one of these values, and none other.
'U' value is the first of the listed values. That’s the reason why a signal of the
std_ulogic type will get the value
'U' if given no initial value. By default, an uninitialized signal will get the leftmost value from the type declaration. That’s how VHDL works.
What does it mean that a type is unresolved?
If multiple drivers are driving different values onto a signal of the
std_ulogic type, that’s not going to work. Conflicting drivers is an error in VHDL. The simulation won’t compile or the design won’t synthesize. Driver conflicts are not resolved with the
std_ulogic type. The type doesn’t have a built-in mechanism to determine what the signal value should be, so it’s an error.
Consider this example where two processes attempt to drive a signal of
library ieee; use ieee.std_logic_1164.all; entity UnresolvedTb is end entity; architecture sim of UnresolvedTb is signal Sig1 : std_ulogic := '0'; begin -- Driver A Sig1 <= '0'; -- Driver B Sig1 <= '1' after 20 ns; end architecture;
If we try to compile this in ModeSim, it reports the following error:
# ** Error: C:/proj/tb.vhd(8): Nonresolved signal 'Sig1' has multiple sources. # Drivers: # C:/proj/tb.vhd(12):Conditional signal assignment line__12 # C:proj/tb.vhd(15):Conditional signal assignment line__15 # ** Error: C:/proj/tb.vhd(17): VHDL Compiler exiting
std_logic – The resolved type
std_logic can represent the same values as the
std_ulogic, but it’s a resolved type. What does that mean? To find out, let’s once again refer to the implementation of the standard. Below is the declaration taken from the IEEE 1164 package.
SUBTYPE std_logic IS resolved std_ulogic;
subtype keyword in VHDL is normally used for declaring a type with a limited range from the base type. But it can also be used for specifying a resolution function. That’s what the above statement is saying. The
std_logic is a subtype of
std_ulogic, and the name of the resolution function is “resolved”.
Now, let’s examine the “resolved” function:
FUNCTION resolved ( s : std_ulogic_vector ) RETURN std_ulogic IS VARIABLE result : std_ulogic := 'Z'; 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 resolved;
It accepts one parameter, an array of
std_ulogic representing all the simultaneous drivers. It then compares all the elements in the vector to each other, one by one, reducing them to a single
std_ulogic value which is returned.
For comparing the values, what seems to be a different function named “resolution_table” is called. But it’s actually a two-dimensional array:
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' ) -- | - | );
The resolution function is what governs the final value that a signal will have in case of multiple drivers.
This is the same example code as before, but with the
std_ulogic converted to
library ieee; use ieee.std_logic_1164.all; entity ResolvedTb is end entity; architecture sim of ResolvedTb is signal Sig1 : std_logic := '0'; begin -- Driver A Sig1 <= '0'; -- Driver B Sig1 <= '1' after 20 ns; end architecture;
The code now compiles in ModelSim without errors. When we run it, it produces this waveform:
At first, only Driver A is driving a
'0' value onto
Sig1. After 20 nanoseconds, Driver B starts driving
'1' onto the same signal. The conflicting values were resolved according to the resolution table, resulting in the signal getting the value
Why use std_logic?
std_logic is generally preferred over the built-in
boolean types in VHDL. This is because they give us more information than the simple
'0', on or off. If you prefer to have the simulator treat multiple drivers as hard errors and stop at the first occurrence, you could of course use
Unintended multiple drivers probably won’t go undetected, even when using the
std_logic type. You will likely see the “metavalue detected” warnings in the simulator transcript window as the
'X' values propagate through the datapath.
std_logic has become the most used type for internal and external interfaces because it accurately models the behaviors that a digital electrical signal can exhibit. The
std_logic type offers a number of benefits and use cases including the following.
Tracking of uninitialized values is useful for detecting missing resets and the use of uninitialized data. Since block RAM cannot have have reset values, they will contain only
'U' values at simulation start. This makes it easy for us to spot uninitialized RAM content being used in the simulation waveform. With a two-value type like
boolean, such errors would go undetected.
Implementing tri-state logic is best done with a resolved signal like
std_logic. The high impedance state
'Z' is normally used for releasing the bus while another driver has control of it. In VHDL, when a process begins to drive a value onto a signal, there is no undo. There exists no keyword to stop driving. The only way to release the signal is by driving a weaker value which can by overridden by another driver.
Modeling external interfaces
To accurately model an external signal, a boolean value is not enough. External digital interfaces can exhibit behaviors like pull-up or pull-down logic. Without the
'L' values, it would be difficult to simulate interfaces that rely on pulling like I2C or SPI.
Propagating errors downstream
A strategy that will make erroneous usage of modules easier to detect is setting the outputs to
'X' when the data is invalid, for example when the
valid output is
'0'. Downstream modules that sample the outputs at the wrong time will receive the
'X' value. This will cause a simulation error or metavalue warning, rather than an invalid
'1' silently being used. It can prevent errors that are otherwise difficult to detect.
We did this when implementing a multiplexer in the Case-When tutorial.