Verilog避坑:全是优先级惹的祸,运算符使用总结
2026/7/13 21:55:43 网站建设 项目流程

作为一个接触Verilog将近3年的菜鸟,大大小小的项目也写过不少。前些日子写一个项目,时间比较赶,某个模块嘎嘎写了大几百行代码后才开始仿真调式。解决掉不少显而易见的Bug后,仿真可以出结果,但结果总和软件对不上,于是开始了长达1小时的Debug。

当时找了好久都没有找到问题,心态炸裂,甚至认为是仿真软件出问题。哎,菜是原罪。最后,靠着一步一步修改代码才定位到了问题。哎,都是优先级惹的祸,而且是第2次遇到优先级的问题,遂记录下来来警示自己。

很多时候,在保证结果正确的情况下,设计者最好弄清楚运算优先级,盲目地添加括号可能会导致EDA工具将本可以并行的运算变成串行运算,使得逻辑级数增加,时序变差。

以下是当时项目中的源码,大家是否能够看出问题。

assign Pixel0_R = pixel_out0[65:54] & {12{sign_out0}} + {11'd0,sign_out0}; assign Pixel0_G = pixel_out0[43:32] & {12{sign_out0}} + {11'd0,sign_out0}; assign Pixel0_B = pixel_out0[21:10] & {12{sign_out0}} + {11'd0,sign_out0}; assign Pixel1_R = pixel_out1[65:54] & {12{sign_out1}} + {11'd0,sign_out1}; assign Pixel1_G = pixel_out1[43:32] & {12{sign_out1}} + {11'd0,sign_out1}; assign Pixel1_B = pixel_out1[21:10] & {12{sign_out1}} + {11'd0,sign_out1}; assign Pixel2_R = pixel_out2[65:54] & {12{sign_out2}} + {11'd0,sign_out2}; assign Pixel2_G = pixel_out2[43:32] & {12{sign_out2}} + {11'd0,sign_out2}; assign Pixel2_B = pixel_out2[21:10] & {12{sign_out2}} + {11'd0,sign_out2}; assign Pixel3_R = pixel_out3[65:54] & {12{sign_out3}} + {11'd0,sign_out3}; assign Pixel3_G = pixel_out3[43:32] & {12{sign_out3}} + {11'd0,sign_out3}; assign Pixel3_B = pixel_out3[21:10] & {12{sign_out3}} + {11'd0,sign_out3};

想要实现的功能是先做与运算,再进行加法运算。然而,加法操作的优先级高于逻辑与运算的优先级,所以正确的写法需要将逻辑与运算用括号括起来,如下。

assign Pixel0_R = ( pixel_out0[65:54] & {12{sign_out0}} ) + {11'd0,sign_out0}; assign Pixel0_G = ( pixel_out0[43:32] & {12{sign_out0}} ) + {11'd0,sign_out0}; assign Pixel0_B = ( pixel_out0[21:10] & {12{sign_out0}} ) + {11'd0,sign_out0}; assign Pixel1_R = ( pixel_out1[65:54] & {12{sign_out1}} ) + {11'd0,sign_out1}; assign Pixel1_G = ( pixel_out1[43:32] & {12{sign_out1}} ) + {11'd0,sign_out1}; assign Pixel1_B = ( pixel_out1[21:10] & {12{sign_out1}} ) + {11'd0,sign_out1}; assign Pixel2_R = ( pixel_out2[65:54] & {12{sign_out2}} ) + {11'd0,sign_out2}; assign Pixel2_G = ( pixel_out2[43:32] & {12{sign_out2}} ) + {11'd0,sign_out2}; assign Pixel2_B = ( pixel_out2[21:10] & {12{sign_out2}} ) + {11'd0,sign_out2}; assign Pixel3_R = ( pixel_out3[65:54] & {12{sign_out3}} ) + {11'd0,sign_out3}; assign Pixel3_G = ( pixel_out3[43:32] & {12{sign_out3}} ) + {11'd0,sign_out3}; assign Pixel3_B = ( pixel_out3[21:10] & {12{sign_out3}} ) + {11'd0,sign_out3};

在写代码过程中,除了容易忽略逻辑运算和算数运算的优先级,更容易忽略的是移位运算和算数运算的优先级。以下举一个例子,完成3*a的电路设计,加深大家的印象,代码如下。

设计代码:

module Precedence( a, dout1, dout2 ); input [3:0]a; output [3:0]dout1; output [3:0]dout2; assign dout1 = a << 2 - a; assign dout2 = (a << 2) - a; endmodule

仿真代码:

module Precedence_tb; reg [3:0]a; wire [3:0]dout1; wire [3:0]dout2; Precedence Precedence_inst( .a(a), .dout1(dout1), .dout2(dout2) ); integer i; initial begin for(i=0;i<16;i=i+1)begin a = i; #10; end $stop; end endmodule

仿真结果:

dout2的输出才是设计想要的3*a,而dout1是先进行减法运算后再完成移位运算,与想要的设计不符。

下面给出IEEE Standard Verilog Hardware Description Language中的运算优先级规定,给大家参考。不需要完全记住这个运算优先级,只需要在写代码的时候有意识地去注意这个问题,遇到不确定优先级的情况,要么直接加括号解决,要么查一下标准解决。本人喜欢尽量不用括号,所以才遇到了这么些问题。

上图中,表示的优先级含义如下:

运算符种类描述

+ - ! ~ & ~& | ~|

^ ~^ ^~(unary)

一元运算符

  • +正号,无显式电路,往往省略。

  • -负号,对操作数取反后加一,以补码形式存储。

  • !逻辑非,将操作数视为布尔值。

  • ~按位取反,逐位翻转。如~4'b0011 = 4'b1100。

  • &归约与,所有位相与,多比特变成单比特。如&4'b1111 = 1'b1。

  • ~&归约与非,所有位相与后再取反,多比特变成单比特。

  • |归约或,所有位相或,多比特变成单比特。如 |4'b0000 = 1'b0。

  • ~|归约或非,所有位相或后再取反,多比特变成单比特。

  • ^归约异或,所有位异或,多比特变成单比特。如 ^4'b1111 = 1'b0。

  • ~^^~归约同或,所有位同或,多比特变成单比特。

**

二元运算符

幂运算

  • ** 幂运算,硬件中不要用这个运算符

* / %

二元运算符

算术运算

  • *乘法,计算两数乘积。如果两数都是变量的话,建议使用DSP或设计乘法器。

  • /除法,计算两数除法。建议使用DSP或设计除法器。

  • %取模(取余),计算除法后的余数,符号同被除数。建议使用DSP或设计模约简算法。

+ -(binary)

二元运算符

算术运算

  • +加法,计算两数之和,硬件映射为加法器电路。

  • -减法,计算两数差值,本质上也是加法器

<< >> <<< >>>

二元运算符

移位运算

  • <<逻辑左移,高位丢弃,低位补0。4'b1100 << 1 = 4'b1000。

  • >>逻辑右移,低位丢弃,高位补0。4'b1100 >> 1 = 4'b0110。

  • <<<算术左移,同逻辑左移,符号位可能丢失。

  • >>>算术右移,低位丢弃,高位补符号位,用于有符号数(即用signed申明过)。

< <= > >=

二元运算符

关系运算

  • <小于

  • <=小于等于

  • >大于

  • >=大于等于

== != === !==

二元运算符

关系运算

  • ==逻辑相等,操作数中有X或Z时结果为X。

  • !=逻辑不等

  • ===全等,严格匹配,包括X和Z的比较。如4'b1x01 === 4'b1x011。

  • !==不全等

&(binary)

二元运算符

位运算

  • & 按位相与,4'b1100 & 4'b1010 = 4'b1000

^ ^~ ~^(binary)

二元运算符

位运算

  • ^ 按位异或,如4'b1100 ^ 4'b1010 = 4'b0110

  • ^~和~^ 按位同或,如4'b1100 ^ 4'b1010 = 4'b1001

|(binary)

二元运算符

位运算

  • | 按位相或,如 4'b1100 & 4'b1010 = 4'b1110

&&

二元运算符

逻辑运算

  • && 逻辑与

||

二元运算符

逻辑运算

  • || 逻辑或

?:(conditional operator)

三元运算符

  • ?: 三目运算,condition ? expr1 : expr2;,条件满足时结果为expr1,否则为expr2。

{} {{}}

拼接与复制运算符

  • {}拼接,连接多个信号,如{a, b}生成新总线。

  • {{}}复制,重复拼接,如{4{2'b01}}= 8'b01010101

以上就是有关Verilog运算优先级的感悟,欢迎大家在评论区进行探讨。细节决定成败啊!

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询