关于Verilog HDL的一些技巧、易错、易忘点(不定期更新)

   本文记录一些关于Verilog HDL的一些技巧、易错、易忘点等(主要是语法上),一方面是方便自己忘记语法时进行查阅翻看,另一方面是分享给大家,如果有错的话,希望大家能够评论指出。

关键词:

  ·技巧篇:

      组合逻辑输出类型选择;

      语法上的变量交换;

 

  ·易忘篇:

      case/casex/casez语句;

      循环语句;

      数制和操作符;

      数据类型;

 

  ·易错:

      数据的截位与扩位

      子模块例化中隐式线网赋值

技巧篇:

  1、组合逻辑输出:描述一个纯组合逻辑电路时,尽量不要把输出定义成输出类型,例如描述下面的电路:

 

 1 module mux #(parameter N=2)(
 2 
 3 input [N-1:0] a, // sel=00时,选择该输入
 4 
 5 input [N-1:0] b, // sel=01时,选择该输入
 6 
 7 input [N-1:0] c, // sel=10时,选择该输入
 8 
 9 input [N-1:0] d, // sel=11时,选择该输入
10 
11 input [1:0] sel, //选择器
12 
13 output[N-1:0] mux_out);// 选择器结果输出
14 
15 reg [N-1:0] mux_temp; // 临时变量,用于防止其他调用者误认为输出锁存
16 
17 assign mux_out=mux_temp;
18 
19 //always_comb //该语句在systemverilog中可以替换下面的语句并检查
20 
21 always @ (a or b or c or d or sel)
22 
23   case (sel)
24 
25     0 : mux_temp = a;
26 
27     1 : mux_temp = b;
28 
29     2 : mux_temp = c;
30 
31     3 : mux_temp = d;
32   
33     default : $display("Error with sel signal");
34 
35   endcase
36 
37 endmodule

 

  2、语法上的变量交换:在always 语句块内部,任何一个语句块(以begin 开始,end 结束)都是串行执行的,只是存在赋值立刻生效还是事后生效的差异,即后面将要重点论述的阻塞赋值和非阻塞赋值两种区别(这两种赋值语句综合的区别请看我的另一篇博文,链接为:

        http://www.cnblogs.com/IClearner/p/7188875.html)。

对于下面的代码,从纯语法上讲:

1 always@(*)begin
2 
3     temp=b;
4 
5     b=a;
6 
7     a=temp;
8 
9 end

上面的例子,就是一个串行执行的例子,能够完成 a 与 b 的数值交换,如果不是串行执行,上述代码将很难完成类似各类程序控制。

 

 

易忘篇/陌生篇:

 

  1、case语句的各种注意情况及对应综合电路

   (留坑,以后填)

  2、循环语句:循环语句,主要包含 for、while、forever、repeat 四类语句,但只有 for 语句才有可能具备可综合性,其余均为测试验证所准备。

循环语句 for 的语法为:

    for(表达式 1;表达式 2;表达式 3) 语句

其实可以将 for 语句理解为:

    for(循环变量赋初值;循环结束条件;循环变量增值)执行语句

·for 循环的例子如下,这是最原始的一个8bit 乘法器实现,其中<<表示左移,等效于乘以2 的移位次方:

module mac_8 #(parameter size = 8)(
    input wire [size-1:0] opa, opb,
    output reg [2*size-1:0] mult_out
    );
reg[2*size-1:0] result;
integer bindex;
always @(*)begin
    result =  opb[0]?opa:0;
    for( bindex=1; bindex<=size-1; bindex=bindex+1 )begin//根据乘法特性,判断后是否进行移位
        if(opb[bindex]) result = result + (opa<<(bindex));
     end
    mult_out = result;
end
endmodule

仿真波形如下所示:

(上述例子也可以当做技巧看,也就是使用位移实现乘法运算)

 

  3、数制与操作符

  这里数制和操作符...其实我已经基本是滚瓜烂熟了,放在这里是给初学者查询的...

上面那个图是我在word写的,在Verilog中,一般一个整数我们称呼为  xx位xx进制数。数的位数和符号数的易忘点,图已经说明了。

通常有时候,一些初学者往往不知道位数与值之间的关系,我当初也不知道,现在我来给自己备忘一下:

    3'b101:3位的二进制数101,(在我们的惯用的数制:10进制)数值大小为5;

    3'd6:3位的10进制数6,等效于3'b110;数制大小为6;

    3'd9:由于只有3位,十进制数9的数值为4位:4‘b1001;所以3‘d9的数值高位被阶段,3'd9表示真实的数值为3’d1或者3‘b001,数值大小为1

