Verilog HDL语言中存在两种赋值语言:非阻塞型赋值语句,阻塞型赋值语句
在Verilog HDL中阻塞赋值"="和非阻塞赋值"<="有着很大的不同.个人认为,作为初学者要掌握可综合风格的Verilog模块编程的8个原则,在综合布局布线的仿真中避免出现竞争冒险现象。(关于竞争冒险,后续会有单独章节记录)
(1) 时序电路建模时,用非阻塞赋值。
(2) 锁存器电路建模时,用非阻塞赋值。
(3) 用always块建立组合逻辑模型时,用阻塞赋值。
(4) 在同一个always块中建立时序和组合逻辑电路时,用非阻塞赋值。
(5) 在同一个always块中不要既用非阻塞赋值又用阻塞赋值。
(6) 不要在一个以上的always块中为同一个变量赋值。
(7) 用$strobe系统任务来显示用非阻塞赋值的变量值。
(8) 在赋值时不要使用 #0延时。
用"="或者是"<="实际上对应的是不同的硬件电路
1.非阻塞型语句
以赋值操作符“<=”来标识的赋值操作称为“非阻塞型过程赋值( NonblockingAssignment)”。 非阻塞型过程赋值语句的特点是:
( 1) 在 begin-end 串行语句块中,一条非阻塞过程语句的执行不会阻塞下一条语句的执行,也就是说在本条非阻塞型过程赋值语句对应的赋值操作执行完之前,下一条语句也可以开始执行。
( 2) 仿真过程在遇到非阻塞型过程赋值语句后首先计算其右端赋值表达式的值,然后等到仿真时间步结束时再将该计算结果赋值变量。也就是说,这种情况下的赋值操作是在同一仿真时刻上的其他普通操作结束后才得到执行的
下面看一下例程非阻塞赋值的代码和仿真:
modulenon_block(clk,rst_n,a,b,c,out);inputclk;inputrst_n;//_n低电平有效inputa,b,c;outputreg[1:0]out;/*outputreg的使用.reg是寄存器型变量的意思,一般在用时序逻辑编程时定义.而output代表此变量为输出。实际上两者并没有直接联系,但是如果这个变量既是时序逻辑(寄存器型)变量又要求输出,就既需要output,也需要reg...一般always@语句中赋值的变量都要声明为reg型*/reg[1:0]d;always@(posedgeclkornegedgerst_n)if(!rst_n)out<=2'b0;//三位数相加以1+1+1=3为例转化为二进制位宽两位elsebegind<=a+b;out<=d+c;endendmodule
RTL视图:
仿真结果:
下面我们改变一下代码
always@(posedgeclkornegedgerst_n)if(!rst_n)out<=2'b0;//三位数相加以1+1+1=3为例转化为二进制位宽两位elsebegin//d<=a+b;out<=d+c;//注意改变后的代码d<=a+b;end
RTL视图:
从上面两张RTL视图中我们可以看到,在非阻塞赋值语句改变代码后,RTL结果不变
仿真结果:
2. 阻塞型语句
以赋值操作符“ =”来标识的赋值操作称为“阻塞型过程赋值( blockingAssignment)”。非阻塞型过程赋值语句的特点是:
( 1) 串行块( begin-end)中的各条阻塞型过程赋值语句将以它们在顺序块后排
列次序依次得到执行。
( 2)阻塞型过程赋值语句的执行过程是:首先计算右端赋值表达式的值,然后立即将计算结果赋值给“ =”左端的被赋值变量。
阻塞型过程赋值语句的这两个特点表明:仿真进程在遇到阻塞型过程赋值语句时将计算表达式的值并立即将其结果赋给等式左边的被赋值变量;在串行语句块中,下一条语句的执行会被本条阻塞型过程赋值语句所阻塞,只有在当前这条阻塞型过程赋值语句所对应的赋值操作执行完后下一条语句才能开始执行。
下面看一下例程阻塞赋值的代码和仿真:
always@(posedgeclkornegedgerst_n)if(!rst_n)out<=2'b0;//三位数相加以1+1+1=3为例转化为二进制位宽两位elsebegind=a+b;out=d+c;end
RTL视图:
仿真结果:
下面我们改变一下代码
if(!rst_n)out<=2'b0;elsebegin//d=a+b;out=d+c;//注意改变后的代码d=a+b;end
RTL视图:
改变后与改变前的图明显不同,多出一个寄存器,我们可以看到,阻塞语句中最后生成结果与程序语句的先后顺序很重要的关联
仿真结果:
以上的仿真代码:
`timescale1ns/1ns`defineclock_period20//定义时钟moduleblock_tb;regclk;regrst_n;rega,b,c;wire[1:0]out;blockblock0(.clk(clk),.rst_n(rst_n),.a(a),.b(b),.c(c),.out(out));//blockblock0(clk,rst_n,a,b,c,out);initialclk=1;always#(`clock_period/2)clk=~clk;initialbeginrst_n=1'b0;a=0;b=0;c=0;#(`clock_period*200+1);rst_n=1'b1;#(`clock_period*200);a=0;b=0;c=0;#(`clock_period*200);a=0;b=0;c=1;#(`clock_period*200);a=0;b=1;c=0;#(`clock_period*200);a=0;b=1;c=1;#(`clock_period*200);a=1;b=0;c=0;#(`clock_period*200);a=1;b=0;c=1;#(`clock_period*200);a=1;b=1;c=0;#(`clock_period*200);a=1;b=1;c=1;#(`clock_period*200);#(`clock_period*200);$stop;endendmodule
注:例化中注释的部分是Augus失误所写成的代码,但是仿真结果全都都对,Augus当时像发现"新大陆"一样激动,在此解释一下,正确的例化:
blockblock0(.clk(clk),.rst_n(rst_n),.a(a),.b(b),.c(c),.out(out));//blockblock0(clk,rst_n,a,b,c,out);
而我失误写的虽然对了,原因是该程序相对简单且端口较少,且默认来模块定义的接口来一一对应,例化(XXX)的顺序和定义的端口顺序相同,所以侥幸未出现失误