1. 项目概述
在数字电路设计中,除法运算一直是个有趣且具有挑战性的课题。今天我要分享的是一个基于FPGA实现的四位无符号二进制除法器设计。这个项目最初是为了满足嵌入式系统中对简单除法运算的需求而开发的,后来发现它在教学演示和小型数字信号处理系统中也非常实用。
这个除法器的核心功能是接收两个4位二进制数(被除数和除数),通过硬件电路直接计算出商和余数。具体来说:
- 输入范围:被除数0~15(4位无符号),除数1~15(除数不能为0)
- 输出结果:4位商(0~15)和4位余数(一定小于除数)
- 特殊处理:当检测到除数为0时,会点亮错误指示灯
我选择在Altera Cyclone IV FPGA上实现这个设计,主要考虑到这款芯片的逻辑资源(6272个LE)完全能满足需求,而且它的IO引脚配置灵活,非常适合这种小型数字电路实验。整个设计采用纯组合逻辑实现,使用Verilog HDL描述,综合后可以直接烧录到FPGA芯片中运行。
2. 设计原理详解
2.1 移位相减算法解析
移位相减算法是硬件实现除法的经典方法,它的核心思想是模拟手工除法的步骤。让我们深入理解这个算法的运作机制:
-
初始化阶段:
- 余数寄存器R清零(初始值为0)
- 商寄存器Q清零
- 准备从被除数的最高位(MSB)开始处理
-
迭代处理(共4次,对应4位):
a. 将当前余数左移1位(相当于乘以2)
b. 将被除数的当前位(从高到低)填入R的最低位(LSB)
c. 比较R和除数B:- 如果R ≥ B:执行R = R - B,并在Q的对应位置1
- 否则:保持R不变,Q的对应位置0
-
结果输出:
- 完成4次迭代后,Q中存储的就是商
- R中存储的就是余数
这个算法的精妙之处在于它完全通过移位和减法实现了除法运算,非常适合硬件实现。在FPGA中,每个步骤都可以用简单的组合逻辑门电路来实现。
2.2 算法实例演示
让我们用一个具体例子来演示这个算法的工作过程。假设我们要计算9 ÷ 2:
初始状态:
- 被除数A = 1001(9)
- 除数B = 0010(2)
- R = 0000, Q = 0000
第1位处理(bit3=1):
- R左移:0000 → 0000
- 填入当前位:0000 | 1 = 0001(1)
- 比较:1 < 2 → Q[3]=0, R保持0001
第2位处理(bit2=0):
- R左移:0001 → 0010
- 填入当前位:0010 | 0 = 0010(2)
- 比较:2 ≥ 2 → R=0010-0010=0000, Q[2]=1
第3位处理(bit1=0):
- R左移:0000 → 0000
- 填入当前位:0000 | 0 = 0000(0)
- 比较:0 < 2 → Q[1]=0, R保持0000
第4位处理(bit0=1):
- R左移:0000 → 0000
- 填入当前位:0000 | 1 = 0001(1)
- 比较:1 < 2 → Q[0]=0, R保持0001
最终结果:
- 商Q = 0100(4)
- 余数R = 0001(1)
这与我们预期的9 ÷ 2 = 4余1完全一致,验证了算法的正确性。
3. 硬件设计与FPGA实现
3.1 FPGA选型与资源配置
我选择Altera Cyclone IV EP4CE6F17C8这款FPGA主要基于以下几点考虑:
- 逻辑资源充足:6272个逻辑单元(LE)对于这个设计绰绰有余(实际使用约50个LE)
- IO引脚丰富:104个用户IO完全满足输入输出需求
- 开发工具成熟:Quartus II开发环境稳定易用
- 成本效益高:对于教学和小型项目非常经济实惠
3.2 硬件接口设计
整个系统的硬件接口设计如下:
输入部分:
- 使用8位拨码开关(SW0~SW7):
- SW3~SW0:设置被除数A(4位)
- SW7~SW4:设置除数B(4位)
输出部分:
- 使用9个LED指示灯:
- D3~D0:显示商Q(4位)
- D7~D4:显示余数R(4位)
- D8:错误指示(除数为0时点亮)
FPGA引脚分配:
verilog复制// 输入引脚
input [3:0] A; // PIN_30~PIN_27
input [3:0] B; // PIN_26~PIN_23
// 输出引脚
output [3:0] Q; // PIN_22~PIN_19 (LED D3~D0)
output [3:0] R; // PIN_18~PIN_15 (LED D7~D4)
output error; // PIN_14 (LED D8)
3.3 时钟与复位考虑
虽然本设计采用纯组合逻辑实现,不需要时钟信号,但为了系统的可扩展性,我还是保留了时钟和复位引脚:
- 时钟输入:PIN_23(50MHz晶振)
- 复位按钮:PIN_24(低电平有效)
这样在后续优化为时序逻辑版本时,可以方便地添加时钟控制。
4. Verilog实现详解
4.1 组合逻辑实现
核心的除法器模块采用纯组合逻辑设计,代码如下:
verilog复制module divider_4bit (
input [3:0] A, // 被除数
input [3:0] B, // 除数
output reg [3:0] Q, // 商
output reg [3:0] R, // 余数
output reg error // 错误标志
);
always @(*) begin
if (B == 4'd0) begin // 除数为0的特殊处理
Q = 4'd0;
R = 4'd0;
error = 1'b1;
end else begin
error = 1'b0;
R = 4'd0; // 初始化余数
Q = 4'd0; // 初始化商
// 4次迭代处理
for (int i = 3; i >= 0; i = i - 1) begin
R = R << 1; // 余数左移
R[0] = A[i]; // 填入被除数当前位
if (R >= B) begin // 比较判断
R = R - B;
Q[i] = 1'b1; // 设置商位
end else begin
Q[i] = 1'b0;
end
end
end
end
endmodule
4.2 顶层模块设计
顶层模块负责将除法器与实际的硬件接口连接起来:
verilog复制module top_divider (
input [7:0] SW, // 拨码开关输入
output [7:0] LED, // LED输出
output ERROR_LED // 错误指示灯
);
// 信号拆分
wire [3:0] A = SW[3:0]; // 低4位是被除数
wire [3:0] B = SW[7:4]; // 高4位是除数
// 中间信号
wire [3:0] Q, R;
wire error;
// 实例化除法器
divider_4bit u_divider (
.A(A),
.B(B),
.Q(Q),
.R(R),
.error(error)
);
// 输出分配
assign LED[3:0] = Q; // 商输出到低4位LED
assign LED[7:4] = R; // 余数输出到高4位LED
assign ERROR_LED = error; // 错误标志输出到单独LED
endmodule
4.3 关键设计技巧
-
组合逻辑的always块:
- 使用
always @(*)表示纯组合逻辑 - 所有输出都必须在这个块中赋值
- 使用
-
for循环的使用:
- 在组合逻辑中使用for循环需要特别小心
- 循环次数必须是编译时可确定的(这里是固定的4次)
-
错误处理机制:
- 优先检查除数为0的情况
- 一旦检测到除数为0,立即设置错误标志
5. 关键问题与解决方案
5.1 除数为0的处理
在硬件除法器中,除数为0是个必须处理的特殊情况:
问题表现:
- 数学上除数为0无定义
- 硬件中可能导致不可预测的行为
解决方案:
- 在算法开始前首先检查除数是否为0
- 如果除数为0:
- 设置错误标志(error=1)
- 输出商和余数都为0
- 点亮错误指示灯
实现代码:
verilog复制if (B == 4'd0) begin
Q = 4'd0;
R = 4'd0;
error = 1'b1;
end
5.2 组合逻辑延迟问题
纯组合逻辑设计虽然简单,但可能存在以下问题:
潜在问题:
- 多级逻辑导致的传播延迟
- 可能产生毛刺(glitch)
- 在输入变化时输出会有短暂不稳定
解决方案:
- 添加输出寄存器:在输出端添加一级D触发器
- 时序约束:在Quartus中设置适当的时序约束
- 流水线设计:将算法分成多个时钟周期完成
优化后的时序逻辑版本核心代码:
verilog复制always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
Q_reg <= 4'd0;
R_reg <= 4'd0;
error_reg <= 1'b0;
end else begin
Q_reg <= Q_comb;
R_reg <= R_comb;
error_reg <= error_comb;
end
end
5.3 数值范围限制
四位无符号数的特性带来了特定的限制:
限制条件:
- 被除数范围:0~15
- 除数范围:1~15(0视为错误)
- 商范围:0~15
- 余数范围:0~(除数-1)
保证措施:
- 通过算法设计确保余数一定小于除数
- 在输入端口添加范围检查逻辑
- 输出结果自动满足范围要求
6. 测试与验证方法
6.1 测试用例设计
为了全面验证设计,我设计了以下几类测试用例:
-
常规情况:
- 8 ÷ 2 = 4余0
- 9 ÷ 2 = 4余1
- 15 ÷ 3 = 5余0
-
边界情况:
- 15 ÷ 1 = 15余0(最大商)
- 15 ÷ 15 = 1余0(最小商)
- 0 ÷ 5 = 0余0(被除数为0)
-
特殊情况:
- 5 ÷ 0 = 错误(除数为0)
- 0 ÷ 0 = 错误(双0输入)
-
随机测试:
- 使用随机数生成多组测试用例
- 与软件计算结果对比
6.2 硬件测试步骤
在实际硬件上的测试流程如下:
-
硬件连接:
- 连接FPGA开发板与电源
- 确保拨码开关和LED正确连接
-
功能测试:
a. 设置被除数和除数(通过拨码开关)
b. 观察LED显示的商和余数
c. 验证结果是否正确
d. 特别测试除数为0的情况 -
性能测试:
- 使用逻辑分析仪测量计算延迟
- 测试最大工作频率
-
稳定性测试:
- 快速切换不同输入组合
- 观察输出是否稳定
6.3 仿真测试
在烧录到FPGA前,先用ModelSim进行仿真测试:
verilog复制initial begin
// 测试1: 8 ÷ 2
A = 4'b1000; B = 4'b0010;
#10;
// 测试2: 9 ÷ 2
A = 4'b1001; B = 4'b0010;
#10;
// 测试3: 除数为0
A = 4'b0010; B = 4'b0000;
#10;
$stop;
end
7. 优化与扩展方向
7.1 时序逻辑优化
虽然组合逻辑实现简单直接,但在某些场景下,时序逻辑实现可能更有优势:
优点:
- 减少组合路径长度
- 提高最大工作频率
- 节省逻辑资源
实现思路:
- 使用状态机控制计算过程
- 将4次迭代分配到4个时钟周期
- 每个周期完成一位的处理
核心状态机设计:
verilog复制reg [1:0] state;
reg [3:0] A_reg;
reg [2:0] bit_counter;
always @(posedge clk) begin
case (state)
0: begin // 初始化
A_reg <= A;
R <= 0;
Q <= 0;
bit_counter <= 3;
state <= 1;
end
1: begin // 迭代处理
R <= (R << 1) | (A_reg >> bit_counter);
if (R >= B) begin
R <= R - B;
Q[bit_counter] <= 1'b1;
end
if (bit_counter == 0) state <= 2;
else bit_counter <= bit_counter - 1;
end
2: state <= 2; // 完成
endcase
end
7.2 有符号数扩展
当前设计只支持无符号数,可以扩展为有符号数除法器:
实现步骤:
- 增加符号位处理:
- 商的符号 = 被除数符号 ^ 除数符号
- 余数的符号与被除数相同
- 对操作数取绝对值
- 调用无符号除法器
- 根据符号位调整最终结果
代码片段:
verilog复制// 符号计算
wire sign_A = A[3];
wire sign_B = B[3];
wire sign_Q = sign_A ^ sign_B;
wire sign_R = sign_A;
// 绝对值计算
wire [3:0] abs_A = sign_A ? (~A + 1) : A;
wire [3:0] abs_B = sign_B ? (~B + 1) : B;
// 调用无符号除法器
divider_4bit u_divider (
.A(abs_A),
.B(abs_B),
.Q(abs_Q),
.R(abs_R),
.error(error)
);
// 结果符号处理
assign Q = sign_Q ? (~abs_Q + 1) : abs_Q;
assign R = sign_R ? (~abs_R + 1) : abs_R;
7.3 位宽扩展
将4位设计扩展到更通用的N位:
修改要点:
- 使用参数化设计:
verilog复制module divider #(parameter WIDTH=4) ( input [WIDTH-1:0] A, input [WIDTH-1:0] B, ... ); - 将固定循环次数改为由参数控制
- 调整比较器和减法器的位宽
优势:
- 代码可重用性高
- 可以根据需求实例化不同位宽的除法器
- 适合作为IP核使用
8. 实际应用与心得体会
8.1 应用场景
这个四位除法器虽然简单,但在许多场景中非常实用:
-
教学演示:
- 数字逻辑课程中的算法硬件实现案例
- FPGA入门教学的实践项目
-
嵌入式系统:
- 资源受限的微控制器辅助计算
- 实时性要求高的简单除法运算
-
数字信号处理:
- 滤波器系数计算
- 数据归一化处理
8.2 开发心得
在实现这个项目的过程中,我总结了一些有价值的经验:
-
组合逻辑的优缺点:
- 优点:延迟低,一个时钟周期完成
- 缺点:可能产生毛刺,频率受限
-
测试的重要性:
- 边界条件测试特别关键
- 硬件测试前一定要做充分的仿真
-
设计权衡:
- 在资源占用和性能之间找到平衡
- 根据应用场景选择合适的实现方式
-
代码可读性:
- 良好的注释和模块划分大大提升可维护性
- 参数化设计增强代码重用性
8.3 性能评估
在实际测试中,这个设计表现出以下特性:
-
资源占用:
- 约50个逻辑单元(LE)
- 无存储器资源使用
-
时序性能:
- 组合逻辑版本延迟约7.2ns
- 最大工作频率约138MHz
-
功耗:
- 静态功耗:22mW
- 动态功耗(50MHz):5mW
这个设计在资源占用和性能方面都表现良好,完全满足一般应用需求。对于更复杂的应用,可以考虑前面提到的优化和扩展方向。