数字系统设计复习笔记:第九篇
必考。
阻塞赋值对应的电路结构往往与触发沿没有关系,只与输入电平的变化有关系。而非阻塞赋值对应的电路结构往往与触发沿有关系,只有在触发沿时才有可能发生赋值的变化。
阻塞赋值
在描述组合逻辑的always
块中用阻塞赋值,则综合成组合逻辑的电路结构。
为什么称这种赋值为阻塞赋值呢? 这是因为在赋值时先计算等号右手方向( RHS )部分的值,这时赋值语句不允许任何别的 Verilog 语句的干扰,直到现行的赋值完成时刻,即把 RHS 赋值给LHS 的时刻,它才允许别的赋值语句的执行。
一般可综合的阻塞赋值操作在 RHS 不能设定有延迟(即使是零延迟也不允许)。从理论上讲,它与后面的赋值语句只有概念上的先后,而无实质上的延迟。若在 RHS上加延迟,则在延迟期间会阻止赋值语句的执行,延迟后才执行赋值,这种赋值语句是不可综合的,在需要综合的模块设计中不可使用这种风格的代码.
举个例子:
1 | input A, B, Cin; |
如果在一个过程块中阻塞赋值的 RHS 变量正好是另一个过程块中阻塞赋值的 LHS 变量,这两个过程块又用同一个时钟沿触发,这时阻塞赋值操作会出现问题,即如果阻塞赋值的顺序安排不好,就会出现竞争。
不能用同一个时钟沿触发两个阻塞赋值
1 | module fbosc1 (y1, y2, clk, rst); |
两个always
块是并行执行的。
若复位信号已从1 到 0,且例中第一个always
块的有效时钟沿比下面的always
块的时钟沿早几个皮秒(由时钟偏差造成)到达,则 y1 和 y2 都会取 1;
而若下面的那个always
块的有效时钟沿早几个皮秒到达,则 y1 和 y2 都会取 0。
说明这个 Verilog 模块是不稳定的, 必定会产生冒险和竞争的情况。
非阻塞赋值
在赋值操作时刻开始时计算非阻塞赋值符的 RHS 表达式,赋值操作结束时刻才更新LHS。在计算非阻塞赋值的RHS表达式和更新LHS期间,其他的Verilog 语句,包括其他的Verilog 非阻塞赋值语句都能同时计算 RHS 表达式和更新LHS。
使用赋值号<=
的过程赋值,指过程语句的RHS执行完,立刻执行下一个语句,而预定在将来某个时刻完成赋值(有延时),没有延时的话就在当前时间步完成赋值.
1 | module evaluates2(out); |
- 仿真器对RHS表达式求值,安排新值的赋值在过程时间控制指定的时间发生;在5时刻,在c的上升沿前,a=0,b=1
- 在时间步结束后、指定的延时已经过去或者适当的事件发生的时刻,仿真器对LHS赋值,更新LHS的值。在c的上升沿后,a=1,b=0
时序与逻辑的编程要点
- 时序电路建模时,用非阻塞赋值。
- 锁存器电路建模时,用非阻塞赋值。//不推荐用锁存器
- 用 always 块建立组合逻辑模型时,用阻塞赋值。
- 在同一个 always 块中建立时序和组合逻辑电路时,用非阻塞赋值。
- 在同一个 always 块中不要既用非阻塞赋值又用阻塞赋值。
- 不要在一个以上的 always 块中为同一个变量赋值。
- 用
$strobe
系统任务来显示用非阻塞赋值的变量值。 - 在赋值时不要使用 #0 延迟。
层次化事件队列
所谓层次化事件队列指的是用于调度仿真事件的不同的Verilog 事件队列。在 IEEE1364-1995Verilog标准的 5.3 节中定义了层次化事件队列在逻辑上分为用于当前仿真时间的 4 个不同的队列,和用于下一段仿真时间的若干个附加队列。
从图中可知,$display
所在的事件层次是活跃事件,而其中的事件发生顺序是随机的。因此$display
不能用于监视被非阻塞赋值的变量。
相对于$display
,$monitor
和$strobe
所在的事件层次是监控事件,和非阻塞赋值更新事件及活跃事件独立,因此可以很好的监控被非阻塞赋值的变量。
自触发的always块
有关always块的内容,请参阅:
一般而言,Verilog的always
块不能触发自己,如下面关于使用阻塞赋值的非自触发振荡器等。
我不触发我自己
1 | module osc1 (clk); |
上例在 initial 块中,经过 10 个单位时间的延迟,clk被立即阻塞赋值为 0 。
当 clk 电平从不定态变为 0 的事件发生时,使always
块的@(clk)
条件触发,经过10个单位时间的延迟,计算 RHS 表达式~clk
得到 1,并立即更新 LHS 的值,clk立即被赋予 1 。
由于在此期间不允许其他语句的干扰,即always
循环回到判断触发条件@(clk)
,由于此时 clk 电平已经为 1,无法感知从 0 到 1 曾经发生过的变化,所以就阻塞在那里,只有等待 clk 变为 0 才能进入下一句。
因此,这是一个不能自触发的振荡器,不能产生时钟波形。
我触发我自己
1 | module osc2 (clk); |
@(clk)
的第一次触发之后,非阻塞赋值的 RHS 表达式便计算岀来,并把值賦给 LHS 的事件并安排在更新事件队列中。
在非阻塞赋值更新事件队列被激活之前,又遇到了@(clk)
触发语句,并且always
块再次对clk 的值变化产生反应。
当非阻塞 LHS的值在同一时刻被更新时,@(clk)
再一次触发。
该例是自触发式,虽例中的代码能产生周期时钟信号, 但在编写仿真测试模块时不推荐使用这种写法的时钟信号源。
还是用initial+forever
吧。
线性反馈移位寄存器
线性反馈移位寄存器( LinearFeedbackShift-Register , LFSR )是带反馈回路的时序逻辑。常用于内建自测试,伪随机数生成,加密解密,数据完整性检验,数据压缩等。
1 | module lfsrb2 (q3, clk, pre_n); |
时序和组合的混合逻辑
将简单的组合逻辑和时序逻辑写在一起很方便。当把组合逻辑和时序逻辑写入到一个always
块中时,应遵从时序逻辑建模的原则,使用非阻塞赋值。
1 | module nbex2 (q, a, b, clk, rst_n); |
Verilog 语法并没有禁止将阻塞和非阻塞赋值自由地组合在一个always
块里。 虽然 Verilog 语法是允许这种写法, 但不建议在可综合模块的编写中采用这种风格。
不建议的风格
1 | //(应尽量避免使用这种风格的代码,在可综合模块中应严禁使用) |
禁止对同一变量既进行阻塞赋值,又进行非阻塞赋值
1 | module ba_nba6 (q, a, b, clk, rst_n); |
上面的代码会产生综合错误。
严禁在多个always块中对同一个变量赋值
1 | module badcode1 (q, d1, d2, clk, |
上面的代码会产生竞争冒险。
对非阻塞赋值的误解
$display
关于$strobe/$display
,可以参阅之前的笔记:
非阻塞语句的赋值在所有的$display
命令执行以后才更新数值。
1 | module display_cmds; |
#0延时
若延时表达式值为0,称为显式零延时。
零延时使当前模拟时间里并行执行的其他语句都执行完了以后,才执行0延时的语句。尽量不要使用零延时控制。
#0延时会将赋值事件强制加入停止运行事件队列中。
1 | module nb_schedule1; |
对同一变量进行多次非阻塞赋值
Verilog 标准定义了在同一个always
块中,可对某同一变量进行多次非阻塞赋值,但在多次赋值中,只有最后一次赋值对该变量起作用。
常见错误
在for循环下标中使用++、--
SystemVerilog支持这种写法,但是VHDL2001不支持。考试不许写。
在多个门原语实例时,实例之间用分号
正确的写法是:
1 | not NT1(NA,A), |
不在initial或always语句中就使用过程语句
多个过程语句没有用begin...end
在always语句中少了事件控制敏感量表
1 | module mux(out, a, b, select); |
上面的代码中,always语句由于没有时序控制,会进入死循环,仿真器时间永远不会向前走。
在always语句描述组合逻辑时,应该把所有的输入变量(always中所有被读到的变量)放在事件控制敏感量表中。