将硬件描述语言直接编译为游戏内的逻辑电路 —— 这一看似不可能的任务已被 Ben C. 实现。通过构建一条从 Verilog 到 Factorio 蓝图的两阶段编译管道,研究者成功将完整的 32 位 RISC-V CPU 带入这款工业模拟经营游戏。本文深入剖析该管道的架构设计、关键转换策略以及工程实现中的可调参数,为硬件到游戏引擎的跨领域编译提供可复用的技术参考。

编译管道总体架构

整个编译流程采用前端与后端分离的两阶段设计,这一设计选择并非偶然,而是基于对转换复杂度的深刻考量。前端负责将 Verilog 源代码转换为与目标无关的逻辑图表示,后端则专注于将这种抽象表示映射为 Factorio 特有的 combinator 实体网络。这种分层架构的核心优势在于分离了语言解析的复杂度与物理实现的约束,使得前端可以专注于语义分析的准确性,后端则可以灵活应对游戏引擎的各种限制条件。

前端编译器接收标准 Verilog 代码,通过词法分析、语法解析生成抽象语法树,随后进行语义检查并输出逻辑网表。这一阶段的输出并非直接面向 Factorio,而是生成一种更为通用的中间表示。研究者在项目中采用了 Yosys 作为前端工具链的核心,Yosys 是一款开源的 Verilog 综合工具,能够将 Verilog 代码综合为门级网表并输出 JSON 格式的中间数据。这种设计使得编译器可以利用 Yosys 成熟的综合优化 passes,同时将转换逻辑聚焦于如何将综合后的逻辑网络映射到游戏中的逻辑元件。

后端完全使用 Rust 实现,这一语言选择并非随意。Rust 的所有权模型和内存安全性对于处理大规模图结构数据尤为合适,而高性能计算需求也要求编译器具备高效的执行效率。后端读取 Yosys 输出的 JSON 格式中间表示,转换为项目内部定义的 MappedDesign 数据结构,这一结构保留了逻辑门、信号连接等核心信息,同时添加了面向 Factorio 映射的元数据。

Verilog 到逻辑图的转换机制

在 Verilog 前端处理中,最关键的挑战在于如何将硬件描述语言中高度抽象的并行描述转换为可以映射到离散逻辑元件的结构。Factorio 的 combinator 系统本质上是一种离散事件驱动的逻辑网络,每个 combinator 在游戏 tick 发生时计算输出,这与 Verilog 的仿真语义存在本质差异。编译器需要处理这种语义鸿沟,将连续的电平信号理解为在每个 tick 上稳定的状态。

转换过程首先通过 Yosys 进行综合,综合阶段会执行常数传播、公共子表达式消除、寄存器平衡等优化。这些优化不仅能降低最终生成的逻辑复杂度,还能消除 Verilog 中某些不便直接映射的结构,例如多维数组访问、复杂的算术运算符链等。综合后的网表包含基本逻辑门、D 触发器、多路选择器等原始元件,这些是 Factorio combinator 可以直接表达的基本构件。

在这个阶段,细粒度的位级逻辑与粗粒度的字级表示共存。位级逻辑对应于 Factorio 中的单信号线路,代表单个比特的运算结果;字级表示则对应于多比特宽度的总线,在游戏中通过多个 combinator 协同工作来实现。编译器需要分析哪些逻辑适合保持细粒度以获得更高的并行度,哪些逻辑应当聚合为字级操作以减少 combinator 数量,这种决策直接影响最终蓝图的可读性和运行效率。

Factorio combinator 映射策略

将逻辑网表映射到 Factorio 实体是整个管道中最具工程挑战性的环节。Factorio 提供了多种类型的 combinator,包括常量 combinator、算术 combinator、逻辑 combinator 以及选择器 combinator,每种 combinator 都有其特定的输入输出语义和信号编码规则。编译器需要为网表中的每个逻辑元件分配合适的 combinator 类型,并通过线路连接建立信号传播路径。

算术 combinator 是实现算术逻辑单元的核心,它支持加法、减法、乘法、除法、移位等基本操作。值得注意的是,Factorio 的算术 combinator 对操作数宽度有限制,单个 combinator 只能处理 32 位以内的数据,因此对于 32 位 RISC-V CPU 的 ALU,需要多个 combinator 级联实现完整的算术逻辑。编译器在映射时会自动分析所需操作的位宽,并将超出限制的运算拆分为多个子操作。

