第三章 硬件描述语言verilog(二) 功能描述-组合逻辑(下)

简介: 第三章 硬件描述语言verilog(二) 功能描述-组合逻辑

上面的代码等价于:

C[0] = A[0] ^ B[0],
C[1] = A[1] ^ B[1],
C[2] = 0 ^ B[2],
C[3] = 0 ^ 0。


5.5.7 经验总结


逻辑运算符和位运算符的区别

逻辑运算符包括&&、||、!,位运算符包括&、|、~。那么逻辑运算符和位运算符有什么区别呢?

将逻辑与“&&”和按位与“&”进行对比可以看出,逻辑与运算符的运算只有逻辑真或逻辑假两种结果,即 1 或 0;而“&”是位运算符,用于两个多位宽数据操作。对于位运算符操作,两个数按位进行相与、相或或者非。

1670837716006.jpg

上面运行的结果为: a=1’b1,b=1’b1,c=1’b0,d=4’b000,e=4’b1111,f=4’b1000。


5.6 关系运算符

1670837728735.jpg

1670837735852.jpg

关系运算符有:>(大于)、<(小于)、>=(不小于)、<=(不大于)、== (逻辑相等)和!= (逻辑不等)。

关系操作符的结果为真(1)或假(0)。如果操作数中有一位为 x 或 z,那么结果为 x。


例:


23 > 45 :结果为假(0 )。

52 < 8'hxFF:结果为 x 。


如果操作数长度不同,长度较短的操作数在最重要的位方向(左方)添 0 补齐。例如:

'b1000 > = 'b01110 等价于:'b01000 > = 'b01110,结果为假(0)。


在逻辑相等与不等的比较中,只要一个操作数含有 x 或 z,比较结果为未知(x),


如假定 Data = 'b11x0; Addr = 'b11x0; 那么 Data == Addr,

比较结果不定,即结果为 x


5.7 移位运算符


在 Verilog HDL 中有两种移位运算符,分别为“<<”(左移位运算符)和“>>”(右移位运算符)。

下面分别介绍两者的用法:

1670837774374.jpg


5.7.1 左移运算符


在 Verilog HDL 中,用“<<”表示左移运算符。其一般表达式为:

A << n;

其中,A 代表要进行移位的操作数,n 代表要左移多少位。此表达式的意义是把操作数 A 左移 n位。左移操作属于逻辑移位,需要用 0 来填补移出的空位,即在低位补 0。左移 n个0。

1670837796726.jpg

以上代码由于左移了 2 位,所以在低位补 2 个零,所以上面代码运行结果是:

a = 4’b1100。

左移操作中有以下三点值得注意的地方:


(1)左移操作是不消耗逻辑资源的,甚至连与门、非门都不需要,它只是线的连接。

1670837809432.jpg

上面代码是将信号 b 左移两位并赋给 c,其所对应的硬件电路如下图:

1670837818934.jpg

(2)左移操作需根据位宽储存结果

在学习过程中可能看到过如下代码:

4’b1001<<1=4’b0010 与 4’b1001<<1=5’b10010


为什么操作数同样是 4’b1001,都是左移一位,但结果一个是 4’b0010,一个是 5’b10010 呢?

这是因为左移操作后,要看用多少位来存储结果。

1670837847100.jpg


上面代码中由于 a 是 4 比特,只能保存 4 位结果,所以 b 左移 1 位赋给 4 bit 的 a,用 0 填补移出的位后结果为 a = 4’b0010 ;

1670837855796.jpg

1670837863414.jpg

而上面代码中由于 a 是 5 比特,能保存 5 位结果,所以 b 左移 1 位赋给 5 bit 的 a,用 0 填补移出的位后结果为 a = 5’b10010


(3)左移操作的操作数可以是常数,也可以是信号。同样,左移操作的移位数、常数也可以是信号。

1670837870075.jpg

上面代码中 cnt 每个时钟加 1,由于是 3 比特,所以值为 0~2。a 则是 4’b1 左移 cnt 位。当 cnt等于 0 时左移 0 位,a 等于 4’b1;当 cnt 等于 1 时左移 1 位,a 等于 4’b10。以此类推,a 的每个时钟变化情况如下所示:

1670837878467.jpg

