1、数据类型
-
空白符
- 空格符(\b)、制表符(\t)、换行符和换页符
- 在编译和综合时,空白符被忽略
-
注释符
- 单行注释:"//"
- 多行注释:"/**/"
-
标识符
- 用来命名信号名、模块名、参数名等
- 可以是任意一组字母、数字、$符号和 _(下划线)符号的组合
- 字母区分大小写,且第一个字符必须是字母或者下划线
-
转义标识符
- 采用转义标识符可以在一条表示标识符包含任何可打印字符
- 以"\"(反斜线)符号开头,以空白结尾(空白可以是一个空格、一个制表符或换行符)
\a+b=c\outgate==outgate
-
关键字
- 关键字或保留字,仅供内部使用
- 所有关键字都是小写的
-
数值
-
Verilog HDL 中有四种基本的逻辑数值状态
状态 含义 0 低电平、逻辑0或“假” 1 高电平、逻辑1或“真” x或X 不确定或未知的逻辑状态 z或Z 高阻态 -
整数及表示
+/-
’<base_format> 8'b10001101:位宽为8位的二进制数10001101数制 基数符号 合法表示符 二进制 b或B 0、1、x、X、z、Z、?、_ 八进制 o或O 0~7、x、X、z、Z、?、_ 十进制 d或D 0~9、_ 十六进制 h或H 0~9、a~f、A~F、x、X、z、Z、?、_ -
实数及其表示
- 十进制表示法:采用十进制格式,小数点两边必须都有数字
- 科学计数法:e不分大小写
-
-
数据类型
物理数据类型:连线型、寄存器型和存储器型数据类型
信号强度表示数字电路中不同强度的驱动源,用来解决不同驱动强度存在下的赋值冲突
(以电流大小作比较)
标记符 名称 类型 强弱程度 supply 电源级驱动 驱动 最强 strong 强驱动 驱动 pull 上拉级驱动 驱动 large 大容性 存储 weak 弱驱动 驱动 medium 中性驱动 存储 small 小容性 存储 highz 高容性 高阻 最弱 -
连线型
连线型数据类型 功能说明 wire,tri 标准连线(缺省为该类型) wor,trior 多重驱动时,具有线或特性的连线型 wand,trand 多重驱动时,具有线与特性的连线型 trireg 具有电荷保持特性的连线型数据(特例) tri1 上拉电阻 tri0 下拉电阻 supply1 电源线,用于对电源建模,为高电平1 supply0 电源线,用于对“地”建模,为低电平0 -
wire和tri
wire/tri 0 1 x z 0 0 x x 0 1 x 1 x 1 x x x x x z 0 1 x z -
wor和trior
wor/trior 0 1 x z 0 0 1 x 0 1 1 1 1 1 x x 1 x x z 0 1 x z
-
-
寄存器型
- reg型时数据储存单元的抽象类型,其对应的硬件电路元件具有状态保持作用,能够存储数据,如触发器、锁存器等
- reg型变量常用于行为级描述,由过程赋值语句对其进行赋值
reg a;、reg[3:0]b、reg[8:1]c,d,e;- reg型变量一般为无符号数,若将一个负数赋给reg型变量,则自动转换为其二进制补码形式
-
存储器型
- 存储器型变量可以描述RAM型、ROM型存储器以及reg文件
-
连线型数据类型的声明
<net_declaration><drive_strength>
[list_of_variables] - net_declaration:包括wire、tri、tri0、tri1、wand、triand、trior、wor中任意一种
- drive_strength:表示连续变量的驱动强度(supply、strong、pull、large、weak、medium、small、highz)
- range:用来指定数据为标量或矢量。若该项默认,表示数据类型为1位的标量,超过1位则为矢量形式
- delay:指定仿真延迟时间
- list_of_variable:变量名称,一次可定义多个名称,之间用逗号分开
-
寄存器型数据类型的声明(没有驱动强度的概念,驱动强度一定是连线型变量)
reg
<list_of_register_variables> - range:可选项,指定了reg型变量的位宽,缺省时为1位
- list_of_register_variables:为变量名称列表,一次可定义多个名称,之间用逗号分开
-
存储器变量声明
reg
<name_of_register> - range1和range2都是可选项,缺省都为1
- range1:表示存储器中寄存器的位宽,格式为[msb:lsb]
- range2:表示寄存器的个数,格式为[msb:lsb],即有msb-lsb+1个
- name_of_register:表示变量名称列表,一次可定义多个名称,之间用逗号分开
-
-
抽象数据类型
-
主要包括整型(integer)、时间型(time)、实型(real)及参数型(parameter)
-
整型:integer<list_of_register_varables>
-
2、运算符和表达式
| Verilog运算符 | 功能 | 运算符的优先级别 |
|---|---|---|
| !、~ | 反逻辑、位反相 | 最高 |
| *、/、% | 乘、除、取模 | |
| +、- | 加、减 | |
| «、» | 左移、右移 | |
| <、<=、>、>= | 小于、小于等于、大于、大于等于 | |
| ==、!=、===、!== | 等、不等、全等、非全等 | |
| & | 按位与 | |
| ^、^~ | 按位逻辑异或和同或 | |
| | | 按位或 | |
| && | 逻辑与 | |
| || | 逻辑或 | |
| ?: | 条件运算符,唯一的三目运算符,等同于if else | 最低 |
-
算数操作符
加法(+);减法(-);乘法(*);除法(/);取模(%)
- 算术操作结果的位宽
- 算术表达式结果的长度由最长的操作数决定
- 在赋值语句下,算术操作结果的长度由操作左端目标长度决定
- 有符号数和无符号数的使用
- 算术操作结果的位宽
-
关系操作符
大于(>);小于(<);大于等于(>=);小于等于(<=)
- 结果有三种:真(1)、假(0)、不定值(x)
- 任何的不定状态的数和已知状态数的比较结果都为未知状态(x)
-
相等关系操作符
等于(==);不等于(!=);全等(===);非全等(!==)
- 结果有三种:真(1)、假(0)、不定值(x)
-
逻辑运算符
逻辑与运算符(&&);逻辑或运算符(||);逻辑非运算符(!)
- 操作数中存在不定态x,则逻辑运算结果也是不定态
-
按位运算符
按位取反(~);按位与(&);按位或(|);按位异或(^);按位同或(^~)
-
归约操作符(缩位运算符)
与(&);或(|);异或(^);与非(~&);或非(~|);异或非(
^ / ^)- 电路内部进行信号处理
-
移位操作符
左移位运算符(«);右移运算符(»)
- 所移动的位数由右边的操作数来决定,然后用0来填补移出的空位
-
条件运算符
<条件表达式>?<表达式1>:<表达式2>
- 结果有三种:真(1)、假(0)、未知态(x)
-
连接和复制运算符
连接运算符({ });复制运算符({{}})
- 连接运算符:信号1的某几位,信号2的某几位,……,信号n的某几位
- 复制运算符:将一个表达式放入双重花括号中,复制因子放在第一层括号中
-
模块的基本概念
-
模块(module)是Verilog HDL语言的基本单元,它代表一个基本的功能块,用于描述某个设计的功能或结构以及与其它模块通信的外部接口
1 2 3 4 5 6 7 8 9// 模块结构组成 module name(port_list); // 模块定义行 端口定义 ... 数据类型说明 ... 逻辑功能描述 ... endmodule // 模块结束行 -
模块主要包括:模块的开始与结束、模块端口定义、模块数据类型说明、模块逻辑功能描述
- 模块的开始与结束:以关键字module开始,以关键字endmodule结束,开始语句必须以分号结束
- 模块端口定义:用来定义端口列表里的变量哪些是输入(input)、输出(output)和双向端口(inout)以及位宽的说明
- 模块数据类型说明:数据类型在语言上包括wire、reg、memory、parameter等类型,用来说明模块中所用到的内部信号、调用模块等的声明语句和功能定义语句
- 模块逻辑功能描述:用来产生各种逻辑(组合逻辑和时序逻辑),主要包括:initial语句、always语句、其它子模块实例化语句、门实例化语句、用户自定义原语(UDP)实例化语句、连续赋值语句(assign)、函数(function)、任务(task)
-
-
端口
-
端口的定义
- 模块的端口可以是输入端口(input)、输出端口(output)或双向端口(inout)
-
模块引用时端口的对应方式
-
在引用时,严格按照模块定义的端口顺序来连接,不用标明源模块定义时规定的端口名
格式:模块名(连接端口1信号名,连接端口2信号名……);
-
在引用时,用", “标明源模块定义时规定的端口名
格式:模块名(端口1名(连接信号1名),端口2名(连接信号2名)……);
好处:可以用端口名和引用模块的端口对应,不必严格按端口顺序对应,提高程序的可读性和可移植性
-
-
3、数据流建模
-
连续赋值语句
-
连续赋值的目标类型主要是标量线网和向量线网两种
- 标量线网,如:wire a,b;
- 向量线网,如:wire [3:0] a,b;
-
显式连续赋值语句
1 2<net_declaration><range><name>; assign #<delay><name>=Assignment expression; -
隐式连续赋值语句
1<net_declaration><drive_strength><range>#<delay><name>=Assignment expression;- net_declaration:连线型变量类型
- range:变量位宽,指明了变量数据类型的宽度,格式为[msb:lab],缺省为1位
- drive_strength:赋值驱动强度,可选,只能在“隐式连续赋值语句”格式中得到指定,用来对连线型变量受到的驱动强度进行指定,Wire(weak0, strong1) out = in1&in2;
- delay:延时量,可选,#(delay1, delay2, delay3)
-
连续赋值语句注意事项
- 赋值目标只能是线网类型(wire)
- 在连续赋值中,只要赋值语句右边表达式任何一个变量有变化,表达式立即被计算,计算的结果立即赋给左边信号(若没有定义延时量)
- 连续赋值语句不能出现在过程块中
- 多个连续赋值语句之间是并行语句,因此与位置顺序无关
- 连续赋值语句中的延时具有硬件电路中惯性延时的特性,任何小于其延时的信号变化脉冲都将被滤除掉,不会体现在输出端口上
-
4、行为级建模
模块描述定义行
端口类型说明 过程块1 过程语句(initial/always)
数据类型说明 过程块2 过程赋值语句
描述体 连续赋值语句 语句块 高级程序语句
结束行 ……
| 类型 | 语句 | 可综合性 |
|---|---|---|
| 过程语句 | initial | |
| always | √ | |
| 语句块 | 串行语句块begin-end | √ |
| 并行语句块fork-join | ||
| 赋值语句 | 过程连续赋值assign | |
| 过程赋值=、<= | √ | |
| 条件语句 | if-else | √ |
| case,casez,casex | √ | |
| 循环语句 | forever | |
| repeat |
-
过程语句
-
initial过程语句
-
语法格式
1 2 3 4 5 6 7initial begin 语句1; 语句2; ... 语句n; end -
仿真和测试
-
-
always语句块
-
从语法描述角度,相对于initial过程块,always语句块的触发状态时一直存在的,只要满足always后面的敏感事件列表,就执行过程块
-
语法格式
1 2 3 4always@(<敏感事件列表>) 语句块; // 敏感事件列表没有 & 概念 // @(a or b) == @(a, b) -
例子
1 2 3 4 5@(a) // 当信号a的值发生改变时 @(a or b) // 当信号a或b的值发生改变时 @(posedge clock) // 当clock的上升沿到来时 @(negedge clock) // 当clock的下降沿到来时 @(posedge clk or negedge reset) // 当clk的上升沿到来或reset信号的下降沿到来时
-
-
过程语句使用注意问题
- 在信号定义形式方面,无论是对时序逻辑还是组合逻辑描述,Verilog HDL要求在过程语句(initial和always)中,被赋值信号必须定义为"reg"类型
- 在敏感事件表方面,这是Verilog HDL语言中一个关键性设计,如何选取敏感事件作为过程的触发条件
-
采用过程对组合电路进行描述时,作为全部的输入信号需要列入敏感信号列表
-
采用过程对时序电路进行描述时,需要把时间信号和部分输入信号列入敏感信号列表,不同的敏感事件列表会产生不同的电路形式
-
-
-
语句块
语句块 串行语句块(begin-end) 并行语句块(fork-join) 语法格式 begin:块名
块内声明语句;
语句1;
语句2;
…
语句n;
endfork:块名
块内声明语句;
语句1;
语句2;
…
语句n;
join执行顺序 按照语句顺序执行 每条语句在同一时刻执行 语句前面延时的意义 相对于前一条语句执行结束的相对时间 相对于并行语句块启动时的时间 起始时间 首句开始执行的时间 转入并行语句块的时间 结束时间 最后一句语句执行结束时的时间 执行时间最长的那条语句执行结束时的时间 行为描述的意义 电路中数据在时钟及控制信号作用下,沿数据通道的各级寄存器之间的传送过程 电路上的电后,各电路模块同时开始工作的过程 应用场景 用于可综合电路程序和仿真测试程序 只能用于仿真测试程序 -
赋值语句
-
过程赋值语句
- 阻塞性过程赋值语句:变量=表达式
- 在串行语句块中,各条阻塞赋值语句将按照先后排列顺序依次执行;在并行语句块中的各条阻塞赋值语句则同时执行,没有先后顺序
- 执行阻塞赋值语句的顺序是,先计算等号右端表达式的值,然后立刻将计算的值赋给左边的变量,与仿真时间无关
- 非阻塞性过程赋值语句:变量<=表达式
- 在串行语句块中,各条非阻塞赋值语句执行没有先后顺序,排在前面的语句不会影响到后面的语句执行,各条语句并行执行
- 执行非阻塞赋值语句的顺序是,先计算右端表达式的值,然后等到延时时间结束时,将计算的值赋给左边的变量
- 阻塞性过程赋值语句:变量=表达式
-
过程连续赋值语句
-
赋值、重新赋值语句(assign、deassign)
1 2assign <寄存器型变量> = <赋值表达式>; deassign <寄存器型变量>; -
强制、释放语句(force、release)
1 2 3// “force”语句的优先级高于“assign”语句 force <寄存器或连线型变量> = <赋值表达式>; release <寄存器或连线型变量>
-
-
-
条件语句
- if条件语句
- if
- if else
- if else if … else
- case条件分支语句
- case:比较0、1、x、z(全等比较)
- 值1到值n之间必须各不相同,一旦判断与某值相同并执行相应语句块后,case语句的执行结束
- 如果某几个连续排列的值项执行的是一条语句,则这几个值项间可用逗号相隔,而将语句放在这几个值项的最后一个中
- default选项相当于if-else语句中的else部分,可依据需要用或不用,当前面已经列出了敏感表达式的所有可能值,则default可以省略
- case语句的所有表达式的值的位宽必须相等,只有这样控制表达式和分支表达式才能进行对应位的比较
- casez:比较0、1、x,其他为1
- casex:比较0、1,其他为1
- 在使用case语句时,应包含所有状态,如果没包含全,那么缺省项(default)必须写,否则将产生锁存器,这在同步时序电路设计中是不允许的
- case:比较0、1、x、z(全等比较)
- if条件语句
-
循环语句
-
forever循环语句
-
无限循环,不包含任何条件表达式,只执行无限的循环,直到遇到系统任务$finish为止,如需从forever循环中退出,则可以使用disable语句
-
语法格式:
1forever 语句或语句块
-
-
repeat循环语句
-
执行固定次数的循环
-
语法格式:
1 2repeat (循环次数表达式) 语句或语句块(循环体);
-
-
while循环语句
-
条件循环
-
语法格式:
1while(条件表达式) 语句或语句块;
-
-
for循环语句
-
条件循环
-
语法格式:
1for(循环变量赋初值; 循环结束条件; 循环变量增值) 语句块;
-
-
循环语句也可以用于可综合电路的设计,当采用循环语句进行计算和赋值的描述时,可以综合得到逻辑电路
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25// 设计一个8位移位寄存器 // 采用赋值语句实现 module shift_regist(Q,D,rst,clk); output [7:0] Q; input D,rst,clk; reg [7:0] Q; integer i; always @(posedge clk) if(!rst) Q<=8'b000000; else Q<={Q[6:0],D}; // { }连接符,D放在最后一位,组成8位 endmodule // 采用“for”循环语句实现 module shift_regist(Q,D,rst,clk); output [7:0] Q; input D,rst,clk; reg [7:0] Q; always @(posedge clk) if(!rst) Q<=8'b000000; else begin for(int i=7; i>0; i=i-1) Q[i]<=Q[i-1]; Q[0]<=D; end endmodule
-
5、结构化建模
结构描述方式就是将硬件电路描述成一个分级子模块系统,通过逐层调用这些模块构成功能复杂的数字逻辑电路和系统的一种描述方式。
在这种描述方式下,组成硬件电路的各个子模块之间的相互层次关系以及相互连接关系都需要得到说明
根据所调用字模块的不同抽象级别,可以将模块的结构描述方式分成如下三类
- 模块级建模:通过调用由用户设计生成的低级子模块来对硬件电路结构进行说明,这种情况下模块由低级模块的实例组成
- 门级建模:通过调用Verilog HDL内部的基本门级元件来对硬件电路的结构进行说明,这种情况下模块将由基本门级元件的实例组成
- 开关级建模:通过调用Verilog HDL内部的基本开关级元件来对硬件电路的结构进行说明,这种情况下模块将由基本开关级元件的实例组成
-
模块级建模
模块级建模就是通过调用用户自己描述产生的module模块对硬件电路结构进行说明,并设计出电路
模块建模方式可以把一个模块看作是由其它模块像积木一样搭建而成。模块中被调用模块属于低一层次的模块,如果当前模块不再被其它模块所调用,那么这个模块一定是所谓的顶层模块。在对一个硬件系统的描述中,必定有且只有一个顶层模块
-
模块调用方式
-
模块可以被任何其它模块调用,这种调用实际上是将模块所描述的电路复制并连接的
-
语法格式:
1模块名 <参数值列表> 实例名 (端口名列表);模块名:被调用模块
参数值列表:可选项
实例名:生成的模块实例,实例名必须各不相同
端口列表:指明了模块实例与外部信号的连接
-
如果同一个模块在当前模块中被调用几次,则需要不同的实例名加以标识,但可在同一条模块调用语句中定义,只要各自的实例名和端口名列表相互间用逗号隔开即可
1 2 3 4模块名 <参数值列表> 实例名1 (端口名列表1), <参数值列表> 实例名2 (端口名列表2), ... <参数值列表> 实例名n (端口名列表n); -
当需要对同一个模块进行多次调用,还可以采用阵列调用方式对模块进行调用(标量输入输出)
1<被调用模块名><实例阵列名>[阵列左边界, 阵列有边界](<端口连接表>);阵列左边界和阵列有边界是两个常量表达式,用来指定调用后生成的模块实例阵列大小
-
-
模块端口对应方式
-
端口位置对应方式
被调用模块按照一定的顺序出现在端口连接表中的一种模块调用方式
语法格式:
1模块名 <参数值列表> 实例名 (<信号1>, <信号2>, ..., <信号n>); -
端口名称对应方式
语法格式:
1模块名 <参数值列表> 实例名 (.端口名1<信号1>, .端口名2<信号2>, ..., .端口名n<信号n>);优势:
位置不必一一对应
允许信号为空
-
-
不同端口位宽的匹配
在端口和端口表达式之间存在着一种隐含的连续赋值语句,因此当端口和端口表达式的位宽不一致时,会进行端口的匹配,采用的位宽匹配规则与连续赋值时使用的规则相同
匹配规则:右端对齐,不足补零,超出截断
-
模块参数值
-
使用带有参数的模块实例语句修改参数值
模块实例本身就能指定新的参数值
1 2模块名 <参数值列表> 实例名 (端口名列表) // 参数值列表又分为位置对应和名称对应 -
使用定义参数语句(defparam语句)修改参数值
defparam语句:参数重定义语句
1 2 3 4 5defparam 参数名1=参数值1, 参数名2=参数值2, ... 参数名n=参数值n; // 参数名必须采用分级路径的形式,才能锁定需要修改的参数是哪个模块当中的
-
-
-
门级建模
-
基本门级元件类型
26个基本元件:14个门级元件 + 12个开关级元件
类型 具体分类 元件 基本门 多输入门 and, nand, or nor, xor, xnor 多输出门 buf(缓冲器), not(非门) 三态门(包含高阻态) 允许定义驱动强度 buffif0, buffif1, notif0, notif1 无驱动强度 nmos, pmos, cmos, mmos, rpmos, rcmos 双向开关 无驱动强度 tran, tranif0, tranif1 无驱动强度 rtran, rtranif0, rtranif1 上拉、下拉电阻 允许定义驱动强度 pullup, pulldown 三态门调用语法格式:
1元件名 <实例名> (<数据输出端口>, <数据输入端口>, <控制输入端口>); -
门级模块调用
-
多输入门元件调用语法格式
1门类型 <实例名> (<输出端口>, <输入端口1>, <输入端口2>, ... , <输入端口n>); -
多输出门元件调用语法格式
1门类型 <实例名> (<输出端口1>, <输出端口2>, ... , <输出端口n>, <输入端口>);
-
-
-
开关级建模
Verilog HDL语言提供了十几种开关级基元,它们是实际的MOS管的抽象标识。这些开关级基元分为两大类,一类是MOS开关,一类是双向开关。每一个大类又可分为电阻型(前缀用r标识)和非电阻型。
-
MOS开关
MOS开关模拟了实际的MOS器件的功能,包括nmos、pmos、cmos三种
1 2 3 4 5// nmos和pmos的实例化语言格式 nmos或pmos 实例名 (out, data, control); // cmos开关的实例化语言格式 cmos 实例名 (out, data, ncontrol, pcontrol); -
双向开关
-
双向开关的每个脚都被声明为inout类型,都可以作为输入驱动另一脚,也可以作为输出被另一脚驱动
-
双向开关包括无条件双向开关(tran)和有条件双向开关(tranif0、tranif1)
1 2 3 4 5// 无条件双向开关实例化语言格式 tran 实例名(inout1, inout2); // 有条件双向开关实例化语言格式 tranif0或tranif1 实例名(inout1, inout2, control);
-
-
6、数字逻辑电路设计方法
- Verilog HDL语言设计思想和可综合性
7、组合电路设计
-
组合电路的特点:电路中任意时刻的稳态输出仅仅取决于该时刻的输入,而与电路原来的状态无关
-
组合电路的设计需要从以下几个方面考虑:
- 所用的逻辑器件数目最少,器件的种类最少,且器件之间的连线最简单,这样的电路称为“最小化”电路
- 为了满足速度要求,应使级数尽量少,以减少门路的延迟;电路的功耗应尽可能的小,工作时稳定可靠
-
描述组合逻辑电路有四种方式:结构描述、逻辑代数、真值表、抽象描述
- 真值表方式
- 是对电路功能最直接和简单的描述方式
- 根据电路的功能,可以通过真值表直接建立起输出与输入之间的逻辑关系
- 逻辑代数方式
- 主要思想是将真值表用卡诺图表示,然后化简电路,得出逻辑函数表达式
- 通过对卡诺图的化简,可以得到组合电路逻辑输出与输入之间的逻辑函数表达式
- 结构描述方式
- 是对电路最直接的标识,早期的数字电路设计通常采用的原理图设计实际上就是一种结构性描述方式
- 抽象描述方式
- 可以直接从电路功能出发,编写代码
- 真值表方式
-
可综合
-
EDA综合工具可以将Verilog HDL程序综合成物理电路形式,通过电路优化,可以得到符合设计要求的最简化电路
-
采用Synplify软件对上面四种方法设计的Verilog HDL程序进行综合(采用Altera公司Stratixll器件),可以得到相同的最简化电路
-
-
数字加法器
-
全加器:考虑来自低位的进位
-
串行加法器
-
超前进位加法器
-
-
数据比较器
- 数据比较器是用来对两个二进制数的大小进行比较,或检测是否相等的逻辑电路。数据比较器包含两个部分:一是比较两个数的大小;二是检测两个数是否一致
- 多位数值比较器的比较过程是由高位到低位逐位进行比较,只有在高位相等时,才进行低位比较
-
数据选择器
- 又称多路选择器(Multiplexwe,简称MUX),它有n位地址输入,2^n^位数据输入,1位数据输出,每次在输入地址的控制下,从多路输入数据中选择一路输出,其功能类似于一个单刀多掷开关
-
数字编码器
-
用文字、符号或数码表示特定对象的过程称为编码。在数字电路中用二进制代码表示有关的信号称为二进制编码,实现编码操作的电路叫做编码器
-
二进制编码器
用n位二进制代码对N=2^n^个一般信号进行编码的电路
任何时刻只允许输入一个有效信号,不允许同时出现两个或两个以上的有效信号
-
优先编码器
允许多个输入信号同时有效,但它只按其中优先级别最高的有效输入信号编码,对级别低的输入信号不予理睬
-
8421BCD编码器
将十进制数0、1、2、3、4、5、6、7、8、9等是十个信号编成二进制代码的电路叫做二进制转化十进制编码器
它的输入是代表0~9这10个数字的状态信息,有效信号为1(即某信号为1时,则表示要对它进行编码),输出是相应的BCD码,因此也称10线-4线编码器
它和二进制编码器特点一样,任何时刻只允许输入一个有效信号
-
8421BCD十进制余3编码器
和8421BCD编码一样,余3码也是一种BCD编码
余3码作十进制加法运算的时候,若两数之和是10,正好等于 二进制数的16,于是便从高位自动产生进位信号,因此可以使用余3码简化计算
余3码是8421BCD码加3,并将其转换为等价二进制数就得到了该十进制数的余3码
-
-
数字译码器
- 译码是编码的逆过程,它将二进制代码所表示的信息翻译成相应的状态信息,实现译码功能电路成为译码器
- 2线-4线译码器
-
奇偶校验器
- 奇偶校验器的功能是检测数据中包含“1”的个数是奇数还是偶数
- 检查数据传输和数码记录中是否存在错误
- 奇校验保证传输数据和校验位中“1”的总数为奇数,如果数据中包含奇数个“1”,则校验位置“0”;如果数据中包含偶数个“1”,则校验位置“1”
- 偶校验保证传输数据和校验位中“1”的总数为偶数,如果数据中包含奇数个“1”,则校验位置“1”;如果数据中包含偶数个“1”,则校验位置“0”
- 奇偶校验只能检测部分传输错误,它不能确定错误发生在哪位或哪几位,所以不能进行错误校正,当数据发生错误时只能重新发送数据
8、时序电路设计
-
与组合逻辑电路不同,时序逻辑电路的输出不仅与当前时刻输入变量的取值有关,而且与电路的原状态,即与过去的输入情况有关
-
时序逻辑电路有两个特点
- 时序逻辑电路包括组合逻辑电路和存储电路两部分,存储电路具有记忆功能,通常由触发器组成
- 存储电路的状态反馈到组合逻辑电路输入端,与外部输入信号共同决定组合逻辑电路的输出
-
同步时序电路设计流程
- 设计要求
- 原始状态图(状态简化→)
- 最简状态图(状态分配→)
- 二进制状态表(触发器选型→)
- 输出函数 激励函数(自启动检查→)
- 逻辑电路图
-
触发器
- 触发器是时序电路的最基本电路单元
- 主要有D触发器、T触发器、RS触发器等
- 根据功能要求不同,触发器还具有置位、复位、使能、选择等功能
-
计数器
-
2进制的计数器
-
任意模值的计数器
确定计数器所需要触发器个数,N个触发器对应了2^N^个状态,应有2^N^>M
任意模值计数器选取满足条件的最小N,N为计数器中触发器个数
有两种实现方法:反馈清零法和反馈置数法
-
-
移位寄存器
-
移位寄存器可以用来实现数据的串并转换,也可以构成移位行计数器,进行计数、分频,还可以构成序列码发生器、序列码检测器等,是数字系统中应用非常广泛的时序逻辑部件之一
-
环形移位寄存器
N位环形寄存器由N个移位寄存器组成,它可以实现环形移位
-
-
序列信号发生器
-
序列信号是数字电路系统中常用的功能单元,按照序列循环长度M和触发器数目n的关系一般可分为三种:
- 最大循环长度序列码,M=2^n^
- 最长线形序列码(m序列码),M=2^n^-1(少了全0的状态)
- 任意循环长度序列码,M<2^n^
-
序列信号发生器是能够产生一组或多组序列信号的时序电路,它可以由纯时序电路构成,也可以包含由时序和组合逻辑的混合电路构成
-
设计方法
-
由移位寄存器构成
优点:移位寄存器输入和输出信号之间没有组合电路,工作频率高
缺点:移位寄存器长度取决于序列长度,占用电路面积大
-
由移位寄存器和组合逻辑电路构成(10-23min)
优点:仅存储当前状态,节省移位寄存器
缺点:有组合反馈网络,电路速度下降
-
由计数器构成(10-26min)
优点:计数器的状态与输出序列没有直接关系
缺点:
-
-
-
伪随机码发生器
- 随机码是一种变化规律与随机码类似的二进制代码,可以作为数字通信中的一个信号源,通过信道发送到接收机,用于检测数字通信系统错码的概率,即误码率
- 在传统的数字电路设计中,伪随机序列信号发生器是用移位存型计数器来实现的,反馈网络输入信号从移位寄存器的部分输出端(Q
N-1~~Q0~)中取出,它的输出端F反馈到移位寄存器的串行输入端 - 通过不同的反馈网络,可以形成不同的移存型计数器,以m序列码为例,反馈函数如左表所示,表中的N是触发器的级数(寄存器数目),F是反馈函数的列表(产生异或的输出端信号的位置)
- 例如N=4,则反馈函数如下:F = Q
1^Q0 - 下面以N=4为例,在15位最长线性序列移存型计数器中,有一个由“0000”构成的死循环,为了打破死循环,可以修改为:F = Q
1^Q0+ 各项取反相与
9、有限同步状态机
-
有限状态机是时序电路的通用模型,任何时序电路都可以表示为有限状态机
-
有限状态机本质上是由寄存器与组合逻辑构成的时序电路,各个状态之间的转移总是在时钟的触发下进行的,状态信息存储在寄存器中,因为状态的个数是有限的所以称为有限状态机
-
同其它时序电路一样,有有限状态机也是由两部分组成:存储电路和组合逻辑电路
- 存储电路:用来生成状态机的状态
- 组合逻辑电路:用来提供输出以及状态机跳转的条件
-
根据输出信号的产生方式,有限状态机可以分为米利型(Mealy)和摩尔型(Moore)两类
-
Mealy型状态机的输出与当前状态和输入有关系
-
Moore型状态机的输出仅依赖当前状态而与输入无关

