跳轉到內容

超級任天堂程式設計/DMA 教程

來自華夏公益教科書

什麼是 DMA,為什麼我們需要它

[編輯 | 編輯原始碼]

超級任天堂(除了慢速記憶體訪問時間之外)的主要限制之一是主處理器。正如 記憶體對映頁面 中的文字所述,這主要是由於 CPU 應該向後相容,這意味著超級任天堂最初應該同時執行 NES ROM 和 SNES ROM。為此,CPU 具有所謂的模擬模式。

原始任天堂娛樂系統中的處理器是 Ricoh 2A03(NTSC)或 Ricoh 2A07(PAL),實際上是簡化版的 6502。它缺少 6502 的二進位制編碼十進位制數支援(在 IEEE754 規範釋出之前,曾一度用於浮點數),但在其他方面與該處理器相容。超級任天堂娛樂系統執行在 Ricoh 5A22 上,基本上是 65816 的增強版。這個 CPU 具有前面提到的模擬模式,可以像 NES 一樣執行遊戲。此外,超級任天堂使用與舊 NES 相同的方法來定址超過 CPU 本地暫存器大小(16 位)的記憶體,方法是使用與舊 NES 相同的方法(銀行切換)。請注意,這些方法也可以在模擬模式下使用。2A03 和 5A22 的時鐘速度非常相似

控制檯 NTSC 版本的時鐘速度 PAL 版本的時鐘速度 記憶體
NES 1.79 MHz 1.77 MHz 2048 位元組(2 KB)
SNES 3.58 MHz 3.55 MHz 131072 位元組(128 KB)

請注意,PAL 版本通常會略微慢一些,因為 NTSC 每秒渲染 60 個(交錯半)幀,而 PAL 僅渲染 50 個。

之所以在這篇文章中更多地介紹 CPU 規格而不是直接訪問記憶體(稍後會介紹),是為了說明超級任天堂與 NES 相比的 CPU 優勢很小。超級任天堂的記憶體是 NES 的 64 倍,但 CPU 功率僅是 NES 的 2.5 倍。

一個可能導致 CPU 速度明顯下降的問題(即使在當今時代)是從裝置 A 到裝置 B 複製資訊(位元組)。CPU 通常比儲存當前狀態的記憶體更快,等待記憶體控制器獲取/設定指定的位元組範圍可能會浪費幾個時鐘週期,在這幾個時鐘週期中,如果 CPU 不是 亂序 超標量 的,就會完全阻止當前程式(即遊戲的 ROM)的執行。不用說,幾個 Ricohs 並不是為了成為那種特定型別的 CPU。

直接記憶體訪問是一種獨立於 CPU 將記憶體動態複製到另一個位置的方法。在任何現代計算機中,DMA 都是一項基本需求。對於超級任天堂來說,DMA 可以用來快速將瓦片和調色盤資料複製到影片 RAM(也稱為 VRAM),否則這些資料將由緩慢的 CPU 複製。瞭解 DMA 是建立大型超級任天堂程式的必要條件,因此本教程將介紹 DMA。

DMA 用於將圖形資料(如 8x8 瓦片和瓦片地圖)複製到 VRAM,並將調色盤資料複製到 CGRAM。這些位置只能透過反覆讀寫某些硬體暫存器來訪問(有關更多資訊,請參閱 記憶體對映硬體暫存器 頁面)。

DMA 詳解

[編輯 | 編輯原始碼]

為了理解 DMA 的工作原理,我們將簡要了解超級任天堂如何處理記憶體。

控制檯基本上有三個匯流排,它們與內部的多個裝置相連。這三個匯流排是

  1. 匯流排 A:一個 24 位寬地址匯流排,它處理 CPU、卡帶(ROM + SRAM)和主記憶體(WRAM)之間的通訊——這是主要的匯流排。
  2. 匯流排 B:一個 8 位寬地址匯流排,可以透過主匯流排地址空間中的特殊地址(硬體暫存器)使用,它將 APU(音訊)和 PPU(影片)與它連線起來。
  3. 一個 8 位寬資料匯流排,它由兩個地址匯流排控制,用於將資料傳送到不同的位置。當您發出一個或多個 DMA 過程時,此匯流排將被 CPU 阻塞。

請注意,資料匯流排實際上只有一位元組寬,而 CPU 則提供各種 16 位暫存器。這意味著一個 16 位暫存器不能在一個匯流排週期時鐘內寫入或讀取,而只能在兩個週期內寫入或讀取。

超級任天堂的 CPU 包含一個 DMA 控制器,它總共支援 8 個 DMA 通道。這意味著可以同時設定和啟動 8 個從一個裝置複製塊到另一個裝置的過程。每個通道都可以專門配置為以特定方式執行。通道執行其程式,而通道 0(索引 0)具有最高優先順序,通道 7(索引 7)具有最低優先順序。

通用 PC 中的 DMA 控制器可以配置為以特定方式執行分配給它的任務,這也稱為“模式”。此外,PC DMA 控制器可以用於各種其他裝置,例如連線到系統匯流排(ISA、PCI、AGP、PCIe)的所有裝置。

