目录
赋值语句
在Verilog HDL语言中,信号有两种赋值方式:
-
非阻塞(Non_Blocking)赋值方式(如b<=a)
-
在语句块中,上面语句所赋值的变量值不能立即就为下面的语句所用:
-
块结束后才能完成这次赋值操作,而所赋的变量值是上一次赋值得到的;
-
在编写可综合模块的时序逻辑模块时,这是最常用的赋值方式
注意:非阻塞赋值<=和小于等于符<=看起来是一样的,但意义完全不同,小于等于符是关系运算符,用于比较大小,而非阻塞赋值用于赋值操作。
阻塞(Blocking)赋值方式(如b=a)
- 赋值语句执行完之后,块才结束;
- b的值在赋值语句执行完后就会立刻改变的;
- 在时序逻辑中使用时,可能会产生意想不到的结果
阻塞赋值和非阻塞赋值是C语言所没有的,阻塞语句,如果没有写延迟时间看起来是在同一时刻运行的,但实际上是有先后的,即在前面的先运行,然后再运行下面的语句,阻塞语句的秩序与逻辑行为具有很大的关系,而非阻塞的就不同了,在begin end之间的所有非阻塞语句都在同一时刻被赋值,因此逻辑行为与非阻塞语句的次序就没有关系。
条件语句
if-else语句
条件语句必须在过程块语句中使用。所谓过程块语句是指由Initial和always语句引导的执行语句的集合。除了这两种块语句引导的begin end块中可以编写条件语句外,模块的其他地方都不能编写。
六点说明:
- 三种形式的if语句中在if后面都有“表达式” ,一般为逻辑表达式或关系表达式。系统对表达式的值进行判断,若为0,x,z,按“假”处理,若为1,按“真”处理,执行指定的语句。
- 第二、第三种形式的if语句中,在每个else前面有一分号,整个语句结束处有一分号。
- 在if和else后面可以包含一个内嵌的操作语句(如上例),也可以有多个操作语句,此时用begin和end这两个关键词将几个语句包含起来成为一个复合块语句。
- 允许一定形式的表达式简写方式。
- if语句的嵌套
- if_else例子
case语句
一般形式如下:
case(表达式) <case分支项> endcase
casez(表达式)<case分支项> endcase
casex(表达式)<case分支项> endcase
case, casez, casex 的真值表:其中case是要完全相同为1,casez则是忽略z状态,x是忽略x和z状态。
case | 0 | 1 | x | z |
---|---|---|---|---|
0 | 1 | 0 | 0 | 0 |
1 | 0 | 1 | 0 | 0 |
x | 0 | 0 | 1 | |
z | 0 | 0 | 0 | 1 |
casez | 0 | 1 | x | z |
---|---|---|---|---|
0 | 1 | 0 | 0 | 1 |
1 | 0 | 1 | 0 | 1 |
x | 0 | 0 | 1 | 1 |
z | 1 | 1 | 1 | 1 |
casex | 0 | 1 | x | z |
---|---|---|---|---|
0 | 1 | 0 | 1 | 1 |
1 | 0 | 1 | 1 | 1 |
x | 1 | 1 | 1 | 1 |
z | 1 | 1 | 1 | 1 |
说明:
- case括弧内的表达式称为控制表达式,case分支项中的表达式称为分支表达式。控制表达式通常表示为控制信号的某些位,分支表达式则用这些控制信号的具体状态值来表示,因此分支表达式又称为常量表达式。
- 当控制表达式的值与分支表达式的值相等时,就执行分支表达式后面的语句。如果所有的分支表达式的值都没有与控制表达式的值相匹配,就执行default后面的语句。
- default项可有可无,一个case语句里只准有一个default语句。
- 每一个case分项的分支表达式的值必须互不相同,否则就会出现问题,即对表达式的同一个值,将出现多种执行方案,产生矛盾。
- 执行完case分项后的语句,则跳出该case语句结构,终止case语句的执行。
- 在用case语句表达式进行比较的过程中,只有当信号的对应位的值能够明确进行比较时,比较才能成功,因此,要注意详细说明case分项的分支表达式的值。
- case语句的所有表达式值的位宽必须相等,只有这样,控制表达式和分支表达式才能进行对应位的比较。一个经常犯的错误就是用'bx,'bz来代替n'bx,n'bz,这样写是不对的,因为信号x和z的默认宽度是机器的字节宽度,通常是32位的,此处n是case控制表达式的位宽。
case语句与if-else-if语句的区别主要有两点:
(1)与case语句中的控制表达式和多分支表达式这种比较结构相比,if-else-if结构中的条件表达式更为直观些。
(2)对于那些分支表达式中存在不定值x和高阻值z的位时,case语句提供了处理这种情况的手段,即casex和casez,这里用来处理比较过程中的不必考虑的情况,其中casez语句用来处理不考虑高阻值z的比较过程,casex语句则将高阻值和不定值都视为不必关心的情况。所谓不必关心的情况,即在表达式进行比较时,不将该位的状态考虑在内。这样在case语句表达式进行比较时,就可以灵活的设置对信号的某些位进行比较。
避免latch锁存器的产生
由于使用条件语句不当在设计中生成了原本没想到有的锁存器。
例子:
//有锁存器
always @(al or d)
begin
if(al) a<=d;
end
//无锁存器
always @(al or d)
begin
if(al) a<=d;
else q<=0;
end
//****************************************
//有锁存器
always @(sel[1:0] or a or b)
case (sel[1:0])
2'b00: q<=a;
2'b11: q<=b;
endcase
//无锁存器
always @(sel[1:0] or a or b)
case (sel[1:0])
2'b00: q<=a;
2'b11: q<=b;
default: q<='b0;
endcase
避免偶然生成锁存器的错误,如果用到if语句最好写上else项,如果用case语句,最好写上default项,以及避免自己给自己赋值,就能避免生成锁存器的结果。
循环语句
四类循环语句
在Verilog HDL中存在着4种类型的循环语句,用来控制执行语句的执行次数。
- forever语句:连续的执行语句
forever循环语句常用于产生周期性的波形,用来作为仿真测试信号,它与always语句不同之处在于不能独立写在程序种,而必须写在initial块中。
2. repeat语句:连续执行一条语句n次
repeat (次数表达式) 语句;
3. while语句:执行一条语句直到某个条件不满足。如果一开始条件不满足则一次都不执行。
while (表达式) 语句;
4. for通过以下三个步骤来决定语句的循环执行
- 先给控制循环次数的变量赋初始值
- 判定控制循环的表达式的值,为假则跳出循环,为真则执行指定的语句,转到第三步。
- 执行一条语句来修正控制循环变量次数的变量值,然后返回第二步。
forever语句
forever语句的格式如下:
forever 语句;或forever begin 多条语句 end
forever循环语句常用于产生周期性的波形,用来作为仿真测试信号。它与always语句不同之处在于不能独立写在程序中,而必须写在initial块中。
repeat语句
repeat语句的格式如下:
repeat (表达式) 语句
或repeat (表达式) begin 多条语句; end
在repeat语句中,其表达式通常称为常量表达式。
while语句
while语句个格式如下:
while (表达式) 语句;
或while (表达式) begin 多条语句; end
for语句
for语句的一般形式如下:
for(表达式1;表达式2;表达式3)语句;
它的执行过程和C语言是一样的
块语句
块语句的作用是将多条语句合并成一组,使他们像一条语句那样。块语句包括两种类型:顺序块和并行块。块语句还有三种特点:命名块、命名块的禁用以及嵌套的块。
顺序块(也称过程块)
关键字begin end用于将多条语句组成顺序块,顺序块具有以下特点:
(1)顺序块中的语句是一条接一条按顺序执行的,只有前面的语句执行完成之后才能执行后面的语句(除了带有内嵌延迟控制的非阻塞赋值语句)
(2)如果语句包括延迟或事件控制,那么延迟总是相对于前面那条语句执行完成的仿真时间的。
(3)块内的语句是按顺序执行的,即只有上面一条语句执行完成之后下面的语句才能执行。
(4)每条语句的延迟时间是相对于前一条语句的仿真时间而言的。
(5)直到最后一条语句执行完,程序流程控制才跳出该语句块。
顺序块语句的格式如下:
begin
//语句1;
//语句2;
//......
//语句n;
end
//或者
begin:块名
//块内声明语句
//语句1;
//语句2;
//......
//语句n;
end
并行块语句
并行块具有下面4个特点:
(1)块内语句是同时执行的,即程序流程控制一进入到该并行块,块内语句同时并行地执行。
(2)块内每条语句的延迟时间是相对于程序流程进入到块内的仿真时间的。
(3)延迟时间是用来给赋值语句提供执行时序的。
(4)当按时间时序排序在最后的语句执行完后或一个disable语句执行时,程序流程控制跳出该程序块。
并行块的格式如下:
fork//fork真有意思,分叉,fork()函数生成子进程也是分叉!!join,是又汇合了啊。。
//语句1;
//语句2;
//......
//语句n;
join
//或者
fork:块名
//块内声明语句
//语句1;
//语句2;
//......
//语句n;
join
命名块
在Verilog HDL语言中还可以给每个块取一个名字,只需将名字加在关键词begin或者fork后面即可,这样做的原因有以下几点:
(1)可以在块内定义局部变量,即只在块内使用的变量。
(2)可以允许块被其他语句调用,如disable语句。
(3)在Verilog语言里,所有的变量都是静态的,即所有的变量都只有一个唯一的存储地址,因此进入或跳出块并不影响存储在变量内的值。
(4)命名块是设计层次的一部分,命名块中声明的变量可以通过层次名引用进行访问,
(5)命名块可以被禁用,例如停止其执行。
假如,有几个模块,在其中一个子模块中的块语句中定义了一些局部变量,在其他模块通过层次调用也可以访问到这些局部变量。
//命名块
module top;
initial //顺序块block1
begin: block1
integer i; //局部变量i
...... //通过层次名top.block1.i被其他模块访问
end
initial
fork: block2 //块名为block2的并行块
reg i; //局部变量
....
//可以通过层次名top.block2.i被其他模块访问
join
嵌套块
块可以嵌套使用,顺序块可以和并行块混合在一起使用。
//嵌套块
initial
begin
x = 1'b0;
fork
#5 y=1'b1;
#10 z = {x,y};
join
#20 w = {y,x};
end
命名块的禁用
Verilog通过关键字disable提供了一种中止命名块执行的方法,disable可以用来从循环中退出,处理错误条件以及根据控制信号来控制某些代码段是否被执行。对块语句禁用导致紧接在后面的那条语句被执行。对于C程序来说,disable类似于C中的关键字break,两者的区别在于break只能退出当前所在的循环,而使用disable可以禁用设计中任意一个命名块。
生成块
生成块可以动态的生成Verilog代码,生成语句能够控制变量的声明、任务或函数的调用,还能对实例引用进行全面的控制,编写代码时必须在模块中说明生成的实例范围,关键字generate-endgenerate用来指定该范围。
生成实例可以是以下的一个或多种类型:
1、模块
2、用户定义原语
3、门级原语
4、连续赋值语句
5、initial和always块
生成的声明和生成的实例能够在设计中被有条件的调用(实例引用)。在设计中可以多次调用(实例引用)生成的实例和生成的变量声明。生成的实例具有唯一的标识名,因此可以用层次命名规则引用。为了支持结构化的元件和过程块语句的相互连接,Verilog语言允许在生成的范围内声明下列数据:
1、net(线网)、reg(寄存器)
2、integer(整型数)、real(实型数)、time(时间型)、realtime(实数时间型)
3、event(事件)
在Verilog中有3种创建生成语句的方法,它们是:
1、循环生成、
2、条件生成
3、case生成
循环生成语句
- generate不放在always块中,而是always放在generate中
- generate-for的名字放在for的begin后面,不要忘记加
- task不能放在generate-for中,要实现同样的功能,用子模块
循环生成语句的主要目的是简化我们的代码书写,利用循环生成语句,我们可以将之前需要书写很多条比较相似的语句才能实现的功能用很简短的循环生成语句来代替。基本语法如下:
genvar i;
generate for(i=0;i<4;i=i+1)
begin:name
end
endgenerate
关于以上语法有四点要注意:
1、循环生成中for语句使用的变量必须用genvar关键字定义,genvar关键字可以写在generate语句外面也可以写在generate语句里面,但是要在for语句声明之前。
2、必须给for循环段起一个名字,这是一个强制规定,并且也是利用循环生成语句生成多个实例的时候,分配名字所必须的
3、for语句的内容必须加begin end,即使只有一条语句也不能省略。这也是一个强制规定,而且给循环起名字也离不开begin关键字
4、可以是实例化语句也可以是连续赋值语句
关于循环生成,举例如下:
module aa(a,b,c,d);
input [3:0] a,b;
output [3:0] c,d;
generate
genvar i;
for (i=0; i < 4; i=i+1)
begin :genExample
myAnd insAnd (.a(a[i]), .b(b[i]), .c(c[i]));
assign d[i] = a[i];
end
endgenerate
endmodule
module myAnd (a,b,c);
input a,b;
output c;
assign c=a&b;
endmodule
注意,利用循环生成语句生成的实例名称不能像数组例化那样用方括号表示,否则会报错。上述实例展开类似:
myAnd genExample(0).insAnd (.a(a[0]), .b(b[0]), .c(c[0]));
myAnd genExample(1).insAnd (.a(a[1]), .b(b[1]), .c(c[1]));
myAnd genExample(2).insAnd (.a(a[2]), .b(b[2]), .c(c[2]));
myAnd genExample(3).insAnd (.a(a[3]), .b(b[3]), .c(c[3]));
这也是为什么循环生成语句必须要有个名字,从上例我们可以看出,当循环语句用作实例时,所表述的功能跟数组实例化语句其实是类似的。
在仿真开始前,仿真器会对生成快中的代码进行确立(展平) ,将生成块转换为展开的代码,然后对展开的代码进行仿真。因此,生成块的本质是使用循环内的一条语句来代替多条重复的Verilog语句,简化用户编程。
关键词genvar用于声明生成变量,生成变量只能用在生成块之中;在确立后的仿真代码中,生成变量是不存在的。
一个生成变量的值只能由循环生成语句来改变。
循环生成语句可以嵌套使用,不过使用同一个生成变量作为索引的循环生成语句不能够相互嵌套。
条件生成语句
条件生成语句类似于if-else-if的生成构造,该结构可以在设计模块中根据经过仔细推敲并确定表达式,有条件的调用(实例引用)以下这些Verilog结构:
1、模块
2、用户定义原语、门级原语
3、连续赋值语句
4、initial或always语句
示例:根据不同的参数选择不同的乘法器:超前进位乘法器 树形乘法器
使用条件生成语句实现参数化乘法器
module multiplier(product,a0,a1)
//参数声明,该参数可以重新定义
parameter a0_width = 8;
parameter a1_width = 8;
//本地参数声明
//本地参数不能用参数重定义(defparam)修改
//也不能在实例引用时通过传递参数语句,即#(参数1,参数2,...)的方法修改
localparam product_width = a0_width + a1_width;
//端口声明语句
output [product_width-1:0] product;
input [a0_width-1:0] a0;
input [a1_width-1:0] a1;
//有条件地调用不同类型的乘法器
//根据参数a0_width和a1_width的值,在调用时引用相应的乘法器
generate
if(a0_width<8||a1_width<8)
cal_multiplier #(a0_width,a1_width) m0(product,a0,a1);
else
tree_multiplier #(a0_width,a1_width) m0(product,a0,a1);
endgenerate
endmodule
case生成语句
case生成语句可以在设计模块中,根据仔细推敲确定多选一case构造,有条件的调用(实例引用)下面这些Verilog结构。
1、模块
2、用户定义原语、门级原语
3、连续赋值语句
4、initial或always块
示例:
//本模块生成N位的加法器
module adder(co,sum,a0,a1,ci)
//参数声明,本参数可以重新定义
parameter N = 4;
//端口声明
output [N-1:0] sum;
output co;
input [N-1:0] a0,a1;
input ci;
//根据总线位宽,调用实例引用相应的加法器
//参数N在调用(实例引用)时可以重新定义,调用(实例引用)
//不同位宽的加法器是根据不同的N来决定的
generate
case(N)
//当N=1或2时分别选用位宽为1或2位的加法器
1:adder_1bit adder1(co,sum,a0,a1,ci);
2:adder_2bit adder2(co,sum,a0,a1,ci);
//默认的情况下选择位宽为N的超前进位加法器
default:adder_cla #(N) adder3(co,sum,a0,a1,ci);
endcase
endgenerate
endmodule