-
-
状态编码方式
-
二进制编码
- 状态寄存器是由触发器组成的,N个触发器可以构成2^n^个状态
- 优点是使用的触发器个数少,节省资源
- 缺点是状态跳转时可能有多个bit位同时变化,引起毛刺,造成逻辑错误
-
格雷编码
- 格雷编码和二进制编码类似
- 优点是相邻状态跳转时只有一个bit位发生变化,减少了产生毛刺和一些暂态的可能
- 缺点是距离较远的状态跳转十分不便
-
One hot(一位独热)编码
-
是对于n个状态采用n个bit位来编码,每个状态编码中只有一个bit位为1,如:001、010、100
-
优点是One hot编码增加了使用触发器的个数,但是这种编码方便译码,可以有效节省和化简组合电路
-
缺点是任何状态之间的跳转只有两个bit发生变化,状态有多少位宽就多大,占用资源特别多
-
-
-
有限状态机的写法(11-26min)
状态转移方程、输出方程、激励方程
- 两段式(状态转移方程、输出方程和激励方程)
- 三段式(状态转移方程、输出方程、激励方程)
-
示例
可以用多种方法

主要是画图(状态表示)

10、电路仿真和验证概述
- 仿真,也叫模拟,是通过使用EDA仿真工具,通过输入测试信号,比对输出信号(波形、文本或者VCD文件)和期望值,来确认是否得到与期望所一致的正确的设计结果,验证设计的正确性
- 验证是一个证明设计思路如何实现,保证设计在功能上正确的一个过程
- 验证Verilog HDL设计的整个流程中分为4个阶段
- 阶段1:功能验证
- 阶段2:综合后验证
- 阶段3:时序验证
- 阶段4:板级验证
11、测试程序设计基础
-
Testbench及其结构
- 在仿真的时候Testbench用来产生测试激励给待验证设计(Design Under Verification,DUV)或者称为待测设计(Design Under Test,DUT)
-
测试程序的一般结构
-
由于Testbench是一个测试平台,信号集成在模块内部,没有输入输出
-
在Testbench模块内,例化待测设计的顶层模块,并把测试行为的代码封装在内,直接对待测系统提供测试激励
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18module 仿真模块名; // 无端口列表 各种输入、输出变量定义 数据类型说明 // 其中激励信号定义为reg型 // 显示信号定义为wire型 integer parameter 待测试模块调用 激励向量定义 always、initial过程块; function、tast结构等 if-else、for、case、while、repeat、disable等控制语句 显示格式定义 $monitor、$time、$display等 endmodule
-
-
Testbench的主要功能
-
为DUT提供激励信号
-
正确实例化DUT
-
将仿真数据显示在终端或者存为文件,也可以显示在波形窗口中以供分析检查
-
复杂设计可以使用EDA工具,或者通过用户接口自动比较仿真结果与理想值,实现结果的自动检查

