首先列上參考的資料來源
算是最全的FC相關資料的網站,能在這里找到所有的FC硬件信息以及邏輯(個人認為查找信息可以,但是不適合用來上手,信息過於詳細)
我能找到的最全、最詳細的中文模擬器開發日志,本文基於此,對其中沒有詳細說的細節或者需要注意的地方進行補充
-
FC模擬器字節順序
FC模擬器使用的CPU,8位2A03 NMOS處理器,是6502的改版,該CPU雖然是8位,但是地址空間是16位,所以有字節序概念,且字節序是小端模式。
該字節順序主要影響rom文件的讀取,以及CPU指令讀取16bit地址。
-
PC寄存器啟動以及重置時的狀態
從CPU地址的0xFFFC讀取低字節,0xFFFD讀取高字節,組成16bit地址,存入PC寄存器中。
其他的寄存器初始狀態可以從wiki中找到。
-
CPU指令執行需要的CPU周期數
在初期開發的時候,就要預留好統計各指令執行所需要的CPU周期數,用於各模塊執行的同步。
-
圖像渲染模塊(PPU)上手
FC主要適應兩種顯示類型,PAL和NTSC,其中NTSC的顯示頻率約等於60幀,建議按照NTSC做。
FC的圖像繪制單元是像素,繪制順序是一行一行,一個坐標點一個坐標點的畫。
PPU中有一個包含64個顏色的寫死在硬件中的調色板,不同硬件類型的FC,顏色上的差異,主要就是由此產生的。
PPU的地址0x3F00-0x3F1F中存儲的,就是上面提到的調色板的索引值。程序是可以修改這段地址中的值,從而改變屏幕上的顏色的。其中低16字節用於背景,高16字節用於精靈。
背景繪制大體流程:
- 當前屏幕xy坐標+屏幕滾動偏移坐標---->Nametable中對應位置的值(即Pattern table的索引)---->Pattern table中數據組合獲取當前坐標的顏色信息低兩位。
- 當前屏幕xy坐標+屏幕滾動偏移坐標---->上一步Nametable后緊隨的Attribute table對應位置的值---->獲取當前坐標的顏色信息高兩位
- 上面兩步組合成4bit數據---->PPU地址0x3F00-0x3F0F背景調色板---->PPU中固定的64個RGB顏色,繪制像素點
復雜點說,是根據當前像素的xy坐標加上屏幕滾動信息(最開始實現時可不考慮滾動),從Nametable中獲取8*8圖塊、即Pattern table的索引,然后根據Pattern table中的信息,計算出8*8圖塊中每個像素調色板索引的索引的低兩位。另外再從緊隨Nametable的Attribute table中,取得該8*8圖塊所有像素的調色板索引的索引高兩位。將這些信息對應組合,獲取8*8圖塊每個像素的4bit調色板索引的索引,從上面提到的低16字節獲取調色板索引,再獲取調色板實際的RGB信息,最終繪制在屏幕上。
精靈繪制大體流程:
- 從oam中取出4個字節,獲取精靈左上角xy坐標
- Byte 1中獲取Pattern table的索引---->Pattern table數據組合獲取顏色信息低兩位
- Byte 2中獲取顏色信息高兩位
- 上面兩步組合成4bit數據---->PPU地址0x3F10-0x3F1F精靈調色板---->PPU中固定的64個RGB顏色,繪制像素點
復雜點說(以8*8精靈為例),首先從oam中確認當前精靈的xy坐標,然后再根據PPUCTRL中bit3確認Pattern tables,oam的Byte 1確認Pattern tables的下標,獲取精靈的全部8*8像素調色板索引的索引低兩位,再通過oam的Byte 2低兩位,獲取像素調色板索引的索引高兩位。這樣就和背景一樣,獲取到4比特調色板索引的索引,再去上面提到的高16字節,獲取調色板索引值,最終獲取RGB信息。
繪制精靈時,如果調色板索引的索引低兩位都是0,則表示該像素點透明。
在實現屏幕滾動(scrolling)時,最好按照wiki中描述的那樣,實現PPU內部的寄存器,vtxw,這樣方便做地址映射。
初期最好以行為單位繪制圖像,每畫完一行,就執行341.5個CPU時鍾周期,這就是簡單的時鍾同步。最精准的時鍾同步是,每畫一個像素點,就執行3個CPU時鍾周期。
畫完240行之后,再執行341.5個PPU時鍾周期,置vblank為1,再根據PPUCTRL中的bit7確認是否執行NMI中斷,然后執行341.5*20個時鍾周期,清除vblank標志,最后再執行一次341.5個(奇數幀340.5個)時鍾周期。這樣就完成一整幀的工作,即wiki中說的0-261行共262行掃描。
-
APU聲音模塊
如果使用SDL作為底層實現,需要根據wiki上面寫的方式,通過查表或者公式計算進行混音,得到的結果是浮點數,
APU中使用最多的一個基本模塊,就是divider,他的最重要的特點,當觸發他的時候,它先檢查當前值是否為0,為0則發出信號,不為0則遞減1。所以wiki中會說,設置divider的值為P,實際上它輸出信號的周期為P+1。這個是永遠都不會變的,在查看wiki的時候,如果他寫的是設置周期為P+1,我們在實現的時候一定要記住,保存的變量為P。我個人就是被wiki中sweep的描述誤導,方波聲音總是不對,再加上查看了好幾個模擬器的實現代碼都是設置+1,可是時鍾發信號的地方都是1變0就發出了。
三角波靜音的時候,是持續輸出最后一個值,而不是輸出0,否則會聽到很清晰的“BABA”爆破音。
最后是建議使用blip_buffer庫作為混音庫,該庫是wiki上最初提供APU信息的人編寫的,這是地址http://www.slack.net/~ant/