ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

32位ALU设计:Verilog实现与边界控制全解析

32位ALU设计:Verilog实现与边界控制全解析 简介面向数字电路与计算机组成原理学习者的32位ALU完整工程资料包基于ISE集成开发环境设计覆盖算术运算、逻辑运算、移位操作、控制信号与输入输出等核心模块适合课程设计、FPGA入门以及硬件描述语言实践者参考。压缩包内共三百九十三个文件以原理图文件、符号图文件、源代码文件、仿真工程及项目配置文件为主。原理图与符号图展示模块连接关系源代码实现具体运算逻辑仿真工程和配置文件便于直接打开工程进行综合、仿真与验证。压缩包整体约一点一兆字节结构清晰方便下载与本地运行。已有两千三百二十八人学习下载。从内容预览看工程内还包含大量辅助测试文件和脚本可用于理解ISE自动生成的仿真流程配套的32位ALU设计覆盖加法、减法、乘法、除法、逻辑与或非、移位以及进位溢出标志处理对照代码与原理图能够快速掌握层次化设计方法节省从头搭建环境的时间。1. 为什么32位ALU最难的部分不是加法而是边界控制一个32位ALU的RTL逻辑代码通常不到150行真正让人花掉大部分调试时间的是进位传递、溢出判定和移位语义这三类边界问题。加法只是把两个32位数拼在一起扩展高位但0x7FFFFFFF 1得到0x80000000时硬件上的进位标志C是1而有符号溢出标志V同样是1——两个标志同时成立往往第一次做课设的人会在这里卡很久。如果只按无符号数理解就会把溢出误判成正常进位如果只看最高位变化又会漏掉减法场景里的符号错位。这套设计流程对应 Xilinx ISE 14.7 环境目标器件按 XC3S 系列 FPGA 展开。设计输入用 Verilog综合由 XST 完成仿真用 ISim。适合两类人一类是在计算机组成原理或数字逻辑课程里做 32 位 ALU 课设的学生另一类是想把运算器数据通路彻底拆开看一遍的嵌入式或软件工程师。ALU 本身没有状态不涉及时钟写起来不难难的是把每一位的边界条件验证干净。2. ALU体系结构拆解位片级联、操作码编码与溢出标志2.1 从1位全加器到32位进位链一个 1 位全加器的本质是把三个输入压缩成两个输出和 S 与进位 C。若定义传播信号 P A 异或 B生成信号 G A 与 B则进位可以写成C(i1) G(i) | (P(i) C(i))这个递推式是理解 32 位加法器的起点。32 位加法不是把 32 个全加器简单排开而是让进位从低位向高位逐级传递。串行进位链在 worst case 下要经过 32 级门延迟速度上不划算但如果在 FPGA 上手写超前进位逻辑又会浪费查找表资源。Xilinx 的 Spartan-3 里已经集成了专用进位链原语 CARRY4手工用门级电路搭建进位逻辑反而会被综合工具优化成同样的结构。所以我的做法是在 RTL 里直接写行为级a b把进位链的物理实现交给 XST。从 1 位扩展到 32 位时还要注意一个位宽对应关系32 位二进制数等于两个 16 位半字或者四个 8 位字节。在仿真波形里看一个 32 位结果时可以对总线按 [31:16] 和 [15:0] 拆分观察也可以在 ISim 的 Waveform 窗口直接右键选择数字格式切换为十六进制或十进制。这个习惯在排查边界问题时很省时间。2.2 有符号补码运算与C/V标志的判定32 位有符号整数采用二进制补码表示范围是-2^31到2^31 - 1。0xFFFFFFFF代表 -10x80000000代表 -21474836480x7FFFFFFF代表 2147483647。无符号加法和有符号加法在硬件上走的是同一条进位链差异只出现在如何解释结果和如何判定溢出。进位标志 C 表示无符号运算结果是否超出 32 位范围溢出标志 V 表示有符号运算结果是否超出补码范围。两者的判定条件完全不同。加法溢出的条件是两个操作数符号相同而结果的符号与操作数不同减法溢出的条件是两个操作数符号不同且结果的符号与被减数不同。写成 Verilog 判断式就是// 加法溢出A 和 B 符号相同结果符号被翻转 v_add (~a[31] ~b[31] sum[31]) | (a[31] b[31] ~sum[31]); // 减法溢出A 和 B 符号不同结果符号与 A 不同 v_sub (~a[31] b[31] diff[31]) | (a[31] ~b[31] ~diff[31]);这组判断式不需要额外的时钟沿是纯组合逻辑。手工推导的方式可以结合二进制数与 16 位、32 位、64 位进制数对应表来验证比如0x7FFFFFFF 1 0x80000000在 32 位无符号视角是正常进位在有符号视角则是正数加正数得到负数的最小值V1。2.3 操作码编码与三级MUX结构32 位 ALU 需要支持至少九种运算加法、减法、与、或、异或、同或、逻辑左移、逻辑右移、算术右移。用 4 位操作码可以完整覆盖同时留出 default 分支处理未定义编码。操作码规划如下表操作码助记符行为C输出V输出0000ADDY A B进位有符号溢出0001SUBY A - B借位有符号溢出0010ANDY A B000011ORY A | B000100XORY A ^ B000101XNORY A ~^ B000110SLLY A B[4:0]000111SRLY A B[4:0]001000SRAY A B[4:0]00内部结构上ALU 通常采用三级并行加 MUX 选通的方案算术模块、逻辑模块、移位模块独立计算最后用一个结果选择器根据操作码选出最终输出。这种做法的好处是各类运算的路径延迟接近一致关键路径稳定落在加法器的进位链上不会因为某次运算的组合逻辑层数过多而产生意外的时序收敛问题。2.4 自写ALU还是例化IP核Xilinx ISE 自带 Core Generator里面提供 Adder Subtracter IP 和 Logic IP 核。直接用 IP 核的方式更省事但会带来两个问题一是 IP 核的接口位宽和延迟方式通常是参数化配置课设更关注的是计算过程和标志位含义IP 核把这些细节封装掉了二是 IP 核的输出常常带寄存器级需要在测试平台里额外处理 latency对理解数据通路的本质反而造成干扰。我一般的建议是加法器和移位器全部手写只有在需要使用特殊进位结构如 SRL16 或 DSP48 乘法器时再考虑例化原语。3. Verilog实现端口定义、算术核心与移位器的一次成型3.1 端口信号与数据宽度的规划端口定义是整个 ALU 的契约。输入有两组 32 位数据 A 和 B一组 4 位操作码 op输出包括 32 位结果 Y、进位标志 C、溢出标志 V 和零标志 Z。零标志在课程设计中经常被忽略但它对应指令集架构里的beq判断是 ALU 必须提供的状态输出。端口规划如下表信号方向位宽说明ainput32操作数 Abinput32操作数 Bopinput4ALU 操作码youtput32运算结果coutput1进位或借位标志voutput1有符号溢出标志zoutput1零标志Y 0 时置 1Verilog 里所有位宽必须显式声明。常见问题是签名打错或者总线宽度不匹配导致仿真波形里出现高位被截断综合时不报错但行为错误。32 位数据在端口和内部连线中统一写成[31:0]不要混用[0:31]否则在 ISim 里看到的总线顺序会和预期完全相反。3.2 算术核心加法、减法与借位标志算术模块是 ALU 里唯一产生标志位的部分。加法直接用拼接赋值扩展进位减法在赋值的同时把借位关系转换为进位标志语义避免测试平台里出现位宽歧义。// alu32.v module alu32 ( input wire [31:0] a, input wire [31:0] b, input wire [3:0] op, output reg [31:0] y, output reg c, output reg v, output reg z ); wire [31:0] sum, diff; wire carry_sum, borrow; // {carry, data} 是 33 位拼接把加法进位显式取出来 assign {carry_sum, sum} a b; // 无符号减法的最高位输出 0 表示需要借位 // 所以这里用 ~borrow 反转成“有借位”标志 assign {borrow, diff} a - b; always (*) begin // 默认输出防止 latch y 32h0; c 1b0; v 1b0; case (op) 4b0000: begin y sum; c carry_sum; v (~a[31] ~b[31] sum[31]) | (a[31] b[31] ~sum[31]); end 4b0001: begin y diff; c ~borrow; v (~a[31] b[31] diff[31]) | (a[31] ~b[31] ~diff[31]); end 4b0010: y a b; 4b0011: y a | b; 4b0100: y a ^ b; 4b0101: y a ~^ b; 4b0110: y a b[4:0]; 4b0111: y a b[4:0]; 4b1000: y $signed(a) b[4:0]; default: y 32h0; endcase z (y 32h0); end endmodule这里的assign {carry_sum, sum} a b;把相加结果的第 33 位接出来作为进位比单独用assign carry_sum a[31] b[31] | ...更简洁也更不容易漏边界。减法部分用borrow表示无符号减法结果的最高位当 A B 时该位为 0取反后作为传统意义上的借位标志传给 C 输出。z标志放在always块末尾统一计算避免在每个分支里反复写z ...。3.3 逻辑运算与三类移位操作的Verilog写法逻辑运算的写法没有悬念直接对应 Verilog 的位运算符。~^是同或运算符等价于~(a ^ b)在 XST 里会映射成 LUT 加上反相器面积开销极小。移位部分有三点需要注意左移和逻辑右移是标准运算符算术右移在 Verilog-2001 中写作$signed(a) b[4:0]它保留符号位右移后高位补 a[31]移位量统一取b[4:0]的 5 位这样移位范围被限制在 0 到 31避免移位量大于 32 时的未定义行为。综合时 XST 会把移位操作映射为多路选择器结构。左移 1 位对应一组 2 选 1 MUX左移 2 位对应另一组 MUX依此类推最终形成一个桶形移位器结构。手写循环展开只会让代码变长综合结果并不会比直接用运算符更好。算术右移则会额外产生一组符号扩展 MUX综合报告里的 LUT 数量会比逻辑右移多这个差异属于正常现象。3.4 ISE综合配置与资源报告核对在 ISE 中新建工程时选择 Verilog Module 类型并在 Device 属性里指定目标 FPGA 型号。综合前把alu32设为 Top Module然后在 Process 窗口双击 Synthesize - XST。综合完成后查看 Device Utilization Summary重点关注 Slice 数量和 LUT 数量。一个 32 位 ALU 在 Spartan-3 上通常消耗 60 到 100 个 LUT如果超过 200大概率是某个分支把结果当成了时序逻辑或写入了不必要的临时变量。综合属性建议保持默认。XST 的 Optimization Goal 可以在 Speed 和 Area 之间切换课设场景选 Area 有时能减少 20% 左右的 LUT但会略微拉长进位链延迟。不要手动关闭flatten hierarchy除非你明确要在波形里观察内部子模块信号。4. 自检测testbench用边界向量覆盖32位ALU的每一条数据通路4.1 封装任务task的对比式检查方法手动在 testbench 里一条条#10 y ...的写法效率太低而且结果只看波形不比对眼很容易漏。更稳的方法是写一个task封装单次测试把预期值传进去自动比较标志位并打印结果。timescale 1ns / 1ps module tb_alu32; reg [31:0] a, b; reg [3:0] op; wire [31:0] y; wire c, v, z; alu32 u_alu ( .a(a), .b(b), .op(op), .y(y), .c(c), .v(v), .z(z) ); task check; input [31:0] exp_y; input exp_c; input exp_v; input exp_z; begin #10; if ((y ! exp_y) || (c ! exp_c) || (v ! exp_v) || (z ! exp_z)) begin $display(FAIL op%h a%h b%h - y%h c%b v%b z%b, op, a, b, y, c, v, z); $display(EXP - y%h c%b v%b z%b, exp_y, exp_c, exp_v, exp_z); $finish; end else begin $display(PASS op%h a%h b%h - y%h c%b v%b z%b, op, a, b, y, c, v, z); end end endtaskcheck任务的四个输入分别是期望结果、期望进位、期望溢出、期望零标志。在每一个测试向量前先给a、b、op赋值再调用check仿真器会在#10之后等待组合逻辑稳定再比对。使用!而不使用!是为了把x和z状态也纳入比较范围一旦设计出现高阻或未定义检查就会直接报失败。4.2 测试用例设计与标志位约束表测试用例不能只覆盖正常数字。加法和减法要分别准备无符号边界和有符号边界逻辑运算要覆盖全 1、全 0 和交替位移位则重点检查符号扩展和移位量为 0 的情况。核心测试组如下测试组ABop期望Ycvz加法溢出0x7FFFFFFF0x0000000100000x80000000010减法溢出0x800000000x0000000100010x7FFFFFFF110全0加10x000000000x0000000100000x00000001000减法借位0x000000000x0000000100010xFFFFFFFF100与运算掩码0xFFFFFFFF0x0000000000100x00000000001逻辑右移0x800000000x0000000101110x40000000000算术右移0x800000000x0000000110000xC0000000000算术右移正数0x400000000x0000000110000x20000000000减法借位组中0x00000000 - 0x00000001的结果是0xFFFFFFFF无符号视角发生借位所以c1有符号视角 -1 在范围内v0。这组用例能同时检验 C 和 V 两套标志逻辑是测试平台里最值得保留的一条。这些向量可以直接写成check连续调用。测试完成后加上若干随机访问用$urandom生成 100 组随机 A、B 和 op再把结果与一个行为级参考模型比对。参考模型可以直接用 Verilog 写一个同样接口的 function这样既不依赖外部文件也可以在$finish之前自动打印整体测试次数。4.3 X态排查仿真里出现xxxx时先查三处仿真波形里出现xxxx是最常见的失败现象但原因通常集中在三个位置。第一是端口连接遗漏例化子模块时某个端口没有接输入悬空会直接表现为x。第二是case分支覆盖不全如果操作码在某个位宽组合下没有任何分支命中输出就会保持前一个值或变成x这也是代码里保留default分支的原因。第三是变量在always块中被条件赋值但缺少初始值这在组合逻辑里会造成 latch。排查时先看综合报告里的WARNING:Xst:737类提示这类提示会明确指出哪些信号存在 latch 风险。5. 上板前的关键边界处理算术右移、综合警告与ILA验证5.1 用断言验证算术右移与符号扩展的一致性算术右移是 ALU 设计中最后被验证的一项原因在于$signed(a) b[4:0]的仿真结果在部分早期仿真器上依赖操作数符号推断。一个稳妥的自检方法是把算术右移手工展开成符号扩展再拼接a n等价于{{n{a[31]}}, a[31:n]}。在 testbench 末尾加一段循环验证用它来确认综合工具的移位语义没有偏integer i; reg [31:0] exp_sra; initial begin a 32h80000000; op 4b1000; for (i 0; i 32; i i 1) begin b i; #10; exp_sra {{i{a[31]}}, a[31:i]}; if (y ! exp_sra) begin $display(SRA FAIL shift%0d y%h exp%h, i, y, exp_sra); $finish; end end $display(SRA all 32 shifts PASS); end endmodule这个循环覆盖了 0 到 31 的全部移位量对0x80000000这种符号位和最高数据位不同的数尤其关键。运行时如果全部通过说明算术右移的符号扩展行为与手工展开一致。5.2 检查综合报告失误率最高的三类警告上板前查看一次完整综合报告是必要的。失误率最高的三类警告分别是未使用输入端口、信号被截断、组合逻辑回路。未使用端口通常由顶层模块修改后残留信号截断则发生在 33 位中间结果赋值给 32 位端口时虽然仿真可能正常但板级行为会缺位组合逻辑回路一定伴随 latch 推断需要回到 always 块重新检查分支。5.3 ChipScope上板验证的信号选择把设计下载到 FPGA 后用 ChipScope 抓内部信号时建议同时观察a、b、op、y和v。操作码和结果放在一个 32 位总线上标志位拆成单 bit 便于触发。触发条件设成op 4b0001再用板子拨码开关制造借位场景就能直接复现仿真里的减法边界情况。注意 ChipScope 会占用少量 BRAMSpartan-3 资源紧张时优先抓 64 深度的窗口而不是全部展开。上板之后记得先跑一遍同样的边界测试加法溢出、减法借位、算术右移符号位用 ChipScope 比对波形和 testbench 的期望值四组测试对上了再继续接外部存储器或总线接口。本文还有配套的精品资源点击获取
返回列表