非阻塞赋值和阻塞赋值
在Verilog HDL语言中,信号有两种赋值方式:非阻塞(Non_Blocking)赋值方式和阻塞(Blocking)赋值方式。
(1)非阻塞赋值方式。
典型语句:b <= a;
① 块结束后才完成赋值操作。
② b的值并不是立刻就改变的。
③ 这是一种比较常用的赋值方法,特别在编写可综合模块时。
(2)阻塞赋值方式。
典型语句:b = a;
① 赋值语句执行完后,块才结束。
② b的值在赋值语句执行完后立刻就改变。
③ 可能会产生意想不到的结果。
非阻塞赋值方式和阻塞赋值方式的区别常给设计人员带来问题。问题主要是给“always”块内的reg型信号的赋值方式不易把握。到目前为止,前面所举的例子中的“always”模块内的reg型信号都是采用下面的这种赋值方式:
b <= a;
这种方式的赋值并不是马上执行的,也就是说“always”块内的下一条语句执行后,b并不等于a,而是保持原来的值。“always”块结束后,才进行赋值。而另一种赋值方式阻塞赋值方式,如下所示:
b = a;
这种赋值方式是马上执行的,也就是说执行下一条语句时,b已等于a。尽管这种方式看起来很直观,但是可能引起麻烦。下面举例说明。
例1:非阻塞赋值。
always @( posedge clk ) begin
b<=a;
c<=b;
end
例1中的“always”块中用了非阻塞赋值方式,定义了两个reg型信号b和c。clk信号的上升沿到来时,b就等于a,c就等于b,这里应该用到了两个触发器。需要注意的是赋值是在“always”块结束后执行的,c应为原来b的值。这个“always”块实际描述的电路功能如图1所示。
例2:阻塞型赋值。
always @(posedge clk) begin
b=a;
c=b;
end
例2中的“always”块用了阻塞赋值方式。clk信号的上升沿到来时,将发生如下的变化:b马上取a的值,c马上取b的值(即等于a)。综合的电路如图2所示。
图1 非阻塞赋值综合电路 图2 阻塞赋值综合电路
它只用了一个触发器来寄存a的值,并同时输出给b和c。这不是设计者的初衷,如果采用例3.5所示的非阻塞赋值方式就可以避免这种错误。
块语句
块语句通常用来将两条或多条语句组合在一起,使其在格式上看更像一条语句。块语句有两种:一种是begin_end语句,通常用来标识顺序执行的语句,用它来标识的块称为顺序块;另一种是fork_join语句,通常用来标识并行执行的语句,用它来标识的块称为并行块。下面进行详细的介绍。
1.顺序块
顺序块有以下特点。
(1)块内的语句是按顺序执行的,即只有上面一条语句执行完后下面的语句才能执行。
(2)每条语句的延迟时间是相对于前一条语句的仿真时间而言的。
(3)直到最后一条语句执行完,程序流程控制才跳出该语句块。
顺序块的格式如下:
begin
语句1;
语句2;
......
语句n;
end
或者:
begin:块名
块内声明语句
语句1;
语句2;
......
语句n;
end
其中:
(1)块名即该块的名字,是一个标识符,其作用后面再详细介绍。
(2)块内声明语句可以是参数声明语句,reg型变量声明语句,integer型变量声明语句或者real型变量声明语句。
下面举例说明。
例3:顺序块。
begin
areg = breg;
creg = areg; //creg的值为breg的值
end
从该例可以看出,第一条赋值语句先执行,areg的值更新为breg的值。然后程序流程控制转到第二条赋值语句,creg的值更新为areg的值。因为这两条赋值语句之间没有任何延迟时间,creg的值实为breg的值。当然可以在顺序块里延迟控制时间来分开两个赋值语句的执行时间,如例4所示。
例4:加延时顺序块。
begin
areg = breg;
#10 creg = areg; //在两条赋值语句间延迟10个时间单位
end
2.并行块
并行块有以下4个特点。
(1)块内语句是同时执行的,即程序流程控制一进入该并行块,块内语句则开始同时并行地执行。
(2)块内每条语句的延迟时间是相对于程序流程控制进入到块内时的仿真时间的。
(3)延迟时间是用来给赋值语句提供执行时序的。
(4)当按时间时序排序在最后的语句执行完后或一个disable语句执行时,程序流程控制跳出该程序块。
并行块的格式如下:
fork
语句1;
语句2;
.......
语句n;
join
或者:
fork:块名
块内声明语句
语句1;
语句2;
......
语句n;
join
其中:
(1)块名即标识该块的一个名字,相当于一个标识符。
(2)块内说明语句可以是参数说明语句、reg型变量声明语句、integer型变量声明语句、real型变量声明语句、ime型变量声明语句或者事件(event)说明语句。
下面举例说明。
例5:并行块1。
fork
#50 r = 'h35; //在绝对时间50单位后,r被赋值
#100 r = 'hE2; //在绝对时间100单位后(非绝对时间150),r再次被赋值
#150 r = 'h00;
#200 r = 'hF7;
#250 -> end_wave; //在绝对时间250单位后,触发事件end_wave
join
在这个例子中用并行块来替代前面例子中的顺序块来产生波形,用这两种方法生成的波形是一样的。
3.块名
在Verilog HDL语言中,可以给每一个块取名字,只需将名字加在关键词begin或fork后面即可,这样做的原因有以下几点。
(1)这样可以在块内定义局部变量,即只在块内使用的变量。
(2)这样可以允许块被其他语句调用,如被disable语句调用。
(3)在Verilog语言里,所有的变量都是静态的,即所有的变量都只有一个惟一的存储地址,因此进入或跳出块并不影响存储在变量内的值。
基于以上原因,块名就提供了一个在任何仿真时刻确认变量值的方法。需要注意的是,块名和变量名一样,都不能是关键词。
4.起始时间和结束时间
在并行块和顺序块中都有一个起始时间和结束时间的概念。对于顺序块,起始时间就是第一条语句开始被执行的时间,结束时间就是最后一条语句执行完的时间。而对于并行块来说,起始时间对于块内所有的语句是相同的,即程序流程控制进入该块的时间,其结束时间是按时间排序在最后的语句执行完的时间。
当一个块嵌入另一个块时,块的起始时间和结束时间是很重要的。跟在块后面的语句只有在该块的结束时间到了才能开始执行,也就是说,只有该块完全执行完后,后面的语句才可以执行。
在fork_join块内,各条语句不必按顺序给出,因此在并行块里,各条语句在前还是在后是无关紧要的,如下所示。
例6:并行块2。
fork
#250 -> end_wave; //按下面几条语句顺序执行结果和例[6]的执行结果一样
#200 r = 'hF7;
#150 r = 'h00;
#100 r = 'hE2;
#50 r = 'h35;
join
在这个例子中,各条语句并不是按被执行的先后顺序给出的,但同样可以生成前面例子中的波形。
关键词
在Verilog HDL中,所有的关键词是事先定义好的确认符,用来组织语言结构。关键词是用小写字母定义的,因此在编写原程序时要注意关键词的书写,以避免出错。下面是Verilog HDL中使用的关键词(请参阅附录:Verilog语言参考手册):
always、and、assign、begin、buf、bufif0、bufif1、case、casex、casez、cmos、deassign、default、defparam、disable、edge、else、end、endcase、endmodule、endfunction、endprimitive、endspecify、endtable、endtask、event、for、force、forever、fork、function、highz0、highz1、if、initial、inout、input、integer、join、large、macromodule、medium、module、nand、negedge、nmos、nor、not、notif0、notifl、or、output、parameter、pmos、posedge、primitive、pull0、pull1、pullup、pulldown、rcmos、reg、releses、repeat、mmos、rpmos、rtran、rtranif0、rtranif1、scalared、small、specify、specparam、strength、strong0、strong1、supply0、supply1、table、task、time、tran、tranif0、tranif1、tri、tri0、tri1、triand、trior、trireg、vectored、wait、wand、weak0、weak1、while、wire、wor、xnor、xor。
在编写Verilog HDL程序时,变量名、端口名、块名等的定义不要与这些关键词冲突。