从逻辑门到加法器:Verilog实现半加器与全加器的三种抽象层级

news2026/5/18 5:01:51
1. 项目概述从逻辑门到加法器的数字世界基石在数字电路和芯片设计的入门路上加法器是一个绕不开的经典课题。它不仅是算术逻辑单元ALU的核心组件更是理解数字系统如何执行基本运算的关键。今天我们不谈复杂的处理器架构就从最基础的1位半加器和1位全加器的Verilog实现开始手把手带你从逻辑门推导出电路再用硬件描述语言将其“描述”出来。无论你是正在学习《数字逻辑》的学生还是初涉FPGA开发的工程师掌握这个从理论到代码的完整流程都是夯实基础、培养硬件思维至关重要的一步。本文将深入解析两者的设计差异、Verilog编码的多种风格门级、数据流、行为级并通过仿真测试验证功能最后分享一些从实践中总结的代码风格与调试心得。2. 核心原理与设计思路拆解2.1 半加器与全加器的本质区别在开始写代码之前我们必须彻底理解这两个电路的功能和由来。半加器顾名思义是一个“不完整”的加法器。它的输入只有两个被加数A和加数B。输出有两个和Sum与进位Cout。它的“不完整”体现在哪里它没有考虑来自低位的进位输入。这意味着半加器只能完成两个单独二进制位的相加无法处理多位二进制数相加时产生的进位链。其真值表如下ABSumCout0000011010101101观察真值表我们可以直接写出Sum和Cout的逻辑表达式Sum A ⊕ BA与B的异或Cout A BA与B的与所以一个半加器可以用一个异或门和一个与门直接实现。全加器则补上了半加器的短板。它有三个输入被加数A、加数B以及来自低位的进位输入Cin。输出同样为和Sum与进位Cout。这使得全加器能够串联起来构成任意位宽的并行加法器如行波进位加法器。其真值表如下ABCinSumCout0000000110010100110110010101011100111111通过卡诺图化简或观察可以得到全加器的逻辑表达式Sum A ⊕ B ⊕ Cin三个输入信号的异或Cout (A B) | (B Cin) | (A Cin)任意两个输入同时为1则产生进位从电路实现上看一个全加器可以由两个半加器和一个或门构成第一个半加器计算A和B的和与进位其和再与Cin输入第二个半加器最终的进位由两个半加器的进位输出相或得到。注意理解这个“两个半加器构成一个全加器”的过程对于建立模块化设计思维非常重要。在Verilog中我们可以先实现半加器模块然后在全加器模块中实例化调用它这正是层次化设计思想的体现。2.2 Verilog描述的三种抽象层级Verilog允许我们在不同的抽象层次上描述同一个硬件电路这为我们提供了灵活的设计方法。针对加法器我们可以从三个层面来实现门级描述直接对应逻辑图使用and,or,xor等内置门级原语进行连接。这种方式最贴近底层电路结构但描述复杂电路时显得冗长。数据流描述使用assign连续赋值语句直接描述输入和输出之间的逻辑函数关系。代码简洁直观是描述组合逻辑的常用方式。行为级描述使用always过程块和case或if语句从算法行为的角度描述电路功能。抽象层次最高设计效率也最高但需要特别注意综合后生成的电路是否与预期一致。在接下来的实现中我们将分别用这三种风格来编写代码你可以对比体会其中的异同。3. Verilog实现详解与代码实操3.1 1位半加器的三种实现方式我们将创建一个名为half_adder的模块。方式一门级描述这种方式直接实例化Verilog内置的基本门单元。module half_adder_gate ( input wire A, input wire B, output wire Sum, output wire Cout ); // 使用内置门原语xor异或门 and与门 xor u_xor (Sum, A, B); and u_and (Cout, A, B); endmodule实操心得门级描述中门的实例名如u_xor可以自定义但输出端口必须写在端口列表的第一个位置这是内置原语的语法规定。这种写法在小型明确电路中很清晰但不易维护。方式二数据流描述使用assign语句直接赋值逻辑表达式。module half_adder_dataflow ( input wire A, input wire B, output wire Sum, output wire Cout ); // 连续赋值语句描述信号间的逻辑关系 assign Sum A ^ B; // ^ 是Verilog中的按位异或运算符 assign Cout A B; // 是Verilog中的按位与运算符 endmodule实操心得数据流描述是最推荐用于组合逻辑的方式之一。它简洁、易读且能清晰地表达设计者的意图。综合工具能高效地将其映射为对应的门级电路。方式三行为级描述使用always块和敏感列表。module half_adder_behavioral ( input wire A, input wire B, output reg Sum, // 在always块中被赋值的输出需要定义为reg类型 output reg Cout ); // always块当A或B中任意一个变化时块内的语句被执行 always (*) begin // (*) 是组合逻辑敏感列表的简洁写法代表所有输入信号 Sum A ^ B; Cout A B; end endmodule注意事项在行为级描述中由于Sum和Cout是在always过程块中被赋值的它们必须被声明为reg类型。但这不意味着它们会被综合成触发器reg类型在这里仅代表一种数据存储的抽象最终综合出的仍是组合逻辑电路因为always (*)描述的是电平敏感逻辑。这是Verilog初学者最容易混淆的概念之一。3.2 1位全加器的三种实现方式我们将创建一个名为full_adder的模块。方式一门级描述根据逻辑表达式直接连接门电路。module full_adder_gate ( input wire A, input wire B, input wire Cin, output wire Sum, output wire Cout ); wire w1, w2, w3; // 定义内部连线 // Sum A xor B xor Cin xor u_xor1 (w1, A, B); xor u_xor2 (Sum, w1, Cin); // Cout (AB) | (BCin) | (ACin) and u_and1 (w2, A, B); and u_and2 (w3, B, Cin); and u_and3 (w4, A, Cin); or u_or1 (Cout, w2, w3, w4); endmodule方式二数据流描述module full_adder_dataflow ( input wire A, input wire B, input wire Cin, output wire Sum, output wire Cout ); assign Sum A ^ B ^ Cin; assign Cout (A B) | (B Cin) | (A Cin); endmodule代码非常直观几乎就是逻辑表达式的直接翻译。方式三行为级描述module full_adder_behavioral ( input wire A, input wire B, input wire Cin, output reg Sum, output reg Cout ); always (*) begin // 可以直接赋值逻辑表达式 // Sum A ^ B ^ Cin; // Cout (A B) | (B Cin) | (A Cin); // 或者使用更行为化的case语句基于真值表 case ({A, B, Cin}) // 使用位拼接运算符{}将三个输入变成一个3位向量 3b000: {Cout, Sum} 2b00; 3b001: {Cout, Sum} 2b01; 3b010: {Cout, Sum} 2b01; 3b011: {Cout, Sum} 2b10; 3b100: {Cout, Sum} 2b01; 3b101: {Cout, Sum} 2b10; 3b110: {Cout, Sum} 2b10; 3b111: {Cout, Sum} 2b11; default: {Cout, Sum} 2b00; // 良好实践添加default分支处理未定义状态 endcase end endmodule注意事项在case语句中我们使用了位拼接{A, B, Cin}和{Cout, Sum}这可以一次性处理多个信号使代码更紧凑。务必添加default分支这是一个重要的代码健壮性习惯可以避免在综合时生成不必要的锁存器并确保仿真时未覆盖状态有确定行为。方式四结构化描述使用半加器模块这是一种体现层次化设计的方法我们先假设已经有一个数据流描述的half_adder模块。module full_adder_structural ( input wire A, input wire B, input wire Cin, output wire Sum, output wire Cout ); wire s1, c1, c2; // 内部连线s1和c1是第一个半加器的输出c2是第二个半加器的进位输出 // 实例化第一个半加器计算 AB half_adder_dataflow HA1 ( .A(A), .B(B), .Sum(s1), // 连接到内部线s1 .Cout(c1) // 连接到内部线c1 ); // 实例化第二个半加器计算 s1Cin half_adder_dataflow HA2 ( .A(s1), .B(Cin), .Sum(Sum), // 直接连接到全加器的和输出 .Cout(c2) ); // 最终的进位输出是两个半加器进位输出的或 assign Cout c1 | c2; endmodule实操心得结构化描述是大型项目的基础。它通过模块实例化将复杂系统分解为简单模块极大地提高了代码的可读性、可维护性和复用性。在实例化时通过端口名如.A(A)显式地连接信号比依赖顺序的位置关联更安全、更清晰尤其在端口较多时能有效避免连接错误。4. 测试验证与仿真分析设计完成后的验证环节至关重要。我们将编写一个简单的测试平台Testbench来验证全加器的功能。4.1 编写Testbenchtimescale 1ns / 1ps // 定义仿真时间单位/精度 module tb_full_adder(); // 声明与DUT被测设备对应的信号 reg a_tb, b_tb, cin_tb; wire sum_tb, cout_tb; // 实例化待测试的全加器模块以数据流描述为例 full_adder_dataflow uut ( .A(a_tb), .B(b_tb), .Cin(cin_tb), .Sum(sum_tb), .Cout(cout_tb) ); // 生成测试激励 initial begin // 初始化输入信号 a_tb 0; b_tb 0; cin_tb 0; #10; // 等待10个时间单位 // 遍历所有8种输入组合 a_tb 0; b_tb 0; cin_tb 0; #10; a_tb 0; b_tb 0; cin_tb 1; #10; a_tb 0; b_tb 1; cin_tb 0; #10; a_tb 0; b_tb 1; cin_tb 1; #10; a_tb 1; b_tb 0; cin_tb 0; #10; a_tb 1; b_tb 0; cin_tb 1; #10; a_tb 1; b_tb 1; cin_tb 0; #10; a_tb 1; b_tb 1; cin_tb 1; #10; // 测试结束 $display(Simulation finished.); $finish; end // 可选在每次信号变化时打印结果便于观察 always (a_tb or b_tb or cin_tb) begin #1; // 等待一个微小延迟让输出稳定 $display(Time%t: A%b, B%b, Cin%b - Sum%b, Cout%b, $time, a_tb, b_tb, cin_tb, sum_tb, cout_tb); end endmodule4.2 仿真结果解读使用Modelsim、Vivado Simulator或Icarus Verilog等工具运行上述测试平台你会看到在控制台或波形图中对于每一种输入组合{A, B, Cin}输出{Cout, Sum}都严格符合全加器真值表。例如当输入为1, 1, 1时输出应为1, 1即Cout1, Sum1因为1113二进制11。排查技巧如果仿真结果与预期不符请按以下步骤排查检查端口连接确认Testbench中实例化模块的端口信号连接是否正确特别是信号名是否拼写错误。检查变量类型在行为级描述中确保在always块内赋值的输出被声明为reg类型。检查敏感列表对于组合逻辑的always块使用always (*)确保所有输入信号的变化都能触发逻辑更新。检查逻辑表达式仔细核对代码中的逻辑运算符^,,|是否正确括号使用是否得当。查看综合报告使用综合工具如Vivado、Quartus进行综合查看其生成的RTL原理图这能最直观地反映你的代码被翻译成了什么电路。5. 进阶思考与工程实践要点5.1 如何构建多位加法器掌握了1位全加器构建一个N位的二进制加法器就水到渠成了。最直接的方法是使用行波进位加法器即将N个1位全加器串联低位全加器的Cout连接到相邻高位的Cin。module ripple_carry_adder #(parameter WIDTH 8) ( input wire [WIDTH-1:0] A, input wire [WIDTH-1:0] B, output wire [WIDTH-1:0] Sum, output wire Cout ); wire [WIDTH:0] carry; // 内部进位链比位宽多一位 assign carry[0] 1b0; // 最低位的进位输入通常为0 genvar i; generate for (i0; iWIDTH; ii1) begin: adder_chain full_adder_dataflow u_full_adder ( .A(A[i]), .B(B[i]), .Cin(carry[i]), .Sum(Sum[i]), .Cout(carry[i1]) ); end endgenerate assign Cout carry[WIDTH]; // 最高位的进位输出 endmodule注意事项行波进位加法器结构简单但进位信号需要从最低位逐级传递到最高位这导致了较长的关键路径延迟限制了加法器的速度。在实际高性能设计中会采用超前进位加法器等更快的结构。5.2 组合逻辑中的竞争与冒险我们实现的全加器和半加器都是纯组合逻辑电路。组合逻辑存在一个潜在问题竞争冒险。当输入信号变化不同步时由于门电路的延迟可能在输出端产生短暂的毛刺非预期的脉冲。例如在全加器中当{A,B,Cin}从011变为100时各条路径延迟不同Sum输出可能在稳定到1之前出现一个短暂的0毛刺。应对策略增加输出滤波电容在低速板级电路中可行但在ASIC或FPGA中不实用。采用同步设计这是最根本、最推荐的方法。在时钟驱动的系统中使用寄存器触发器在时钟边沿采样稳定的组合逻辑输出。这样只要毛刺在时钟边沿到来之前稳定下来就不会影响系统功能。这也是为什么在实际的数字系统如CPU中加法运算通常是在一个时钟周期内完成的。// 一个简单的带寄存输出的8位加法器示例 module registered_adder ( input wire clk, input wire [7:0] A, input wire [7:0] B, output reg [7:0] Sum, output reg Cout ); wire [7:0] sum_comb; wire cout_comb; ripple_carry_adder #(.WIDTH(8)) u_adder ( .A(A), .B(B), .Sum(sum_comb), .Cout(cout_comb) ); always (posedge clk) begin Sum sum_comb; // 在时钟上升沿锁存结果 Cout cout_comb; end endmodule5.3 代码风格与可综合指南命名规范模块名、信号名使用有意义的英文单词或缩写如adder,counter。对于低有效信号可以加_n后缀如rst_n。我个人的习惯是Testbench中的激励信号加_tb后缀以示区分。注释清晰对模块功能、端口含义、关键代码段、复杂逻辑进行必要注释。好的注释是给未来的自己或同事最好的礼物。可综合代码确保你的always块能够被综合工具正确理解。描述组合逻辑时使用always (*)并在块内对所有条件分支完整赋值避免生成不想要的锁存器。描述时序逻辑时使用always (posedge clk or posedge rst)等并统一使用非阻塞赋值。参数化设计如上例中的#(parameter WIDTH 8)使用参数使得模块位宽可配置极大增强了代码的复用性。从最基础的逻辑门到可用的加法器模块这个过程清晰地展示了数字电路自底向上的设计方法。理解并亲手实现它是打开硬件设计大门的第一把钥匙。在实际项目中你可能会直接调用EDA工具提供的优化过的算术运算符如但隐藏在运算符背后的这些基本原理永远是分析和解决复杂问题的基石。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2619531.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…