当前课程知识点:IC设计与方法 > 3、Verilog语法 > k) 面向测试的Verilog语法(2) > 3-3-2 面向测试的Verilog语法(2)
在描述正式的testbench之前
我们还要再接触一些新的Verilog语法知识
在进行电路测试的时候
通常我们会用到一些Verilog所定义的
系统任务和函数来观测电路的输出
控制电路的行为
最常见的大概有这四类
大家注意
在这四类语句之前都有一个$符号
凡是$符号打头的这些语句
都表示是Verilog系统所定义好的
任务和函数
我们可以直接调用
最常见的有这四大类
第一个是$time语句
用来显示当前的时间
仿真器当前已经运行到什么时刻了
第二类是$display和$monitor语句
用来显示或者说观测我们所需要关注的
信号
第三类($stop)是让仿真器能够停止下来
暂停下来
第四类$finish语句
是让整个仿真器完全停止下来
在实际的物理电路设计的时候
一般来说是不允许使用延时语句来描述电路的
时间特性的
但是在仿真的时候
我们需要描述电路的时间特性 时序特性
例如我们需要描述时钟周期信号
时钟clock信号的周期
它的频率等等这些特性
描述复位信号的时候
我们(需要)定义复位信号的脉冲
到底在什么时刻发生
所以这时候我们要用到
Verilog一个新的语法知识
#符号
#符号是Verilog所定义的
用来描述延时相关的特征的
我们来看几个例子
#在Verilog里有多个用处
一个是在测试的时候
我们可以用这样的形式表示
复位信号rst=1是在10个时间单位后
注意这里面不是绝对时间
是10个时间单位之后
把复位信号变成1
用这种方式来描述一些信号的物理延时
第二个是在元件例化的时候
我们可以用一个#号来表示实际元器件的延时
第三种情况是
用#号也可以实现元器件参数的传递
反单引号是Verilog所定义的一些特殊的语句
以便我们的代码去包含一些其它的语句段
或者定义一些常量
常见的反单引号大致有这四类
我们在这门课里面只简单地介绍常见的
例如`define语句
用来定义一些特殊或者说定义一些常用的常数
`include语句用来包含一些Verilog语句段
一些在整个项目工程里边都会用到的Verilog语句段
我们可以把它写在一个单独的文件里
然后用`include把它包含进来
`include语句的效果就相当于
把另一个文件完整的拷贝到这个文件里面来
`timescale刚才已经说了
描述电路的时间特性
`resetall用来把所有的
编译引导恢复到缺省状态
我们来看一下`include语句的例子
下面是三个简单的例子
例如我们在一个project里
通常会有好多Verilog文件
分别描述不同的模块
如果在这些Verilog文件里
都用到公用的常数
公用的参数以及公用的定义
那么我们可以
把它写到一个公用的global.v文件里面
然后用这条语句把它包含起来
另一种方式
就是把我们电路下面的一个子电路
整个包含进来
例如我们可以把
某个counter.v包含到当前设计里面来
第三种情况是我们的设计用到了一些底层的
库文件
在ASIC设计里面
经常会用到一些底层的库文件
我们可以直接把库文件
包含到我们这个文件里面来
这是`include的三种常见用法
最后我们再来详细看一下`timescale语句
刚才已经说了
`timescale是用来描述我们的
代码里面的时间单位
以及仿真的精度的
通常`timescale是放在代码的第一行
我们来看一个具体的例子
在这个例子里
`timescale描述了电路的仿真的单位是10个ns
仿真的步长是1个ns
我们再来看具体的例子
例如在这个例子里
用了一个#号
它的单位是2.33
表示这个与门的延时是2.33个时间单位
2.33个时间单位代表的
实际的物理意义是什么呢
我们就要把2.33乘以一个时间单位
我们得到的数字是23.3
也就是说
这个与门的真实的延时是23.3ns
由于我们设的仿真精度是1个ns
所以四舍五入以后
这个与门的实际延时是23ns
通过这种方式
Verilog在每一个门的描述的时候
定义的是一个抽象的时间
然后通过时间单位和时间精度
可以描述出具体的准确的时间
下面我们再来看一下counter设计
也就是说我们的被测设计
在这个被测设计里
我们定义的counter有三个(输入输出)信号
有时钟信号 复位信号以及输出信号
时钟信号和复位信号是输入信号
输出信号是out
是一个4位的信号
由于这个4位的
输出信号out是一个always语句产生的
所以我们要把它定义成reg类型
这个电路是一个时序电路
在时钟的上升沿工作
复位信号是一个低电平有效的复位信号
所以先判断复位信号
当复位信号有效的时候
计数器被清零
复位信号无效的时候
每一个时钟有效沿
也就是时钟上升沿计数器加1
这就是一个非常简单的
具备低电平有效的异步清零的“加计数器”
我们再来看一下
怎么在testbench里测试这个电路
testbench的第一部分
描述了时间特性
也就是我们这个仿真的时间单位是1个ns
也就是说我们后面代码里所有的
跟时间相关的常数
它的单位都是1个ns
第二个是我们仿真的精度是1个ns
我们的测试平台起的名字叫top
它没有任何输入和输出信号
这一段Verilog代码是
和我们前面测试平台的那张图是一一对应的
在测试平台里面底层里面一共有三根信号线
是时钟、复位和dout
时钟和复位信号
我们应该定义成什么类型呢
取决于这两个信号是怎么产生的
在我们这段代码里
我们看一下右边
rst信号和左下角的clk信号
都是由initial块产生的
所以我们在定义的时候
需要把它定义成reg类型
dout信号在刚才的counter里
dout信号是由一个always块产生的
所以在counter里
它是定义成reg类型
但是在这个电路里
dout信号是是由一个元件例化语句产生的
并不是always
或者是initial语句来产生的
所以我们这里就把它定义成wire类型
这跟我们上节课讲的
在RTL电路设计里面的规则是一样的
我们观测一个信号到底需要定义成reg类型
还是wire类型
只需要去观测在当前这个层次
它到底是由谁产生的
只有initial
或者always语句产生的信号
我们才需要把它定义成是reg类型
这是前面这部分
信号的定义
再下一部分是元件例化
把我们的被测设计例化出来
采用元件例化语句
把counter例化成uut
然后它的三个信号依次连接到电路板上的
这三个信号上
再往下我们就要产生clk信号和复位信号(rst)了
在这里边我们用到了一个新的语句叫forever
顾名思义
是一个循环语句
这段代码我们可以看一下
它是一个类似于八股的代码
用来产生一个周期信号
首先为clk赋初值0
在0时刻的时候给它赋一个0
然后用循环语句
每过10个时间单位
clk取非
也就是说原来是1就变为0
原来是0就变为1
这段语句
直觉上大家就可以猜的出来
它用来产生一个时钟周期
是20个时间单位的clk信号
高电平10个时间单位
低电平也是10个时间单位
所以大家就知道了
任何一个电路
如果要产生时钟信号
就可以用类似这段代码来产生
然后通过修改这个数值
就可以改变时钟周期
通过修改前面这个值
就可以设置时钟的信号的初值是0
或者是1
这是周期信号
对于非周期信号呢
我们用这段代码来产生
复位信号
一开始的时候它的初值是1
经过15个时间单位以后
注意我们这里边用的是阻塞赋值
所以下一条语句是经过15个时间单位之后
才把复位信号置成0
因为是阻塞赋值语句
所以要再经过10个时间单位之后
赋值信号才变成1
然后再经过175个时间单位之后
$finish让整个仿真器停下来
也就是说仿真器在全部加起来
大约是200个时间单位之后
仿真器就能够停下来了
这段语句不但能够产生复位信号
还能够控制仿真器在所有的
信号结束之后停下来
所以用这两段语句
为我们的电路产生了电路所需要的
两种输入信号
最后是一段代码
用来观测电路的输出
前面我们说过
观测电路的输出
可以用两类
一类是用$display语句
另外一个是用$monitor语句
$monitor语句表示观测哪几个信号
在这里面我们可以看到
我们观测了rst、clk和dout
并且按照指定的格式显示出来
这条语句的
格式和C语言里面的
printf的格式是完全一样的
我这里面就不多说了
这条语句的意思就是说
在任何时刻只要这三个信号的值发生了变化
都按照这种格式
把这三个信号的值显示出来
这段代码我们可以看到
就是完整的测试平台的描述方式
例化元器件
为电路产生输入信号
以及指定观测哪些输出信号
这就是一段标准的Verilog代码
测试平台的代码
在这段代码里面出现了initial和always
下面我们再来看一下initial跟always的区别
always表示它所包含的这段代码会始终循环
不断的执行
initial语句正好相反
它从头到尾只执行一遍
所以initial语句通常只用来干几件事情
第一个是用来产生激励信号
因为它只执行一次
所以可以用来产生前面我们所看到的非周期信号
或者是脉冲信号
第二个initial语句
可以用来指定检测哪些输出信号
第三个我们后面还会说
initial语句还可以用来为某些信号赋一些初值
always语句通常要么用来检查电路的输出
要么用来产生周期性信号
或者用来设计RTL级的代码
我们来看一下怎么用initial语句来产生信号的初值
对于左边这段代码
这段代码就是我们刚才所说的计数器
我们用了一个异步复位信号
rst信号来为计数器赋一个初值
当rst信号有效的时候
初值置成0
对于这段代码
我们必须要为电路设计一个复位信号
实际在工程上
有时候为了使电路的输入端尽量的少
有时候电路的输入信号都没有
这样的电路制造的时候因为少了一个输入端
电路的成本会适当的有所降低
对于性价比要求比较高的电路而言
这种电路方式有它的优势
如果没有这段代码
没有这个复位信号
我们的计数器就变成了右下角这段代码
只有四行(代码)的计数器
这个计数器也能完成正常的计数工作
它也能够从0计到最大值
然后再返回到0
再继续计数
从某种角度来说
这个计数器也是能够正常工作的
但是这样的计数器
在仿真的时候就会出问题了
在仿真器仿真的时候
在物理的绝对的0时刻
这个计数器的初值是不确定的
在物理的绝对0时刻
计数器的初值有可能是从0开始计数
有可能是从1开始计数
对于一个实际的计数器
不管是从0还是从1(开始)
都不是问题
只要它能够循环往复的计数就可以
但是在仿真的时候就有问题了
如果是从0开始计数
它是一种表现情况
从1开始计数就是另一种表现情况
所以仿真器只能判定当前计数器的值是不确定的
是X
然后由于初值是X
所以计数器的以后
整个时刻(轴)上
计数器的值都是X
就会导致我们的仿真器
无法验证这个电路是不是能正常工作
所以我们就要采用一种手段
让仿真器能够工作起来
这就是用initial语句
我们可以用initial语句人为的
为这个计数器赋一个初值
让仿真器能够正常的工作
所以我们总结一下initial语句它的
用处仅仅是为了让仿真器能够正常工作
而对实际的物理电路没有任何的影响
这是initial语句的一个用处
下面我们再来看一下
激励信号产生的几种方式
我们说了
时钟信号可以用左边的模板来产生
在初始的时候
为时钟信号赋一个初值
然后周期地每工作10个时间单位是高电平
再10个时间单位是低电平
所以用这种方式产生
周期是20个时间单位的时钟信号
结合`timescale语句
我们可以描述出比如20ns
或者是其它周期的时钟信号
对于非周期信号
例如复位信号
或者说电路所需要的一些其它信号
比如说控制信号
或者是数据信号
都可以采用下面的这些代码段来产生
在刚才我们的例子里
我们采用的是用阻塞赋值语句
加上延时语句来产生非周期信号的方式
在这个方式里
我们可以看到
所有的延时
这里面的15、10和175
指的是相对于代码开始执行的时候
或者说相对于0时刻的相对时刻
指的是相对时刻
我们以10为例
是指相对于当前时刻而言之后的时间
所以我们说这里面时间都是相对时刻
用initial语句的最常见的一种方式
第二种方式
有时候用相对时间
如们相对时间非常多
那么很难去算
或者说算起来不太方便
有时候我们描述信号的绝对时间
比如我们描述这张图的时候
我们可以描述在15ns的时候
产生一个脉宽是10ns的复位信号
这是一种描述方式
另一种描述方式
我们可以说
在15ns的时候
把复位信号置低
在25ns的时候
绝对时间25ns的时候
复位信号置高
然后在绝对时间是200ns的时候
仿真器结束运行
如果是用这种方式来描述的时候
我们希望是用一种绝对时间来描述它
那么这时候我们可以用fork-join这种语句
如果initial后面的begin和end换成fork和join
表示这里面的fork和join之间的
所有语句都是在同一时刻运行的
所以这段代码就变成了
在0时刻执行第一条语句rst置1
然后在0时刻执行第二条语句
也就是说相对于0时刻而言
在第15个时间单位的时候
把rst置0
然后在相对0时刻而言第25个时间单位的时候
把rst信号置1
从0时刻开始算起
第200个时间单位的时候
仿真器结束运行
这是第二种方式
第三种方式
是用非阻塞赋值语句
前面我们都是用的阻塞赋值语句
如果用非阻塞赋值语句
那么我们是用这样的描述方式
我们把延时的描述
放在赋值号的右边进行描述
这也是一种描述方式
在这种方式里边
由于非阻塞赋值语句的几句话都是同时运行的
所以在这里面的延时也是表述的绝对时刻
这是常见的三种方式
表示非周期信号的方式
大家可以任选一个你所喜欢的方式来使用
在读代码的时候
我们可能三种方式都会遇到
最后我们再来看一下
如何检查电路的输出
我们说可以用$monitor语句
或者说$display语句来检查电路的输出
在这个initial块里边
我们用的是$monitor语句
我们说了$monitor语句
是指当我们所要观测的
信号只要发生变化的时候
都会显示出电路的输出
所以它的输出是这样的情况
在0时刻的时候
(信号)发生了变化
0时刻的时候或者说电路的初值都会显示出来
然后在1时刻的时候
某一信号发生了变化
out发生了变化
所以会把out和in的值都打印一遍
然后在10时刻的时候
in的值发生了变化
所以又打一遍
也就是说
只要信号有变化
只要被观测的信号发生了变化
那么仿真器都会把相关的信号显示出来
$display语句和$monitor语句相比而言
它只是显示在当前时刻的信号值
换句话说$display语句只会执行一次
所以右边这段代码
只会执行一句
这样看来
好像$display语句没有什么太大的意义
$display语句主要的
作用是可以结合延时语句同时工作
它可以指定
比如说在前面加一个延时语句
后面可以再加延时语句
再运行同样的$display
这样就可以实现在指定的时刻观测信号
这样$display语句
如果加上一串的延时语句之后
就可以指定比如说在10ns的时候
观测一次信号
在100ns的时候观测一次信号
在150ns的时候再观测一次信号
就实现了可以在特定时间观测特定信号
前面$monitor语句有点像
在盲目的观测所有的信号
最后我们再总结一下和测试相关的语法知识
我们说
一个是元件例化
可以把被测电路例化到我们的
测试平台里面去
第二
我们可以用initial语句
或者是always语句观测
设置激励信号
产生复位信号 时钟信号
所需要的数据信号
也可以用
initial语句和always语句结合
$monitor、$display语句观测电路的输出
最后我们可以用$finish语句停止仿真
在服务器上运行这样的仿真程序的时候尤其重要
如果我们不用$finish去停止仿真器的运行
仿真器会不断在那运行
耗费CPU的资源
只有用$finish语句
或者手动停止的方式才可以把CPU释放出来
把CPU释放给其它使用服务器的同学
-软件下载说明
-a) 集成电路的应用及市场
-a) 集成电路的应用及市场--作业
-b)集成电路的制造过程
-b)集成电路的制造过程--作业
-c)从CPU的发展看IC的进展
-c)从CPU的发展看IC的进展--作业
-d)从行业的发展看IC的进展
--Video
-d)从行业的发展看IC的进展--作业
-e)从ISSCC看IC的发展方向
--讲课视频
-e)从ISSCC看IC的发展方向--作业
-a)数字系统的实现方法 (ASSP/FPGA/ASIC的对比)
--讲课视频
-a)数字系统的实现方法 (ASSP/FPGA/ASIC的对比)
-b)组合逻辑电路
--Video
-2、数字集成电路设计方法--b)组合逻辑电路
-c)时序逻辑电路(1)
-d)时序逻辑电路(2)
-2、数字集成电路设计方法--d)时序逻辑电路(2)
-a)Verilog的历史和学习要点
--讲课视频
-b)端口、信号及数据类型
--讲课视频
-b)端口、信号及数据类型--作业
-c)逻辑电平及数据操作
--讲课视频
-3、Verilog语法--c)逻辑电平及数据操作
-d)Assign 语句
-e)Assign 举例
-f)Always
-f)Always--作业
-g)阻塞与非阻塞赋值
--Video
-3、Verilog语法--g)阻塞与非阻塞赋值
-h)D触发器的描述
--Video
-i)时序电路的设计
--Video
-i)时序电路的设计--作业
-j) 面向测试的Verilog语法(1)
-k) 面向测试的Verilog语法(2)
-k) 面向测试的Verilog语法(2)--作业
-a)电路设计实例1
--Video
-b)电路设计实例2
--讲课视频
-b)电路设计实例2--作业
-c)电路设计实例3
--讲课视频
-Modelsim仿真
-a)综合及相关基本概念
--Video
-a)综合及相关基本概念--作业
-b)综合及优化
--Video
-c)门级仿真
--门级仿真
-d)Quartus综合及分析(1)
--讲课视频
-e)Quartus综合及分析(2)
--讲课视频
-e)Quartus综合及分析(2)--作业
-f)Quartus综合及分析(3)
--Video
-g)Quartus综合及分析(4)
--Video
-g)Quartus综合及分析(4)--作业