一周掌握FPGA Verilog HDL语法 day 3
今天给大侠带来的是一周掌握FPGA Verilog HDL 语法,今天开启第三天。
上一篇提到了变量可分为wire型、reg型、memory型,各种运算符,此篇我们继续来看赋值语句和块语句以及后续其他内容,结合实例理解理论语法,会让你理解运用的更加透彻。下面咱们废话就不多说了,一起来看看吧。
赋值语句和块语句
赋值语句
在Verilog HDL语言中,信号有两种赋值方式:
(1).非阻塞(Non_Blocking)赋值方式( 如 b <= a; )
-
块结束后才完成赋值操作。
-
b的值并不是立刻就改变的。
-
这是一种比较常用的赋值方法。(特别在编写可综合模块时)
(2).阻塞(Blocking)赋值方式( 如 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"块实际描述的电路功能如下图所示:
always @(posedge clk)
begin
b=a;
c=b;
end
上述的例2中的 "always"块用了阻塞赋值方式。clk信号的上升沿到来时,将发生如下的变化:b马上取a的值,c马上取b的值(即等于a),生成的电路图如下所示只用了一个触发器来寄存器a的值,又输出给b和c。这大概不是设计者的初衷,如果采用[例1]所示的非阻塞赋值方式就可以避免这种错误。
块语句
块语句通常用来将两条或多条语句组合在一起,使其在格式上看更象一条语句。块语句有两种,一种是begin_end语句,通常用来标识顺序执行的语句,用它来标识的块称为顺序块。一种是fork_join语句,通常用来标识并行执行的语句,用它来标识的块称为并行块。下面进行详细的介绍。
一.顺序块
顺序块有以下特点:
-
块内的语句是按顺序执行的,即只有上面一条语句执行完后下面的语句才能执行。
-
每条语句的延迟时间是相对于前一条语句的仿真时间而言的。
-
直到最后一条语句执行完,程序流程控制才跳出该语句块。顺序块的格式如下:
begin
语句1;
语句2;
......
语句n;
end
或者
begin:块名
块内声明语句
语句1;
语句2;
......
语句n;
end
其中:
-
块名即该块的名字,一个标识名。其作用后面再详细介绍。
-
块内声明语句可以是参数声明语句、reg型变量声明语句、integer型变量声明语句、real型变量声明语句。
下面举例1说明:
begin
areg = breg;
creg = areg; //creg的值为breg的值。
end
从该例可以看出,第一条赋值语句先执行,areg的值更新为breg的值,然后程序流程控制转到第二条赋值语句,creg的值更新为areg的值。因为这两条赋值语句之间没有任何延迟时间,creg的值实为breg的值。当然可以在顺序块里延迟控制时间来分开两个赋值语句的执行时间,见[例2]:
[例2]:
begin
areg = breg;
#10 creg = areg;
//在两条赋值语句间延迟10个时间单位。
end
[例3]:
parameter d=50; //声明d是一个参数
reg [7:0] r; //声明r是一个8位的寄存器变量
begin //由一系列延迟产生的波形
#d r = 'h35;
#d r = 'hE2;
#d r = 'h00;
#d r = 'hF7;
#d -> end_wave; //触发事件end_wave
end
这个例子中用顺序块和延迟控制组合来产生一个时序波形。
二. 并行块
并行块有以下四个特点:
-
块内语句是同时执行的,即程序流程控制一进入到该并行块,块内语句则开始同时并行地执行。
-
块内每条语句的延迟时间是相对于程序流程控制进入到块内时的仿真时间的。
-
延迟时间是用来给赋值语句提供执行时序的。
-
当按时间时序排序在最后的语句执行完后或一个disable语句执行时,程序流程控制跳出该程序块。
并行块的格式如下:
fork
语句1;
语句2;
.......
语句n;
join
或者
fork:块名
块内声明语句
语句1;
语句2;
......
语句n;
join
其中:
-
块名即标识该块的一个名字,相当于一个标识符。
-
块内说明语句可以是参数说明语句、reg型变量声明语句、integer型变量声明语句、real型变量声明语句、time型变量声明语句、事件(event)说明语句。
下面举例说明,
[例4]:
fork
#50 r = 'h35;
#100 r = 'hE2;
#150 r = 'h00;
#200 r = 'hF7;
#250 -> end_wave; //触发事件end_wave.
join
在这个例子中用并行块来替代了前面例子中的顺序块来产生波形,用这两种方法生成的波形是一样的。
三. 块名
在VerilgHDL语言中,可以给每个块取一个名字,只需将名字加在关键词begin或fork后面即可。这样做的原因有以下几点。
-
这样可以在块内定义局部变量,即只在块内使用的变量。
-
这样可以允许块被其它语句调用,如被disable语句。
-
在Verilog语言里,所有的变量都是静态的,即所有的变量都只有一个唯一的存储地址,因此进入或跳出块并不影响存储在变量内的值。
基于以上原因,块名就提供了一个在任何仿真时刻确认变量值的方法。
四. 起始时间和结束时间
在并行块和顺序块中都有一个起始时间和结束时间的概念。对于顺序块,起始时间就是第一条语句开始被执行的时间,结束时间就是最后一条语句执行完的时间。而对于并行块来说,起始时间对于块内所有的语句是相同的,即程序流程控制进入该块的时间,其结束时间是按时间排序在最后的语句执行完的时间。
当一个块嵌入另一个块时,块的起始时间和结束时间是很重要的。至于跟在块后面的语句只有在该块的结束时间到了才能开始执行,也就是说,只有该块完全执行完后,后面的语句才可以执行。
在fork_join块内,各条语句不必按顺序给出,因此在并行块里,各条语句在前还是在后是无关紧要的。见下例
[例5]:
fork
#250 -> end_wave;
#200 r = 'hF7;
#150 r = 'h00;
#100 r = 'hE2;
#50 r = 'h35;
join
在这个例子中,各条语句并不是按被执行的先后顺序给出的,但同样可以生成前面例子中的波形。
条件语句
if_else语句
if语句是用来判定所给定的条件是否满足,根据判定的结果(真或假)决定执行给出的两种操作之一。Verilog HDL语言提供了三种形式的 if 语句。
(1).if(表达式)语句 例如:
if ( a > b ) out1 <= int1;
(2).if(表达式) 语句1 else 语句2 例如:
if(a>b) out1<=int1;
else out1<=int2;
(3).
if(表达式1) 语句1;
else if(表达式2) 语句2;
else if(表达式3) 语句3;
........
else if(表达式m) 语句m;
else 语句n;
例如:
if(a>b) out1<=int1;
else if(a==b) out1<=int2;
else out1<=int3;
六点说明:
(1). 三种形式的if语句中在if后面都有“表达式”,一般为逻辑表达式或关系表达式。系统对表达式的值进行判断,若为0,x,z,按“假”处理,若为1,按“真”处理,执行指定的语句。
(2). 第二、第三种形式的if语句中,在每个else前面有一分号,整个语句结束处有一分号。例如:
这是由于分号是Verilog HDL语句中不可缺少的部分,这个分号是if语句中的内嵌套语句所要求的。如果无此分号,则出现语法错误。但应注意,不要误认为上面是两个语句(if语句和else语句)。它们都属于同一个if语句。else子句不能作为语句单独使用,它必须是if语句的一部分,与if配对使用。
(3). 在if和else后面可以包含一个内嵌的操作语句(如上例),也可以有多个操作语句,此时用begin和end这两个关键词将几个语句包含起来成为一个复合块语句。如:
if(a>b)
begin
out1<=int1;
out2<=int2;
end
else
begin
out1<=int2;
out2<=int1;
end
注意在end后不需要再加分号。因为begin_end内是一个完整的复合语句,不需再附加分号。
(4). 允许一定形式的表达式简写方式。如下面的例子:
if(expression) 等同与 if( expression == 1 )
if(!expression) 等同与 if( expression != 1 )
(5). if语句的嵌套 在if语句中又包含一个或多个if语句称为if语句的嵌套。一般形式如下:
if(expression1)
if(expression2) 语句1 (内嵌if)
else 语句2
else
if(expression3) 语句3 (内嵌if)
else 语句4
应当注意if与else的配对关系,else总是与它上面的最近的if配对。如果if与else的数目不一样,为了实现程序设计者的企图,可以用begin_end块语句来确定配对关系。例如:
if( )
begin
if( ) 语句1 (内嵌if)
end
else
语句2
这时begin_end块语句限定了内嵌if语句的范围,因此else与第一个if配对。注意begin_end块语句在if_else语句中的使用。因为有时begin_end块语句的不慎使用会改变逻辑行为。见下例:
if(index>0)
for(scani=0;scani<index;scani=scani+1)
if(memory[scani]>0)
begin
$display("...");
memory[scani]=0;
end
else /*WRONG*/
$display("error-indexiszero");
尽管程序设计者把else写在与第一个if(外层if)同一列上,希望与第一个if对应,但实际上else是与第二个if对应,因为它们相距最近。正确的写法应当是这样的:
if(index>0)
begin
for(scani=0;scani<index;scani=scani+1)
if(memory[scani]>0)
begin
$display("...");
memory[scani]=0;
end
end
else /*WRONG*/
$display("error-indexiszero");
(6) .if_else例子。
下面的例子是取自某程序中的一部分。这部分程序用if_else语句来检测变量index以决定三个寄存器modify_segn中哪一个的值应当与index相加作为memory的寻址地址。并且将相加值存入寄存器index以备下次检测使用。程序的前十行定义寄存器和参数。
//定义寄存器和参数。
reg [31:0] instruction, segment_area[255:0];
reg [7:0] index;
reg [5:0] modify_seg1, modify_seg2, modify_seg3;
parameter
segment1=0, inc_seg1=1,
segment2=20, inc_seg2=2,
segment3=64, inc_seg3=4,
data=128;
//检测寄存器index的值
if(index<segment2)
begin
instruction = segment_area[index + modify_seg1];
index = index + inc_seg1;
end
else if(index<segment3)
begin
instruction = segment_area[index + modify_seg2];
index = index + inc_seg2;
end
else if (index<data)
begin
instruction = segment_area[index + modify_seg3];
index = index + inc_seg3;
end
else
instruction = segment_area[index];
Day 3 就到这里,Day 4 继续开始case语句,大侠保重,告辞。
【QQ交流群】
群号:173560979,进群暗语:FPGA技术江湖粉丝。
多年的FPGA企业开发经验,各种通俗易懂的学习资料以及学习方法,浓厚的交流学习氛围,QQ群目前已有1000多名志同道合的小伙伴,无广告纯净模式,给技术交流一片净土,从初学小白到行业精英业界大佬等,从军工领域到民用企业等,从通信、图像处理到人工智能等各个方向应有尽有。
【微信交流群】
现微信交流群已建立08群,人数已达数千人,欢迎关注“FPGA技术江湖”微信公众号,可获取进群方式。
完
后续会持续更新,带来Vivado、 ISE、Quartus II 、candence等安装相关设计教程,学习资源、项目资源、好文推荐等,希望大侠持续关注。
江湖偌大,继续闯荡,愿大侠一切安好,有缘再见!