需要注意的是,当移位数是信号时,其综合的电路并不是简单的连线,可能会综合出如下图所示的选择器。然而即便如此,这种硬件电路所消耗的资源依然比较少。

1670837884805.jpg


5.7.2 右移运算符


在 Verilog HDL 中,用“>>”表示右移运算符。其一般表达式为:

A >>n;

其中,A 代表要进行移位的操作数,n 代表要右移多少位。此代码表示的意义是把操作数 A 右移n 位。在右移操作中有以下三点值得注意的地方:

(1)右移操作属于逻辑移位,需要用 0 来填补移出的空位,即在高位补 0,补多少个 0,取决

于保存结果的信号的位宽。

1670837901879.jpg

4’b0111 右移两位后的结果为 2’b01,由于 a 是 6 位的,2 位赋值给 6 位需要在高位补 0,因此

需要补 4 个 0。所以上面代码运行结果是:

a = 6’b0001

(2)与左移操作相似,右移操作是不消耗逻辑资源的,甚至连与门、非门都不需要,其只是线

的连接。

1670837912990.jpg

上面代码是将信号 b 左移两位并赋给 a,其所对应的硬件电路如下图所示

1670837921169.jpg

(3)左移操作的操作数可以是常数,也可以是信号。同样,右移操作的移位数可以是常数,也可以是信号。

1670837928755.jpg


上面代码中,cnt 每个时钟加 1,由于是 3 比特,所以值为 0~2。a 则是 4’b1000 右移 cnt 位。当 cnt 等于 0 时右移 0 位,a 等于 4’b1000;当 cnt 等于 1 时右移 1 位,a 等于 4’b0100。以此类推,a 的每个时钟变化情况如下图所示。

1670837936711.jpg

与左移操作类似,在右移操作中,如果移位数是信号时,其综合的电路就不是简单的连线,而是有可能会综合出如下图所示的选择器。然而同样在这一情况下,这种硬件电路所消耗的资源依然比较少。

1670837943286.jpg


5.7.3 经验总结


通过左移乘法运算

FPGA 中要尽量避免乘法运算,因为这种计算需要占用较大的硬件资源,并且运算速度较慢。当不得不使用乘法的时候,尽量乘以 2 的 N 次方,这样在设计中可以利用左移运算来实现该乘法运算,从而大大减少硬件资源。

当乘数是 2 的 N 次方的常数时可以用移位运算来实现乘法。例如:a2,等价于 a<<1;a4 等价于 a<<2;a*8 等价于 a<<3,依此类推。

即使乘数不是 2 的 N 次方的常数,也可以通过移位运算来简化实现。例如:

1670837955209.jpg

上面代码中 b 和 c 都可以实现 a127,但第 1 行消耗了一个乘法,而第 2 行则只用到一个减法器。

1670837966469.jpg

上面代码中,b 和 c 都可以实现 a67,但第 1 行消耗了一个乘法,而第 2 行则只用到两个加法器,从而节省了资源。

可以注意到,上面两个例子中的乘数都是常数,那么在设计时这种乘法也要花时间和精力来考虑优化吗?其实是不必要的,因为现在综合工具都很强大,当工具发现乘数是常数时会自动按上述过程进行优化,也就是说乘以常数在实质上并不消耗乘法器资源,可以放心使用。

但当出现乘数不是常数的情况时就要注意乘法的使用了。尽量将信号转换为与 2 的 N 次方相关的形式。例如当数据要扩大后来计算时,不要按照惯有思维将数据扩大 100 倍,而是应该直接将其扩大 128 倍。

利用右移实现除法运算

FPGA 设计中要极力避免除法,甚至是严禁使用“ /”来用于除法计算。这是由于除法器会占用极大的资源,其占用资源量要多于乘法器,而且很多时候不能在一个时钟周期内得出结果。而当不得不使用除法的时候,应尽量使除法转化为除以 2 的 N 次方形式,这样便可以利用右移运算来实现该除法运算,从而大大减少硬件资源。

当除数是 2 的 N 次方的常数时,就可以用移位运算来实现除法。例如:a/2,等价于 a>>1;a/4 等价于 a>>2;a/8 等价于 a>>3,依此类推。

与左移不同的是,当除数不是 2 的 N 次方的常数时,不能简单地通过移位运算来简化实现。总而言之,在 FPGA设计中应尽力避免除法。


