1
触发事件控制
①电平敏感事件是指 指定信号的电平发生变化时发生指定的行为。
②边沿触发事件(信号跳变沿)是指 指定信号的边沿信号跳变时发生指定的行为,分为信号的上升沿(x→1或者z→1或者0→1)和下降沿x→0或者z→0或者1→0)。
③信号跳变沿触发电路对信号的某一跳变沿敏感名字一个时钟周期内,只有一个上升沿和一个下降沿,因此计算结果在一个周期内保持不变,而电平触发电路则可能会引起数据在一个时钟周期内变化一次或多次。
2
条件语句
Verilog的条件语句包括if语句和case语句。
(1)if语句
①if语句中的条件判断表达式(括号中的那个)一般为逻辑表达式或者关系表达式或者就一个变量。如果表达式的值是0、X或者Z,则全部按照“假”处理;若为1,则按照“真”处理。
②在应用中,else if 分支的语句数目由实际情况决定;else分支可以省略,但在描述组合逻辑中,会综合得到锁存器。
(2)case语句
①case语句,case语句是一个多路条件分支的形式,常用于多路译码、状态机以及微处理器的指令译码等场合,有case分支 、casez分支、casex分支这三种形式。
②case语句首先对条件表达式求值,然后同时并行对各分支项求值并进行比较;当case语句跳转到某一分支后,控制指针将转移到endcase。
③case分支,case分支语句在执行时,条件表达式和分支之间进行的比较是一种按位进行的全等比较,也就是说,只有在分支项表达式和条件表达式的每一位都彼此相等的情况下,才会认为二者是相等的;此外x、z这两种逻辑状态也作为合法的状态参与比较(1 ==1,,0==0,x==x,z==z)。
④当分支的结果以常数出现时,如果没有指定位宽,则Verilog编译器默认其具有与PC字长相等的位宽。
⑤在casez分支中,如果分支取值的某些位为高阻z,则这些位的比较就不予以考虑,只关注其他位的比较结果。
⑥在casex分支中,则x、z都不予以考虑。
(3)if与case的区别
①if语句指定了一个有优先级的编码逻辑,而case语句生成的逻辑是并行的,不具有优先级。
②通常if结构得出的电路速度较慢,但是占用面积较小,由于速度慢,因此不适合构建特别长的if语句。
③case结构较if结构的速度快,但是占用面积大。
3
循环语句
循环语句有四种:for循环、repeat循环、while循环、forever循环。但是forever循环不能进行综合,而其他三种在一定情况下可以进行综合,因此这里记录可以综合的这三种。一般在电路设计中,不是经常用到循环语句,因为循环语句不好进行优化,占用的资源多,即使用到也是用到for循环。
(1)for循环
①for循环中,在循环次数确定的情况下,也就是循环结束条件是个常量下,才可以进行综合。主体注意要用begin...end来进行包括
。
②在Verilog中,不支持++,--这些运算,需要用完整的i=i+1来完成。
(2)repeat循环
①repeat语句执行指定的循环次数,如果循环次数是x或者z,那么循环次数将会按照0次处理。主体注意要用begin...end来进行包括。
②当循环次数在程序编译过程中保持不变时,可以进行综合。
(3)while循环
①只有在指定的循环条件为真(0、x、z都是假)时才会重复执行循环体,注意循环体要用begin...end来包括。
②在执行语句中,必须有改变循环执行条件表达式的值的语句,否则循环可能进入死循环。
循环语句使用注意事项:
①循环语句中出现的变量都采用阻塞赋值(时序中,即沿触发中),这是因为在always块中,使用非阻塞赋值时,只有在always结束后才会把右端的值赋给左边的寄存器,如果采用非阻塞赋值,则会造成循环语句只执行一次。
②在沿触发中,语句采用阻塞赋值,这是由于循环语句本质是由组合逻辑实现的,虽然整体是基于时序逻辑,但是循环部分确是组合逻辑。
③基于循环语句的Verilog显得相对精简,但是面向硬件设计的关注点是时序、面积、功耗等,在使用循环语句进行电路设计时要慎重。
4
任务和函数
(1)任务语句(task)
任务(task)就是一段封装在“task-endtask”之间的程序。任务是通过调用来执行的,而且只有在调用时才执行,如果定义了任务,但是在整个过程中都没有调用它,则该任务不会被执行的。调用某个任务时可能需要它处理某些数据并返回操作结果,所以Verilog 中的task 存在输入端和输出端。
任务定义语法格式如下所示:
图片
1 task<任务名>; // <= task task_id;
2 <端口及数据类型声明语句> // <= [declaration]
3 <语句1> // <= procedural_statement
4 <语句2> // <= procedural_statement
5 ..... // <=
6 <语句n> // <= procedural_statement
7 endtask // <= endtask
图片
上升的格式中,关键词 task 和 endtask 将它们之间的内容标志成一个任务定义,task 标志着一个任务定义结构的开始;task_id 是任务名;可选项 declaration 是端口声明语句和变量声明语句,任务接收输入值和返回输出值就是通过此处声明的端口进行的;procedural_statement 是一段用来完成这个任务操作的过程语句,如果过程语句多于一条,应将其放在语句块内;endtask 为任务定义结构体结束标志。一个任务的例子如下所示:
图片
1 task find_max; //任务定义结构开头,命名为 find_max
2 input [15:0] x,y; //输入端口说明
3 output [15:0] tmp; //输出端口说明
4 if(x>y) //给出任务定义的描述语句
5 tmp = x;
6 else
7 tmp = y;
8 endtask
图片
编写任务时,需要注意:
①调用某个任务时,可能需要它处理某些数据并返回操作结果,因此任务应当有接收数据的输入端和返回数据的输出端。
②任务的输入、输出和双向端口的数量不受限制,甚至可以没有输入、输出和双向端口。
③在任务定义的描述语句中,可以出现不可综合的语句,但是这样会引起任务不可综合。
④任务的定义结构定义不能在initial或者always语句中,在任务定义结构内不能出现 initial 和 always 过程块;但是任务的调用必须在这两个语句块中。
⑤任务定义中可以出现“disable”终止语句,但是这是不可综合的;在仿真中,如果出现的该终止语句,将中断正在执行的任务;任务被中断后,程序流程将返回到调用任务的地方继续向下执行。
⑥在第一行“task”语句中不能列出端口名称。
⑦可以使用出现不可综合操作符合语句(使用最为频繁的就是延迟控制语句,例如#10ns),但这样会造成该任务不可综合。
任务的使用(调用):
task_id (端口1,端口2,...,端口n);
task_id 是要调用的任务名,端口 1、端口 2,…是参数列表。参数列表给出传入任务的数据(进入任务的输入端)和接收返回结果的变量(从任务的输出端接收返回结果)。
任务调用的注意事项:
①任务调用中接收返回数据的变量必须是寄存器类型。
②任务调用语句和一条普通的行为描述语句的处理方法一致。
③可综合任务只能实现组合逻辑;也就是说调用可综合任务的时间为0。而在面向仿真的任务中可以带有时序控制,如多个时钟周期或时延,因此面向仿真的任务调用时间不为0。
④在任务中可以调用其他任务或者函数,也可以调用自身。
⑤当调用输入、输出和双向端口时,任务调用语句必须包含端口名列表,且信号端口顺序和类型必须和任务定义结构中的顺序和类型一致。任务的输出端口必须和寄存器类型的数据变量相对应。
(2)函数(function)
函数与任务类似,函数(function)是一段封装在“function-endfunction”之间的程序。函数的定义格式如下所示:
图片
1 function<返回值的类型或范围>(函数名);
2 <端口说明语句>// input XXX;
3 <变量类型说明语句>// reg YYY;
4 begin
5 <语句>
6 ........
7 函数名= ZZZ; //函数名就相当于输出变量;
8 end
9 endfunction
图片
函数编写的注意事项如下:
①定义函数时至少要有一个输入参量;可以按照ANSI 和module 形式直接定义输入端口;例如:
function[63:0] alu (input[63:0] a, b,input[7:0] opcode );
此外,函数至少有一个输入端口,可以有多个输入端口;不允许输出声明,也不允许双向端口,因为函数名本身充当一个返回值。
②和任务一样,函数的定义只能在模块中完成,不能出现在过程块中。
③函数结果中,不能出现任何形式的时间控制语句(如#,wait),也不能使用disable。
④函数内部可以调用函数,但是不能调用任务。
⑤如果描述语句是可综合的,则必须所有分支均赋值,不允许存在不赋值情况,只能按照组合逻辑方式描述。
函数编写的一个例子如下所示,这是一个计算有符号数绝对值的函数:
1 function[width-1 : 0] DWF_absval;
2 input [width-1 : 0] A;
3 begin
4 DWF_absval = ((^(A ^ A) !== 1'b0)) ? {width{1'bx}} :(A[width-1] == 1'b0) ? A : (-A);
5 end
6 endfunction
关于函数调用的注意事项:
①函数调用可以在过程块中完成,也可以在assign这样的连续赋值语句中出现。
②函数调用语句不能单独作为一条语句出现,只能作为赋值语句的右端操作数。
使用函数的一个完整实例:一个乘法电路——输入操作数a和b,输出他们的乘积;这里使用移位相加的算法来实现乘法。使用函数的实现的代码如下:
图片
1 module MAC #(parameter N=8)(
2 input clk,
3 input reset,
4 input [N-1:0] opa,
5 input [N-1:0] opb,
6 output reg[2*N-1:0] out
7 );
8
9 function[2*N-1:0] mult;//函数定义,mult 函数完成乘法操作
10 input[N-1:0] opa; //函数只能定义输入端
11 input[N-1:0] opb; //输出端口为函数名本身
12 reg[2*N-1:0] result;
13 integer i;
14 begin
15 result = opa[0]? opb : 0;
16 for(i= 1; i <= N-1; i = i+1)
17 begin
18 if(opa[i]==1) result=result+(opb<<i);
19 end
20 mult=result;
21 end
22 endfunction
23
24 wire[2*N-1:0] sum;
25 assign sum = mult(opa,opb) + out;
26 always@(posedge clk or negedge reset)
27 if(!reset)
28 out<=0;
29 else
30 out<=sum;
31
32 endmodule
图片
|