Open ewruen opened 5 years ago
非常感谢您的建议!
CPU 的代码在寒假末修bug时也给我带来了不少的困扰,现在因为开学事情很多所以处于搁置的状态,目前的打算是暑期如果有空的话就彻底重写 CPU部分的代码…还是非常感谢!
伙计, 有个小技巧来调试CPU. 那别人现成的模拟器CPU核心, 把MMU HOOK替换掉.. 区域内存各自一份, 读写全部用相同的函数 然后自己的CPU和别人的CPU逐指令帧比较测试. 这是能获得一些进度的捷径, 即使你对调错的方法不熟悉, 至少能为你解决一些显而易见的粗心的错误. 一开始的CPU写完之后我就是这么做的, 当时还写得挺随便的, 还是能够解决了一部分基础的错误, 这部分其实挺困难的, 我也没啥好方法, 抱着别人CPU调试代码调无可调之后都是死乞白赖的一个个仔细看手册对照着检查写纠错的, CPU写代码和调试都非常肉疼,难过.. GBA不像NES和GBC有专门的可供测试CPU指令的ROM给你用.(其实mGBA作者的测试ROM勉强算上一个吧,不过只是ALU指令的C位测试). 等把根据模拟器代码把自己的CPU成色稍微弄得纯一点时..(至少除了PSW程序状态字标志寄存器外的大部分寄存器应该要一致).. 这个时候你没写其他设备, BIOS或者程序代码会在某处等待系统内存IO的信号(如VBLANK信号进行画面的VSync), 你没法进行下一步调试, 此时程序实际在提醒是时候捣鼓你的其他设备(如GPU了).
GPU的编写你其实可以大可不必先写渲染... GPU有这么几个关键时间序列点, %1: LY线帧更新 %2: HBLANK水平消隐 %3: VBLANK垂直消隐 CPU会等待这几个关键信号同步,触发逻辑控制, 到时候注意给flag 和中断就行了,还有VBL, HBL DMA测试. 比如可以这样写 (部分细节可能有出入,但是大致过程便是如此)... for (scanline := 0; scanline != 228; scanline++) { // 清空HBL标志 // GPU渲染前160根扫描线,这部分你可以先不写,渲染基本不会改变关键数据,只是负责提交framebuffer而已 // CPU跑渲染一根扫描线的周期
// --- 1---- 扫描线渲染完毕,电子枪水平回扫, 进入HBL, 此时线帧LY+1 // HBL 要做这么几件事情 // %1 HBL DMA测试- // %2 设置 GPU的HBL状态掩码 // %3 测试当前是否允许HBL中断, (也是在GPU状态寄存器里面存在HBL致能掩码), 允许发生中断,这里的发生中断仅仅是将IF的指定位域设置 // %4 当前为暂停状态, 消除暂停,恢复正常运行.
// --- 2---- LY增加了,测试LY线匹配, 如果匹配则 // %1 设置 GPU的LY-MATCH状态掩码 // %2 测试当前是否允许LY中断 // %3 当前为暂停状态, 消除暂停,恢复正常运行. // 否则清空LY-MATCH状态掩码
// CPU跑HBL的周期
// --- 3---- 测试VBLANK (LINE:= 160), 触发类似上面动作..
} //清空VBL状态掩码-- 你先跑个一段时间, 如果代码没跑飞,且PC指针不是一直停留在某处等待 IO, 以及一段固定的循环块内, 那CPU基本就是能用了, 比如加载好BIOS, 从RESET中断开始能不能跑到GBA ROM的0x80000000开始处...
GPU只要写完mode0和mode1就行了, GPU技术层面上来说很简单, 4个卷轴+SP输出, 16位颜色+简单的仿射变换功能 可实际GPU是个大杂烩,既保留了强化CGB/NES的Tile发色模式,也有扩展的8位调色板和直接RGB16输出. 还有一些简单图形滤波器, 这部分真的很麻烦 得慢慢写,而且性能极易出问题, 写的不好会很慢很慢, 也挺考验编译器的优化技术 GPU和CPU一样都是需要日CASE来提供性能提升的.... (我试过强制内联的像素滤波器函数作为回调参数传给GPU处理器的时候在release模式下 GCC还是老老实实的call,不会内联. CL却能内联,好奇怪.) (参考GBE-PLUS这个模拟器, release版本也挺慢的,完全跑不到60帧, 可能也是我thinkpad配置太渣⑧) 等能把BIOS的启动动画完美的跑出来的时候. (BIOS的LOGO显示用了mode1的仿射和精灵窗口的镂空显示) 你的模拟器已经差不多能跑一些简单的游戏了, 曙光初现啊~
小时候也经常玩任地狱的游戏机, NES, GBC和GBA..,觉得模拟器这东西还是挺有意思的, 所以看了一段时间资料自己写过Gameboy 模拟器的demo.(https://imgchr.com/i/AyJOfI) 看到Github有国人写类似的程序所以来看看热闹. 写模拟器的挺少, 还蛮难得的,所以说的有点多,其实也是无聊额.(半夜总是睡不着啊,我擦)
http://t.cn/EMptNxF (我写的关于GBA的GPU解释) http://t.cn/EM0VoKY (我写的关于GBA的CPU解释,这部分可能有些有遗漏的地方..)
显存镜像有点偏差 首先每0x20000镜像一次, 在这0x20000里面, 前0x17FFF都是镜像0x06000000 ~ 0x06017FFF, 最后这32K映射镜像 0x06010000~0x06017FFF, 而且都可以读写无论是命名表还是OAM...
区域内存根据内存总线宽度可能也会有镜像读写的问题. 如写8位调色板, 写一次byte数据复制合并成 byte | byte 的halfword数据写入调色板 (可见 mgba源代码 memory.c ::GBAStore16, GBALoad16函数实现)
IF的写也有问题, IE写类似于ARM7的BIC指令, IF := IF & ~value
你的CPU宏写的太多了, 不怎么容易看懂, 我说几个容易错的地方你可以自己参考下 %1 未对齐访问, 对于HALFWORD总是对齐2个字节(&-2), WORD则是四个字节. %2 非常态化的未对齐读写, LDRH的扩展读写, 和LDR的循环移位读写. 这个GBATEK上也有, GBATEK的内容全部来自NO$GBA模拟器的Help文档, 有兴趣可以看一下 ARM7模式,thumb模式都会是如此 %3 机器码位域带S的加减指令, 此时的IMM移位不需要设置C位 %4 ARM7的立即数生成, ROR+ EVEN IMM8, 此时如果带位域带S且指令不影响C位(如TEST, TEQ等) 也会设置C位--- %5 LDR Rn回写模式如果Rn和Rd相同引起的冲突会导致 Rn不写回
%6 SHIFT+IMM模式下 如果IMM := 0且SHIFT:= SHL C位不会修改 %7 ARM7的I周期, 卡带可能进行缓存预取 %8 STM在寄存器List中有R15,也就是PC, 压进去的PC实际是当前指令+12处的值 (ARM7模式, Thumb模式未测试过) %9 SHL, SHR, ASR, ROR 在RM, IMM >31或者等于特定数目时会发生诡异的结果,这点可以参考 arm_arm.pdf..(不过很多模拟器也没写这些feature也没事)..