本文github備份地址

STEP2: 6502彙編

寫模擬器當然要了解使用到的彙編, 不管是調試模擬器還是模擬器調試都需要.

前面知道了6502彙編用$表示十六進位, 那先講講6502機器碼, 由一個操作碼和0~2個地址碼構成, 都是8位的:

/// <summary>
/// StepFC: 6502機器碼
/// </summary>
typedef union {
// 用32位保存數據
uint32_t data;
// 4個8位數據
struct {
// 操作碼
uint8_t op;
// 地址碼1
uint8_t a1;
// 地址碼2
uint8_t a2;
// 顯示控制
uint8_t ctrl;
};

} sfc_6502_code_t;

其中, 值得注意的是C11才支持的匿名struct/union. 請檢查自己編譯器支持的情況.

理論上, 6502擁有256條操作碼, 這裡是所有的指令表: 非官方OpCode

有一些被稱為非法或者說未被文檔記錄的操作碼, 但是文檔提到

An accurate NES emulator must implement all instructions, not just the official ones. A small number of games use them (see below).

我們必須實現所有操作碼.

指令

舉一個簡單的指令LDA, 就是"load A(累加器)"的意思

定址方式

指令LAD對應多個機器碼, 區別是定址方式的不同, 現在先列舉所有的定址方式以及格式, 詳細的解釋在下一節.

  • 累加器A定址 Accumulator 單位元組指令, 格式:

INS A

  • 隱含定址 Implied Addressing 單位元組指令, 格式:

INS

  • 立即定址 Immediate Addressing 雙位元組指令, 格式

INS #$AB

  • 絕對定址 Absolute Addressing 三位元組指令, 格式

INS #$ABCD

  • 絕對X變址 Absolute X Indexed Addressing 三位元組指令, 格式

INS #$ABCD, X

  • 絕對Y變址 Absolute Y Indexed Addressing 三位元組指令, 格式

INS #$ABCD, Y

  • 零頁定址 Zero-page Absolute Addressing 雙位元組指令, 格式

INS #$A

  • 零頁X變址 Zero-page X Indexed Addressing 雙位元組指令, 格式

INS #$A, X

  • 零頁Y變址 Zero-page Y Indexed Addressing 雙位元組指令, 格式

INS #$A, Y

  • 間接定址 Indirect Addressing 三位元組指令, 格式

INS ($ABCD)

  • 間接X變址: Pre-indexed Indirect Addressing 雙位元組指令, 格式

INS (#$A, X)

  • 間接Y變址: Post-indexed Indirect Addressing 雙位元組指令, 格式

INS ($AB), Y

  • 相對定址: Relative Addressing 雙位元組指令, 格式

INS $AB - 16進位
INS +-abc - 有符號十進位
INS xxxxx - 跳轉的目標地址的標籤, 由彙編器自動計算
INS $ABCD - 跳轉的目標地址, 由彙編器自動計算

反彙編

由於我們只需要反彙編而不需要彙編, 所以輸出格式看自己喜好.比如除了最後一個, 看自己喜好實現吧.

現在就是根據操作碼查找定址方式和指令就能反彙編了:

/// <summary>
/// StepFC: 指定地方反彙編
/// </summary>
/// <param name="address">The address.</param>
/// <param name="famicom">The famicom.</param>
/// <param name="buf">The buf.</param>
void sfc_fc_disassembly(uint16_t address, const sfc_famicom_t* famicom, char buf[]) {
// TODO: 根據操作碼讀取對應位元組

sfc_6502_code_t code;
code.data = 0;
// 暴力(NoMo)讀取3位元組
code.op = sfc_read_cpu_address(address, famicom);
code.a1 = sfc_read_cpu_address(address + 1, famicom);
code.a2 = sfc_read_cpu_address(address + 2, famicom);
// 反彙編
sfc_6502_disassembly(code, buf);
}

目前暴力(?)讀取3位元組, 下次再實現讀取指定位元組數量.

建立一張表用於反彙編:

/// <summary>
/// 命令名稱
/// </summary>
struct sfc_opname {
// 3字名稱
char name[3];
// 定址模式
uint8_t mode;
};

/// <summary>
/// 反彙編用數據
/// </summary>
static const struct sfc_opname s_opname_data[256] = {
{ B, R, K, SFC_AM_IMP },
// 下略
};

就能反彙編了:

  • 00 - BRK
  • 等等等等
  • 這些全部要自己查表纔行
  • 自己使用的微軟編譯器, 對C11支持不大行, 像_Alignas之類的明明C++那邊實現了, C這邊卻沒有
  • 對於查表用的數據可以 以CPU緩存行為單位對齊

反彙編

有了數據反彙編就太簡單了, 核心部分應該是儘可能少使用外部函數. 這裡可以用snprintf之類的格式化函數, 但是畢竟核心部分, 自己還是自己手寫了像格式化位16進位字元串.

輸出每個向量的第一個執行代碼的彙編代碼

C004 - SEI

這裡, RESET執行的第一個指令是: SEI, 即 Set I flag

項目地址Github-StepFC-Step2

作業

  • 基礎: 利用反彙編函數輸出所有256個機器碼的彙編代碼
  • 擴展: 刪掉反彙編實現函數, 自己實現反彙編函數.
  • 從零開始: 從零開始實現自己的模擬器吧

REF

  • CPU unofficial opcodes
  • 6502 CPU

推薦閱讀:

相關文章