逻辑 combinator 处理布尔运算,包括与、或、非、异或等。对于 RISC-V CPU 中的控制单元和条件判断逻辑,这些 combinator 构成了状态机实现的基础。状态机在 Factorio 中的典型实现方式是将当前状态编码为信号值,通过组合逻辑计算下一状态,这种实现方式与硬件描述语言中的状态机描述具有良好的对应关系。

电力分配与布线算法

将数百个 combinator 组装为功能完整的 CPU 蓝图,电力分配是首要解决的物理问题。Factorio 中的电力网络通过电力塔和变电站覆盖,每个 combinator 必须位于某个电力覆盖范围内,否则将无法正常运行。编译器采用了覆盖率最大化的启发式算法:首先检测现有电力覆盖范围外的 combinator,在缺失区域放置新的变电站以最大化覆盖面积,然后通过图遍历算法优化铜线使用数量,确保形成单一连通的电力分配网络。

对于一个典型的 RISC-V 核心,编译器通常只需要手动调整少量变电站位置即可达到 100% 覆盖率,这验证了自动电力分配算法的有效性。布线的复杂度远高于电力分配,因为需要考虑信号完整性、延迟平衡以及游戏中的线路容量限制。Factorio 的线路系统支持每条线路携带多个不同信号,这是通过信号类型区分实现的,编译器利用这一特性将多比特总线编码为同一线路上的多个信号。

信号完整性在游戏中的表现与硬件世界截然不同。由于 Factorio 的信号传递是 tick 同步的,不存在真正的异步传输,但线路长度会影响信号从输入到输出的延迟时间。较长的线路意味着信号需要经过更多中继 combinator 才能可靠传输,这间接影响了处理器的最高工作频率。编译器在布局阶段会尽量缩短关键信号路径,但对于某些必须跨越较大区域的信号,会自动插入缓冲 combinator 以确保信号完整性。

可配置参数与优化空间

编译管道提供了多个可调参数以适应不同的设计需求。综合优化级别是最直接的影响因素,较高的优化级别会执行更激进的资源共享和逻辑重构,这可以显著减少 combinator 数量,但可能增加关键路径延迟。对于追求最小面积的 CPU 实现,建议启用全部优化 passes;对于需要最高运行速度的设计,可以降低优化级别以减少逻辑深度。

图分区参数决定了逻辑如何在空间上组织为独立的蓝图书签订。较大的分区会生成更紧凑的蓝图,但可能导致单张蓝图过大而难以在游戏中加载;较小的分区则便于管理但会增加跨分区信号的开销。RISC-V CPU 的典型分区策略是将取指单元、译码单元、执行单元、访存单元分别放置在独立的蓝图中,通过全局信号进行协调。

布局算法也提供了可调参数,包括 combinator 间距、最小线路长度约束、转弯惩罚权重等。这些参数影响最终蓝图的美观度和可维护性,对于教学展示目的的设计,建议增大间距参数以提高可读性;对于追求极限集成度的应用,可以采用紧凑布局以减少总面积。

工程验证与局限分析

该编译管道已成功生成可在 Factorio 2.0 环境中运行的实际 RISC-V CPU,包括完整的 32 位整数指令集支持、流水线结构以及基本的内存访问功能。在游戏中的实际运行验证表明,编译生成的 CPU 能够正确执行简单的程序,例如循环计数、斐波那契数列计算等基准测试。

然而,该方法也存在明显的局限。首先是性能瓶颈:Factorio 的逻辑计算以游戏 tick 为单位,单个 tick 的时间固定且无法精确控制,这导致模拟 CPU 的时钟频率远低于真实硬件。其次是资源消耗:即使经过优化,一个完整的 RISC-V 核心也需要数千个 combinator,这对于大规模程序运行仍然是挑战。最后是可观测性:游戏中的调试手段有限,缺乏硬件仿真器级别的波形显示能力,这使得设计验证主要依赖功能正确性而非时序分析。

从编译器工程的视角审视,这条管道展示了领域特定编译器设计的典型挑战:将抽象的计算描述映射到具有独特约束的目标平台。Factorio combinator 的同步执行模型、有限的操作类型、信号编码规则共同构成了目标平台的规范,编译器需要在这组约束下寻找可行的映射方案。对于从事硬件加速器设计或领域特定编译器的工程师而言,这种跨领域的编译映射经验具有重要的参考价值。


资料来源:Hackster News 报道的 Ben C. 项目实现分析。