VHDL的基本构造:以一位全加器为例
VHDL的构造
-
事先声明:
- -- 相当于C语言的//,表示注释
- VHDL语言不区分大小写
相关库和程序包
- 程序包包括行为和函数实现代码,它们属于公用设计单元,可以被其他程序模块调用,相当于C语言中的头文件
- 其具体代码示例如下:
-- library and package
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_ARITH.ALL;
USE IEEE.STD_LOGIC_UNSIGNED.ALL;
-
其使用的关键词如下:
- LIBRARY:打开需要设计库名,此处为IEEE
- USE ……ALL:以USE IEEE.STD_LOGIC_1164.ALL为例,表示允许使用已经打开的库中STD_LOGIC_1164程序包的所有内容,这将允许我们在程序中使用STD_LOGIC数据类型
- 每一句结尾都有分号
-
我们也可以自己定义程序包、函数,详见
实体
- 设计的基本模块,用于规定电路中的各个符号、接口和数据类型,相当于C语言声明变量
- 其具体代码示例如下:
-- entity
ENTITY 实体名 IS
GENERIC(参数名: 数据类型 := 设定值);
PORT(端口名1,端口名2……: 端口模式 数据类型;
端口3:端口模式 数据类型);
--同一类的端口可以用逗号隔开,在一句内完成定义
--括号中每一句定义之间需要用分号隔开,而接末尾括号的最后一句不需要
END ENTITY 实体名;
-
其格式如下:
-
开头的ENTITY之后为实体命名(这个命名应该与文件名相同)
-
IS之后是实体包含的内容,如电路器件外部情况、各个信号端口的方向、数据类型等性质;
- 描述电路的端口及端口信号需要用端口语句PORT( );引导,示例中定义了三个输入端口,分别是in1,in2与in3,还描述了两个输出端口out1,out2;
-
最后的END表示对实体描述结束
-
构造体
- 具体实现一个实体,描述电路的构造与逻辑功能
- 其具体代码示例如下:
-- architecture
ARCHITECTURE 构造体名 OF 实体名 IS
--说明语句部分:
数据对象类型 标识符1,标识符2……:数据类型; --对数据对象的定义
--例化语句部分:
COMPONENT 要调用的实体名
PORT (那个实体的端口表);
END COMPONENT; --例化语句,用于引入另一个文件中设计好的实体,将其作为元件复用:
BEGIN
--功能描述语句部分:
--包括对各个数据对象的操作和例化语句的调用
END ARCHITECTURE 构造体名;
-
其格式如下:
-
开头的ARCHITECTURE后对构造体命名,还要有OF指明这个构造体所用的实体
-
IS后是构造体的内容,包括:
- 说明语句(这部分并非必须):说明和定义数据对象、数据类型、元件调用声明
- 功能描述语句(必须存在):描述电路功能,与所用实体中描述输入输出端口对应,本质就是写出输入和输出的逻辑关系
-
BEGIN与END相当于C语言中的{ },其之间的内容就是功能描述语句
-
- 实体和结构体是VHDL必须存在的部分,知道了其基本格式后,接下来介绍其中包含的内容:
实体表达
- 实体中包括端口说明语句和参数定义语句(非必需)
端口说明语句
-
包括电路的端口构成(有那些端口)及其端口类型(信号的流动方向和方式)和端口上流动信号的属性,定义端口具体的语句如下:
PORT(端口名1,端口名2……: 端口模式 数据类型; 端口3:端口模式 数据类型);
-
写端口说明语句,需要了解标识符、端口模式、数据类型
标识符
-
设计者自定义的词语,用于标识实体、结构体、端口、信号
-
与VHDL的关键字一样都不区分大小写,比如SUM和Sum会被认为是同一个名字
-
标识符命名规则
-
有效字符包括26个字母(不区分大小写)、数字和下划线
-
允许包含图像符号,如回车、换行符,也允许有空格,但有效字符以外的符号不能使用,如#或-等
-
必须以英文字母开头
-
不能以下划线结尾,也不能连续使用多个下划线
-
不能是语法中已有的关键字,如return
-
端口模式
-
定义端口上数据的流动方向和方式
-
IN 输入端口
定义单向只读模式的通道,规定数据只能从此端口被读入到实体中
-
OUT 输出端口
定义单向输出模式的通道,规定数据只能从此端口从实体向外流出
-
INOUT 双向端口
具备输入与输出两种功能,如RAM数据口和单片机I/O口都是此类
不能同时输入输出,双向端口的设计必须考虑不能让信号产生矛盾
-
BUFFER 缓冲端口
类似INOUT端口,区别在于当需要输入数据时,只允许内部回读输出的信号(即允许反馈),回读信号不由外部输入,而是由内部产生、向外输出的信号
- 比如计数器设计,将计数器输出的计数信号回读,作为下一计数值的初值
数据类型
-
VHDL是强类型语言,任何一种数据对象都要严格限定取值范围和数值类型,下面介绍一些常用的数据类型
-
BIT
- 界定为逻辑位’1‘和’0‘
- 可以参与逻辑运算,得到结果同样是逻辑位
- 逻辑位需要加单引号,否则会被识别为整数类型
-
BOOLEAN
- 同样界定为逻辑位’1‘和’0‘
- 不同于BIT的是’1‘代表了真,’0‘代表了伪
- 逻辑判断式的计算结果得到的是BOOLEAN类型的TRUE和FALSE,而非BIT类型的二进制数
-
INTEGER
整数数据类型,也就是常见的数字
-
STD_LOGIC 与 STD_LOGIC_VECTOR
-
包含在IEEE.STD_LOGIC_1164内
-
STD_LOGIC同样是逻辑位类型,其包括了BIT类型,并且有9种取值,分别是
- ’U‘未初始化,’X‘强未知,’W‘弱未知,’0‘和’1‘,’Z‘高阻态,’L‘弱逻辑0,’H‘弱逻辑1,’-‘忽略
相比BIT类型,多出的这些取值能更好地描述实际电路中的各种状态,比如’Z‘可以用于描述三态门,因此比BIT更常用
- ’U‘未初始化,’X‘强未知,’W‘弱未知,’0‘和’1‘,’Z‘高阻态,’L‘弱逻辑0,’H‘弱逻辑1,’-‘忽略
-
STD_LOGIC_VECTOR是由多个STD_LOGIC组成的标准一维矢量数组,用于表达电路中并列的多通道端口或是总线
-
在使用前必须注明其数组宽度,即位宽;在赋值时,必须确定两个矢量数据类型相同
-
注意:VHDL中一位二进制数使用‘ ’,而多于一位用“ ”
A:OUT STD_LOGIC_VECTOR(7 DOWNTO 0); --这就定义了一个端口组A,其包含0到7一共八个标准逻辑位STD_LOGIC,分别是A(7),A(6)……A(0) A <= "0110 0101"; A(4 DOWNTO 1) <="1101"; A(0) <= '1' --可以一次对多个逻辑位赋值 A <=B; --两个向量之间可以直接赋值,但数据类型和位宽必须相同 A(7 DOWNTO 4) <= C; --此处设C是已经定义为(1 TO 4)的4位宽向量,则A(7)=C(1),A(4)=C(4)
-
-
UNSIGNED 和 SIGNED
- 包含在STD_LOGIC_ARITH内
- 用于设计数学计算程序,UNSIGNED用于无符号数的计算,SIGNED用于有符号数的计算
- UNSIGNED被解释为无符号的二进制数
UNSIGNED'("1000") --这代表十进制的8 VARIABLE a: UNSIGNED(5 TO 0); --这定义了6位数值的无符号数,最大可以表示64
- SIGNED是有符号二进制数,最高位是符号位
SIGNED'("1011") --表示-5 VARIABLE a: SIGNED(0 TO 5); --这时的a最大只能表示32,a(0)用于表示数的正负
-
UNSIGNED和SIGNED的性质具有双重性:
- 并不属于整数INTEGER类型,本质属于STD_LOGIC的数组类型
- 但是并不能参与逻辑运算,可以直接进行算术逻辑
GENERIC参数定义语句
- 实体中定义的端口反映到实际上是连接实体与外部电路信号的通道,而为了方便电路的构建和测试,可以定义一种类似端口却不实际存在的通道,让实体其在综合与仿真中接受外部的数据,方便地修改一个设计实体
-
GENERIC(S:INTEGER:=4);--表示一个参数S,其是整数4 --之后就能在结构体中使用它 SIGNAL A: STD_LOGIC_VECTOR(2*S DOWNTO 1);--借助参数,就可以方便的修改A实际的位数
- 由于和PORT语句行为类似,一般将其放在实体定义部分,且放在端口说明语句前
结构体表达
- 结构体描述包含说明语句和功能描述语句
说明语句
-
用于说明和定义数据对象、数据类型、元件调用声明
数据对象类型 标识符1,标识符2……:数据类型; 数据对象类型 标识符3:数据类型;
-
写说明语句,要了解什么是数据对象,并且知道如何用例化语句调用元件(这一部分在例化语句中单独介绍)
数据对象
-
类似一种容器,接受数据的赋值,有三种类型:信号、变量和常数
-
SIGNAL 信号
-
如同一根导线在整个结构体中传递信息,能够把数据带出或带入进程,这一特性使其常用作内部连接线,这种功能在((20241014202337-h3oc3bi "例化语句"))的运用时再具体解释
-
所有端口默认的数据对象都是信号
-
能在实体、结构体(说明语句部分)和程序包中进行信号定义并使用,不能在进程(PROCESS)和子程序中定义
SIGNAL 信号名:数据类型 := 初始值; --初始值不是必须的,下为示例 SIGNAL a : STD_LOGIC; --前者是数据对象名,规定a这个对象的行为方式,后者限定a的数据类型 --此句将a定义为SIGNAL,类型是STD_LOGIC,这句的意思就是:a可以传递STD_LOGIC类型的数据
-
对信号赋值
目标信号名 <= 驱动表达式; --为信号进行赋值,表达式可以是一个运算,也可以是数据对象 目标信号名 <= 表达式 AFTER 时间量; --数据的传入可以设置延时量,比如after 3ns --这样的语句只在仿真中有效,无法综合对应电路,而现实中信号传输总会存在一定延时
-
-
VARIABLE 变量
-
变量是局部量,只在进程(PROCESS)和子程序中使用,不能把数据带出对其进行定义的结构中,因此其主要作用是作进程中的临时数据储存单元
-
与信号相反,只在进程(PROCESS)和子程序里面定义并使用
VARIABLE 变量名 : 数据类型 := 初始值;
-
变量的赋值赋值符号与信号不同
目标变量名 := 表达式;
-
-
CONSTANT 常数
-
恒定不变的值,一旦定义后不能再改变,具有全局性
但是如果其定义在某一结构体中,就只能用在该结构体中,定义在某个进程里就只能用于该进程,它与信号遵循相同的可视性规则
-
定义方式为:
CONSTANT 常数名: 数据类型 := 表达式; --下为示例 CONSTANT FBT : STD_LOGIC_VECTOR := "010110";
-
功能描述语句
-
VHDL程序中必须存在的部分,用以描述程序的逻辑功能和电路结构
-
分为并行语句和顺序语句两种
-
并行语句
-
无论有多少行,并行语句都是同时执行,与其书写顺序无关
-
因此对同个信号不能多次赋值,否则并行赋值会产生冲突
-
-
顺序语句 与 进程语句PROCESS
- 顺序语句指的是其执行同一般程序一样,自上到下逐条执行
- 顺序语句必须放在进程语句中,进程语句结构由PROCESS引导,具体写法如下:
PROCESS(a,b,s) --进程语句的起始与敏感信号表 BEGIN --此处为顺序语句 END PROCESS;--结束
-
PROCESS后的括号是敏感信号表:其中某一信号发生变化,就会启动此进程语句,执行一遍其中顺序语句,然后返回起始端,直到下一次的敏感信号变化
敏感信号表中的数据类型只能是信号,而不能是变量(进程只对信号敏感)
-
一个结构体中可以包含任意个进程语句结构,所有的进程语句都是并行语句,进程PROCESS引导的语句则属于顺序语句
-
在顺序语句中对信号多次赋值,会取最后的值
操作符
-
功能描述语句中重要的组成部分,发挥各种运算符的功能对数据进行操作,以下语句为例:
CO <= A AND B; --这一句表示的功能是:把输入A和B进行逻辑与操作,得到的结构赋值给输出端口CO
-
其中<=为信号赋值符号
-
赋值符号两边的信号数据类型必须保持一致
-
不同数据类型所用赋值符号不同,在数据对象中已经有介绍,不再赘述
-
-
AND是逻辑与操作符,是逻辑操作符的一种
-
-
逻辑操作符
VHDL共有7种逻辑操作符,可以方便地对信号进行逻辑操作
- AND:与,两种都为1才得1,1 * 1 = 1
- NAND:与非,对与操作的结果取反,~(1 * 1) = 0
- OR:或,有一个为1则得1,1 + 0 = 1
- NOR:或非,对或操作的结果取反
- XOR:异或,不同得1,相同得0
- NOT:取非
其进行运算的信号数据类型只能是BIT、BOOLEAN、STD_LOGIC三种
其优先级最低,次于关系运算符和计算符(除了NOT,NOT优先级最高)
-
计算操作符
包含在
USE IEEE.STD_LOGIC_UNSIGNED.ALL;
中,本质是函数包括+ - * / ADD这些算术符号
需要用在
INTEGER
类型 -
并置操作符
- & 用于将数组合并起来形成新的数组矢量
a <= '1' & '0' & b(1) & '1'; --a为4位矢量,并置后得到数组长度为4
-
关系运算符
其优先级高于逻辑符,低于计算符,用于比较数值的大小,有=、/=(不等于)、<、>、>=、<=
例化语句
-
位于说明语句的位置,用于引入另一个文件中设计好的实体,将其作为元件复用
-
例化语句的格式
包含两部分:
- 将现有的实体定义为一个元件
- 对这个元件作调用声明
COMPONENT 要调用的实体名 IS PORT (那个实体的端口表); END COMPONENT; --元件定义语句,必须放在ARCHITECTURE与BEGIN之间 --就是将现有实体的描述的ENTITY改为COMPONENT 例化名: 例化语句中的实体名 PORT MAP (端口名=>连接端口名); --调用元件语句,放在结构体BEGIN和END之间 --通过端口映射PORT MAP将所调用元件本身的端口连接到当前实体的端口,在双方间传输数据
-
端口映射
内部元件的端口和当前设计实体的端口信号之间需要进行数据传递,有两种方法:
- 端口名关联法:使用=>符号连接端口名,元件中的端口名在左,当前实体的端口名在右
- 位置映射法:类似于C语言调用函数时的参数传递,写在端口表的端口顺序和元件端口定义的顺序必须一一对应不能变更
一位全加器设计
-
以调用半加器实现全加器的设计为例,演示例化语句的使用,同时展示前文所述VHDL程序格式如何书写:
-
半加器
通过对输入A与B的逻辑操作,实现了无进位的情况下将A和B相加,输出结果SUM和进位Cout
LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; ENTITY half_adder IS PORT( A: IN STD_LOGIC; B: IN STD_LOGIC; SUM: OUT STD_LOGIC; Cout: OUT STD_LOGIC ); END ENTITY half_adder; ARCHITECTURE bhv0 OF half_adder IS BEGIN SUM <= A XOR B; Cout <= A AND B; END ARCHITECTURE bhv0;
-
全加器
通过调用半加器,实现全加器(可以处理从低位来的进位信号)
--相关库和实体描述不再赘述 ARCHITECTURE bhv OF full_adder IS COMPONENT half_adder --例化半加器,输入A与B,得到本位的结果S和进位C PORT ( A, B: IN STD_LOGIC; S, C: OUT STD_LOGIC ); END COMPONENT; SIGNAL S1, C1, C2: STD_LOGIC; --定义两个信号作为内部的端口连接线 --调用半加器设计全加器,要求输入A、B和来自低位的进位Cin,输出本位结果SUM和进位Cout --调用两个半加器,例化名分别为HA1和HA2,HA1计算本位相加的结果,HA2计算从低位进位与本位和相加的结果 HA1: half_adder PORT MAP (A1, B1, S1, C1); --位置映射法: --将当前实体的A1传递到了调用元件half_adder的A,B1对应B --half_adder输出的S将赋值给S1,C赋值给C1 HA2: half_adder PORT MAP (A => S1, B => Cin, S => SUM, C => C2); --端口名关联法 --注意这里的=>不代表数据流动方向,只是S1的值送给半加器的A,以此类推 CO <= C1 OR C2; --最后要处理本位相加产生的进位 以及 自低位进位与本位和产生的进位,两个进位只要存在一个就进位 END ARCHITECTURE bhv;
本文来自博客园,作者:无术师,转载请注明原文链接:https://www.cnblogs.com/untit1ed/p/18613646
本文使用知识共享4.0协议许可 CC BY-NC-SA 4.0
请注意: 特别说明版权归属的文章以及不归属于本人的转载内容(如引用的文章与图片)除外
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通