利用左移位产生独热码

独热码,也叫 one-hot code,就是只有 1 个比特为 1,其他全为 0 的一种码制。例如 8’b00010000,8’b1000000 等。

独热码在设计时非常有用,可以用来表示状态机的状态使状态机更健壮,也可以用于多选一的电路中,表示选择其中的一个。

利用左移位操作,可以方便地产生独热码,例如产生 4’b0010,可以是 4’b1 << 1。类似地,也可以产生 1 个比特为 0,其他为 1 的码制。例如产生 4’b1011,可以是~(4’b1 <<2)。利用左移操作,还可以产生其他需要的数字结果:

例如,产生 5’b00111,可以是(5’b1<<3)-1。

例如,产生 5’b11100,可以是~((5’b1<<2)-1)。


5.8 条件运算符

1670837980142.jpg

1670837986482.jpg


5.8.1 三目运算符


Verilog HDL 语法中条件运算符(?:)带有三个操作数(即三目运算符),其格式一般表达为:

1670837998036.jpg

其含义为:当“条件表达式”为真(即逻辑 1),执行“真表达式”;当“条件表达式”为假(即逻辑 0),执行“假表达式”。即当 condition_expr 为真(即值为 1),选择 true_expr;如果 condition_expr 为假(值为 0),选择 false_expr。如果 condition_expr 为 x 或 z ,结果将是按以下逻辑 true_expr 和 false_expr 按位操作的值: 0 与 0 得 0,1 与 1 得 1,其余情况为 x 。

应用举例如下:

1670838005702.jpg


在上面的表达式中 s 如果为真,则把 t 赋值给 r;如果 s 为假,则把 u 赋值给 r。

对应硬件电路图如下所示:

1670838013088.jpg

条件运算符的使用有以下几点需要注意的地方:


(1)条件表达式的作用实际上类似于多路选择器,如图 1.3-8 所示。同时,其可以用 if-else 语句来替代。

1670838035534.jpg

(2)条件运算符可用在数据流建模中的条件赋值,这种情况下条件表达式的作用相当于控制开关。例如:

1670838046511.jpg

其中,表达式 Marks > 18 如果为真,则 Grade_A 赋值为 student;如果 Marks > 18 为假,则Grade_C 赋值为 student。

对应硬件电路图如下所示。

1670838053801.jpg

(3)条件运算符也可以嵌套使用,每个“真表达式”和“假表达式”本身就可以是一个条件表达式。例如:

1670838062918.jpg

上面代码所代表的含义是:表达式 M == 1 如果为真,则判断 CTL 是否为真,如果 CTL 为真就将 A 赋值给 OUT,如果为假就将 B 赋值给 OUT;如果 M = = 1 为假,则判断 CLT 是否为真,如果CLT 为真就将 C 赋值给 OUT,如果为假就将 D 赋值给 OUT。

对应硬件电路图如下所示。

1670838071934.jpg


5.8.2 if语句


“if”语句的语法如下:

if(condition_1)
procedural_statement_1;
{else if(condition_2)
procedural_statement_2};
{else
procedural_statement_3};


其含义为:如果对 condition_1 条件满足,不管其余的条件是否满足,都执行 procedural_statement_1,procedural_statement_2 和 procedural_statement_3 都不执行。


如果 condition_1 不满足而 condition_2 满足,则执行 procedural_statement_2,而 procedural _statement_1 和 procedural_statement_3 都不执行。

如果 condition_1 不满足并且 condition_2 也不满足时,执行 procedural_statement_3,而 procedural_statement_1 和 procedural_statement_2 都不执行。

通过下面一个例子来具体说明:

if(Sum < 60) begin
Grade = C;
Total_C = Total _C + 1;
end
else if(Sum < 75) begin
Grade = B;
Total_B = Total_B + 1;
end
else begin
Grade = A;
Total_A = Total_A + 1;
end


注意条件表达式必须总是用括号括起来,如果使用 if - if - else 格式,那么可能会有二义性,如下面的示例所示:

if(Clk)
if(Reset)
Q = 0;
else
Q = D;


这里存在一个疑问:最后一个 else 属于哪一个 if 语句? 是属于第一个 if 的条件(Clk)还是属于第二个 if 的条件 (Reset)? 这在 Verilog HDL 中已通过将 else 与最近的没有 else 的 if 语句相关联来解决。在这个例子中, else 与内层 if 语句相关联。

