verilog hdl 基础语法入门
DESCRIPTION
Verilog HDL 基础语法入门. 第一部分 课程简介. 目的: 简单介绍 Verilog HDL 语言和仿真工具 介绍讲课计划 介绍如何不断地学习新的有关知识. 讲座中关于 Verilog HDL 的主要内容. 讲课内容主要包括: Verilog 的应用 Verilog 语言的组成部件 结构级的建模与仿真 行为级的建模与仿真 延迟参数的表示 Verilog 的测试平台: 怎样产生激励信号和控制信号 输出响应的产生、记录和验证 任务和函数 用户定义的元器件( primitives) 可综合风格的 Verilog 建模. - PowerPoint PPT PresentationTRANSCRIPT
Verilog HDL Verilog HDL
基础语法入门基础语法入门
Verilog HDL Verilog HDL
基础语法入门基础语法入门
第一部分 课程简介
目的: 简单介绍 Verilog HDL 语言和仿真工具 介绍讲课计划 介绍如何不断地学习新的有关知识
讲座中关于 Verilog HDL 的主要内容
讲课内容主要包括:Verilog 的应用Verilog 语言的组成部件 结构级的建模与仿真行为级的建模与仿真延迟参数的表示Verilog 的测试平台: 怎样产生激励信号和控制信号 输出响应的产生、记录和验证
任务和函数用户定义的元器件( primitives )可综合风格的 Verilog 建模
讲座中关于 Verilog 仿真工具的主要内容
讲课内容主要包括:
如何对所做的设计进行编译和仿真如何使用元器件库如何用 Verilog-XL 命令行界面调试代码如何用图形用户界面( GUI )延迟的计算和标记仿真性能建模循环多次仿真
目的 : 了解用 HDL 语言设计数字逻辑的优点 了解 Verilog 主要应用领域 了解 Verilog 的发展历史 了解电路系统的不同层次的 Verilog 抽象
第二部分: Verilog 的应用
Verilog HDL 是一种用于数字逻辑电路设计的语言 :
- 用 Verilog HDL 描述的电路设计就是该电路 的 Verilog HDL 模型。- Verilog HDL 既是一种行为描述的语言也 是一种结构描述的语言。这也就是说,既可以用电路的功能描述也可以用元器件和它们之间的连接来建立所设计电路的
Verilog HDL 模型。 Verilog 模型可以是实际电路的不同级别的抽象。这些抽象的级别和它们对应的模型类型共有以下五种:
Verilog 的应用
Verilog 的应用
• 系统级 (system): 用高级语言结构实现设计模块的外部性能的模
• 算法级 (algorithmic): 用高级语言结构实现设计算法的模型。
• RTL 级 (Register Transfer Level): 描述数据在寄存器之间流动和如何处理这些数据的模型。
• 门级 (gate-level): 描述逻辑门以及逻辑门之间的连接的模型。
• 开关级 (switch-level): 描述器件中三极管和储存节点以及它们之间连接的模型。
Verilog 的应用
一个复杂电路的完整 Verilog HDL 模型是由若个 Verilog HDL 模块构成的,每一个模块又可以由若干个子模块构成。
利用 Verilog HDL 语言结构所提供的这种功能就可以构造一个模块间的清晰层次结构来描述极其复杂的大型设计。
Verilog HDL 行为描述语言作为一种结构化和过程性的语言,其语法结构非常适合于算法级和 RTL 级的模型设计。这种行为描述语言具有以下八项功能:
Verilog 的应用
• 可描述顺序执行或并行执行的程序结构。• 用延迟表达式或事件表达式来明确地控制过程的启动
时间。• 通过命名的事件来触发其它过程里的激活行为或停止
行为。• 提供了条件、 if-else 、 case 、循环程序结构。• 提供了可带参数且非零延续时间的任务 (task) 程序结
构。• 提供了可定义新的操作符的函数结构 (function) 。
Verilog 的应用
提供了用于建立表达式的算术运算符、逻辑运算符、位运算符。
Verilog HDL 语言作为一种结构化的语言也非常适合于门级和开关级的模型设计。
Verilog HDL 的构造性语句可以精确地建立信号的模型。这是因为在 Verilog HDL 中,提供了延迟和输出强度的原语来建立精确程度很高的信号模型。信号值可以有不同的的强度,可以通过设定宽范围的模糊值来降低不确定条件的影响。
Verilog 的应用
Verilog HDL 作为一种高级的硬件描述编程语言,有着类似 C 语言的风格。其中有许多语句如: if 语句、 case 语句等和 C 语言中的对应语句十分相似。如果读者已经掌握 C 语言编程的基础,那么学习 Verilog HDL 并不困难,我们只要对 Verilog HDL 某些语句的特殊方面着重理解,并加强上机练习就能很好地掌握它,利用它的强大功能来设计复杂的数字逻辑电路。下面我们将对 Verilog HDL 中的基本语法逐一加以介绍。
模块的抽象
技术指标:用文字表示用算法表示用高级行为的 Verilog 模块表示
RTL/ 功能级:用可综合的 Verilog 模块表示
门级 /结构级:用实例引用的 Verilog 模块表示
版图布局 /物理级:用几何形状来表示
行为综合
逻辑综合
综合前仿真
综合后仿真
布局布线
第三部分 .简单的 Verilog HDL 模块
目的 :
通过简单的例子了解 Verilog 模块的基本构成 了解 Verilog 模块的层次结构和行为模块 了解 Verilog 模块的测试
简单的 Verilog HDL 模块
下面先介绍几个简单的 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声明语句里的。
简单的 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
这个程序描述了一个比较器 .在这个程序中 ,/*........*/和 //.........表示注释部分 , 注释只是为了方便程序员理解程序 ,对编译是不起作用的。
简单的 Verilog HDL 模块
例 [2.1.3]:
module trist2(out,in,enable); output out; input in, enable; bufif1 mybuf(out,in,enable);endmodule
这个程序描述了一个三态驱动器。程序通过调用一个实例元件 bufif1 来实现其功能。
简单的 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); output out; input in, enable; assign out = enable? In : 'bz; endmodule
简单的 Verilog HDL 模块
上述程序例子通过另一种方法描述了一个三态门。
在这个例子中存在着两个模块:模块 trist1 调用模块 mytri 的实例元件 tri_inst 。
模块 trist1 是上层模块。模块 mytri 则被称为子模块。
通过这种结构性模块构造可构成特大型模块。
简单的 Verilog HDL 模块
通过上面的例子可以看到 :• Verilog HDL 程序是由模块构成的。模块是可以进行层次嵌套的。正
因为如此 , 才可以将大型的数字电路设计分割成不同的小模块来实现特定的功能 , 最后通过顶层模块调用子模块来实现整体功能。
• 每个模块要进行端口定义 , 并说明输入输出口 , 然后对模块的功能进行行为逻辑描述。
• Verilog HDL 程序的书写格式自由 , 一行可以写几个语句 , 一个语句也可以分写多行。
• 除了 endmodule 语句外 , 每个语句和数据定义的最后必须有分号
• 可以用 /*.....*/ 和 //... 对 Verilog HDL 程序的任何部分作注释。一个好的 , 有使用价值的源程序都应当加上必要的注释 , 以增强程序的可读性和可维护性。
模块的结构
Verilog 的基本设计单元是“模块” (block) 。一个模块是由两部分组成的:一)描述接口;二)描述逻辑功能,即定义输入是如何影响输出的。下面举例说明:
module block (a,b,c,d);input a,b;output c,d;
assign c= a | b ;assign d= a & b;endmodule
a
b
c
d
模块的结构
从上面的例子可以看出:
- Verilog 模块结构完全嵌在 module和 endmodule声明语句之间;
- 每个 Verilog 程序包括四个主要部分:端口定义、 I/O说明、内部信号声明、功能定义。
模块的测试
如何检查上述例子其功能是否正确?
需要有测试激励信号输入到被测模块 需要记录被测模块的输出信号 需要把用功能和行为描述的 Verilog 模块 转换为门级电路互连的电路结构(综合)。 需要对已经转换为门级电路结构的逻辑 进行测试(门级电路仿真)。 需要对布局布线后的电路结构进行测试。 (布局布线后仿真)。
模块的测试
被测模块
激励和 控制信号
输出响应和验证
模块的测试
测试模块常见的形式: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
模块的测试
测试模块中常用的过程块:
initial always
所有的过程块都在 0时刻同时启动;它们是并行的,在模块中不分前后。 initial 块 只执行一次。 always 块 只要符合触发条件可以循环执行。
模块的测试
如何描述激励信号: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
模块的测试
如何观察被测模块的响应: 在 initial 块中,用系统任务 $time 和 $monitor $time 返回当前的仿真时刻 $monitor 只要在其变量列表中有某一个或某几个变 量值发生变化,便在仿真单位时间结束时显示其变 量列表中所有变量的值。
例: initial begin $monitor ($time, , “out=%b a=%b sel=%b”, out,a,b,sel); end
模块的测试
如何把被测模块的输出变化记录到数据库文件中?(文件格式为 VCD, 大多数的波形显示工具都能读取该格式)可用以下七个系统任务:
1) $dumpfile(“file.dump”); //打开记录数据变化的数据文件2) $dumpvars(); //选择需要记录的变量3) $dumpflush; //把记录在数据文件中的资料转送到硬盘保存4) $dumpoff; //停止记录数据变化5) $dumpon; //重新开始记录数据变化6) $dumplimit(<file_size>); //规定数据文件的大小(字节)7) $dumpall; // 记录所有指定信号的变化值到数据文件中
模块的测试
如何把被测模块的响应变化记录到数据库文件中?举例说明:$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 所有信号的变化(包括其两层以下子模块的
信号变化)。
模块的测试
如何把被测模块的响应变化记录到数据库文件中?
举例说明:下面的 Verilog 代码段可以代替测试文件中的系统任
务 $monitor initial begin $dumpfile(“vlog.dump” ); $dumpvars(0,top); end
语法详细讲解第四部分 . Verilog 语法要点
目标 : 理解 Verilog 语言的一些重要规定 .
学会认识一些有关的重要语言符号 .
掌握 Verilog 中如何规定时间单位
语法详细讲解Verilog 与 C 的主要不同点
Verilog 有许多语法规则与 C 语言一致。 但与 C 语言有根本的区别:
- 并行性
- 块的含义: initial 块 和 always 块- 两种赋值语句: 阻塞 赋值 “ = ”
非阻塞赋值 “〈 = ”
语法详细讲解空格和注释
Verilog 是一种格式很自由的语言。 空格在文本中起一个分离符的作用,
别的没有其他用处。 单行注释符用 //*********
与 C 语言一致 多行注释符用 /* ------------------------- */
与 C 语言一致
语法详细讲解整数和实常数
Verilog 语言中常数可以是整数或实数: 整数可以标明位数也可以不标明位数,表示方法: 《位数》‘《基数》《值》 其中《位数》表明该数用二进制的几位来表示 《基数》可以是二( b )、八( O )、十( d)或 十六( h)进制 《数值》可以是所选基数的任何合法的值包括 不定值 x 位和高阻值 z 。如 :64‘hff01 8’b1101_0001 ‘h83a
实常数可以用十进制表示也可以用科学浮点数表示,如: 32e-4 (表示 0.0032 ) 4.1E3( 表示 4100)
语法详细讲解字符串
Verilog 语言中,字符串常常用于表示命令内需要显示的信息。
用“ ”括起来的一行字符串,换新一行用 “ \n” 字符 , 与 C 语言一致。
在字符串中可以用 C 语言中的各种格式控制符,如 \t, \”, \\…
在字符串中可以用 C 语言中的各种数值型式控制符 (有些不同 ) ,如:
%b( 二进制 ), %o( 八进制 ), %d( 十进制 ), %h(十六进制 ), %t( 时间类型 ), %s ( 字符串类型 )…
语法详细讲解标识符
所谓标识别符就是用户为程序描述中的 Verilog 对象所起的名字。
标识符必须以英语字母( a-z, A-Z)起头,或者用下横线符( _ )起头。其中可以包含数字、 $符和下横线符。
标识符最长可以达到 1023个字符。
模块名、端口名和实例名都是标识符。
Verilog 语言是大小写敏感的,因此 sel 和 SEL 是两个不同的标识符。
语法详细讲解合法和非法标识符
合法的:shift_reg_a
busa_index
bus263
非法的:34net // 不能用数字开头
a*b_net // 不能含有非字母符号 *
n@263 // 不能含有非字母符号@
Verilog 是 大 小 写 敏 感 的 。 所 有 的Verilog 关键词都是小写的。
语法详细讲解特别的标识符
特别标识符是用 “ \” 符开始,以空格符结束的标 识符。它可以包含任何可打印的 ASCII 字符。
但“ \”符和空格并不算是标识符的一部分。
特别标识符往往是由 RTL 级源代码或电路图类型的 设计输入经过综合器自动综合生成的网表结构型 Verilog 语句中的标识符。举例说明:\~#@sel , \bus+index , \{A,B} ,
Top.\3inst .net1 , //在层次模块中的标识名
语法详细讲解系统任务和函数
$< 标识符 > ‘$’ 符号表示 Verilog 的系统任务和函数 常用的系统任务和函数有下面几种:1) $time //找到当前的仿真时间2) $display, $monitor //显示和监视信号值的变化3) $stop //暂停仿真4) $finish // 结束仿真-------------------------------------------------------例: initial $monitor($time,,”a=%b, b=%b”, a, b); // 每当 a 或 b值变化时该系统任务都显示当前的仿真时刻并分别用二进
制和十六进制显示信号 a和 b的值
语法详细讲解特殊符号 “#”
特殊符号 “ #” 常用来表示延迟:
在过程赋值语句时表示延迟。例: initial begin #10 rst=1; #50 rst=0; end
在门级实例引用时表示延迟。例: not #1 not1(nsel, sel); and #2 and2(a1, a, nsel);
在模块实例引用时表示参数传递 介绍参数类型变量时再讲。。。。
语法详细讲解编译引导语句
编译引导语句用主键盘左上角小写键 “ ` ” 起头 用于指导仿真编译器在编译时采取一些特殊处理 编译引导语句一直保持有效,直到被取消或重写 `resetall 编译引导语句把所有设置的编译引导恢复到缺省状态
常用的编译引导有:a) `defineb) `includec) `timescaled) `uselibe) `resetall
……..
语法详细讲解编译引导语句
使用 `define 编译引导能提供简单的文本替代功能 `define <宏名 > <宏文本 > 在编译时会用宏文本来替代源代码中的宏名。 合理地使用 `define 可以提高程序的可读性
举例说明:
`define on 1’b1`define off 1’b0`define and_delay #3在程序中可以用有含义的文字来表示没有意思的数码提高了程序的可读性,在程序中可以用 `on , `off , `and_delay 分别表示 1 , 0 ,和 #3 。
语法详细讲解编译引导语句
使用 `include 编译引导,在编译时能把其指定的整个文件包括进来一起处理
举例说明:`include “global.v”`include “parts/counter.v”`include “../../library/mux.v”
合理地使用 `include 可以使程序简洁、清晰、条理清楚、易于查错。
语法详细讲解编译引导语句
`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 的整数倍
语法详细讲解编译引导语句
仿真步长即仿真单位( 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中共有 1230000 个 STU ( 100fs) endmodule
`timescale 1ps/100fs module M3(….); not #1.23 not1(nsel, sel); //1.23 ps中共有 12 个 STU ( 100fs) endmodule
语法详细讲解编译引导语句
时间单位 :
fs (呼秒) femptoseconds: 1.0E-15 秒 ps (皮秒) picoseconds: 1.0E-12 秒 ns (纳秒) nonoseconds: 1.0E-9 秒 us (微秒) microseconds: 1.0E-6 秒 ms (毫秒) milliseconds: 1.0E-3 秒 s ( 秒) seconds: 1.0 秒
语法详细讲解编译引导语句
`uselib 编译引导语句: 用于定义仿真器到哪里去找库元件 如果该引导语句启动的话,它就一直有效 直到遇到另外一个 `uselib 的定义或 `resetall语句
比其他配置库搜索路径的命令选项作用大 如果仿真器在 `uselib 定义的地点找不到器件库,
它不会转向由编译命令行 -v 和 -y 选项指定的器件库去找。
语法详细讲解编译引导语句
使用 `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
语法详细讲解第五部分 Verilog 的数据类型和逻辑值
目的:
掌握 Verilog 不同逻辑值的含义
学习 Verilog 不同的数据类型
理解如何使用和在什么场合下使用不同的数据类型
学习声明数据类型的语法
语法详细讲解Verilog 的四种逻辑值
0、低、伪、逻辑低、地、 VSS 、负插入0
1
X
Z0
buf
buf
buf
bufif1
1、高、真、逻辑高、电源、 VDD 、正插入
X 、不确定:逻辑冲突无法确定其逻辑值
HiZ 、高阻抗、三态、无驱动源
语法详细讲解主要的数据类型
Verilog 有三种主要的数据类型:
Nets 表示器件之间的物理连接 , 称为网络连接类型
Register 表示抽象的储存单元,称为寄存器 /变量类型
Parameter 表示运行时的常数,称为参数类型
语法详细讲解主要的数据类型
Nets(网络连线):
由模块或门驱动的连线。
驱动端信号的改变会立刻传递到输出的连线上。
例如:右图上, selb 的改变,会自动地立刻影响或门的输出。
nets
a
bsl
selb
sela
nsl out
语法详细讲解主要的数据类型
连接( Nets ) 类型变量的种类:
在为不同工艺的基本元件建立库模型的时候,常常需要用不同的连接类型来与之对应,使其行为与实际器件一致。常见的有以下几种。 类型 功能 wire, tri 对应于标准的互连线(缺省) supply1, supply2 对应于电源线或接地线 wor, trior 对应于有多个驱动源的线或逻辑连接 wand, triand 对应于有多个驱动源的线与逻辑连接 trireg 对应于有电容存在能暂时存储电平的连接 tri1, tri0 对应于需要上拉或下拉的连接
如果不明确地说明连接是何种类型,应该是指 wire 类型。
语法详细讲解主要的数据类型
寄存器( register)类型变量 register 型变量能保持其值,直到它被赋于新的值。 register 型变量常用于行为建模,产生测试的激励信号。 常用行为语句结构来给寄存器类型的变量赋值。
a
b
sl
selb
sela
nsl out
reg_a
reg_sel
reg_b
语法详细讲解主要的数据类型
寄存器( register)类型变量的数据类型
寄存器类型变量共有四种数据类型: 类型 功能 . reg 无符号整数变量,可以选择不同的位宽。 integer 有符号整数变量, 32位宽,算术运算可产生
2 的 补码。 real 有符号的浮点数,双精度。 time 无符号整数变量, 64位宽( Verilog-XL 仿真 工具用 64位的正数来记录仿真时刻)
语法详细讲解主要的数据类型
如何选择正确的数据类型?
输入口( input)可以由寄存器或网络连接驱动,但它本身只能驱动网络连接。
输出口 (output) 可以由寄存器或网络连接驱动,但它本身只能驱动网络连接。
输入 / 输出口 (inout)只可以由网络连接驱动,但它本身只能驱动网络连接。
如果信号变量是在过程块 (initial 块 或 always 块 )中被赋值的,必须把它声明为寄存器类型变量
语法详细讲解主要的数据类型
举例说明数据类型的选择
module top;wire y; reg a, b; DUT u1(y,a,b); initial begin a = 0; b = 0; #10 a =1; …. endendmodule
模块 DUT 的边界
输入口 输出口
输出 / 入口
net
netnet/register
net
net/register
netinout
module DUT(Y, A, B_);
output Y;
input A,B:
wire Y, A, B;
and (Y, A, B);
endmodule
语法详细讲解主要的数据类型
选择数据类型时常犯的错误
在过程块中对变量赋值时,忘了把它定义为寄存器 类型( reg)或已把它定义为连接类型了
( wire )
把实例的输出连接出去时,把它定义为寄存器类型了
把模块的输入信号定义为寄存器类型了。
这是经常犯的三个错误!!!
语法详细讲解主要的数据类型
参数( 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); ……. #20000 display(“%s”,file); $stop end ….endmodule
语法详细讲解主要的数据类型
参数值的改写 (方法之一 )
举例说明 :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”; …endmodule
语法详细讲解主要的数据类型
参数值的改写 (方法之二 )举例说明 :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); …endmodule
语法详细讲解寄存器阵列
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];
语法详细讲解 第十一部分 用文本接口的调试
目的: 在本节中我们将学习如何用 Verilog-XL 提供的
TUI( 用户文本接口 )来调试所设计的模块 进入交互方式的仿真模式 控制和观察仿真 浏览设计的各个层次 设置仿真的检查断点、退出仿真
语法详细讲解
语法详细讲解
语法详细讲解 第十四部分 Verilog测试模块的编写
目的:
复习如何编写较复杂的测试文件,对所做的设计 进行完整的测试和验证。
掌握组织模块测试的常用方法;学会编写常用的 测试代码。
语法详细讲解 用 Verilog 设计的步骤
注:虚线表示编译器能检查输入文件的可读性和是否存在以及是否允许生成输出文件
include
文件
设计文件
厂家元件
库文件
输入文件:激励和期望的输出信号
输出文件:激励和实际输出的信号
编译器
仿真器
仿真器
语法详细讲解 测试平台的组成
激励信号
需要验证的设计
激励信号和用于验证的结果数据
需要验证的设计
简单的测试平台
复杂的测试平台
语法详细讲解 并行块
在测试块中常用到 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 开始执行时间不同,但能同时运行。
语法详细讲解并行块
时间 data_bus
0 8’b0000_0000
10 8’b0100_0101
30 8’b0100_0110
40 8’b0100_0111
45 8’b1000_1110
50 8’b1000_1111
60 8’b1001_0000
65 8’b0010_0000
70 8’b0010_0001
时间 data_bus
80 8’b0010_0010
85 8’b0100_0100
90 8’b0100_0101
100 8’b0010_0001
105 8’b0100_0110
110 8’b1000_1100
120 8’b1000_1110
125 8’b0001_1100
140 8’b0000_1111
上面模块的仿真输出如下:
语法详细讲解
语法详细讲解强制激励
在一个过程块中,可以用两种不同的方式对信号变量或表达式进行连续赋值。
过程连续赋值往往是不可以综合的,通常用在测试模块中。 两种方式都有各自配套的命令来停止赋值过程。 两种不同方式均不允许赋值语句间的时间控制。
assign 和 deassign 适用于对寄存器类型的信号(例如: RTL 级上
的节点或测试模块中在多个地方被赋值的信号)进行赋值。
initial begin
#10 assign top.dut.fsml.state_reg = `init_state;
#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; end 在以上两个例子中,在 10 到 20 这个时间段内,网络或寄存器类
型的信号被强制赋值,而别处对该变量的赋值均无效。force 的赋值优先级高于 assign 。如果先使用 assign ,再使用 force 对同一信号赋值,则信号的值为 force 所赋 的值,
语法详细讲解强制激励
语法详细讲解字符串
语法详细讲解强制激励
语法详细讲解强制激励
当执行 release 后,则信号的值为 assign 所赋 的值。如果用 force 对同一个信号赋了几次值,再执行 release ,则所有赋的值均不再存在。可以对信号的某(确定)位、某些(确定)位或拼接的信号,使用 force 和 release赋值;但不能对信号的可变位使用 force 和 release 来赋值。不能对寄存器类型的信号某位或某些位使用 assign 和deassign 来赋值。
语法详细讲解强制激励
语法详细讲解建立时钟
虽然有时在设计中会包含时钟,但时钟通常用在测试模块中。下面三个例子分别说明如何在门级和行为级建立不同波形的时钟模型。[ 例 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注:在有些仿真器中,如果设计所用的时钟是由与其相同抽象级别的时钟模型产生的,则仿真器的性能就能得到提高。
[ 例 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
注:这两个时钟模型有些不同,行为描述的模型延迟期间一直是低电平,而门级描述的模型开始延迟有半个周期是不确定的。
语法详细讲解建立时钟
[ 例 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
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
注:这两个时钟模型也有些不同,行为描述的模型一开始就有确定的电平,而门级描述的模型有延迟 , 开始时电平是不确定的。
语法详细讲解怎样使用任务
举例说明如何使用任务: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
语法详细讲解怎样使用任务
语法详细讲解怎样使用任务
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
语法详细讲解怎样使用任务
语法详细讲解怎样使用任务
在测试模块中使用任务可以提高程序代码的效率,可以用任务把多次重复的操作包装起来。
语法详细讲解怎样使用任务
wait waitwait waitdata1 data2 data3 data4
cpu_data
clk
data_valid
data_rd
read_cpu_state
语法详细讲解存储建模
目标
学会如何用 Verilog 对存储器建模。
学会如何用 Verilog中对双向(即输入 / 输出)端口 , ( inout)建模。
语法详细讲解第十五部分 存储器建模
存储器建模必须注意以下两个方面的问题: 声明存储器容量的大小。 明确对存储器访问操作的权限。
例如:指出可以对存储器做以下哪几种操作: 1)只读 2 )读写 3 )同步读写 4 )多次读,同时进行一次写 5 )多次同步读写,同时提供一些方法保证一致性
语法详细讲解存储器建模
`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);always @ (addr or read_en_) if(!read_en_) read_data=mem[addr];endmodule
语法详细讲解简单 ROM 建模
my_rom_data 0000 0101 1100 0011 1101 0010 0011 1111 1000 1001 1000 0001 1101 1010 0001 1101
ROM 的数据存储在另外的一个独立的文件中
语法详细讲解简单 ROM 建模
上页所示的 ROM 模型说明: 如何在 Verilog中用二维的寄存器组来定义存储器。 ROM中的数据保存在一个独立的文件中,如上页的右边的虚线方框所示。
这是一种保存 ROM 数据的通用的方法,它可以使数据和 ROM 模型分开。
语法详细讲解简单 RAM 建模
`timescale 1ns/1nsmodule 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;//从总线写入存储器 always @ (posedge write) memory[addr]=data;endmodule
语法详细讲解简单 RAM 建模
RAM 模型比 ROM 模型稍微复杂: 它必须具有读写能力; 进行读写时通常使用相同的数据总线; 需要新技术来处理双向总线; 当读信号无效时, RAM 模型与总线脱离,如果此时写
信号也无效,总线无驱动源,则总线进入高阻状态,
这就避免了 RAM中的读写竞争。 上页的 RAM 模块是可综合的,但综合出来是一大堆寄存器
,占比较大的面积,经济上不太合算。
例: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
语法详细讲解存储量可变的只读存储器建模
语法详细讲解存储量可变的只读存储器建模
上述的例子演示了怎样通过设置字长和地址位数来编 写 只读存储器的行为模块。 [ 注意 ] !! 在上例中,存储字的范围从 0开始的,而
不是从 1开始,这是因为存储单元是直接通过地址线寻址定位的。
同样地,也可以用下面的方法来定义存储器和寻址: reg [wordsize:1] mem [1:words]; //存储器地址 从 1 开始 // 地址一个一个地增加直到包含了每个地址对应的存储器 wire [wordsize:1] mem_word = mem[address+1];
可以在初始化块中用一个循环或系统任务把初始数据存入存储器的每个单元。
使用循环把值赋给存储器数组。 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 块中,加载这些初始化数据不需要时间。
语法详细讲解存储器的加载
语法详细讲解怎样使用双向口
使用 inout 关键字声明端口为双向口。 inout [7:0] databus;
使用双向口必需遵循下面的规则: inout 口只能声明为网络连接类型, 不允许把它声明为寄存器类型。(所以仿真器能确定多个驱动源的最终值。)
在设计中,每次只能从一个方向来驱动 inout口。 例如:当使用总线读 RAM中的数据时,如果同时又
向 RAM 模型的双向数据总线写数据,就会产生逻辑竞争,导致总线数据无法确定。所以必须为 inout 口设计控制逻辑,只有这样才能保证正确的操作。
语法详细讲解怎样使用双向口
[注意 ]:
声明一个 inout 口,可以用来输入或输出数据。 inout 口默认为网络连接类型。不允许在过程块( initial 或 always 块)中对网络连接类型的数据进行过程赋值;但可以在过程块外把一个寄存器数据类型通过连续赋值语句赋给它( inout 口),或者把它与用户定义的源语( UDP)相连。
必须为 inout 口设计控制逻辑,用来保证正确的操作。当把 inout口作为输入口时,必须通过控制逻辑禁止输出到 inout 口。
使用 Verilog 中的基本元件( bufif1)为双向口建模:
语法详细讲解双向口建模
b2
b1
en_a_b
en_b_a
bus_a
bus_b
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上
语法详细讲解双向口建模
[ 注意 ]: 在上页的例子中,使用 en_a_b 和 en_b_a 来控制元器件 bufifl, 如果控制信号同时有效,则结果无法确定。
所以必须把控制信号 en_a_b 和 en_b_a 在时间上分开。
使用连续赋值为双向口建模:en_a_
b
en_b_a
bus_a
bus_b
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上
语法详细讲解双向口建模
b2
b1
[ 注意 ]:
在 assign 语句中,通过 en_a_b 和 en_b_a 控制bus_a 与 bus_b 之间的数据交换。
如果控制信号同时有效,则结果不能确定。所以必须把控制信号 en_a_b 和 en_b_a 在时间上分开。
语法详细讲解双向口建模
存储器的端口建模:
语法详细讲解双向口建模
module ram_cell(databus,rd.wr);inout databus;input rd,wr;reg datareg; assign databus=rd? datareg:’bz; always @(negedge wr) datareg<=databus;endmodule
当 rd 等于 1时datareg 的值被赋给databus
当 wr 的下降沿到达时,databus 的值被写入datareg
测试模块 RAM单元
数据总线数据寄存器
rd
wr
[ 注意 ]:
上页中存储单元在 wr 的下降沿到达时存入数据。上页模块在 wr 处于高电平时,通过数据总线写入数据,但必须保证wr 的高电平维持时间长于数据的写入时间。
在 rd 处于高电平时,上述存储单元通过数据总线读出数据。由于此模型为单口存储模型,因此wr变低电平时, rd 不能同时为高电平,否则就无法确定存储器的读出 / 写入的结果。
语法详细讲解双向口建模
目标 :
学会怎样定义或调用任务和函数。 学会怎样使用命名块。 学会怎样禁止命名块和任务。 理解有限状态机的作用,学会如何显式
地为有限状态机建模。
语法详细讲解第十六部分 Verilog 中的高级结构
通过把代码分成小的模块或者使用任务和函数,可把一项任务分成许多较小的、易于管理的部分,从而提高代码的可读性、可维护性和可重用性。
任务 一般用于编写测试模块,或者行为描述的模块。 其中可以包含时间控制(如: # delays, @, wait) ; 也可以包含 input, output 、 inout 端口定义和参数; 也可以调用其他的任务或函数 函数 一般用于计算,或者用来代替组合逻辑。 不能包含任何延迟;函数在零时间执行。 函数只有 input变量,虽然没有 output变量, 但可以通过函数名返回一个值。 可以调用其他的函数,但不可以调用任务
语法详细讲解Verilog 中的高级结构
[ 注意 ] : 只能调用本模块内的任务和函数。 在任务和函数中不能声明网络连接类型的变量。 所有的输入和输出变量实际上都是本地寄存器 。 只有当任务或函数调用并执行完后,才能有返回值。
[举例说明 ] :
若任务或函数中包含一个 forever 循环时,永远无法执行完,就不可能有返回值。
语法详细讲解Verilog 中的高级结构
语法详细讲解Verilog 任务
下面模块中的任务含有定时控制和一个输入,并且引用了一个本模块的变量,但是没有输出,也没有双向总线和内部变量,不显示任何内容。
用于定时控制的信号,例如 clk ,绝对不能作为任务的输入,这是因为输入值只向任务内部传递一次。
module top;
reg clk, a, b;
DUT u1(out, a, b, clk);
always #5 clk=!clk;
语法详细讲解整数和实常数
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
语法详细讲解Verilog 任务
要点:
任务调用是通过在 Verilog 模块中写入任务名来实现的。
任务中可以包含 input, output 和 inout 端口变量的声明。 传递给任务的变量与任务 I/O 端口变量的声明次序相同。虽然传递给任
务的变量名可以和任务内声明的 I/O 端口变量名相同,但是为了使任务成为一个独立的可共用的任务块,建议不要使用与任务内声明的 I/O 端口变量名相同的变量名,最好给传递到任务的变量起新的不同的名字。
在任务中可以使用时间控制。
任务使 Verilog 有更广阔的适用范围。
关键字 disable 可以用来禁止任务的执行。
语法详细讲解 Verilog 任务
[ 注意 ]:
不要在程序的不同部分同时调用同一个任务。这是因为任务只有一组本地变量,同一时刻调用两次相同的任务将会导致错误。这种情况常发生在使用定时控制的任务中。
在任务或函数中,引用父模块中声明的变量时要特别
注意(即注意变量的层次命名规则)。若想在其它模块中调用任务或函数,该任务和函数中所使用的变量必须全都包含在输入 / 输出口列表中。
语法详细讲解 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 always @ (posedge clk) reverse_bits (D); ……
语法详细讲解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; always @ (posedge clk) multme(a, b, out); // 任务调用
语法详细讲解Verilog 任务
task multme; // 任务定义 input [3:0] xme, tome; output [7:0] result; wait (en_mult) result=xme*tome; endtask endmodule
语法详细讲解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; always @ (a or b or c or d or e) out = f_or_and (a, b, c, d, e); // 函数调用
function [7:0] f_or_and; input [7:0] a, b, c, d, e; if (e= =1) f_or_and = (a|b) & (c|d); else f_or_and=0; endfunctionendmodule
语法详细讲解Verilog 函数
虽然函数不能包含定时控制,但是可以在包含定时控制的过程块中调用函数。
在模块中,使用名为 f_or_and 的函数时,是把它作为名为
f_or_and 的寄存器类型变量来处理的。
要点 函数定义不能包含任何定时控制语句。 函数必须至少有一个输入,但绝不能含有任何输出和总线口; 一个函数只能返回一个值,该值的变量名与函数同名,数据类
型默认为 reg 类型。 传递给函数的变量顺序与函数输入口声明的顺序相同。 函数定义必须包含在模块定义之内。 函数不能调用任务,但任务可以调用函数。 函数使 Verilog 有更广阔的适用范围。
语法详细讲解Verilog 函数
虽然函数只能返回一个值,但是它的返回值可以直接赋给一个由多个子信号拼接构成的信号变量, 使其实际等效于产生了多个输出。
{o1, o2, o3, o4}=f_or_and(a, b, c, d, e);
语法详细讲解Verilog 函数
在函数定义时,如果在函数名前面定义了位宽,该函数就可以返回由多位构成的矢量。如果定义函数的语句比较多时,可以用 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
语法详细讲解Verilog 函数
若把函数定义为整型、实型或时间类型, 就可以返回相应类型的数据。我们可以在任何类型的表达式中调用函数。
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 always @ (a or b) begin if ( subtr (a,b) <0) neg = 1; else neg = 0; end endmodule
语法详细讲解Verilog 函数
函数类型、端口和行为定义时也可以使用参数, 这样就可以构成参数化函数使其返回的数据类型、输入端口的位宽等很容易做修改。所以参数化函数就有更广泛的适用范围。
…. 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 always @ (posedge clk) begin …. D= reverse_bits(D); ….. end ………
语法详细讲解Verilog 函数
语法详细讲解命名块
可以通过在关键字 begin 或 fork 后加上:〈块名〉来给块命名。
module named_blk; …… begin :seq_blk …… end …… fork : par_blk …… join …… endmodule
可以在命名块中声明本地变量。 可以使用 disable禁止命名块。
注意: 命名块使 Verilog 有更广阔的适用范围。 命名块的使用缩短了仿真的时间。
语法详细讲解命名块
语法详细讲解禁止命名块和任务
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; always @(posedge 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 always @(negedge en_mult) begin //停止计算 disable multme; //***禁止任务的执行 *** diable arith_block; //***禁止命名块的执行 *** end //在此定义任务和函数 …………..endmodle
注意: disable 语句用来终止命名块或任务的执行。这是指在尚未执
行该命名块或任务任何一条语句前,就从该命名块 / 任务执行中返回。
语法: disable 〈块名〉 或 disable 〈任务名〉
禁止执行命名块或任务后,所有在事件队列中由该命名块 / 任务安排的事件都将被删除。
一般 情况下 disable 语句是不可综合的。
在上页的例子中,只禁止命名块也可以得到预期的结果:命名块中所有的事件,包括任务和函数的执行都将被取消。
语法详细讲解禁止命名块和任务
语法详细讲解有限状态机( FSM)
隐式 FSM:
不需要状态寄存器
仿真更加有效
只能很好 地处理线 性的状态改变
大 部 分 综 合 工 具 不支持隐式 FSM
state 1
state 2
state 3
state 4
语法详细讲解编译引导语句
显式 FSM:
结构比较复杂
可以很方便的用来处理默认状态
能够处理复杂的状态改变
所有的综合工具均支持显式FSM 的综合
语法详细讲解有限状态机( FSM)
state A
state B1 state B2
state C
state D
注意:
在隐式状态机中,只要发生在一个时钟周期内写数据,在另一个时钟周期内读数据的情况,都会生成寄存器。
任何状态机都必须有复位控制信号,状态的改变必需只与某单一时钟信号沿同步。
一般情况下,如果状态改变比较简单,又定义得比较好,而且综合工具支持隐式状态机的综合,就可以使用隐式状态机。如果状态改变比较复杂,最好使用显式状态机,这样效果更好。
隐式状态机属于行为级,不属于 RTL 级。代码中主要包含循环语 句 、 嵌 入 的 定时控 制 , 有时也含有 命名事件 、 wait 和 disable 语句。一般情况下,常用的综合工具不支持隐式状态机的综合。
语法详细讲解有限状态机( FSMs)
语法详细讲解显式有限状态机
module exp(out, datain, clk, rst);input clk, rst, datain;output out; reg out;reg state;always @(posedge 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 语句
0
1
datain = 0
datain = 1
out=datain; state=1’b0; end default: {state, out}=2’b00; endcaseendmodule
注: 在过程块中可以使用一个时钟沿和 case 语句来描述一个显式
状态机。 必须指定一个状态变量,来记录状态机的状态。 要改变当前的状态,必须改变状态变量的值, 其改变要与时钟沿同步。
写得比较好的状态机常为不应产生的条件规定一个默认动作。
语法详细讲解显式有限状态机
转到下一个状态
默认状态指针
0 11 01 识别 11序列
clk
rst
out
begin: seq_block
out=1’b0;
if(!datain) // 状态一:输出零 disable seq_block;
@(posedge clk) // 状态二:输出第二位 out=datain;
end
endmodule
语法详细讲解隐式有限状态机
0 11 01 识别 11序列
clk
rst
out
注意:
在过程块中可以使用多个时钟沿(即每次状态改变都用一个新的时钟沿)、条件语句、循环语句、 disable 语句来描述隐式 FSM 。隐式 FSM往往是不可综合的。
隐式 FSM 不必指定状态变量。
当下一个激活时钟沿到达时,状态就有可能发生改变。下一个状态是否改变,将由条件语句决定;除非用强制性语句使状态重复(例如:用循环语句或用 disable 语句来强制改变状态),
在隐式状态机中,很难规定一个默认动作。
语法详细讲解隐式有限状态机
目标
学会怎样使用用户定义的原语来创建逻辑。
用户定义的源语元件 (UDP) 其行为与 Verilog 语法中本来就存在的 primitive(源语元件)相似,它用一个表格来定义它的逻辑功能。
语法详细讲解第十七部分 用户定义的原语
在 Verilog 结构建模时,可以使用: 二十多个门级源语元件( primitives )。 用户定义的源语元件( UDP )。
UDP 可用于 ASIC 库中的基本元件( cell)设计,以及小规模芯片和中规模芯片的设计。
使用 UDP 可以在现有的 Verilog 语言支持的源语元件的基础上编写新的源语元件。
UDP 是一个独立元件, 不能用实例调用的方法调用其他的模块。 UDP 既可以用来表示时序逻辑元件,也可以表示组合逻辑元件。 UDP 的行为是使用真值表来描述的 。 调用 UDP 的方式与调用 Verilog 语言提供的源语元件的方式相
同。
语法详细讲解什么是 UDP ?
注意: UDP 是一种紧凑的表示简单逻辑关系部件的方法。 在 Verilog 语言提供的几种基本源语元件中,若在输入中包含不确定值 x ,则在输出时可能出现不确定值 x;而在 UDP 中则不允许出现此种情况。
由几个原语元件组成的逻辑可以用一个 UDP 表示。在仿真时使用这样的 UDP 来代替分散的原语元件可以节省计算资源,加快仿真速度。一般的仿真器处理行为模型表示的逻辑所需时间比处理用门级语句表示的相同逻辑所需时间少;而硬件仿真器正好相反。
语法详细讲解什么是 UDP ?
UDP 只能有一个输出端,而且必须是端口说明列表的第一项。
UDP 可以有多个输入端,最多允许有 10 个。 UDP 所有端口变量必须是标量,不允许使用双向端口。 UDP 不支持 Z (高阻)逻辑值。 在仿真的开始时刻,可以使用 initial 语句把 UDP 的
输出初始化为一个已知值。 UDP 不支持综合,即不能通过综合把它转变为门级结
构逻辑。
语法详细讲解 UDP 的特点
注: UDP 只能有一个输出。如果逻辑功能要求有多个输出
端时,则需要把其它的原语元件连接到 UDP 的 输出,或同时使用多个 UDP ,保证其最终输出只有一个。
UDP 输入端最多可以有 10 个,但是当输入端的个数多于 5 个时,仿真时需要的内存个数将呈现近似指数的增加。下表列出了当输入数目不同时,在仿真过程中,对每个输入信号,计算机中所需要开销的内存数目。
语法详细讲解 UDP 的特点
语法详细讲解 UDP 的特点
输入端口的个数 所需内存的字节数
1-5 <1
6 5
7 17
8 56
9 187
10 623
组合逻辑示例: 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 0 : 0; ? 1 0 : 1; 0 0 x : 0; 1 1 x : 1; endtableendprimitive
原语名
输出端口必须为第一个端口
注: 在模块外定义 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 语言提供的基本源语元件更加准确。
语法详细讲解举例说明
可以只使用两个 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 : 1; ? 1 1 : 1; 0 0 ? : 0; 0 ? 0 : 0; ? 0 0 : 0; endtalbe
endprimitive
语法详细讲解组合逻辑示例:全加器
向上一级进位下一级来的进位
//全加器求和实现部分primitive U_ADDR2_S(S, A, B,CI);
output S;
input A, B, CI;
table // A B CI : S 0 0 0 : 0; 0 0 1 : 1; 0 1 0 : 1; 0 1 1 : 0; 1 0 0 : 1; 1 0 1 : 0; 1 1 0 : 0; 1 1 1 : 1; endtableendprimitive
语法详细讲解组合逻辑示例:全加器
若使用 UDP 设计全加器,仅需要两个 UDP ; 而使用 Verilog 原语元件,则需要 5 个 Verilog 语言提供的基本原语元件。
当设计需要使用大量全加器时,采用 UDP 来表示全加器,将大大减少内存的需要。
事件的数目将大大降低。 ?表示逻辑值可以为 0 , 1或 x 。
语法详细讲解组合逻辑示例:全加器
primitive latch(q, clock, data); output q; reg q; input clock, data; initial q=1’b1; table // clock data current next // state state 0 1 : ? 1; 0 0 : ? 0; 1 ? : : -; endtableendprimitive
语法详细讲解电平敏感的时序逻辑示例:锁存器
注意此寄存器的用法,此寄存器用来存储。
输出初始化为 1‘b1.
? 表示无须考虑输入和当前状态的值
注: 锁存器的动作行为如下: 当时钟信号为 0时,输入数据的值直接传给输出。 当时钟信号为 1时,输出保持当前状态不变。 next state 栏中的 “ -” 表示输出保持不变。 输出必须定义为寄存器类型,用来保存前一个状态。 initial q=1’b1; 是时序 UDP 的初始化语句。使用此语句可以在
仿真的开始对输出进行赋值。 在实际的部件模型中,很少使用初始赋值。但在测试 UDP 的功
能时,初始赋值相当有用。
语法详细讲解电平敏感的时序逻辑示例:锁存器
注: 锁存器的动作行为如下: 当时钟信号为 0时,输入数据的值直接传给输出。 当时钟信号为 1时,输出保持当前状态不变。 next state 栏中的 “ -” 表示输出保持不变。 输出必须定义为寄存器类型,用来保存前一个状态。 initial q=1’b1; 是时序 UDP 的初始化语句。使用此语句可以在
仿真的开始对输出进行赋值。 在实际的部件模型中,很少使用初始赋值。但在测试 UDP 的功
能时,初始赋值相当有用。
语法详细讲解电平触发时序逻辑的示例:锁存器
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;
语法详细讲解跳边沿敏感的时序逻辑示例: D 触发器
// 忽略时钟的下降沿 (?0) ? : ? : -;
(1x) ? : ? : -;
// 忽略时钟稳定时的数据改变 endtable
endprimitive
在大多数情况下,可以在任何表入口语句中规定一个输入过渡。 如果规定了任何输入过渡,则必须规定所有输入的所有过渡。
语法详细讲解跳边沿敏感的时序逻辑示例: D 触发器
可综合建模类型只有两种 :
1) 组合逻辑 :任何时候,如果输出信号直接由当前的输入信号的组合决定,则此逻辑为组合逻辑。
2) 时序逻辑:如果逻辑中具有记忆功能,则此逻辑为时序逻辑。在任何给定的时刻,如果输出不能完全由输入信号确定,则此逻辑具有记忆功能。
语法详细讲解第十八部分 可综合风格的 Verilog 建模类型
综合工具不支持下面的 Verilog 结构:1) initial
2) 循环语句:3) repeat
4) forever
5) while
6) for 的非结构用法7) 一部分数据类型8) event
9) real
10) time
语法详细讲解不能综合的 Verilog 结构
11) UDPs
12) fork…join 块13) wait
14) 过程连续赋值语句15) assign 和 deassign
16) force 和 release
17) 部分操作符18) = = =
19) ! = =
语法详细讲解不能综合的 Verilog 结构
由输入信号中任意一个电平发生变化所引起的过程块: 由输入信号中的某一个电平发生变化启动的过程块,可以通过综
合产生组合逻辑。该过程块称为组合块。如下例所示: always @(a or b) // 实现与门 y=a&b;
由单个跳变沿引起的过程块: 由控制信号的跳变沿(下降沿或上升沿)启动的过程块通过综合
可以生成同步逻辑。该过程块称为同步块。如下例所示: always @(posedge clk) // 实现 D 触发器 q<=d;
语法详细讲解过程块
在同步块中可以添加异步复位,举例说明如下:
always @(posedge clk or negedge rst_)
if(!rst_)
q<=0;
else
q<=d;
语法详细讲解过程块
在同步块中使用 reg 类型变量: 如果在一个时钟沿对 reg变量赋值,而在下一个时钟沿对其采样,
则综合器把该 reg变量转换为硬件寄存器。 如果只把 reg变量作为基本输出,则综合器不一定把它转换为硬
件寄存器。 如果不属于上述两种情况,同步块中的 reg变量有可能被优化掉。在组合块中使用 reg 类型变量: 当组合块中任何一个输入变量的值改变时, reg变量的值也随之改变,则综合器不会把该 reg变量转换为硬件寄存器。
当块的某一个输入的值改变时, reg变量的值不一定立即改变,而要等其他输入信号的值改变时才改变,则综合器将把该 reg变量转换为锁存器。
语法详细讲解过程块中寄存器类型的信号变量
同步寄存器示例:在下面的例子中, rega 仅用作临时存储器,因此在综合时它将被优化掉。module ex1reg(d, clk, q);input d, clk;output q;reg q, rega; always @(posedge clk) begin rega = 0; if(d) rega = 1; q = rega; endendmodule
语法详细讲解寄存器
在下面的例子中,用两个 always 块,它们的触发条件是相同的:即用同一时钟沿来处理两个存储元素,这样就可以使综合器在综合过程中保留 rega ,使它不被优化掉。module ex2reg(d, clk, q);input d, clk;output q;reg q, rega; always @(posedge clk) begin rega=0; if(d) rega=1; end
语法详细讲解寄存器
always @(posedge clk)
q = rega;
endmodule
注:在后面的 always 块中,块执行的顺序是不确定的,因此 q 可以获得在前一个周期中赋给 rega 的值。
语法详细讲解寄存器
组合寄存器示例: 下面的两个例子中, rega 都是临时变量,在综合中它们都会被优化掉。在本例中, y 和 rega 不断被赋新值(因为语句中有 else rega = 0; ),综合出的电路是一个纯组合逻辑。module ex3reg(y, a, b, c);
input a, b, c;
output y;
reg y, rega;
always @(a or b or c) begin
语法详细讲解寄存器
if(a&b)
rega=c;
else
rega=0;
y=rega;
end
endmodule
在下面的例子中, rega 只是有时被赋新值 (没有 else 语句, rega在条
件不符合时保持原值 );因此综合出来的是一个以 y 作为输出的锁存器。
语法详细讲解寄存器
moudule ex4reg(y, a, b, c);
input a, b, c;
output y;
reg y, rega;
always @(a or b or c)
begin
if(a&b)
rega=c;
y=rega;
end
endmodule
语法详细讲解寄存器
在下面的例子中, a 、 b 和 sl 均是块的输入。 在两个例子中, sl 均为 always 块的条件。 在第二个例子中, a 和 b 也用作 always 块的条件。
不完整电平敏感列表:module sens(a, q, b, sl);
input a, b, sl;
output q;
reg q;
always @(sl)
begin
语法详细讲解电平敏感列表
if(!sl)
q=a;
else
q=b;
end
endmodule
完整的电平敏感列表:module sens(q, a, b, sl);
input a, b, sl;
output q;
语法详细讲解电平敏感列表
reg q;
always @(sl or a or b)
begin
if(!sl)
q=a;
else
q=b;
end
endmodule
注:在电平敏感列表中最好包括所有的输入。对于不完整的列表,不同的综合工具处理的方法不同:一些综合工具认为不完整列表是
语法详细讲解敏感列表
不合法的,而其他的综合工具则发出警告并将其当作完整列表处理。因此,综合出来的电路功能可能与程序模块的描述有所不同。
语法详细讲解电平敏感列表
用连续赋值语句表达的是:任何一个输入的改变都将立即导致输出更新;与理想的与、或、非门的线路连接一致。
module orand(out, a, b, c, d, e);
input a, b, c, d, e;
output out;
assign out=3&(a|b)&(c|d);
endmodule
语法详细讲解连续赋值
过程连续赋值是在过程块( always 和 initial 块 ) 内部对寄存器类
型的变量进行的连续赋值。module latch_quasi(q, en, d);input en, d;output q;reg q; always @(en) if(en) assign q=d; else deassign q;endmodule
语法详细讲解过程连续赋值
大部分综合工具都能按照约定,正确地处理在源代码中用注释行表示的指导综合器如何转换到门级网表的指令即综合指令。
可以在 Verilog 语句之间用注释行嵌入综合指令, Verilog 仿真器运行时将忽略嵌入的指令,而当综合工具编译时,这些符合约定的综合指令是有意义的,能对综合过程进行干预和指导。
不同的综合工具有不同的综合指令集,使用综合指令为的是:使同样的 RTL 代码能综合出更高质量优化的门级网表。
下面列出了一部分 Cadence 综合工具支持的综合指令,它们与其他综合工具(例如: Synopsys DC)中的指令非常相似。
//ambit synthesis on
//ambit synthesis off
//ambit synthesis case=full 、 parallel 、 mux
语法详细讲解综合指令
结构指令//ambit synthesis architecture=cla or rpl
有限状态机指令//ambit synthesis enum xyz
//ambit synthesis stat_vector sig state_vector_flag
注:指令中通常包括综合工具或公司的名称,例如:上面指令中的ambit 表示使用的综合器是 Envisia Ambit 。
语法详细讲解综合指令
当 Verilog 模块中 case –endcase 块被综合时,有多种门级实现方法可供选择:综合指令 case 可以用来干预综合过程。让由 case 指定的编译方式被综合器优先考虑,即采用 case = 后列出的方法转换为门级电路。 case 综合指令的含义如下所示: //ambit synthesis case=parallel 对解码逻辑进行并行编译,没有优先级。 //ambit synthesis case=mux 如果库中含有多路器,则使用多路器编译解码逻辑。 //ambit synthesis case=full 不用考虑没有包含在 case 条件语句中的情形(即这些情形不会发
生),这样设定,可以综合出的优化的门级逻辑,并可避免发生状态机死锁。
语法详细讲解综合指令
语法详细讲解综合指令
完整条件语句module comcase(a, b, c, d, e);input a, b, c, d;output e;reg e;always @(a 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; endcaseendmodule
语法详细讲解条件语句
module compif(a, b, c, d, e);input a, b, c, d;output e;reg e;always @(a 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
语法详细讲解条件语句
module inccase(a, b, c, d, e);
input a, b, c, d;
output e;
reg e;
always @(a or b or c ord)
case ({a,b})
2’b11: e=d;
2’b10: e=~c;
endcase
endmodule
语法详细讲解不完整条件语句
module incpif(a, b, c, d, e);
input a, b, c, d;
output e;
reg e;
always @(a or b or c or d)
if (a&b)
e=d;
else if(a&~b)
e=~c;
endmodule
语法详细讲解不完整条件语句
在前面所述的例子中,当 a 为 0 时,没有值赋给 e 。因此, e 将保存原来的值,直到 a 变为 1 。此行为与锁存器的特性相同。
语法详细讲解不完整条件语句
module comcase(a, b, c, d, e);
input a, b, c, d;
output e;
reg e;
always @(a or b or c or d)
case ({a,b})
2’b11: e=d;
2’b10: e=~c;
default: e=‘bx;
endmodule
语法详细讲解带有缺省项的完整条件语句
module compif(a, b, c, d, e);input a, b, c, d;output e;reg e;always @(a or b or c or d) if (a&b) e=d; else if (a&~b) e=~c; else e=‘bx;endmodule
语法详细讲解带有缺省项的完整条件语句
在前面的例子中,虽然没有定义所有可能的选择,但为没有定义的选择定义了缺省的行为。因此,它们都是纯的组合逻辑,并没有产生额外的锁存器。
语法详细讲解带有缺省项的完整条件语句
module dircase(a, b, c, d)
input b, c;
input [1:0] a;
output d;
reg d;
always @(a or b or c)
case (a) //ambit synthesis case = full
2’b00: d=b;
2’b01: d=c;
endcase
endmodule
语法详细讲解带有指令的完整 case 语句
在此例中,虽然没有定义所有可能的选择,但其中的指令通知优化器没有定义的选择将不会发生。此例为纯组合逻辑,不会产生锁存器。
当设置了 case 指令为 full 时,也可从 case 语句中综合出锁存器。示例如下:
module select ( a, b, sl);
input [1:0] sl;
output a, b;
reg a, b;
always @(sl)
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
语法详细讲解 case 指令的例外情况
2‘b11: b=1;
default: begin a=‘bx; b=‘bx; end
endcase
endmodule
语法详细讲解 case 指令的例外情况
函数不包含时间控制,因此它们综合为组合逻辑。可以在过程块和连续赋值语句中调用函数。
下面是 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);
语法详细讲解 函数
else
forand=0;
endfunction
endmodule
语法详细讲解 函数
任务仅用于测试模块中,因为: 不包含时间控制的任务与函数的作用相似。 包含时间控制的任务是不可综合的。下面的 or/and 块中调用了任务:module orandtask(out, a, b, c, d, e);
input a, b, c, d, e;
output out; reg out;
always @(a or b or c or d or e)
orand(out, a, b, c, d, e);
task orand;
input a, b, c, d, e;
语法详细讲解 任务
output out;
if(e==1)
out=(a|b)&(c|d);
else
out=0;
endtask
endmodule
语法详细讲解 任务
在 always 块中,如果没有规定所有的条件,则会产生锁存器。在下面的例子中,当 enable 为低电平时,没有定义怎样处理 q 和 data ,因此 data 的值将会被保存下来。综合器必须使用存储元件来编译此逻辑。
module latch(q, data, enable);input data,enable;output q;reg q;always @(enable or data) if(enable) q=data;endmodule
语法详细讲解 怎样产生锁存器
综合工具一般只支持同步反馈,而不支持组合反馈。 在条件语句每个分支中,当过程块没有为每个输出赋值时,就会
产生反馈
语法详细讲解 怎样产生同步反馈
产生反馈:module dffn(q,d,clk,en);
input d, clk, en;
output q;
reg q;
always @(negedge clk)
if(en)
q<=d;
endmodule
不产生反馈:module dffn(q,d,clk,en);input d, clk, en;output q;reg q;always @(negedge clk) if(en) q<=d; else q=‘bx;endmodule
赋值的类型的选择取决于建模的逻辑类型 在时序块的 RTL 代码中使用非阻塞赋值。 非阻塞赋值在块结束后才完成赋值操作,此赋值方式可以避免在
仿真出现冒险和竞争现象。 在组合的 RTL 代码中使用阻塞赋值。 使用阻塞方式对一个变量进行赋值时,此变量的值在在赋值语句执行完后就立即改变。
语法详细讲解 阻塞与非阻塞(赋值方式)
使用非阻塞赋值方式进行赋值时,各个赋值语句同步执行;因此,通常在一个时钟沿对临时变量进行赋值,而在另一个时钟沿对其进行采样。
语法详细讲解 阻塞与非阻塞(赋值方式)
下面的模块综合为触发器,其中采用了阻塞赋值方式:module bloc(clk,a,b);input clk, a;output b; reg b;reg y;always @(posedge clk)begin y=a; b=y;endendmodule
下面的模块综合为两个触发器,其中采用了非阻塞赋值方式:module nonbloc(clk,a,b);input clk, a;output b; reg b;reg y;always @(posedge clk)begin y<=a; b<=y;endendmodule
上面的两个例子的综合的结果不同,左边的例子使用了阻塞赋值方式,综合器将其综合为一个触发器。右边的例子使用了非阻塞赋值方式,综合器将其综合为两个触发器, y 将出现在综合列表中,作为第二个触发器的输入。综合结果如下所示:
语法详细讲解 阻塞与非阻塞(赋值方式)
a b
clkclk
a by
复位是可综合风格代码的重要组成部分,通常在有限状态机中使用复位建模。
语法详细讲解 复位建模
同步复位:module sync(q,ck,r,d);input ck, d, r;output q; reg q;always @(negedge ck) if(r) q<=0; else q<=d;endmodule
同步块中的异步复位:module async(q,ck,r,d);input ck, d, r;output q; reg q;always @(negedge ck or
posedge r)
if(r) q<=0; else q<=d;endmodule
在下面的例子中,使用了一个独立的异步复位来实现异步复位。module async(q, ck, r, d);input ck, d, r;output q; reg q;always @(negedge ck) if(!r) q<=d;always @(posedge r) q<=0;endmodule不提倡使用上述风格的异步复位代码,上述代码是不可综合的。在仿真时,若 r 和 ck 同时在同一个时间单元中改变,则结果是不确定的。
语法详细讲解 复位建模
下面的例子演示了更加复杂的复位建模。其中的敏感列表是完全的,因为这是一个锁存器。module latch(q, enable, set, clr, d);input enable, d, set, clr;output q; reg q;always @(enable or set or clr or d) begin if(set) q<=1; else if (clr) q<=0; else if (enable) q<=d; endendmodule
语法详细讲解锁存器复位
语法详细讲解 阻塞与非阻塞(赋值方式)
语法详细讲解
语法详细讲解
语法详细讲解
语法详细讲解Verilog 函数