Verilog HDL有许多的行为语句,使其成为结构化和行为性的语言。Verilog HDL语句包括:赋值语句、过程语句、块语句、条件语句、循环语句、编译预处理等,如表5-14所示。符号“√”表示该语句能够为综合工具所支持,是可综合的。
5.3.1 赋值语句
赋值语句包括持续赋值语句与过程赋值语句。
1. 持续赋值语句
assign为持续赋值语句,主要用于对wire型(连线型)变量赋值。
例如:
assign c=~(a&b);
在上面的赋值中,a、b、c三个变量皆为wire型变量,a和b信号的任何变化,都将随时反映到c上来。
2. 过程赋值语句
过程赋值语句多用于对reg型变量进行赋值。过程赋值有阻塞赋值和非阻塞赋值两种方式。
a. 非阻塞赋值方式
非阻塞赋值符号为“<=”,如:b<=a;
非阻塞赋值在整个过程块结束时才完成赋值操作,即b的值并不是立刻就改变的。
b. 阻塞赋值方式
阻塞赋值符号为“=”,如:b=a;
阻塞赋值在该语句结束时就立即完成赋值操作,即b的值在该语句结束时立刻改变。
如果在一个块语句中(例如always块语句),有多条阻塞赋值语句,那么在前面的赋值语句没有完成之前,后面的语句就不能被执行,仿佛被阻塞了一样,因此称为阻塞赋值方式。
5.3.2 过程语句
VerilogHDL中的多数过程模块都从属于以下2种过程语句:initial及always。在一个模块(module)中,使用initial和always语句的次数是不受限制的。initial语句常用于仿真中的初始化,initial过程块中的语句仅执行一次;always块内的语句则是不断重复执行的。
1. initial过程语句
initial过程语句使用格式如下:
initial
begin
语句1;
语句2;
|
语句n;
end
intial语句不带触发条件,initial过程中的块语句沿时间轴只执行一次。initial语句通常用于仿真模块中对激励向量的描述,或用于给寄存器变量赋初值,它是面向模拟仿真的过程语句,通常不能被逻辑综合工具所接受。
2. aIways过程语句
always过程语句使用格式如下:
always @ (<敏感信号表达式>)
begin
//过程赋值
//if-else,case,casex,casez选择语句
//while,repeat,for循环
//task,function调用
end
always过程语句通常是带有触发条件的,触发条件写在敏感信号表达式中。只有当触发条件满足敏感信号表达式时,其后的“begin-end”块语句才能被执行。
3. 敏感信号表达式
所谓敏感信号表达式又称事件表达式,即当该表达式中变量的值改变时,就会引发块内语句的执行,因此敏感信号表达式中应列出影响块内取值的所有信号。若有两个或两个以上信号时,它们之间用“or”连接。
敏感信号可以分为两种类型:一种为边沿敏感型,一种为电平敏感型。
对于边沿敏感信号,posedge 表示时钟信号的上升沿作为触发条件,而negedge表示时钟信号的下降沿作为触发条件。
例如:
@ (a) //当信号a的值发生改变
@ (a or b) //当信号a或信号b的值发生改变
@ (posedge clock) //当clock的上升沿到来时
@ (negedge clock) //当clock的下降沿到来时
@ (posedge clock or negedge reset) //当clock的上升沿到来或reset信号的下降沿到来时
每一个always过程最好只由一种类型的敏感信号来触发,而最好不要将边沿敏感型和电平敏感型信号列在一起,如下面的例子。
always @ (posedge clk or posedge clr) //两个敏感信号都是边沿型
always @ (a or b) //两个敏感信号都是电平敏感型
always @ (posedge clk or clr) //不建议这样做,最好不要将边沿敏感型和电平敏感型信号列在一起
4. always过程块实现比较复杂的组合逻辑电路
always过程语句通常用来对寄存器类型的数据进行赋值,但always过程语句也可以用来设计组合逻辑。在比较复杂的情况下,使用assign来实现组合逻辑电路,会显得冗长且效率低下,而适当地采用always过程语句来设计组合逻辑,则显得简洁明了,效果也更好。
例如:通过对下面一个简单的指令译码电路的设计分析,我们不但了解了设计者的设计思路,而且因为使用了always过程语句,代码看起来整齐有序,便于理解。
`define add 3’d0 //常量定义
`define minus 3’dl
`define band 3'd2
`define bor 3'd3
`define bnot 3'd4
module alu(out,opcode,a,b);
output[7:0] out;
reg[7:0] out;
input[2:0] opcode; //操作码
input[7:0] a,b; //操作数
always @ (opcode or a or b) //电平敏感的 always块
begin
case (opcode)
`add: out=a+b; //加操作
`minus: out=a-b; //减操作
`band: out=a&b; //按位与
`bor: out=a|b; //按位或
`bnot: out=~a; //按位取反
default:out=8'hx; //未收到指令时,输出任意态
endcase
end
endmodule
5.3.3 块语句
块语句通常用来将两条或多条语句组合在一起,用快标志begin-end或fork-join界定的一组语句。当块语句只包含一条语句时,块标志可以缺省。
1. 串行块begin-end
串行块定义格式如下:
begin
语句1;
语句2;
|
语句n;
end
begln-end串行块中的语句按串行方式顺序执行,即只有上一条语句执行完成后才执行下面的语句。全部语句执行完后才跳出该语句块。
例如:
begin
regb=rega;
regc=regb;
end
第一条语句先执行regb=rega;然后流程转到执行第二条语句regc=regb;因为这两条语句之间没有任何时间延迟,所以regc的值实际就是rega的值。当然我们也可在语句之间控制延迟时间,例如下面就是利用延迟时间产生波形。
parameter d=100 //声明d为延迟参数,延迟100个时间单位
reg[7:0] r; //声明r为8位的寄存器变量
begin //由系列延迟产生波形
#d r=`h50;
#d r=`hE1;
#d r=`h00;
#d r=`hFF;
#d ->end_wave; //
end
2. 并行块fork-join
并行块定义格式如下:
fork
语句1;
语句2;
|
语句n;
join
并行块fork-join中的所有语句是并发执行的。
例如:
fork
regb=rega;
regc=regb;
join
由于fork-join并行块中的语句是并发执行的,在上面的regb=rega;语句执行完成后,regb更新为rega的值,而regc的值更新为没有改变前的regb值,regb与rega的值是不同的。
在Verilog HDL中,还可以给每个块取一个名字,只需将名字加在关键字begin或fork后面即可(例如:begin test)。这样做,可以在块内定义局部变量,也允许块被其他语句调用,如被disable语句调用。
5.3.4 条件语句
条件语句有if-else语句和case语句两类,它们都是顺序语句,应放在always块内。
1. if-eIse语句
if-eIse语句的使用方法有以下3种。
a. if (表达式) 语句1;
b. if (表这式) 语句1;
else 语句2;
c. if (表迭式1) 语句1;
else if (表达式2) 语句2;
else if (表达式3) 语句3;
|
else if (表达式n) 语句n;
else 语句n+1;
这3种方式中,“表达式”一般为逻辑表达式或关系表达式。系统对表达式的值进行判断,若为0,x,z,按“假”处理;若为1,按“真”处理,执行指定语句。语句如果是多句时应用“begjn-end”块语句括起来。对于if语句的嵌套,若不清楚if和else的匹配,最好用“begin-end”语句括起来。
2. case语句
if-eIse语句只有两个分支,而处理复杂问题时往往需要多分支选择。Verilog HDL的case语句是一种多分支语句,故case语句可用于多条件选择电路,如译码器、数据选择器、状态机及微处理器的指令译码等。case语句有case、casez、casex三种。
a. case语句
case语句的格式如下:
case (敏感表达式)
值1: 语句1; // case分支项
值2: 语句2;
|
值n: 语句n;
default:语句 n+l;
endcase
当敏感表达式的值为值1时,执行语句1;为值2时,执行语句2;………。如果敏感表达式的值与上面列出的值都不符的话,则执行default后面的语句。如果前面已列出了敏感表达式所有可能的取值,则default语句可以省略。
b. casez与casex语句
case语句中,敏感表达式与值1~值n间的比较是一种全等比较,必须保证两者的对应位全等。casez与casex语句是case语句的两种变体,在casez语句中,如果分支表达式某些位的值为高阻z,那么对这些位的比较就不予考虑,因此只需关注其他位的比较结果。而在casex语句中,如果比较的双方有一方的某些位的值为高阻z或不定值x,那么这些位的比较也都不予考虑。表5-15中是case、casez和casex在进行比较时的比较真值表。此外,还有另外一种标识x或z的方式,即用表示无关值的符号“?”来表示。
3. 条件语句使用注意事项
在使用条件语句时,应注意列出所有条件分支,否则,编译器认为条件不满足时,会引进一个触发器保持原值。这一点可用于设计时序电路,例如计数器的设计中,条件满足则加1,否则保持不变;而在组合电路设计中,应避免这种隐含触发器的存在。当然,一般不可能列出所有分支,因为每一个变量至少有4种取值0,1,z,x。为包含所有分支,可在if语句最后加上else;在case语句的最后加上default语句。
5.3.5 循环语句
在Verilog HDL中存在四种类型的循环语句,用来控制语句的执行次数,这四种语句分别为:
● forever 连续地执行语句;多用在“initial”块中。
● repeat 连续执行一条语句n次。
● while 执行一条语句直到某个条件不满足。如一开始条件不满足,则一次也不执行。
● for 有条件的循环语句。
1. forever语句
forever语句的使用格式如下:
forever 语句;
或
forever begin
|
end
forever循环语句连续不断地执行后面的语句或语句块,常用来产生周期性的波形,作为仿真激励信号。forever语句一般用在intial过程语句中。
2. repeat语句
repeat语句的使用格式为:
repeat (循环次数表迭式) 语句;
或
repeat (循环次数表这式)
begin
|
end
3. while语句
while语句的使用格式如下:
while (循环执行条件表达式) 语句;
或
while (循环执行条件表达式)
begin
|
end
while语句在执行时,首先判断循环执行条件表达式是否为真。若为真,执行后面的语句或语句块,然后再回头判断循环执行条件表达式是否为真;若为真的话,再执行一遍后面的语句,如此不断,直到条件表达式不为真。因此在执行语句中,必须有一条改变循环执行条件表达式的值的语句。
4. for语句
for语句的使用格式如下所示:
for (循环变量赋初值;循环结束条件;循环变量增值) 语句;
for语句执行过程如下:
a. 循环变量赋初值
b. 判断循环结束条件。若为“真”, 执行语句,然后转到c;若为“假”则退出for语句。
c. 执行循环变量增值语句,转回到b继续执行。
5.3.6 编译预处理
Verilog HDL语言和C语言一样,也提供了编译预处理功能。在编译时,通常先进行“预处理”,然后再将预处理的结果和源程序一起进行编译。
为了与其他语句区别开来,编译预处理语句以符号“`”开头。Verilog HDL提供了二十几条编译预处理语句,常用的有:`define、`include、`ifdef、`else、`endif等。
1. 宏替换`define
`define语句用于将一个指定的标识符(或称为宏名)来代替一个复杂的字符串,其使用格式为:
`define 宏名(标识符) 字符串
如:`define sum ina+inb
在上面的语句中,用简单的宏名sum来代替了一个复杂的表达式“ina+inb”。采用了这样的定义形式后,在后面的程序中,就可以直接用sum来代替表达式“ina+inb”。
2. 文件包含`include
文件包含是指一个源文件将另一个源文件的全部内容包含进来。`include用来实现文件包含的操作。其格式为:
`include “文件名”
使用`include语句时应注意以下几点:
a. 一个`include语句只能指定一个被包含的文件。如要包含n个文件,就要用n个`include语句。
b. `include语句可以出现在源程序的任何地方。被包含的文件若与包含文件不在同一个子目录下,必须指明其路径名。
c. 文件包含允许多重包含,比如文件1包含文件2,文件2又包含文件3等。
3. 条件编译`ifdef、`else、`endif
条件编译命令`ifdef、`else、`endif可以仅对程序中指定的部分内容进行编译,这3个命令的使用形式如下:
a. `ifdef 宏名(标识符)
语句块
`endif
这种表达式的意思是:如果宏名在程序中被定义过(用`define语句定义),则下面的语句块参与源文件的编译,否则,该语句块将不参与源文件的编译。
b. `ifdef 宏名(标识符)
语句块1
`else 语句块2
`endif
这种表达式的意思是:如果宏名在程序中被定义过(用`define语句定义),则语句块1将被编译到源文件中,否则,语句块2将被编译到源文件中。
5.3.7 任务和函数
任务和函数的关键字分别是task和function,利用任务和函数可以把一个大的程序模块分解成许多小的任务和函数,以方便调试,并且能使写出的程序结构更清晰。
1. 任务
任务定义的格式如下:
task <任务名>; //注意无端口列表
端口及数据类型声明语句;
其他语句;
endtask
任务调用的格式如下:
<任务名> (端口1, 端口2, ……端口n)
需要注意的是:任务调用时和任务定义时的端口变量应一一对应。
例如任务定义时:
task test;
input in1,in2;
output out1,out2;
#1 out1=in1&in2;
#1 out2=in1|in2;
endtask
任务调用时:
test(data1,data2,code1,code2);
调用任务时test时,变量data1和data2的值赋给in1和in2,而任务完成后,out1和out2的值赋给了code1和code2。
在使用任务时, 应特别注意以下几点:
a. 任务的定义与调用必须在同一个module模块内。
b. 定义任务时,没有端口名列表,但需要紧接着进行输入、输出端口和数据类型的说 明。
c. 当任务被调用时,任务被激活。任务的调用与模块调用一样通过任务名调用实现, 调用时,需列出端口名列表,端口名的排序和类型必须与任务定义时相一致。
d. 一个任务可以调用别的任务和函数,可以调用的任务和函数个数不受限制。
2. 函数
函数的目的是返回一个值,以用于表达式的计算。
函数的定义格式为:
function <返回值位宽或类型说明> (函数名);
端口声明;
局部变量定义;
其他语句;
endfunction
上面的定义格式中,<返回值位宽或类型说明>是一个可选项,如果缺省,则返回值为1位寄存器类型的数据。
3. 函数的调用
函数的调用是通过将函数作为表达式中的操作数来实现的。
调用格式如下:
<函数名> (<表达式> , <表达式>);
4. 任务与函数的区别
任务与函数的区别见表5-16。
联系客服