Download presentation
Presentation is loading. Please wait.
1
Verilog HDL 基础语法入门
2
第一部分 课程简介 目的: 简单介绍Verilog HDL语言和仿真工具 介绍讲课计划 介绍如何不断地学习新的有关知识
3
讲座中关于Verilog HDL的主要内容
讲课内容主要包括: Verilog 的应用 Verilog 语言的组成部件 结构级的建模与仿真 行为级的建模与仿真 延迟参数的表示 Verilog 的测试平台: 怎样产生激励信号和控制信号 输出响应的产生、记录和验证 任务和函数 用户定义的元器件(primitives) 可综合风格的Verilog 建模
4
讲座中关于Verilog仿真工具的主要内容
讲课内容主要包括: 如何对所做的设计进行编译和仿真 如何使用元器件库 如何用Verilog-XL命令行界面调试代码 如何用图形用户界面(GUI) 延迟的计算和标记 仿真性能建模 循环多次仿真
5
第二部分:Verilog 的应用 目的: 了解用HDL语言设计数字逻辑的优点 了解Verilog 主要应用领域
6
Verilog 的应用 的 Verilog HDL模型。 是一种结构描述的语言。 Verilog HDL是一种用于数字逻辑电路设计的语言:
7
Verilog 的应用 系统级(system): 用高级语言结构实现设计模块的外部性能的模
算法级(algorithmic): 用高级语言结构实现设计算法的模型。 RTL级(Register Transfer Level): 描述数据在寄存器之间流动和如何处理这些数据的模型。 门级(gate-level): 描述逻辑门以及逻辑门之间的连接的模型。 开关级(switch-level): 描述器件中三极管和储存节点以及它们之间连接的模型。
8
Verilog 的应用 一个复杂电路的完整Verilog HDL模型是由若个 Verilog HDL 模块构成的,每一个模块又可以由若干个子模块构成。 利用Verilog HDL语言结构所提供的这种功能就可以构造一个模块间的清晰层次结构来描述极其复杂的大型设计。 Verilog HDL行为描述语言作为一种结构化和过程性的语言,其语法结构非常适合于算法级和RTL级的模型设计。这种行为描述语言具有以下八项功能:
9
Verilog 的应用 可描述顺序执行或并行执行的程序结构。 用延迟表达式或事件表达式来明确地控制过程的启动时间。
通过命名的事件来触发其它过程里的激活行为或停止行为。 提供了条件、if-else、case、循环程序结构。 提供了可带参数且非零延续时间的任务(task)程序结构。 提供了可定义新的操作符的函数结构(function)。
10
Verilog 的应用 提供了用于建立表达式的算术运算符、逻辑运算符、位运算符。
Verilog HDL语言作为一种结构化的语言也非常适合于门级和开关级的模型设计。 Verilog HDL的构造性语句可以精确地建立信号的模型。这是因为在Verilog HDL中,提供了延迟和输出强度的原语来建立精确程度很高的信号模型。信号值可以有不同的的强度,可以通过设定宽范围的模糊值来降低不确定条件的影响。
11
Verilog 的应用 Verilog HDL作为一种高级的硬件描述编程语言,有着类似C语言的风格。其中有许多语句如:if语句、case语句等和C语言中的对应语句十分相似。如果读者已经掌握C语言编程的基础,那么学习 Verilog HDL并不困难,我们只要对Verilog HDL某些语句的特殊方面着重理解,并加强上机练习就能很好地掌握它,利用它的强大功能来设计复杂的数字逻辑电路。下面我们将对Verilog HDL中的基本语法逐一加以介绍。
12
模块的抽象 技术指标: RTL/功能级: 门级/结构级: 版图布局/物理级: 行为综合 综合前仿真 逻辑综合 综合后仿真 布局布线
用文字表示 用算法表示 用高级行为的Verilog模块表示 RTL/功能级: 用可综合的Verilog模块表示 门级/结构级: 用实例引用的Verilog模块表示 版图布局/物理级: 用几何形状来表示
13
第三部分.简单的 Verilog HDL 模块 目的: 通过简单的例子了解Verilog模块的基本构成
14
下面先介绍几个简单的Verilog HDL程序,然后从中分析Verilog HDL程序的特性。
例[2.1.1]: module adder ( count,sum,a,b,cin ); input [2:0] a,b; input cin; output count; output [2:0] sum; assign {count,sum}=a+b+cin; endmodule 这个例子描述了一个三位的加法器。从例子中可以看出整个Verilog HDL程序是嵌套在module和endmodule声明语句里的。
15
简单的 Verilog HDL 模块 例[2.1.2]: module compare ( equal,a,b );
output equal; //声明输出信号equal input [1:0] a,b; //声明输入信号a,b assign equal=(a==b)?1:0; /*如果两个输入信号相等,输出为1。否则为0*/ endmodule 这个程序描述了一个比较器.在这个程序中,/* */和// 表示注释部分,注释只是为了方便程序员理解程序,对编译是不起作用的。
16
简单的 Verilog HDL 模块 例[2.1.3]: module trist2(out,in,enable); output out;
input in, enable; bufif1 mybuf(out,in,enable); endmodule 这个程序描述了一个三态驱动器。程序通过调用一个实例元件bufif1来实现其功能。
17
简单的 Verilog HDL 模块 例[2.1.4]: module trist1(out,in,enable); output out;
input in, enable; mytri tri_inst(out,in,enable); endmodule module mytri(out,in,enable); assign out = enable? In : 'bz;
18
简单的 Verilog HDL 模块 上述程序例子通过另一种方法描述了一个三态门。
在这个例子中存在着两个模块:模块trist1 调用模块 mytri 的实例元件 tri_inst。 模块 trist1 是上层模块。模块 mytri 则被称为子模块。 通过这种结构性模块构造可构成特大型模块。
19
简单的 Verilog HDL 模块 通过上面的例子可以看到:
每个模块要进行端口定义,并说明输入输出口,然后对模块的功能进行行为逻辑描述。 Verilog HDL程序的书写格式自由,一行可以写几个语句,一个语句也可以分写多行。 除了endmodule语句外,每个语句和数据定义的最后必须有分号 可以用/*.....*/和//...对Verilog HDL程序的任何部分作注释。一个好的,有使用价值的源程序都应当加上必要的注释,以增强程序的可读性和可维护性。
20
模块的结构 Verilog的基本设计单元是“模块”(block)。一个模块是由两部分组成的:一)描述接口;二)描述逻辑功能,即定义输入是如何影响输出的。下面举例说明:
21
模块的结构 从上面的例子可以看出: - Verilog模块结构完全嵌在module和endmodule声明语句之间; - 每个Verilog程序包括四个主要部分:端口定义、I/O说明、内部信号声明、功能定义。
22
模块的测试 如何检查上述例子其功能是否正确? 需要有测试激励信号输入到被测模块 需要记录被测模块的输出信号
需要把用功能和行为描述的Verilog模块 转换为门级电路互连的电路结构(综合)。 需要对已经转换为门级电路结构的逻辑 进行测试(门级电路仿真)。 需要对布局布线后的电路结构进行测试。 (布局布线后仿真)。
23
模块的测试 被测模块 激励和控制信号 输出响应和验证
24
模块的测试 测试模块常见的形式: module t; reg …; //被测模块输入/输出变量类型定义
wire…; //被测模块输入/输出变量类型定义 initial begin …; …; …; end … …//产生测试信号 always #delay begin …; end … …//产生测试信号 Testedmd m(.in1(ina), .in2(inb), .out1(outa), .out2(outb) ); //被测模块的实例引用 initial begin ….; ….; …. end //记录输出和响应 endmodule
25
模块的测试 测试模块中常用的过程块: 所有的过程块都在0时刻同时启动;它们是并行的,在模块中不分前后。 initial always
26
模块的测试 如何描述激励信号: module t; reg a, b, sel; wire out; //引用多路器实例
mux2_m (out, a, b, sel); //加入激励信号 initial begin a=0; b=1; sel=0; #10 b=0; #10 b=1; sel=1; #10 a=1; #10 $stop; end
27
模块的测试 如何观察被测模块的响应: 在initial 块中,用系统任务$time 和 $monitor $time 返回当前的仿真时刻
量值发生变化,便在仿真单位时间结束时显示其变 量列表中所有变量的值。 例: initial begin $monitor ($time, , “out=%b a=%b sel=%b”, out,a,b,sel); end
28
模块的测试 如何把被测模块的输出变化记录到数据库文件中? 可用以下七个系统任务:
(文件格式为VCD,大多数的波形显示工具都能读取该格式) 可用以下七个系统任务: $dumpfile(“file.dump”); //打开记录数据变化的数据文件 $dumpvars(); //选择需要记录的变量 $dumpflush; //把记录在数据文件中的资料转送到硬盘保存 $dumpoff; //停止记录数据变化 $dumpon; //重新开始记录数据变化 $dumplimit(<file_size>); //规定数据文件的大小(字节) $dumpall; //记录所有指定信号的变化值到数据文件中
29
模块的测试 如何把被测模块的响应变化记录到数据库文件中? 举例说明: $dumpvars; //记录各层次模块中所有信号的变化
$dumpvars(1,top); //只记录模块top中所有信号的变化 $dumpvars(2,top.u1); //记录top模块中实例u1和它以下一层子模块所有信号的变化 $dumpvars(0,top.u2,top.u1.u13.q); //记录top模块中实例u2和它本层所有信号的变化,还有top.u1.u13.q 信号的变化。 $dumpvars(3,top.u2,top.u1); //记录top模块中u2和u1所有信号的变化(包括其两层以下子模块的信号变化)。
30
模块的测试 如何把被测模块的响应变化记录到数据库文件中? 举例说明:
下面的 Verilog 代码段可以代替测试文件中的系统任务$monitor initial begin $dumpfile(“vlog.dump”); $dumpvars(0,top); end
31
语法详细讲解 第四部分. Verilog 语法要点
目标: 理解Verilog 语言的一些重要规定. 学会认识一些有关的重要语言符号. 掌握Verilog 中如何规定时间单位
32
语法详细讲解 Verilog 与 C 的主要不同点
- 并行性 - 块的含义: initial 块 和 always块 两种赋值语句: 阻塞 赋值 “ = ” 非阻塞赋值 “〈= ”
33
语法详细讲解 空格和注释 Verilog 是一种格式很自由的语言。 空格在文本中起一个分离符的作用, 别的没有其他用处。 单行注释符用 //********* 与C 语言一致 多行注释符用 /* */
34
整数可以标明位数也可以不标明位数,表示方法: 《位数》‘《基数》《值》 其中《位数》表明该数用二进制的几位来表示
语法详细讲解 整数和实常数 Verilog 语言中常数可以是整数或实数: 整数可以标明位数也可以不标明位数,表示方法: 《位数》‘《基数》《值》 其中《位数》表明该数用二进制的几位来表示 《基数》可以是二(b)、八(O)、十(d)或 十六(h)进制 《数值》可以是所选基数的任何合法的值包括 不定值 x 位和高阻值 z。 如:64‘hff01 8’b1101_0001 ‘h83a 实常数可以用十进制表示也可以用科学浮点数表示,如:32e-4 (表示0.0032) 4.1E3( 表示 4100)
35
语法详细讲解 字符串 Verilog 语言中,字符串常常用于表示命令内需要显示的信息。 用“ ”括起来的一行字符串,换新一行用 “\n” 字符,与 C 语言一致。 在字符串中可以用 C 语言中的各种格式控制符,如\t, \”, \\… 在字符串中可以用 C 语言中的各种数值型式控制符(有些不同),如: %b(二进制), %o(八进制), %d(十进制), %h(十六进制), %t(时间类型),%s (字符串类型)…
36
语法详细讲解 标识符 所谓标识别符就是用户为程序描述中的Verilog 对象所起的名字。 标识符必须以英语字母(a-z, A-Z)起头,或者用下横线符( _ )起头。其中可以包含数字、$符和下横线符。 标识符最长可以达到1023个字符。 模块名、端口名和实例名都是标识符。 Verilog语言是大小写敏感的,因此sel 和 SEL 是两个不同的标识符。
37
Verilog 是大小写敏感的。所有的Verilog 关键词都是小写的。
语法详细讲解 合法和非法标识符 非法的: 34net //不能用数字开头 a*b_net //不能含有非字母符号* 合法的: shift_reg_a busa_index bus263 Verilog 是大小写敏感的。所有的Verilog 关键词都是小写的。
38
语法详细讲解 特别的标识符 特别标识符是用 “\” 符开始,以空格符结束的标 识符。它可以包含任何可打印的ASCII字符。 但“\”符和空格并不算是标识符的一部分。 特别标识符往往是由RTL级源代码或电路图类型的 设计输入经过综合器自动综合生成的网表结构型 Verilog 语句中的标识符。 举例说明: , \bus+index , \{A,B} , Top.\3inst .net1 , //在层次模块中的标识名
39
‘$’ 符号表示 Verilog 的系统任务和函数 常用的系统任务和函数有下面几种: $time //找到当前的仿真时间
语法详细讲解 系统任务和函数 $<标识符> ‘$’ 符号表示 Verilog 的系统任务和函数 常用的系统任务和函数有下面几种: $time //找到当前的仿真时间 $display, $monitor //显示和监视信号值的变化 $stop //暂停仿真 $finish //结束仿真 例: initial $monitor($time,,”a=%b, b=%b”, a, b); //每当a 或b值变化时该系统任务都显示当前的仿真时刻并分别用二进制和十六进制显示信号a和 b的值
40
语法详细讲解 特殊符号 “#” 特殊符号 “#” 常用来表示延迟: 在过程赋值语句时表示延迟。 例:initial begin #10 rst=1; #50 rst=0; end 在门级实例引用时表示延迟。 例:not #1 not1(nsel, sel); and #2 and2(a1, a, nsel); 在模块实例引用时表示参数传递 介绍参数类型变量时再讲。。。。
41
语法详细讲解 编译引导语句 编译引导语句用主键盘左上角小写键 “ ` ” 起头 用于指导仿真编译器在编译时采取一些特殊处理 编译引导语句一直保持有效,直到被取消或重写 `resetall 编译引导语句把所有设置的编译引导恢复到缺省状态 常用的编译引导有: `define `include `timescale `uselib `resetall ……..
42
使用`define 编译引导能提供简单的文本替代功能 `define <宏名> <宏文本>
语法详细讲解 编译引导语句 使用`define 编译引导能提供简单的文本替代功能 `define <宏名> <宏文本> 在编译时会用宏文本来替代源代码中的宏名。 合理地使用`define可以提高程序的可读性 举例说明: `define on 1’b1 `define off 1’b0 `define and_delay #3 在程序中可以用有含义的文字来表示没有意思的数码提高了程序 的可读性,在程序中可以用 `on, `off, `and_delay 分别表 示 1,0,和 #3 。
43
`include “parts/counter.v” `include “../../library/mux.v”
语法详细讲解 编译引导语句 使用`include 编译引导,在编译时能把其指定的整个文件包括进来一起处理 举例说明: `include “global.v” `include “parts/counter.v” `include “../../library/mux.v” 合理地使用`include 可以使程序简洁、清晰、条理清楚、易于查错。
44
`timescale 用于说明程序中的时间单位和仿真精度 举例说明: `timescale 1ns/100ps
语法详细讲解 编译引导语句 `timescale 用于说明程序中的时间单位和仿真精度 举例说明: `timescale 1ns/100ps `timescale 语句必须放在模块边界前面 举例说明: `timescale 1ns/100ps module MUX2_1(out,a,b,sel); … … not #1 not1(nsel, sel); and #2 and1(a1, a, nsel); endmodule 尽可能地使精度与时间单位接近,只要满足设计的实际需要就行。 举例说明:在上例中所有的时间单位都是1ns的整数倍
45
语法详细讲解 编译引导语句 仿真步长即仿真单位(STU) 是所有参加仿真模块中由`timescale 指定的精度中最高(即时间最短)的那个决定的: (STU=100fs) 举例: `timescale 1ns/10ps module M1(….); not #1.23 not1(nsel, sel); //1.23 ns中共有12300个STU(100fs) endmodule `timescale 100ns/1ns module M2(….); not #1.23 not1(nsel, sel); //123 ns中共有 个STU(100fs) `timescale 1ps/100fs module M3(….); not #1.23 not1(nsel, sel); //1.23 ps中共有12个STU(100fs)
46
ps (皮秒) picoseconds: 1.0E-12 秒 ns (纳秒) nonoseconds: 1.0E-9 秒
语法详细讲解 编译引导语句 时间单位 : fs (呼秒)femptoseconds: E 秒 ps (皮秒) picoseconds: E 秒 ns (纳秒) nonoseconds: E 秒 us (微秒) microseconds: E 秒 ms (毫秒) milliseconds: E 秒 s ( 秒) seconds: 秒
47
如果该引导语句启动的话,它就一直有效 直到遇到另外一个`uselib的定义或`resetall语句
语法详细讲解 编译引导语句 `uselib 编译引导语句: 用于定义仿真器到哪里去找库元件 如果该引导语句启动的话,它就一直有效 直到遇到另外一个`uselib的定义或`resetall语句 比其他配置库搜索路径的命令选项作用大 如果仿真器在`uselib定义的地点找不到器件库,它不会转向由编译命令行-v 和-y选项指定的器件库去找。
48
`uselib 器件库1的地点 器件库2的地点 。。。
语法详细讲解 编译引导语句 使用 `uselib 的语法: `uselib 器件库1的地点 器件库2的地点 。。。 上面的器件库地点可用以下两种方法表示: 1) file = 库文件名的路径 2) dir = 库目录名的路径 libext = .文件扩展 例如: `uselib dir =/lib/FAST_lib/ `uselib dir =/lib/TTL_lib/ libext=.v file = /libs/TTL_U/udp.lib
49
语法详细讲解 第五部分 Verilog 的数据类型和逻辑值
目的: 掌握Verilog不同逻辑值的含义 学习Verilog不同的数据类型 理解如何使用和在什么场合下使用不同的数据类型 学习声明数据类型的语法
50
1 X Z 语法详细讲解 Verilog 的四种逻辑值 buf bufif1 0、低、伪、逻辑低、地、VSS、负插入
1 X Z buf bufif1 0、低、伪、逻辑低、地、VSS、负插入 1、高、真、逻辑高、电源、VDD、正插入 X、不确定:逻辑冲突无法确定其逻辑值 HiZ、高阻抗、三态、无驱动源
51
语法详细讲解 主要的数据类型 Verilog 有三种主要的数据类型: Nets 表示器件之间的物理连接, 称为网络连接类型 Register 表示抽象的储存单元,称为寄存器/变量类型 Parameter 表示运行时的常数,称为参数类型
52
nets a b 语法详细讲解 主要的数据类型 Nets(网络连线): 由模块或门驱动的连线。 驱动端信号的改变会立刻 sela
传递到输出的连线上。 例如:右图上,selb的改 变,会自动地立刻影响或 门的输出。 a b sl selb sela nsl out nets
53
连接(Nets) 类型变量的种类: 语法详细讲解 主要的数据类型 类型 功能 wire, tri 对应于标准的互连线(缺省)
在为不同工艺的基本元件建立库模型的时候,常常需要用不同的 连接类型来与之对应,使其行为与实际器件一致。常见的有以下 几种。 类型 功能 wire, tri 对应于标准的互连线(缺省) supply1, supply2 对应于电源线或接地线 wor, trior 对应于有多个驱动源的线或逻辑连接 wand, triand 对应于有多个驱动源的线与逻辑连接 trireg 对应于有电容存在能暂时存储电平的连接 tri1, tri 对应于需要上拉或下拉的连接 如果不明确地说明连接是何种类型,应该是指 wire 类型。
54
a 语法详细讲解 主要的数据类型 sela sl nsl out selb b 寄存器(register)类型变量
常用行为语句结构来给寄存器类型的变量赋值。 a b sl selb sela nsl out reg_a reg_sel reg_b
55
寄存器(register)类型变量的数据类型
语法详细讲解 主要的数据类型 寄存器(register)类型变量的数据类型 寄存器类型变量共有四种数据类型: 类型 功能 reg 无符号整数变量,可以选择不同的位宽。 integer 有符号整数变量,32位宽,算术运算可产生2的 补码。 real 有符号的浮点数,双精度。 time 无符号整数变量,64位宽(Verilog-XL仿真 工具用64位的正数来记录仿真时刻)
56
如何选择正确的数据类型? 语法详细讲解 主要的数据类型 输入口(input)可以由寄存器或网络连接驱动,但它本身只能驱动网络连接。
输出口 (output)可以由寄存器或网络连接驱动,但它本身只能驱动网络连接。 输入/输出口(inout)只可以由网络连接驱动,但它本身只能驱动网络连接。 如果信号变量是在过程块 (initial块 或 always块)中被赋值的,必须把它声明为寄存器类型变量
57
举例说明数据类型的选择 语法详细讲解 主要的数据类型 module top; wire y; reg a, b;
DUT u1(y,a,b); initial begin a = 0; b = 0; #10 a =1; …. end endmodule 模块DUT的边界 输入口 输出口 输出/入口 net net/register inout module DUT(Y, A, B_); output Y; input A,B: wire Y, A, B; and (Y, A, B); endmodule
58
这是经常犯的三个错误!!! 选择数据类型时常犯的错误 语法详细讲解 主要的数据类型 在过程块中对变量赋值时,忘了把它定义为寄存器
类型(reg)或已把它定义为连接类型了(wire) 把实例的输出连接出去时,把它定义为寄存器类型了 把模块的输入信号定义为寄存器类型了。 这是经常犯的三个错误!!!
59
语法详细讲解 主要的数据类型 参数(parameters)类型 常用参数来声明运行时的常数。
可用字符串表示的任何地方,都可以用定义的参数来代替。 参数是本地的,其定义只在本模块内有效。 举例说明: module md1(out,in1,in2); ….. parameter cycle=20, prop_del=3, setup=cycle/2-prop_del, p1=8, x_word=16’bx, file = “/user1/jmdong/design/mem_file.dat”; wire [p1:0] w1; //用参数来说明wire 的位宽 …. initial begin $open(file); ……. # display(“%s”,file); $stop end endmodule
60
语法详细讲解 主要的数据类型 参数值的改写(方法之一) 举例说明: module mod ( out, ina, inb); …
parameter cycle = 8, real_constant=2.039, file = “/user1/jmdong/design/mem_file.dat”; endmodule module test; mod mk(out,ina,inb); defparam mk.cycle=6, mk.file=“../my_mem.dat”;
61
参数值的改写(方法之二) 语法详细讲解 主要的数据类型 举例说明: module mod ( out, ina, inb); …
parameter cycle = 8, real_constant=2.039, file = “/user1/jmdong/design/mem_file.dat”; endmodule module test; mod # (5, 3.20, “../my_mem.dat”) mk(out,ina,inb);
62
Verilog 语言支持寄存器阵列的声明:
语法详细讲解 寄存器阵列 Verilog 语言支持寄存器阵列的声明: 举例说明: integer NUMS [7:0]; // 8个整型变量的寄存器阵列 time t_vals [3:0]; //4个时间变量的寄存器阵列 数据类型为 reg 的阵列常称为存储器(即 memory): reg [15:0] MEM [0:1023]; // 1K x 16 位的存储器 reg [7:0] PREP [‘hfffe : ‘hffff]; // 2 x 8 位的存储器 可以用参数来表示存储器的大小: parameter wordsize = 16; parameter memsize = 1024; reg [wordsize-1:0] MEM3[memsize-1:0];
63
目的: 语法详细讲解 第十一部分 用文本接口的调试
语法详细讲解 第十一部分 用文本接口的调试 目的: 在本节中我们将学习如何用Verilog-XL提供的TUI(用户文本接口)来调试所设计的模块 进入交互方式的仿真模式 控制和观察仿真 浏览设计的各个层次 设置仿真的检查断点、退出仿真
64
语法详细讲解
65
语法详细讲解
66
语法详细讲解 第十四部分 Verilog测试模块的编写
目的: 复习如何编写较复杂的测试文件,对所做的设计 进行完整的测试和验证。 掌握组织模块测试的常用方法;学会编写常用的 测试代码。
67
注:虚线表示编译器能检查输入文件的可读性和是否存在以及是否允许生成输出文件
语法详细讲解 用Verilog设计的步骤 include 文件 仿真器 厂家元件 库文件 编译器 设计文件 输入文件:激励和期望的输出信号 仿真器 输出文件:激励和实际输出的信号 注:虚线表示编译器能检查输入文件的可读性和是否存在以及是否允许生成输出文件
68
语法详细讲解 测试平台的组成 需要验证的 激励 设计 信号 简单的测试平台 激励信号 和用于验 证的结果 需要验证的 设计 数据
语法详细讲解 测试平台的组成 需要验证的 设计 激励 信号 简单的测试平台 激励信号 和用于验 证的结果 数据 需要验证的 设计 复杂的测试平台
69
语法详细讲解 并行块 module inline_tb; reg [7:0] data_bus; initial fork
语法详细讲解 并行块 在测试块中常用到fork…join块。用并行块能表示以同一个时间起点算起的多个事 件的运行,并行地执行复杂的过程结构,如循环或任务。举例说明如下: module inline_tb; reg [7:0] data_bus; initial fork data_bus= 8’b00; #10 data_bus = 8’h45; #20 repeat (10) #10 data_bus = data_bus +1; #25 repeat (5) # 20 data_bus = data_bus <<1; #140 data_bua = 8’h0f; join endmodule //这两个repeat开始执行时间不同,但能同时运行。
70
语法详细讲解 并行块 时间 data_bus 时间 data_bus 上面模块的仿真输出如下: 0 8’b0000_0000
71
语法详细讲解
72
语法详细讲解 强制激励 在一个过程块中,可以用两种不同的方式对信号变量或表达式进行连续赋值。
过程连续赋值往往是不可以综合的,通常用在测试模块中。 两种方式都有各自配套的命令来停止赋值过程。 两种不同方式均不允许赋值语句间的时间控制。 assign和deassign 适用于对寄存器类型的信号(例如:RTL级上 的节点或测试模块中在多个地方被赋值的信号)进行赋值。 initial begin #10 assign top.dut.fsml.state_reg = `init_state;
73
语法详细讲解 字符串 语法详细讲解 强制激励 语法详细讲解 强制激励
#20 deassign top.dut.fsml.state_reg; end force 和 release 用于寄存器类型和网络连接类型(例如:门级扫描寄存器的输出)的强制赋值,强制改写其它地方的赋值。 initial begin # 10 force top.dut.counter.scan_reg.q=0; # 20 release top.dut.counter.scan_reg.q; 在以上两个例子中,在10到20 这个时间段内,网络或寄存器类型的信号被强制赋值,而别处对该变量的赋值均无效。 force的赋值优先级高于assign。 如果先使用assign,再使用force对同一信号赋值,则信号的值为force所赋 的值,
74
当执行release后,则信号的值为assign所赋 的值。
语法详细讲解 强制激励 语法详细讲解 强制激励 当执行release后,则信号的值为assign所赋 的值。 如果用force对同一个信号赋了几次值,再执行release,则所有赋的值均不再存在。 可以对信号的某(确定)位、某些(确定)位或拼接的信号,使用force和release赋值;但不能对信号的可变位使用force和release 来赋值。 不能对寄存器类型的信号某位或某些位使用 assign 和deassign 来赋值。
75
语法详细讲解 建立时钟 语法详细讲解 建立时钟 虽然有时在设计中会包含时钟,但时钟通常用在测试模块中。下面
三个例子分别说明如何在门级和行为级建立不同波形的时钟模型。 [例1] 简单的对称方波时钟: reg clk; always begin #period/2 clk=0; #period/2 clk=1; end reg go; wire clk; nand #(period/2) ul (clk,clk,go); initial begin go=0; #(period/2) go=1; end 注:在有些仿真器中,如果设计所用的时钟是由与其相同抽象级别的时钟模型产生的,则仿真器的性能就能得到提高。
76
语法详细讲解 建立时钟 [例2]简单的带延迟的对称方波时钟: reg clk; initial begin clk=0; #(period)
forever #(period/2) clk=!clk end reg go; wire clk; nand #(period/2) ul (clk,clk,go); initial begin go=0; #(period) go=1; end 注:这两个时钟模型有些不同,行为描述的模型延迟期间一直是低电平,而门级描述的模型开始延迟有半个周期是不确定的。
77
语法详细讲解 建立时钟 语法详细讲解 建立时钟 [例3]. 带延迟、头一个脉冲不规则的、占空比不为1的时钟: reg clk;
initial begin #(period+1) clk=1; #(period/2-1) forever begin #(period/4) clk=0; #(3*period/4) clk=1; end reg go; wire clk; nand #(3*period/4,period/4) ul(clk,clk,go); initial begin #(period/4+1) go=0; #(5*period/4-1) go=1; end 注:这两个时钟模型也有些不同,行为描述的模型一开始就有确定的电平,而门级描述的模型有延迟, 开始时电平是不确定的。
78
语法详细讲解 怎样使用任务 语法详细讲解 怎样使用任务 举例说明如何使用任务: module bus_ctrl_tb;
reg [7:0] data; reg data_valid, data_rd; cpu ul(data_valid,data,data_rd); initial begin cpu_driver (8’b0000_0000); cpu_driver (8’b1010_1010); cpu_driver (8’b0101_0101); end
79
语法详细讲解 怎样使用任务 语法详细讲解 怎样使用任务 task cpu_driver; input [7:0] data_in;
begin #30 data_valid=1; wait(data_rd==1); #20 data=data_in; wait(data_rd==0); #20 data=8’hzz; #30 data_valid=0; end endtask endmodule
80
语法详细讲解 怎样使用任务 语法详细讲解 怎样使用任务 在测试模块中使用任务可以提高程序代码的效率,可以用任务把多次重复的操作包装起来。
cpu_data clk data_valid data_rd read_cpu_state wait wait data1 data2 wait data3 data4 wait
81
语法详细讲解 存储建模 语法详细讲解 第十五部分 存储器建模 目标 学会如何用Verilog对存储器建模。 学会如何用Verilog中对双向(即输入/输出)端口, (inout)建模。
82
存储器建模必须注意以下两个方面的问题: 声明存储器容量的大小。 明确对存储器访问操作的权限。 语法详细讲解 存储器建模
例如:指出可以对存储器做以下哪几种操作: 1)只读 2)读写 3)同步读写 4)多次读,同时进行一次写 5)多次同步读写,同时提供一些方法保证一致性
83
语法详细讲解 简单 ROM 建模 `timescale 1ns/10ps
module myrom(read_data,addr,read_en_); input read_en_; input [3:0] addr; output [3:0] read_data; reg [3:0] read_data; reg [3:0] mem [0:15]; initial $readmemb(“my_rom_data”,mem); (addr or read_en_) if(!read_en_) read_data=mem[addr]; endmodule my_rom_data 0000 0101 1100 0011 1101 0010 1111 1000 1001 0001 1010 ROM的数据存储在另外的一个独立的文件中
84
上页所示的ROM模型说明: 语法详细讲解 简单ROM建模 如何在Verilog中用二维的寄存器组来定义存储器。
85
语法详细讲解 简单RAM建模 `timescale 1ns/1ns module mymem(data,addr,read,write); inout [3:0] data; inout [3:0] addr; input read, write; reg [3:0] memory [0:15]; //4 bits, 16 words //从存储器读出到总线上 assign data=read? memory[addr]:4’bz; //从总线写入存储器 (posedge write) memory[addr]=data; endmodule
86
语法详细讲解 简单RAM建模 RAM模型比ROM模型稍微复杂: 它必须具有读写能力; 进行读写时通常使用相同的数据总线;
需要新技术来处理双向总线; 当读信号无效时,RAM模型与总线脱离,如果此时写 信号也无效,总线无驱动源,则总线进入高阻状态, 这就避免了RAM中的读写竞争。 上页的 RAM 模块是可综合的,但综合出来是一大堆寄存器,占比较大的面积,经济上不太合算。
87
语法详细讲解 存储量可变的只读存储器建模 例: module scalable_ROM (mem_word, address);
parameter addr_bits=8; //size of address bus parameter wordsize=8; //width of a word parameter words=(1<<addr_bits); //size of mem output [wordsize:1] mem_word; //word of memory input [addr_bits:1] address; //address bus reg [wordsize:1] mem [0 : words-1]; //mem declaration //output one word of memory wire [wordsize:1] mem_word=mem[address]; endmodule
88
上述的例子演示了怎样通过设置字长和地址位数来编 写 只读存储器的行为模块。
语法详细讲解 存储量可变的只读存储器建模 上述的例子演示了怎样通过设置字长和地址位数来编 写 只读存储器的行为模块。 [注意] !! 在上例中,存储字的范围从0开始的,而不是从1开始,这是因为存储单元是直接通过地址线寻址定位的。 同样地,也可以用下面的方法来定义存储器和寻址: reg [wordsize:1] mem [1:words]; //存储器地址 从1 开始 //地址一个一个地增加直到包含了每个地址对应的存储器 wire [wordsize:1] mem_word = mem[address+1];
89
语法详细讲解 存储器的加载 可以在初始化块中用一个循环或系统任务把初始数据存入存储器的每个单元。 使用循环把值赋给存储器数组。
for(i=0;i<memsize;i=i+i) // initialize memory mema[i]={wordsize{1’b1}}; 调用$readmem系统任务。 //从文件 mem_file.txt 中, 把初始数据存入存储器(mem)的每个单元 $readmemb(“mem_file.txt”,mem); 注意:上面两项必须写 在initial 块中,加载这些初始化数据不需要时间。
90
语法详细讲解 怎样使用双向口 使用inout关键字声明端口为双向口。 inout [7:0] databus; 使用双向口必需遵循下面的规则: inout口只能声明为网络连接类型, 不允许把它声明为寄存器类型。(所以仿真器能确定多个驱动源的最终值。) 在设计中,每次只能从一个方向来驱动inout口。 例如:当使用总线读RAM中的数据时,如果同时又向RAM模型的双向数据总线写数据,就会产生逻辑竞争,导致总线数据无法确定。所以必须为inout口设计控制逻辑,只有这样才能保证正确的操作。
91
语法详细讲解 怎样使用双向口 [注意]: 声明一个inout口,可以用来输入或输出数据。inout口默认为网络连接类型。不允许在过程块(initial 或always块)中对网络连接类型的数据进行过程赋值;但可以在过程块外把一个寄存器数据类型通过连续赋值语句赋给它(inout口),或者把它与用户定义的源语(UDP)相连。 必须为inout口设计控制逻辑,用来保证正确的操作。当把inout口作为输入口时,必须通过控制逻辑禁止输出到inout口。
92
语法详细讲解 双向口建模 en_a_b bus_a bus_b en_b_a 使用Verilog中的基本元件(bufif1)为双向口建模:
module bus_xcvr (bus_a,bus_b,en_a_b,en_b_a); inout bus_a,bus_b; input en_a_b,en_b_a; bufifl b1(bus_b,bus_a,en_a_b); bufifl b2(bus_a,bus_b,en_b_a); //结构模块逻辑 endmodule 当en_a_b=1时,元器件b1激活,bus_a的值传到bus_b上 当en_b_a=1时,元器件b1激活,bus_b的值传到bus_a上
93
所以必须把控制信号 en_a_b 和 en_b_a 在时间上分开。
语法详细讲解 双向口建模 [注意]: 在上页的例子中,使用en_a_b和en_b_a 来控制元器件bufifl,如果控制信号同时有效,则结果无法确定。 所以必须把控制信号 en_a_b 和 en_b_a 在时间上分开。
94
语法详细讲解 双向口建模 使用连续赋值为双向口建模: en_a_b bus_a bus_b en_b_a module bus_xcvr
(bus_a,bus_b,en_a_b,en_b_a); inout bus_a,bus_b; input en_a_b,en_b_a; assign bus_b=en_a_b? bus_a:’bz; assign bus_a=en_b_a? bus_b:’bz; //结构模块逻辑 endmodule 当en_a_b=1时,bus_a的值传到bus_b上 当en_b_a=1时,bus_b的值传到bus_a上
95
语法详细讲解 双向口建模 [注意]: 在assign语句中,通过en_a_b和en_b_a控制bus_a与bus_b之间的数据交换。 如果控制信号同时有效,则结果不能确定。所以必须把控制信号 en_a_b 和 en_b_a 在时间上分开。
96
语法详细讲解 双向口建模 存储器的端口建模: module ram_cell(databus,rd.wr); inout databus;
测试模块 RAM单元 数据总线 数据 寄存 器 rd wr module ram_cell(databus,rd.wr); inout databus; input rd,wr; reg datareg; assign databus=rd? datareg:’bz; wr) datareg<=databus; endmodule 当rd等于1时datareg的值被赋给databus 当wr的下降沿到达时,databus的值被写入datareg
97
语法详细讲解 双向口建模 [注意]: 上页中存储单元在wr的下降沿到达时存入数据。上页模块在 wr处于高电平时,通过数据总线写入数据,但必须保证wr的高电平维持时间长于数据的写入时间。 在rd处于高电平时,上述存储单元通过数据总线读出数据。由于此模型为单口存储模型,因此wr变低电平时,rd不能同时为高电平,否则就无法确定存储器的读出/写入的结果。
98
语法详细讲解 第十六部分 Verilog中的高级结构
目标: 学会怎样定义或调用任务和函数。 学会怎样使用命名块。 学会怎样禁止命名块和任务。 理解有限状态机的作用,学会如何显式地为有限状态机建模。
99
语法详细讲解 Verilog中的高级结构 通过把代码分成小的模块或者使用任务和函数,可把一项任务分成许多较小的、易于管理的部分,从而提高代码的可读性、可维护性和可重用性。 任务 一般用于编写测试模块,或者行为描述的模块。 其中可以包含时间控制(如:# wait); 也可以包含input, output 、inout 端口定义和参数; 也可以调用其他的任务或函数 函数 一般用于计算,或者用来代替组合逻辑。 不能包含任何延迟;函数在零时间执行。 函数只有input变量,虽然没有output变量, 但可以通过函数名返回一个值。 可以调用其他的函数,但不可以调用任务
100
语法详细讲解 Verilog中的高级结构 [注意]: 只能调用本模块内的任务和函数。 在任务和函数中不能声明网络连接类型的变量。 所有的输入和输出变量实际上都是本地寄存器 。 只有当任务或函数调用并执行完后,才能有返回值。 [举例说明]: 若任务或函数中包含一个forever循环时,永远无法执行完,就不可能有返回值。
101
下面模块中的任务含有定时控制和一个输入,并且引用了一个本模块的变量,但是没有输出,也没有双向总线和内部变量,不显示任何内容。
语法详细讲解 Verilog 任务 下面模块中的任务含有定时控制和一个输入,并且引用了一个本模块的变量,但是没有输出,也没有双向总线和内部变量,不显示任何内容。 用于定时控制的信号,例如 clk,绝对不能作为任务的输入,这是因为输入值只向任务内部传递一次。 module top; reg clk, a, b; DUT u1(out, a, b, clk); always #5 clk=!clk;
102
语法详细讲解 整数和实常数 语法详细讲解 Verilog 任务 task neg_clocks;
input [31:0] number_of_edges; repeat(number_of_edges) @(negedge clk); endtask initial begin clk=0; a=1; b=1; neg_clocks(3); //任务调用 a=0; neg_clocks(5); b=0; end endmodule
103
语法详细讲解 Verilog 任务 要点: 任务调用是通过在Verilog模块中写入任务名来实现的。
任务中可以包含input, output和inout端口变量的声明。 传递给任务的变量与任务I/O端口变量的声明次序相同。虽然传递给任务的变量名可以和任务内声明的I/O端口变量名相同,但是为了使任务成为一个独立的可共用的任务块,建议不要使用与任务内声明的I/O端口变量名相同的变量名,最好给传递到任务的变量起新的不同的名字。 在任务中可以使用时间控制。 任务使Verilog有更广阔的适用范围。 关键字disable可以用来禁止任务的执行。
104
语法详细讲解 Verilog 任务 [注意]: 不要在程序的不同部分同时调用同一个任务。这是因为任务只有一组本地变量,同一时刻调用两次相同的任务将会导致错误。这种情况常发生在使用定时控制的任务中。 在任务或函数中,引用父模块中声明的变量时要特别注意(即注意变量的层次命名规则)。若想在其它模块中调用任务或函数,该任务和函数中所使用的变量必须全都包含在输入/输出口列表中。
105
语法详细讲解 Verilog 任务 下面模块中的任务只含有一个双向总线(inout)端口和一个内部变量,没有其它输入端口、输出端口和定时控制,没有引用模块变量,不显示任何内容。 在任务调用时,任务的输入变量(端口)在任务内部被当作寄存器类型变量处理。 parameter MAX_BITS=8; reg [MAX_BITS:1] D; task reverse_bits; inout [7:0] data; //双向总线端口被当作寄存器类型! integer K; for (k=0; k<MAX_BITS; K=K+1) reverse_bits [MAXBITS – (K+1)] = data[K]; endtask (posedge clk) reverse_bits (D); ……
106
语法详细讲解 Verilog 任务 下面模块中定义的任务含有输入、输出、时间控制和一个内部变量,并且引用了一个本模块的变量,但是没有输出,不显示任何内容。 任务调用时变量顺序应与任务定义中声明的顺序相同。 module mult(clk, a, b, out, en_mult); input clk, en_mult; input [3:0] a, b; output [7:0] out; reg [15:0] out; (posedge clk) multme(a, b, out); //任务调用
107
语法详细讲解 Verilog 任务 task multme; //任务定义 input [3:0] xme, tome;
output [7:0] result; wait (en_mult) result=xme*tome; endtask endmodule
108
语法详细讲解 Verilog 函数 module orand (a, b, c, d, e, out);
input [7:0] a, b, c, d, e; output [7:0] out; reg [7:0] out; (a or b or c or d or e) out = f_or_and (a, b, c, d, e); //函数调用 function [7:0] f_or_and; if (e= =1) f_or_and = (a|b) & (c|d); else f_or_and=0; endfunction endmodule
109
语法详细讲解 Verilog 函数 虽然函数不能包含定时控制,但是可以在包含定时控制的过程块中调用函数。
在模块中,使用名为f_or_and的函数时,是把它作为名为f_or_and 的寄存器类型变量来处理的。 要点 函数定义不能包含任何定时控制语句。 函数必须至少有一个输入,但绝不能含有任何输出和总线口; 一个函数只能返回一个值,该值的变量名与函数同名,数据类型默认为reg类型。 传递给函数的变量顺序与函数输入口声明的顺序相同。 函数定义必须包含在模块定义之内。 函数不能调用任务,但任务可以调用函数。 函数使Verilog有更广阔的适用范围。
110
语法详细讲解 Verilog 函数 虽然函数只能返回一个值,但是它的返回值可以直接赋给一个由多个子信号拼接构成的信号变量, 使其实际等效于产生了多个输出。 {o1, o2, o3, o4}=f_or_and(a, b, c, d, e);
111
语法详细讲解 Verilog 函数 module foo;
在函数定义时,如果在函数名前面定义了位宽,该函数就可以返回由多位构成的矢量。如果定义函数的语句比较多时,可以用 begin 和end 把它们组合起来。 在函数内,无论以函数名命名的变量被赋了多少次值,函数只有一个返回值。 下例中的函数,声明了一个内部整型变量。举例说明如下: module foo; input [7:0] loo; //也可以用连续赋值语句调用函数 wire [7:0] goo = zero_count (loo); function [3:0] zero_count; input [7:0] in_bus; integer I; begin zero_count = 0; for (I=0; I<8; I= I+1) if (!in_bus[I]) zero_count = zero_count +1; end endfunction endmodule
112
语法详细讲解 Verilog 函数 module checksub(neg,in_a,in_b); output neg;
若把函数定义为整型、实型或时间类型, 就可以返回相应类型的数据。我们可以在任何类型的表达式中调用函数。 module checksub(neg,in_a,in_b); output neg; input a, b; reg neg; function integer subtr; input [7:0] in_a, in_b; subtr = in_a – in_b; //运算结果可以为负数 endfunction (a or b) begin if ( subtr (a,b) <0) neg = 1; else neg = 0; end endmodule
113
语法详细讲解 Verilog 函数 …. parameter MAX_BITS =8; reg [MAX_BITS:1] D;
函数类型、端口和行为定义时也可以使用参数, 这样就可以构成参数化函数使其返回的数据类型、输入端口的位宽等很容易做修改。所以参数化函数就有更广泛的适用范围。 …. parameter MAX_BITS =8; reg [MAX_BITS:1] D; function [MAX_BIT:1] reverse_bits; input [7:0] data; for(K=0; K< MAX_BITS; K=K+1) reverse_bits[ MAX_BITS – (K+1)] = data [K]; endfunction (posedge clk) begin …. D= reverse_bits(D); ….. end ………
114
语法详细讲解 命名块 可以通过在关键字begin或fork后加上:〈块名〉来给块命名。 module named_blk; ……
begin :seq_blk end fork : par_blk join endmodule 可以在命名块中声明本地变量。 可以使用disable禁止命名块。
115
语法详细讲解 命名块 注意: 命名块使Verilog有更广阔的适用范围。 命名块的使用缩短了仿真的时间。
116
语法详细讲解 禁止命名块和任务 module do_arith(out, a, b, c, d, e, clk, en_mult);
input clk, en_mult; input [7:0] a, b, c, d, e; output [15:0] out; reg [14:0] out; clk) begin : arith_block //***命名名为arith_block的块*** reg [3:0] tmp1, tmp2; //***本地变量*** {tmp, tmp2}=f_or_and(a, b, c, d, e); // 函数调用 if(en_mult) multme(tmp1, tmp2, out); //任务调用 end en_mult) begin //停止计算 disable multme; //***禁止任务的执行*** diable arith_block; //***禁止命名块的执行*** //在此定义任务和函数 ………….. endmodle
117
语法详细讲解 禁止命名块和任务 注意: disable语句用来终止命名块或任务的执行。这是指在尚未执行该命名块或任务任何一条语句前,就从该命名块/任务执行中返回。 语法: disable 〈块名〉 或 disable 〈任务名〉 禁止执行命名块或任务后,所有在事件队列中由该命名块/任务安排的事件都将被删除。 一般 情况下disable语句是不可综合的。 在上页的例子中,只禁止命名块也可以得到预期的结果:命名块中所有的事件,包括任务和函数的执行都将被取消。
118
语法详细讲解 有限状态机(FSM) 隐式FSM: state 1 不需要状态寄存器 仿真更加有效 只能很好地处理线性的状态改变
119
语法详细讲解 编译引导语句 语法详细讲解 有限状态机(FSM) 显式FSM: 结构比较复杂 可以很方便的用来处理默认状态
能够处理复杂的状态改变 所有的综合工具均支持显式FSM 的综合 state A state B1 state B2 state C state D
120
语法详细讲解 有限状态机(FSMs) 注意:
在隐式状态机中,只要发生在一个时钟周期内写数据,在另一个时钟周期内读数据的情况,都会生成寄存器。 任何状态机都必须有复位控制信号,状态的改变必需只与某单一时钟信号沿同步。 一般情况下,如果状态改变比较简单,又定义得比较好,而且综合工具支持隐式状态机的综合,就可以使用隐式状态机。如果状态改变比较复杂,最好使用显式状态机,这样效果更好。 隐式状态机属于行为级,不属于RTL级。代码中主要包含循环语句、嵌入的定时控制,有时也含有命名事件、wait 和 disable 语句。一般情况下,常用的综合工具不支持隐式状态机的综合。
121
1 语法详细讲解 显式有限状态机 语法详细讲解 显式有限状态机 module exp(out, datain, clk, rst);
1 datain = 0 datain = 1 module exp(out, datain, clk, rst); input clk, rst, datain; output out; reg out; reg state; clk or posedge rst) if(rst) {state, out}=2’b00; else case(state) 1’b0: begin out=1’b0; if(!datain) state=1’b0; else state=1’b1; end 1’b1 begin 状态变量 case语句
122
语法详细讲解 显式有限状态机 out=datain; state=1’b0; end
default: {state, out}=2’b00; endcase endmodule 注: 在过程块中可以使用一个时钟沿和 case 语句来描述一个显式状态机。 必须指定一个状态变量,来记录状态机的状态。 要改变当前的状态,必须改变状态变量的值, 其改变要与时钟沿同步。 写得比较好的状态机常为不应产生的条件规定一个默认动作。 转到下一个状态 默认状态指针 1 识别11序列 clk rst out
123
语法详细讲解 隐式有限状态机 begin: seq_block out=1’b0; if(!datain) //状态一:输出零
disable seq_block; @(posedge clk) //状态二:输出第二位 out=datain; end endmodule 1 识别11序列 clk rst out
124
语法详细讲解 隐式有限状态机 注意: 在过程块中可以使用多个时钟沿(即每次状态改变都用一个新的时钟沿)、条件语句、循环语句、disable语句来描述隐式FSM。隐式FSM往往是不可综合的。 隐式FSM不必指定状态变量。 当下一个激活时钟沿到达时,状态就有可能发生改变。下一个状态是否改变,将由条件语句决定;除非用强制性语句使状态重复(例如:用循环语句或用disable语句来强制改变状态), 在隐式状态机中,很难规定一个默认动作。
125
目标 语法详细讲解 第十七部分 用户定义的原语 学会怎样使用用户定义的原语来创建逻辑。
语法详细讲解 第十七部分 用户定义的原语 目标 学会怎样使用用户定义的原语来创建逻辑。 用户定义的源语元件 (UDP) 其行为与 Verilog 语法中本来就存在的primitive(源语元件)相似,它用一个表格来定义它的逻辑功能。
126
语法详细讲解 什么是UDP? 在 Verilog 结构建模时,可以使用: 二十多个门级源语元件(primitives)。
UDP 可用于ASIC 库中的基本元件(cell)设计,以及小规模芯片和中规模芯片的设计。 使用 UDP可以在现有的Verilog 语言支持的源语元件的基础上编写新的源语元件。 UDP 是一个独立元件, 不能用实例调用的方法调用其他的模块。 UDP 既可以用来表示时序逻辑元件,也可以表示组合逻辑元件。 UDP 的行为是使用真值表来描述的 。 调用 UDP 的方式与调用Verilog语言提供的源语元件的方式相同。
127
注意: 语法详细讲解 什么是UDP? UDP 是一种紧凑的表示简单逻辑关系部件的方法。
在Verilog语言提供的几种基本源语元件中,若在输入中包含不确定值x,则在输出时可能出现不确定值 x;而在 UDP 中则不允许出现此种情况。 由几个原语元件组成的逻辑可以用一个UDP表示。在仿真时使用这样的UDP来代替分散的原语元件可以节省计算资源,加快仿真速度。一般的仿真器处理行为模型表示的逻辑所需时间比处理用门级语句表示的相同逻辑所需时间少;而硬件仿真器正好相反。
128
语法详细讲解 UDP的特点 UDP 只能有一个输出端,而且必须是端口说明列表的第一项。 UDP 可以有多个输入端,最多允许有 10 个。 UDP 所有端口变量必须是标量,不允许使用双向端口。 UDP 不支持Z(高阻)逻辑值。 在仿真的开始时刻,可以使用 initial 语句把UDP 的输出初始化为一个已知值。 UDP 不支持综合,即不能通过综合把它转变为门级结构逻辑。
129
语法详细讲解 UDP的特点 注: UDP 只能有一个输出。如果逻辑功能要求有多个输出端时,则需要把其它的原语元件连接到 UDP的 输出,或同时使用多个 UDP,保证其最终输出只有一个。 UDP 输入端最多可以有 10 个,但是当输入端的个数多于 5 个时,仿真时需要的内存个数将呈现近似指数的增加。下表列出了当输入数目不同时,在仿真过程中,对每个输入信号,计算机中所需要开销的内存数目。
130
语法详细讲解 UDP的特点 输入端口的个数 所需内存的字节数 1-5 <1 6 5 7 17 8 56 9 187 10 623
131
语法详细讲解 举例说明 组合逻辑示例:2-1 多路器 原语名 primitive multiplexer(o, a, b, s);
output o; input s, a, b; table // a b s : o 0 ? 1 : 0; 1 ? 1 : 1; ? : 0; ? : 1; 0 0 x : 0; 1 1 x : 1; endtable endprimitive 输出端口必须为第一个端口
132
语法详细讲解 举例说明 注: 在模块外定义 UDP 。 如果在表中没有规定输入组合,将输出不确定逻辑值 (x)。
表的列中元素的顺序应与端口列表中的一致。 表中的 ?的意义是:重复的输入 0,1或 任意不确定逻辑值(x)。 表中开始两行表示:当 s等于 1 时,不管 b 逻辑值如何变化,输出 o 将与 输入 a 保持一致。 表中的下两行表示:当 s 等于 0 时,不管 a逻辑值如何变化,输出 o 将与输入 b 保持一致。 表中 的最后两行使此器件的描述更加的全面、准确。它们表示:当输入 a 和 b 的逻辑值相同时,如果 sel 逻辑值不确定,则输出 o 的值 将与输入 a 和 b 的值相同。这种行为不能使用 Verilog 语言提供的基本源语元件进行建模。UDP 将 x 作为实际的未知值,而不是 Verilog 语言逻辑值来进行处理,因此使其比Verilog语言提供的基本源语元件更加准确。
133
语法详细讲解 组合逻辑示例:全加器 可以只使用两个 UDP 来描述全加器的逻辑功能。 // 全加器进位实现部分
primitive U_ADDR2_C (CO, A, B, CI); output CO; input A, B, CI, table // A B CI : CO ? : 1; 1 ? 1 : 1; ? : 1; ? : 0; 0 ? 0 : 0; ? : 0; endtalbe endprimitive 向上一级进位 下一级来的进位
134
语法详细讲解 组合逻辑示例:全加器 //全加器求和实现部分 primitive U_ADDR2_S(S, A, B,CI);
output S; input A, B, CI; table // A B CI : S : 0; : 1; : 1; : 0; : 1; : 0; : 0; : 1; endtable endprimitive
135
语法详细讲解 组合逻辑示例:全加器 若使用 UDP 设计全加器,仅需要两个 UDP; 而使用 Verilog 原语元件,则需要 5 个Verilog语言提供的基本原语元件。 当设计需要使用大量全加器时,采用UDP来表示全加器,将大大减少内存的需要。 事件的数目将大大降低。 ?表示逻辑值可以为 0,1或 x。
136
语法详细讲解 电平敏感的时序逻辑示例:锁存器
primitive latch(q, clock, data); output q; reg q; input clock, data; initial q=1’b1; table // clock data current next // state state : ? ; : ? ; ? : : ; endtable endprimitive 注意此寄存器的用法,此寄存器用来存储。 输出初始化为 1‘b1. ? 表示无须考虑输入和当前状态的值
137
语法详细讲解 电平敏感的时序逻辑示例:锁存器
注: 锁存器的动作行为如下: 当时钟信号为 0时,输入数据的值直接传给输出。 当时钟信号为1时,输出保持当前状态不变。 next state 栏中的 “-” 表示输出保持不变。 输出必须定义为寄存器类型,用来保存前一个状态。 initial q=1’b1; 是时序 UDP 的初始化语句。使用此语句可以在仿真的开始对输出进行赋值。 在实际的部件模型中,很少使用初始赋值。但在测试 UDP 的功能时,初始赋值相当有用。
138
语法详细讲解 电平触发时序逻辑的示例:锁存器
注: 锁存器的动作行为如下: 当时钟信号为 0时,输入数据的值直接传给输出。 当时钟信号为1时,输出保持当前状态不变。 next state 栏中的 “-” 表示输出保持不变。 输出必须定义为寄存器类型,用来保存前一个状态。 initial q=1’b1; 是时序 UDP 的初始化语句。使用此语句可以在仿真的开始对输出进行赋值。 在实际的部件模型中,很少使用初始赋值。但在测试 UDP 的功能时,初始赋值相当有用。
139
语法详细讲解 跳边沿敏感的时序逻辑示例:D 触发器
primitive d_edge_ff (q, clk, data); output q; input clk, data; reg q; table // clk dat state next (01) 0 : ? : 0; (01) 1 : ? : 1; (0x) 1 : 1 : 1; (0x) 0 : 0 : 0; (x1) 0 : 0 : 0; (x1) 1 : 1 : 1;
140
语法详细讲解 跳边沿敏感的时序逻辑示例:D 触发器
// 忽略时钟的下降沿 (?0) ? : ? : -; (1x) ? : ? : -; // 忽略时钟稳定时的数据改变 endtable endprimitive 在大多数情况下,可以在任何表入口语句中规定一个输入过渡。 如果规定了任何输入过渡,则必须规定所有输入的所有过渡。
141
语法详细讲解 第十八部分 可综合风格的Verilog建模类型
可综合建模类型只有两种: 组合逻辑: 任何时候,如果输出信号直接由当前的输入信号的组合决定,则此逻辑为组合逻辑。 时序逻辑: 如果逻辑中具有记忆功能,则此逻辑为时序逻辑。在任何给定的时刻,如果输出不能完全由输入信号确定,则此逻辑具有记忆功能。
142
语法详细讲解 不能综合的 Verilog结构
initial 循环语句: repeat forever while for 的非结构用法 一部分数据类型 event real time
143
语法详细讲解 不能综合的 Verilog 结构
UDPs fork…join 块 wait 过程连续赋值语句 assign 和 deassign force 和 release 部分操作符 = = = != =
144
由输入信号中任意一个电平发生变化所引起的过程块:
语法详细讲解 过程块 由输入信号中任意一个电平发生变化所引起的过程块: 由输入信号中的某一个电平发生变化启动的过程块,可以通过综合产生组合逻辑。该过程块称为组合块。如下例所示: or b) // 实现与门 y=a&b; 由单个跳变沿引起的过程块: 由控制信号的跳变沿(下降沿或上升沿)启动的过程块通过综合可以生成同步逻辑。该过程块称为同步块。如下例所示: clk) //实现 D 触发器 q<=d;
145
在同步块中可以添加异步复位,举例说明如下:
语法详细讲解 过程块 在同步块中可以添加异步复位,举例说明如下: clk or negedge rst_) if(!rst_) q<=0; else q<=d;
146
语法详细讲解 过程块中寄存器类型的信号变量
在同步块中使用 reg 类型变量: 如果在一个时钟沿对reg变量赋值,而在下一个时钟沿对其采样,则综合器把该reg变量转换为硬件寄存器。 如果只把reg变量作为基本输出,则综合器不一定把它转换为硬件寄存器。 如果不属于上述两种情况,同步块中的reg变量有可能被优化掉。 在组合块中使用 reg 类型变量: 当组合块中任何一个输入变量的值改变时,reg变量的值也随之改变,则综合器不会把该reg变量转换为硬件寄存器。 当块的某一个输入的值改变时,reg变量的值不一定立即改变,而要等其他输入信号的值改变时才改变,则综合器将把该reg变量转换为锁存器。
147
语法详细讲解 寄存器 同步寄存器示例: 在下面的例子中,rega 仅用作临时存储器,因此在综合时它将被优化掉。
module ex1reg(d, clk, q); input d, clk; output q; reg q, rega; clk) begin rega = 0; if(d) rega = 1; q = rega; end endmodule
148
语法详细讲解 寄存器 在下面的例子中,用两个always块,它们的触发条件是相同的:即
用同一时钟沿来处理两个存储元素,这样就可以使综合器在综合过 程中保留rega,使它不被优化掉。 module ex2reg(d, clk, q); input d, clk; output q; reg q, rega; clk) begin rega=0; if(d) rega=1; end
149
语法详细讲解 寄存器 always @(posedge clk) q = rega; endmodule
注:在后面的always块中,块执行的顺序是不确定的,因此 q 可以获得在前一个周期中赋给 rega 的值。
150
组合寄存器示例: 下面的两个例子中,rega 都是临时变量,在
语法详细讲解 寄存器 组合寄存器示例: 下面的两个例子中,rega 都是临时变量,在 综合中它们都会被优化掉。在本例中,y和rega 不断被赋新值(因 为语句中有else rega = 0;),综合出的电路是一个纯组合逻辑。 module ex3reg(y, a, b, c); input a, b, c; output y; reg y, rega; or b or c) begin
151
语法详细讲解 寄存器 if(a&b) rega=c; else rega=0; y=rega; end endmodule
在下面的例子中,rega 只是有时被赋新值 (没有else 语句,rega在条 件不符合时保持原值);因此综合出来的是一个以 y 作为输出的锁存 器。
152
语法详细讲解 寄存器 moudule ex4reg(y, a, b, c); input a, b, c; output y;
reg y, rega; or b or c) begin if(a&b) rega=c; y=rega; end endmodule
153
语法详细讲解 电平敏感列表 在下面的例子中,a、b 和 sl 均是块的输入。 在两个例子中, sl 均为 always 块的条件。
在第二个例子中, a 和 b 也用作always 块的条件。 不完整电平敏感列表: module sens(a, q, b, sl); input a, b, sl; output q; reg q; begin
154
语法详细讲解 电平敏感列表 if(!sl) q=a; else q=b; end endmodule 完整的电平敏感列表:
module sens(q, a, b, sl); input a, b, sl; output q;
155
语法详细讲解 敏感列表 reg q; always @(sl or a or b) begin if(!sl) q=a; else q=b;
end endmodule 注:在电平敏感列表中最好包括所有的输入。对于不完整的列表, 不同的综合工具处理的方法不同:一些综合工具认为不完整列表是
156
语法详细讲解 电平敏感列表 不合法的,而其他的综合工具则发出警告并将其当作完整列表处理。
因此,综合出来的电路功能可能与程序模块的描述有所不同。
157
语法详细讲解 连续赋值 用连续赋值语句表达的是:任何一个输入的改变都将立即导致输出 更新;与理想的与、或、非门的线路连接一致。
module orand(out, a, b, c, d, e); input a, b, c, d, e; output out; assign out=3&(a|b)&(c|d); endmodule
158
语法详细讲解 过程连续赋值 过程连续赋值是在过程块(always 和 initial 块)内部对寄存器类 型的变量进行的连续赋值。
module latch_quasi(q, en, d); input en, d; output q; reg q; if(en) assign q=d; else deassign q; endmodule
159
语法详细讲解 综合指令 大部分综合工具都能按照约定,正确地处理在源代码中用注释行表示的指导综合器如何转换到门级网表的指令即综合指令。
可以在 Verilog 语句之间用注释行嵌入综合指令,Verilog 仿真器运行时将忽略嵌入的指令,而当综合工具编译时,这些符合约定的综合指令是有意义的,能对综合过程进行干预和指导。 不同的综合工具有不同的综合指令集,使用综合指令为的是:使同样的RTL代码能综合出更高质量优化的门级网表。 下面列出了一部分 Cadence 综合工具支持的综合指令,它们与其他综合工具(例如:Synopsys DC)中的指令非常相似。 //ambit synthesis on //ambit synthesis off //ambit synthesis case=full、parallel、mux
160
语法详细讲解 综合指令 结构指令 //ambit synthesis architecture=cla or rpl 有限状态机指令
//ambit synthesis enum xyz //ambit synthesis stat_vector sig state_vector_flag 注:指令中通常包括综合工具或公司的名称,例如:上面指令中的ambit 表示使用的综合器是Envisia Ambit。
161
语法详细讲解 综合指令 语法详细讲解 综合指令 当Verilog 模块中case –endcase 块被综合时,有多种门级实现方法可
供选择:综合指令case 可以用来干预综合过程。让由case 指定的编 译方式被综合器优先考虑,即采用 case = 后列出的方法转换为门级 电路。case 综合指令的含义如下所示: //ambit synthesis case=parallel 对解码逻辑进行并行编译,没有优先级。 //ambit synthesis case=mux 如果库中含有多路器,则使用多路器编译解码逻辑。 //ambit synthesis case=full 不用考虑没有包含在 case 条件语句中的情形(即这些情形不会发生),这样设定,可以综合出的优化的门级逻辑,并可避免发生状态机死锁。
162
语法详细讲解 条件语句 完整条件语句 module comcase(a, b, c, d, e); input a, b, c, d;
output e; reg e; or b or c or d) case ({a,b}) 2’b11: e=d; 2’b10: e=~c; 2’b01: e=1’b0; 2’b00: e=1’b1; endcase endmodule
163
语法详细讲解 条件语句 module compif(a, b, c, d, e); input a, b, c, d; output e;
reg e; or b or c or d) if(a&b) e=d; else if (a&~b) e=~c; else if (~ a&b) e=1’b0; else if (~a&~b) e=1’b1; endmodule
164
语法详细讲解 不完整条件语句 module inccase(a, b, c, d, e); input a, b, c, d;
output e; reg e; or b or c ord) case ({a,b}) 2’b11: e=d; 2’b10: e=~c; endcase endmodule
165
语法详细讲解 不完整条件语句 module incpif(a, b, c, d, e); input a, b, c, d;
output e; reg e; or b or c or d) if (a&b) e=d; else if(a&~b) e=~c; endmodule
166
语法详细讲解 不完整条件语句 在前面所述的例子中,当 a 为 0 时,没有值赋给 e。因此,e 将保存原来的值,直到 a 变为 1。此行为与锁存器的特性相同。
167
语法详细讲解 带有缺省项的完整条件语句 module comcase(a, b, c, d, e); input a, b, c, d;
output e; reg e; or b or c or d) case ({a,b}) 2’b11: e=d; 2’b10: e=~c; default: e=‘bx; endmodule
168
语法详细讲解 带有缺省项的完整条件语句 module compif(a, b, c, d, e); input a, b, c, d;
output e; reg e; or b or c or d) if (a&b) e=d; else if (a&~b) e=~c; else e=‘bx; endmodule
169
语法详细讲解 带有缺省项的完整条件语句 在前面的例子中,虽然没有定义所有可能的选择,但为没有定义的选择定义了缺省的行为。因此,它们都是纯的组合逻辑,并没有产生额外的锁存器。
170
语法详细讲解 带有指令的完整 case 语句 module dircase(a, b, c, d) input b, c;
input [1:0] a; output d; reg d; or b or c) case (a) //ambit synthesis case = full 2’b00: d=b; 2’b01: d=c; endcase endmodule 在此例中,虽然没有定义所有可能的选择,但其中的指令通知优化器没有定义的选择将不会发生。此例为纯组合逻辑,不会产生锁存器。
171
语法详细讲解 case 指令的例外情况 当设置了 case 指令为 full 时,也可从 case 语句中综合出锁存器。示例如下:
module select ( a, b, sl); input [1:0] sl; output a, b; reg a, b; case (sl) //ambit synthesis case = full 2’b00: begin a=0; b=0; end 2’b01: begin a=1; b=1; end 2’b00: begin a=0; b=1; end
172
语法详细讲解 case 指令的例外情况 2‘b11: b=1; default: begin a=‘bx; b=‘bx; end
endcase endmodule
173
语法详细讲解 函数 函数不包含时间控制,因此它们综合为组合逻辑。可以在过程块和连续赋值语句中调用函数。
语法详细讲解 函数 函数不包含时间控制,因此它们综合为组合逻辑。可以在过程块和连续赋值语句中调用函数。 下面是 or/and 块,其中在连续赋值语句中调用了函数。 module orand(out, a, b, c, d, e); input a, b, c, d, e; output out; wire out; assign out=forand(a, b, c, d, e); function forand; if(e==1) forand(a|b)&(c|d);
174
语法详细讲解 函数 else forand=0; endfunction endmodule
175
语法详细讲解 任务 任务仅用于测试模块中,因为: 不包含时间控制的任务与函数的作用相似。 包含时间控制的任务是不可综合的。
语法详细讲解 任务 任务仅用于测试模块中,因为: 不包含时间控制的任务与函数的作用相似。 包含时间控制的任务是不可综合的。 下面的 or/and 块中调用了任务: module orandtask(out, a, b, c, d, e); input a, b, c, d, e; output out; reg out; or b or c or d or e) orand(out, a, b, c, d, e); task orand;
176
语法详细讲解 任务 output out; if(e==1) out=(a|b)&(c|d); else out=0; endtask
语法详细讲解 任务 output out; if(e==1) out=(a|b)&(c|d); else out=0; endtask endmodule
177
语法详细讲解 怎样产生锁存器 在 always 块中,如果没有规定所有的条件,则会产生锁存器。在下面的例子中,当 enable 为低电平时,没有定义怎样处理 q 和 data,因此 data 的值将会被保存下来。综合器必须使用存储元件来编译此逻辑。 module latch(q, data, enable); input data,enable; output q; reg q; or data) if(enable) q=data; endmodule
178
语法详细讲解 怎样产生同步反馈 综合工具一般只支持同步反馈,而不支持组合反馈。
语法详细讲解 怎样产生同步反馈 综合工具一般只支持同步反馈,而不支持组合反馈。 在条件语句每个分支中,当过程块没有为每个输出赋值时,就会产生反馈 不产生反馈: module dffn(q,d,clk,en); input d, clk, en; output q; reg q; clk) if(en) q<=d; else q=‘bx; endmodule 产生反馈: module dffn(q,d,clk,en); input d, clk, en; output q; reg q; clk) if(en) q<=d; endmodule
179
语法详细讲解 阻塞与非阻塞(赋值方式) 赋值的类型的选择取决于建模的逻辑类型 在时序块的 RTL 代码中使用非阻塞赋值。
语法详细讲解 阻塞与非阻塞(赋值方式) 赋值的类型的选择取决于建模的逻辑类型 在时序块的 RTL 代码中使用非阻塞赋值。 非阻塞赋值在块结束后才完成赋值操作,此赋值方式可以避免在仿真出现冒险和竞争现象。 在组合的 RTL 代码中使用阻塞赋值。 使用阻塞方式对一个变量进行赋值时,此变量的值在在赋值语句执行完后就立即改变。
180
语法详细讲解 阻塞与非阻塞(赋值方式) 使用非阻塞赋值方式进行赋值时,各个赋值语句同步执行;因此,通常在一个时钟沿对临时变量进行赋值,而在另一个时钟沿对其进行采样。 下面的模块综合为触发器, 其中采用了阻塞赋值方式: module bloc(clk,a,b); input clk, a; output b; reg b; reg y; clk) begin y=a; b=y; end endmodule 下面的模块综合为两个触发器 ,其中采用了非阻塞赋值方式: module nonbloc(clk,a,b); input clk, a; output b; reg b; reg y; clk) begin y<=a; b<=y; end endmodule
181
语法详细讲解 阻塞与非阻塞(赋值方式) 上面的两个例子的综合的结果不同,左边的例子使用了阻塞赋值方式,综合器将其综合为一个触发器。右边的例子使用了非阻塞赋值方式,综合器将其综合为两个触发器,y 将出现在综合列表中,作为第二个触发器的输入。综合结果如下所示: a y b b a clk clk
182
语法详细讲解 复位建模 复位是可综合风格代码的重要组成部分,通常在有限状态机中使用复位建模。 同步复位:
语法详细讲解 复位建模 复位是可综合风格代码的重要组成部分,通常在有限状态机中使用复位建模。 同步复位: module sync(q,ck,r,d); input ck, d, r; output q; reg q; ck) if(r) q<=0; else q<=d; endmodule 同步块中的异步复位: module async(q,ck,r,d); input ck, d, r; output q; reg q; ck or posedge r) if(r) q<=0; else q<=d; endmodule
183
语法详细讲解 复位建模 在下面的例子中,使用了一个独立的异步复位来实现异步复位。 module async(q, ck, r, d);
语法详细讲解 复位建模 在下面的例子中,使用了一个独立的异步复位来实现异步复位。 module async(q, ck, r, d); input ck, d, r; output q; reg q; ck) if(!r) q<=d; r) q<=0; endmodule 不提倡使用上述风格的异步复位代码,上述代码是不可综合的。在 仿真时,若 r 和 ck 同时在同一个时间单元中改变,则结果是不确定 的。
184
语法详细讲解 锁存器复位 下面的例子演示了更加复杂的复位建模。其中的敏感列表是完全的, 因为这是一个锁存器。
module latch(q, enable, set, clr, d); input enable, d, set, clr; output q; reg q; or set or clr or d) begin if(set) q<=1; else if (clr) q<=0; else if (enable) q<=d; end endmodule
185
语法详细讲解 阻塞与非阻塞(赋值方式)
186
语法详细讲解
187
语法详细讲解
188
语法详细讲解
189
语法详细讲解 Verilog 函数
Similar presentations