半加器、全加器、行波进位加法器与 BCD 加法器(Arithmetic Circuits)
1.半加器
半加器是实现两个一位二进制数加法运算的器件。它具有两个输入端(被加数A和加数B)及输出端Y。
是数据输入被加数A、加数B,数据输出S和数(半加和)、进位C0。
A和B是相加的两个数,S是半加和数,C是进位数。
所谓半加就是不考虑进位的加法,它的真值表如下(见表):
| 被加数A | 加数B | 和数S | 进位数C |
|---|---|---|---|
| 0 0 1 1 | 0 1 0 1 | 0 1 1 0 | 0 0 0 1 |
表中:
半加器是实现两个一位二进制码相加的电路,因此只能用于两个二进制码最低位的相加。
因为高位二进制码相加时,有可能出现低位的进位,因此两个加数相加时还要计算低位的进位,需要比半加器多进行一次相加运算。半加器不考虑低位向本位的进位,因此它不属于时序逻辑电路,有两个输入端和两个输出。
设加数(输入端)为A、B;和为S;向高位的进位为Ci+1
逻辑表达式:
半加器有两个输入和两个输出,输入可以标识为A、B,输出通常标识为求和(Sum)和进位(Carry)。输入经异或(XOR)运算后即为S,经和(AND)运算后即为C。
半加器有两个二进制的输入,其将输入的值相加,并输出结果到和(Sum)和进位(Carry)。半加器虽能产生进位值,但半加器本身并不能处理进位值。
作业:做一个半加器。半加法器加两位(无进位输入),产生求和进位输出。
module top_module( input a, b, output cout, sum ); assign sum=a+b; assign cout=a&b; endmodule2.全加器
真值表
一位全加器的真值表如下图,其中Ai为被加数,Bi为加数,相邻低位来的进位数为Ci-1,输出本位和为Si。向相邻高位进位数为Ci
| 一位全加器的真值表 | ||||
|---|---|---|---|---|
| 输入 | 输出 | |||
| Ci-1 | Ai | Bi | Si | Ci |
| 0 | 0 | 0 | 0 | 0 |
| 0 | 0 | 1 | 1 | 0 |
| 0 | 1 | 0 | 1 | 0 |
| 0 | 1 | 1 | 0 | 1 |
| 1 | 0 | 0 | 1 | 0 |
| 1 | 0 | 1 | 0 | 1 |
| 1 | 1 | 0 | 0 | 1 |
| 1 | 1 | 1 | 1 | 1 |
描述
一位全加器的表达式如下:
Si=Ai⊕Bi⊕Ci-1
第二个表达式也可用一个异或门来代替或门对其中两个输入信号进行求和:
创建一个完整的加法器。满加法器会加三位(包括进位进位),并产生求和进位输出。
module top_module( input a, b, cin, output cout, sum ); assign sum=(a^b^cin); assign cout=(a&b)|(a&cin)|(b&cin); endmodule3.三位二进制涟漪进位加法器
既然你已经知道如何构建一个完整的加法器,就做3个实例,创建一个3位二进制涟漪进位加法器。(你需要在提交的代码中写完整的加法器模块,可能还会重用你的解法来 fadd。)加法器会添加两个3位数字和一个进位输入,生成3位和输出。为了鼓励你实际实例化完整加法器,也可以在波纹进位加法器中输出每个满加器的进位输出。Cout[2] 是上一个满加法器的最后一个进位,也是你通常看到的进位。
module fadd( input a, b, cin, output sum, cout ); assign sum = a ^ b ^ cin; assign cout = (a & b) | (a & cin) | (b & cin); //进位的逻辑 endmodule module top_module( input [2:0] a, b, input cin, output [2:0] cout, output [2:0] sum ); genvar i; generate for (i = 0; i < 2; i = i + 1) begin : adder_loop wire current_cin = (i == 0) ? cin : cout[i-1]; //波纹进位的特点 fadd u_fadd ( .a(a[i]), .b(b[i]), .cin(current_cin), .sum(sum[i]), .cout(cout[i]) ); end endgenerate endmodule把i<2改成i<3就可以了
4.Adder
学会使用实例化
module fadd( input a, b, cin, output sum, cout ); assign sum = a ^ b ^ cin; assign cout = (a & b) | (a & cin) | (b & cin); endmodule module top_module ( input [3:0] x, input [3:0] y, output [4:0] sum ); wire c1, c2, c3; // 中间进位信号 // 第 0 位:最低位,进位输入为 0 fadd u0 (.a(x[0]), .b(y[0]), .cin(1'b0), .sum(sum[0]), .cout(c1)); // 第 1 位 fadd u1 (.a(x[1]), .b(y[1]), .cin(c1), .sum(sum[1]), .cout(c2)); // 第 2 位 fadd u2 (.a(x[2]), .b(y[2]), .cin(c2), .sum(sum[2]), .cout(c3)); // 第 3 位:最高位,进位输出直接作为和的第 4 位 fadd u3 (.a(x[3]), .b(y[3]), .cin(c3), .sum(sum[3]), .cout(sum[4])); endmodule5.带符号加法溢出
假设你有两个8位2的补数,a[7:0] 和 b[7:0]。这些数字被加起来得出s[7:0]。还要计算是否发生了(带符号的)溢出。
这道题其实不难,关键是理解补码加法的溢出判断。我们一步步来。
①.补码加法怎么做?
两个8位补码相加,硬件上就是直接做二进制加法,然后只保留低8位作为结果s。
最高位的进位(第9位)直接丢掉,因为结果只有8位。
在Verilog里就是:
assigns=a+b;a+b会得到一个9位的结果,但赋给8位的s时,高位会被截断,只留低8位。这正是补码加法的行为。
②.什么是有符号溢出?
有符号溢出不是看最高位有没有进位,而是看结果的符号是否合理。
对于补码:
-正数的最高位(符号位)是0
-负数的最高位是1
溢出只会发生在:
-正数+正数=负数(两个正数相加,结果却变成负数)
-负数+负数=正数(两个负数相加,结果却变成正数)
正数+负数、或负数+正数,永远不会溢出。
③.怎么用逻辑判断?
设a[7]、b[7]是两个操作数的符号位,s[7]是结果的符号位。
溢出条件:
-a[7]==b[7](两个操作数符号相同)
-并且s[7]!=a[7](结果符号与操作数符号不同)
写成逻辑表达式:
overflow=(a[7]==b[7])&&(a[7]!=s[7]);也可以展开成与或形式:
overflow=(a[7]&b[7]&~s[7])|(~a[7]&~b[7]&s[7]);意思是:
-两个都是正数(a[7]=0,b[7]=0),结果却是负数(s[7]=1)
-或者两个都是负数(a[7]=1,b[7]=1),结果却是正数(s[7]=0)
④.完整代码
moduletop_module( input[7:0]a, input[7:0]b, output[7:0]s,outputoverflow
); assigns=a+b; assignoverflow=(a[7]==b[7])&&(a[7]!=s[7]); endmodule⑤.验证一个例子
假设:
-a=8'h7F(127,正数,符号位0)
-b=8'h01(1,正数,符号位0)
-a+b=128,但8位补码最大正数是127,所以结果变成8'h80(-128,符号位1)
此时:
-a[7]==b[7]成立(都是0)
-a[7]!=s[7]成立(0!=1)
-所以overflow=1,正确检测到溢出。
完整代码
module top_module ( input [7:0] a, input [7:0] b, output [7:0] s, output overflow ); assign overflow=(a[7]==b[7])&&(a[7]!=s[7]); assign s =a+b; endmodule总结
-加法:assigns=a+b;
-溢出:两个操作数符号相同,但结果符号与它们不同。
-表达式:assignoverflow=(a[7]==b[7])&&(a[7]!=s[7]);
6.100位二进制加法器
多位加法运算器中用拼接是最简单的
module top_module( input [99:0] a, b, input cin, output cout, output [99:0] sum ); assign {cout, sum} = a + b + cin; endmodule解释:
a + b + cin 会得到一个 101 位的结果:低 100 位是 sum,最高位是 cout。
{cout, sum} 是位拼接,把 1 位的 cout 和 100 位的 sum 拼成 101 位,正好接收加法结果。
这样既考虑了进位输入,也正确产生了进位输出,而且代码极简。
用全加器级联(结构化描述)
也可以用 generate 实例化 100 个全加器,但没必要,因为 + 运算符已经能综合出正确的加法器。如果题目强制要求用门级或全加器,可以这样:
module top_module( input [99:0] a, b, input cin, output cout, output [99:0] sum ); wire [100:0] c; // 进位链 assign c[0] = cin; genvar i; generate for (i = 0; i < 100; i = i + 1) begin : adder_loop assign sum[i] = a[i] ^ b[i] ^ c[i]; assign c[i+1] = (a[i] & b[i]) | (a[i] & c[i]) | (b[i] & c[i]); end endgenerate assign cout = c[100]; endmodule用门级,必须用进位链,且 for 要放在 generate 或 always 中,并正确声明变量和语法。
7.实例化-四位BCD加法器
作业:创建一个100位二进制加法器。加法器会添加两个100位数字和一个进位输入,生成100位的求和和。
• 实例化其实就是“把别人写好的模块拿过来用”,就像在代码里调用一个函数。
你会得到一个名为bcd_fadd的BCD(二进制编码十进制)一位加法器,它会加两个BCD数字和进位输入,并产生求和和输出。
• 解题步骤
• ① 先看被调用的模块
• 题目已经给了 bcd_fadd 模块:
module bcd_fadd ( input [3:0] a, input [3:0] b, input cin, output cout, output [3:0] sum );• 它有 5 个端口:a、b、cin、cout、sum。
• 我们要在 top_module 里用 4 个这样的模块,拼成一个 4 位 BCD 加法器。
• ② 实例化的基本语法
• verilog
• 模块名 实例名 (
• .端口名(你要连接的信号),
• .端口名(你要连接的信号),
• ...
);• - 模块名:就是 bcd_fadd
• - 实例名:你自己取的名字,比如 u0、u1、u2、u3,不能重复
• - .端口名(信号):把模块内部的端口,连到当前模块里的信号上
• ③ 分析 4 位 BCD 加法器怎么连
• 总输入:
• - a[15:0]:4 个 BCD 数字,从低到高是 a[3:0]、a[7:4]、a[11:8]、a[15:12]
• - b[15:0]:同理
• - cin:最低位的进位输入
• 总输出:
• - sum[15:0]:4 个 BCD 和,对应分组
• - cout:最高位的进位输出
• 中间需要 3 根进位线,把 4 个 bcd_fadd 串起来:
• - 第 0 个:cin 接总 cin,cout 接 c1
• - 第 1 个:cin 接 c1,cout 接 c2
• - 第 2 个:cin 接 c2,cout 接 c3
• - 第 3 个:cin 接 c3,cout 接总 cout
• 所以先声明:
• verilog
wire c1, c2, c3;• ④ 写第一个实例,你照着模仿
• verilog
bcd_fadd u0 ( .a(a[3:0]), .b(b[3:0]), .cin(cin), .cout(c1), .sum(sum[3:0]) );• 解释:
• - u0 是实例名,随便取,但要有意义
• - .a(a[3:0]):把模块的 a 端口连到 top_module 的 a[3:0]
• - .b(b[3:0]):同理
• - .cin(cin):最低位的进位输入来自总 cin
• - .cout(c1):这个模块产生的进位输出给 c1
• - .sum(sum[3:0]):这个模块的和输出给总 sum 的低 4 位
• ⑤ 剩下的三个,你来模仿
• 第二个:
• verilog
bcd_fadd u1 ( .a(a[7:4]), .b(b[7:4]), .cin(c1), .cout(c2), .sum(sum[7:4]) );• 第三个:
• verilog
bcd_fadd u2 ( .a(a[11:8]), .b(b[11:8]), .cin(c2), .cout(c3), .sum(sum[11:8]) );• 第四个:
• verilog
bcd_fadd u3 ( .a(a[15:12]), .b(b[15:12]), .cin(c3), .cout(cout), .sum(sum[15:12]) );• ⑥ 完整代码
实例化4个bcd_fadd副本,创建一个4位BCD的连带进位加法器。你的加法器应该添加两个4位BCD数字(打包在16位向量中)和一个进位输入,生成一个4位数的求和和。
module top_module ( input [15:0] a, b, input cin, output cout, output [15:0] sum ); wire c1,c2,c3; bcd_fadd u0( .a(a[3:0]), .b(b[3:0]), .cin(cin), .cout(c1), .sum(sum[3:0]) ); bcd_fadd u1( .a(a[7:4]), .b(b[7:4]), .cin(c1), .cout(c2), .sum(sum[7:4]) ); bcd_fadd u2( .a(a[11:8]), .b(b[11:8]), .cin(c2), .cout(c3), .sum(sum[11:8]) ); bcd_fadd u3( .a(a[15:12]), .b(b[15:12]), .cin(c3), .cout(cout), .sum(sum[15:12]) ); endmodule• ⑦. 关键点总结
| 要点 | 说明 |
|---|---|
| 实例名 | 每个实例必须唯一,如 u0、u1、u2、u3 |
| 端口连接 | 用 .端口名(信号) 格式,清晰不易错 |
| 位切片 | a[3:0]、a[7:4] 等,把 16 位分成 4 组 |
| 进位链 | c1、c2、c3 把 4 个模块串起来 |
| 最低位 cin | 接总 cin |
| 最高位 cout | 接总 cout |
• ⑧. 怎么练习实例化?
• 1. 先抄一遍上面的代码,理解每一行。
• 2. 试着改一改:比如只实例化两个,把进位连起来。
• 3. 再试着用位置连接(不写 .端口名),但容易搞错顺序,不推荐初学用。
• 4. 自己写一个简单的模块(比如与门),然后在另一个模块里实例化它。
• 实例化就像搭积木,把现成的模块拼起来。多写几次,你就会发现它比写 always 块还简单。加油!