acgoal的个人空间 https://blog.eetop.cn/553746 [收藏] [复制] [分享] [RSS]

空间首页 动态 记录 日志 相册 主题 分享 留言板 个人资料

日志

阻塞赋值和非阻塞赋值深度解析——仿真事件的调度

已有 4140 次阅读| 2015-9-6 22:24 |个人分类:经验集锦

  下面有一段verilog代码和仿真文件,用的是VCS仿真和编译工具。我们来研究一下不同的驱动赋值方式对仿真结果的影响。下面我把我做的例子和大家分享一下。
   设计源代码如下:
`timescale 1ns/1ps
module counter (data_out0, data_out1,clk,rst_n, data_in0, data_in1);
output [3:0] data_out0;
output [3:0] data_out1;
input [3:0] data_in0;
input [3:0] data_in1;
input clk;
input rst_n;

reg [3:0] data_out0;
reg [3:0] data_out1;
always @(posedge clk or negedge rst_n) begin
  if(!rst_n) begin
    data_out0 <= 4'd0;
  end
  else begin
    data_out0 <= data_in0;
  end
end
always @(posedge clk) begin
  if(!rst_n) begin
    data_out1 <= 4'd0;
  end
  else begin
    data_out1 <= data_in1;
  end
end
endmodule



这里有两段,分别表示带异步复位和带同步复位的。

测试程序如下:
`timescale 1ns/1ps
module tb_top;
reg clk;
reg rst_n;
wire [3:0] data_out0;
wire [3:0] data_out1;
reg [3:0] data_in0;
reg [3:0] data_in1;
counter u_counter0(.clk(clk), .rst_n(rst_n), .data_out0(data_out0), .data_out1(data_out1), .data_in0(data_in0), .data_in1(data_in1));

initial  begin
  clk=1'b0;
end
always begin
  #3 clk=~clk;
end
initial begin
  rst_n = 1'b1;
  #15 rst_n = 1'b0;
  #180 rst_n = 1'b1;
  #200 $finish;
end

initial begin
  data_in0 <= 0;
  wait(!rst_n);
  wait(rst_n);
  @(posedge clk);
  data_in0 <= 1;
  @(posedge clk);
  data_in0 <= 2;
  @(posedge clk);
  data_in0 <= 4;
  @(posedge clk);
  data_in0 <= 8;
  @(posedge clk);
  data_in0 <= 0;
end

initial begin
  data_in1 <= 0;
  wait(!rst_n);
  wait(rst_n);
  @(posedge clk);
  data_in1 <= 1;
  @(posedge clk);
  data_in1 <= 2;
  @(posedge clk);
  data_in1 <= 4;
  @(posedge clk);
  data_in1 <= 8;
  @(posedge clk);
  data_in1 <= 0;
end

initial begin
  $vcdpluson();
end
endmodule


注意这里红色的部分,data0和data1的驱动都用非阻塞赋值。得到的仿真波形图如下:



下面我把非阻塞赋值改成阻塞赋值,改动部分如下:

initial begin
  data_in0 = 0;
  wait(!rst_n);
  wait(rst_n);
  @(posedge clk);
  data_in0 = 1;
  @(posedge clk);
  data_in0 = 2;
  @(posedge clk);
  data_in0 = 4;
  @(posedge clk);
  data_in0 = 8;
  @(posedge clk);
  data_in0 = 0;
end

initial begin
  data_in1 = 0;
  wait(!rst_n);
  wait(rst_n);
  @(posedge clk);
  data_in1 = 1;
  @(posedge clk);
  data_in1 = 2;
  @(posedge clk);
  data_in1 = 4;
  @(posedge clk);
  data_in1 = 8;
  @(posedge clk);
  data_in1 = 0;
end


这样的代码,仿真波形图如下:



和上面的不一样,为什么呢,为什么这次的flop行为没有了呢?直接给结论吧。

根据verilog/system verilog的standard。我这里以system verilog的standard为例,STD 1800-2009版本,第四章“Scheduling Semantics”,这一章关于仿真的调度描述。

simulation的event大体分为:Active event -> Inactive event -> NBA event -> Overserved event -> Reactive -> Re-Inactive -> Re-NBA 

一般的,阻塞赋值发生在active/inactive event,而NBA( Non-blocking assignment update)从字面意思就能看出来是做非阻塞赋值的。
再结合上面的例子,如果data_in0是阻塞赋值出去的话,因为例子里的dut是在时钟上升延来的时候进行非阻塞赋值(data_out0 <= data_in0),这句话属于NBA event,而阻塞赋值data_in0 = 1 (或者2,4,8)是Active event,他比NBA要先执行,所以我们看到data_out0在当前的cycle就被采样到了。这就是后面一个波形图的样子,看起来就像flop没有用一样。实际上是我们driver写的有问题。

如果data_in0在testbench里面用阻塞赋值就是 data_in0 <= 1 (或者2,4,8,见例子),那么data_out0 <= data_in0, 和data_in0 <= 1同属于NBA event。这样子这两句话同时执行,这样就和实际电路中的flop行为一致了(就是并行执行)。


结论:做驱动的接口信号最好是非阻塞赋值出去。

点赞

发表评论 评论 (3 个评论)

回复 mxy_19 2017-5-4 19:07
非常有帮助!谢谢您的文章!

facelist

您需要登录后才可以评论 登录 | 注册

  • 关注TA
  • 加好友
  • 联系TA
  • 0

    周排名
  • 0

    月排名
  • 0

    总排名
  • 0

    关注
  • 1

    粉丝
  • 0

    好友
  • 0

    获赞
  • 20

    评论
  • 2995

    访问数
关闭

站长推荐 上一条 /1 下一条

小黑屋| 关于我们| 联系我们| 在线咨询| 隐私声明| EETOP 创芯网
( 京ICP备:10050787号 京公网安备:11010502037710 )

GMT+8, 2024-4-25 20:04 , Processed in 0.013860 second(s), 8 queries , Gzip On, Redis On.

eetop公众号 创芯大讲堂 创芯人才网
返回顶部