Verilog ALU设计:33位CF寄存器与标志位处理
发布时间:2026/9/19 17:07:55 锦皓数字建站

简介这是一份杭州电子科技大学计算机组成原理课程的多功能ALU设计实验报告PDF面向正在学习计算机组成原理、Verilog HDL或FPGA数字电路设计的高校学生与开发者。报告完整覆盖实验目的、Xilinx ISE Design Suite 13.4实验环境、主要步骤与结论重点给出了ALU_TOP、输入数据选择、输出数据选择及顶层模块的Verilog代码实现与、或、异或、加法、减法、比较、左移等运算并处理零标志与溢出标志可帮助读者快速理解多功能ALU工作机制和模块化设计思路。资源为单个PDF文件体积约239KB便于直接下载阅读已有402人浏览学习适合作为课程实验报告撰写、FPGA上板验证和Verilog编程练习的参考资料。1. 一个 33 位寄存器把这个 ALU 的加减法和标志位全部包圆杭电计算机组成原理这门课的 ALU 实验核心模块只要二十多行 Verilog但里面藏了一个值得反复琢磨的设计reg [32:0] CF。它用 33 位宽度同时装下 32 位运算结果和进位后续FCF[31:0]、CCF[32]一拆零标志 ZF、溢出标志 OF 在这个 always 块里一次算完。比网上常见的{C,AB}拼接写法更干净也更好排错。这篇博文把这份多功能 ALU 实验从运算核心、数据选择、顶层集成到仿真验证拆开讲代码和参数都给了适合正在做计算机组成原理实验、准备期末或者想用 Verilog 行为建模练手的读者。2. ALU_TOP 运算核心8 个 case 分支与 CF 位扩展的取舍2.1 端口约定与 33 位 CF 的拆分逻辑直接看 ALU_TOP 的完整代码module ALU_TOP(A, B, F, ZF, OF, ALU_OP); input [31:0] A, B; input [2:0] ALU_OP; output [31:0] F; output ZF, OF; reg [31:0] F; reg ZF, OF; reg [5:0] i; reg C; reg [32:0] CF; always (ALU_OP or A or B) begin case (ALU_OP) 3b000: CF A B; // 与 3b001: CF A | B; // 或 3b010: CF A ^ B; // 异或 3b011: CF A ~^ B; // 同或 3b100: CF A B; // 加 3b101: CF A - B; // 减 3b110: CF (A B); // 无符号比较 3b111: CF B A; // 左移 endcase F[31:0] CF[31:0]; // 低 32 位送给结果 F C CF[32]; // 第 33 位作为进位/借位标志 ZF 0; for (i 0; i 32; i i 1) ZF ZF | F[i]; // 逐位或全 0 时 ZF 才为 1 ZF ~ZF; OF A[31] ^ B[31] ^ F[31] ^ C; end endmodule这段代码的入口是三个输入两个 32 位操作数 A、B一个 3 位控制信号 ALU_OP。输出里 F 是运算结果ZF 为零标志OF 为溢出标志。always (ALU_OP or A or B)是组合逻辑的显式敏感列表写法电平触发任何输入变化都会重新计算一遍所有输出。这里没有用(*)是因为这份报告基于 Xilinx ISE 13.4 的老模板习惯后面复刻时可以直接写成always (*)综合结果没有区别。CF 位扩展是这段代码的核心。每个分支都把结果写进 33 位寄存器 CF而不是直接写 F。这样做的直接好处是加减法产生进位或借位时第 33 位被自然保留下来不需要额外为加法和减法单独写 carry 逻辑。逻辑运算、比较、移位的 32 位结果在高位补零不会干扰 C。最后用F[31:0] CF[31:0]和C CF[32]一次性拆开思路和用 64 位变量接 32 位乘法中间量的做法类似属于扩位承接中间量的常规手段。一个易漏点减法A - B在 33 位下是补码回绕。以A0, B1为例33 位结果是33h1_FFFF_FFFFC 取到 1表示发生了借位F 取低 32 位32hFFFF_FFFF也就是 -1 的补码。这个行为与A (~B) 1等价所以后面 OF 公式对加法和减法都适用不需要为减法单开分支。提示显式敏感列表容易漏输入尤其是后期给模块加端口时。新工程里建议统一用always (*)让工具自动推断敏感信号。2.2 8 种运算的位宽与标志位行为对照把 8 个分支整理成表仿真和查错时直接对照ALU_OP运算CF 的 33 位含义F低 32 位C第 33 位3b000按位与高位置 0A B03b001按位或高位置 0A | B03b010按位异或高位置 0A ^ B03b011按位同或高位置 0A ~^ B03b100无符号加进位参与A B进位3b101无符号减借位参与A - B借位1 表示有借位3b110无符号比较高位置 0A B 为真时 F103b111左移高位置 0B A0表里值得注意是 3b011 同或。A ~^ B按位同或两个操作数高位如果相同那些位结果全是 1所以拿32h3和32h607做同或得到的是32hFFFF_F9FB而不是某个小整数。很多同学第一次仿真看到一串 F 以为算错了其实用程序员计算器的 XNOR 按一下就能确认。3b110 比较分支只产生 0 或 1C 固定 03b111 左移里 A 是 32 位移位计数综合出的移位器只关心低 5 位这个问题留到最后一章单独讲。2.3 标志位生成阻塞赋值顺序决定 ZF 和 OF 读到的最新值ZF 的计算是一个循环先把 ZF 置 0遍历 F 的 32 个位做按位或只要有一个 1ZF 就变成 1最后ZF ~ZF取反。所以 ZF 的语义是结果 F 全为 0 时置 1等价于ZF ~(|F)。循环写法更容易被初学者理解综合器会把它优化成一个 32 输入或非门。OF 的计算更有意思OF A[31] ^ B[31] ^ F[31] ^ C。它把两个操作数符号位、结果符号位、最高位进位异或在一起覆盖了正正相加、负负相加、正负相加三种情况的溢出判定具体推导放在最后一章。这里只强调仿真层面的细节代码用的是阻塞赋值OF 赋值排在 F 和 C 之后所以 OF 一定读到本次运算更新后的 F[31] 和 C。如果改成非阻塞赋值F 和 C 在 always 块结束时才更新OF 读到的就是旧值溢出标志会整体错乱。这是行为建模里最容易踩的时序坑。还有一个边界必须在验证前确认A B是无符号比较因为 A、B 声明成input [31:0]没有加signed。AB_SW3b101 那组A32h8000_0000, B32hFFFF_FFFF无符号视角下 A 是 2147483648B 是 4294967295比较结果是 0按带符号看是 -2147483648 -1结果是 1。写验证表之前先想清楚自己在验证哪种语义。3. 输入输出选择模块8 组预设数据与 LED 分时显示3.1 Choice 模块用 AB_SW 在 8 组 A/B 数据间切换module Choice(AB_SW, A, B); input [2:0] AB_SW; output [31:0] A, B; reg [31:0] A, B; wire [2:0] AB_SW; // 8 组预设数据覆盖全零、溢出、全 1、正负混合等典型场景 always (*) begin case (AB_SW) 3b000: begin A 32h0000_0000; B 32h0000_0000; end // 全 0验证 ZF 3b001: begin A 32h0000_0003; B 32h0000_0607; end // 普通正整数 3b010: begin A 32h8000_0000; B 32h8000_0000; end // 负负相加溢出 3b011: begin A 32h7FFF_FFFF; B 32h7FFF_FFFF; end // 正正相加溢出 3b100: begin A 32hFFFF_FFFF; B 32hFFFF_FFFF; end // 全 1 数据 3b101: begin A 32h8000_0000; B 32hFFFF_FFFF; end // 符号相反 3b110: begin A 32hFFFF_FFFF; B 32h8000_0000; end // 符号相反 3b111: begin A 32h1234_5678; B 32h3333_2222; end // 左移验证 default: begin A 32h9ABC_DEF0; B 32h1111_2222; end endcase end endmoduleChoice 的逻辑本质是一个 3 位选择控制的 32 位数据多路器。8 组数据是实验指导书预定的覆盖了几类典型情况AB_SW0 全零验证 ZF 是否在结果为零时置位AB_SW2 是两个符号位为 1 的大数相加专门制造溢出AB_SW3 是两个最大正数相加溢出方向相反AB_SW4 全 1考察与、或、异或在全 1 数据下的行为AB_SW5 和 6 是一正一负组合验证补码运算和比较分支。这些数据没有随机性每个人跑出来的波形完全一致报告贴图、验算都有对证。数据组映射记录如下方便后续对照AB_SWAB设计意图00032h0000_000032h0000_0000全 0验证 ZF00132h0000_000332h0000_0607日常正整数运算01032h8000_000032h8000_0000负负相加溢出01132h7FFF_FFFF32h7FFF_FFFF正正相加溢出10032hFFFF_FFFF32hFFFF_FFFF全 1 数据10132h8000_000032hFFFF_FFFF符号相反无符号比较为假11032hFFFF_FFFF32h8000_0000符号相反加法为负11132h1234_567832h3333_2222普通数据验证左移wire [2:0] AB_SW这行是冗余声明input 端口默认就是 wire 类型这是旧版 Verilog 端口列表风格的常见写法。真正要注意的是 A、B 声明成reg因为它们在 always 块里被赋值。想用 ANSI 风格合并成output reg [31:0] A, B也可以ISE 13.4 对两种写法都支持新工具里后者可读性更好。3.2 CLED 模块8 位 LED 上分时查看 32 位结果module CLED(F_LED_SW, LED, F, ZF, OF); input [31:0] F; input ZF, OF; input [2:0] F_LED_SW; output [7:0] LED; reg [7:0] LED; wire [31:0] F; wire [2:0] F_LED_SW; wire ZF, OF; always (*) begin case (F_LED_SW) 3b000: LED F[7:0]; // 显示低 8 位 3b001: LED F[15:8]; 3b010: LED F[23:16]; 3b011: LED F[31:24]; // 显示高 8 位 default: begin LED[7] ZF; // 最高位亮表示零标志 LED[0] OF; // 最低位亮表示溢出标志 LED[6:1] 6b0; end endcase end endmodule开发板上的 LED 只有 8 个F 是 32 位所以用 F_LED_SW 做分时切片。F_LED_SW 取 000 到 011 时分别显示 F 的四个字节取 100 到 111 时走 default 分支把 ZF 和 OF 拼到 LED 上。LED[7] 放 ZFLED[0] 放 OF中间 6 位拉低一眼能看出标志位状态。验证加法溢出时把 F_LED_SW 拨到 100LED 最高位亮表示 ZF1最低位亮表示 OF1其余全灭比来回切四个字节看数据更直观。F_LED_SW 映射关系如下F_LED_SW输出内容3b000F[7:0]3b001F[15:8]3b010F[23:16]3b011F[31:24]3b100 ~ 3b111LED[7]ZFLED[0]OF其余为 0这个设计在 FPGA 实验里很常见外设资源有限时用拨码开关分时复用数据通道。CLED 只做组合输出没有时钟LED 变化和 F 同步省掉锁存器资源占用低。如果改成动态扫描显示十六进制数才需要引入时钟和计数器那是另一层设计。4. 顶层模块与仿真验证三个子模块实例化与数据通路检查4.1 顶层连接wire 显式声明与端口顺序对应module ALU(AB_SW, ALU_OP, F_LED_SW, LED); input [2:0] AB_SW, ALU_OP, F_LED_SW; output [7:0] LED; wire [2:0] AB_SW, ALU_OP, F_LED_SW; wire [31:0] A, B, F; // 中间数据通路 wire ZF, OF; wire [7:0] LED; Choice C1(AB_SW, A, B); ALU_TOP A1(A, B, F, ZF, OF, ALU_OP); CLED C2(F_LED_SW, LED, F, ZF, OF); endmodule顶层把三个子模块串成完整数据通路AB_SW 驱动 Choice 产生 A、BA、B 连同 ALU_OP 进入 ALU_TOP 算出 F、ZF、OFF 和标志位再由 CLED 切片成 8 位 LED 输出。模块名 C1、A1、C2 是原报告命名例化名和模块名区分开避免和顶层 ALU 重名。例化用的是旧式端口顺序对应不是.A(A)显式命名端口顺序一旦写错就会接错线。复刻这个工程时建议把三个子模块端口声明打印出来对着排靠记忆容易漏。注意 A、B、F、ZF、OF 这些中间信号都显式声明了 wire。不声明也能用隐式 net但遇到拼写错误时容易产生宽度不匹配的隐性警告。显式声明在 ISE 日志里更容易定位问题保留是更稳的习惯。4.2 仿真激励遍历循环一次跑完 8 种运算实验报告里的流程是 ISE 13.4 下建工程、写模块、仿真、生成 BIT、下载 FPGA。仿真的核心是给 AB_SW、ALU_OP、F_LED_SW 三组输入加激励。我习惯写一个小 testbench不依赖手动画波形module alu_tb; reg [2:0] AB_SW, ALU_OP, F_LED_SW; wire [7:0] LED; ALU dut( .AB_SW(AB_SW), .ALU_OP(ALU_OP), .F_LED_SW(F_LED_SW), .LED(LED) ); initial begin AB_SW 3b001; // 选第二组数据A3, B0x607 F_LED_SW 3b000; for (ALU_OP 0; ALU_OP 8; ALU_OP ALU_OP 1) begin #10; $display(AB_SW%b ALU_OP%b F%h ZF%b OF%b, AB_SW, ALU_OP, dut.F, dut.ZF, dut.OF); end AB_SW 3b010; // 换一组输入验证溢出 for (ALU_OP 0; ALU_OP 8; ALU_OP ALU_OP 1) begin #10; $display(AB_SW%b ALU_OP%b F%h ZF%b OF%b, AB_SW, ALU_OP, dut.F, dut.ZF, dut.OF); end $finish; end endmoduletestbench 用dut.F、dut.ZF、dut.OF层次引用访问顶层内部 wire绕开 LED 分时显示的限制控制台直接打印完整 32 位结果。$display里%h输出十六进制%b输出二进制标志位每条运算一行方便和验证表逐行对照。如果换成 Vitis 或 Vivado这个 testbench 可以直接拿过去三个子模块源码不改就能跑综合。仿真时注意两点。一是组合逻辑没有时钟#10只是给波形加观察窗口不是时序约束。二是敏感列表是ALU_OP or A or B如果 testbench 同时翻转 AB_SW 和 ALU_OP组合输出在一个 delta 周期内会先按旧值算一次再按新值算一次波形可能有毛刺。实际验证把输入变化拉开 10ns 以上或者只看稳定后的电平毛刺不影响结论。4.3 用程序员计算器核对结果以第二组数据为例实验报告里提到用系统自带的程序员计算器验证大数运算这是成本最低的验算手段。切到 HEX 模式输入 32 位十六进制数并选中 DWORD就能把 ALU 每种运算独立核对。以 AB_SW3b001 这组A3, B0x607为例预期结果如下ALU_OP运算预期 F000A B32h0000_0003001A | B32h0000_0607010A ^ B32h0000_0604011A ~^ B32hFFFF_F9FB100A B32h0000_060A101A - B32hFFFF_F9FC110A B32h0000_0001111B A32h0000_3038减法和同或结果是一串 F 时不要慌这是补码和按位取反的正常体现。计算器用 SUB 模式和 XNOR 模式分别验证一致就能放心继续。核对完仿真再上板上板时 AB_SW、ALU_OP、F_LED_SW 三组拨码分别对应输入选择、运算选择、输出选择LED 显示 F[7:0] 加标志位和仿真低 8 位对照即可。验证溢出时把 AB_SW 拨到 3b010、ALU_OP 拨到 3b100、F_LED_SW 拨到 3b100LED 最低位 OF 灯亮、最高位 ZF 灯亮对应 0x80000000 0x80000000 0 且溢出标志置位和仿真完全一致。5. OF 公式推导与移位分支的边界坑5.1 溢出标志公式的一个快速证明OF A[31] ^ B[31] ^ F[31] ^ C看着像拼凑实际按加数符号分成三类就能验证。设 A_s、B_s、F_s 分别为两个输入和结果的符号位C 为第 33 位进位组合A_sB_sF_sCOF正 正溢出00101负 负无溢出11110负 负溢出11011一正一负加法010/1与之互补0正正相加时最高位不可能产生进位C 恒为 0溢出时结果符号位翻成 1公式输出 1负负相加必然有 C1无溢出时 F_s1 输出 0溢出时 F_s0 输出 1。一正一负时 C 和 F_s 总是互补异或结果为 0。减法在硬件上等价于 A (~B) 1同一套公式直接适用。这个推导和唐朔飞教材里最高位进位与符号位进位相异则溢出的表述一致这里只是用异或把符号位、结果位、进位压缩成了一个表达式。5.2 移位分支的低 5 位陷阱3b111: CF B A是这份代码里唯一需要小心的地方。综合时移位器的移位量只取 A 的低 5 位但仿真时如果 A 的高位非零不同工具行为可能有差异。AB_SW7 那组A32h1234_5678低 5 位是 0x1824B 24 32h2200_0000恰好正确如果有人把 A 改成32h0000_0030低 5 位是 16完整值是 48行为就可能不一致。更稳妥的写法是显式截断移位计数CF {1b0, B} A[4:0]让仿真和综合都只看低 5 位。上板验证左移分支时建议把 AB_SW 固定在 3b111并用十六进制模式确认 A 的低 5 位再套结果。最后还有一个容易踩的点CLED 的 default 分支覆盖了 F_LED_SW100 到 111 四个值拨到这些档时 LED 显示的是 ZF 和 OF 而不是数据。如果在板上看到 LED 只在最低位和最高位有反应先检查 F_LED_SW 是不是拨进了 default 区别急着怀疑 ALU 算错了。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。