-
-
在编写Testbench时需要注意的问题
- Testbench代码不需要可综合,Testbench代码只是硬件行为描述不是硬件设计
- 行为级描述效率高,Verilog HDL语言具备5个描述层次,分别为开关级、门级、RTL级、算法级、系统级
- 掌握结构化、程序化的描述方式,结构化的描述有利于设计维护,可通过initial、always以及assign语句将不同的测试激励划分开来,一般不要将所有的测试都放在一个语句块中
-
测试平台举例
测试平台需要产生时钟信号、复位信号和一系列的仿真向量,观察DUT的响应,确认仿真结果

-
组合逻辑电路仿真环境的搭建
-
时序逻辑电路仿真环境的搭建
在于时序逻辑电路仿真环境中,需要考虑时序、定时信息和全局复位、置位等信号要求,并定义这些信号
-
-
仿真结果确认
-
直接观察波形(电路规模中等)
通过直接观察各信号波形的输出,比较测试值和期望值的大小,来确定仿真结果的正确性
-
打印文本输出法(电路规模小)
系统任务打印
- $display,直接输出到标准输出设备
- $monitor,监控参数的变化
- $fdisplay,输出到文件
-
自动检查仿真结果
自动检查仿真结果是通过在设计代码中的关键节点添加断言监控器,形成对电路逻辑综合的注释或是对设计特点的说明,以提高设计模块的观察性
-
使用VCD文件
Verilog HDL提供一系列系统任务用于记录信号值变化保存到标准的VCD(Value Change Dump)格式数据库中,VCD文件是一种标准格式的波形记录文件,只记录发生变化的波形
-
-
仿真效率
-
因为要通过串行软件代码完成并行语义的转化,Verilog HDl行为级仿真代码的执行时间比较长
-
提高Verilog HDL行为级仿真代码的执行时间比较长
-
减小层次结构
仿真代码的层次越少,执行时间就越短
-
减少门级代码的使用
由于门级建模属于结构级建模,建议仿真代码尽量使用行为级语句,建模层次越抽象,执行时间就越短
-
仿真精度越高,效率越低
计时单位值与计时精度值的差距越大,则模拟时间越长
-
进程越少,效率越高
代码中的语句块越少仿真越快,这是因为仿真器在不同进程之间进行切换也需要时间
-
减少仿真器的输出显示
Verilog HDL语言包含一些系统任务,可以在仿真器的控制台显示窗口输出一些提示信息,但会降低仿真器的执行效率
-
-
12、与仿真相关的系统任务
-
$display和$write
-
语法格式
1 2 3 4 5 6 7 8 9$display("<format_specifiers>", <signal1, signal2,...,signaln>); $write("<format_specifiers>", <signal1, signal2,...,signaln>); "<format_specifiers>":通常称为“格式控制” "<signal1, signal2,...,signaln>":则为“信号输出列表” /* $display自动地在输出后进行换行 $write输出特定信息时不自动换行 */ -
输出格式
由”%“和格式字符组成,其作用是将输出的数据转换成指定的格式输出
输出格式 说明 %h或%H 以十六进制数的形式输出 %d或%D 以十进制数的形式输出 %o或%O 以八进制数的形式输出 %b或%B 以二进制数的形式输出 %c或%C 以ASCII码字符的形式输出 %v或%V 输出网络型数据信号的强度 %m或%M 输出等级层次的名字 %s或%S 以字符串的形式输出 %t或%T 以当前的时间格式输出 %e或%E 以指数的形式输出实型数 %f或%F 以十进制数的形式输出实型数 %g或%G 以指数或十进制数的形式输出实型数 -
特殊字符可通过表中的转换序列输出
换码序列 功能 \n 换行 \t 横向跳格(即跳到下一个输出区) \ 反斜杠字符\ " 双引号字符” \o 1到3位八进制数代表的字符 %% 百分符号% -
注意事项
在display中,输出列表中数据的显示宽度是自动按照输出格式进行调整的,总是用表达式的最大可能值所占的位数来显示表达式的当前值
-
-
$monitor和$strobe
-
$monitor
-
语法格式
1$monitor(<"format_specifiers">, <signal1, signal2, ... ,signaln>);任务$monitor提供了监控和输出参数列表中的表达式或变量值的功能
每当参数列表中的变量或表达式的值发生变化时,整个参数列表中变量或表达式的值都将输出显示
1 2$monitor($time,,"rxd=%b txd=%b", rxd,txd); // ",,"代表一个空参数,空参数在输出时显示为空格 -
使用
$monitoron和$monitoroff任务的作用是通过打开和关闭监控标志来控制监控任务$monitor的启动和停止,这样使得程序员可以很容易控制$monitor何时发生
$monitor和$display的不同之处在于$monitor往往在initial块中调用,只要不调用$monitoron,$monitoroff便不间断地对所设定的信号进行监视
monitor可以独立于信号赋值语句
-
-
$stobe
-
$stobe语法格式
1 2$strobe(<functions_or_signals>); $strobe("<string_and/or_variables>",<functions_or_signals>);探测任务用于在某时刻所有时间处理完后,在这个时间步的结尾输出一行格式化文本,常用系统任务如下
- $strobe:在所有时间处理完后,以十进制格式输出一行格式化文本
- $strobeb:在所有时间处理完后,以二进制格式输出一行格式化文本
- $strobeo:在所有时间处理完后,以八进制格式输出一行格式化文本
- $stroben:在所有时间处理完后,以十六进制格式输出一行格式化文本
-
使用
$strobe任务在被调用的时刻所有
-
-
-
$time和$realtime
用这两个时间系统函数可以得到当前的仿真时刻,所不同的是,$time函数以64位整数值的形式返回仿真时间,而$realtime函数则以实数型数据返回仿真时间
-
系统函数$time
返回的时间数字是整数类型,以时间尺度为基准
-
系统函数$realtime
返回的时间数字是一个实型数,该数字也是以时间尺度为基准的
-
-
$finish和$stop
系统任务$finish和$stop是用于对仿真过程进行控制,分别表示结束仿真和中断仿真,其语法格式
1 2 3 4$finish; $finish(n); $stop; $stop(n);其中,n是$finish和$stop的参数,n可以取0、1或2几个值,分别表示如下含义
n的取值 含义 0 不输出任何信息 1 给出仿真时间和位置 2 给出仿真时间和位置,同时还有所有memory及CPU时间的统计 -
$finish
-
退出仿真器,返回主操作系统,也就是结束仿真过程
-
任务$finish可以带参数,根据参数的值输出不同的特征信息,如果不带参数,默认$finish的参数值为1
-
-
$stop
-
把EDA工具(例如仿真器)置成暂停模式,在仿真环境下给出一个交互式的命令提示符,将控制权交给用户
-
这个任务可以带有参数表达式,根据参数值(0, 1, 2)的不同,输出不同的信息,参数值越大,输出的信息越多
-
-
-
$readmemb和$readmemh
用来从文件中读取数据到存储器中,这两个系统任务可以在仿真的任何时刻被执行使用
1 2 3 4 5 6$readmemb("<file_name>", <memory_name>); $readmemb("<file_name>", <memory_name>, <start_addr>); $readmemb("<file_name>", <memory_name>, <start_addr>, <finish_addr>); $readmemh("<file_name>", <memory_name>); $readmemh("<file_name>", <memory_name>, <start_addr>); $readmemh("<file_name>", <memory_name>, <start_addr>, <finish_addr>); -
$random
-
$random是产生随机数的系统函数,每次调用该函数将返回一个32位的随机数,该随机数是一个带符号的整数,语法格式:
1$random%<number>; -
这个系统函数提供了一个产生随机数的手段,当函数被调用时返回一个32bit的随机数,它是一个带符号的整型数
-
$random一般用法是
1 2 3 4$random % b // 当b>0时,它给出了一个范围在(-b+1)-(b-1)的随机数 {$random} % b // 当b>0时,它给出了一个范围在0-(b-1)的随机数
-
13、信号时间赋值语句

