---
title: FPGA上的CHIP-8游戏机模拟器
url: "https://www.fpgalearn.com/8958.html"
kind: article
id: 8958
excerpt: 这是一个在FPGA芯片（具体为TinyFPGA BX）上运行的CHIP-8游戏机模拟器。 实现注释与思考 编写单元测试（见cpu，gpu，bcd）对我的帮助极大…
published_at: 2025-05-02
author: FPGA小白
tags: FPGA工程, FPGA源码, 游戏机模拟器
word_count: 1615
source: "https://www.fpgalearn.com"
---

# FPGA上的CHIP-8游戏机模拟器

> 标签：FPGA工程、FPGA源码、游戏机模拟器
> 这是一个在FPGA芯片（具体为TinyFPGA BX）上运行的CHIP-8游戏机模拟器。 实现注释与思考 编写单元测试（见cpu，gpu，bcd）对我的帮助极大…

> 这是一个在FPGA芯片（具体为TinyFPGA BX）上运行的CHIP-8游戏机模拟器。 实现注释与思考 编写单元测试（见cpu，gpu，bcd）对我的帮助极大…

这是一个在FPGA芯片（具体为TinyFPGA BX）上运行的CHIP-8游戏机模拟器。
![图片](https://admin.shaonianxue.cn/wp-content/uploads/2025/05/invaders-small.jpg)
![图片](https://admin.shaonianxue.cn/wp-content/uploads/2025/05/invaders2-small.jpg)
## 实现注释与思考
编写单元测试（见cpu，gpu，bcd）对我的帮助极大，使我能够对大部分指令进行仿真测试。我还编写了一些简单的汇编程序，并使用Tortilla-8项目将其编译为CHIP-8格式。
为了使屏幕和键盘功能正常工作，还需要一些手动测试。我为芯片编写了简单程序运行，并修正了对CHIP-8行为的一些误解（例如，内存读写涉及多个寄存器，以及图形绘制可以环绕边界）。在这个过程中，我也修正了我的Rust语言编写的CHIP-8模拟器，有趣的是，尽管之前的理解有误，它仍能运行许多游戏。
CHIP-8规范包括16个一字节寄存器（V0到VF）和20个两字节栈空间。最初我想让它们成为独立数组，但很难协调访问以实现为RAM合成，因此我决定将它们映射到内存中（参见[cpu.v]顶部描述的内存映射）。
屏幕内容也被映射到了系统内存中，这意味着CPU和屏幕模块需要竞争内存访问。我决定，在读取屏幕内容时暂停CPU（即不执行下一条指令）。
处理屏幕显示是令人烦恼的，我按行存储帧数据（每个字节代表8像素的水平条），而我使用的屏幕期待的是8像素的垂直条，因此需要额外逻辑来旋转这些数据。
BCD指令（将字节转换为三个十进制数字）在电路中实现较为困难，因为它涉及到除以10的操作。我采用了Hacker’s Delight一书中描述的方法，该方法涉及位移操作和手动调整。详细可见[bcd.v]。
向内存加载游戏很有趣。IceStorm工具提供了`icebram`实用程序，可以在已准备好的比特流中替换内存内容，这样就不必每次想玩不同的游戏时都重复整个漫长的（约30秒）构建过程。
TinyFPGA BX的默认时钟速度为16MHz，这对CHIP-8游戏来说太快了。所以我将整体速度限制为每秒500条指令。
我还添加了一个“调试模式”，同时按下1和F键激活。这时，我会渲染寄存器、堆栈和部分程序内存而不是屏幕缓冲区，观看其运作十分有趣。
随机数生成使用了Xorshift算法，每一周期计算一次新值，若检测到按键则迭代计算两次，确保结果受用户输入影响。
最后，由于我是Verilog的新手，项目在某些方面显得笨拙：
- 单条指令大约需20个周期。
- 内存访问遵循“读-确认”循环，其间有一个不必要的单周期暂停。
- iCE40内存是双端口的，理论上可同时读写，但我并未利用这一点。
- 整个项目占用1600多个LUT，我相信可以优化至少于1000个LUT，以便能适应更小的iCEstick。
## 硬件配置
我使用了以下硬件：
- TinyFPGA BX，搭载Lattice iCE40-LP8K芯片
- SparkFun的16按钮键盘
- WaveShare的128×64像素单色OLED屏幕
## 源码概述
Verilog模块：
- `chip8.v` —— 专为TinyFPGA BX设计的顶层模块
- `cpu.v` —— 包含内存控制器的CPU
- `mem.v` —— 系统内存
- `gpu.v` —— 图形精灵绘制
- `bcd.v` —— BCD转换电路
- `rng.v` —— 虚拟随机数生成器
- `screen_bridge.v` —— OLED屏幕与CPU之间的桥接，以访问帧缓冲区
测试文件：
- `*_tb.v` —— 模块的测试平台（见下方如何运行）
- `asm/` —— 各种汇编程序
