当软件工程师 Ben C. 在 2024 年 Factorio 2.0 发布后意识到可以用 Verilog 描述游戏中的组合逻辑电路时,一个独特的编译器项目就此诞生。这个名为 Verilog-to-Factorio 的工具链不仅能够将简单的数字逻辑编译为 Factorio 蓝图,更实现了将完整的 RISC-V CPU 嵌入游戏世界的壮举。本文将从编译器工程的视角,剖析从 Verilog 到 Factorio 的完整编译流程,探讨前端解析、中间表示转换与后端信号映射的工程实现细节。

编译器架构总览

Verilog-to-Factorio 编译器采用两阶段架构设计,这一设计选择体现了对硬件描述语言特性和游戏电路特性的双重适配。第一阶段是 Verilog 前端编译器,负责将硬件描述语言转换为逻辑图结构;第二阶段使用 Rust 实现,负责将中间表示转换为 Factorio 可用的蓝图格式。这种架构的优势在于将语言处理与目标平台映射清晰分离,使得前端可以专注于 Verilog 语义解析,而后端则专注于 Factorio 组合逻辑的物理布局。

从技术栈角度看,该项目选择 Rust 作为主要实现语言并非偶然。Rust 的所有权系统和零成本抽象为编译器开发提供了内存安全保证,同时其高性能特性确保了大规模电路设计的处理效率。Ben C. 在项目阐述中提到,最初只是为了学习 Rust 和编译器相关知识,但项目逐渐演变成一个包含 Lua 脚本、基于节点的可视化界面、组合逻辑模拟器、图划分算法、SVG 生成、整数线性规划求解器以及超参数优化的综合性软件实验。这种演进路径反映了硬件编译流程本身的复杂性 —— 从语法分析到物理布局,每个环节都需要专门的算法支持。

在输入输出层面,编译器的输入是标准 Verilog 硬件描述文件,输出是纯 vanilla Factorio 蓝图。这意味着编译结果可以直接导入游戏而无需任何模组支持,大大降低了使用门槛。Factorio 2.0 的发布为这一项目提供了关键的底层支持,新版本对组合逻辑电路的改进使得更复杂的硬件设计得以在游戏中实现。

前端解析:Verilog 到逻辑图

编译器的前端核心目标是解析 Verilog 代码并生成可被 Factorio 理解的逻辑图表示。这一过程借鉴了成熟硬件编译器的工作流程,但针对游戏环境进行了适配。Yosys 开源综合工具在这一环节扮演了关键角色 —— 它提供了工业级的 Verilog 前端和 RTLIL(RTL Intermediate Language)中间表示。

Verilog 前端的处理流程包含预处理、词法分析、语法解析以及抽象语法树构建等标准编译步骤。Yosys 的 Verilog 前端能够处理常见的硬件描述语法,包括模块定义、端口声明、线网类型、寄存器定义以及 always 块中的组合逻辑和时序逻辑描述。在语法分析完成后,Verilog 代码被降低为 RTLIL 表示,这是硬件编译器中常见的中间形式,它保留了电路的结构化信息同时屏蔽了语法细节。

RTLIL 作为中间表示的优势在于其统一的结构化表示能力。在 RTLIL 层面,硬件设计被建模为模块(Module)、单元(Cell)和线网(Wire)的层次化图结构。每个单元代表一个逻辑原语或用户定义模块实例,线网则描述了单元之间的连接关系。这种图表示非常适合后续的优化和转换步骤,因为算法可以直接在结构化数据上运作。

从前端到中端的过渡涉及一个关键转换:RTLIL 需要被序列化为 JSON 格式以便 Rust 程序读取。JSON 作为一种与语言无关的序列化格式,在这个桥接环节发挥了重要作用,因为它允许前端使用成熟的 C++ 编写的 Yosys,而后端可以使用纯 Rust 实现,彼此之间通过标准数据交换格式解耦。这种双语言架构在编译器设计中并不罕见,它允许每个阶段选择最适合特定任务的工具和语言。

中间表示:JSON 到 MappedDesign

JSON 格式的中间表示在发送到 Rust 后端之前,通常会经过额外的处理和优化。Rust 程序将 JSON 反序列化为内部称为 MappedDesign 的数据结构。这一步骤不仅仅是简单的数据转换,还包括对电路表示的调整,使其更适合 Factorio 物理实现的约束条件。

MappedDesign 保留了原始设计的层次化结构,但针对游戏中的组合逻辑进行了适配。在真实硬件综合中,设计会被降低到门级(gate-level)表示,包括与门、或门、非门以及触发器等基本单元。然而,Factorio 的组合逻辑系统是基于组合器(Combinator)和信号网络的,这两者之间存在显著的语义差异。硬件综合关注的是逻辑等价性和时序特性,而 Factorio 的组合逻辑更关注信号传播和数据流的组织方式。

图划分(Graph Partitioning)是这一阶段的重要算法任务。原始电路图需要被分解为多个可映射到 Factorio 建设区域的子图。每个子图代表一个物理上可部署的电路单元,同时需要最小化跨区域连接的数量,因为跨区域连接需要使用信号塔(Signal Tower)或线缆等额外设施。Ben C. 在项目中采用了启发式图遍历算法来优化线缆使用,同时确保统一的电力分配网络。实践中,即使是 RISC-V 核心这样复杂的设计,也只需要手动放置少量变电站(Substation)即可实现完整覆盖。

