从零到一:在Vivado中构建4x4阵列乘法器的完整流程

张开发
2026/4/21 19:49:23 15 分钟阅读

分享文章

从零到一:在Vivado中构建4x4阵列乘法器的完整流程
1. 阵列乘法器基础概念第一次接触阵列乘法器时我完全被那些密密麻麻的全加器连线图吓到了。后来才发现这东西就像搭积木一样有趣。简单来说阵列乘法器就是用全加器搭建的乘法计算电路特别适合在FPGA上实现。和我们平时手算乘法的原理很像都是先逐位相乘再相加。4x4阵列乘法器能计算两个4位二进制数的乘积输出8位结果。比如计算1101(13) x 1011(11)正确结果应该是10001111(143)。这种结构最大的优点是规整性好所有加法操作可以并行进行速度比串行乘法器快得多。我在Xilinx Artix-7开发板上实测过4x4阵列乘法器能在10ns内完成计算。2. Vivado开发环境准备工欲善其事必先利其器。建议直接安装Vivado 2022.2版本这个版本对初学者最友好。安装时记得勾选WebPACK版本免费和Artix-7器件支持。我第一次装漏了器件支持包结果新建工程时找不到目标器件白白折腾半天。新建工程时要注意几个关键设置工程类型选择RTL项目添加源文件时先不填我们后面手写代码目标器件选xc7a35ticsg324-1L对应常用的Basys3开发板在Simulation设置页勾选XSim仿真器有个小技巧建议把Project is an extensible Vitis platform的勾去掉这个选项对我们纯FPGA设计没用。创建完工程后先别急着写代码我习惯先建好目录结构project/ ├── sources/ ├── sim/ └── constraints/3. 模块化设计思路看过原理图就知道阵列乘法器最适合模块化设计。我的方案是把电路拆分成5个模块顶层模块zhenliechengfa第一列专用模块lie1通用列模块lie234超前进位加法器chaoqian3全加器基础模块fa这种划分方式有个好处如果要改成8x8乘法器只需要增加lie234的实例数量其他模块完全复用。我在项目里测试过用模块化设计比写一个超大verilog文件要节省30%的开发时间。重点说下lie234这个通用列模块。它包含3个全加器负责处理中间各列的计算。通过参数化设计我把进位输入(cin)、上层结果(u,aa)都做成端口这样不同列的连接关系就非常清晰。调试时发现个小技巧给每个端口加上[3:0]这样的位宽声明能避免很多隐式类型转换的坑。4. Verilog代码实现细节先看最简单的全加器模块(fa)这是整个设计的基石module fa( input a, b, cin, output sum, cout ); wire S1, T1, T2, T3; xor x1 (S1, a, b); // 半加器第一步 xor x2 (sum, S1, cin); // 半加器第二步 and A1 (T3, a, b); // 进位生成 and A2 (T2, b, cin); and A3 (T1, a, cin); or O1 (cout, T1, T2, T3); // 进位合并 endmodule超前进位加法器是提速的关键我参考了经典的三级超前进位结构module chaoqianjinwei( input [2:0]x, [2:0]y, c0, output [2:0]c ); wire [2:0]G, P; assign P x | y; // 传播信号 assign G x y; // 生成信号 // 超前进位逻辑 assign c[0] G[0] | (c0P[0]); assign c[1] G[1] | (P[1]G[0]) | (P[1]P[0]c0); assign c[2] G[2] | (P[2]G[1]) | (P[2]P[1]G[0]) | (P[2]P[1]P[0]c0); endmodule有个容易出错的地方超前进位模块的输出进位c[2]要接到顶层模块的z[7]这个连接关系在原理图上不太明显我当初就接错过导致结果高位总是差1。5. 仿真验证技巧仿真文件我建议这样写timescale 1ns / 1ps module tb_zhenlie(); reg [3:0] x, y; wire [7:0] z; // 实例化被测模块 zhenliechengfa uut (.x(x), .y(y), .z(z)); initial begin // 边界值测试 x4b0000; y4b0000; #10; x4b1111; y4b1111; #10; // 随机测试 for(int i0; i20; i) begin x$random; y$random; #10; $display(x%b, y%b, z%b, x, y, z); end // 特殊用例 x4b1010; y4b0101; #10; $finish; end endmodule仿真时我发现一个常见问题当输入变化太快时组合逻辑的毛刺会导致输出短暂异常。解决方法是在测试用例之间留够时间间隔比如#10。另外建议在波形窗口把x和y设置为无符号十进制显示这样检查结果更直观。6. 实际调试经验第一次烧写到开发板时我的结果总是比预期少1。后来用SignalTap抓波形才发现是超前进位加法器的c0端口没接低电平。这个教训让我明白所有输入端口必须显式初始化不能依赖默认值。资源占用情况大概是这样LUT: 58个寄存器: 32个最大时钟频率: 125MHz如果资源紧张可以考虑两点优化把全加器的xor/and门实现改成更节省资源的写法超前进位加法器改用两级结构7. 性能优化方向想让乘法器跑得更快可以试试这些方法流水线设计在每列之间插入寄存器改用Booth编码减少加法器数量优化进位链使用更高效的超前进位结构我在Artix-7上实测过加入两级流水后频率能从125MHz提升到200MHz代价是延迟从1周期变成3周期。具体选择哪种优化得看你的应用场景是要求低延迟还是高吞吐。8. 常见问题排查Q: 仿真结果全是x A: 检查所有wire是否都有驱动特别是进位信号Q: 输出高位总是0 A: 很可能是超前进位加法器的输出没接对Q: 时序仿真失败 A: 尝试降低时钟频率或插入寄存器有个小工具特别有用Vivado的Schematic Viewer。当代码行为不符合预期时用它查看综合后的电路图能快速定位连接错误。我后来养成了习惯每写完一个模块先用这个工具检查一遍电路结构。9. 扩展思考虽然我们做的是4x4乘法器但这个架构很容易扩展。比如要改成8x8增加lie234模块的实例数量扩展超前进位加法器的位宽调整顶层模块的端口定义另一个改进方向是支持有符号数乘法。只需要在输入处增加符号位处理最后对结果进行符号校正注意溢出情况的处理记得保存这个项目模板以后做其他算术运算电路时很多模块都可以直接复用。我后来做FIR滤波器时就直接用了这里的乘法器结构节省了大量开发时间。

更多文章