HDLbits 2.3 学习笔记 · 算术电路

发布时间:2026/10/30 8:22:21
HDLbits 2.3 学习笔记 · 算术电路 半加器、全加器、行波进位加法器与 BCD 加法器Arithmetic Circuits1.半加器半加器是实现两个一位二进制数加法运算的器件。它具有两个输入端(被加数A和加数B)及输出端Y。是数据输入被加数A、加数B数据输出S和数(半加和)、进位C0。A和B是相加的两个数S是半加和数C是进位数。所谓半加就是不考虑进位的加法它的真值表如下(见表)被加数A加数B和数S进位数C0011010101100001表中半加器是实现两个一位二进制码相加的电路因此只能用于两个二进制码最低位的相加。因为高位二进制码相加时有可能出现低位的进位因此两个加数相加时还要计算低位的进位需要比半加器多进行一次相加运算。半加器不考虑低位向本位的进位因此它不属于时序逻辑电路有两个输入端和两个输出。设加数输入端为A、B和为S向高位的进位为Ci1逻辑表达式半加器有两个输入和两个输出输入可以标识为A、B输出通常标识为求和Sum和进位Carry。输入经异或XOR运算后即为S经和AND运算后即为C。半加器有两个二进制的输入其将输入的值相加并输出结果到和Sum和进位Carry。半加器虽能产生进位值但半加器本身并不能处理进位值。作业做一个半加器。半加法器加两位无进位输入产生求和进位输出。module top_module( input a, b, output cout, sum ); assign sumab; assign coutab; endmodule2.全加器真值表一位全加器的真值表如下图其中Ai为被加数Bi为加数相邻低位来的进位数为Ci-1输出本位和为Si。向相邻高位进位数为Ci一位全加器的真值表输入输出Ci-1AiBiSiCi0000000110010100110110010101011100111111描述一位全加器的表达式如下SiAi⊕Bi⊕Ci-1第二个表达式也可用一个异或门来代替或门对其中两个输入信号进行求和创建一个完整的加法器。满加法器会加三位包括进位进位并产生求和进位输出。module top_module( input a, b, cin, output cout, sum ); assign sum(a^b^cin); assign cout(ab)|(acin)|(bcin); 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把i2改成i3就可以了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(1b0), .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[70] 和 b[70]。这些数字被加起来得出s[70]。还要计算是否发生了带符号的溢出。这道题其实不难关键是理解补码加法的溢出判断。我们一步步来。①.补码加法怎么做两个8位补码相加硬件上就是直接做二进制加法然后只保留低8位作为结果s。最高位的进位第9位直接丢掉因为结果只有8位。在Verilog里就是assignsab;ab会得到一个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); assignsab; assignoverflow(a[7]b[7])(a[7]!s[7]); endmodule⑤.验证一个例子假设-a8h7F127正数符号位0-b8h011正数符号位0-ab128但8位补码最大正数是127所以结果变成8h80-128符号位1此时-a[7]b[7]成立都是0-a[7]!s[7]成立0!1-所以overflow1正确检测到溢出。完整代码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 ab; endmodule总结-加法assignsab;-溢出两个操作数符号相同但结果符号与它们不同。-表达式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[i1] (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 接总 cincout 接 c1• - 第 1 个cin 接 c1cout 接 c2• - 第 2 个cin 接 c2cout 接 c3• - 第 3 个cin 接 c3cout 接总 cout• 所以先声明• verilogwire c1, c2, c3;• ④ 写第一个实例你照着模仿• verilogbcd_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 位• ⑤ 剩下的三个你来模仿• 第二个• verilogbcd_fadd u1 ( .a(a[7:4]), .b(b[7:4]), .cin(c1), .cout(c2), .sum(sum[7:4]) );• 第三个• verilogbcd_fadd u2 ( .a(a[11:8]), .b(b[11:8]), .cin(c2), .cout(c3), .sum(sum[11:8]) );• 第四个• verilogbcd_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 块还简单。加油

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询