Buhe Blog - Microsoft Internet Explorer

5.1.运行我们的第一个测试 ROM


NES 开发社区已经创建了大量的测试套件,可用于检查我们的模拟器。 它们几乎涵盖了控制台的各个方面,包括嵌入在平台中的怪癖和值得注意的错误。 我们将从涵盖主要 CPU 功能的最基本测试开始:指令集、内存访问和 CPU 周期。 测试的 iNES 文件位于:nestest.nes 测试附带 一个执行日志,显示执行应该是什么样子:nestest.log 下一个目标是在运行程序时为 CPU 生成类似的执行日志。 现在,我们可以忽略最后一列而专注于前五列。 第四列@ 80 = 0200 = 00有点意思。

  • 如果我们对请求地址应用偏移量,第一个数字是我们得到的实际内存引用。0xE1 使用“Indirect X”寻址方式,偏移量由寄存器 X 定义
  • 第二个数字是从**[0x80 .. 0x81]**获取的 2 字节目标地址。在这个例子,它是[0x00, 0x02]
  • 第三个数字是地址单元 0x0200 的内容 我们已经有了拦截 CPU 执行的地方:

impl CPU  {

// ..

	pub fn run_with_callback<F>(&mut self, mut callback: F)
   where
       F: FnMut(&mut CPU),
   {
			let ref opcodes: HashMap<u8, &'static opcodes::OpCode> = *opcodes::OPCODES_MAP;loop {
           callback(self);
// ...
      }
   }
}

我们需要做的就是创建一个回调函数来跟踪 CPU 状态:


fn main() {
//...
    cpu.run_with_callback(move |cpu| {
       println!("{}", trace(cpu));
   }
}

获得与提供的日志中使用的完全相同的执行日志格式至关重要。 以下测试可以帮助您做对:


#[cfg(test)]
mod test {
   use super::*;
   use crate::bus::Bus;
   use crate::cartridge::test::test_rom;
   #[test]
   fn test_format_trace() {
       let mut bus = Bus::new(test_rom());
       bus.mem_write(100, 0xa2);
       bus.mem_write(101, 0x01);
       bus.mem_write(102, 0xca);
       bus.mem_write(103, 0x88);
       bus.mem_write(104, 0x00);let mut cpu = CPU::new(bus);
       cpu.program_counter = 0x64;
       cpu.register_a = 1;
       cpu.register_x = 2;
       cpu.register_y = 3;
       let mut result: Vec<String> = vec![];

       cpu.run_with_callback(|cpu| {
           result.push(trace(cpu));
       });
       assert_eq!(
           "0064  A2 01     LDX #$01                        A:01 X:02 Y:03 P:24 SP:FD",
           result[0]
       );

       assert_eq!(
           "0066  CA        DEX                             A:01 X:01 Y:03 P:24 SP:FD",
           result[1]
       );
       assert_eq!(
           "0067  88        DEY                             A:01 X:00 Y:03 P:26 SP:FD",
           result[2]
       );
   }

   #[test]
   fn test_format_mem_access() {
       let mut bus = Bus::new(test_rom());
// ORA ($33), Y
       bus.mem_write(100, 0x11);
       bus.mem_write(101, 0x33);//data
       bus.mem_write(0x33, 00);
       bus.mem_write(0x34, 04);//target cell
       bus.mem_write(0x400, 0xAA);let mut cpu = CPU::new(bus);
       cpu.program_counter = 0x64;
       cpu.register_y = 0;
       let mut result: Vec<String> = vec![];
       cpu.run_with_callback(|cpu| {
           result.push(trace(cpu));
       });

       assert_eq!(
           "0064  11 33     ORA ($33),Y = 0400 @ 0400 = AA  A:00 X:00 Y:00 P:24 SP:FD",
           result[0]
       );
   }
}

现在是时候将我们的执行日志与黄金标准进行比较了。


cargo run > mynes.log
diff -y mynes.log nestest.log

您可以使用任何您喜欢的差异工具。但是因为我们的 NES 还不支持 CPU 时钟周期,所以删除提供的日志中的最后一列是有意义的: ```plain text

cat nestest.log | awk ‘{print substr($0,0, 73)}’ > nestest_no_cycle.log diff -y mynes.log nestest_no_cycle.log ``` 如果一切正常,第一个不匹配应该如下所示:


C6B3  A9 AA     LDA #$AA                        A:FF X:97 Y:4   C6B3  A9 AA     LDA #$AA                        A:FF X:97 Y:4
C6B5  D0 05     BNE $C6BC                       A:AA X:97 Y:4   C6B5  D0 05     BNE $C6BC                       A:AA X:97 Y:4
C6BC  28        PLP                             A:AA X:97 Y:4   C6BC  28        PLP                             A:AA X:97 Y:4
                                                              > C6BD  04 A9    *NOP $A9 = 00                    A:AA X:97 Y:4

即,我们的模拟器生成的所有内容都应该完全符合黄金标准,直到 **0xC6BC **行。如果上线前有任何问题,说明我们的 CPU 实现存在错误,需要修复。 但这并不能解释为什么我们的程序会被终止。为什么我们在 **0xC6BC **行之后没有得到完美匹配? 该程序在


C6BD  04 A9    *NOP $A9 = 00

看起来我们的 CPU 不知道如何解释操作码 0x04。 坏消息是:大约有 110 条非官方 CPU 指令。大多数真正的NES游戏都大量使用它们。为了让我们继续前进,我们需要实现所有这些。 可以在这里找到规格:


C68B  8D 15 40  STA $4015 = FF                  A:02 X:FF Y:15 P:25 SP:FB

几乎在 NES 测试日志文件的最后。 这是一个好兆头。4015是APU寄存器的内存映射,我们还没有实现。

本章完整源代码:GitHub