-
时间延迟的语法说明
延迟语句用于对各条语句的执行时间进行控制,从而快速满足用户的时序要求,Verilog HDL语言中延时控制的语法格式有两类:
- #<延迟时间>行为语句;
- #<延迟时间>;
其中,符号"#“是延迟控制的关键字符,<延迟时间>可以是直接指定的延迟时间量,并以多少个仿真时间单位的形式给出,在仿真过程中,所有的时延都根据时间单位定义
-
根据时间控制部分的过程赋值语句中出现的位置,可以把过程赋值语句中的时间控制方式分为外部时间控制方式和内部时间控制方式
-
外部时间控制方式
外部时间控制方式是时间控制出现在整个过程赋值语句的最左端,也就是出现赋值目标变量的左边的时间控制方式,其语法结构如下
1 2 3 4 5 6 7 8 9#5 a=b; // 在仿真执行时就相当于如下 initial begin #5; a=b; end -
内部时间控制方式
内部时间控制方式是过程赋值语句中的时间控制部分还可以出现在”赋值操作符“和”赋值表达式“之间的时间控制方式,其语法格式如下
1 2 3 4 5 6 7 8 9 10a=#5b; // 相当于 initial begin temp=b; #5; a=temp; end
-
-
时间延迟的描述形式
此处时间延迟的描述形式是指延时控制的描述形式,其分为串行延迟控制、并行延迟控制、阻塞式延迟控制和非阻塞式延迟控制四种形式
-
串行延迟控制(相对延迟时间)
-
串行延迟控制是最为常见的信号延迟控制,它是由begin-end过程加上延迟赋值语句构成,其中延迟赋值语句可以为外部时间控制,也可以为内部时间控制方式,在<延迟时间>之后也可以根据情况来确定是否执行相应的行为语句
-
在<延迟时间>后有相应的行为语句,则仿真进程遇到这条带有延迟控制的行为语句后并不立即执行行为语句指定的操作,而是要延迟等待到“<延迟时间>”所指定的时间量过去后才真正开始执行行为语句指定的操作
-
-
并行延迟控制(绝对延迟时间)
-
并行延迟控制是最为常见的信号延迟控制,它是由fork-join过程加上延迟赋值语句构成,其中延迟赋值语句同串行延迟控制方式一样,既可以是外部时间控制,也可以是内部时间控制方式,在<延迟时间>之后也可以根据情况来确定是否执行相应的行为语句
-
在<延迟时间>后有相应的行为语句,则仿真进程遇到这条带有延迟控制的行为语句后并不立即执行行为语句指定的操作,而是要延迟等待到“<延迟时间>”所指定的时间量过去后才真正开始执行行为语句指定的操作
-
并行延迟控制方式与串行延迟控制方式不同在于并行延迟控制方式中的多条延迟语句时并行执行的,并不需要等待上一条语句的执行完成才开始执行当前的语句
-
-
阻塞式延迟控制
-
以赋值操作符”=“来标识的赋值操作称为“阻塞式过程赋值”
-
阻塞式延迟控制是在阻塞式过程赋值基础上带有延时控制的情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24initial begin a = 0; a = #5 1; a = #10 0; a = #15 1; end // 等价于 initial begin a = 0; temp = 1; #5; a = temp; temp = 0; #10; a = temp; temp = 1; #15; a = temp; end -
-
非阻塞式延迟控制
-
以赋值操作符”<=“来标识的赋值操作称为“非阻塞式过程赋值”
-
非阻塞式延迟控制是在非阻塞式过程赋值基础上带有延时控制的情况
1 2 3 4 5 6 7initial begin a <= 0; a <= #5 1; a <= #10 0; a <= #15 1; end -
-
-
边沿触发事件控制
-
语法格式
1 2 3 4@(<事件表达式>) 行为语句; @(<事件表达式>); @(<事件表达式1>or<事件表达式2>or......or<事件表达式n>) 行为语句; @(<事件表达式1>or<事件表达式2>or......or<事件表达式n>); -
事件表达式
1 2 3<信号名> posedge<信号名> negedge<信号名>在信号发生逻辑变化(正跳变或负跳变)的过程中,信号的值是从0、1、x、z四个值中的一个值变化到另一个值
正跳变 负跳变 0→x 1→x 0→z 1→z 0→1 1→0 x→1 x→0 z→1 z→0
-
-
电平敏感事件控制
电平敏感事件控制是另一种事件控制方式,与边沿触发事件控制不同,它是在指定的条件表达式为真时启动需要执行的语句,电平敏感事件控制是用关键词“wait”来表示
1 2wait(条件表达式) 行为语句; wait(条件表达式);当仿真进程执行到这条电平敏感控制语句时条件表达式的值为“真”,那么语句块立即得到执行,否则语句块要一直等到条件表达式变为“真”时再开始执行
1 2 3 4 5 6 7 8 9 10 11 12wait(enable == 1) begin d = a & b; d = d | c; end begin wait(enable == 1) ; d = a & b; d = d | c; end
14、任务和函数
-
任务
-
任务的定义
1 2 3 4 5 6 7 8 9 10task<任务名字>; 端口和类型声明 局部变量声明 begin 语句1; 语句2; ... 语句n; end endtask-
任务的定义是嵌在关键字task和endtask之间的,其中关键词task标志着一个任务定义结构的开始,endtask标志着一个任务定义结构的结束。<任务名>是所定义任务的名称,在<任务名>后不能出现输入输出端口列表
-
任务定义时的注意事项
- 第一行"task"语句中不能列出端口名列表
- 任务中可以有延时语句、敏感事件控制语句等事件控制语句
- 任务可以没有或可以有一个或多个输入、输出和双向端口
- 任务可以没有返回值,也可以通过输出端口或双向端口返回一个或多个返回值
- 任务可以调用其它任务或函数,也可以调用任务本身
- 任务定义结构内不允许出现过程块(initial或always过程块)
- 任务定义结构内可以出现disable终止语句,这条语句的执行将中断正在执行的任务,在任务被中断后,程序流程将返回到调用任务的地方继续向下执行
-
-
任务的调用
-
通过任务调用语句来实现,任务调用语句列出了传入任务的参数值和接收结果的变量值,任务的调用格式如下:
<任务名>(端口1, 端口2, … , 端口n);
-
-
-
函数
-
函数的定义
1 2 3 4 5 6 7 8 9 10function <返回值类型或位宽> <函数名>; <输入变量与类型声明> <局部变量说明> begin 语句1; 语句2; ... 语句n; end endfunction- 函数定义是嵌入在关键字function和endfunction之间的,其中关键字functionh标志着一个函数定义结构的开端,endfunction标志着一个函数定义结构的结束。“<函数名>”是给被定义函数取的名称,这个函数名在函数定义结构内部还代表着一个内部变量,函数调用后的返回值是通过这个函数名变量传递给调用语句的
- <返回值类型或位宽>是一个可选项,它是用来对函数调用返回数据的类型或宽度进行说明,它可以有如下三种形式
- [msb:lsb]:这种形式说明函数名所代表的返回数据变量是一个多位的寄存器变量,它的位宽是由[msb:lsb]指定
- integer:这种形式说明函数名所代表的返回数据变量是一个整数型变量
- real:这种形式说明函数名所代表的返回数据变量是一个实数型变量
- <输入变量与类型声明>是对函数各个输入端口的宽度和类型进行说明,在函数定义中,必须至少有一个输入端口的声明,不能有输出端口的声明。数据类型声明语句用来对函数内用到的局部变量进行宽度和类型的说明,这个说明语句的语法与进行模块定义时的相应说明语句语法是一致的
- <局部变量说明>是对函数内部局部变量进行宽度和类型的说明
- 函数定义时的注意事项
- 与任务一样,函数定义结构只能出现在模块中,而不能出现在过程块中
- 函数至少有一个输入端口
- 函数不能有任何类型的输出端口(out)和双向端口(inout)
- 在函数定义结构中的行为语句部分内不能出现任何类型的时间控制描述,也不允许使用disable终止语句
- 与任务定义一样,函数定义结构内部不能出现过程块
- 在一个函数内可以对其它函数进行调用,但是函数不能调用其它任务
- 在第一行“function”语句中不能出现端口名列表
- 在函数声明时,在Verilog HDL的内部隐含地声明了一个名为function_identifier(函数标识符)的寄存器类型变量,函数的输出结构将通过这个寄存器类型变量被传递回来
-
函数的调用
-
函数的调用是通过将函数作为表达式中的操作数来实现的,函数的调用格式如下:
<函数名>(<输入表达式1>, <输入表达式2>, … ,<输入表达式n>);
其中,输入表达式与函数定义结构中说明的输入端口一一对应
-
函数调用时的注意事项
- 函数的调用不能单独作为一条语句出现,它只能作为一个操作数出现在调用语句内
- 函数的调用既能出现在过程块中,也能出现在assign连续赋值语句中
- 函数定义中声明的所有局部寄存器都是静态的,即函数中的局部寄存器在函数的多个调用之间保持它们的值
-
-
-
任务与函数的区别
函数 任务 函数能调用另外一个函数,但不能调用另外一个任务 任务能调用另外一个任务,也能调用另外一个函数 函数总是在仿真时刻0就开始执行 任务可以在非零仿真时刻执行 函数一定不能包含任何延迟、事件或时序控制声明语句 任务可以包含延迟、事件或时序控制语句声明 函数至少有一个输入变量,没有输出变量 任务可以没有或有多个输入、输出和双向变量 函数只能返回一个值,函数不能有输出或双向变量 任务不返回任何值,任务可以通过输出或双向变量传递多个值 函数不能单独作为一条语句出现,它只能以语句的一部分的形式出现 任务的调用则是通过一条单独的任务调用语句实现 函数调用可以出现在过程块或连续赋值语句中 任务调用只能出现在过程块中 函数的执行不允许由disable语句进行中断 任务的执行可以由disable语句进行中断
15、典型测试向量的设计
-
变量初始化
有两种方法初始化变量,一种是利用初始化变量,另一种就是在定义变量时直接赋值初始化,这两种初始化任务是不可综合的,主要用于仿真过程
- initial初始化方式
- 在大多数情况下,Testbench中变量初始化的工作通过initial过程块来完成,可以产生丰富的仿真激励
- initial语句只执行一次,即在设计被开始模拟执行时开始(0时刻)直到过程结束,专门用于对输入信号进行初始化和产生特定信号波形
- 一个Testbench可以包含多个initial过程语句块,所有的initial过程同时执行,initial语句中的变量必须为reg类型
- 定义变量时初始化
- 直接用”=“在变量右端赋值即可
- initial初始化方式
-
数据信号测试向量产生
-
初始化和产生都在单个initial块中进行
适合不规则数据序列,长度较短
-
初始化在initial语句中完成,产生在always语句块中完成
适合具有一定规律的数据序列,长度不限
-
-
时钟信号测试向量产生
-
基于initial语句的方法
initial + 循环
-
基于always语句的方法
###眼图(产生相位偏移的时钟信号)
-
-
总线信号测试向量产生
-
总线是运算部件之间数据流通的公共通道,在RTL级描述中,总线指的是由逻辑单元、寄存器、存储器、电路输入或其它总线驱动的一个共享向量
-
总线功能模型则是一种将物理的接口时序操作转化成更高抽象层次接口的总线模型,如下图所示:

