跳轉到內容

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));
}
華夏公益教科書