Verilog连续赋值、过程赋值、过程连续赋值总结

最近总是遇到systemverilog的赋值问题,查看了一下手册发现SV的赋值方式总的还是继承了verilog的赋值方式,而且verilog赋值方面的资料比较多,所以就写了先写一篇关于verilog的赋值总结。

连续赋值

连续赋值就是一旦赋值,输出将随输入改变而变化,一旦修改输入则立刻体现在输出上。

input a,b;
wire out;

assign out = a & b;
  • 要求赋值左侧必须为net型
  • 关键词assign
  • 赋值过程类似于物理场景的导线连接,out将跟随a异或b的电路变化而变化,一旦a或b有修改则立刻体现在输出上。
  • 连续赋值属于并发执行,与书写顺序无关,不论在何时赋值都会在一开始执行。
  • 对于同一参数多次赋值,视情况和强度而定,但不要进行该操作。
  • 常用于组合逻辑

过程赋值

过程赋值只会在赋值时刻进行改变,其余时间保持不变,而且根据使用的赋值符号=<=,分为阻塞赋值和非阻塞赋值。

阻塞赋值 =

//例1
initial begin
      ai            = 4'd1 ; 	  	//(1)
      ai            = 4'd2 ;   		//(2)
      //ai 最此刻为 2
      #20 ;                    		//(3)
      ai            = 4'd3 ;     	//(4)
      bi            = 4'd4 ;     	//(5)
      value_blk     = ai + bi ;  	//(6)
      //value_blk 最终为7
        #40;
    end
  • 要求赋值左侧必须为reg、integer, real, time-variable, or memory(等非net型);
  • 赋值过程在initial或always块中;
  • 阻塞赋值类似于C语言的赋值方式,后面的赋值会覆盖掉前面的赋值,执行顺序为1->2->...->6,类似软件编程的赋值方式,随时赋值随时开始,但前一个赋值如果不结束,后面的赋值便不会来说,正因为如此被称为阻塞赋值。
  • 最终结果与书写顺序相关.
  • 常用于testbench测试用例/组合逻辑.

非阻塞赋值 =>

//例1
initial begin
      ai            = 4'd1 ; 	  	//(1)
      bi            = 4'd2 ;   		//(2)
      //ai和bi的初始值分别为1,2
      #20 ;                    		//(3)
      //非阻塞赋值
      ai            <= 4'd3 ;     	//(4)
      bi            <= 4'd4 ;     	//(5)
      value_nonblk     <= ai + bi ;  	//(6)
      #40;
      //value_blk 最终为3
end

image

  • 要求赋值左侧必须为reg型
  • 关键词为initial或always
  • 非阻塞赋值类似于物理电路中的时序电路,其中可以这样理解代码执行顺序,1->2->3之后其他的非阻塞赋值(4)(5)(6)不着急执行,而是列入到"事件队列"中,一直存到#40需要被执行前,即下一个时刻需要执行前,(4)(5)(6)将会被同时执行,此时对于value_nonblk而言其对应的ai和bi依旧是1和2,因此结果为3,可用于时序电路建模。
  • 非阻塞赋值是前一个赋值并不会阻塞后一个赋值,而是将所有赋值放到“事件队列”中,并在该时刻的“最后”同时执行,也就是说,赋值过程不会阻塞。
  • 最终结果与书写顺序相关
  • 常用于时序逻辑

下面使用阻塞赋值和非阻塞分别描述构成的组合逻辑和时序逻辑。

module top_module(
input a, output reg out1, output reg out2);
always@(*)	begin
		out1 = a;       //a的值直接到out1
    out2 = out1;    //当前out1直接赋给out2
end                 //也就是说a的值直接out2
endmodule

阻塞赋值在always(*)块下,直接综合时形成了如下的组合逻辑电路。
image

module top_module(
input a, input clk, output reg out1, output reg out2);
always@(posedge clk)	begin
		out1 <= a;       //a的值赋给out1
    out2 <= out1;    //此时左侧的out1并不是a的大小,而是上一周期的值,因此这时是将上一周期的out1的值赋给out2
end                                                  
endmodule           

而非阻塞赋值在always(posedge clk)块下,在综合时形成了如下时序电路。
image