-
在总线中,对于每个请求端,有一个输入来选择驱动该总线所对应的请求端。选择多个请求端会产生总线冲突,根据总线的类型,冲突会产生不同的结果,当有多个请求端发出请求时,相应的操作由总线的类型决定
-
在Verilog HDL测试中,总线测试信号通常是通过将片选信号,读(或写)使能信号、地址信号、以及数据 信号以task任务的形式描述,通过调用以task形式的总线信号测试向量来完成相应的总线功能
-
16、用户自定义元件模型UDP
通过UDP可以把一块组合逻辑电路或时序逻辑电路封装在一个UDP内,并把这个UDP作为一个基本门元件来使用,需要注意的是,UDP是不能综合的,只能用于仿真
-
UDP的定义与调用
1 2 3 4 5 6 7 8 9 10 11 12primitive <元件名称> (<输出端口名>, <输入端口名1>, ..., <输入端口名3>); 输出端口类型声明(output) 输入端口类型声明(input) // 所有的输入输出端口都是标量,都是1bit 输出端口寄存器变量说明(reg) 元件初始状态说明(initial) table // table只能用于UDP中 <table表项1>; <table表项2>; ... <table表项n>; endtable endprimitive和Verilog HDL中的模块相比,UDP具备以下特点
- UDP的输出端口只能有一个,且必须位于端口列表的第一项,只有输出端口能定义为reg类型
- UDP的输入端可有多个,一般时序电路UDP的输入端口最多9个(+clk),组合电路UDP的输入端口最多10个
- 所有端口变量的位宽必须是1比特
- 在table表项中,只能出现0、1、x这三种状态,z将被认为是x状态
UDP的调用(位置映射):UDP 例化名 (连接端口1信号名,连接端口2信号名,……);
-
UDP分类
这几类UDP的差别主要体现在table表项的描述上
-
组合电路UDP
-
组合逻辑电路的功能列表类似真值表,就是规定了不同的输入值和对应的输出值
-
表中的每一行形式是“output, input1, input2, …”,排列顺序和端口列表中的顺序相同(组合电路的输入端口最多10个)
-
如果某个输入组合没有定义的输出,那么就把这种情况输出置为x
符号 意义 符号 意义 0 逻辑0 (AB) 由A到B 1 逻辑1 * 与(??)相同 x 未知的值 r 上升沿,与(01)相同 ? 0、1或x中的任一个 f 下降沿,与(10)相同 b 0或1中的任一个 p (01)、(0x)和(x1)的任一种 - 输出保持 n (10)、(1x)和(x0)的任一种 -
-
时序电路UDP
- UDP还可以描述具有电平触发和边沿触发特性的时序电路,时序电路拥有内部状态序列,其内部状态必须用寄存器变量进行建模,该寄存器的值就是时序电路的当前状态,它的下一个状态就是由放在基元功能列表中的状态转换表决定的,而且寄存器的下一个状态就是这个时序电路UDP的输出值
- 时序电路UDP由两部分组成:状态寄存器和状态列表
- 定义时序UDP的工作也分为两部分:初始化状态寄存器和描述状态列表
-
混合电路UDP
- 在同一个表中能够混合电平触发和边沿触发项
- 电平触发项覆盖边沿触发项
-
17、基本门级元件和模块的延时建模
-
门级延时建模
-
门级延时分类
- 上升延时:表示由信号"0”、“x"或"z"状态变化到"1"状态时受到的门传输延时
- 下降延时:表示由信号"1”、“x"或"z"状态变化到"0"状态时受到的门传输延时
- 到不定态的延时:表示由信号"0”、“1"或"z"状态变化到"x"状态时受到的门传输延时
- 截止延时:表示由信号"0”、“1"或"x"状态变化到"z"状态时受到的门传输延时
-
门级延时的基本延时表达形式
在门级延时的基本表达形势下,delay内可以包含0-3个延时值
延时值 无延时 1个延时值(d) 2个延时值(d1,d2) 3个延时值(dA,dB,dC) Rise 0 d d1 dA Fall 0 d d2 dB To_x 0 d min(d1,d2) min(dA,dB,dC) Turn_off 0 d min(d1,d2) dC -
门级延时的最小、典型、最大延时表达式
除了基本延时表达形式外,门级延时量还可以采用“最小、典型、最大”延时表达式,在这种表示方式下,门级延时量中的每一项将由“最小延时”、“典型延时”和“最大延时”三个值来表示,其语法格式如下:# (d_min: d_typ: d_max)
-
-
模块延时建模
-
延时说明块Specify Block
- 在模块输入和输出引脚之间的延迟称为模块路径延迟
- 在Verilog HDL中,在关键字specify和endspecify之间给路径延迟赋值,关键字之间的语句组成specify块(即指定块),“specify"和"endspecify"分别是延时说明块的起始标识符和终止标识符
- specify块包含下列操作语句
- 定义穿过模块的所有路径延迟
- 在电路中设置时序检查
- 定义specparam常量
-
路径延迟描述方式
-
并行连接
-
每条路径延迟语句都有一个源域或一个目标域
-
在specify块中,用符号”=>“说明连接,语法格式如下:
(<source_field>=><destination_field>) = <delay_value>
-
<delay_value>可以包含1-3个延时量,也可以采用"最小、典型、最大"延时表达形式,在延时量由多个值组成的情况下,应在延时量的外面加上一对括号
-
在并行连接中,源域中的每一位与目标域中相应位进行连接,位宽必须保持一致
-
并行连接说明了源域的每一位到目标域的每一位之间的延迟
-
-
全连接
-
在specify块中,用符号”*>“表示全连接,语法格式如下:
(<source_field>*><destination_field>) = <delay_value>
-
在全连接中,源域中的每一位与目标域中每一位相连接,位宽不必一致
-
全连接描述的是源中的每一位和目标中的每一位之间的延迟
-
-
specparam声明语句
- specparam用来定义specify块中的参数
- specparam和parameter的区别
- specparam语句只能在延时说明块(specify块)中出现,而parameter语句则不能在延时说明块内出现
- 由specparam语句进行定义的参数只能是延时参数,而由parameter语句定义的参数可以是任何数据类型的常数参数
- 由specparam语句定义的延时参数只能在延时说明块中使用,而由parameter语句定义的参数则可以在模块内任意位置使用
- 在模块中提供specify参数是为了方便给延迟赋值,建议用specify参数而不是数值来表示延迟,这样如果时序电路发生变化,用户只需要改变参数值,而不必逐一修改
-
-
-
与时序检查相关的系统任务
-
$setup
-
建立时间检查可以用系统任务$setup进行,其语法格式如下:
$setup(data_event, reference_event, limit);
- data_event:是被检查的信号,检查它是否违约
- reference_event:用于检查"data_event"信号的参考信号
- limit:是"data_event"需要的最小建立时间
-
如果(time_of_reference_event - time_of_data_event) < limit,则报告时序冲突(time violation)

