在自定義 RISC-V 模擬器上執行 DOOM

在幾乎任何運算設備上執行 DOOM 已成為硬體與軟體開發領域中一個備受讚譽的基準。從智慧手錶到驗孕棒,這款經典遊戲證明了開發者的巧思以及現代運算的靈活性。隨著最近一個展示在自定義構建的 RISC-V 模擬器上執行 DOOM 的專案,這項傳統得以延續,展現了開源指令集架構的力量與易用性。

這個專案不僅實現了流行的迷因,還為模擬器開發、系統呼叫(syscall)實作以及將應用程式移植到全新且極簡環境中的挑戰提供了寶貴的見解。這是一段從簡單的「hello world」到完全可玩的遊戲的旅程,說明了從零開始構建功能性運算平台所需的基礎步驟。

構建 RISC-V 模擬器

這項工作的核心是一個 RISC-V 模擬器 rvcore,旨在實作 RV32IM 指令集。選擇此指令集代表著對精簡且功能強大的架構之關注,非常適合嵌入式系統與教育專案。該模擬器提供了一個極簡的 syscall 介面,這對於任何程式與底層虛擬硬體進行互動(處理如輸入/輸出與記憶體管理等操作)至關重要。

ELF 載入與記憶體管理

為了執行編譯後的程式,模擬器整合了 ELF (Executable and Linkable Format) 載入功能。目前,它支援單個 PT_LOAD 段,在簡化初始實作的同時,對於許多應用程式而言仍已足夠。專案採用了自定義的連結器腳本(linker script),以確保 C 程式的進入點 _start 始終位於虛擬位址 0x000000,進一步簡化了 ELF 載入器的設計。

記憶體組織是任何系統的關鍵面向,而 rvcore 模擬器定義了一個特定的記憶體映射(memory map)以配合 DOOM:

0x7FFFFF +-------------------------------------+
         |                                     |
         | QUEUE_SIZE (32 bytes)               |
         |                                     |
0x7FFFDF +-------------------------------------+ <-- QUEUE_START
0x7FFFDE | QUEUE_READ_IDX                      |
0x7FFFDD | QUEUE_WRITE_IDX                     |
         +-------------------------------------+
         |                                     |
         |                                     |
         | VRAM (1,024,000 bytes)              |
         |                                     |
         |                                     |
0x705FDD +-------------------------------------+ <-- STACK_START
         | Stack                               |
         |                                     |
         | v                                   |
         |                                     |
         | ^                                   |
         |                                     |
         | Program data + Heap                 |
         |                                     |
0x000000 +-------------------------------------+

此佈局將大部分空間分配給從 0x705FDD 開始的 VRAM (1,024,000 bytes),DOOM 在此渲染其影格。記憶體頂部的微小佇列區域用於處理輸入,並配有專用的讀取與寫入索引。

移植 DOOM:循序漸進的方法

執行 DOOM 的旅程始於較簡單的任務,並逐漸建立起必要的基礎設施。

從「Hello World」到 C 程式

最初,模擬器是使用基礎的「hello world」組合語言程式進行測試。下一個重大障礙是實現 C 程式的執行。這是透過整合 newlib(一種針對嵌入式系統優化的 C 標準函式庫)來實現的。newlib 允許開發者逐步實作 syscall stub,為底層環境提供必要的函式,直到目標程式可以成功執行。

利用 doomgeneric

為了簡化移植過程,該專案利用了 doomgeneric。這個專案為 DOOM 提供了一個極簡且可移植的層,抽象化了平台特定的細節,使得在基礎 syscall 與渲染機制就緒後,更容易讓遊戲運行起來。有了 doomgeneric,主要任務就變成了實作與模擬器虛擬硬體互動所需的 stub。

輸入與輸出處理

DOOM 將其輸出渲染至記憶體中的固定區域,特別是從 0x705FDD 開始的 VRAM 區域。模擬器負責將此記憶體區域解釋為顯示輸出。輸入(例如鍵盤指令)則透過將其寫入記憶體中指定的佇列中來處理。在模擬器中運行的 DOOM 實例隨後會攔截並處理這些輸入,從而完成互動式迴圈。

DOOM 移植的持久遺產

像這樣的 RISC-V 模擬器執行 DOOM 的專案在技術社群中引起了深遠的共鳴。這些專案通常會受到熱烈歡迎,因為它們體現了探索與技術精通的精神。正如一位 Hacker News 使用者 @patrickhener 所說:

There is nothing, that can not run DOOM. I love those type of projects. Thanks for showing.

這種情緒反映了移植 DOOM 的文化意義,這已演變成許多開發者探索新硬體或軟體平台時的一種「成年禮」。這是一個強大的系統能力展示,也是對遊戲本身持久設計的證明。另一位使用者 @ani_k47 則簡單地表示:「Things like this are really cool,」總結了人們對此類具備創意與挑戰性的嘗試的廣泛讚賞。

這個 RISC-V DOOM 模擬器是一個極佳的範例,展示了透過開源硬體架構與專注的開發可以達成什麼成就。它不僅為經典遊戲提供了一個功能性平台,還為對系統模擬與應用程式移植的底層層面感興趣的人提供了教育資源。 ",

Sources