用Verilog在Quartus II里手搓一个4位乘法器:从原理图到FPGA烧录全流程
从零手搓4位乘法器:Verilog+Quartus II实战指南
第一次在FPGA上实现数字电路时,那种看到LED灯按预期亮起的成就感至今难忘。本文将带你完整走一遍四位乘法器的实现流程,从Verilog代码编写到最终烧录,过程中遇到的每个坑都会详细说明。不同于教科书式的实验报告,这里聚焦的是真实项目开发中那些没人告诉你的细节。
1. 理解乘法器的核心逻辑
四位乘法器的本质是将两个4位二进制数相乘,得到一个8位的结果。在数字电路领域,实现乘法运算主要有两种思路:
- 组合逻辑实现:通过门电路直接计算,延迟固定但资源占用较多
- 流水线实现:分多个时钟周期完成,适合高速连续运算
对于初学者,组合逻辑是更好的起点。其核心原理可以用这个例子说明:
假设A=1101(13),B=1011(11),计算过程如下:
1101 (A) × 1011 (B) ------- 1101 (B[0]=1, 结果加上A<<0) 1101 (B[1]=1, 结果加上A<<1) 0000 (B[2]=0, 不加) 1101 (B[3]=1, 结果加上A<<3) ------- 10001111 (143)Verilog实现时需要注意三个关键点:
- 位宽处理:4位输入需要8位输出寄存器
- 移位操作:使用
<<运算符实现位置对齐 - 条件判断:仅当乘数位为1时才累加部分积
提示:实际电路中并没有真正的"乘法",而是通过移位和加法组合实现的,这与CPU中的ALU工作原理类似。
2. Verilog代码编写实战
打开Quartus II新建工程后,创建名为mult4x4.v的Verilog文件。以下是经过实际验证的代码版本,包含常见错误防范:
module mult4x4( input [3:0] A, // 被乘数,接拨码开关 input [3:0] B, // 乘数,接按键 output reg [7:0] R // 结果,接LED ); always @(*) begin R = 8'b0; // 初始化结果寄存器 for (int i=0; i<4; i=i+1) begin if (B[i]) R = R + (A << i); // 关键运算逻辑 end end endmodule这段代码有几个易错点需要特别注意:
- 寄存器初始化:必须在使用前清零,否则会产生锁存器
- 敏感列表:使用
@(*)自动包含所有输入信号 - 循环变量:现代Verilog支持
int类型,比integer更规范
保存后将其设为顶层实体(右键文件→Set as Top-Level Entity),然后进行首次编译。如果遇到这些错误:
- Error (10170):通常是缺少分号或括号不匹配
- Warning (10240):未使用的信号,检查端口声明
3. ModelSim仿真技巧
仿真能提前发现90%的逻辑错误。新建mult4x4_tb.v测试文件:
`timescale 1ns/1ns module tb_mult4x4; reg [3:0] A, B; wire [7:0] R; mult4x4 uut (.*); // 自动连接端口 initial begin // 测试用例1:3×5 A = 4'b0011; B = 4'b0101; #10; // 测试用例2:15×15 A = 4'b1111; B = 4'b1111; #10; // 边界测试:0×0 A = 4'b0000; B = 4'b0000; #10; $stop; end endmodule在ModelSim中运行后,正确的波形应该显示:
| 时间(ns) | A | B | R |
|---|---|---|---|
| 0-10 | 0011 | 0101 | 00001111 |
| 10-20 | 1111 | 1111 | 11100001 |
| 20-30 | 0000 | 0000 | 00000000 |
如果发现输出全为X(未知值),通常是:
- 寄存器未初始化
- 敏感列表遗漏信号
- 端口连接错误
4. 管脚分配与电路连接
根据常见的DE10-Standard开发板,推荐管脚分配如下:
| 信号 | 管脚号 | 开发板位置 |
|---|---|---|
| A[0] | PIN_34 | SW1 |
| A[1] | PIN_33 | SW2 |
| A[2] | PIN_31 | SW3 |
| A[3] | PIN_30 | SW4 |
| B[0] | PIN_24 | KEY1 |
| B[1] | PIN_25 | KEY2 |
| B[2] | PIN_26 | KEY3 |
| B[3] | PIN_27 | KEY4 |
| R[0] | PIN_156 | LED1 |
| ... | ... | ... |
| R[7] | PIN_161 | LED8 |
在Assignment Editor中输入这些映射后,需要特别注意:
- 电压标准:确保IO Standard设置为3.3V LVTTL
- 弱上拉:按键输入建议启用Weak Pull-Up
- 保留管脚:设为As input tri-stated
生成原理图时,总线连接的正确方式是:
- 右键Verilog文件→Create Symbol Files
- 新建Block Diagram/Schematic File
- 添加生成的符号,用总线工具连接(名称后加[n..0])
5. 烧录与调试实战
编译成功后,连接USB-Blaster下载器:
- 点击Programmer→Auto Detect选择FPGA型号
- 添加输出的.sof文件
- 勾选Program/Configure后点击Start
常见问题解决方案:
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
| No Hardware detected | 驱动未安装 | 安装Altera USB-Blaster驱动 |
| JTAG communication error | 接触不良或线缆问题 | 检查连接,更换下载线 |
| 配置成功后无反应 | 管脚冲突或时钟未设置 | 检查全局复位和时钟分配 |
实际测试时,建议按这个顺序验证:
- 输入0×0确认所有LED熄灭
- 输入1×1检查最低位LED
- 输入15×15验证所有LED亮
- 随机组合测试如5×3、10×6等
当发现结果偏差时,用这个排查流程:
- 检查ModelSim波形是否正常
- 确认管脚分配与物理连接一致
- 用SignalTap II逻辑分析仪抓取实际信号
最后分享一个调试技巧:在代码中添加以下临时输出,可以通过开发板上的蜂鸣器快速定位问题点:
assign debug = (A == 4'b0000) && (B == 4'b0000) && (R != 8'b00000000);这个项目最让我意外的是,看似简单的乘法器在实际硬件实现时会遇到这么多细节问题。最初版本因为忘记初始化寄存器,导致结果随机波动,花费了两小时才找到原因。这也正是硬件设计的魅力所在——每个细节都直接影响最终结果。