用例

说明

'hAE

8 位十六进制数

10'b10

左边添 0 占位,实际为 10'b0000000010

10'bx1x0

左边添 x 占位,实际为 10'bxxxxxxx1x0

3'b1001_0011

3'b011 相等

 

 

运算类别

符号

运算符含义

算术运算符

+

加法(二元运算符)

-

减法(二元运算符)

*

乘法(二元运算符)

/

除法(二元运算符)

%

取模(二元运算符)

关系运算符

>

大于

<

小于

>=

不小于

<=

不大于

==

逻辑相等

!=

逻辑不等

逻辑运算符

&&

逻辑与

||

逻辑或

逻辑非

按位逻辑运算符

~

一元非,相当于非门运算

&

二元与,相当于与门运算

|

二元或,相当于或门运算

^

二元异或,相当于异或门运算

~^,^~

 

二元异或非即同或,相当于同或门运算

 

 

移位运算符

>>

右移

<<

左移

赋值运算符

=

阻塞赋值,等效于立即生效

<=

非阻塞赋值,等效于当前模块结束后赋值,或者下个时钟

 

周期赋值生效

缩减运算符

&

一元与,相当于数据 bit 逐个进行与操作

|

一元或,相当于数据 bit 逐个进行或操作

^

一元异或,相当于数据 bit 逐个进行异或操作

~^

一元同或,相当于数据 bit 逐个进行同或操作

      单元运算符:可以带一个操作数,操作数放在运算符的右边。

      二元运算符:可以带二个操作数,操作数放在运算符的两边。

      三元运算符:可以带三个操作数,这三个数用三目运算符分隔开。

  缩减运算符是对单个操作数进行或与非递推运算,最后的运算结果是一位的二进制数。缩减运算符目前支持或与非三种操作。具体运算过程如下:第一步先将操作数的第一位与第二位进行或与非运算,第二步将运算结果与第三位进行或与非运算,依次类推,直至最后一位。

  拼接运算符则与缩减运算符相反,主要目的是将两个或多个信号的某些位拼接起来进行运算操作。拼接运算不消耗任何逻辑资源,只是一个单纯的连线逻辑。其使用方法如下:

    {信号1的某几位,信号2的某几位,..,..,信号n的某几位}

