Hotdry.

Article

Nibble 定向 CPU 的 Verilog 科学计算器:从门电路到超越函数的硬件实现

解析 gdevic/FPGA-Calculator 项目中的 nibble 架构设计、BCD 运算单元与微码状态机,探讨专用计算器指令集的硬件建模思路。

2026-05-15systems

在开源硬件社区中,独立开发者 Goran Devic 的 FPGA-Calculator 项目展现了一种与众不同的工程路径:不是使用现成 IP 核,也不是基于通用 MCU 移植软件,而是从最基本的门电路出发,构建一台完整的科学计算器。这个项目以 nibble(4 位)作为基本数据单元,设计了一颗专用于数值计算的软核 CPU,并通过微码固件实现了三角函数、对数和指数等超越函数。本文深入解析该项目的架构设计与实现细节,探讨专用计算器指令集在硬件层面的建模方法。

Nibble 架构的选择与设计动机

传统 CPU 以字节(8 位)或字(16/32 位)作为基本操作单元,而本项目选择 nibble(4 位)作为数据通路宽度,这一决定源于科学计算器的本质需求。十进制数的每一位恰好可以用 4 位表示,范围从 0 到 9,正好对应一个 nibble 的有效编码空间。选择 nibble 导向的架构意味着硬件可以原生理解十进制数字的边界,无需在二进制与十进制之间反复转换。

这种设计决策贯穿整个架构:从寄存器宽度到 ALU 数据路径,从内存寻址到指令编码。以尾数寄存器为例,项目支持 16 个 BCD 位(即 16 个 nibble),这直接对应科学计算器常见的 16 位精度规格。硬件无需处理二进制小数的舍入误差,十进制运算的进位与借位直接在 nibble 边界上进行控制。这种原生十进制支持是本项目区别于通用 CPU 实现的关键特征。

从电路实现角度看,4 位宽度的数据路径在 FPGA 资源利用上更为紧凑。ALU 中的加法器、移位器和比较器都可以针对 BCD 操作进行优化设计,每个时钟周期处理一个 nibble,通过迭代完成多位运算。这种串行化处理虽然在时钟周期数上不如并行二进制运算,但在硬件复杂度和数值精度上具有明显优势。

BCD 运算单元的硬件实现

二进制编码十进制(BCD)运算在硬件层面需要专门的加法、减法和调整机制。项目中的 ALU 模块实现了完整的 BCD 运算指令集,其中最关键的是 DAA(Decimal Adjust After Add)和 DAS(Decimal Adjust After Subtract)指令。这两条指令负责在二进制加减结果基础上进行十进制调整,确保结果符合 BCD 编码规范。

当两个 BCD 数字相加时,普通的二进制加法可能产生无效编码。例如 8 加 9 得到二进制 10001,但这不是有效的 BCD 表示 —— 应该得到 17,即高位 nibble 为 1、低位 nibble 为 7。DAA 指令检测低半字节的进位或溢出条件,必要时加上 6(0110)进行修正,同时将进位传递到高半字节。这种调整机制确保每次运算后结果的每一位都在 0-9 范围内。

减法调整(DAS)面临更复杂的挑战,尤其是处理借位时。当被减数小于减数时,二进制减法会产生补码结果,DAS 需要检测这种情况并进行反向调整。项目通过 SBC(Subtract with Carry)指令与 DAS 的配合,实现了完整的 BCD 减法运算链。硬件状态机跟踪借位标志,在必要时注入补偿值,确保最终结果的 BCD 格式正确。

对于乘法运算,项目采用了查找表(LUT)方法而非迭代累加。由于 nibble 的进位传播需要在每一步处理完整的 16 位运算,传统的移位累加算法在硬件上实现复杂度较高。通过预计算部分乘积并存储在 ROM 表中,MUL 指令可以在常数时间内完成一位乘以四位的查找,再通过移位和累加完成整个乘法。这种方法以存储资源换取计算速度,符合 FPGA 的资源特性。

微码引擎与计算器专用指令集

