跳转到内容

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;
}
华夏公益教科书