I have two designs:
library ieee;
use ieee.std_logic_1164.all;
entity eq_test1 is
port (a,b : IN std_logic_vector (1 downto 0);
o : OUT std_logic);
end eq_test1;
architecture strange_behavior of eq_test1 is
begin
P: process (a,b)
begin
if a = b then o <= '1';
else o <= '0';
end if;
end process P;
end strange_behavior;
Forcing in Modelsim to a have "00" and b have "0L" shows that o becomes '0'. So L is NOT interpreted as 0, "00" = "0L" is false. Ok.
But when I take the same design and add
use ieee.std_logic_unsigned.all;
to the list, the behavior is different. Then "00" = "0L" returns true, so L IS the same as 0 (0 becomes '1'). With that unsigned packages included, even "0X" = "0Z" returns true.
Can anyone explain why?
Adding use ieee.std_logic_unsigned.all;
is like opening Pandora's box to
Synopsys libraries. After a deep dive shown below, the conclusion is that the
std_logic_unsigned package in this case makes the values '0' and 'L' equal
through an table.
The call starts when the "=" operator is redefined to:
function "="(L: STD_LOGIC_VECTOR; R: STD_LOGIC_VECTOR) return BOOLEAN is
begin
return UNSIGNED(L) = UNSIGNED(R);
end;
That is however just the beginning, since std_logic_unsigned includes
use IEEE.std_logic_arith.all;
, which defined the type:
type UNSIGNED is array (NATURAL range <>) of STD_LOGIC;
The compare of UNSIGNED in "=" thus result in call of std_logic_arith function:
function "="(L: UNSIGNED; R: UNSIGNED) return BOOLEAN is
-- synopsys subpgm_id 341
constant length: INTEGER := max(L'length, R'length);
begin
return bitwise_eql( STD_ULOGIC_VECTOR( CONV_UNSIGNED(L, length) ),
STD_ULOGIC_VECTOR( CONV_UNSIGNED(R, length) ) );
end;
In this function the CONV_UNSIGNED is interesting:
function CONV_UNSIGNED(ARG: UNSIGNED; SIZE: INTEGER) return UNSIGNED is
constant msb: INTEGER := min(ARG'length, SIZE) - 1;
subtype rtype is UNSIGNED (SIZE-1 downto 0);
variable new_bounds: UNSIGNED (ARG'length-1 downto 0);
variable result: rtype;
-- synopsys built_in SYN_ZERO_EXTEND
-- synopsys subpgm_id 372
begin
-- synopsys synthesis_off
new_bounds := MAKE_BINARY(ARG);
if (new_bounds(0) = 'X') then
result := rtype'(others => 'X');
return result;
end if;
result := rtype'(others => '0');
result(msb downto 0) := new_bounds(msb downto 0);
return result;
-- synopsys synthesis_on
end;
Now we are getting close, since the above calls:
function MAKE_BINARY(A : UNSIGNED) return UNSIGNED is
-- synopsys built_in SYN_FEED_THRU
variable one_bit : STD_ULOGIC;
variable result : UNSIGNED (A'range);
begin
-- synopsys synthesis_off
for i in A'range loop
if (IS_X(A(i))) then
assert false
report "There is an 'U'|'X'|'W'|'Z'|'-' in an arithmetic operand, the result will be 'X'(es)."
severity warning;
result := (others => 'X');
return result;
end if;
result(i) := tbl_BINARY(A(i));
end loop;
return result;
-- synopsys synthesis_on
end;
And in here we have the reason for 'L' being equal to '0', since the tbl_BINARY is a constant defined as:
type tbl_type is array (STD_ULOGIC) of STD_ULOGIC;
constant tbl_BINARY : tbl_type :=
('X', 'X', '0', '1', 'X', 'X', '0', '1', 'X');
To understand this mapping, it is useful to align with the definition of values in STD_ULOGIC:
std_ulogic: ( 'U', 'X', '0', '1', 'Z', 'W', 'L', 'H', '-');
tbl_BINARY: ( 'X', 'X', '0', '1', 'X', 'X', '0', '1', 'X');
This shows that after conversion through tbl_BINARY then the equivalent groups are ('U', 'X', 'Z', 'W', '-'), ('0', 'L'), and ('1', 'H').
A closing comment is that even through the std_logic_unsigned package resides in a library called "ieee", the package is not IEEE standard like VHDL, but a Synopsys package. The package, and the other related Synopsys packages, have been around for a while and are widely used.
However, you may consider using the IEEE standard package numeric_std instead.