跳转到内容

N64 编程/编译

来自维基教科书,开放世界中的开放书籍
从头开始用 C 语言编写的应用程序的错误处理例程

N64 绝不是资源受限的,因此用 C 语言为其编写软件是完全合理的。不过,您必须牢记一件事:为 N64 编码需要深入了解 C 语言和 MIPS R4K 汇编。但是,汇编只需要在初始化 N64(或处理异常)的小例程中使用。您还需要熟悉 GNU 工具链(主要是 binutils 和 gcc)。

初始步骤

[编辑 | 编辑源代码]

选择一个您想要编译二进制文件的目录,将其设置为 $PREFIX,并确保它存在。例如,

export PREFIX=/opt/n64
mkdir -p $PREFIX

接下来,将 $GCC 设置为您将使用的编译器。例如,

export GCC=gcc

构建 binutils

[编辑 | 编辑源代码]

下载 binutils 源代码(我们将使用 2.35.1 版本),并解压缩它。

创建一个树外构建目录,并进入其中

mkdir -p build-binutils
cd build-binutils

然后运行以下命令

../binutils-2.35.1/configure \
  --target=mips64-elf --prefix=$PREFIX \
  --program-prefix=mips64- --with-cpu=vr4300 \
  --with-sysroot --disable-nls --disable-werror
make CC=$GCC
make install

希望一切顺利,现在您将拥有一个针对 MIPS 的 binutils 软件包。回到您的工作目录,现在该构建 GCC 了。

编译 GCC

[编辑 | 编辑源代码]

GCC 需要使用您上面编译的一些二进制文件,因此请执行以下操作

export PATH=$PREFIX/bin:$PATH

这将使 GCC 能够找到它们。

与 binutils 一样,下载 gcc 源代码(我们将使用 10.2.0 版本)并解压缩它。

创建一个树外构建目录,并进入其中

mkdir -p build-gcc
cd build-gcc

然后运行以下命令

../gcc-10.2.0/configure \
  --target=mips64-elf --prefix=$PREFIX \
  --program-prefix=mips64- --with-arch=vr4300 \
  -with-languages=c,c++ --disable-threads \
  --disable-nls --without-headers
make all-gcc CC=$GCC
make all-target-libgcc CC=$GCC
make install-gcc
make install-target-libgcc

mips64 工具链现在应该安装在您选择的 $PREFIX/bin 中。

编码示例

[编辑 | 编辑源代码]

虽然为 N64 编写 C 代码与任何其他平台没有区别(除了您没有可用的库),但您有时可能需要写入内存映射寄存器。以下示例(使用 DMA)演示了这一点

/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ **
** 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;
}

您可能还必须经常使用内联汇编。以下函数在内存区域设置断点

enum
{
        BREAKPOINT_READ  = 1,
        BREAKPOINT_WRITE = 2
};

/* Set breakpoint */
void bp_set ( u32 addr, u8 flags )
{
        addr &= 0x3FFFF8; /* assuming lower 4MB, also doubleword */
        flags &= 0x03; /* only lower two bits */
        addr |= flags;

        asm("mtc0 %0, $18\n" /* WatchLo */
        "mtc0 $zero, $19\n" /* WatchHi */
        ::"r"(addr));
}
华夏公益教科书