Verilog 14: 阻塞和非阻塞赋值的异同
verilog 的层次化事件队列
仿真器在解析和处理 Verilog 模块时其执行流程如下:
- 动态事件队列 (下列事件执行顺序可任意安排)
- 阻塞赋值
- 计算非阻塞赋值语句右边的表达式
- 连续赋值
- 执行 $display 命令
- 计算原语的输入和输出的变化
- 停止运行的事件队列:#0延时阻塞赋值
- 非阻塞事件队列:更新非阻塞赋值语句LHS(左边变量)的值
- 监控事件队列
- 执行$monitor命令;
- 执行$strobe命令。
- 其它自定义 pli 命令
阻塞和非阻塞赋值的异同
定义
RHS: 右值, 赋值等号右边的表达式或变量
LHS: 左值, 赋值等号左边的表达式或变量
阻塞赋值 =
所谓的阻塞概念是指在同一个 always 块中, 在赋值时先计算 RHS 表达式的值,这时赋值语句不允许任何别的 Verilog 语句干扰,直到赋值完成它才允许别的赋值语句执行。
对于可综合的阻塞赋值语句,因为不允许延时,它与后面的赋值语句实际是一个先后关系,即前面表达式的电路将输出给后面表达式的电路。
仿真时,阻塞赋值会在计算出 RHS 表达式后立即赋值给 LHS,然后执行下一条语句。当并行块中存在变量的相互引用和赋值时,这种立刻发生改变的赋值方式将导致竞争冒险。
例如下面的模块是竞争的,当复位信号由 1 变 0 后
- 若第一个 always 时钟沿先触发, 则 y1 = y2 = 1, 而后 y2 = y1 = 1
- 若第二个 always 时钟沿先触发, 则 y2 = y1 = 0, 而后 y1 = y2 = 0
module fboscl(
input clk, rst,
output y1, y2
);
reg y1, y2;
always @(posedge clk or posedge rst)
if(rst) y1 = 0;
else y1 = y2;
always @(posedge clk or posedge rst)
if(rst) y2 = 1;
else y2 = y1;
endmodule
非阻塞赋值 <=
非阻塞赋值也是先计算 RHS 表达式的值,但赋值操作结束时刻才更新 LHS
非阻塞赋值只能对寄存器类型变量进行赋值,因此只能在过程块中,不允许连续赋值
仿真时,阻塞赋值会在计算出 RHS 表达式将其保存起来,并往下执行,当离开 always 块后才赋值给 LSH。
例如对前一个例子改为非阻塞赋值后,当复位由 1 变为 0 后,不论哪个 always 块的时钟沿先到达,两个 always 块的非阻塞赋值都在赋值开始时刻计算 RHS 表达式,并保存到中间变量。而在离开 always 后才更新 LHS。因此 RHS 计算的结果是确定的。之后赋值给 LHS 的值也是确定的。
module fboscl(
input clk, rst,
output y1, y2
);
reg y1, y2;
always @(posedge clk or posedge rst)
if(rst) y1 <= 0;
else y1 <= y2;
always @(posedge clk or posedge rst)
if(rst) y2 <= 1;
else y2 <= y1;
endmodule
在这个例子中阻塞赋值和非阻塞赋值虽然综合出来的电路是一样的,但是仿真结果却不一样。使用非阻塞赋值使得前仿真和后仿真结果是一致的。
自触发时钟
如下的时钟振荡器使用了阻塞赋值, 在 initial 块,当 clk 由不确定态变为 0 使得 always 块的 @(clk)
条件触发。
always 块触发后先经过 10 个单位时间的延迟,然后计算 RHS 表达式 (~clk)
得到 1 并立即更新 LHS 的值,clk 立即被赋予 1。
当 clk 电平已经为 1,才跳出 always,这时 @(clk)
无法感知从 0 到 1 曾经发生过的变化,所以就阻塞在那,只有等到 clk 变为 0 才能进入下一句。因此这是一个不能自触发的振荡器。
module mod(
output clk
);
initial #10 clk = 0;
always @(clk) #10 clk = ~clk;
endmodule
当采用非阻塞赋值时, @(clk)
的第一次触发之后,RHS 表达式 (~clk)
先被计算出来,但是还没发生实际的赋值操作,而是等离开 always 块后赋值事件才发生。
这时 always @(clk)
能够检测到 clk 的变化,从而能够进入下一次赋值操作,如此往复。
module mod(
output clk
);
initial #10 clk = 0;
always @(clk) #10 clk <= ~clk;
endmodule
移位寄存器
1. 不正确地使用阻塞赋值实现移位寄存器
因为 q3 的最终输出依赖 q1 和 q1 以及 d 的值, 而这些值的计算在离开 always 块前就被赋值和确定,因而三个赋值语句最终等价于 q3 = d;
module pipebl(
input clk,
input [7:0] d,
output [7:0] q3
);
reg [7:0] q3, q2, q1;
always @(posedge clk) begin
q1 = d;
q2 = q1;
q3 = q2;
end
endmodule
2. 正确用阻塞方式实现移位寄存器
需要考虑阻塞赋值的顺序, 仿真和综合结果一致,但是不推荐这种实现方法。
q3 = q2;
q2 = q1;
q1 = d;
3. 多个 always 阻塞赋值描述移位寄存器风格
阻塞赋值被放在了不同的 always 块中,仿真时这些块的先后顺序是随机的,而阻塞赋值离开 always 块前就已经发生赋值, 因此发生了竞争冒险。
但是综合的结果却是正确的移位寄存器,也就是说前仿真和后仿真结果可能不一致。
always @(posedge clk) q1 = d;
always @(posedge clk) q2 = q1;
always @(posedge clk) q3 = q2;
上述三个例子若都采用非阻塞赋值,则仿真和综合结果都是一致且正确的。
仿真
module tb;
reg clk;
reg [7:0] d;
wire [7:0] q3;
initial begin
clk = 0;
d = 0;
forever #10 begin
clk = ~clk;
d = clk == 1 ? d + 1: d;
end
end
pipebl u_pipebl(clk, d, q3);
endmodule
虽然在时序逻辑中使用阻塞赋值如果考虑周到也能建立正确的模型,通过仿真并综合成期望的逻辑。
但是这容易养成使用阻塞赋值的习惯,在较为复杂的有多个 always 块的设计项目中就可能会发生竞争冒险。
因此在时序逻辑中应当时刻使用非阻塞赋值比较好。
组合逻辑建模时应使用非阻塞赋值
1. 使用非阻塞赋值描述组合逻辑
由于非阻塞赋值会先计算 RHS 的值,在离开 always 块后才发生赋值,因此 tmp1 和 tmp2 被计算时仍是进入 always 块时的值, 而不是在 always 块中经过计算得到值。
从而在仿真时 y 的值并没能更新正确的值,对于第一次触发 always 条件时,y 的值将为不确定值。
虽然能够综合出正确的电路,但是前仿真和后仿真将不一致。
module ao4(
input a, b, c, d,
output y
);
reg y, tmp1, tmp2;
always @(a or b or c or d) begin
tmp1 <= a & b;
tmp2 <= c & d;
y <= tmp1 | tmp2;
end
endmodule
2. 将 tmp1 和 tmp1 加入到敏感事件表
通过将 tmp1 和 tmp2 中间变量加入到敏感事件表,使得当第一次进入 always 并离开时对 tmp1 和 tmp2 的更新能够再次触发 always,从而给 y 的值进行更新。
虽然这种方法是可行的,但是一次 always 有多次参数传递,降低了仿真器的性能。
always @(a or b or c or d or tmp1 or tmp2) begin
tmp1 <= a & b;
tmp2 <= c & d;
y <= tmp1 | tmp2;
end
3. 使用阻塞赋值实现
通过对 tmp1 和 tmp2 的赋值使用阻塞赋值,使得 tmp1 和 tmp2 的值总是能够立即得到更新,这样在计算 y 时,RHS 表达式中的 tmp1 和 tmp2 的值总是最新的。
这时,不论 y 使用非阻塞赋值(离开 always 块后才进行赋值), 还是使用阻塞赋值(立即进行赋值), y 的值总是正确的了。
always @(a or b or c or d) begin
tmp1 = a & b;
tmp2 = c & d;
y <= tmp1 | tmp2;
end
不过为了统一性,y 还是也使用阻塞赋值比较好,即 y = tmp1 | tmp2;
并避免混合使用非阻塞和阻塞赋值。
时序和组合的混合逻辑中使用非阻塞赋值
有时为了方便,将组合逻辑和时序逻辑写到一个 always 块中,这时应遵循时序逻辑建模的原则
module nbex2(
input clk, rst_n;
input a, b;
output reg q;
);
always @(posedge clk or negedge rst_n)
if(!rst_n)
q <= 0; // 时序逻辑
else q <= a^b; // 异或,为组合逻辑
endmodule
等价于
reg y;
// 纯时序逻辑
always @(posedge clk or negedge rst_n)
if(!rst_n)
q <= 0;
else q <= y;
// 纯组合逻辑
always @(a or b)
y = a ^ b;
非阻塞赋值的3个问题
1. 变量显示问题
display 立即显示该值, strobe 其他语句执行完毕之后,才执行显示任务,monitor 监视变量,只要发生变化就执行显示任务
module display_cmds:
reg a;
initial $monitor("\$monitor: a = %b", a);
initial begin
$strobe("\$strobe: a = %b", a);
a = 0;
a <= 1;
$display("\$display: a = %b", a);
#1 $finish;
end
endmodule
将输出
$display: a = 0
$monitor: a = 1
$strobe: a = 1
#0 使用问题
#0
就像一个微小的延时, 用于模拟实际电路波形上升或下降的延迟区间。
#0
可将事件强行插入到停止运行的事件队列。
原本 display 事件和连续赋值事件 b = a
属于动态事件队列
,其执行顺序是未定义的。 通过对 display 添加一个 #0
延时, 使得显示事件被插入到停止运行的事件队列中, 从而显示一定是在连续赋值之后运行,保证 b 值一定是 0.
`timescale 1ns/1ps
module test();
reg a;
wire b;
initial begin
#1;
a = 1;
#1;
a = 0;
#0 $display("b is %b", b);
end
assign b = a;
endmodule
3. 对于同一个变量进行多次非阻塞赋值
最后一个非阻塞赋值决定了变量的值
initial begin
a <= 0;
a <= 1;
end
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!