ハードウェアの気になるあれこれ

技術的に興味のあることを調べて書いてくブログ。主にハードウェアがネタ。

ゲームボーイを作る(2) - テストROMの解析1

スポンサーリンク

ゲームボーイを作るその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で実行した結果をキャプチャしたのが下の画像だ。

f:id:diningyo-kpuku-jougeki:20210711211659j:plain
BGBの画面出力

デバッガはこんな感じ↓。

f:id:diningyo-kpuku-jougeki:20210711211710j:plain
デバッガ

レジスタの値なども含めて必要な情報が集約されていて、とても使いやすく感じた。 ハードウェア実装としては、まずはこのテスト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でコンパイルされたものなんだろう。。。コメント読む限りでは、何となくデフォルトのやつっぽいけど。。