项目的 CPU 采用微码(Microcode)架构,微指令存储在同步 ROM 中,每条微指令控制数据通路中各个功能单元的连接与操作。这种设计将指令集的灵活性与硬件实现的规整性结合在一起:新增指令无需修改数据通路逻辑,只需在微码 ROM 中写入新的微程序。

微码引擎的核心是一个状态机,典型执行流程包括取指(FETCH)、译码(DECODE)和执行(EXEC)三个阶段。取指阶段从微码 ROM 读取当前 PC 指向的微指令;译码阶段解析操作码和寻址模式;执行阶段驱动 ALU、寄存器堆和内存端口完成数据搬运和运算。每个阶段可以包含多个微周期,允许复杂指令在多个时钟周期内完成。

计算器专用指令集围绕数值运算进行了大量优化设计。分支指令支持多种条件判断,包括零(Z)、非零(NZ)、大于(GT)、小于(LT)、等于(EQ)、不等于(NE)和大于等于(GE)等。条件 CALL 允许在满足特定条件时将返回地址压栈,这在实现科学计算算法中的循环和分支时非常有用。BRAC(Branch with Condition)指令提供更紧凑的条件跳转编码,减少指令长度。

寄存器寻址模式的设计同样体现了计算器的应用特征。直接寻址(STM/LDM)用于常规的寄存器加载存储;扩展寻址(STX/LDX)支持基于地址寄存器的索引访问,这对于遍历 BCD 数组特别方便;双索引寻址(STX2/LDX2)允许以两个寄存器的和作为内存地址,适合矩阵运算和表格查找场景。地址指针(AP)机制提供了类似基址寄存器的功能,可以实现栈帧管理和递归调用。

脚本引擎是项目的一个独特功能,允许用户编写宏命令序列并存储在专用的 scripts.mem 文件中。这超越了简单的按钮响应,实现了可编程的计算流程。用户可以定义包含参数传递、条件分支和子程序调用的脚本,将常用操作序列自动化。这一功能依赖于微码中的脚本解释器,它从 scripts.mem 读取字节码并调度相应的微程序。

状态机建模与计算器固件结构

计算器的用户交互逻辑通过状态机建模,主要状态包括输入等待、数值输入、操作符输入、执行计算和结果显示等。输入等待状态监听键盘矩阵扫描,当检测到有效按键时转换到对应状态。数值输入状态维护当前的输入缓冲区,支持小数点和符号输入,并在每个数字输入后更新显示。操作符输入状态记录前一个操作数和待执行的运算符,准备进入二元运算的执行流程。

固件源码组织清晰地反映了功能模块划分。main.asm 作为入口点,通过 include 机制引入所有其他模块;boot.asm 处理初始化和复位向量;calc.asm 实现状态机主循环和模式切换;input.asm 处理键盘事件和数值解析;display.asm 管理 LCD 显示更新;keyhandlers.asm 定义每个按键对应的响应动作。运算模块按功能分离:addsub.asm 处理加减法,mul.asm 和 div.asm 实现乘除,sqrt.asm 处理平方根计算。超越函数的实现分布在 log.asm、exp.asm、tan.asm 和 arctan.asm 中,共享 scripts.scp 中的辅助例程。

数值格式的定义对于整个系统至关重要。项目采用尾数加指数的表示方法:16 位 BCD 尾数存储在连续的 RAM 单元中,指数部分和符号位存储在专用的系统变量中。这种表示法直接对应科学计算器的显示格式,显示驱动模块负责将内部表示转换为七段数码或 LCD 像素图案。

验证与测试基础设施

硬件实现的正确性通过多层次的测试框架验证。CPU 自检程序(test_self_check.asm)是核心验证工具,它逐个测试每条指令并将结果与预期值比较。自检程序覆盖调用返回、条件分支、寄存器读写、BCD 调整、ALU 运算、移位操作、乘法和栈操作等完整指令集。测试通过 halt 指令的条件判断实现断言:如果断言为真则进入 FAULT 状态,通过检查 FLAG_AUX 位判断是通过还是失败。

