ゲームボーイを作るその2。今日は実装に使用するテストROMを解析してメモった内容。
テストROMの解析1
使うのは前回紹介したblargg-gb-tests。
このサイトには次のテストがテストごとにzipファイルで提供されている。
- cbg_sound.zip
- cpu_instrs.zip
- dmg_sound.zip
- halt_bug.zip
- instr_timing.zip
- interrupt_time.zip
- mem_timing-2.zip
- mem_timing.zip
- oam_bug.zip
まずはCPUの実装から始めるので、cpu_instrs
について見ていく。
cpu_instrs
cpu_instrs.zip
を解凍すると、次のようなファイルが得られる。
-rw-rw-r-- 1 diningyo diningyo 65536 2月 7 2009 cpu_instrs.gb drwxrwxr-x 2 diningyo diningyo 4096 7月 5 22:17 individual -rw-rw-r-- 1 diningyo diningyo 5044 2月 5 2009 readme.txt drwxrwxr-x 3 diningyo diningyo 4096 7月 5 22:02 source
cpu_instrs.gb
が実際に使用するテストバイナリで、これはBGB等のエミュレータで実行可能となっている。
実際にBGBで実行した結果をキャプチャしたのが下の画像だ。
デバッガはこんな感じ↓。
レジスタの値なども含めて必要な情報が集約されていて、とても使いやすく感じた。 ハードウェア実装としては、まずはこのテストROMを実行して、エミュと同じ様に実行されるように作るのが最初の関門になる。
このcpu_instrs.gb
は、異なる目的の複数のテストをまとめて実行できるようにしたバイナリになっていて、個々のテスト用のバイナリはindividual
ディレクトリに格納されている。
$ ls -l -rw-rw-r-- 1 diningyo diningyo 32768 2月 5 2009 01-special.gb -rw-rw-r-- 1 diningyo diningyo 32768 2月 5 2009 02-interrupts.gb -rw-rw-r-- 1 diningyo diningyo 32768 2月 5 2009 03-op sp,hl.gb -rw-rw-r-- 1 diningyo diningyo 32768 2月 5 2009 04-op r,imm.gb -rw-rw-r-- 1 diningyo diningyo 32768 2月 5 2009 05-op rp.gb -rw-rw-r-- 1 diningyo diningyo 32768 2月 5 2009 06-ld r,r.gb -rw-rw-r-- 1 diningyo diningyo 32768 2月 5 2009 07-jr,jp,call,ret,rst.gb -rw-rw-r-- 1 diningyo diningyo 32768 2月 5 2009 08-misc instrs.gb -rw-rw-r-- 1 diningyo diningyo 32768 2月 5 2009 09-op r,r.gb -rw-rw-r-- 1 diningyo diningyo 32768 2月 5 2009 10-bit ops.gb -rw-rw-r-- 1 diningyo diningyo 32768 2月 5 2009 11-op a,(hl).gb
PASS/FAIL判定
CPUを作成するに当たって、テスト環境を立ち上げる必要があるのだが、どのような条件でテストのPASS/FAILを判定しているのか把握しないといけない。
なので、一番最初に実行される01-special
のテストがどのような構成になっているかを、ざっと調査して行こうと思う。
ソースコードはsources/01.special.s
だ。ここでは冒頭と末尾を抜粋して記載する。まずは冒頭部分。
; Tests instructions that don't fit template .include "shell.inc" main: set_test 2,"JR negative" ld a,0 jp jr_neg inc a - inc a inc a cp 2 jp nz,test_failed
shell.inc
をインクルードした後に、main
セクションがあってそこから、各コマンドのテストが実行される。
テスト後にFAIL条件を満たした場合(ここではjp nz,test_failed
なのでフラグレジスタがnz
だった場合)はtest_failed
にジャンプして、FAIL処理が行われるようになっている。
各テストがPASSした場合は次のテストに進み、すべてのテストでFAILしなかった場合にのみ、次のコードへとたどり着くようになっている。
jp tests_passed
このPASS/FAILでジャンプする先のラベルは、各テストで共通して使用されるものなので、common
ディレクトリに格納されている別のファイルに定義されている。
- common/testing.s
; Reports "Passed", then exits with code 0 tests_passed: call print_newline print_str "Passed" ld a,0 jp exit
shell.inc
さきほどのエミュで動かしてみた画像では、テスト結果が画面に出力されており、この処理はテスト結果でジャンプした先のprint_str
が担っている。
このあたりの処理は、各テストでインクルードされているshell.inc
を追っていくと、わかりそう。
- shell.inc
.incdir "common" ; GBS music file .ifdef BUILD_GBS .include "build_gbs.s" .endif ; Devcart .ifdef BUILD_DEVCART .include "build_devcart.s" .endif ; Sub-test in a multi-test ROM .ifdef BUILD_MULTI .include "build_multi.s" .endif ; GB ROM (default) .ifndef RUNTIME_INCLUDED .include "build_rom.s" .endif
これzipに含まれている*.gb
はどのdefineでコンパイルされたものなんだろう。。。コメント読む限りでは、何となくデフォルトのやつっぽいけど。。