从 0.7 FPS 到 15 - 20 FPS:自定义 CPU 运行《毁灭战士》的艰难提速之旅!
《毁灭战士》简介《毁灭战士》是 id Software 在 1993 年发布的电子游戏它在游戏界引发革命迅速风靡全球奠定了现代第一人称射击游戏的基础。超高人气让“《毁灭战士》能在任何设备上运行”的说法诞生为验证此说法它几乎被移植到所有设备从微控制器、烤面包机甚至到细菌。两周前成功在自己搭建的 CPU 上运行或者说“艰难运行”了《毁灭战士》还发布了相关视频获得数百万浏览量。在逻辑门层面设计了自定义 CPU将其与外设相连适配《毁灭战士》源代码使其能在机器上运行最后部署到现场可编程门阵列上实时运行。运行《毁灭战士》前只运行过自己编写的简单程序像《乒乓》和曼德勃罗集相关程序。面临的挑战从提出的流水线设计开始想运行更复杂程序。但面临内存和速度两大难题。更大程序需要更大内存最初设计只能利用 FPGA 的块随机存取存储器其容量不足 1MB。《毁灭战士》的基础共享软件版本就有 14MB这还只是存储程序所需内存不包括运行时所需内存。第二个障碍是速度《毁灭战士》在现代 PC 上运行轻松但对该 CPU 是巨大挑战。我和利亚姆决定分别解决这两个问题。利亚姆在为乱序处理奠定基础我负责解决内存集成问题。内存集成最初的 CPU 设计中内存管理简单。FPGA 的块随机存取存储器延迟仅 1 个周期且接口简单。这种一致性让流水线处理器无需因内存操作停顿稳定延迟已纳入流水线设计。此外块随机存取存储器有粒度性可逐字读取和编辑内存。相比之下DDR3 内存速度慢、延迟可变且总线宽度宽。这使内存操作更复杂、不可预测速度也慢得多。若所有内存操作都通过 DDR3 内存进行CPU 运行速度将大幅下降。这时缓存发挥作用将活跃内存区域存储在块随机存取存储器中提高访问速度。优化良好的缓存几乎可消除 DDR3 带来的额外延迟。CPU 设计这个版本的 CPU 采用相对标准的 5 级流水线指令获取、解码、寄存器读取、执行和写回。此外该设计将内存操作抽象为统一接口简化核心流水线阶段。核心流水线-指令获取阶段该阶段跟踪程序指针通过指令缓存从内存中获取正确指令。与之前设计不同它内部处理重定向和停顿。随着 DDR 内存加入重定向更复杂。以前内存延迟为 1 个周期可每个周期获取一条指令并在重定向请求到来的周期切换内存请求地址。但新设计中若获取阶段收到重定向请求内存可能已有请求在处理。需跟踪内存的下一个响应是否无效然后请求正确地址。-解码阶段这个阶段较简单将 32 位指令拆分成各个组成部分保存到指令包中再发送到流水线的下一个阶段。不过这个阶段也有独特问题将在后面章节详细介绍。-寄存器读取阶段这个阶段有重大修改。之前的寄存器文件采用组合逻辑读取减慢了 CPU 速度。这个版本将读取操作流水线化虽增加 1 个周期延迟但减少了关键路径延迟。另一个变化是处理“写后读”冒险。“写后读”冒险指在待写入操作完成前读取寄存器得到错误数据。之前的读取阶段内部跟踪通过它的最后几次寄存器写入操作这种方法逻辑高效但脆弱不总是有效。更严格测试表明特殊情况会导致它无法检测到冒险。新版本依赖由周围核心计算并输入到该阶段的“寄存器使用映射”。“寄存器使用映射”是 32 位映射用于跟踪寄存器是否正在使用。它能自然适应不同流水线长度无需使用特殊数字。使用“寄存器使用映射”读取阶段可决定是否触发冒险标志并暂停前一个阶段或让指令继续执行。-执行阶段该阶段决定如何处理每条指令。它将指令各部分路由到不同组件调用算术逻辑单元进行算术运算、解析分支指令并与内存交互。解析跳转指令时它会向流水线前面发送刷新信号清除错误阶段。遇到内存指令时它会进入特定内存等待阶段直到内存响应。-写回阶段这个阶段只是将值写回到寄存器文件中。内存处理核心流水线较标准真正复杂的是内存处理。理想的流水线 CPU 中指令获取阶段每个周期获取一个新字执行阶段每个周期获取新数据。相比之下FPGA 上的 DDR3 每 30 - 60 个周期才能返回 2 个字远低于期望吞吐量所以需要缓存。实际上需要两个缓存指令缓存和数据缓存它们可独立运行满足获取和执行阶段在同一周期可能请求两个不同地址的需求。缓存设计指令缓存和数据缓存相对简单且彼此相似。实际上指令缓存使用的底层代码与数据缓存完全相同只是去掉了写逻辑。两个缓存都是简单的单路直接映射缓存。当前版本中它们各自使用 2048 个缓存行每行包含 4 个字。收到内存请求时缓存会分离出字地址通过去掉最低两位然后计算缓存索引。缓存索引是字地址的最低 10 位。接着它从块随机存取存储器中提取该缓存行。缓存行由数据、标签和状态三个组件组成。数据是缓存跟踪的 4 个实际内存字标签是字地址的高位用于防止缓存别名问题。别名问题指多个地址指向同一个缓存索引需确保不混淆它们。2 位的状态位表示有效和脏信号。有效信号表示该行数据是实际数据而非初始化时的随机值脏信号告诉我们 CPU 自从从内存中获取该行数据后是否对其进行了修改。如果提取的缓存行有效且标签匹配缓存会根据操作进行简单的读取或读 - 修改 - 写操作无需向内存发送请求。然而如果标签不匹配缓存必须访问 DDR3 内存。有效缓存行有干净和脏两种可能状态。如果一行是干净的可直接从内存中读取新的缓存行并丢弃当前数据。如果是脏的缓存必须先将修改后的行写回内存然后再请求新的数据。考虑到 DDR3 的延迟可能在 30 - 100 个周期之间而缓存命中仅需 2 个周期这非常慢。坦率地说这是个效率较低的设计。更好的缓存应使用更长的缓存行提高存储密度和空间重用性。但选择 4 个字的行宽是为了简化与原始 FPGA 板上 128 位宽的内存接口生成器接口的交互。后来更换了不同的板子所以这个低效设计就暂时保留了下来。仲裁机制如果指令缓存和数据缓存同时发出内存请求怎么办在[6.191]中这个问题通过为每个缓存配备一个内存芯片来解决但板子只有一个芯片所以这种方法不可行。这时内存仲裁器发挥作用。它对两个缓存来说就像一个内存接口通常只是将请求转发到实际的 DDR3 内存。但当一个请求正在处理时又收到另一个请求它会假装内存已经收到了请求实际上只是将其排队直到内存空闲时再发送。当然这可能导致死锁问题即一个缓存会使另一个缓存无法访问内存。通过优先处理数据缓存的请求来解决这个问题因为它在流水线中处于更下游的位置最终会完成内存请求。底层实现通过上述设计CPU 在模拟内存环境中能完美运行但在实际硬件中却无法正常工作。首先DDR3 内存接口非常复杂需要精确的时序和控制。这就是赛灵思的内存接口生成器发挥作用的地方它让原本复杂的接口变得相对简单。第二个问题是内存接口生成器在自己的时钟域中运行如果直接将 CPU 连接到内存接口生成器会遇到难以理解的时序问题导致系统崩溃。第三个问题是无法让内存接口生成器在原来的 FPGA 板带有 128 位总线上正常工作所以借用了朋友[瑞安·唐]的板子它有一个较小的、带有 64 位总线的 DDR3 芯片。这些问题通过一系列的时钟域转换、先进先出队列和协议包装器来解决相关代码可在 GitHub 上找到。最重要的是最终一切都能正常工作了。有了新的内存甚至可以运行《蜜蜂总动员》相关程序。外设接口与 I/O将《毁灭战士》移植到硬件前还需做些工作。虽然 CPU 能正常工作但无法与外界通信。需要显示输出、硬件定时器、调试输出和键盘输入等外设。决定为这些外设使用内存映射 I/O。VGA 控制器已搭建好将其扩展到 12 位色彩并连接到 HDMI 端口。硬件定时器简单以微秒为单位跟踪时间并将其保存到内存中的一个字中。调试输出稍复杂。简单版本只是将通用异步收发传输器发送器连接到可写的内存值上。但如果 CPU 连续打印多个字符会丢失一些字符导致输出混乱。因此使用了一个先进先出缓冲区防止溢出。键盘输入问题更大。最初计划与瑞安借给的厄巴纳 FPGA 板上的 USB 主机芯片交互。但编写串行外设接口驱动程序后惊讶地发现串行外设接口总线上没有数据传输。似乎板子上的 USB 端口不提供电源无法为键盘供电。于是连接了一个通用异步收发传输器接收器将笔记本电脑的按键输入转发到 FPGA。虽不太完美但至少能工作。至此完成了运行《毁灭战士》所需的所有准备工作。移植《毁灭战士》与调试将《毁灭战士》移植到新芯片是具有挑战性的任务。要找到加载程序的方法访问正确的外设实现输入和输出还要解决因平台特性产生的各种问题。将其移植到自定义 CPU 更具挑战性因为程序出现问题时不知道是代码有误还是 CPU 本身存在问题。不过奥兹克尔创建了[doomgeneric]简化了移植过程。尽管有这个简化工具还是遇到许多挑战。朋友[利亚姆]负责移植过程他可能会在他的博客上分享见解。在众多挑战中有文件系统问题、渲染问题还有很多与 printf 相关的问题以至于他最后不得不自己编写了一个版本。最奇怪的问题是printf 有时能正常工作有时却不行。许多这些 bug 暴露了 CPU 的错误。例如常看到系统陷入包含数百条指令和跳转的无限循环。有时它似乎会随机跳转跳到毫无意义的地址。然而一次性调试整个《毁灭战士》程序不可行所以将其分解成多个部分逐步测试更复杂的程序。偶尔缓存仲裁器和内存接口生成器的时钟域转换会出现问题导致写回请求丢失。有时立即数的计算会出错还有时数据缓存会覆盖指令数据。其中一些问题早在第一个[单周期处理器]中就存在只是之前从未遇到过。最终在模拟环境中让一切都正常工作了。然而在硬件上它就是无法正常运行。编写的任何程序都能正常工作但《毁灭战士》不行。它似乎会在随机的地方停止跳转到随机的地址甚至开始读取半字指令。认为问题出在内存接口生成器模拟上因为这是唯一难以模拟的部分但编写的所有内存测试在模拟和硬件环境中都能完美运行。经过长时间困惑决定在加载程序前将 DDR3 内存初始化为随机值。这导致《毁灭战士》无法运行但其他程序不受影响。似乎《毁灭战士》假设未初始化的内存为零而在硬件中并非如此。经过一些链接和汇编方面的修复《毁灭战士》终于在硬件上加载成功了。之后进展相对顺利让《毁灭战士》以大约 0.7 帧每秒的速度运行实际上应该说是每帧需要几秒。原本打算在这里结束博客因为已实现最初目标。但对 0.7 帧每秒的速度不满意。例如本来打算坐下来学习线性代数但最后却开始优化 CPU。可能已经上瘾了。迈向 30 FPS 的征程《毁灭战士》以 1 帧每秒的速度运行实在不尽人意。虽令人印象深刻但根本无法正常游玩。想玩《毁灭战士》而不是看幻灯片。最简单的提速方法是提高时钟速度。将时钟频率从 100MHz 提高到 125MHz帧率提高约 20%。之所以不是 25%是因为内存延迟与 CPU 核心速度是解耦的。接下来的优化是在 CPU 的指令获取阶段。原来的获取设计效率极低更注重正确性而非速度。因此它每条指令经常会向指令缓存发送 2 个请求。修复这个问题后将帧率提高到约 2.5 帧每秒速度提升超过 300%。然而到这个阶段进一步提高性能更困难。开始增加更多指令。实现了乘法运算符但选择不实现除法或取模运算因为这会增加流水线的复杂性。因此CPU 变成了一个 RV32I-ZMMUL CPU。这又让帧率提高了 1 帧每秒达到 3.5 帧每秒。考虑到 15 帧每秒的目标这虽是进步但还是有些令人失望。之后仔细分析了哪些函数占用 CPU 时间发现将屏幕缓冲区的数据复制到 VGA 控制器的块随机存取存储器中非常耗时。这不仅增加了内存带宽的压力还刷新了缓存。因此修改了代码使其直接写入 VGA 的块随机存取存储器。同时意识到可以对缓存读取命中进行流水线处理将延迟从 2 个周期降低到 1 个周期。这两个优化措施结合起来将帧率提高到约 6.7 帧每秒。从这一点来看如果不彻底改变流水线似乎没有太多简单的方法可以提高速度。考虑到目前正在开发一个乱序流水线它将取代现有的大部分流水线不想在这个流水线上投入太多时间。不过还有一个可以尝试的方法编译器优化。当前运行的《毁灭战士》版本是使用 O0 编译的这意味着编译器不会对代码进行任何优化。这应该很简单只需要将 0 改为 2 就可以了。但每次尝试这样做时程序都会崩溃也不知道原因。最初认为是 CPU 出现了错误毕竟编译器优化怎么会导致代码崩溃呢经过长时间的调试终于找到了问题所在。没有将硬件定时器的内存映射 I/O 指针标记为 volatile。编译器会认为从未向定时器写入数据因此将其从代码中优化掉了。发现[这篇博客文章]在学习优化方面很有帮助。修复这个问题后游戏运行得非常流畅。最终成果经过编译器优化后《毁灭战士》以 15 - 20 帧每秒的速度流畅运行现在已经非常适合游玩也很有趣。终于明白为什么它在当年如此受欢迎了。通过乱序处理和其他优化希望最终能达到 30 帧每秒的目标。总结与展望移植《毁灭战士》是有趣但有些痛苦的经历。学到了很多关于内存和缓存的知识但最大的收获是学会了如何调试大型系统。还有件有趣的事发布的[9 秒运行视频]已有 200 万浏览量。至于未来计划目前致力于实现乱序处理以及更好的内存访问模式这应该会让游戏运行得更快。也许之后编写基本的图形处理器后可以尝试移植《雷神之锤 2》。希望不久后能在更大的 FPGA 上运行游戏。感谢阅读如果您有任何问题请随时联系我

