前言
VHDL(Very-High-Speed Integrated Circuit Hardware Description Language)是一种用于电路设计的高级语言,出现于80年代的后期,最初是给美军使用的。目前为止一共有两个版本(IEEE-1076(简称 87 版)、1076-1993版本(简称 93 版))。
VHDL主要用于描述数字系统的结构、行为、功能和接口。除了包含许多具有硬件特征的语句外,VHDL的语言形式、描述风格以及语法与一般的计算机高级语言十分类似。VHDL的程序结构特点是将一项工程设计,或称为设计实体(元件,电路模块或系统)分成外部(可视部分及端口)和内部(不可视部分),既涉及实体的内部功能和算法完成部分。在对一个设计实体定义了外部界面后,一旦其内部开发完成后,其他的设计就可以直接调用这个实体。这种将设计实体分成内外部分的概念是VHDL系统设计的基本点。
1. VHDL基础语法
VHDL 和其他高级语言一样,具有多种数据类型。对大多数数据类型的定义两者是一致的(例如整数型),但是也有一些数据类型是 VHDL 所独有的。
1.1 VHDL的数据类型
VHDL 的数据对象有信号(Signal)、变量(Variable)、常量(Constant)和文件(File),其中文件(File)是 VHDL-93 标准,不可以被综合。
1)信号(signal)
信号用于将元件的装配端口连在一起形成模块,可以理解成现实中能看见的”导线”,性质是一样的。信号是实体间动态数据交换的手段,信号申明格式如下:
signal < signal_name > : < signal_type > [ :=initial_value ] ;
signal PrSv_s_Cnt : std_logic_vector(3 downto 0) := x”0”; -- count
在关键字 signal后跟一个或者多个信号名,每个信号名将建立一个新信号,用冒号把信号名和信号的数据类型分隔开,信号数据类型规定信号包含的数据类型信息及初始化信号指定的初值。
建议:关键字 signal后跟一个信号名,代码的格式清晰便于修改。
2)变量(variable)
变量用于存储进程和子程序中的局部数据,变量的赋值是立即执行的,没有延时。变量的申明格式如下:
variable variable_name ,variable_name : variable_type[:= value];
关键字 VARIABLE 后跟着一个或多个变量名,每个变量名对应建立一个新变量。variable_type 字段定义了变量的数据类型,并且还可以指定一个可选的初值。只可以在进程说明部分和子程序说明部分声明变量。
和信号相比,变量有以下优点:
• 变量处理起来更快,因为变量赋值是立即发生的,而信号却必须为此事件作相应的处理。
• 变量用很少的存储器,相反为了做一个调度安排和处理信号属性,需要存储更多的信号信息。
• 变量比信号更容易实现同步处理。
3)常量(constant)
常量是为特定的数据类型值所赋予的名称,如果需要在多个具体元件中存放一个固定值就使用常量。例如可以如下定义常量 PI(π ):
CONSTANT PI: REAL:= 3.14;
定义常量的格式如下:
CONSTANT constant_name,constant_name : type_name[:= value];
constant MMCM_LOCK_CNT_MAX : integer := 256;
一般情况下,VHDL 中的常量是在程序包申明中进行申明,而在程序包体中指定具体的值。使用常量需要注意以下几个问题:
• 在程序包中说明的常量被全局化。
• 在实体说明部分的常量被那个实体中任何结构体引用。
• 在结构体中的常量能被其结构体内部任何语句采用,包括为进程语句采用。
• 在进程说明中说明的常量只能在进程中使用。
• 在数组和一些线性运算中经常用常量表,VHDL 的设计描述用常量表特别适于实现 ROM 网络的电路与函数设计。
1.2 VHDL 语言的数据类型
根据VHDL使用的目的和场合,VHDL数据类型可以分为标准数据类型和用户自定义数据类型。
1)标准数据类型
其中,在数据类型后面,可以加上约束区间,比如:
Integer range 100 downto 20;
Bit_vector(7 downto 0) real range 2.0 to 100.0;
STD_LOGIC 和 STD_LOGIC_VECTOR 的逻辑数据取值,可以有 9 种状态,如下所示。
2)用户自定义的数据类型
用户自定义数据类型的格式如下:
Type 数据类型名{,数据类型名} 数据类型定义;
数据类型定义放在语句的定义部分中,定义范围为从本定义行开始到本语句作用的最后。
一般用户自定义的数据类型分为以下几种:
1)枚举类型(enumeration)
枚举类型的格式如下:
Type 数据类型名 is (元素, 元素, ……)
例如,将一星期七天作为一个枚举,可以如下定义:
Type week is (mon, tue, wed, thu, fri, sat, sun);
在枚举类型中,元素是有序列性的,第 1 个元素对应逻辑电路状态 000,第 2 个为状态 001,第 3 个为状态 010……后一个逻辑状态为前一个元素逻辑状态加 1。所以,上面的例子中,mon对应逻辑状态 000,tue 对应逻辑状态 001,……,sun 对应逻辑状态 110。
2)整数类型、实数类型(INTEGER,REAL)
这里的整数类型和实数类型其实是前面所述的标准整数类型和实数类型的子类,定义的格式如下:
TYPE 数据类型名 IS 数据类型定义 约束范围
例如:
TYPE current IS REAL RANGE -1E4 TO 1E4
定义了 current 类型实数的范围是-104到 104。
3)数组(ARRAY)
数组定义的格式如下:
TYPE 数据类型名 IS ARRAY 范围 OF 原数据类型名;
注意:如果 范围 这一项没有被指定,则使用整数数据类型。
下面通过例子说明数组的定义方法。
TYPE word IS ARRAY (1 TO 8) OF STD_LOGIC;
TYPE tmem IS ARRAY (0 TO 2, 3 DOWNTO 0) OF STD_LOGIC;
以上定义了 tmem 一种数组类型,可以定义一个此类新的常数,如下:
CONSTANT mem:tmem:= ( ('0', '0', '0', '0'), ('0', '0', '1', '0'), ('1', '1', '0', '0') );
当范围这一项需用整数类型以外的其他数据类型时(如枚举类型),则应在指定数据范围前加数据类型名。例如:
TYPE week IS (sun, mon, tue, wed, thu, fri, sat);
TYPE workday IS ARRAY (week mon TO fri) OF STD_LOGIC;
如果要取得数组内的一个元素,格式如下:
数组名(下标)
例如,word(1)区的 word 数组序号为 1 的元素。
当数组类型定义中的范围用“(Natural Range <>)”或“(Positive Range <>)”代替时,表示本数组类型为非限定的类型,下标范围在信号或变量定义时再具体指定。
library IEEE;
use IEEE.std_logic_1164.all;
entity tmyarray is
port (
a : in std_logic ;
c : out std_logic
);
end ;
architecture myarray of tmyarray is
type array0 is array (natural range <>) of std_logic;
begin
process(a)
variable item:array0(7 downto 0);--定义一个变量 item
begin
item(7):=a;
c<=item(7);
end process;
end;4)时间(TIME)
时间的格式和例子如下:
type <数据类型名> is <范围>
units <基本单位>;
<单位描述>;
end units
type time is range -1E18 TO 1E18
units fs;
ps=1000fs;
ns=1000ps;
us=1000ns;
ms=1000us;
sec=1000ms;
min=60sec;
hr=60min;
end units;5)记录(Record)
TYPE 数据类型名 IS RECORD
元素名:数据类型名;
元素名:数据类型名;
…
元素名:数据类型名;
END RECORD;
注意:引用记录数据类型中的元素应使用“.”,而不是数组的括号。
例如,定义一个窗口尺寸的记录,如下:
TYPE window IS RECORD
length:INTEGER;
width:INTEGER;
END RECORD;
当需要使用 window 类型记录的元素时,方法如下:
signal win: window;
win.length<=10;6)用户自定义的子类型
用户定义的子类型是用户对已定义的数据类型做一些范围限制而形成的一种数据类型。子类型的名称通常采用用户较容易理解的名字。
子类型的定义格式为:
SUBTYPE 子类型名 IS 数据类型名[范围];
例如:
SUBTYPE digit IS INTEGER RANGE 0 TO 9;
SUBTYPE abus IS STD_LOGIC_VECTOR(7 DOWNTO 0);
signal a: STD_LOGIC_VECTOR (7 downto 0);
signal b: STD_LOGIC_VECTOR (15 downto 0);
signal c: abus;
a<=c; --正确
b<=c; --错误注意:在 VHDL 中,数据类型的定义是相当严格的,不同类型的数据是不能进行运算和直接代入的。为了实现正确的代入操作,必须将要代入的数据进行类型变换。
变换函数通常由 VHDL 语言的包集合提供。例如由 “ STD_LOGIC_1164 ”、“STD_LOGIC_ARITH”、STD_LOGIC_UNSIGNED”的包集合提供的变化函数如下所示:
有些数据,从数据本身是断定不出其类型的,如“01010001”,如果没有上下文,VHDL 编译器就无法知道它是字串型还是位数组类型。这时就要进行数据类型的限定。
类型限定的格式如下:
类型名'(数据)
例如:
a <= std_logic_vector'("01010001");
这样,编译器知道“01010001”肯定是矢量型,而不是别的类型。
1.3 VHDL 语言的运算符
在 VHDL 语言中,常用的运算符有逻辑运算(Logic)、关系运算(Relational)、算术运算(Arithmetic)和移位运算(Shift)。
1)逻辑运算符
逻辑运算符可以对 bit 和 boolean 类型的值进行运算,也可对这些类型的一维数组进行运算。对数组型的运算,运算施加于数组中的每个元素,结果与原来数组长度相同。
逻辑判断的运算为“短路运算”,也就是说,条件表达式的左边成立时,就不再进行右边的判断。比如,IF (a=0) AND (b/a>2) THEN…这个判断运算,当 a=0 时,后面的判断不再继续,避免出现除数为 0 的运算。
2)关系运算符
关系运算符两边必须为相同的类型,其结果为 boolean 类型。
等号(=)和不等号(/=)两边可以为任意类型的运算对象。其他关系运算符的运算对象必须为标量类型或离散类型的一维数组。对于复杂的运算对象,如数组,两个值相等意味着两个值的所有对应元素相等。
3)算术运算符
算术运算符包括一些基本的算术运算,使用算术运算符需要注意的是乘方(**)运算的右边必须为整数。VHDL 的算术运算符如下图所示:
4)移位运算符
移位运算符为二元运算符,左边必须为一维数组,且元素类型为 bit 或 boolean 类型。右边运算数为整数,可以为负数,相当于反方向移位。一位移位与循环移位的语义示意如下图所示:
除了上面介绍的,VHDL 中运算符还包括正号“+”、负号“-” 以及“&”。其中,连接符号(&)用于一维数组,这个数组的元素个数可以为 1,运算结果为右边数组连接在左边数组之后形成新数组。
例如:sel <= a & b;
假设 a 信号为“0”,b 信号为“1”,那么得到的 sel 信号就是“01”。
所有的 VHDL 运算符之间都有优先级的关系,各运算符优先级从最高到最低,顺序如表 10 所示(同一行优先级相同)。
附录:VHDL的代码格式
library IEEE;
use IEEE.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
-- Uncomment the following library declaration if using
-- arithmetic functions with Signed or Unsigned values
--use IEEE.NUMERIC_STD.ALL;
-- Uncomment the following library declaration if instantiating
-- any Xilinx leaf cells in this code.
library UNISIM;
use UNISIM.VComponents.all;
entity M_Top is
port (
CpSl_i_Clk : in std_logic ; -- Clock
CpSl_i_Rst_n : in std_logic ; -- Reset_Active_low
CpSv_i_Data : in std_logic_vector(3 downto 0) ; -- Data
CpSl_i_DataVld : in std_logic ; -- DataValid
CpSl_o_DataVld : out std_logic ; -- DataVld_out
CpSl_o_Data : out std_logic_vector(7 downto 0) -- Data_out
);
end entity;
architecture arch_M_Top of M_Top is
------------------------------------
-- constant_describe
------------------------------------
constant PrSv_s_Cnt : std_logic_vector(3 downto 0) := x"F";
------------------------------------
-- IP_describe
------------------------------------
------------------------------------
-- signal_describe
------------------------------------
signal PrSv_s_Cnt : std_logic_vector(3 downto 0); -- count
begin
----------------------------------------------------------------------------
-- Map_describe
----------------------------------------------------------------------------
process (CpSl_i_Rst_n, CpSl_i_Clk) begin
if (CpSl_i_Rst_n = '0') then
elsif rising_edge(CpSl_i_Clk) then
if (PrSv_s_Cnt = PrSv_c_Cnt) then
PrSv_s_Cnt <= x"0";
else
PrSv_s_Cnt <= PrSv_s_Cnt + '1';
end if ;
end if;
end process;
......
----------------------------------------------------------------------------
-- End_coding
----------------------------------------------------------------------------
end arch_M_Top;上面的格式,是VHDL的基本用法也是常用的用法。万变不离其宗,加油吧!
小结
这一小节,主要是介绍了VHDL语言的基础语法,后面会结合实例,继续强化VHDL语言的基础。要是有关于FPGA相关的问题,欢迎咨询,互相促进,共同进步。
发表评论