Verilator 仿真环境提供了快速迭代的验证手段。Makefile 整合了汇编、仿真和波形生成流程,开发者可以通过 make test 自动完成固件编译、Verilog 综合、仿真执行和结果验证。仿真环境支持 VCD 波形导出,gtkwave 可以查看任意时刻的信号状态,这对于调试复杂的状态机行为极为有用。

Calctest 是面向算术运算的端到端验证工具。它读取 Proto 子模块生成的测试向量,这些向量包含已知输入和预期输出的数值对。测试工具将输入写入模拟器的 RAM,执行固件中的运算指令,然后读取结果与预期值比较。这种方法确保 BCD 运算的每一步都符合数学规范,验证微码实现的正确性。

实现约束与性能考量

FPGA 资源的硬性约束深刻影响了架构设计。Altera Cyclone II 的逻辑单元数量决定了微码 ROM 的规模上限,项目将微码空间限制在 4096 字,每个微指令 24 位宽度。这一限制要求开发者精心优化微程序的指令密度,避免将过于复杂的功能塞入单一指令。

时钟频率的选择是精度与速度的折中。较低的时钟频率允许更长的组合逻辑路径,保证 BCD 调整和进位传播在单周期内完成。较高的时钟频率需要将复杂操作拆分为多个流水线阶段,增加状态机复杂度。项目中 50 MHz 的时钟频率在 Cyclone II 上可以稳定运行,同时保证科学计算所需的足够响应速度。

查找表方法在存储资源与计算效率之间取得了平衡。乘法查找表(mul_table.mem)预计算了所有可能的单 nibble 乘积,占用约 256 个存储单元,消耗约 1 KB 的 ROM 资源。这种方法将乘法运算从多次移位累加简化为一次表查找加一次加法,显著减少了微程序步数和执行时间。常数表(constants.mem)存储了超越函数计算所需的数学常数,避免在运行时重复计算。

工程化的工具链支持

Python 编写的汇编器(casm.py)是固件开发的核心工具。它解析类似汇编语言的源文件,生成二进制的微码字节码和符号表。汇编器支持宏定义、条件汇编和符号引用,简化了重复代码的编写和维护。脚本编译器(cscript.py)处理脚本源文件,将高级脚本命令转换为脚本引擎可解释的字节码。

Qt 桌面模拟器提供了交互式的开发调试环境。它使用 Verilator 将 Verilog 设计转换为 C++ 模型,集成到 Qt 应用中运行。模拟器支持单步执行、断点设置和寄存器查看,开发者可以在没有实际 FPGA 硬件的情况下验证固件功能。模拟器还可以编译为 WebAssembly,在浏览器中运行,这为项目的展示和分享提供了便利。

Quartus 工程文件封装了 FPGA 综合的配置信息。通过 make revB 命令,开发者可以组装针对特定硬件版本(RevB)的固件,生成 microcode.mem 和 scripts.mem 文件,然后导入 Quartus 进行综合和布局布线。最终生成的编程文件可以烧录到 Cyclone II 开发板上,实现物理硬件的运行。

设计哲学与工程价值

本项目代表了硬件设计的一种独特理念:不是为了通用计算而优化,而是为特定应用场景定制完整解决方案。从数据通路宽度到指令集设计,从数值表示格式到显示驱动,所有决策都服务于科学计算器这一明确目标。这种领域专用架构(Domain-Specific Architecture)的设计方法,在 ASIC 和 FPGA 应用中日益受到重视。

项目的文档和源码结构为硬件爱好者和教育场景提供了丰富的学习资源。读者可以从最基础的 Verilog 模块开始,逐步理解 CPU 数据通路、微码执行机制和状态机设计,最终掌握完整的系统集成方法。983 个提交记录了从概念验证到成熟产品的完整演进过程,展示了独立开发者如何在有限资源下实现复杂系统。

资料来源

项目仓库:https://github.com/gdevic/FPGA-Calculator

Verilog 源码:https://github.com/gdevic/FPGA-Calculator/blob/main/verilog/README.md

微码固件:https://github.com/gdevic/FPGA-Calculator/blob/main/ucode/README.md

systems

内容声明:本文无广告投放、无付费植入。

如有事实性问题,欢迎发送勘误至 i@hotdrydog.com