相关新闻

Spring Boot整合JWT实现企业级无状态认证方案

Spring Boot整合JWT实现企业级无状态认证方案

1. 企业级JWT认证架构设计在分布式系统架构中,无状态认证已成为现代应用的标准配置。Spring Boot结合JWT实现的无状态认证方案,完美契合微服务架构的需求。这套方案的核心价值在于:服务端无需维护会话状态,降低服务器内存压力天然…

2026/7/22 6:38:16 阅读更多 →
MotrixNext:现代化多协议下载工具架构与优化

MotrixNext:现代化多协议下载工具架构与优化

1. MotrixNext项目概述MotrixNext是一款基于Motrix深度重构的现代化下载工具,核心目标是突破传统下载器的协议限制与性能瓶颈。作为长期使用各类下载工具的老用户,我发现现有解决方案普遍存在两个痛点:一是对新协议的支持滞后(比如…

2026/7/22 6:38:16 阅读更多 →
C++组合模式实战:透明与安全模式选择及智能指针应用

C++组合模式实战:透明与安全模式选择及智能指针应用

1. 项目概述:从“组合”到“组合模式”的实践最近在社区里看到不少朋友在讨论C的“组合”实现,这个词本身有点宽泛。在编程语境下,它可能指代“组合数学”中的排列组合算法,也可能指代面向对象设计中的“组合模式”。从大家搜索的…

2026/7/22 6:37:16 阅读更多 →

最新新闻

妈妈模拟器:沉浸式体验背后的技术与应用

妈妈模拟器:沉浸式体验背后的技术与应用

1. 项目概述:妈妈模拟器的核心价值与设计理念怀孕分娩育儿模拟器(俗称"妈妈模拟器")是近年来在年轻女性群体中悄然流行的一种沉浸式体验工具。这个看似简单的概念背后,其实融合了生理学模拟、心理学设计和生活场景还原三…

2026/7/22 7:25:36 阅读更多 →
RLHF技术解析:让AI对话更自然的强化学习方法

RLHF技术解析:让AI对话更自然的强化学习方法

1. 为什么RLHF值得每个程序员关注?上周帮团队调试一个客服聊天机器人时,遇到个典型场景:用户问"套餐到期怎么办",模型机械地列出操作步骤,而人工客服会补句"别担心,我帮您延期"。这个细…

2026/7/22 7:25:36 阅读更多 →
VggNet模型C++部署实战:基于OnnxRuntime的完整流程与优化

VggNet模型C++部署实战:基于OnnxRuntime的完整流程与优化

1. 项目概述:从模型到应用的最后一步在深度学习的实际落地过程中,我们常常会遇到一个“最后一公里”的难题:实验室里训练好的、在Python环境下跑得飞起的模型,如何变成一个能在生产环境中稳定、高效运行的独立应用?特别…

2026/7/22 7:25:36 阅读更多 →
课题立项不看论文!评审只卡这 2 条标准

课题立项不看论文!评审只卡这 2 条标准

同样是申报社科基金,有人轻松立项,有人年年陪跑反复落选,很多人以为差距全在论文数量,其实评审判断课题能不能过,根本不靠发了多少文章,核心只看两大评判标准,吃透这两点,申报通过率…

2026/7/22 7:25:36 阅读更多 →
C++实现MACD三大进阶策略:平滑、量价、趋势过滤与量化实战

C++实现MACD三大进阶策略:平滑、量价、趋势过滤与量化实战

1. 项目概述:从公式到代码的进阶之路如果你在通达信里用过MACD,大概率会觉得它有点“钝”。金叉死叉信号滞后,震荡市里反复打脸,这都是经典MACD的老毛病。但你可能不知道,在专业交易员和量化开发者手里,MAC…

2026/7/22 7:25:36 阅读更多 →
WorkshopDL:无Steam客户端跨平台下载创意工坊模组实战指南

WorkshopDL:无Steam客户端跨平台下载创意工坊模组实战指南

1. 项目概述:当创意工坊遇上“无客户端”时代 如果你是一名资深游戏玩家,或者像我一样,经常在Linux、Mac甚至是一些没有安装Steam客户端的设备上折腾,那么你一定遇到过这个痛点:看到社区里分享的一个绝妙的《幻兽帕鲁…

2026/7/22 7:24:36 阅读更多 →

日新闻

TI DSP系统配置模块SYSCFG详解:中断机制与主设备优先级配置实战

TI DSP系统配置模块SYSCFG详解:中断机制与主设备优先级配置实战

1. 项目概述与SYSCFG模块的核心价值在嵌入式系统,尤其是像TI C6000系列这样的高性能DSP开发中,我们常常会与芯片手册里那些密密麻麻的寄存器打交道。很多开发者可能更关注算法实现、内存优化或者外设驱动,但对于一个稳定、高效的系统而言&…

2026/7/22 0:00:26 阅读更多 →
微信Server酱:高到达率的应急通知方案实践

微信Server酱:高到达率的应急通知方案实践

1. 为什么我们需要"最次"的通知方案? 在数字化协作环境中,消息通知系统的重要性不言而喻明。但现实情况是,企业级通知方案往往需要复杂的API对接(如企业微信、钉钉、飞书),个人开发者的小项目又经…

2026/7/22 0:00:26 阅读更多 →
甲方要的“简洁“PPT,到底是简洁还是省事?

甲方要的“简洁“PPT,到底是简洁还是省事?

甲方说"简洁一点",乙方听到的是"少做几页"。甲方说"不要太复杂",乙方理解成"别放图表了"。结果交过去,甲方说"我说的简洁不是这个意思"。"简洁"这个词在PPT语境里,是…

2026/7/22 0:00:26 阅读更多 →

周新闻

Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/21 8:48:31 阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/21 5:34:47 阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/21 8:25:39 阅读更多 →

月新闻