即把某些信号的某些位详细地列出来,中间用逗号分开,最后用大括号括起来表示一个整体信号。例如:

    {a,b[3:0],c,3'b101}

也可以写成如下形式:

    {a,b[3],b[2],b[1],b[0],c,1'b1,1'b0,1'b1}

在位拼接表达式中不允许存在没有指明位数的信号。这是因为在计算拼接信号的位宽的大小时必需知道其中每个信号的位宽。位拼接还可以用重复法来简化表达式,例如:

    {6{a}}//这等同于{a,a,a,a,a,a,a},a可为任意比特位宽

位拼接还可以用嵌套的方式来表达,例如:

    {c,{3{a,b}}}//这等同于{c,a,b,a,b,a,b}

用于表示重复的表达式如上例中的6 和3,必须是常数表达式或者参数

 

 

  4、数据类型:

  ·当一个wire 类型的信号没有被驱动时,缺省值为Z(高阻)。

  ·有一种专门针对存储器模型(RAM)的定义方法,例如:

    (* ramstyle ="MLAB"*)reg[31:0] RegFile1[15:0];

    (* ramstyle ="MLAB"*)reg[31:0] RegFile2[15:0];

  在ASIC 设计中,这种描述方式只会被识别为一系列的寄存器堆,并不会被识别为RAM;ASIC 中应当利用RAM 单元库(IP)例化的方法描述RAM。而在FPGA 中,综合器首先将这种描述识别为RAM 的声明,并通过识别对象的行为确认描述对象是RAM 还是寄存器堆。如果后续的描述行为满足RAM 的特征,就自动替换为FPGA 内部内置的RAM 单元库,否则将识别为寄存器堆。上例的RegFile1RegFile2 对象在Altera FPGA 中,将被识别为16 个32bit 位宽的RAM,而且指定为MLAB 类型。

 

 易错篇

1、数据的截位与扩位

(1)扩位操作

  

  位宽扩展:如果所规定的位宽太小,那个将会截断高的几个位(如2’b1101,将变成2'b01),如果指定的位宽太大,则会用0或者x/z来向左扩展数值,但不会扩展符号位。

  对于有符号数:如果位宽位宽小于数值规定,符号位可能被截断(如数-4‘sd15,即1111_0001,将会被截断,代表的值为+1,即0001);

如果位宽大于数值规定,都是用0来扩充,因此负数可能被扩充为正数。

  ①在定点计算中,经过加法和乘法运算后,输出结果的位宽会增加。但如果继续使用和输入操作数同等位宽的数来表示结果,就会丢失有用的比特信息,造成输出结果错误。

  ②例如,在有限字长的情况下,若两个M 位的数相加,其结果最高可能为M +1位;若两个M 位的数相乘,其结果最多可为2M 位。

  ③4 比特加法运算中的扩位现象:

有符号数):

  4'b0101 和4'b0111 分别对应着+5 和+7,二者相加后本应为+12,即5'b01100。但由于位宽限制,如不扩位,只能保留低4 位,即4'b1100,对应着-4,造成严重的计算错误。类似的错误还会造成负数相加变成正数。

无符号数):

  4'b1111 + 4'b1111 = 5'11110 ,但是由于加数是4 位,在Verilog语言中只保留低4 位,就会得到4'b1111 + 4'b1111 = 4'1110 的结果,这样就会造成计算错误。

注意:

   ①无论是有符号数还是无符号数,高位宽的变量(或者数)赋值给低位宽的变量(或者数),低位宽将只能接收到高位宽数的的低位数值。

   ②低位宽赋值给高位宽时,有:无符号数/变量 给 无符号数/变量,符号数/变量 给 符号数/变量,这两个都不会出错;

无符号数/变量 给 符号数/变量,符号数/变量 给 无符号数/变量 时将出现错误。

 

 

2)截位操作

  ①在有限字长的情况下,若两个M 位的数相加,其结果就是M +1位;若两个M 位的数相乘,其结果就是2M 位。但在实际的操作过程中,考虑到资源的问题,不能任由相加、相乘操作来增加操作数的位宽,必须进行截断。

  ②例如,两个16 位数相乘后,其结果为32 位,如再和一个16 位数相乘,结果就变为48 位,这样下去,用不了几个乘法操作就会使操作数的位宽剧增,所占用的硬件资源也会很多。因此,需要将乘积结果进行截位,寄存在M 位的寄存器中。

3)截位与扩位规范(Verilog中

  ①加法实现规范,扩展符号位后相加。

1   reg[12:0] Adder_Out;
2   reg[11:0] Adder_In1,Adder_In2;
3   Adder_Out <= {Adder_In1[11],Adder_In1} + {Adder_In2[11],Adder_In2};

  ②对于截取乘法的结果,需要加溢出保护的截取规范。例如要截取12 比特输出的第6 位到第2 位,其实现代码为:

1 if((addRakeOut[11:6] == 0) || (addRakeOut[11:6] == 63))
2     tmptraffic <= addRakeOut[6:2];
3 else
4     tmptraffic <= (addRakeOut[11] == 1) ? 16 : 15;

或者:

1 if((addRakeOut[11:6] == 6'b000000) || (addRakeOut[11:6] == 6'b111111))
2     tmptraffic <= addRakeOut[6:2];
3 else
4     tmptraffic <= (addRakeOut[11] == 1) ? 5’b10000 : 5’sb01111;

  ③移位操作规范,移位操作的截位和加法器的截位规则类似,如16 比特数据的左、右1 比特移位示例:

1 reg[15:0] Data;
2 if((Data[15:14] == 2'b00) || (Data[15:14] == 2'b11))
3     Data <= {Data[14:0],1'b0};//左移一位
4 else
5     Data <= (Data[15]) ? 16'b1000_0000_0000_0000 : 16'b0111_1111_1111_1111;
6 //右移一位
7 Data <= {Data[15],Data[15:1]};

 2、子模块例化中隐式线网赋值时

  子模块例化时,要用线网类型的变量  连接  被调用的子模块端口信号  和主模块的 信号,当这个线网变量没有在主模块中声明时,该线网变量的位宽有以下情况:

  ①如果该线网变量是用于连接 主模块的端口信号 和  被调用的子模块的端口信号,那么该线网变量的位宽 跟 主模块端口的位宽相同。

  ②如果该线网变量只在主模块里起连接作用(如连接两个 被调用的子模块),该线网位宽默认为1位。

  ③Verilog判断隐式线网变量的位宽从顶层环境开始,也就是判断隐式线网的位宽并不参看被调用的子模块端口的位宽。

 

原文地址:https://www.cnblogs.com/IClearner/p/7203887.html