在之前的讨论中,加数、被加数、减数和被减数的运算过程都没有使用有符号数。现在使用有符号数的补码重新对其进行表示。假设加数、被加数、减数和被减数都是 2 位(范围为-2~1),
考虑到进位和借位原因,结果用 3 位来表示(范围为-4~3)。因为结果位宽变为 3 位,所以减数和被减数都扩展成用 3 位表示,列出下表:
总结运算步骤如下:
根据“人的常识”,预计结果的最大最小值,从而确定结果的信号位宽。
将加数、减数等数据,位宽扩展成结果位宽一致。
按二进制加减法进行计算。
通过以上方式,得到的就是补码的结果。事实上,在 FPGA 甚至计算机系统中,所有数据的保存的方式都是补码的形式。如果想要了解更多关于补码的内容可以参阅相关资料。
5.4 逻辑运算
在 Verilog HDL 语言中存在 3 种逻辑运算符,它们分别是:
(1)&&:逻辑与;
(2)| | :逻辑或;
(3)!:逻辑非
5.4.1 逻辑与
“&&”是双目运算符,其要求有两个操作数,如 a && b。
(1)1 位逻辑与
A 和 B 都为 1 时,C 为 1,否则 C 为 0。
对应硬件电路图如下所示:
(2)多位逻辑与
A 或 B 都不为 0 时,C 为 1,否则为
5.4.2 逻辑或
“||”是双目运算符,其要求有两个操作数,如 a||b。
(1)1 位逻辑或
A 和 B 其中 1 个为 1,C 为 1,否则 C 为 0。
对应硬件电路图如下图所示:
(2)多位逻辑或
A 和 B 其中 1 个非 0,C 为 1,否则 C 为 0。
对应硬件电路图如下图所示:
5.4.3 逻辑非
“!”是单目运算符,只要求有一个操作数,如!(a>b)。
对操作数 a 需要先判断非 a 是否为真,为真就执行{}内的操作,为假的话就结束操作。下表为逻辑运算的真值表,其表示当 a 和 b 的值为不同的组合时各种逻辑运算所得到的值。
逻辑运算符最后的结果只有逻辑真或逻辑假两种,即 1 或 0。一般情况下用逻辑运算符作判断条
件时逻辑与操作只能是两个 1 位宽的数,只有两个表达式同时为真才为真,有一个为假则为假。
如果操作数是多位的,则可以将操作数看做整体,若操作数中每一位都是 0 值则为逻辑 0 值若操作数中有 1 则为逻辑 1 值。
由于 4’b0111 和 4’b1000 都不是 0,不为 0 则被认为是逻辑真,所以上面的代码等效于如下代码。
也就是结果为 a 为逻辑真,b 为逻辑真,c 为逻辑假。
5.4.4 经验总结
逻辑运算符的优先级
逻辑运算符中“&&”和“||”的优先级低于算数运算符;“!”的优先级高于双目逻辑运算符。
举例如下:
逻辑运算符两边对应的是1比特信号
使用心得:逻辑运算符两边对应的是 1 比特信号
注意上文代码,其中 a 和 b 都是多比特信号,表示两个多比特信号进行逻辑与。这句代码的正确理解是:当 a 不等于 0 并且 b 不等于 0 时,d 的值为 1。然而即使是有过多年工作经验的工程师也很难从直观上直接理解上文代码所隐含的意思。不等于 0 就是表示逻辑真,等于 0 就表示逻辑假的这一概念很容易被忽略。
因此上文代码,虽然从功能上没有错误,但设计师在设计中不应该以炫耀技术为目的进行代码编写,而且这种写法很容易出现误设计的情况,例如可能原本要表达 assign d =a & b,但最后由于设计不够直观而写成了上面的代码,导致设计出现问题。因此在设计中写出直观能理解,让自己与他者看到代码时可以立刻明白代码的意思非常重要,所以建议上面代码写成如下形式。
多用括号区分优先级
使用心得 2:不要试图记住优先级,而是要多用括号
实际上,工程师们在工作中并不会记住所有优先级排序,记住所有的优先级的这一工作也并不会大幅度的提升工程师的工作效率。在设计中可能会遇到如下所示代码:
假如没有记住运算符的优先级,遇到类似这三个例子的情况时势必会花一定的时间来理清思路,思考究竟先判断哪部分。假如工程师能够记住优先级,也需要就这些代码进行沟通和检查的工作,而且人是容易犯错的,而这些错误经常会被忽略,很难被检查出来。
因此,为了提高程序的可读性,明确表达各运算符间的优先关系,建议在设计时多使用括号。上面的三个例子就可分别写成:
少用逻辑非
使用心得 3:多用“逻辑与”和“逻辑或”,少用逻辑非
“逻辑与”翻译成中文就是“并且”,“逻辑或”翻译成中文就是“或者”。假设有一个信号 flag,0 表示空闲,1 表示忙。
“(!(flag0) &&a4’b1000)”,读起来就是“在空闲的时候取其反状态,并且当 a 等于 4’b1000 时条件成立”。这样读起来非常拗口,并且在阅读这一代码时还需要脑袋还要多转一下弯,多进行一层思考。为了让代码更加直观,建议上面的例子写成“flag1 && a4’b1000”,读起来就是“在忙并且 a 等于 4’b1000的时候”条件成立。
5.5 按位逻辑运算符
注:~ ^, ^ ~(二元异或非即同或):(相当于同或门运算。
在 Verilog HDL 语言中有下面几种按位运算符:
~(一元非):(相当于非门运算)
&(二元与):(相当于与门运算)
|(二元或):(相当于或门运算)
^(二元异或):(相当于异或门运算)
这些操作符在输入操作数的对应位上按位操作,并产生向量结果。下图各真值表种显示对于不同按位逻辑运算符按位操作的结果:
5.5.1 单目按位与
单目按位与运算符&,运算符后为需要进行逻辑运算的信号,表示对信号进行每位之间相与的操作。例如
Reg[3:0] A,C; assign C=&A;
上面代码等价于
C = A[3] & A[2] & A[1] & A[0];
如果 A=4’b0110,C 的结果为 0
5.5.2 单目按位或
单目按位或运算符|,运算符后为需要进行逻辑运算的信号,表示对信号进行每位之间相或的操作。例如
reg[3:0] A,C; assign C=|A;
上面代码等价于
C = A[3] | A[2] | A[1] | A[0];
如果 A=4’b0110,C 的结果为 1。
5.5.3 单目按位非
单目按位非运算符~,运算符后为需要进行逻辑运算的信号,表示对信号进行每位取反的操作。
例如
reg[3:0] A,C; assign C=~A;
上面代码等价于
C[3] = ~A[3],C[2] = ~A[2],C[1] = ~A[1],C[0] = ~A[0]。
如果 A=4’b0110,C 的结果为 4’b1001。
5.5.4 双目按位与
双目按位与运算符&,信号位于运算符的左右两边,表示的是对这两个信号进行对应位相与的操作。例如
reg[3:0] A,B,C; assign C = A & B;
上面的代码等价于:
C[0] = A[0] & B[0], C[1] = A[1] & B[1], C[2] = A[2] & B[2], C[3] = A[3] & B[3]。
如果
A=4’b0110, B=4’b1010,
C 的结果为 4’b0010。
如果操作数长度不相等, 长度较小的操作数在最左侧添 0 补位。例如,
reg[1:0] A; reg[2:0] B; reg[3:0] C; assign C = A & B;
上面的代码等价于:
C[0] = A[0] & B[0], C[1] = A[1] & B[1], C[2] = 0& B[2], C[3] = 0 & 0。
5.5.5 双目按位
双目按位或运算符|,信号位于运算符的左右两边,表示的是对这两个信号进行对应位相或的操作。例如
reg[3:0] A,B,C; assign C = A | B;
上面的代码等价于:
C[0] = A[0] | B[0], C[1] = A[1] | B[1], C[2] = A[2] | B[2], C[3] = A[3] | B[3]。
如果 A=4’b0110,B=4’b1010,C 的结果为 4’b1110。
如果操作数长度不相等, 长度较小的操作数在最左侧添 0 补位。例如,
reg[1:0] A; reg[2:0] B; reg[3:0] C; assign C = A | B;
上面的代码等价于:
C[0] = A[0] | B[0], C[1] = A[1] | B[1], C[2] = 0
5.5.6 双目按位异或
双目按位异或运算符^,信号位于运算符的左右两边,表示的是对这两个信号进行对应位相异或的操作。异或是指 0^0=0,1^1=0,0^1=1,即相同为 0,不同为 1。
例如
reg[3:0] A,B,C; assign C = A ^ B;
上面的代码等价于:
C[0] = A[0] ^ B[0], C[1] = A[1] ^ B[1], C[2] = A[2] ^ B[2], C[3] = A[3] ^ B[3]。
如果 A=4’b0110,B=4’b1010,C 的结果为 4’b1100。
如果操作数长度不相等, 长度较小的操作数在最左侧添 0 补位。例如,
reg[1:0] A; reg[2:0] B; reg[3:0] C; assign C = A | B;