第3章 CHAPTER 3 VHDL的基本语法规则 3.1VHDL基本术语 首先介绍一些常见的VHDL术语,它们是几乎在每一个VHDL程序中都会使用的基本VHDL构建块。 1. 实体ENTITY 实体是设计中最基本的构建块,所有的设计都会有实体。整个设计中位于最顶层的实体叫顶层实体(TopLevel Entity)。如果设计是分层递阶的,则顶层实体描述会包含更低层的描述。这些更低层描述就是包含在顶层描述中的更低层实体。 2. 结构体ARCHITECTURE 所有能仿真的实体都具有结构体描述。结构体描述实体的行为。一个实体可能具有多个结构体。一个设计的结构体可以从行为角度描述,也可以从结构角度描述。 3. 配置CONFIGURATION 配置语句用于将一个实体结构体对和一个组件实例绑定起来。配置可以被当作设计中的元件清单表。它描述的是对于每个实体,使用的是哪个行为,更像一个元件清单表,用来描述设计中每个器件具体使用的哪个元件。 4. 包PACKAGE 包中包含了设计中公用的数据类型和子程序的集合。可以把包想象成包含构建设计所需要工具的一个工具箱。 5. 驱动器DRIVER 驱动器就是信号的驱动源,如果一个信号被两个源所驱动,那么当两个驱动源都有效时,则这个信号具有两个驱动源。 6. 总线BUS 提到总线,通常会想到是一组信号或者是硬件设计中所使用的一种特定通信方法。在VHDL中,总线是一类特殊类型的信号,它能够将其驱动源关掉,即呈现高阻态。 7. 属性ATTRIBUTE 属性是附属于VHDL对象的数据。比如,缓冲器的并行驱动能力,器件的最大工作温度。 8. 类属说明GENERIC 类属参数是用于表示向实体传递信息的参数。比如,如果一个实体是具有上升延时和下降延时的门级模型,则上升延时和下降延时的具体数值可以使用GENERIC参数向实体传送。 9. 进程PROCESS 进程是VHDL中基本的执行单元。VHDL描述的仿真中,所有的操作都可以被分解成一个或多个进程。 10. 库LIBRARY 库是经编译后的数据集合。它存放包括集合定义、实体定义、结构体定义和配置定义。库的功能类似于操作系统中的目录,库中存放设计的数据。库通常放在设计的最前面,意味着库中的内容可以供设计者共享。 3.2VHDL的三种不同描述风格 VHDL包括主级设计单元和次级设计单元。其中主级设计单元包括实体ENTITY和包PACKAGE,次级设计单元是结构体ACHITECTURE和包体PACKAGE BODY。次级设计单元通常是和主级设计单元相关的。库则是主级设计单元和次级设计单元的集合。 3.2.1实体 以四选一的数据选择器,其逻辑符号描述如图31所示,其中a、b、c、d是四个数据输入端,s1、s0是地址选择端,x是输出端。 图31四选一数据 选择器符号 其所对应的实体描述如下: ENTITY mux IS PORT ( a, b, c, d : IN BIT; s0, s1 : IN BIT; x : OUT BIT); END mux; 其中VHDL预留的关键词都用大写表示: 关键词ENTITY及IS标明是实体描述开始; END 标明实体描述结束; PORT表示开始描述端口信号信息; IN 表示端口信号的方向为输入信号; OUT表示端口信号的方向为输出信号; BIT表示端口信号的信号类型为位类型(0、1两种取值)。实体描述中小写的mux是设计者定义的实体名称,a、b、c、d、s0、s1、x是设计者定义的端口信号名称。 实体的端口方向(也称端口模式)包括四类,如表31所示。 表31实体端口模式 端 口 模 式说明 IN数据只能从端口流入实体 OUT数据只能从实体流出端口 INOUT双向,数据可以从端口流入/流出实体 BUFFER数据从端口流出实体,同时被内部反馈 由此可见,实体描述包括实体名称和器件端口情况,包括哪些端口信号,信号的方向以及信号类型。总之,实体描述的就是器件的逻辑符号,即器件封装起来对外呈现的样子。上面的实体描述是所有复杂设计的基础框架,在此基础上,实体还可以包含更多的信息。比如类属说明语句GENERIC,它通常用来定义端口的总线宽度,实体中子元件的数目,以及实体参数等静态信息。它本质上就是定义一个常数,并且将其传递到实体描述内部。 比如, GENERIC(w:INTEGER:=16); PORT(abus: OUT BIT_VECTOR(w-1 DOWNTO 0)); 上面两句就相当于 PORT(abus: OUT BIT_VECTOR(15 DOWNTO 0)); 语法说明: 这个端口定义了一个端口信号abus,信号方向为输出,信号类型为BIT_VECTOR。BIT_VECTOR是位矢量,可以理解成是一维数组,数组的每一位都是BIT类型,数组元素个数和元素排序则由括号里内容决定: (15 DOWNTO 0)表示数组元素个数为16个,元素排序从15到0; 而如果是BIT_VECTOR(0 to 15),则仅仅是元素排序从0到15。 3.2.2结构体 结构体则描述了实体的基本功能,包含了建模实体行为的具体描述。基于VHDL描述数字系统,有三种不同的描述风格: (以下三种不同风格的结构体描述均基于前面4选1数据选择器的实体描述)。 1. 数据流描述风格(也称作并行语句描述风格) ARCHITECTURE dataflow OF mux IS SIGNAL select : INTEGER; BEGIN select <= 0 WHEN s0 = '0' AND s1 = '0' ELSE 1 WHEN s0 = '1' AND s1 = '0' ELSE 2 WHEN s0 = '0' AND s1 = '1' ELSE 3; x <= a AFTER 0.5 NS WHEN select = 0 ELSE b AFTER 0.5 NS WHEN select = 1 ELSE c AFTER 0.5 NS WHEN select = 2 ELSE d AFTER 0.5 NS; END dataflow; 其中关键词ARCHITECTURE标明结构体描述的开始,该结构体的名字为dataflow,其所匹配的实体为mux。关键词ARCHITECTURE和BEGIN之间是结构体中所使用的信号声明和组件声明。在上例中,声明了一个select信号,并且其信号类型为INTEGER。需要强调: VHDL是强数据类型的语言,不管常量、信号、还是变量,声明都要说清楚数据类型。在ARCHITECTURE后面第一个BEGIN之后一直到关键词END之间是并行语句,即所有语句是并行执行,和语句书写的先后顺序无关,也就说这个区段的语句可以互相颠倒顺序,而不影响设计的行为功能。 条件信号赋值 select <= 0 WHEN s0 = '0' AND s1 = '0' ELSE 1 WHEN s0 = '1' AND s1 = '0' ELSE 2 WHEN s0 = '0' AND s1 = '1' ELSE 3; 符号“<=”代表信号赋值。这里是给信号select进行赋值,具体取值可能为0、1、2、3四种。当信号s0、s1取00时,select信号为0; 当s0、s1取10时,select信号为1; 当s0、s1取01时,select信号为2; 其他情况,select信号为3(关于赋值语句的更多内容请参见信号赋值语句部分)。 接下来对信号x的赋值也是类似的理解方法,根据select信号的取值情况给信号赋值,不同之处在于信号x的赋值并不是立即发生的,而是需要往后延时0.5ns才发生。 这两条信号赋值语句是并行语句,也就是二者是被同时执行的,其书写顺序可以颠倒并不影响功能。但是信号赋值是否被激活,取决于赋值符号右侧的信号,如果右侧的信号至少有一个有事件,则信号赋值被激活。所谓事件就是信号从0到1或者从1到0的一次变化。也就是说赋值符号右侧的信号中,至少有一个信号有变化,信号赋值才真正被激活,否则赋值语句挂起。 结构体中的语句是并行语句,究其本质是因为它描述的数字电路中,各个节点的逻辑值向后传递是同时发生,与节点所在的位置无关。而节点则对应VHDL中的信号,节点的这种并行传输信号特性,也就决定了结构体中多条信号赋值语句必然也是并行执行的。进而推广结论: 结构体中的语句都是并行语句。 2. 结构化描述风格(元件例化,也称作图形网表设计) 结构化描述是从网表的角度,描述各元件的引脚连接。上例中,在结构体的BEGIN之前,声明了待设计的数据选择器使用三个元件(COMPONENT),分别为andgate、inverter、orgate。而这三个元件应该都有属于自己的VHDL完整描述,也即有以andgate、inverter、orgate命名的实体和实体对应的结构体。 ARCHITECTURE netlist OF mux IS COMPONENT andgate PORT(a, b, c : IN BIT; x : OUT BIT); END COMPONENT; COMPONENT inverter PORT(in1 : IN BIT; x : OUT BIT); END COMPONENT; COMPONENT orgate PORT(a, b, c, d : IN BIT; x : OUT BIT); END COMPONENT; SIGNAL s0_inv, s1_inv, x1, x2, x3, x4 : BIT; BEGIN U1 : inverter PORT MAP(s0, s0_inv); U2 : inverter PORT MAP(s1, s1_inv); U3 : andgate PORT MAP(a, s0_inv, s1_inv, x1); U4 : andgate PORT MAP(b, s0, s1_inv, x2); U5 : andgate PORT MAP(c, s0_inv, s1, x3); U6 : andgate PORT MAP(d, s0, s1, x4); U7 : orgate PORT MAP(b => x2, a => x1, d => x4, c => x3, x => x); END netlist; 元件声明语句的格式如下: COMPONENT 元件名 IS PORT(端口名表) END COMPONENT; 在数据选择器的顶层描述中,这三个元件的调用和连接关系是用元件例化语句实现的。元件例化语句的格式如下: 例化名: 元件名 PORT MAP ([端口名=>] 连接端口名,…); 在本例中,比如U1 : inverter(s0,s0_inv); 表示调用元件模型inverter,在mux的顶层设计中被称作U1,inverter的in1在顶层中连接的是s0信号,x在顶层中连接的是s0_inv信号。这种按照元件端口名顺序依次匹配关联顶层设计中的连接端口名的方式称作位置映射法,上例中U1U6都是采用这种关联方式。此时,端口的位置必须严格与元件声明中的完全相同,不可调整; U7则是采用端口名映射法,即明确这里的符号“=>”仅代表连接映射关系,不代表信号流动的方向,即不限制信号的流动方向。符号“=>”左边是元件的内部端口名,右边则是元件外部需要连接的端口名或信号名。此时直接描述的是端口名的关联关系,因此端口名的描述位置是可以调整的。比如,上例中U7,先描述的是端口b的关联关系,后描述的是端口a的关联关系。这与元件声明中的端口顺序不同。上面结构化描述对应的网表连接图如图32所示。 图324选1数据选择器原理结构图 3. 行为描述风格(也称作时序描述风格,要使用PROCESS语句) ARCHITECTURE sequential OF mux IS BEGIN PROCESS(a, b, c, d, s0, s1 ) VARIABLE sel : INTEGER; BEGIN IF s0 = '0' and s1 = '0' THEN sel := 0; ELSIF s0 = '1' AND s1 = '0' THEN sel := 1; ELSIF s0 = '0' AND s1 = '0' THEN sel := 2; ELSE sel := 3; END IF; CASE sel IS WHEN 0 => x <= a; WHEN 1 => x <= b; WHEN 2 => x <= c; WHEN OTHERS => x <= d; END CASE; END PROCESS; END sequential; 行为描述风格采用PROCESS语句去描述数字选择器的行为功能。进程PROCESS关键词后面括号内是进程的敏感信号表,只有当敏感信号表中至少一个信号有事件,即信号有从0到1或者从1到0的变化,进程才被执行,称作进程被“激活”; 反之,如果敏感信号表中的信号都没有事件,进程不会被执行,称作进程被“挂起”。进程PROCESS关键词到BEGIN之间是声明区,可以声明进程中使用的变量,此例中声明了一个sel变量。变量在进程内使用,出了进程,则变量不再存在。进程下面的BEGIN标识到END PROCESS之间是进程的详细描述,这之间的语句是顺序执行语句,即按照语句出现的先后顺序执行。本例中使用了IF结构语句和CASE语句。CASE语句根据sel变量的值选择不同的分支执行。比如变量sel为1,则CASE语句选择执行x <= b。 3.2.3配置 前面针对同一个4选1数据选择器实体,有三种不同描述风格的结构体。使用配置语句,设计者可以选择究竟使用哪个结构体。另外,配置语句也可以在结构体中选择不同的元件。 CONFIGURATION muxcon1 OF mux IS FOR netlist FOR U1,U2 : inverter USE ENTITY WORK.myinv(version1); END FOR; FOR U3,U4,U5,U6 : andgate USE ENTITY WORK.myand(version1); END FOR; FOR U7 : orgate USE ENTITY WORK.myor(version1); END FOR; END FOR; END muxcon1; 前面的配置描述具体含义如下: 这是实体mux的配置方案,称作muxcon1。对于最顶层实体mux,使用结构体netlist; 而对于结构体netlist中被例化的两个inverter类型U1、U2,使用WORK库中的实体为myinv,结构体为version1的设计描述; 对于结构体netlist中被例化的四个andgate类型U3、U4、U5、U6,使用WORK库中的实体为myand,结构体为version1的设计描述; 对于结构体netlist中被例化的orgate类型 U7,使用WORK库中的实体为myor,结构体为version1的设计描述。如果需要更换实体mux的结构体,则只需要更换配置,不需要重新进行结构体描述。下面配置为实体mux的第二个配置方案: 对于最顶层实体mux,使用结构体dataflow。 CONFIGURATION muxcon2 OF mux IS FOR dataflow END FOR; END muxcon2; 由此可见,利用配置语句,可以非常方便地实现在设计的各个级别上(包括结构体和其内部元件等不同级别)进行灵活选择不同风格的设计描述。 3.3数据对象 数据对象是VHDL程序设计中进行各种运算与操作的对象。VHDL使用的数据对象主要有四种: 常量、变量、信号和文件。 3.3.1常量 常量(CONSTANT)是用来存储指定数据类型的数值。虽然常量与数值在程序的功能上没有什么差别,但它能有效提高VHDL的可读性与安全性。通俗地讲,常量就是有了名字的固定数值,常量在声明时需要指定相应的数据类型与初始值。常量的数据类型可以是标量或复合类型,但不能是文件类型或存取类型。常量的声明格式如下: CONSTANT常量名: 常量的数据类型∶=值 例如: CONSTANT PI: REAL:=3.14; CONSTANT bus_width: INTEGER:=16; CONSTANT led: STD_LOGIC_VECTOR(7 DOWNTO 0):="10101010"; 常量的作用域要视常量的声明位置而定,在VHDL程序不同位置声明的常量,作用域也不一样。VHDL程序中以下的程序位置可进行常量的声明: (1) 程序包中声明的常量,引用该程序包的VHDL程序都可以使用该常量。 (2) 实体声明部分的generic语句声明的常量,则该实体的结构体可以使用该常量。 (3) 结构体声明的常量,则该结构体内的所有语句都可以使用该常量。 (4) 进程、子程序和函数中声明的常量,则只能在其内部使用该常量。 代码31和代码32详细说明了常量的声明与使用的规则。 【代码31】简单的程序包。 PACKAGE mylib IS CONSTANT bus_length: INTEGER:=8; END mylib; 代码31是一个简单的VHDL库文件,它声明了一个整型常量bus_length,它的值为8。 【代码32】常量应用的程序。 LIBRARY IEEE; --ieee库的引用 USE IEEE. STD_LOGIC_1164. ALL; USE IEEE. STD_LOGIC_UNSIGNED. ALL; LIBRARY mylib;--自定义库mylib的引用 USE mylib.all; ENTITY counter IS--实体声明 GENERIC( --类属性常量定义 period: STD_LOGIC_VECTOR(bus_length-1 DOWNTO 0)∶="01100100"; ); PORT(clk: IN STD_LOGIC; reset_n: IN STD_LOGIC; cntout: OUT STD_LOGIC_VECTOR(bus_length-1 DOWNTO 0)); END counter; ARCHITECTURE behave OF counter IS CONSTANT addone:STD_LOGIC_VECTOR(bus_length-1 DOWNTO 0):="00010100"; SIGNAL cnt: STD_LOGIC_VECTOR(bus_length-1 DOWNTO 0):="00000000"; BEGIN PROCESS(clk, reset_n) BEGIN IF reset n= '0' THEN cntout<= "00000000"; ELSIF clk'EVENT AND clk= '1' THEN cntout<= cnt; END IF; END PROCESS; PROCESS(clk, reset_n) CONSTANT addtwo: STD_LOGIC_VECTOR(bus_length-l DOWNTO 0):="00101000"; BEGIN IF reset_n='0' THEN cnt<= "00000000"; ELSIF clk'EVENT AND clk= '1' THEN IF cnt>period THEN --当计数器大于计数周期时,计数器归零 cnt<= "00000000"; ELSIF cnt>addtwo THEN --当计数器大于addtwo时计数器作加4计数 cnt<=cnt+"00000100"; ELSIF cnt>addone THEN --当计数器大于addone时计数器作加2计数 cnt<= cnt+"00000010"; ELSE--否则计数器作加1计数 cnt<= cnt+"00000001"; END IF; END IF; END PROCESS; END behave; 代码32分别从程序包、实体类属性、结构体声明和进程声明四个VHDL程序不同的位置介绍常量的声明。其中,程序包声明的常量,整个VHDL程序都可以使用; 实体类属性中声明的常量,在声明该常量以下VHDL程序都可以使用; 结构体声明中声明的常量,在该结构体中可以使用; 在进程、子程序和函数中声明的常量,则只能在内部使用。 3.3.2变量 变量(VARIABLE)也是用来存储指定数据类型的数值,但与常量不同的是,可以通过对变量赋值来改变变量的数值。变量的声明格式如下: VARIABLE变量名: 数据类型: =初始值; 例如: VARIABLEcnt: INTEGER:=0; VARIABLEtemp: BIT:='0'; VARIABLEflag: STD_LOGIC_VECTOR(7 DOWNTO 0); 其中,初始值是可选的,变量的初始值只在仿真时起作用,综合过程一般忽略变量的初始值。可以进行声明的VHDL程序位置是进程、子程序和函数,而且变量只在声明该变量的进程、子程序和函数中可见。 变量被声明后,就可以对变量进行赋值来更改变量数值。变量的赋值是理想化的数据传输,是即时的,不需要延时,这一点与信号不一样。变量赋值的格式如下: 变量名: =表达式; 例如: cnt: =10; temp: ='1'; 由于变量的赋值是即时的,因此在变量赋值时加上时间延时是非法的,例如: temp: =10 after 10ns;—非法语句 变量赋值只出现在进程、子程序和函数中。将代码32修改为代码33来介绍变量的声明与使用。 【代码33】变量应用的程序。 LIBRARYIEEE; USE IEEE. STD_LOGIC_1164. ALL; USE IEEE. STD_LOGIC_UNSIGNED. ALL; ENTITY counter IS GENERIC( period: STD_LOGIC_VECTOR(7 DOWNTO 0):="01100100" ); PORT(clk: IN STD_LOGIC; reset_n: IN STD_LOGIC; cntout: OUT STD_LOGIC_VECTOR(7 DOWNTO 0)); END counter; ARCHITECTURE behave OF counter IS CONSTANT addone: STD_LOGIC_VECTOR(7 DOWNTO 0):="00010100" BEGIN PROCESS(clk, reset_n) CONSTANT addtwo: STD_LOGIC_VECTOR(7 DOWNTO 0):="00101000" VARIABLE cnt: STD_LOGIC_VECTOR(7 DOWNTO 0):="00000000" BEGIN IF reset_n= '0' THEN cnt: ="00000000"; cntout<= "00000000"; ELSIF clk'EVENT AND clk= '1' THEN cntout<= cnt; IF cnt>period THEN cnt: ="00000000"; ELSIF cnt>addtwo THEN cnt: =cnt+"00000100"; ELSIF cnt>addone THEN cnt: =cnt+"00000010"; ELSE cnt: =cnt+"00000001"; END IF; END IF; END PROCESS; END behave; 由于变量只有在声明该变量的进程中使用,所以不能像代码32在另外一个进程对cntout进行赋值,而只能在同一进程进行赋值。 2002版VHDL标准支持共享变量,共享变量可以在进程、子程序和函数之外声明,也可以在结构体或程序包中声明。相应地,共享变量的使用域相对普通变量要广,与信号应用相类似,但没有信号相应的属性。代码34介绍了共享变量的使用方法。 【代码34】共享常量应用的程序。 LIBRARY IEEE; USE IEEE. STD_LOGIC_1164. ALL; USE IEEE. STD_LOGIC_UNSIGNED. ALL; ENTITY sharevar IS PORT(clk:IN STD_LOGIC; sel: IN STD_LOGIC; dout: OUT STD_LOGIC_VECTOR(7 DOWNTO 0)); END sharevar; ARCHITECTURE behave OF sharevar IS CONSTANT dcon: STD_LOGIC_VECTOR(7 DOWNTO 0):="10101010"; —在结构体的声明中声明一个共享变量cnt SHARED VARIABLE cnt: STD_LOGIC_VECTOR(7 DOWNTO 0):="00000000"; BEGIN PROCESS(clk) --在此进程中对共享变量进行加1计数 BEGIN IF clk'EVENT AND clk='1'THEN cnt: =cnt+"00000001"; END IF; END PROCESS; PROCESS(sel)--在另外一个进程对共享变量进行使用 BEGIN IF sel='1'THEN dout<=cnt; ELSE dout<=dcon; END IF; END PROCESS; END behave; 3.3.3信号 信号(SIGNAL)是VHDL作为硬件描述语言的一个主要特征。信号既可以描述硬件内部之间的连线; 也可以描述一种数值寄存器,可以保留历史值。在VHDL中,信号是具有最多属性的数据对象,有关信号的属性将在后面章节进行讨论。信号的声明的格式如下: SIGNAL信号名: 数据类型: =初始值; 例如: SIGNAL temp: STD_LOGIC; SIGNAL cnt: STD_LOGIC_VECTOR(7 DOWNTO 0); SIGNAL data_bus: STD_LOGIC_VECTOR(7 DOWNTO 0):="00000000"; 其中,信号声明中的初始值是可选的,而且信号的初始值只在仿真时起作用,综合过程一般会被忽略。信号的声明位置可以是实体的声明部分、结构体和程序包,但是不可在进程、子程序和函数中声明。其中,在程序包声明中声明的信号在引用该程序包的VHDL程序中都是可见的,使用时需要特别注意。在实体声明部分声明的信号即端口信号,除了有方向限制外,其他与在结构体声明中声明的信号一样的。 信号声明后就可以对信号进行赋值操作以改变信号数值。信号赋值的格式如下: 信号名<=表达式; 例如: temp<='1'; cnt<=cnt+"00000001"; data_bus<="00001000"; 同样,由于VHDL语言是强类型的程序设计语言,因此信号赋值语句右边表达值的类型必须与信号的类型一致,如果不一致则需要进行类型转化。信号的赋值操作与变量的赋值操作除了操作符不一样外(信号赋值为“<=”,变量赋值为“: =”),信号赋值有一个延时过程,而变量赋值没有延时过程是即时的。信号赋值可以直接出现在结构体作为并行语句,也可出现在进程、子程序和函数中作为顺序语句,代码35介绍了信号的使用方法。 【代码35】信号应用的程序。 LIBRARY IEEE; USE IEEE. STD_LOGIC_1164. ALL; USE IEEE. STD_LOGIC_UNSIGNED. ALL; ENTITY usesignal IS PORT(clk: IN STD_LOGIC; sel: IN STD_LOGIC; dcon: IN STD_LOGIC_VECTOR(7 DOWNTO 0); dout: OUT STD_LOGIC_VECTOR(7 DOWNTO 0); cntout: OUT STD_LOGIC_VECTOR(7 DOWNTO 0)); END usesignal; ARCHITECTURE bebave OF usesignal IS --结构体声明中声明信号cnt SIGNAL cnt: STD_LOGIC_VECTOR(7 DOWNTO 0):="00000000"; BEGIN PROCESS (clk)--信号在进程中使用 BEGIN IF clk'EVENT AND clk= '1' THEN cnt<= cnt+"00000001"; END IF; END PROCESS; cntout<= cnt; --信号在进程外使用 PROCESS(sel, dcon, cnt) BEGIN IF sel= '1' THEN dout<= cnt; ELSE dout<= dcon; END IF; END PROCESS; END behave; 代码35中实体声明部分的端口名前面虽然没有保留字SIGNAL,但实体端口隐含了信号的意义,默认也为信号对象。'EVENT是信号属性之一,它表示信号的值是否有事件(EVENT)发生,如果有事件发生则返回真,否则返回假。 信号是VHDL的一大特色,VHDL支持以下的信号属性,假设sig为一信号,t为一时间值。其中前4条属性返回为特殊的信号,可用于正常信号可使用的位置,包括敏感信号表,但不能用于子程序中。  sig'DELAYED(t)——结果是创建一个类型与sig相同的信号,且相对sig信号具有t时间的延时。  sig'STABLE(t)——结果是创建一个BOOLEAN类型的信号,如果sig在时间t内没有事件发生则结果为真,否则为假。  sig'QUIET(t)——结果是创建一个BOOLEAN类型的信号,如果sig在时间t内没有事件/事项处理(TRANSACTION)发生则结果为真,否则为假。  sig'TRANSACTION——当sig有事件/事项处理(TRANSACTION)发生时,则所创建的BIT类型信号发生翻转。  sig'EVENT——如果sig有事件发生则结果为真,否则为假。  sig'ACTIVE——如果sig有事件/事项处理(TRANSACTION)发生则结果为真,否则为假。  sig'LAST_EVENT——sig最后一次事件发生的时间间隔。  sig'LAST_ACTIVE——sig最后一次事件/事项处理发生的时间间隔。  sig'LAST_VALUE——sig最后一次事件发生前的值。 信号属性应用举例:  clk'EVENT AND clk='1'——时钟信号clk上升沿的表示方法。  clk'EVENT AND clk='0'——时钟信号clk下降沿的表示方法。 3.3.4别名 别名是指向一个数据对象或数据对象的部分可替换标识符,对这个标识符的操作相当于对被替换数据对象的操作。别名的声明格式如下: ALIAS别名名称: 别名的数据类型IS数据对象; 对别名的应用举例如下: VARIABLE addr: STD_LOGIC_VECTOR(7 DOWNTO 0); VARIABLE data: STD_LOGIC_VECTOR(15 DOWNTO 0); ALIAS myaddr STD_LOGIC_VECTOR(7 DOWNTO 0) IS addr; --声明一个别名myaddr指向addr ALIAS udata: STD_LOGIC_VECTOR(7 DOWNTO 0) IS data(15 DOWNTO 8); --声明一个别名udata指向data的高8位 ALIAS ldata: STD_LOGIC_VECTOR(7 DOWNTO 0) IS data(7 DOWNTO 0); --声明一个别名ldata指向data低8位 myaddr:="01001101"; --相当于对addr赋值 udata:="11110000";--相当于对data的高8位赋值 ldata:="00001111";--相当于对data的低8位赋值 3.3.5常量、变量和信号的比较 从硬件电路系统看,常量相当于电路中的恒定电平,如GND或VCC接口; 而信号则相当于组合电路系统中门/模块间的连接及其连线上的信号值; 变量则用来暂存中间结果。 从行为仿真和VHDL语句功能上看,变量和信号的区别主要表现在接收和保持信息的方式、信息保持与传递的区域大小上。例如,信号可以设置延时量,而变量则不能; 变量只能作为局部的信息载体,而信号则可作为模块间的信息载体。变量的设置有时只是一种过渡,最后的信息传输和接口间的通信都靠信号来完成。 从综合后对应的硬件电路结构看,信号一般对应特定的硬件结构,但在许多情况下,信号和变量没有什么区别。例如,在满足一定条件的进程中,综合后它们都能引入寄存器。这时它们都具有能够接受赋值这一重要的共性,而VHDL综合器并不理会它们在接受赋值时存在的延时特性。 虽然VHDL仿真器允许变量和信号设置初始值,但在实际运用中,VHDL综合器并不会把这些信息综合进去。这是因为实际的FPGA/CPLD芯片在上电后,并不能确保其初始状态的取值。因此,对于时序仿真来说,设置的初始值在综合时是没有实际意义的。 信号和变量是VHDL中重要的客体,主要区别有: (1) 信号赋值至少要有delta延时; 而变量赋值没有。 (2) 信号除当前值外有许多相关的信息,如历史信息; 而变量只有当前值。 (3) 进程对信号敏感,而对变量不敏感。 (4) 信号可以是多个进程的全局信号; 而变量只在定义它们的顺序域可见(共享变量除外)。 (5) 信号是硬件中连线的抽象描述,它们的功能是保存变化的数据值和连接子元件,信号在元件的端口连接元件。变量在硬件中没有类似的对应关系,用于硬件特性的高层次建模所需要的计算中。 信号相对变量来说,具有更多的硬件属性,信号与电路节点相对应,而变量则仅仅是为临时存储中间结果而设置的。 3.4数据类型 数据类型是程序设计语言的重要特征之一,VHDL又是强类型的程序设计语言,因此对数据类型是否了解在进行VHDL程序设计起到关键的作用。之所以称VHDL为强类型的程序设计语言,是因为每一种数据类型决定了它声明的数据对象所能进行的操作,即不同数据类型数据对象的操作是不同的。VHDL支持的常用数据类型主要有四类: (1) 标量(Scalar): 标量类型的数据只有一个存储单元,有具体的数值大小,主要包括枚举类型、整数类型、物理类型和浮点类型。 (2) 复合(Composite): 复合类型的数据是由相同类型的元素(数组类型)或不同类型的元素(记录类型)组成。 (3) 存取(Access): 存取类型提供对指定类型对象的存取,本书不展开讨论。 (4) 文件(File): 文件提供对一系列指定类型数据的读取、写入和文件的检查,本书也不展开讨论。 VHDL语言中的数据类型总览图如图33所示。 图33VHDL中的数据类型总览图 3.4.1标量 标量类型的数值是有大小之分的,关系操作符可以操作标量的数据对象。标量类型的数据具体包括: 整型(INTEGER)、实型(REAL)、物理型(Physical)、枚举型(Enumeration)。枚举型又包括字符(CHARACTER)、位(BIT)和布尔型(BOOLEAN)等,物理型常用的有时间型(Time)。这里仅介绍VHDL中比较独特的部分数据类型,而关于整型和实型请大家自行参考相关说明。 1. 物理型 物理型的值是物理量的度量单元,比如长度、质量、电流、时间等。物理型数据类型一般是不可综合的,它们只应用在仿真过程中。它们的声明是基本单元和二级单元的组合,在默认的STANDARD程序包中预定义的物理型是时间TIME数据类型。TIME数据类型在STANDARD程序包的定义如下: TYPE TIME IS RANGE0 TO 2147483647 UNITS fs;--飞秒VHDL中的最小时间单位 ps= 1000 fs;--皮秒 ns= 1000 ps;--纳秒 us= 1000 ns;--微秒 ms= 1000 us;--毫秒 sec= 1000 ms;--秒 min= 60 sec;--分 hr= 60 min;--时 END UNITS; 2. 枚举型 枚举型定义了一种拥有用户定义数值的类型,把属于该类型的所有元素都列出来。一大优点是直观,在元素数量不多的情况下可以有效提高VHDL程序的可读性。用户定义的数值包括标识符和字符,枚举型声明举例如下: TYPE traclight IS (green, yellow, red); TYPE op IS (add, sub, mul, div, mod); TYPE logic IS('0','1','z','x'); 和其他标量数据类型一样,枚举型的元素也是有大小之分的。枚举型值的大小是通过声明时的顺序来确定的,左边的值总是小于右边的值,如上面声明的op枚举型add来代替,定义格式如下: TYPE数组名IS ARRAY(索引下标子类型RANGE<>) OF数据类型; 举例如下: TYPE data IS ARRAY (INTEGER<>) OF BIT; TYPE bus IS ARRAY (INTEGER<>) OF BIT; 约束性数组使用举例如下: --声明一个data数组类型的信号datain时需要加上具体的范围约束条件,它包含8位 SIGNAL datain: data(7 DOWNTO 0); --声明一个bus数组类型的信号addr_bus时需要加上具体的范围约束条件,它包含8位 SIGNAL addr_bus: bus(0 TO 7); 常用的非约束数组类型有: STRING是字符元素的集合,BIT_VECTOR是BIT的集合; STD_LOGIC_VECTOR是STD_LOGIC的集合。它们的声明如下: TYPE STRING IS ARRAY (POSITIVE RANGE<>) OF CHARACTER; TYPE BIT_VECTOR IS ARRAY (NATURAL RANGE<>) OF BIT; TYPE STD_LOGIC_VECTOR IS ARRAY (NARURAL RANGE<>) OF STD_LOGIC; --以上索引范围没有确定的数组是非约束型的数组类型 --用上面的数据类型声明声明信号的格式如下 SIGNAL str: STRING(1 TO 12):="I love FPGA!"; SIGNAL data: BIT_VECTOR(7 DOWNTO 0); SIGNAL address: BIT_VECTOR(7 DOWNTO 0); 二维数组在硬件中一般对应一块存储块,其定义举例如下: --定义二维数组方式一: 非约束方式 TYPE readdata IS ARRAY (NATURAL RANGE<>, NATURAL<>) OF BIT; --定义二维数组方式: 约束方式 TYPE writedata IS ARRAY(7 DOWNTO 0) OF BIT_VECTOR(7 DOWNTO 0); --定义二维数组方式一:定义的数组类型声明一个二维数组需要指定约束范围 SIGNAL rdata: readdata(7 DOWNTO 0,7 DOWNTO 0); --定义二维数组方式二:定义的数组类型声明一个二维数组不需要指定约束范围 SIGNAL wdata: writedata; VHDL中数组的属性提供了数组对象或类型索引下标值信息。VHDL支持的预定义属性如下(a代表数组对象存取类型变量,n代表数组标号):  a'LEFT(n)——n维数组索引下标范围中最左边的值;  a'RIGHT(n)——n维数组索引下标范围中最右边的值;  a'LOW(n)——n维数组索引下标范围中最小的值;  a'HIGH(n)——n维数组索引下标范围中最大的值;  a'RANGE(n)——n维数组索引下标范围;  a'REVERSE_RANGE(n)——与原来n维数组的方向和边界相反的数组的范围;  a'LENGTH(n)——n维数组索引下标范围的长度;  a'ASCENDING(n)——如果n维数组索引下标范围为升序,则为TRUE,否则为FALSE。 3.4.3数据类型转换 由于VHDL是强类型的程序设计语言,当不同数据类型的数据对象进行运算时,就需要做数据类型转换。STD_LOGIC_ARITH程序包定义了多个数据类型转换函数,引用它就可以在VHDL程序中使用STD_LOGIC_ARITH提供的数据转换函数实现数据类型的转换。STD_LOGIC_ARITH提供的转换函数有如下: FUNCTION CONV_INTEGER(arg: INTEGER) RETURN INTEGER; FUNCTION CONV_INTEGER(arg: UNSIGNED) RETURN INTEGER; FUNCTION CONV_INTEGER(arg: SIGNED) RETURN INTEGER; FUNCTION CONV_INTEGER(arg: STD_ULOGIC) RETURN INTEGER; FUNCTION CONV_UNSIGNED(arg: INTEGER; size: INTEGER) RETURN UNSIGNED; FUNCTION CONV_UNSIGNED(arg: UNSIGNED; size: INTEGER) RETURN UNSIGNED; FUNCTION CONV_UNSIGNED(arg: SIGNED; size: INTEGER) RETURN UNSIGNED; FUNCTION CONV_UNSIGNED(arg: STD_ULOGIC; size: INTEGER) RETURN UNSIGNED; FUNCTION CONV_SIGNED(arg: INTEGER; size: INTEGER) RETURN SIGNED; FUNCTION CONV_SIGNED(arg: UNSIGNED; size: INTEGER) RETURN SIGNED; FUNCTION CONV_SIGNED(arg: SIGNED; size: INTEGER) RETURN SIGNED; FUNCTION CONV_SIGNED(arg: STD_ULOGIC; size: INTEGER) RETURN SIGNED; FUNCTION CONV_STD_LOGIC_VECTOR(arg: INTEGER; size: INTEGER) RETURN STD_LOGIC_VECTOR; FUNCTION CONV_STD_LOGIC_VECTOR(arg: UNSIGNED; size:INTEGER) RETURN STD_LOGIC_VECTOR; FUNCTION CONV_STD_LOGIC_VECTOR(arg: SIGNED; size: INTEGER) RETURN STD_LOGIC_VECTOR; FUNCTION CONV_STD_LOGIC_VECTOR(arg: STD_ULOGIC; size: INTEGER) RETURN STD_LOGIC_VECTOR; STD_LOGIC_1164程序包提供的类型转换如下: FUNCTION TO_BIT(s:STD_ULOGIC;xmap: BIT:='0') RETURN BIT; FUNCTION TO_BITVECTOR(s:STD_LOGIC_VECTOR;xmap: BIT:='0') RETURN BIT_VECTOR; FUNCTION TO_BITVECTOR(s:STD_ULOGIC_VECTOR; xmap: BIT:='0') RETURN BIT_VECTOR; FUNCTION TO_STDULOGIC(b:BIT) RETURN STD_ULOGIC; FUNCTION TO_STDLOGICVECTOR(b: BIT_VECTOR) RETURN STD_LOGIC_VECTOR; FUNCTION TO_STDLOGICVECTOR(s:STD_ULOGIC_VECTOR) RETURN STD_LOGIC_VECTOR; FUNCTIoN TO_STDULOGICVECTOR(b:BIT_VECTOR)RETURN STD_ULOGIC_VECTOR; FUNCTION TO_STDULOGICVECTOR(s:STD_LOGIC_VECTOR) RETURN STD_ULOGIC_VECTOR; 代码36介绍了使用类型转换函数的VHDL程序。 【代码36】函数转换应用的程序。 --库的引用 LIBRARY IEEE; USE IEEE.STD_LOGIC_1164. ALL; USEIEEE. STD_LOGIC_ARITH. ALL; USE IEEE. STD_LOGIC_UNSIGNED. ALL; ENTITY typeconv IS PORT(clk: IN STD_LOGIC; reset_n: IN STD_LOGIC; dout: OUT STD_LOGIC_VECTOR(7 DOWNTO 0)); END typeconv; ARCHITECTURE behave OF typeconv IS SIGNAL cnt:INTEGER RANGE 0 TO 255; BEGIN PROCESS(clk, reset_n) BEGIN IF reset_n='0' THEN --应用类型转换函数,把INTEGER类型转换为STD_LOGIC_VECTOR dout<= CONV_STD_LOGIC_VECTOR(0,8); ELSIF clk'EVENT AND clk='1' THEN --应用类型转换函数,把INTEGER类型转换为STD_LOGIC_VECTOR dout<= CONV_STD_LOGIC_VECTOR(cnt,8); END IF; END PROCESS; END bebave; 3.4.4子类型 子类型是某一数据类型的一个子集。有时为了提高程序的可读性,一些数据对象的取值只是某一数据类型的一个子集,这时可以定义一个子类型来专门为这些数据对象进行声明。子类型的基本格式如下: SUBTYPE子类型名IS数据类型RANGE<约束范围>; 举例如下: --定义一个子类型smallnum,它是整型的子集,取值范围为0~255 SUBTYPE smallnum IS INTEGER RANGE 0 TO 255; --用子类型smallnum声明一个变量,这样cnt的取值范围就是0~255 VARIABLE cnt: smallnum:=0; 3.5行为描述 3.5.1信号赋值 信号赋值分为顺序信号赋值和并行信号赋值两种情况。 1. 顺序信号赋值 顺序信号赋值发生在进程中,前面例子中的信号只是简单形式的信号赋值,另外还有条件信号赋值和选择信号赋值。 条件信号赋值语句: reg : PROCESS (clk) IS BEGIN IF RISING_EDGE(clk) THEN q <= (OTHERS => '0') WHEN reset ELSE d; END IF; END PROCESS reg; 上面进程中的选择信号赋值语句相当于下面这个if结构。 IF reset THEN q <= (OTHERS => '0'); ELSE q<=d; END IF; 选择信号赋值语句: 如果在一个进程中使用选择信号赋值语句去实现一个数据选择器,如下。 WITH d_sel SELECT q <= source0 WHEN "00", source1 WHEN "01", source2 WHEN "10", source3 WHEN "11", source4 WHEN OTHERS; 它可以用下面的case语言来等效: CASE d_sel IS WHEN "00" => q <= source0; WHEN "01" q <= source1; WHEN "10 q <= source2; WHEN "11" q <= source3; WHENOTHERS q <= source4; END CASE; 进程的信号赋值并不是按照出现的先后顺序赋值的,而是在进程结束时同时被赋值的。如果在进程存在多条信号赋值语句,则这些信号赋值都是在进程结束时同时被赋值的。如果在进程中有多条赋值语句对同一个信号赋值,则该信号被赋予的值是最后一条赋值语句的数值。而如果结构体中有多个进程,则认为各个进程是同时结束的。 【代码37】 LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; ENTITY mux4 IS PORT (i0, i1, i2, i3, a, b : IN STD_LOGIC; q:OUT STD_LOGIC) ; END mux4; ARCHITECTURE body_mux4 OF mux4 IS SIGNAL muxval : INTEGER RANGE 7 DOWNTO 0; BEGIN PROCESS(i0, i1, i2, i3, a,b) BEGIN muxval<=0; IF(a=’1’) THEN muxval<=muxval+1; END IF; IF(b=’1’) THEN muxval<=muxval+2; END IF; CASE muxval IS WHEN 0 => q<=i0; WHEN 1 => q<=i1; WHEN 2 => q<=i2; WHEN 3 => q<=i3; WHEN OTHERS => NULL; END CASE; END PROCESS; END body_mux4; 【代码38】 LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; ENTITY mux4 IS PORT (i0, i1, i2, i3, a, b : IN STD_LOGIC; q:out STD_LOGIC) ; END mux4; ARCHITECTURE body_mux4 OF mux4 IS BEGIN PROCESS(i0,il,i2,i3,a,b) VARIABLE muxval : INTEGER RANGE 7 DOWNTO 0; BEGIN muxval:=0; IF(a=’1’) THEN muxval:=muxval+1; END IF; IF(b=’1’) THEN muxval:=muxval+2; END IF; CASE muxval IS WHEN 0 => q<=i0; WHEN 1 => q<=i1; WHEN 2 => q<=i2; WHEN 3 => q<=i3; WHEN OTHERS => NULL; END CASE; END PROCESS; END body_mux4; 代码37中,信号muxval在进程中出现了三次赋值操作,即有三个赋值源: muxval<=0,muxval<=muxval+1和muxvalv<=muxval+2; 但根据进程中信号的赋值规则,前两个赋值语句中的赋值目标信号muxval都不可能得到更新,只有最后的muxval<=muxval+2语句中的 muxval的值得到更新。然而,由于传输符号右边的muxval始终未得到任何确定的初始值, 即语句muxval<=0并未完成赋值,所以muxval始终是个未知值。可以证明,代码37的设计只能被综合成随b和a (a和b被综合成了时钟输入信号)变动的时序电路,从而导致 muxval成为一个不确定的信号。结果在进程最后的CASE语句中,无法通过判断muxval的值来确定选通输入,即对q的赋值。这在图34中十分清晰。 图34代码37的错误工作时序 代码38就不一样了。程序首先将muxval定义为变量,根据变量顺序赋值以及暂存数据的规则,首先执行了语句muxval: =0(muxval即刻被更新),从而使两个IF语句中的muxval都能得到确定的初值。另一方面,当IF语句不满足条件时,即当a或b不等于1时,由于muxval已经在第一条赋值语句中被更新为确定的值(即0) 了,所以尽管两个IF语句从表面上看很像不完整的条件语句,但都不可能被综合成时序电路。显然代码38是一个纯组合电路,它们也就有了图35的正确波形输出。 图35代码38的正确工作时序 2. 并行信号赋值 所谓并行信号赋值,是指发生在结构体中的信号赋值。同样也存在简单信号赋值、条件信号赋值、选择信号赋值三种情况。由于结构体的语句都是并行语句,因此如果有多个信号赋值,则它们之间是并行执行的,即都是在结构体结束的瞬间同时被赋值的。但是如果对同一个信号进行多次并行赋值,则VHDL原则是不允许的,即此时认为发生了线与,需要进行决断处理,否则系统会报错。 在结构体中的一次信号赋值被称为信号的一次驱动,等号右边的信号被称作驱动源,而信号赋值语句则构成了一个驱动器。比如, ARCHITECTURE test OF test IS BEGIN a <= b AFTER 10 ns; a <= c AFTER 10 ns; END test; 上面例子中,信号a被赋值两次,即被驱动两次,这两次的驱动源分别为经过延时的信号b和信号c。由于在结构体中存在两次对同一个信号a的并行赋值,因此相当于对两个驱动源实现线与,这是不允许的,需要进行决断处理。 再比如,下面代码39例子中,对于信号q相当于存在4个驱动源,这显然是不允许的,因此需要复杂的信号决断处理。如果能够改成代码310,用一条条件信号赋值语句去实现,信号q相当于只有一个驱动源,则可以免去信号决断处理,并且能够实现正确的数据选择功能。 【代码39】 LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; ENTITY mux IS PORT (i0, i1, i2, i3, a, b: IN STD_LOGIC; q : OUT STD_LOGIC); END mux; ARCHITECTURE bad OF mux IS BEGIN q <= i0 WHEN a = '0' AND b = '0' ELSE '0'; q <= i1 WHEN a = '1' AND b = '0' ELSE '0'; q <= i2 WHEN a = '0' AND b = '1' ELSE '0'; q <= i3 WHEN a = '1' AND b = '1' ELSE '0'; END bad; 【代码310】 ARCHITECTURE better OF mux IS BEGIN q <= i0 WHEN a = '0' AND b = '0' ELSE i1 WHEN a = '1' AND b = '0' ELSE i2 WHEN a = '0' AND b = '1' ELSE i3 WHEN a = '1' AND b = '1' ELSE 'X'; -- unknown END better; 3.5.2延时 在VHDL中,关于行为建模有两种类型的延时: 惯性延时和传输延时,两类延时特性不同。除此之外,还有一个专为仿真设置的delta延时。 1. 惯性延时INERTIAL Delay 真实的电子电路通常不具有无限频率响应,由于分布寄生电容、电阻等的存在,使得输出值随输入值的改变通常需要延时特定的时间,这个时间通常被称作Tpd。只有输入值维持足够长的时间,输出值才能有相应改变,类似于器件具有一定的惯性,因此称作惯性延时(INERTIAL Delay)。惯性延时机制使得较小的输入脉冲不会引发器件输出变化。通常信号赋值中默认的延时机制就是惯性延时,用关键词INERTIAL表示。 如图36所示的缓冲器,其惯性延时时间为20ns,在10ns时刻输入信号从0变成1,则预期其将导致在30ns时刻输出发生变化。输入信号A在20ns时刻从1变到0,则预期其将导致在40ns时刻输出发生变化。由于器件的惯性延时为20ns,输出在30ns的变化还没有来得及传过来,就被输入信号新的变化所冲掉,导致输出在30ns的变化根本不会发生,而直接取代发生的是输出信号在40ns的变化。因此,在输出端,看起来输入信号的脉冲并未影响到输出端信号,其根本原因是输入信号的脉冲过窄,小于器件的惯性延时时间20ns,相当于将输入的窄小脉冲自动过滤掉了。 图36惯性延时模型 2. 传输延时 传输延时(TRANSPORT Delay)机制用来建模具有无限频率响应的理想器件,所谓无限频率响应,是指不管输入脉冲多么短暂,都会产生一个输出脉冲。理想的传输线是这类器件的典型实例,它会传送所有的输入变化,只是需要延时信号赋值语句中规定的时间。传输延时关键词是TRANSPORT,不能省略。 仍然以缓冲器为例,如果将其延时机制换成传输延时,延时时间仍然为20ns,则输入信号波形A的任何变化都会导致如图37中器件输出信号B的相应变化。传输延时不会过滤掉窄小的输入脉冲,而是会将输入信号上的任何变化都反应在输出端。而实际器件中往往不存在这样的情况,因此这种延时机制较少使用,仅仅在讨论导线延时时,通常会使用它。 图37传输延时模型 具有500ps延时的传输线建模如下所示: transmission_line : PROCESS (line_in) IS BEGIN line_out <= TRANSPORT line_in AFTER 500 ps; END PROCESS transmission_line; 3. delta仿真延时 在VHDL仿真和综合器中,有一个默认的固有延时量,它在数学上是一个无穷小量,被称为δ延时,或称仿真δ。它是VHDL仿真器的最小分辨时间,并不代表器件实际的惯性延时情况。在VHDL程序语句中,如果没有指明延时的类型与延时量,就意味着默认釆用了这个固有延时量δ延时。 要理解δ延时,首先要弄清楚VHDL仿真的过程。仿真初始化阶段,所有的进程均被执行一次,直到进程被挂起,然后就周而复始地完成多个仿真周期,直到没有新的信号更新值,不会激活新的进程执行为止。每个仿真周期包括两个阶段: 信号更新阶段和进程执行阶段。进程中,通常会对信号进行赋值,然而信号赋值并不是立即发生,信号赋值语句仅仅是信号的赋值计划(Schedules),这些赋值计划均需要等到进程被执行完,再次挂起时才能真正被赋值。也就说进程执行完,被挂起,则进入信号更新阶段。然而在信号更新阶段,往往存在多个信号需要同时被赋值。如果某些信号赋值之后有事件发生,即有从0到1或者从1到0的变化,则会激活相应的进程,进而转入下一个时刻的进程执行阶段。如此信号更新和进程执行周而复始,完成整个VHDL仿真波形的绘制。而在信号更新阶段,虽然可以理解成是瞬间完成的,各个信号同时被赋值,但是由于EDA工具本质上是在微机系统中完成的,而微机系统核心工作原理就是程序存储执行原理,即冯·诺依曼原理,其程序执行是按照顺序执行的。如果将信号更新的瞬间无限放大,就会发现,各个信号的赋值过程其实是按照逻辑电路的连接顺序先后被赋值的,此时各个信号被赋值的先后顺序会导致电路的各节点的仿真波形不同,违背特定赋值顺序即会导致电路功能错误。假设每个信号赋值需要δ延时完成,于是信号更新的瞬间就可以被细化成若干个δ延时,而每个δ延时均为无穷小的量,若干个(只要不是无限多)信号赋值时长在数学上也仍然是无穷小的量。 因此,δ延时设置仅为了仿真(计算)正确,由于数字电路中各个节点传送信号是并行的,为了实现并行信号赋值的特点,同时又保证能得到逻辑功能正确的仿真波形。 3.5.3进程 进程(PROCESS)是描述系统行为的最重要并行语句之一,进程本身是并行语句,多个进程语句是并行执行的,但它内部却是顺序语句组成的。它可以实现组合电路的行为描述,也可以实现时序逻辑电路的行为描述。 进程的敏感信号列表至少需要一个敏感信号。敏感信号的变化决定着进程是否执行,如果进程敏感信号列表没有信号,那进程永远挂起而不执行; 但是,如果进程敏感信号列表没有信号,则可以使用WAIT语句来代替敏感信号列表的功能。 关于在敏感信号列表中需要列出哪些信号,经验规则是: 如果进程是实现组合逻辑功能,则赋值操作符右边的信号就应该都列入敏感信号列表(除非有特殊考虑); 如果进程是实现时序逻辑功能且没有一个异步信号,则只需要把时钟信号列入敏感信号列表就可以了; 如果进程实现时序逻辑功能但有异步信号,则需要把时钟信号与异步信号列入敏感信号列表。 进程的声明语句可以为进程定义一些变量供进程内部使用,举例如下: PROCESS( clk) VARIABLE temp: INTEGER RANGE 0 TO 127:=0;--进程内部声明变量的位置 BEGIN IF clk'EVENT AND clk= '1' THEN temp: =temp+1; dout<= temp; END IF; END PROCESS; 代码311介绍了各种进程的使用方法。 【代码311】进程应用的程序。 LIBRARYIEEE; --库的引用 USE IEEE. STD_LOGIC_1164. ALL; USE IEEE. STD_LOGIC_UNSIGNED. ALL; ENTITY useprocess IS --实体声明 PORT(clk: IN STD_LOGIC; da: IN STD_LOGIC_VECTOR(7 DOWNTO 0); db: IN STD_LOGIC_VECTOR(7 DOWNTO 0); rst: IN STD_LOGIC; dand: OUT STD_LOGIC_VECTOR(7 DOWNTO 0); isequal: OUT STD_LOGIC; isless: OUT STD_LOGIC; cntout: OUT STD_LOGIC_VECTOR(7 DOWNTO 0)); END useprocess; ARCHITECTURE behave OF useprocess IS BEGIN --纯组合逻辑功能:敏感信号列表必须列出全部的右操作信号 andout: PROCESS(da, db) BEGIN dand<= da AND db; END PROCESS andout; --纯同步的时序逻辑功能:敏感信号列表只需要列出时钟信号即可 PROCESS(clk) BEGIN IF clk'EVENT AND clk= '1' THEN IF da= db THEN isequal<= '1'; ELSE isequal<= '0'; END IF; END IF; END PROCESS; --没有敏感信号列表:需要使用WAIT语句来控制进程的启动 PROCESS BEGIN IF da) OF fourval; FUNCTION resolve( s: fourvalvector) RETURN fourval;--决断函数声明 SUBTYPE resfour IS resolve fourval; --决断类型 END fourpack; PACKAGE BODY fourpack IS FUNCTION resolve( s: fourvalvector) RETURN fourval IS--决断函数体定义 VARIABLE result : fourval := Z; BEGIN FOR i IN s’RANGE LOOP CASE result IS WHEN Z => CASE s(i) IS WHEN H => result := H; WHEN L => result := L; WHEN X => result := X; WHEN OTHERS => NULL; END CASE; WHEN L => CASE s(i) IS WHEN H => result := X; WHEN X => result := X; WHEN OTHERS => NULL; END CASE; WHEN H => CASE s(i) IS WHEN L => result := X; WHEN X => result := X; WHEN OTHERS => NULL; END CASE; WHEN X => result := X; END CASE; END LOOP; RETURN result; END resolve; END fourpack; USE WORK.fourpack.ALL; ENTITY mux2 IS PORT( i1, i2, a : IN fourval; q : OUT fourval); END mux2; ARCHITECTURE different OF mux2 IS COMPONENT and2 PORT( a, b : IN fourval; c : OUT fourval); END COMPONENT; COMPONENT inv PORT( a : IN fourval; b : OUT fourval); END COMPONENT; SIGNAL nota : fourval; -- resolved signal SIGNAL intq : resolve fourval := X;--决断信号 BEGIN U1: inv PORT MAP(a, nota); U2: and2 PORT MAP(i1, a, intq); U3: and2 PORT MAP(i2, nota, intq); q <= intq; END different; 需要说明的是,在代码312的包声明中的四句声明顺序不能颠倒,如果没有前面的声明,则后面的声明不成立。信号intq是一个决断信号,因为它同时是两个器件U2和U3的输出,即有多个源同时驱动信号intq,因此需要用决断函数resolve来进行决断。而信号nota则不需要采用决断信号,因为它只是U1器件的输出。 方法②: 定义个决断子类型,然后声明一个信号使用该子类型。 代码312中,可以将信号声明部分做如下改变,效果相同。 SIGNAL intq : resolve fourval := X; 可以替换成 SIGNAL intq : resfour := X; VHDL中广泛使用的程序包IEEE STD_LOGIC_1164中的数据类型STD_LOGIC,其实就是一个多值决断子类型。该决断子类型对应的基本类型为STD_ULOGIC,其定义为: TYPE STD_ULOGIC is ('U', 'X', '0', '1', 'Z', 'W', 'L', 'H', '-'); 所对应的向量类型STD_ULOGIC_VECTOR定义如下: TYPE STD_ULOGIC_VECTOR IS ARRAY ( NATURAL RANGE <> ) OF STD_ULOGIC; 这两个类型是定义决断子类型和决断函数的基本类型。在此基础上,决断函数和决断子类型的声明如下: FUNCTION resolved ( s : STD_ULOGIC_VECTOR ) RETURN STD_ULOGIC; SUBTYPE STD_LOGIC IS resolved STD_ULOGIC; SUBTYPE STD_LOGIC_VECTOR (resolved) STD_ULOGIC_VECTOR; 在程序包声明中声明了决断函数,同时程序包包体中还定义了决断函数的具体实现: 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' )-- '-' ); 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 FUNCTION resolved; 在决断子类型、决断函数均已声明和定义的基础上,可以使用决断子类型来定义信号,如其他部分程序中所使用的STD_LOGIC和STD_LOGIC_VECTOR。 3.6子程序 VHDL中子程序(Subprogram)包括过程(PROCEDURE)和函数(FUNCTION)。过程是一系列语句的集合,过程调用相当于一条独立的语句; 而函数则是计算某个特定值,相当于定义了一种新的运算操作,其调用通常是放在赋值或者表达式中使用。 VHDL子程序与其他软件语言程序中的子程序的应用目的是相似的,即能更有效地完成重复性的工作。子程序的使用方式只能通过子程序调用及与子程序的界面端口进行通信。子程序可以在VHDL程序的三个不同位置进行定义,即在程序包、结构体和进程中定义。由于只有在程序包中定义的子程序才可被其他不同的设计所调用,所以一般应该将子程序放在程序包中。VHDL子程序具有可重载的特点,即允许有许多重名的子程序,但这些子程序的参数类型及返回值数据类型是不同的。子程序的调用分为顺序调用和并行调用,即在进程中调用和在结构体中调用。 3.6.1过程 过程由过程首和过程体构成。过程首也不是必需的,过程体可以独立存在和使用。过程体如果在进程或结构体中定义,则不必定义过程首,而在程序包中必须定义过程首。过程名后面的参数表可以包括常数、变量和信号三类数据对象,并用关键词IN、OUT和INOUT定义这些参数的工作模式,即信息的流向。如果没有指定模式,则默认为IN。对于IN参数,默认是常数; 对于OUT参数,则默认是变量。过程调用可以在进程中,也可以在结构体中。调用方法为: 直接写出过程名以及参数传递关系,将其看成是一条独立的语句去执行。 过程PROCEDURE的格式: PROCEDURE 过程名(参数表)--过程首 PROCEDURE 过程名(参数表) IS--过程体 [说明部分] BEGIN 顺序语句 END PROCEDURE 过程名 下面是在进程中定义过程的示例,该过程没有形参列表,过程的调用发生在进程中。 ARCHITECTURE rtl OF control_processor IS TYPE func_code IS (add, subtract); SIGNAL op1, op2, dest : INTEGER; SIGNAL Z_flag : BOOLEAN; SIGNAL func : func_code; ... BEGIN alu : PROCESS IS PROCEDURE do_arith_op IS VARIABLE result : INTEGER; BEGIN CASE func IS WHEN add => result := op1 + op2; WHEN subtract => result := op1 - op2; END CASE; dest <= result AFTER Tpd; Z_flag <= result = 0 AFTER Tpd; END PROCEDURE do_arith_op; BEGIN ... do_arith_op; ... END PROCESS alu; ... END ARCHITECTURE rtl; 多数过程均带有形参列表,假设在结构体的声明部分定义了一个过程p(s1,s2,val1),其中,s1,s2为输入信号,val1为输入常数,那么, 并行调用的过程调用发生在结构体中,如下所示: call_proc : p ( s1, s2, val1 ); 等效成一个进程,这意味着当信号s1,s1值发生变化,即有事件时,过程会被再次执行。需要强调的是,只有与IN,INOUT模式关联的信号参数才会被放在进程的敏感信号表中。 call_proc : PROCESS IS BEGIN p ( s1, s2, val1 ); WAIT ON s1, s2; END PROCESS call_proc 3.6.2函数 —般地,函数定义应由两部分组成,即函数首和函数体。 函数首是由函数名、参数表和返回值的数据类型三部分组成的,如果将所定义的函数组织成程序包入库的话,定义函数首是必需的。函数的参数表是用来定义输入值的,因此不必以显式表示参数的方向,函数参量可以是信号或常数,不能是变量。参数名需放在关键词 CONSTANT或SIGNAL之后。如果没有特别说明,则参数被默认为常数。函数体的输出是用RETURN来指定的。如果要将一个已编制好的函数并入程序包,函数首必须放在程序包的说明部分,而函数体需放在程序包的包体内。函数体也可以在结构体或者进程的声明部分进行定义,则此时不需要定义函数首。函数调用可以在进程中,也可以在结构体中。调用方法为: 直接写出函数名以及参数传递关系,将其看成是表达式的一部分去执行。 函数定义的一般格式: FUNCTION函数名(参数表)RETURN数据类型 --函数首 FUNCTION函数名(参数表)RETURN数据类型IS --函数体 [说明部分] BEGIN 顺序语句; END FUNCTION 函数名 函数体定义举例: FUNCTION limit ( value, min, max : INTEGER ) RETURN INTEGER IS BEGIN IF value > max THEN RETURN max; ELSIF value < min THEN RETURN min; ELSE RETURN value; END IF; END FUNCTION limit; 函数调用举例: new_temperature := limit ( current_temperature+ increment, 10, 100 ); new_motor_speed := old_motor_speed + scale_factor * limit ( error, -10, +10 ); 在结构体中调用函数举例: FUNCTION bv_add ( bv1, bv2 : IN BIT_VECTOR ) RETURN BIT_VECTOR IS BEGIN ... END FUNCTION bv_add; 在结构体中假设定义了两个信号: SIGNAL source1, source2, sum : BIT_VECTOR(0 TO 31); 结构体完成并行调用函数的语句如下: adder : sum <= bv_add(source1, source2) AFTER T_delay_adder; 3.6.3函数/过程重载 实际应用时,函数/过程名通常是标识函数/过程所完成的操作,而其参数列表则是操作的对象。VHDL允许针对不同数据类型的相同操作以相同的名字命名函数/过程。即同名函数/过程可以用不同的数据类型为其参数定义多次,以此定义的函数/过程称为重载函数/过程。这样可以确保在调用时,不会造成混淆,究竟调用哪个函数,由函数/过程的参数所对应的数据类型决定。如果函数是以“+”“-”“<”等运算符命名的,则称为运算符重载。 下面是一个完整的重载函数max的定义和调用的示例。 【代码313】 LIBRARY IEEE USE IEEE.STD_LOGIC_1164.ALL; PACKAGE packexp IS FUNCTION max(a,b: IN STD_LOGIC_VECTOR) RETURN STD_LOGIC_VECTOR ; FUNCTION max(a,b : IN BIT_VECTOR) --定义函数首 RETURN BIT_VECTOR ; FUNCTION max( a,b : N INTEGER ) --定义函数首 RETURN INTEGER ; END; PACKAGE BODY packexpIS FUNCTION max( a,b : IN STD_LOGIC_VECTOR)--定义函数体 RETURN STD_LOGIC_VECTOR IS BEGIN IF a > b THEN RETURN a; ELSE RETURN b; END IF; END FUNCTION max; --结束FUNCTION语句 FUNCTION max ( a,b : IN INTEGER) --定义函数体 RETURN INTEGER IS BEGIN IF a > b THEN RETURN a; ELSE RETURN b; END IF; END FUNCTION max; --结束FUNCTION语句 FUNCTION max ( a,b : IN BIT_VECTOR) RETURN BIT_VECTOR IS BEGIN IF a > b THEN RETURN a; ELSE RETURN b; END IF; END FUNCTION max; END; --结束PACKAGE BODY语句 LIBRARY IEEE ; --以下是调用重载函数max的程序; USE IEEE.STD_LOGIC_1164.ALL ; USE WORK.packexp.ALL ENTITY axamp IS PORT(al,bl : IN STD_LOGIC_VECTOR(3 DOWNTO 0); a2,b2 : IN BIT_VECT0R(4 DOWNTO 0); a3,b3 : IN INTEGER RANGE 0 TO 15; cl : OUT STD_LOGIC_VECTOR(3 DOWNTO 0); c2 : OUT BIT_VECTOR(4 DOWNTO 0); c3 : OUT INTEGER RANGE 0 TO 15); END; ARCHITECTURE bhv OF axamp IS BEGIN cl <= max (al,bl); --对函数max (a,b : IN STD_LOG1C_VECTOR)的调用 c2 <= max (a2,b2); --对函数max (a,b : IN B1T_VECTOR)的调用 c3 <= max (a3,b3);--对函数max (a,b : IN INTEGER)的调用 END; 3.7设计库和标准程序包 库一般是一些常用VHDL代码的集合,包括: 数据类型的定义、函数的定义、子程序的定义、元件引用声明、常量的定义等一些可复用或共享的VHDL代码。程序引用了库就可以使用该库中的VHDL代码。 库的声明格式: LIBRARY库名; USE库名.库中程序包.程序包中的项; 例如: LIBRARY IEEE; USE IEEE. STD_LOGIC_1164. ALL; 其中,IEEE是库名,是VHDL设计中使用频率最高的库之一,包括一些常用数据类型的定义及相关的操作。IEEE库有以下几个常用的程序包: (1) STD_LOGIC_1164库定义了STD_LOGIC和STD_ULOGIC的数据类型。 (2) STD_LOGIC_SIGNED库定义了与SIGNED数据类型相关的函数。 (3) STD_LOGIC_UNSIGNED库定义了与UNSIGNED数据类型相关的函数。 (4) STD_LOGIC_ARITH库定义了一些不同类型数据之间相互转换的函数。 此外,IEEE还包括MATH_REAL、NUMERIC_BIT、NUMERIC_STD等程序包,只用于算术运算,读者可根据实际需要进行引用。除了IEEE库外,比较常用的库还有标准库STD库、工作库WORK库。WORK库是设计者自己定义的。而在标准库STD中的STANDARD程序包中规定了关于BIT、BOOLEAN、CHARACTER、INTEGER等最基础的数据类型,因此几乎每个VHDL设计程序都要使用。VHDL规定标准库STD和工作库WORK是默认打开的。STD库和WORK库隐含在每个VHDL程序中,也就是说不需要显示引用时就可以使用STD库和WORK库的VHDL代码。相当于所有的VHDL设计程序前面都默认添加下面两句。VHDL将当前工程文件夹所在的位置默认为工作库WORK。 LIBRARY STD, WORK; USE STD.STANDARD.ALL; 除了设计库中的程序包之外,用户可以自行设计程序包。程序包PACKAGE包括包声明和包主体两部分。 包声明中的声明区可以声明函数、过程、数据类型、子类型、常量、信号。包声明相当于定义了包的接口,帮助设计者了解包中都包含哪些内容。其中这里声明的信号是全局信号。包声明中声明项对于使用该包的实体而言,都是可见的。对于信号和类型及子类型而言,设计者需要完整的定义。而对于函数和过程,则设计者只需要知道函数首和过程首,以便调用,而不关心其具体实现过程。因此,只需要在此声明函数首和过程首; 对于某些常量,如果并不需要将常量的具体值呈现给包的使用者,则可以在这里只声明常量,而将对其的赋值操作延时到包主体中完成。包声明格式如下: PACKAGE 标识 IS {包声明项 } END [ PACKAGE ] [标识 ] ; 包主体是包声明的具体实现。包主体中的声明项对于使用该包的实体而言,是不可见的。所有在包声明中声明的函数和过程以及被延时赋值的常量,都需要在包主体中给出具体实现,并且要求其具体实现要包含与包声明中完全一致的函数首和过程首的声明,以便逐项确认。而如果在包声明中不包含函数和过程以及被延时赋值的常量,则可以省略包主体。除此之外,包主体中还可以声明新的类型、子类型、常量和函数与过程,但这些声明项对外不可见,通常它们是实现包声明中所声明的函数和过程所必需的项目。需要强调,在包主体中,是不能声明信号的。包主体的格式如下: PACKAGE BODY 标识IS { 包主体声明项 } END [ PACKAGE BODY ] [ 标识] ; 下面是包的一个完整定义示例,这是一个BIT_VECTOR类型的有符号算数运算程序包,其中定义了关于BIT_VECTOR类型的有符号算数运算的一系列函数。 【代码314】 PACKAGE bit_vector_signed_arithmetic IS --包声明 FUNCTION "+" ( bv1, bv2 : BIT_VECTOR ) RETURN BIT_VECTOR; FUNCTION "-" ( bv : BIT_VECTOR ) RETURN BIT_VECTOR;--包内的声明区 FUNCTION "*" ( bv1, bv2 : BIT_VECTOR ) RETURN BIT_VECTOR; ... END PACKAGE bit_vector_signed_arithmetic; PACKAGE BODY bit_vector_signed_arithmetic IS --包体 FUNCTION "+" ( bv1, bv2 : BIT_VECTOR ) RETURN BIT_VECTOR IS ... FUNCTION "-" ( bv : BIT_VECTOR ) RETURN BIT_VECTOR IS ... FUNCTION mult_unsigned ( bv1, bv2 : BIT_VECTOR ) RETURN BIT_VECTOR IS ... BEGIN ... END FUNCTION mult_unsigned; FUNCTION "*" ( bv1, bv2 : bit_vector ) RETURN BIT_VECTOR IS BEGIN IF NOT bv1(bv1'LEFT) AND NOT bv2(bv2'LEFT) THEN RETURN mult_unsigned(bv1, bv2); ELSIF NOT bv1(bv1'LEFT) AND bv2(bv2'LEFT) THEN RETURN -mult_unsigned(bv1, -bv2); ELSIF bv1(bv1'LEFT) AND NOT bv2(bv2'LEFT) THEN RETURN -mult_unsigned(-bv1, bv2); ELSE RETURN mult_unsigned(-bv1, -bv2); END IF; END FUNCTION "*"; ... END PACKAGE BODY bit_vector_signed_arithmetic; 代码315和代码316则是两个自定义包的典型应用实例。请读者自行阅读并理解。 【代码315】 PACKAGE my_pack IS --包声明 TYPE nineval IS (Z0, Z1, ZX, R0, R1, RX, F0, F1, FX); TYPE nvector2 IS ARRAY(0 TO 1) OF nineval; TYPE fourstate IS (X, L, H, Z); FUNCTION convert4state(a : fourstate) RETURN nineval; FUNCTION convert9val(a : nineval) RETURN fourstate; END my_pack; PACKAGE body my_pack IS--包体 FUNCTION convert4state(a : fourstate) RETURN nineval IS BEGIN CASE a IS WHEN X => RETURN FX; WHEN L => RETURN F0; WHEN H => RETURN F1; WHEN Z => RETURN ZX; END CASE; END convert4state; FUNCTION convert9val(a : nineval) RETURN fourstate IS BEGIN CASE a IS WHEN Z0 => RETURN Z; WHEN Z1 => RETURN Z; WHEN ZX => RETURN Z; WHEN R0 => RETURN L; WHEN R1 => RETURN H; WHEN RX => RETURN X; WHEN F0 => RETURN L; WHEN F1 => RETURN H; WHEN FX => RETURN X; END CASE; END convert9val; END my_pack; USE WORK.my_pack.ALL; ENTITY trans2 IS PORT( a, b : INOUT nvector2; enable : IN nineval); END trans2; ARCHITECTURE struct OF trans2 IS COMPONENT trans PORT( x1, x2 : INOUT fourstate; en : IN fourstate); END COMPONENT; BEGIN U1 : trans PORT MAP( convert4state(x1) => convert9val(a(0)), convert4state(x2) => convert9val(b(0)), en => convert9val(enable) ); U2 : trans PORT MAP( convert4state(x1) => convert9val(a(1)), convert4state(x2) => convert9val(b(1)), en => convert9val(enable) ); END struct; 【代码316】 PACKAGE my_std IS TYPE fourval IS (X, L, H, Z); TYPE fourvalue IS ('X', '0', '1', 'Z'); TYPE fvector4 IS ARRAY(0 TO 3) OF fourval; END my_std; USE WORK.my_std.ALL; ENTITY reg IS PORT(a : IN fvector4; clr : IN fourval; clk : IN fourval; q : OUT fvector4); FUNCTION convert4val(S : fourval) RETURN fourvalue IS BEGIN CASE S IS WHEN X => RETURN 'X'; WHEN L => RETURN '0'; WHEN H => RETURN '1'; WHEN Z => RETURN 'Z'; END CASE; END convert4val; FUNCTION convert4value(S : fourvalue) RETURN fourval IS BEGIN CASE S IS WHEN 'X' => RETURN X; WHEN '0' => RETURN L; WHEN '1' => RETURN H; WHEN 'Z' => RETURN Z; END CASE; END convert4value; END reg; ARCHITECTURE structure OF reg IS COMPONENT dff PORT(d, clk, clr : IN fourvalue; q : OUT fourvalue); END COMPONENT; BEGIN U1 : dff PORT MAP(convert4val(a(0)), convert4val(clk), convert4val(clr), convert4value(q) => q(0)); U2 : dff PORT MAP(convert4val(a(1)), convert4val(clk), convert4val(clr), convert4value(q) => q(1)); U3 : dff PORT MAP(convert4val(a(2)), convert4val(clk), convert4val(clr), convert4value(q) => q(2)); U4 : dff PORT MAP(convert4val(a(3)), convert4val(clk), convert4val(clr), convert4value(q) => q(3)); END structure;