超級任天堂只支援一種模式,“突發”,這基本上意味著只要有一個 DMA 過程尚未完成,CPU 就會完全停止。原因是,CPU 和 DMA 控制器都使用系統匯流排來與其他裝置通訊,但一次只能有一個裝置(主裝置)使用系統匯流排——CPU 或控制器(更現代的控制器提供了機會傳輸少量資料,然後將系統匯流排的控制權交還給 CPU,立即再次請求系統匯流排,以便 CPU 可以完成其掛起的操作,然後將匯流排的控制權交還給控制器——或者不交還,如果 CPU 必須執行大量記憶體訪問操作)。此外,超級任天堂控制器只能與 APU 或 PPU 通訊。

控制器的暫存器透過系統銀行 $00 – $3F 和 $80 – $BF 中的 $4200 到 $4400 的硬體暫存器公開。有一個主暫存器,寫入它後,控制器將根據寫入位元組的位掩碼啟用要啟動的所有 DMA 傳輸。後續暫存器將包含有關每個通道如何執行的資訊,以及在啟用時該通道應該做什麼。

雖然普通的 DMA 控制器只能以“突發”模式複製它被程式設計為複製的資料,而且最多複製 64 KB(整個一個銀行),但超級任天堂提供另一種方法來複制塊到不同的裝置,這種方法有點類似於 PC DMA 控制器的執行方式。這種型別的 DMA 稱為 HDMA(水平直接記憶體訪問),只在 H 空閒期間執行(即光束雷射器在繪製完一行後重置到原始位置所用的時間)。由於這個時間跨度非常小,因此在一個 H 空閒期間只能傳輸 1 到 4 個位元組。

DMA 暫存器

[編輯 | 編輯原始碼]

注意:x 是通道的索引(由 0 到 7 索引)。因此,基本上,每個通道保留了 16 位元組的地址空間。

地址 描述
$420B 主 DMA 暫存器。向它寫入一個位元組時,控制器將根據位元組的位掩碼啟用通道。例如,73(0100 1001)將啟用通道 6、3 和 0。
$43x0 指定通道應該如何執行傳輸,同樣使用位掩碼(有關更多資訊,請參見下文)。
$43x1 DMA 通道 x 目的地($21xx)
$43x2 DMA 通道 x 源地址偏移量(低位)
$43x3 DMA 通道 x 源地址偏移量(高位)
$43x4 DMA 通道 x 源地址銀行
$43x5 DMA 通道 x 傳輸大小(低位)
$43x6 DMA 通道 x 傳輸大小(高位)

注意:本教程不完整,尚未經過測試。

注意:DMA 通道的傳輸大小,當設定為 #$0000 時,將被讀取為 65536 位元組的傳輸,而不是 0 位元組。

一些程式碼

[編輯 | 編輯原始碼]

載入調色盤

[編輯 | 編輯原始碼]

這裡是一個將調色盤資料載入到 CGRAM(儲存調色盤的地方)的宏。

 ;macro for loading palette data into the CGRAM
 ;only use if SIZE is less than 256 bytes
 ;syntax SetPalette LABEL CGRAM_ADDRESS SIZE
 .macro SetPalette
 pha
 php
 
 rep	#$20		; 16bit A
 lda	#\3
 sta	$4305		; # of bytes to be copied
 lda	#\1		; offset of data into 4302, 4303
 sta	$4302
 sep	#$20		; 8bit A
 
 lda	#:\1		; bank address of data in memory(ROM)
 sta	$4304	
 lda	#\2
 sta	$2121		; address of CGRAM to start copying graphics to
 
 stz	$4300		; 0= 1 byte increment (not a word!)
 lda	#$22
 sta	$4301		; destination 21xx   this is 2122 (CGRAM Gate)
 
 lda	#$01		; turn on bit 1 (corresponds to channel 0) of DMA channel reg.
 sta	$420b		;   to enable transfer
 
 plp
 pla
 .endm

載入 VRAM

[編輯 | 編輯原始碼]

這裡是一個將資料載入到 VRAM(儲存瓦片和瓦片地圖的地方)的宏。

 ;macro for loading graphics data into the VRAM
 ;only use if SIZE is less than 256 bytes
 ;syntax LoadVRAM LABEL  VRAM_ADDRESS  SIZE
 .macro LoadVRAM

 pha			; save the current accumulator, Y index and status registers for the time the function is executed.
 phy
 php
 
 rep	#$20		; set the accumulator (A) register into 16 bit mode
 sep	#$10		; set the index (X and Y) register into 8 bit mode

 ldy	#$80		;  we will try to write 128 ($80) bytes in one row ...
 sty	$2115		; ... and we will let the PPU let this know.

 lda	#\2		; the controller will get the hardware register ($2118) as location to where to write the data.
 sta	$2116		; but we still need to specify WHERE in VRAM we want to write the data - what we are doing right now.

 lda	#\3		; number of bytes to be sent from the controller.
 sta	$4305

 sep	#$20		; set the accumulator (A) register into 8 bit mode

 lda	#\1		; from where the data is supposed to be loaded from		
 sta	$4302
 
 ldy	#:\1		; from which bank the data is supposed to be loaded from
 sty	$4304

 ldy	#$01		; set the mode on how the channel is supposed to do it's work. 1= word increment
 sty	$4300

 ldy	#$18		; remember that I wrote "the controller will get the hardware register"? This is it. 2118 is the VRAM gate.
 sty	$4301
 
 ldy	#$01		; turn on bit 1 (channel 0) of DMA - that is, start rollin'
 sty	$420b
  
 plp			; Restore the state of all registers before leaving the function.
 ply
 pla
 .endm

外部連結

[編輯 | 編輯原始碼]
華夏公益教科書