在VHDL程序设计中经常会涉及到std_ulogic与std_logic逻辑类型,这两种类型都是以数据包形式由IEEE库提供。VHDL包含很少的内置类型,但通过扩展包提供了几种附加类型。两种最广泛使用的类型是 std_logic 和 std_ulogic。它们之间的区别在于前者具有决断特性,而后者没有具有决断特性。那么什么具有决断特性呢?在IEEE.std_logic_1164数据中,提供了一个称为决断特性的函数。下面先看看std_ulogic和std_logic的数据类型,然后再研究一下决断函数,就明白了它们两者的区别。
1. std_ulogic与std_logic数据类型
Bit和boolean是标准包的一部分,不需要导入即可使用它们。但std_logic和std_ulogic只能在导入IEEE.std_logic_1164包后才能使用。要导入导入IEEE.std_logic_1164,只需将这些行添加到VHDL文件的顶部即可,导入格式如下:
library ieee;
use ieee.std_logic_1164.all;
std_logic和 std_ulogic这两种类型的共同点是它们可以用表1的值表示:
表1
‘1’ | Logic 1 | 逻辑 1 |
‘0’ | Logic 0 | 逻辑 0 |
‘Z’ | High impedance | 高阻 |
‘W’ | Weak signal, can’t tell if 0 or 1 | 弱信号,不能分辨是0还是1 |
‘L’ | Weak 0, pulldown | 弱0, 下拉(弱) |
‘H’ | Weak 1, pullup | 弱1, 上拉(弱) |
‘-‘ | Don’t care | 无关,一般指输入对逻辑或输出没有影响 |
‘U’ | Uninitialized | 未初始化 |
‘X’ | Unknown, multiple drivers | 未知,多重驱动的结果 |
大多数情况下,将使用“1”和“0”来表示逻辑高或低值。 'U' 将用于表示未初始化的值,例如启动时的RAM内容。有时也会看到“X”值,表示某种驱动程序冲突。
- STD_ULOGIC – THE UNRESOLVED TYPE
我们先来看看std_ulogic类型,两者的未解析版本。下面是从std_logic_1164包的实现中摘录的类型声明。
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 是一种枚举类型,它将可能的值列为枚举文字。当使用std_ulogic作为类型声明信号或变量时,信号或变量的值可以是这些值中的任何一个,而不能再有其他值。“U”值是枚举值中的第一个。这就是如果没有初始值,std_ulogic 类型的信号将获得值“U”的原因。默认情况下,未初始化的信号将从类型声明中获取最左边的值。这就是 VHDL 的工作原理。那么为决断的数据类型到底是什么含义呢?看下面的例子,
例1:
library ieee; use ieee.std_logic_1164.all; entity Unresolved_tb is end entity; architecture sim of Unresolved_tb is signal Sig1 : std_ulogic := '0'; begin -- Driver A Sig1 <= '0'; -- Driver B Sig1 <= '1' after 20 ns; end architecture;
如果再Vivado或Modelsim下编译,可以看到如下类似的结果:
# ** 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
从编译结果可以看出,编译器提示信号’Sig1’ 有多重驱动。分析一下原因就会发现,Sig1的两条赋值语句都是并发赋值语句,而且一条语句赋值为0,另一条赋值为1;因此最终结果未知。但是std_ulogic类型对于该运算结果并没有解决方案。
2. 决断函数
但是在std_logic_1164的程序包中,有定义另一种类型,即std_logic类型,
SUBTYPE
std_logic
IS
resolved
std_ulogic
;
可以看出std_logic类型是std_ulogic类型的子类型,
而且是resolved std_ulogic类型。那么resolved到底起到什么作用呢?在该数据包里还定义了一个函数:
function resolved (s : std_ulogic_vector) return std_ulogic; 可以看出std_logic是resolved 函数的返回值,数据类型依然是std_ulogic。下面看看resolved 函数到底是什么,该函数的实现是在std_logic_1164-body.vhd实现的。首先看一个常数数组,即二维的决断表。
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 -- the test for a single driver is essential otherwise the -- loop would return 'X' for a single driver of '-' and that -- would conflict with the value of a single driver unresolved -- signal. 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;
决断函数与一般函数有如下不同之处:
(1)信号赋值时自动调用
(2)使用没有范围限制的STD_ULOGIC_VECTOR矢量,表示多重赋值的次数不受限制,如果只有一次,就变成对std_ulogic类型的信号赋值。
(3)STD_ULOGIC_VECTOR矢量是指多重赋值的形成的赋值矢量,而不是对std_ulogic_vector<range> 类型的决断。
首先分析决断表的含义,如果有个变量前后两次的值不同且是并发赋值的,可以在2维数组交叉查出应该得到什么输出值。如上例中第一次为0,第二次为1,查表后得到’X’,因此输出就是’X’,如图1所示。这样就把问题解决了。因此resolved函数起到决断作用。修改例1的代码如例2所示:
图1
例2:std_logic类型信号多重并发赋值(多驱动)。
library ieee; use ieee.std_logic_1164.all; entity resolved_tb is end entity; architecture sim of resolved_tb is signal Sig1 : std_logic := '0'; begin -- Driver A Sig1 <= '0'; -- Driver B Sig1 <= '1' after 20 ns; end architecture;
在vivado下仿真,没有编译错误,波形如图2所示,
图2
从图1可以看出,在20 ns后,Sig1的输出为X。可见std_logic在多重驱动时有决断结果的。这样就为数字建模,仿真,调试带来方便。9值逻辑对问题的描述更细致,更准确。
例3: 观察std_ulogic类型的多重驱动在modelsim下编译情况
library IEEE; use IEEE.STD_LOGIC_1164.ALL; entity std_logic_test_tb is end std_logic_test_tb; architecture ARCH_std_logic_test of std_logic_test_tb is signal test_a: std_ulogic;--:='L'; signal test_b: std_ulogic:='H'; begin --test_a<='Z'; test_a<='1' after 60 ns; test_a<='H' after 40 ns; --test_a<='1'; -- test_a<='1' after 20 ns; -- test_a<='L' after 20 ns; test_a<='0' after 80 ns; test_a<='U' after 100 ns; test_a<='0' after 120 ns; test_b<='L' after 20 ns; test_b<='1' after 40 ns; test_b<='0' after 60 ns; end ARCH_std_logic_test;
上例在modelsim 下编译时会报出如下错误信息如图3所示,
图3 std_ulogic多重驱动的编译错误信息
图3显示std_ulogic只是定义相应的数据类型,但多驱动时并没有解决方案。修该例3的代码,如例4所示,将会得到正确的输出结果。
例4:
library IEEE; use IEEE.STD_LOGIC_1164.ALL; entity std_logic_test_tb is end std_logic_test_tb; architecture ARCH_std_logic_test of std_logic_test_tb is signal test_a: std_logic;--:='L'; signal test_b: std_logic:='H'; begin --test_a<='Z'; test_a<='1' after 60 ns; test_a<='H' after 40 ns; --test_a<='1'; -- test_a<='1' after 20 ns; -- test_a<='L' after 20 ns; test_a<='0' after 80 ns; test_a<='U' after 100 ns; test_a<='0' after 120 ns; test_b<='L' after 20 ns; test_b<='1' after 40 ns; test_b<='0' after 60 ns; end ARCH_std_logic_test;
例4使用std_logic数据类型后,编译正确通过,仿真波形如图4所示,
图4
同时也可以看出,由于test_a在信号声明时为初始化,因此在多重并发赋值时只接受最后一个时刻的赋值评估,本例在100ns 时评估test_a有’H’- -40ns , ‘1’- -60 ns, ‘0’- – 80 ns, ‘U’- -100ns 。 由于’U’强于其它类型,因此最终的结果与初始结果一致,都为’U’。 对于test_a仅保留如下两句
test_a<=’1′ after 60 ns;
test_a<=’H’ after 40 ns;
仿真结果如图5所示,
图5
图5的结果充分说明,对于未初始化的变量在决断时,仅对最后时刻的所有赋值进行评估并决断。而对于声明时有初值的信号,则按照信号发生的时刻进行评估。
修改例4的代码如例5所示,将test_a在声明时初始化。
例5:
library IEEE; use IEEE.STD_LOGIC_1164.ALL; entity std_logic_test_tb is end std_logic_test_tb; architecture ARCH_std_logic_test of std_logic_test_tb is signal test_a: std_logic:='L'; signal test_b: std_logic:='H'; begin --test_a<='Z'; test_a<='1' after 60 ns; test_a<='H' after 40 ns; --test_a<='1'; -- test_a<='1' after 20 ns; -- test_a<='L' after 20 ns; test_a<='0' after 80 ns; test_a<='U' after 100 ns; test_a<='0' after 120 ns; test_b<='L' after 20 ns; test_b<='1' after 40 ns; test_b<='0' after 60 ns; end ARCH_std_logic_test;
仿真波形如图5所示,
图5
从图5的显示结果可以看出,test_a输出结果逐渐从’L’,’H’,’1′, ‘X’, ‘U’的变化。而且由于’U’类型强度最高,即使在120 ns赋值‘0’也不会再发生变化。
思考题: 信号未初始化时的默认值为’U’与使用并发赋值语句对信号赋值’U’有何不同?