-
-
$hold
-
保持时间检查可以用系统任务$hold进行,其语法格式如下:
$hold(reference_event, data_event, limit);
- reference_event:用于检查"data_event"信号的参考信号
- data_event:是被检查的信号,检查它是否能违反约束
- limit:是"data_event"需要的最小保持时间
-
如果(time_of_data_event - time_of_reference_event) < limit,则报告数据保持时间时序冲突

-
-
18、编译预处理语句
编译预处理是Verilog HDL编译系统的一个组成部分,指编译系统会对一些特殊命令进行预处理,然后将预处理结果和源程序一起再进行通常的编译处理
以”`"(反引号)开始的某些标识符是编译预处理语句
在Verilog HDL语言编译时,特定的编译器指令在整个编译过程中有效(编译过程可跨越多个文件),直到遇到其它的不同编译程序之类
常用编译预处理语句如下
|
|
-
仿真时间标度
-
`timescale命令用来说明跟在该命令后的模块的时间单位和时间精度
-
`timescale命令的格式:
`timescale<时间单位>/<时间精度>
- 时间单位:定义模块中仿真时间和延迟时间的基准单位
- 时间精度:声明该模块的仿真时间的精确程序,用来对延迟时间值进行取整操作(仿真前)
- 如果一个程序中有多个`timescale命令,则用最小的时间精度值来决定仿真的时间单位,时间精度值不能大于时间单位值
-
`timescale命令中用于说明时间单位和时间精度参量值的数字必须是整数,其有效数字为1、10、100,单位为秒(s)、毫秒(ms)、微秒(us)、纳秒(ns)、皮秒(ps)、毫皮秒(fs)
-
19、测试方法简介
-
完全测试法
对于复杂的设计来说,常常通过检查代码的覆盖率来检查验证工作是否完成的一种重要方法
代码覆盖率可以指示Verilog HDL代码描述的功能有多少在仿真过程中被验证过,通常代码覆盖率包括以下内容:
- 语句覆盖率
- 路径覆盖率
- 状态机覆盖率
- 触发覆盖率
- 表达式覆盖率
-
随机测试法
在Verilog HDL中提供了多个用于随机测试的系统命令,通常使用随机测试的系统函数来仿真真实应用的情况,如在通信领域中常用的帧同步搜索电路需要从接收的数据流中检测发送端固定插入的某个特殊的码型,而数据本身也有可能包括该码型,在这种情况下进行随机化测试就更接近于真实应用的情况,其中最常用的系统命令是随机数产生系统任务$random
-
自动测试法
在集成电路测试领域,全面准确的测试才能保证大规模集成电路的正常工作,而确保一个设计能够得到全面测试的唯一途径就是实现任务的自动化,通常通过创建一个检验表,使用相应个数的采样值,当修改过源代码后,所有的测试程序都将自动被再次执行,但需要注意,使用自动测试可能会存在阶段误差
20、数字电路系统设计的层次化描述方法

