在嵌入式系统设计中,计算器的实现通常被视为入门项目 —— 但当你决定用 FPGA 从零构建一台支持三角函数、对数运算的科学计算器时,挑战便从软件算法转向了数字逻辑与硬件架构的深层设计。本文将深入剖析一个完整 FPGA 计算器项目的硬件实现细节,涵盖输入扫描、显示驱动、自定义 CPU 指令集以及 BCD(二进制编码十进制)运算单元的工程化设计思路。
为什么选择 BCD 架构
大多数现代处理器采用二进制补码表示数值,但计算器面向的是人类习惯的十进制运算。若用标准字节寻址 CPU 处理 16 位十进制尾数,开发者将陷入不断的移位、掩码和双 nibble 拼接操作中。HP 公司在 1984 年为 HP-71B 和 HP-48 系列开发的 Saturn 处理器采用了 nibble 级 BCD 架构,每个寄存器 64 位宽(16 个 nibble),操作可直接作用于任意 nibble 字段。
借鉴这一思路,本项目设计了一款专门的 BCD 处理器:4 位 ALU 原生支持 nibble 运算,内存按 nibble 寻址,指令编码围绕 nibble 粒度访问展开。这种架构使 16 位十进制尾数的逐位遍历变得 trivial,大幅简化了算术函数的微码实现。
输入系统:按键矩阵扫描与消抖
计算器采用 7×5 的按键矩阵布局,共 35 个轻触开关。硬件扫描逻辑由 FPGA 实现:依次将每一列驱动为低电平,读取行线状态,通过上拉电阻确保未按下时行线保持高电平。
机械开关的触点弹跳是硬件设计中的经典问题。实测表明,所用 Cherry 等效 6mm 轻触开关的弹跳时间低于 5 毫秒。消抖逻辑在检测到电平变化后启动计数器,只有连续稳定超过阈值才确认有效按键。消抖时间过短会记录幽灵按键,过长则让用户感到响应迟滞 ——5 毫秒是基于实测数据的工程折中。
按键编码通过专用输入端口送入 CPU,KEYCALL指令可根据按键码直接跳转至对应的处理程序,实现高效的按键分发。
显示驱动:兼容 HD44780 的 OLED 接口
显示模块选用 Newhaven NHD-0216AW,这是一款 2 行 ×16 字符的 OLED 模组,兼容 1987 年 Hitachi 推出的 HD44780 并行接口协议。接口包含 8 位数据线、RS(寄存器选择)和 E(使能)信号。
OLED 初始化序列对时序有严格要求:控制器内部复位较慢,主机必须在特定命令间插入延迟。这些时序参数先在 ModelSim 中仿真验证,再上板测试,确保显示模块一次点亮。驱动代码通过LCDWC(写控制字)、LCDWD(写 ASCII 数据)和LCDWR(写寄存器值为十六进制数字)三条专用指令与 CPU 集成。
自定义 CPU:12 位定长指令与哈佛架构
为满足 BCD 运算的特殊需求,项目设计了一套全新的指令集架构:
指令格式:采用 12 位定长指令,恰好容纳 3 个 nibble,与系统的 nibble 导向设计理念一致。8 位指令空间受限,16 位又显浪费,12 位是精妙的折中 ——DEC 的 PDP-8 小型机(1965 年)同样采用 12 位指令和 12 位地址空间(4096 字)。
内存模型:采用哈佛架构,指令空间与数据空间完全分离。指令总线 12 位宽(4096 条指令),数据总线 4 位宽(4096 个 nibble 地址),使代码扩展不会挤占数据空间。
寄存器设计:指令编码按 nibble 划分,寄存器索引自然适配 4 位字段,理论上支持 16 个通用寄存器(R0-R15)。实际实现采用 8 个寄存器,通过 SystemVerilog 参数可在综合时选择,逻辑单元占用差异仅约 3%。8 个寄存器已满足全部微码需求。
指令集概览:包含加载 / 存储(LDM/STM、LDX/STX索引寻址)、ALU 运算(14 种操作,含DAA/DAS十进制调整)、乘法(MUL,通过查表实现)、控制流(条件跳转 / 调用 / 返回)、I/O(LCD 专用指令)等。CALLI指令在微码分析后发现可节省 5.3% 的代码空间 —— 这是一个典型的 ISA 优化案例,源于对实际指令频率的测量。
BCD 运算单元与 ALU 设计
ALU 宽度为 4 位,核心挑战在于 BCD 运算的正确性。DAA(加法十进制调整)在 nibble 加法结果大于 9 时自动加 6 修正,并设置进位标志;DAS(减法十进制调整)则在减法后执行类似修正。这两条指令 borrowed directly from the 8086 processor,是 BCD 串行算法的硬件基础。
BSHR(BCD 右移)实现真正的十进制除 2 操作:当前数字除以 2,加上来自低位的半值(若进位输入为 1 则加 5)。进位输出为当前数字的最低位,传递给高位。该指令与 HP Saturn 架构的 SRB(Shift Right BCD)微原语功能等效。
内存映射与地址空间
数据地址空间布局如下:
0x000-0x0FF:寄存器文件(16 个寄存器 ×16nibble 尾数)0x100-0x11F:指数区(16 个寄存器 ×2nibble)0x120-0x12F:符号记录(16 个寄存器 ×1nibble)0x130-0x13F:系统变量(显示格式、状态位等)0x140-0x209:用户存储区(STO/RCL 寄存器)0x300-0x3FF:数据栈(向下增长,0x300 为下溢保护阈值)0x400-0x5FF:常量 ROM(π、e、CORDIC 表等)0x600-0x7FF:MMIO(LED 控制、随机数生成器、按键状态)0x800-0xFFF:脚本 ROM(4 位 token 的脚本解释器)
从仿真到硬件:验证流程
设计采用迭代验证策略:每添加一条指令,同步更新汇编器、编写测试用例,通过 Verilator 编译为周期精确的 C++ 模型进行验证,确认正确后才继续。test_self_check.asm作为第一道防线,运行每条指令并检查结果,失败时触发 HALT 并输出故障地址。
硬件原型采用双板方案:EP2C5 开发板通过 40 针 IDC 排线连接按键 / 显示板。这种分离策略隔离了风险 —— 若出现问题,几乎可以确定在新设计的按键板上。实测验证了消抖时间、上拉电阻值和 OLED 初始化时序后,才进入单板集成阶段。
最终实现的计算器占用 1593 个逻辑单元,仅占小型廉价 FPGA 的 35%。整个设计证明了:在资源受限的 FPGA 上,通过精心定制的指令集和针对性的硬件架构,完全可以实现功能完备的科学计算器。
资料来源
- Baltazar Studios. "A Calculator (5): The Hardware." baltazarstudios.com, 2026.
- Baltazar Studios. "A Calculator (6): Designing the CPU." baltazarstudios.com, 2026.
内容声明:本文无广告投放、无付费植入。
如有事实性问题,欢迎发送勘误至 i@hotdrydog.com。