8086汇编4位bcd码_Re: 从零开始的红白机模拟 - [04]6502汇编

f640c2f809cbfac7d6d497f575802318.png

本文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'(累加器)"的意思

寻址方式

指令LDA对应多个机器码, 区别是寻址方式的不同, 现在先列举所有的寻址方式以及格式, 详细的解释在下一节.

  • 累加器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进制字符串.

输出每个向量的第一个执行代码的汇编代码

e1f8e96b595776a1e9648304b6651347.png
C004 - SEI

这里, RESET执行的第一个指令是: SEI, 即 'Set I flag'

项目地址Github-StepFC-Step2

作业

  • 基础: 利用反汇编函数输出所有256个机器码的汇编代码
  • 扩展: 删掉反汇编实现函数, 自己实现反汇编函数.
  • 从零开始: 从零开始实现自己的模拟器吧

REF

  • CPU unofficial opcodes
  • 6502 CPU
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值