下面再举一个 if 语句的例子:

if(Sum < 100)
Sum = Sum + 10;
if(Nickel_In)
Deposit = 5;
Elseif (Dime_In)
Deposit = 10;
else if(Quarter_In)
Deposit = 25;
else
Deposit = ERROR;


建议:

1、条件表达式需用括号括起来。

2、若为 if - if 语句,请使用块语句 begin — end,如下所示。

if(Clk) begin
  if(Reset)
  Q = 0
else
  Q = D;
end


以上两点建议是为了使代码更加清晰,防止出错。


5.8.3 case 语句


case 语句是一个多路条件分支形式,其语法如下:

case(case_expr)
case_item_expr{case_item_expr} :procedural_statement
. . . . . .
[default:procedural_statement]
endcase


case 语句下首先对条件表达式 case_expr 求值,然后依次对各分支项求值并进行比较,执行第一个与条件表达式值相匹配的分支中的语句。可以在 1 个分支中定义多个分支项,且这些值不需要互斥。缺省分支覆盖所有没有被分支表达式覆盖的其他分支。

例:

case (HEX)
4'b0001 : LED = 7'b1111001; // 1
4'b0010 : LED = 7'b0100100; // 2
4'b0011 : LED = 7'b0110000; // 3
4'b0100 : LED = 7'b0011001; // 4
4'b0101 : LED = 7'b0010010; // 5
4'b0110 : LED = 7'b0000010; // 6
4'b0111 : LED = 7'b1111000; // 7
4'b1000 : LED = 7'b0000000; // 8
4'b1001 : LED = 7'b0010000; // 9
4'b1010 : LED = 7'b0001000; // A
4'b1011 : LED = 7'b0000011; // B
4'b1100 : LED = 7'b1000110; // C
4'b1101 : LED = 7'b0100001; // D
4'b1110 : LED = 7'b0000110; // E
4'b1111 : LED = 7'b0001110; ,// F
default:LED = 7'b1000000; // 0
endcase


书写建议: case 语句的缺省项必须写,防止产生锁存器。


5.8.4 选择语句


Verilog 语法中有一个常用的选择语句,其语法形式为:

vect[a +: b] 或 vect [a -: b];

vect 为变量名字,a 为起始位置,加号或者减号代表着升序或者降序,b 表示进行升序或者降序的宽度。

vect[a +: b]等同于 vect[a : a+b-1],vect 的区间从 a 开始向大于 a 的方向进行 b 次计数;vect[a -: b]等同于 vect[a : a-b+1],vect 的区间从 a 开始向 a 小的方向进行 b 次计数。

a 可以是一个常数也可以是一个可变的数,但 b 必须是一个常数。


例 1:vect[7 +: 3];


其中,起始位置为 7,+代表着升序,宽度为 3。即从 7 开始向比 7 大的方向数 3 个数。其等价形式为:

vect[7 +: 3]==vect[7 : 9]。


例 2:vect[9 -: 4];


其中,起始位置为 9,-代表着降序,宽度为 4。即从 9 开始向比 9 小的方向数 4 个数。其等价形式为:

vect[9 -: 4]==vect[9 : 6]。


在实际使用的过程中该语法最常用的形式是将 a 作为一个可变的数来使用。


例如需要设计具有如下功能的代码:

当 cnt0 时,将 din[7:0]赋值给 data[15:8];当 cnt1 时将 din[7:0]赋值给 data[7:0]。

在设计的时候便可以写成:data[15-8cnt -: 8] <= din[7:0](此时需要将 15-8cnt 视为一个整体 a,其会随着 cnt 变化而发生变化),这样一来就完成了对代码的精简工作。

选择语句的硬件电路结构如下图所示,其本质上是一个选择器。当 cnt0 时,选中 data[15:8]的锁存器,将 din[7:0]赋值给 data[15:8],而 data[7:0]的锁存器保持输出不变;当 cnt1 时,选中 data[7:0]的锁存器,将 din[7:0]赋值给 data[7:0],而 data[15:8]的锁存器保持输出不变

1670838213115.jpg

用 Modelsim 对以上例子进行功能仿真后的仿真图如下所示。

