未闻verilog---时序模型
时序模型
分层事件列解释了事件调度运行的顺序和策略,但是它没有很好的解释仿真器是如何处理语句中的时序控制部分,所以需要学习时序模型。
仿真器的事件推进模型是时序模型,它反映了仿真时间如何推进以及事件如何调度。
敏感表:是仿真模型的输入表,它由接受新值的元素组成,当输入发生变化时,它能显示哪些输入的变化会导致仿真模型的执行。
扇出表:由将产生新值的元素组成,他能表示当一个事件发生时需要计算哪些元素。
时序模型分为:门级时序模型,过程时序模型。
门级时序模型
Verilog门级时序模型主要用于所有的连续赋值语句,过程连续赋值语句,门原语,用户自定义原语。
任意时刻任意输入发生变化时,门实例将重新计算他们的输出,如果输出有变化,以后可能会产生一个新的事件。所有的输入对变化总是敏感的,这种变化又将导致仿真模型的执行。(输入有变化,将激活门级更新事件)
假设存在一个表示门级时序模型的特定元素的一个事件已被调度,但尚未执行,如果该元素的输出又导致产生一个新事件,那么撤销先前已调度的事件,并调度新的事件。
所以Verilog门级模型具有惯性延时。门级时序模型非常精确地模拟了电路中的惯性延时。
过程时序模型
过程时序模型的敏感性依赖于控制的上下文,它不是对任何时刻的任何变化都敏感。一般来说,Initial和always语句只对输入的一个子集(输入的一部分)敏感,这种敏感性是随着仿真执行时间而改变的,因此这些敏感元素是根据行为模型的当前执行部分来确定的。比如always语句对@ 符号后面括号里的变量敏感。(如果没有运行到@敏感列表处,就不会对敏感列表中的元素敏感)
事件调度方面,假设一个寄存器的一个更新事件已经被调度,如果再调度一个寄存器的另一个更新事件,即使再同一个时刻,前一个事件也不会被取消。所以,一个实体(比如寄存器)的事件列表中可能有多个事件,如果同时有几个更新事件,那么他们的执行顺序是不确定的(如果有begin...end块存在则约束了语句运行的顺序,当然这个时候更新事件就不同时了,这里的同时应该指的是不同快之间对同一个寄存器的更新事件)。
总而言之,门级过程模型会在新的事件激活时,会撤销先前的已调度但未执行的事件。过程结构语句则先前的事件和之后的事件会并存。
一般来说,门级时序模型和过程时序模型定义了语言中的两大类元件:门机时序模型主要用于对组合逻辑建模,而过程时序模型主要用于对时序逻辑建模,例如触发器和锁存器。
惯性延迟和传输延时
前面提到了门级时序模型非常精确地模拟了电路中的惯性延时,那什么是惯性延时,什么是传输延时?
电路中存在着两种延时:惯性延时(Inertial delay)和传输延时(Transport delay)
惯性延迟
假设一个与非门电路,门延时是5ns,那么任何小于这个延时值的输入变化都不会对输出造成影响。
由于输入变化过快,小于门本身的延时值,导致输出无响应的特性,被称为电路的惯性延时,也就是说电路具有一定的惯性或者惰性。
一般来说,Verilog的门级时序模型是具有纯惯性延时的模型。
传输延时
假设存在一条延时5ns的传输线(信号通过传输线也需要时间),输入端的任何变化经过延时值都会在输出端显示出来,这种叫做电路的**传输延时。 **(就是说不管输入的变化速度如何,都能在输出端反映出来)
对非阻塞赋值的右式(RHS)加延迟参数,可以模拟传输延时。
延时
连续赋值加延时
注意:对于连续赋值加延时,延时只能加在assign与变量之间,不能放在等号后面。
这里举个例子
assign #50 a = b;
这里的意思是将b的值延迟50个时间单位赋给a。
但是通过仿真可以看出,如果b的变化时间小于50个时间单位,b的变化并不能在a上显示出来,只有当b的变化时间大于50个时间单位,才能在50个时间单位后显示出来。
分析一下,比如b的变换时间是30个时间单位。因为连续赋值是门级时序模型,无论c何时发生变化,都会以及产生并执行一个计算事件,计算等号右边的值,同时产生一个更新事件,把值赋给a,但并没有执行,只是把这一更新事件调度到当前仿真以后50个时间单位去执行。
前面我们说过,门级时序模型,新激活的事件会撤销之前已经激活但没有执行的事件。当b从低电平拉高到高电平,这时激活一个事件将高电平(1)赋给a,但没有立即执行,需要等到50个时间单位之后。当过了30个时间单位,b的电平从高电平拉到低电平,这时有激活了一个事件将低电平(0)赋给啊,但没有立即执行,需要等到50个事件单位之后,因为之前存在一个没有执行的事件,所以这个把a赋值为0的事件将把a赋值为1的事件给撤销掉,所以当b拉高电平之后50个时间单位时,a的值并没有拉高,因为这个事件被把a赋值为0的事件给撤销掉了。所以最后a的值看起来并没有将b的变化延迟50个单位输出。
如果b的变化时间为80个时间单位,当b从低电平拉高为高电平时,激活了一个事件将高电平(1)赋值给a,这个事件被激活但没有立即执行,等过了50个事件单位,才执行,从开始计数80个时间单位,b从高电平拉低,激活了将a赋值为0的事件,事件没有立即执行,等待了50个事件单位才执行将a赋值为0,这时距一开始b拉高已经过了150个时间单位,所以从外面看看来,a将b80个时间单位的变化输出出来了。
所以连续赋值加延时将高于本身延时的信号变化显示出来,但小于本身延时的信号变化却不能显示出来。
阻塞赋值加延时
对于阻塞赋值加延时,有两种方式
always @(a or b)
begin
#5 sum = a + b; //语句A
end
always @(a or b)
begin
sum = #5 a + b; //语句B
end
对于语句A,假设现在的时间为T,当a或b发生变化时,always块被触发,导致always语句开始执行,然后遇到了#5,立刻将该always进程挂起(这里的挂起是因为遇到了延时),等5个时间单位后,即T+5时刻,再将这个时刻(T+5)的a和b相加赋值给sum。这时的sum为T+5时刻的a与b之和。在这期间由于延时将always块挂起,所以a和b的任何变化都被忽略。
对于语句B,假设现在的时间为T,当a或b发生变化时,always块被触发,导致always语句开始执行,先将T时刻的a和b进行求和,由于存在#5,所以需要延迟5个时间单位再将值赋值给sum,因为阻塞赋值会将块内的其他语句阻塞,所以在等待延时时会将always块挂起(这里的挂起是因为阻塞赋值不能完成更新,阻塞了整个块),等5个时间单位后(T+5时刻)将T时刻的a和b之和赋值给sum。因为中间always块被挂起,所以在这段时间里a和b的任何变化都被忽略。
(当延时加在语句的前面,会阻塞整个块,之后等延时结束在用当前的数据进行语句的运算,当延时加在等号之后,则先用当前时刻的数据计算后面的计算式,再延时,之后等延时结束再更新左值)
从上面的分析可以看出,阻塞赋值加延时,中间输入变量的任何变化都被忽略掉,不能模拟任何电路中的延时类型,容易被忽视,也容易一出错,不建议使用。
非阻塞赋值加延时
对于非阻塞赋值加延时,有两种方式
always @(a or b)
begin
#5 sum <= a + b; //语句A
end
always @(a or b)
begin
sum <= #5 a + b; //语句B
end
对于语句A,假设现在的时间为T,当a或b发生变化时,always块被触发,导致always语句开始执行,然后遇到了#5,立刻将该always进程挂起(这里的挂起是因为遇到了延时),等5个时间单位后,即T+5时刻,再将这个时刻(T+5)的a和b相加赋值给sum。这时的sum为T+5时刻的a与b之和。在这期间由于延时将always块挂起,所以a和b的任何变化都被忽略。
对于语句B,假设现在的时间为T,当a或b发生变化时,always块被触发,导致always语句开始执行,先将T时刻的a和b进行求和,由于存在#5,所以需要延迟5个时间单位再将值赋值给sum,因为非阻塞赋值不会将块内的其他语句阻塞,所以在等待延时时always块将运行结束,等5个时间单位后(T+5时刻)将T时刻的a和b之和赋值给sum。因为中间always块已经结束,always块再次恢复对a和b的敏感,所以在这段时间里a和b的任何变化都不能被忽略,由前面结构时序模型的特点可以知道,在等待延时的这段时间内,如果a和b再次发生变化将再次触发always块,会产生新的更新事件,新的事件不会撤销前面的事件,会排在前面事件的后面,所以输入信号任何时间长度的变化都会在输出上有所反应,所以对非阻塞赋值的右值加延时可以准确的模拟出电路中的传输延时。
(对于非阻塞赋值来说,在右值前面加延时只会使非阻塞赋值的左值更新被挂起,不会挂起整个语句,也不会阻塞后面语句的运行)
参考 《轻松成为设计高手》