21、典型电路设计
-
加法器树乘法器
-
加法器树乘法器的设计思想是“移位后加”,并且加法运算采用加法器树的形式
-
乘法运算的过程是,被乘数与乘数的每一位相乘并且乘以相应的权值,最后将所得的结果相加,便得到了最终的乘法结果

- 流水线结构,提高速度

-
-
Wallace树乘法器(3-2压缩器)
简单地讲即许多个加数求和,每3个加数分为一组,压缩至2个加数,循环往复
-
复数乘法器
x = a + bi,y = c + di
x * y = (ac - bd) + (ad + bc)i

如果是低速电路,可以采用更简单的形式

-
FIR滤波器设计
有限冲击响应(FIR)滤波器是一种常用的数字滤波器,采用对已输入样值的加权和来形成它的输出,其系统函数为:
$$ H(z) = \frac{y(z)}{x(z)} = a + bz^{-1}+ cz^{-2} $$其中,$z^{-1}$表示延时一个时钟周期,$z^{-2}$表示延时两个时钟周期
对于输入序列X[n]的FIR滤波器可用下图所示的结构示意图来表示,其中X[n]是输入数据流
各级的输入连接和输出连接被称为抽头,并且系数(b
0, b1, ……, bn)被称为抽头系数一个M阶的FIR滤波器将会有M + 1个抽头,通过移位寄存器用每个时钟边沿n(时间下标)处的数据流采样值乘以抽头系数,并将它们加起来形成输出Y[n]