1670838221815.jpg

可以看出仿真结果满足实际的设计需求。

经验总结:在实际工程中可以多使用选择语句 vect[a +: b]或 vect [a -: b]的形式来进行代码编写,这将有助于精简设计代码。


5.8.5 经验总结


if 语句和 case 语句是 Verilog 里两个非常重要的语句, if 和 case 语句有一定的相关性,也有一定的区别。相同的地方在于两者几乎可以实现一样的功能,下面主要介绍一下两者之间的区别。


if 语句每个分支之间是有优先级的,综合得到的电路是类似级联的结构。case 语句每个分支是平等的,综合得到的电路则是一个多路选择器。因此,多个 if else-if 语句综合得到的逻辑电路延时有可能会比 case 语句稍大。对于初学者而言,在一开始学习 Veriolg 的过程中往往喜欢用 if else-if 语句,因为这种语法表达起来更加直接。但是在运行速度比较关键的项目中,使用 case 语句的效果会更好。


下面通过一个具体的案例来对比一下,使用 if 语句和 case 语句来描述同一功能电路的综合结果。


首先是用 if 语句写的代码。

1670838231905.jpg

1670838244902.jpg

其综合后的 RTL 视图如下所示。

1670838250481.jpg

从上图中所示的 RTL 图可以看到,此电路中含有两个二选一多路选择器,并且右边的优先级要高于左边(因为 q 的值是直接与右边的选一选择器连接),当 en[0]不为 1 时才会继续判断 en[1]。

也就是说,在 if 语句下综合出的电路含有优先级。接下来分析一下使用 case 语句描述的代码:

1670838258560.jpg

其综合后的 RTL 视图如下所示:

1670838268255.jpg

可以看出,使用 case 语句编写的逻辑代码综合出的电路是并行的,没有优先级,不影响电路的运行速度。

虽然在 RTL 视图中,两种语句综合出的电路存在着较大的差别,但是由于目前的开发工具已经足够智能,在布局布线的时候会自动对电路进行优化,其最终映射到 FPGA 内部的电路之间基本毫无差别。

1670838278203.jpg

最后总结一下 if 语句和 case 语句之间的区别与联系:

If 语句具有优先级,当 if 下的条件不满足时才执行 else 后面的部分。而 case 语句是并行的,没有优先级,这在两者综合出来的 RTL 视图中可以明显的观察出来。但由于现在的仿真和综合工具已经足够强大,最后综合后的结果 if…else…与 case…语句其实并无不同,只不过是两种不同的实现方式而已,因此基本上不用考虑这两者间的区别。在不影响功能的前提下设计师不需要做局部的优化工作,例如不需要考虑 if/case 语句的资源耗费差异、不需要考虑优化电路。只有在影响功能(即时序约束报错)的前提下,根据提示对电路进行优化。


5.9拼接运算符

1670838302248.jpg

拼接操作是将小表达式合并形成大表达式的操作,其形式如下:

{expr1, expr2, . . .,exprN} ;


拼接符是不消耗任何硬件资源的,其只是把线换一种组合方式,可以参照如下实例:


1670838310079.jpg

由于非定长常数的长度未知, 不允许连接非定长常数。因此如下所示代码是不符合语法规定的。

{Dbus,5} ;/ /不允许连接操作非定长常数。
相关文章
|
算法 芯片 计算机视觉
第三章 硬件描述语言verilog(二) 功能描述-组合逻辑(上)
第三章 硬件描述语言verilog(二) 功能描述-组合逻辑
567 0
第三章 硬件描述语言verilog(二) 功能描述-组合逻辑(上)
|
异构计算
第三章 硬件描述语言verilog(二) 功能描述-组合逻辑(中)
第三章 硬件描述语言verilog(二) 功能描述-组合逻辑
203 0
第三章 硬件描述语言verilog(二) 功能描述-组合逻辑(中)
|
存储 程序员 开发工具
第三章 硬件描述语言verilog(一)
第三章 硬件描述语言verilog(一)
288 0
第三章 硬件描述语言verilog(一)
|
芯片 异构计算
第三章 硬件描述语言verilog(三)功能描述-时序逻辑
第三章 硬件描述语言verilog(三)功能描述-时序逻辑
179 0
第三章 硬件描述语言verilog(三)功能描述-时序逻辑