Verilog 学习笔记(5)——Verilog 语言基础(3)
Contents
行为描述
三种描述方式中的最后一种行为描述。理论上所有的电路综合到最后都是门级的,而门级显然是纯组合逻辑可以完成的。但是为了描述方便,所以有了行为描述和结构化描述。本章介绍一些行为描述的基本语句和用法,用来从行为上去描述电路。
initial 和always
在行为描述中,主要的语句类型分为 initial 和 always。注意与数据流描述所用的数据类型不同,一般所描述的信号不是 wire(线网)类型,而是 reg(寄存器)类型。
initial
initial 在0仿真时间执行,并且只执行一次,一般用来对模块进行初始化。举个例子:
initial
Clk=0; //在时间0时刻时钟信号Clk赋值0
always
always 语句可以执行多次,既可以用来描述组合逻辑也可以用来描述时序逻辑。同时 always 语句一般有两种控制方式:事件(@)或延时(#)(实际上还有等待语句)。举个例子:
always@(*)
begin
sum=a^b^cin;
cout= a&b | a&cin | b&cin;
end
这个语句就是以事件控制的组合逻辑的 always 块的基本写法,其中括号内为敏感列表,通俗地讲就是列表内所表示的信号发生改变时就执行块内的语句。括号内为 * 即表示所有信号都在敏感列表内,只要有一个信号改变就执行块内语句。同时和 C 语言不同的是,verilog 里语句块并不是用大括号来表示,而是使用 begin
和 end
。 以上组合逻辑同时也可以用 assign 表示,如下:
assign sum=a^b^cin;
assign cout= a&b | a&cin | b&cin;
区别在于数据类型 always 中 sum、cout 要使用 reg ,即使没有信号驱动寄存器也会保存最后的信号,而 assign 为 wire 型,需要持续驱动。
此外,always 语句还可以用来描述时序逻辑,如下:
always@(posedge Clk or negedge rst)
begin
if(~rst)
T<=~T; //时钟信号上升沿T取反
else
T<=0; //复位信号时将T置0
end
其中 posedge Clk
指时钟信号上升沿,同样 negedge
指下降沿,若多个信号触发则在信号之间用 or 隔开。<=
是非阻塞赋值,关于阻塞赋值和非阻塞赋值在下面介绍。
延时在语言基础(1)中有提及,大致就是#后跟数字为延时,这里不再赘述。
过程赋值
initial 和 always 语句块里的赋值语句称为过程赋值语句。赋值对象只能是寄存器变量。
阻塞赋值
阻塞赋值中使用和连续赋值一样的=
进行赋值,=
右边的表达式计算与=
左边变量被赋值为连续的两个操作,中间不能插入其他操作,由于这个过程执行前其他语句不能执行,相当于阻塞住了,故称为阻塞赋值。
非阻塞赋值
非阻塞赋值中使用<=
进行赋值,与阻塞赋值不同的是,非阻塞赋值先将过程块中的所有右侧计算完成并寄存,再赋给左值,并不会等待前一条赋值完成再对下一条进行计算。
阻塞赋值与非阻塞赋值在应用中的区别
举个例子,以下为两个分别使用阻塞赋值与非阻塞赋值的代码块:
always@(posedge Clk)
begin
a1=b1;
b1=a1;
end
//阻塞赋值
always@(posedge Clk)
begin
a2<=b2;
b2<=a2;
end
//非阻塞赋值
在上面的代码中,阻塞赋值先将 b1 的值赋给 a1,再将已经被赋值的 a1 的值赋给 b1,实际的效果就是 b1 的值赋给 a1 而自己不变。非阻塞赋值先把赋值前的 b2 和 a2 的值分别寄存,再分别赋值给 a2 和 b2,所实现的功能就是 a2 和 b2 的值互换。
实际操作中一般组合逻辑用阻塞赋值,时序逻辑用非阻塞赋值就行。
过程连续赋值
还有一类赋值语句比较特殊,称为过程赋值语句。类型主要有以下两种:
- assign 与 deassign:在过程语句块中强制为寄存器变量赋值并释放;
- force 与 release:在过程语句块中对寄存器和线网进行强制赋值和释放。
举个例子,一个带异步清零端的 D 触发器的清零部分:
always@(Clr)
begin
if(~Clr)
assign Q=0; //D的值对Q无效,在清零信号持续期间Q强制连续赋值0
else
deassign Q; //释放Q
语句组
除了上文提到的begin…end
语句组,还有fork…join
语句组,区别在于前者是顺序执行的,后者是并行执行的。
举个例子:
begin
#5 a=b;
#10 c=d;
end
fork
#5 w=x;
#10 y=z;
join
前者在时刻5执行a=b
时刻15(5+10)执行c=d
,后者在时刻5执行w=x
时刻10执行y=z
编程语句
为了更方便地描述电路的行为,verilog 中也有一些编程语句。
if-else
前文已经用到的比较多了,大致的写法就是
if(condition)
begin
...
end
else
begin
...
end
注意的是,使用时尽量避免引入不必要的寄存器而导致的功能错误,也就是说要保证所有的情况下需要的变量都有确定的状态。若只写 if 语句而不确定 else 的语句,当综合时由于 else 的情况未确定就会为该变量增加一个寄存器以保持该变量的值不变,可能造成功能的错误。在下面的 case 语句中也是一样的,在需要的状态后加 default 确定剩下状态。
case
case 语句与 C 中的 switch 语句较为类似,可以实现类似于选择器的效果。举个例子,一个四选一的选择器:
module Mux(input [3:0] in,input [1:0] sel,output reg b);
//注意声明输出默认为wire,需要改为reg
always@(*)
case(sel)
2'b00:b=a[0];
2'b01:b=a[1];
2'b10:b=a[2];
2'b11:b=a[3];
endcase
endmodule
其中 case 后的括号是选择控制信号,下面跟的是不同控制信号下执行的语句。与 C 中不同的是,这些语句是并行的,所以不需要跳出。最后 endcase 收尾。
在有一些情况下,输入信号只有某些位有用,这个时候为了简化状态,可以使用 casez 语句。比如一个8位优先译码器(输出右往左第一个信号1的位置,如10100000就输出6,全0输出0),如果使用 case 语句就需要$2^8$个状态,显然是费力而没有必要的,下面给出用 casez 的方案。
module top_module(
input [7:0] in,
output reg [2:0] pos
);
always@(*)
casez(in)
8'bzzzzzzz1:pos=3'd0;
8'bzzzzzz10:pos=3'd1;
8'bzzzzz100:pos=3'd2;
8'bzzzz1000:pos=3'd3;
8'bzzz10000:pos=3'd4;
8'bzz100000:pos=3'd5;
8'bz1000000:pos=3'd6;
8'b10000000:pos=3'd7;
default:pos=0;
endcase
endmodule
其中的 z
也可以换成 ?
for
在某些需要使用重复的语句的时候,为了避免反复编写的麻烦,可以使用 for 语句。
比如当有一个100位的输入需要倒序输出时,需要赋值100次,这个情况下可以使用 for 来简化语句。
module top_module(
input [99:0] in,
output [99:0] out
);
integer i;
always@(*)
for(i=0;i<100;i=i+1)
out[i]=in[99-i];
endmodule
基本写法和 C 中一致,但是循环变量注意定义在循环外(据说定义在循环里 ISE 综合会警告,仿真会错误),并且整形是 integer 不是 int。
其他
此外还有一些语句,如 forever(一直循环执行) 、repeat(n)(重复执行 n 次)、while(condition)(为真时循环执行)和上面的比较类似或者比较简单就不再介绍了,感兴趣的可以自己了解。
生成块
前面有提到过 Verilog 作为硬件描述语言与编程语言的区别,并且有提到其实循环是不能或者很难综合的,上面的循环语句本质是将该语句重复数遍,再将这些重复的语句进行综合。比如 for 就是重复了数次语句块的所有语句的综合,if 和 case 就是将所有的条件下的电路都综合出来,再根据判断条件选择哪块电路。在某些情况下这是对资源的浪费。而生成块则是动态生成语句,在 for 里就是将单独的 for 内语句实例化,再进行相应操作,这样就不需要在循环的参量改变时重新综合,再条件语句中就是根据条件只综合需要的状态,而不需要综合多余的状态。
如上面的语句用生成块就可以写成:
genvar i;
generate
for (i=0; i<100; i = i+1)
begin: my_block_name
assign out[i] = in[99-i];
end
endgenerate
注意生成块需要命名,并且循环的变量不用 integer 而是 genvar。
牛刀小试
在已经给出一个 bcd 一位全加器的情况下,编写100位 bcd 全加器。
bcd 全加器:
module bcd_fadd {
input [3:0] a,
input [3:0] b,
input cin,
output cout,
output [3:0] sum );
顶层端口声明:
module top_module(
input [399:0] a, b,
input cin,
output cout,
output [399:0] sum );
参考解答:
module top_module(
input [399:0] a, b,
input cin,
output cout,
output [399:0] sum );
wire [399:0] c;
genvar i;
bcd_fadd a0(
.a(a[3:0]),
.b(b[3:0]),
.cin(cin),
.cout(c[0]),
.sum(sum[3:0])
);
generate
for(i=4;i<400;i=i+4)
begin:bcdf
bcd_fadd adder(
.a(a[i+3:i]),
.b(b[i+3:i]),
.cin(c[i-4]),
.cout(c[i]),
.sum(sum[i+3:i])
);
end
endgenerate
assign cout=c[396];
endmodule
No Comments