本文主要学习 Verilog 的仿真特性, 以及仿真器对 Verilog 的处理, 算是对 Verilog 知识的增量学习. 本文内容与我的另一篇博文 (http://www.cnblogs.com/IClearner/p/7262653.html) 一些有重叠的内容.
一, Verilog 仿真特性
虽然现在 SystemVerilog 在仿真验证中占据主流的位置, 不过了解一下 Verilog 是如何仿真的, 对以后学习 systemverilog 也是有帮助的. 本文主要学习 verilog 的一些仿真特性, 因为一方面, 若是写的代码质量不高, 那么不同的仿真器 / 工具在进行仿真的时候, 对 verilog 解释有可能不一样导致仿真结果不一样; 另一方面, 由于在 Verilog HDL 语言的 IEEE 标准中, 其语义采用非形式化的描述方法, 因此不同厂家的仿真工具, 综合语句的后台策略肯定存在差异, 同一段代码在不同仿真软件的运行结果可能是不同的. 因此我们首先要了解一下 verilog 的仿真特性, 避免写出 让仿真器产生歧义结果的 代码.
1. 仿真时间(当前仿真时间与将来仿真时间)
仿真时间是指由仿真器维护的时间值, 用来对仿真电路所用的真实时间进行建模.
0 时刻为仿真起始时刻. 当仿真时间推进到某一个时间点时, 该时间点就被称为当前仿真时间, 而以后的任何时刻都被称为将来仿真时间.
注意: 仿真时间只是对电路行为的一个时间标记, 和仿真程序在 PC 机上的运行时间没有关系. 对于一个很复杂的程序, 尽管只需要很短的仿真时间, 也需要仿真器运行较长的时间; 而对于简单的程序, 即时仿真很长时间, 也只需要短的运行时间. 下图中, 最上面一栏的以 "ns" 为单位的数字就是仿真时间. 本质上, 仿真时间是没有单位的, 之所以会出现图 "**ns", 则是由于 `timescale 语句的定义导致.[
.`timescale 用于定义延时的单位和延时的精度, 如 `timescale 1ns/100ps 那么时间单位就是 1ns, 精度就是 100ps.
. 时间单位, 表示了仿真时测量的单位, 比如延时 1,1ns; 精度则表示仿真器只识别的范围, 比如精度是 100ps, 那么如果你 1.3ns, 编译器是识别, 但是如果写 1.32, 那么由于精度达不到那么细, 所以 0.02 被四舍五入掉.
.`timescale 影响着全部模块, 知道遇到另外的 `timescale.]
2. 事件驱动的仿真特性
Verilog 具有事件驱动的仿真特性. 我们首先来了解一下什么是事件(可以从下面三个方面理解):
Verilog 里面可以定义事件(event), 可以对 testbench 里面的信号进行监测, 满足事件触发条件就触发事件并引发相应的处理, Verilog 里面的 event 只用于仿真, 不能综合.
例如, 设计者可以定义 "输出变化" 或者 "输入变化" 为一个事件, 如边沿敏感事件, 电平敏感事件.
阻塞赋值, 非阻塞赋值, 某些系统函数也可以是事件.
verilog 中的某个行为可以看成是事件, 而 verilog 的仿真时间, 就是由某些事件驱动的. 打个可能不是很恰当的比方:
. 学霸给自己一天的生活规划是: 早上起床吃早餐然后上课, 中午会寝室休息然后下午去上课, 晚上去图书馆仔细然后回寝休息.
.(一天中, 给人一种: 学霸所做的事 驱动学霸的时间 往下 流动的感觉, 即学霸做事驱动学霸不断生活; emmm 貌似给人一种我思故我在的感觉...)
. 学霸一天的生活 比作 仿真过程, 学霸的某个行为 (比如吃早餐) 就像是 verilog 的某个行为 / 事件(比如赋值), 学霸的一天就是在这些行为中驱动的, 如同 verilog 是事件驱动的那样(如: assign 赋值事件在驱动这你 verilog 的仿真, always 也是驱动着你的仿真等等), 说到这里, 应该知道 verilog 的事件到底是什么了吧
附注: 另外一种简单的理解是, 事件是一个信号的变化引起了其他一个信号的变化, 如果没有引起其他信号的变化, 则不叫事件.
注意: 如果没有事件驱动, 控制仿真时间将不会前进, 但并不是所有的事件都能驱动仿真时间, 仿真时间只能被下列事件中的一种来推进:
. 定义过的门级或者线传输延时;
. 更新时间(指线网, 寄存器数值的任何改变, 如上面提到的输入输出变化);
."#" 的事件控制;
."always" 关键字引入的事件控制;
."wait" 的等待语句
所有的仿真事件都是严格按照仿真时间向前推进的, 也就是说在恰当的时间执行恰当的操作, 事实上, 上述事件都是循环, 相互触发, 来共同推动仿真时间的前进. 如果在同一仿真时刻有多个事件需要执行, 那么首先需要根据它们之间的优先级来判定谁先执行. 如果优先级相同, 则不同仿真器的执行方式是不同的, 有可能随机, 也有可能按照代码出现的顺序来执行. 大多数仿真器采用后一种方法.
到这里应该知道 Verilog 具有事件驱动的仿真特性是什么回事了吧.
二, Verilog 的分层事件队列与 (VCS) 仿真器的工作原理
学过 verilog 应该都知道(至少听说过)verilog 具有并发性含义(如 assign,always 语句等是并行的), 在仿真中, Verilog HDL 语句也是串行执行的, 其面向硬件的并行特性则通过其语言含义 / 语义来实现的. 也就是说,, 虽然在仿真中所有代码是串行执行的, 但由于语法语义的存在, 并不会丢失代码的并行含义和特征.
那么 verilog 在仿真器里面是怎么用串行 来体现 并行的含义 的呢?
答: 你 verilog 不是有各种事件么? 我仿真器先把你 verilog 的事件进行分层(即 Verilog 的分层事件队列), 再进行分层, 一层一层地执行. 具体来说就是:
Verilog 具有离散事件时间仿真器的特性, 也就是说在离散的时间点, 预先安排好各个事件, 并将它们按照时间顺序排成事件等待队列. 最先发生的事件排在等待队列的最前面, 而较迟发生的事件依次放在其后. 仿真器总是为当前仿真时间移动整个事件队列, 并启动相应的进程. 在运行的过程中, 有可能为后续进程生成更多的事件放置在队列中适当的位置. 只有当前时刻所有的事件都运行结束后, 仿真器才将仿真时间向前推进, 去运行排在事件队列最前面的下一个事件.
其中, 活跃事件的优先级最高(最先执行), 而监控事件的优先级最低, 而且在活跃事件中的各事件的执行顺序是随机的(注: 为方便起见, 在一般的仿真器中, 对同一区域的不同事件是按照调度的先后关系执行的, 也就是哪个事件的代码在前面, 就先执行哪个事件). 仿真器首先按照仿真时间对事件进行排序, 然后在当前仿真时间里按照事件的优先级顺序进行排序. 活跃事件是优先级最高的事件, 非活跃事件的优先级次之, 非阻塞赋值的优先级为第三, 监控事件的优先级第四; 将来事件的优先级最低. 将来仿真时间内的所有事件都将暂存到将来事件队列中, 当仿真进程推进到某个时刻后, 该时刻所有的事件都会被加入当前仿真事件队列内. 在 Verilog 中, 事件队列可以划分为 5 个不同的区域, 不同的事件根据规定放在不同的区域内, 按照优先级的高低决定执行的先后顺序, 下图列出了部分 Verilog 分层事件队列:
由上图可以知道, 阻塞赋值属于活跃事件, 会立刻执行, 这就是阻塞赋值 "计算完毕, 立刻更新" 的原因. 此外, 由于在分层事件队列中, 只有将活跃事件中排在前面的事件调出, 并执行完毕后, 才能够执行下面的事件.
仿真过程模型如下所示:
用具体的流程图来如下所示:
开始仿真时即
A: 它就会同时读进的 initial, always,assign 等这些语句块, 然后等第一个事件发生(比如某个上升沿), 进入
B: 读进来之后,(VCS)仿真器会把这些语句按照固定的顺序写进一个队列里面(比如 always 中 begin...end 块语句, 会把块内的语句按照原来的顺序写入), 然后先执行没有延时的语句(比如没有 #的延时, 初始化的变量表达式等). 当执行了一部分语句的时候, 也就是执行完没有延时的语句之后, 进入
C: 仿真器把当前时间置为 0, 也就是说现在电路的运行时候的仿真才真正开始, 这个就是电路真正运行的时间起点. 设置完之后, 进入
D(以后就是分层的仿真序列了): 仿真器就进入了一个 active 的区域, 在这个区域里面, 仿真器主要执行一些原语(如 UDP),display 系统函数, 无延时的 assign, 阻塞赋值(一步到位), 非阻塞赋值(只执行右边的计算, 赋值未完全完成). 然后进入
E:inactive 区, 如有 #的延时, 这个区域处理一些延时. 然后进入
F: 非阻塞赋值区, 这个区域进行完成非阻塞的赋值. 然后进入
G:monitor 区, 也就是执行监视系统函数的区域, 如执行 $monitor 系列的函数.(这样就使得监视语句有了不同的用法, 比如监视非阻塞赋值时, 你用 display 由于在 active 区, 所以只能监测到旧值, 因为非阻塞赋值还没有完全完成而用 monitor 则可以检测到非阻塞赋值完成后的新值)最后进入
H:future 区, 进行处理一些其他的命令和代码.
今天就先学习了这些, 这些内容了解一下比较好, 若是能记住, 那是最好不过的了(记不住的时候翻多几遍就记住了). 下一篇计划开始学习 VCS 了.
来源: https://www.cnblogs.com/IClearner/p/8797323.html