CHIP-8 是一種直譯式程式語言,出現於 1970 年代末期至 1980 年代初期,主要應用在 DIY 電腦系統上。本篇將藉由介紹 CHIP-8 背景以及實際規格,為之後的小型專案鋪路。
背景知識
什麼是 CHIP-8
感覺這個名字似乎會引起誤會,CHIP-8 不是 chip,它是一種簡單的直譯式程式語言,也是一套簡單的虛擬機規格,出現於 1970 年代末期至 1980 年代初期,主要應用在 DIY 電腦系統上。那個年代系統規格非常有限:
- 通常被設計成直接連接電視作為顯示螢幕
- 系統的 RAM 通常只有 1K 到 4K 的容量
- 主要的輸入裝置是 16 鍵的十六進位鍵盤
- CHIP-8 的直譯器本身僅佔用 512 bytes 的記憶體空間,而使用者以十六進位輸入的程式碼所佔用的空間甚至更小
它最早由 Joseph Weisbecker 在 1970 年代設計,用在 COSMAC VIP、Telmac 1800 這類早期 8-bit 微電腦上,目的是讓人在硬體資源很少的情況下,也能比較容易寫小遊戲或互動程式。
可以簡單想成 CHIP-8 規定了:
- 有多少記憶體
- 有哪些暫存器
- 螢幕怎麼顯示
- 鍵盤怎麼輸入
- timer 怎麼運作
- 每個 opcode 代表什麼操作
這個小型的系統架構,總共只有 34 個指令需要實作,非常適合第一次學習模擬器開發的一套系統。很多經典入門 emulator 專案會選它,因為它規格簡單,但又包含完整的電腦概念。
什麼是 Emulator(模擬器)
要了解 emulator 在做什麼,可以把它想像成一位翻譯官,它的核心任務是讀取原本專為目標機器編譯出來的機器碼指令,接著進行解譯,最後在 host machine(在這個案例中就是現代電腦)重現目標機器的所有功能。
舊遊戲的 ROM 檔案裡面裝的其實就是滿滿的原始機器指令。模擬器會讀取這些 ROM 檔案裡的指令,並且透過軟體的方式,去模仿當年那台機器的真實運作行為。
如果是開發更進階的系統,模擬器不僅要模擬 CPU,還要模擬圖形處理器與音效晶片等複雜的硬體元件。那能不能現在不要搞那麼複雜,圖形先處理單色像素,音效也處理一道單一頻率的蜂鳴聲……那就是架構非常單純的 CHIP-8 啦!
所以寫 CHIP-8 emulator,就是用現代程式語言,例如 C 或是 C++ 去實作這套規格,讓 CHIP-8 程式可以在現代電腦上跑。
CHIP-8 = 一套小型程式語言 / 虛擬機規格
CHIP-8 ROM = 用 CHIP-8 寫成的程式檔
CHIP-8 emulator = 負責執行 CHIP-8 程式檔的模擬器
CPU 簡介
在開始動手寫 emulator 之前,必須先對 CPU(中央處理器)的運作方式有一個基本的認識。
CPU 的運作原理其實非常直觀:它會不斷地從記憶體中的某個地方讀取「指令」,然後照著指令告訴它的內容去做事。可以把 CPU 想像成一個執行速度極快、但本身沒有自主思考能力的工人,必須用一組非常明確且邏輯嚴謹的指令集(Instruction Set),一步一步地告訴它該做什麼。
這些指令在電腦底層都是以數字的方式進行編碼,用來代表某種操作與對應的資料。以 CHIP-8 來說,指令通常會以十六進位(Hexadecimal,常帶有 $ 或 0x 的前綴)來表示。在處理電腦底層資訊時,十六進位是最常被使用的進位制,因為它能用更簡潔的方式來表示龐大的二進位數值與記憶體位址。
雖然不同 CPU 架構有不同的指令集與硬體設計,但大多數 CPU 執行程式時,都可以簡化成三個主要步驟:
- 提取指令 Fetch:從記憶體取出下一條指令
- 解碼指令 Decode:解析這條指令代表什麼操作
- 執行指令 Execute:執行該指令,並更新暫存器、記憶體或其他系統狀態
CPU 都在不斷重複三個核心動作,而撰寫模擬器的核心任務,就是用軟體程式碼完美重現這個無窮迴圈。CHIP-8 emulator 的核心也會圍繞這個流程實作。每一次執行循環中,emulator 會從 CHIP-8 記憶體取出 opcode,判斷它是哪一種指令,然後執行對應的行為。
從這個專案起頭的原因
要做一件事情之前一定要搞清楚目的,為什麼選擇做 CHIP-8 emulator 主要有兩個原因:
現代電腦看不懂 CHIP-8 程式
CHIP-8 遊戲檔裡面不是 Windows / macOS / Linux 可以直接執行的指令。
例如 CHIP-8 程式可能有一條指令意思是:把 0x05 放進 V1 暫存器,但我現在手邊的機器 CPU 不知道什麼是 V1,也不知道 CHIP-8 的螢幕、鍵盤、timer 等規則,所以需要一個 CHIP-8 emulator 來做翻譯,轉換成現代電腦上的行為,讓 CHIP-8 程式可以在現代電腦上面跑。
Emulator / CPU 基礎練手
這其實才是最最最主要的目的:CHIP-8 很小,所以很適合當入門專案。
從讀取程式放進 memory、fetch、decode、執行指令,甚至於更新畫面、鍵盤、timer 等等,這就是很多 CPU、VM、emulator 的核心流程。所以初學者很適合從 CHIP-8 emulator 著手,主要不是因為實用度,而是因為它剛好夠小又包含完整概念。
用軟體開發角度看,CHIP-8 emulator 很適合練:
- instruction decoding
- memory model
- fetch-decode-execute cycle
- graphics rendering
- keyboard input
- timers
- emulator architecture
至少我會因為這個專案學會一台電腦怎麼執行指令。
認識 CHIP-8 規格
接下來呢,就要來解讀規格,這樣才可以知道 emulator 該怎麼製作。我將會以經典的《Cowgod’s Chip-8 Technical Reference》這份規格書作為藍圖。
Memory
首先需要了解這台虛擬機器有多少記憶體以及如何分區。
CHIP-8 具有 4KB(4,096 bytes)的記憶體,位址範圍從 0x000 到 0xFFF:
- 系統保留區(
0x000–0x1FF)
這前 512 bytes 原本是保留給早期的直譯器使用的。但在現代模擬器中,這裡通常用來存放0到F的十六進位字型資料(Font Data)。 - 程式存放區(
0x200–0xFFF)
大多數的 Chip-8 遊戲或程式,都會從0x200這個位址開始載入並執行。
+---------------+= 0xFFF (4095) End of Chip-8 RAM
| |
| |
| |
| |
| |
| 0x200 to 0xFFF|
| Chip-8 |
| Program / Data|
| Space |
| |
| |
| |
+- - - - - - - -+= 0x600 (1536) Start of ETI 660 Chip-8 programs
| |
| |
| |
+---------------+= 0x200 (512) Start of most Chip-8 programs
| 0x000 to 0x1FF|
| Reserved for |
| interpreter |
+---------------+= 0x000 (0) Start of Chip-8 RAM
Registers
CPU 在進行運算時,需要有地方暫時存放資料,這就是暫存器的功用。CHIP-8 系統中定義了幾種不同用途的暫存器:
- 一般用途暫存器(V0 – VF)
CHIP-8 共有 16 個 8-bit 的暫存器,通常被標示為V0到VF。其中,VF是一個非常特殊的暫存器,一般程式不應該隨意使用它,因為某些特定的指令會把它當作「旗標(Flag)」來使用,例如用來標記加減法是否發生進位(Carry Flag、Borrow Flag)、像素是否產生碰撞(Collision Flag)等。 - 索引暫存器(I)
這是一個 16-bit 的特殊暫存器。由於 CHIP-8 的最大記憶體位址到了0xFFF,超出了 8-bit 暫存器能儲存的上限,因此必須使用這個 16-bit 的暫存器來專門儲存記憶體位址(通常用來指向繪圖時需要的圖形資料位置)。 - 程式計數器(Program Counter, PC)
這是一個 16-bit 的指標,CPU 必須依靠它來記錄「下一步要從哪個記憶體位址讀取指令」。 - 堆疊(Stack)與堆疊指標(Stack Pointer, SP)
當程式執行「呼叫副程式」的指令時,CPU 必須記住原本執行的位置以便未來返回。CHIP-8 具備了 16 層的堆疊,代表它可以處理最多 16 層的副程式呼叫。同時,它需要一個 8-bit 的堆疊指標來記錄目前堆疊陣列使用到了哪一層(最頂端在哪裡)。
Keyboard
原本執行 CHIP-8 語言的早期電腦,使用的是一種 16 鍵的十六進位鍵盤。它的原始佈局長這樣:
| 1 | 2 | 3 | C |
| 4 | 5 | 6 | D |
| 7 | 8 | 9 | E |
| A | 0 | B | F |
在現代開發模擬器時,就必須將這個 16 宮格的硬體鍵盤,對應到現代電腦鍵盤的按鍵上。
Display
原始的 CHIP-8 語言是透過一台解析度為 64×32 像素的單色顯示器來呈現畫面。它的座標系統從左上角開始:
| (0,0) | … | (63,0) |
| … | … | … |
| (0,31) | … | (63,31) |
關於畫面的顯示,規格書有幾個重要的定義:
- 精靈圖(Sprites)
CHIP-8 是透過把精靈圖畫到螢幕上來顯示圖像。精靈圖本質上是一段二進位資料(1 代表亮,0 代表暗),一般的精靈圖最大可以達到 8×15 像素(15 bytes)。 - 內建字型
系統本身內建了0到F共 16 個十六進位數字的精靈圖,每個字型固定為 8×5 像素(5 bytes)。這些字型的資料就存放在剛剛提到的「記憶體系統保留區」中。 - XOR 繪圖邏輯
這是 CHIP-8 繪圖最特別的地方。當新圖形被畫到螢幕上時,會與螢幕現有的像素進行「互斥或(XOR)」運算。也就是說,如果畫上去的新像素與原本畫面上的亮點重疊,該像素就會反白變暗(關閉)。如果這導致了畫面上有任何像素被關閉,剛剛提到的VF暫存器就會被設定為 1,遊戲就是靠這個機制來判斷物體是否發生了碰撞。
Timers & Sound
CHIP-8 提供了兩個獨立的 8-bit 計時器來控制遊戲節奏與發出聲音:
- 延遲計時器(Delay Timer)
這個計時器負責控制時間。只要它的數值不為 0,它就會以 60Hz 的速率自動遞減,直到歸零為止。 - 音效計時器(Sound Timer)
運作的原理與延遲計時器一模一樣,也是以 60Hz 的速率遞減。唯一的差別在於,只要音效計時器的數值大於 0,CHIP-8 就會控制蜂鳴器發出單一頻率的嗶聲,直到數值歸零聲音才會停止。
後記
之前因為工作花了不少心思在硬體概念上,買了不少書、聽了不少線上課程都一知半解,結果透過經典案例卻學更多更快,更能融會貫通。這個 CHIP-8 emulator 的例子也是,因為要認識軟體的 emulator 製作概念,透過大家的熱心分享以及專案的開放,反而先理解了所謂 CPU 基礎架構,進而貫通了整個運作方式。
在我學習硬體概念的這條路上反而是因為軟體的開放性而被解救(或是西方文化?),果然還是比較喜歡軟體圈的開放氛圍。