-
FIFO设计
-
FIFO是一种先进先出的数据缓存器,通常用于接口电路的数据缓存,与普通存储器的区别是没有外部读写地址线,可以使用两个时钟分别进行写和读操作,FIFO只能顺序写入数据和顺序读出数据,其数据地址由内部读写指针自动加1完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址
-
FIFI由存储器块和对数据进出FIFO的通道进行管理的控制器构成,每次只对一个寄存器提供存取操作,而不是对整个寄存器阵列进行,FIFO有两个地址指针,一个用于将数据写入下一个可用的存储单元,一个用于读取下一个未读存储单元的操作,读写数据必须一次进行
-
读写过程

-
一个FIFO的组成一般包括两个部分:地址控制部分和存储数据的RAM部分
地址控制部分可以根据读写指令生成RAM地址,RAM用于存储堆栈数据,并根据控制部分生成的地址信号进行数据的存储和读取操作,这里的RAM采用的是前面提到的双口RAM
-
-
键盘扫描和编码器
键盘扫描和编码器用于在拥有键盘的数字系统中手工输入数据,通过检测按键是否按下,产生一个唯一对应此按键的扫描码(键盘一定是低速电路)

-
log函数的Verilog HDL设计
log函数是一种典型的单目计算函数,与其相应的还有指数函数、三角函数
对于单目计算函数的硬件加速器设计一般两种简单方法
- 查找表
- 使用泰勒级数展开成多项式进行近似计算
这两种方式在设计方法和精确度方面有很大的不同
-
查找表是通过存储器进行设计,设计方法简单,其精度需要通过提高存储器深度实现,在集成电路中占用面积大,因此这种方式通常在精度要求不高的近似计算中使用
-
泰勒级数展开方式采用乘法器和加法器实现,可以通过增加展开级数提高计算精确度