⚠️注意
虽然阻塞赋值always@(*) out_block = a & b;和连续赋值assign out = a & b;赋值方式不同,而且左侧采用的分别为reg类型和net类型,但最终综合出来却是一样的组合逻辑电路,都不会出现寄存器,这说明声明的类型与合成的硬件电路类型无关。
同样赋值过程并完全等同于硬件实现过程 always@(posedge clk) a <= 1;always@(posedge clk) a = 1; 两者综合出来的电路同样也没有区别,a作为输出都有寄存器的出现,也就是赋值方式与硬件实现过程也可以是无关的这主要是Verilog语法的历史遗留问题, 只采用声明的类型和赋值方式很难区分,而systemverilog干脆直接用logic代替了wire和reg两种类型,对声明的类型不做区分【难办,难办就别办了~~】,因此了解综合后的硬件电路,要重点关注语法本身是组合还是时序。

因此作为一个ICer,不能为了炫技而采用各种花里胡哨的技巧,而是采用一些原则来保证自己的代码可以更便于观察。因此为了更清晰的描述出确定的电路, 在使用非阻塞与阻塞赋值时最好遵循以下原则:

  1. 时序电路建模时,使用非阻塞赋值 ;
  2. 锁存器电路建模时,使用非阻塞赋值 ;
  3. 使用always块写组合逻辑电路时,采用阻塞赋值;
  4. 在同一个always块中同时建立时序和组合逻辑电路时,用非阻塞赋值 ;
  5. 在同一个always块中不要同时使用非阻塞赋值和阻塞赋值 ;
  6. 不要在多个always块中,为一个变量赋值;
  7. 用$strobe系统任务来显示用非阻塞赋值的变量值;
  8. 在赋值时不要使用#0延迟【有时间了再去聊原因】;

遵循以上逻辑可以消除90%-100%的在仿真中产生的竞争冒险现象,有助于正确编写可综合硬件。
image


过程连续赋值

连续赋值通过assign语句驱动net类型变量,而过程赋值通过initial和always块驱动reg类型的变量。这两种驱动方式基本上可以构成常用的电路,但verilog又给出了一种赋值方式,即过程连续赋值,可以通过覆盖现有的赋值,并在一定时期内驱动net、reg数据变量。
主要有两对过程连续赋值:assign-deassignforce-release。但这种赋值方式最好不要用于设计,会使得代码难以理解,更多用于testbench,在测试时,为出现对应结果,将设计中某些必要的信号强制赋为期待值。

assign-deassign

assign通过在有一段时间内覆盖掉现有的过程赋值控制的reg变量的值。并在执行deassign之后,就会释放掉过程连续赋值,但可以保持一段时间,直到其他赋值进行修改。

module assign_deassign_ex;
  reg [3:0] d1;
  initial begin
    $monitor("At time T = %0t: d1 = %0d", $time, d1);
    d1 = 5;
    #20 d1 = 7;
  end
  
  initial begin
    #5;
    assign d1 = 3;	//直接覆盖掉d1从5->3
    #5 deassign d1; //d1释放之后,d1维持一段时间,在#20时刻从5变为7
    $display("At time T = %0t: deassign d1", $time);
  end
endmodule

//输出结果
At time T = 0: d1 = 5
At time T = 5: d1 = 3
At time T = 10: deassign d1
At time T = 20: d1 = 7

force-release

force 和 release 语句通过在一段时间内覆盖现有的过程、连续或过程连续赋值来控制 net 和 reg 数据类型变量值,force的权限比assign要高,可以覆盖掉其他的赋值方式。release同deassign一样,可以释放掉赋值,但对于过程赋值和过程连续赋值前可以保持前一个值。而对于net类型,直接恢复到前一个连续赋值的值。

module assign_deassign_ex;
  reg [3:0] d1;
  wire [3:0] d2;
  
  assign d2 = 2;
  initial begin
    $monitor("At time T = %0t: d1 = %0d, d2 = %0d", $time, d1, d2);
    d1 = 5;
    #20 d1 = 7;
  end
  
  initial begin
    #5;
    $display("At time T = %0t: force d1 and d2", $time);
    force d1 = 3;		//强制将d1赋值为3
    force d2 = 4;		//d2赋值为4
    #5 release d1;	//d1可以保持为3,一直到#20恢复为7
    release d2;			//d2一旦释放则恢复到2
    $display("At time T = %0t: release d1 and d2", $time);
  end
endmodule

//输出
At time T = 0: d1 = 5, d2 = 2
At time T = 5: force d1 and d2
At time T = 5: d1 = 3, d2 = 4
At time T = 10: release d1 and d2
At time T = 10: d1 = 3, d2 = 2
At time T = 20: d1 = 7, d2 = 2

参考文献
[1] Procedural continuous assignments - VLSI Verify-教程类
[2] Verilog Assignments-教程类
[3] Verilog 过程连续赋值-教程类
[4] How does SystemVerilog force work? -问答类

posted @ 2024-07-28 21:18  NullBeer  阅读(2091)  评论(0)    收藏  举报