跳轉到內容

N64 程式設計/記憶體對映

來自華夏公益教科書,自由的教科書
CPU  Main Memory (RDRAM) (2)           CDROM (8)
 |        |                               |
 ------------------------------------------
    |                    |
 Registers (1)      Cartridge (ROM) (4)


當然,我們希望將資料儲存在暫存器中(1 個時鐘週期)。

讓 CPU 從 CDROM 執行會很慢(8 個週期)。即使是卡帶記憶體也更快。

最有可能的是,遊戲會將重要的程式碼和資料複製到 RDRAM 以減少載入時間。並提高執行速度。

這是透過直接記憶體訪問 (DMA) 完成的,快速傳輸。

請注意,由於我們從 RAM 執行,因此您也可能會發現自修改程式碼。

因為程式碼是從記憶體中執行的,所以它是從 RAM 編譯的,而不是 ROM 地址。

想想 8 位 NES/SMS/GB 頁面偏移量。

[0361:d824] 8002A14C: AND     k1[0000FF03],k1[0000FF03],at[FFFF00FF]
[0369:d825] 8002A150: OR      k1[00000003],k1[00000003],t1[0000FF00]
[3c09:a430] 8002A154: LUI     t1[0000FF00],FFFFA430h

位於 ROM $554C(不可思議的迷宮 2)。

記憶體對映

[編輯 | 編輯原始碼]

我們的記憶體對映從 $0000:0000-FFFF:FFFF。

COP0 可以訪問 $2000:0000 及以上。這意味著它可以改變指向 24 位偏移量(16 MB)的物理地址。或者 8MB 下降到 4KB 頁面。

一般而言,

$0000:0000 = ROM。
$1000:0000 = ROM。
$8000:0000 = RDRAM。程式碼。
$A000:0000 = RDRAM。資料。
$A400:0000 = PI,SI。DMA 暫存器。
$B000:0000 = ROM(DMA,LD)。

您會看到它被稱為“轉換後備緩衝區”(TLB)。

DMA 是所有現代系統中的一種功能,用於在不涉及 CPU 的情況下快速在不同元件之間移動資料。需要注意的是,PI DMA 記憶體傳輸以 64 位塊進行。我們可以從

  • RDRAM 到/從卡帶(ROM,SRAM,FlashRAM,..)
  • RDRAM 到/從 RCP

DMA 暫存器

$A460:0000 = RAM 地址(address & 0x00FFFFFF)
$A460:0004 = ROM 地址(address & 0x1FFFFFFF)
$A460:0008 = 傳輸大小(從 RAM 到卡帶)
$A460:000C = 傳輸大小(從卡帶到 RAM)
$A460:0010 = DMA 狀態

最後兩個地址接受 DMA 複製長度(減 1 - BPL 迴圈)並啟動傳輸。您寫入哪個長度暫存器取決於您想要的傳輸方向。一旦傳輸啟動,可以透過讀取狀態暫存器來檢查傳輸狀態。以下標誌可用於檢查狀態

  • DMA_BUSY = 0x00000001
  • DMA_ERROR = 0x00000008

以下是使用 N64 的 DMA 功能的一些示例 C 程式碼

/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ **
** N64 DMA                         **
** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */

typedef struct
{
	/* Pointers to data */
	void *ramp;
	void *romp;
	
	/* Filesizes (8-byte aligned) */
	u32 size_ramrom; /* RAM -> ROM */
	u32 size_romram; /* RAM <- ROM */
	
	/* Status register */
	u32 status;
} DMA_REG;

/* DMA status flags */
enum
{
	DMA_BUSY  = 0x00000001,
	DMA_ERROR = 0x00000008
};

/* DMA registers ptr */
static volatile DMA_REG * dmaregs = (DMA_REG*)0xA4600000;

/* Copy data from ROM to RAM */
int dma_write_ram ( void *ram_ptr, void *rom_ptr, u32 length )
{
	/* Check that DMA is not busy already */
	while( dmaregs->status & DMA_BUSY );
	
	/* Write addresses */
	dmaregs->ramp = (u32)ram_ptr & 0x00FFFFFF; /* ram pointer */
	dmaregs->romp = (u32)rom_ptr & 0x1FFFFFFF; /* rom pointer */
	
	/* Write size */
	dmaregs->size_romram = length - 1;
	
	/* Wait for transfer to finish */
	while( dmaregs->status & DMA_BUSY );
	
	/* Return size written */
	return length & 0xFFFFFFF8;
}
華夏公益教科書