值得注意的是,Factorio 的电力系统为电路布局带来了额外的设计约束。游戏中的电力网络和信号网络是独立的,这要求编译器在布局时既要考虑逻辑连通性,也要考虑电力供应。解决方案是利用游戏中的电力杆(Power Pole)同时传输电力和信号,编译器需要计算每个组合器的电力覆盖范围,在必要时放置新的电力杆以确保所有用电设施都处于供电网络内。

后端信号映射:逻辑图到 Factorio 组合器

后端阶段是将中间表示转换为 Factorio 可执行蓝图的核心环节。Factorio 的组合逻辑系统提供多种基本单元:常数组合器(Constant Combinator)用于生成恒定信号,常绿组合器(Decider Combinator)用于条件判断和比较,运算组合器(Arithmetic Combinator)用于算术运算。这些基本单元的组合可以表达任意布尔函数和有限状态机。

信号映射策略决定了如何将 RTLIL 中的逻辑原语转换为 Factorio 的组合器网络。对于简单的组合逻辑,直接映射是可行的 —— 每个与门、或门等可以转换为对应的组合器网络。然而,这种朴素方法的效率极低,因为 Factorio 的组合器延迟(每个组合器一个游戏刻)和信号传播方式与真实硬件有本质区别。更有效的方法是识别电路中的数据通路和控制流,将高频使用的功能模块化为可复用的组合器组。

RISC-V CPU 的实现充分展示了这一映射策略的复杂性。一个完整的 RV32I 处理器包含取指(Fetch)、译码(Decode)、执行(Execute)、访存(Memory Access)和写回(Write-back)五个流水线阶段,每个阶段都涉及大量的组合逻辑和时序元件。将这样的设计映射到 Factorio 需要仔细考虑游戏的物理约束:游戏中的大型建筑需要通过建设规划器(Blueprint Book)组织,每个建筑网格单元(Tile)的尺寸限制了单个模块的规模,信号延迟随线路长度累积,这一切都与真实硬件综合形成鲜明对比。

实际实现中,RISC-V 核心的 Factorio 版本需要处理几个关键适配。首先是指令存储和取指 ——Factorio 的存储单元容量有限,通常需要使用火车存储系统或分布式存储网络来模拟内存层次。其次是流水线控制 ——Factorio 的时钟机制与真实硬件不同,需要设计替代方案来实现流水线停顿和转发。最后是外设交互 ——RISC-V 需要与 Factorio 的输入输出系统对接,这涉及游戏内部信号与处理器内存空间的映射。

工程实践参数与优化建议

基于该项目的工程实践经验,可以总结出若干可操作的参数和建议。在编译器配置层面,建议使用 Yosys 的综合选项进行适当的层次展平(flattening),这有助于减少跨层次连接的复杂性,但需要权衡综合时间与优化效果。对于大型设计,保留一定的层次结构可以便于调试和模块化布局。

在物理布局层面,Factorio 的网格系统决定了最优单元尺寸。研究表明,边长为 32 到 64 格的建筑区域是较好的折中选择 —— 太大的区域会导致布局复杂度上升,太小的区域则会增加跨区域连接的开销。电力覆盖方面,每个变电站的覆盖半径约为 14 格,在布局规划时应以此为参考确保无遗漏区域。

信号完整性是另一个需要关注的工程问题。Factorio 中的长距离信号传输会引入延迟,这在时序敏感的设计中可能导致功能异常。建议使用信号放大器(Signal Amplifier)定期重建信号强度,特别是在跨区域连接处。对于高频信号路径,可以考虑使用独立的信号网络来减少竞争和干扰。

在验证层面,由于 Factorio 无法直接运行传统硬件仿真器,需要设计替代的验证策略。一种方法是在编译器中集成仿真功能,利用 Rust 编写的行为级模型验证逻辑正确性后再生成蓝图。另一种方法是在游戏中构建验证电路,通过预设输入模式并检查输出响应来确认功能正确性。Ben C. 的项目提供了在线模拟器和可视化界面,这大大简化了调试过程。

结论与资源

Verilog-to-Factorio 编译器代表了硬件描述语言在游戏模拟领域的有趣应用,其技术架构展示了如何将成熟的编译器工程技术与新兴的目标平台相结合。从 Verilog 前端解析到 RTLIL 中间表示,从 JSON 数据交换到 MappedDesign 的图划分优化,再到最终的 Factorio 蓝图生成,每个环节都有其独特的工程挑战和解决方案。

对于希望在类似方向进行探索的开发者,建议首先熟悉 Yosys 的工作原理和 RTLIL 数据结构,这是硬件编译器开发的良好起点。同时,深入理解 Factorio 的组合逻辑系统特性,包括各种组合器的功能、信号传播机制以及物理布局约束,将有助于设计更高效的映射算法。RISC-V 作为一个开放指令集标准,提供了丰富的开源实现可以用于测试编译流程,是验证端到端功能的理想目标。

项目源码托管于 GitHub,采用 GNU Affero 通用公共许可证开源发布,同时提供了带有图形界面的在线演示版本供 experimentation 使用。

资料来源