ゲームボーイを作るその10。前回でCPUレジスタの仮実装を行ったので、テスト環境側の期待値比較処理を作っていく。
CPUレジスタの期待値比較
CpuTbの修正
第7回で作ったCpuTb
というテストベンチのモジュールに処理を追加していく。
追加するのはBoringUtils
に関する記述で、CPUレジスタをCpu
モジュールクラスのコードを修正せずに、テストベンチ側で参照できるようにする。イメージはVerilog HDL
とかで階層間参照をするような形だ。
BoringUtils
については、以前に記事を書いているのでそちらも参照してもらえるとありがたい。
BoringUtils
によるCPUレジスタの参照を追加したCpuTb
モジュールのコードは次のようになる。
class CpuTestTb(val testRom: String) extends Module { val io = IO(new Bundle { val finish = Output(Bool()) val is_success = Output(Bool()) val timeout = Output(Bool()) val regs = Output(new CpuReg) }) io := DontCare val mem = Module(new Mem(testRom)) val dut_cpu = Module(new Cpu) dut_cpu.io.mem <> mem.io // BoringUtilsでCpuクラスのregsへのパスを通す val w_regs = WireDefault(0.U.asTypeOf(new CpuReg)) BoringUtils.bore(dut_cpu.r_regs, Seq(w_regs)) // w_regsをIOポートに接続して、テストから参照できるようにする io.regs := w_regs }
このようにしておくことで、Cpu
モジュールのコードには手を入れずに、期待値比較対象であるCPUレジスタをテスト側から参照できるようになった。
CpuTestに01_ld.s用のテストを実装
次にテストクラス側。とは言っても、テストベンチモジュールのIOポートまで、CPUレジスタを引き出したので、後はテスト記述側でクロックを進めつつ期待値比較をするだけになる。
まだCPUの各命令の仕様があやふやなので、期待値側に間違いがあるかも。。。
class CpuTest extends FlatSpec with ChiselScalatestTester with Matchers { val annos = Seq(VerilatorBackendAnnotation) behavior of "Cpu" import BlarggGbTests._ def compareReg( a: Int, b: Int, c: Int, d: Int, e: Int, h: Int, l: Int, sp: Int, pc: Int, f_z: Boolean, f_n: Boolean, f_h: Boolean, f_c: Boolean )(implicit dut: CpuTestTb): Unit = { println(s"reg = ${dut.io.regs.peek}") dut.io.regs.a.read.expect(a.U) dut.io.regs.b.read.expect(b.U) dut.io.regs.c.read.expect(c.U) dut.io.regs.d.read.expect(d.U) dut.io.regs.e.read.expect(e.U) dut.io.regs.h.read.expect(h.U) dut.io.regs.l.read.expect(l.U) dut.io.regs.sp.read.expect(sp.U) dut.io.regs.pc.read.expect(pc.U) // flagはとりあえず各ビット単位で比較 dut.io.regs.f.z.expect(f_z.B) dut.io.regs.f.n.expect(f_n.B) dut.io.regs.f.h.expect(f_h.B) dut.io.regs.f.c.expect(f_c.B) } val testName = "01_ld.s" it should f"be passed ${testName} tests" in { val testHexFilePath = s"src/test/resources/cpu/${testName}.gb.hex" test(new CpuTestTb(testHexFilePath)).withAnnotations(annos) { c => implicit val dut = c c.clock.setTimeout(10) compareReg(0, 0, 0, 0, 0, 0, 0, 0, 0x100, false, false, false, false) c.clock.step(100) // 1cycleごとに期待値を比較していく // NOTE: 初期値どうしよう。。bgbの値に合わせても良いのかも。 // ld a, $a5 ; a = $a5 // ld a, imm -> need 2 cycles // a b c d e h l sp pc f_z f_n f_h f_c compareReg(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x101, false, false, false, false) c.clock.step(1) compareReg(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x102, false, false, false, false) c.clock.step(1) compareReg(0xa5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x103, false, false, false, false) c.clock.step(1) // ld b, a compareReg(0xa5, 0xa5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x102, false, false, false, false) c.clock.step(1) // ld c, b compareReg(0xa5, 0xa5, 0xa5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x100, false, false, false, false) c.clock.step(1) // ld d, c compareReg(0xa5, 0xa5, 0xa5, 0xa5, 0x00, 0x00, 0x00, 0x00, 0x100, false, false, false, false) c.clock.step(1) // ld e, d compareReg(0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0x00, 0x00, 0x00, 0x100, false, false, false, false) c.clock.step(1) // ld l, e compareReg(0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0x00, 0x00, 0x00, 0x100, false, false, false, false) c.clock.step(1) // ld e, l compareReg(0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0x00, 0x00, 0x00, 0x100, false, false, false, false) } } }
テスト部分を実装した以外に第7回のコードから変更した点に、Verilatorのバックエンドを指定するアノテーションの記述を追加した点がある(以下の部分)。
// 使用時には次の2つのインポートが必要 // import chiseltest.internal.VerilatorBackendAnnotation // import chiseltest.experimental.TestOptionBuilder._ val annos = Seq(VerilatorBackendAnnotation) // test()の後に.withAnnotations(annos)が追加された it should f"be passed ${testName} tests" in { val testHexFilePath = s"src/test/resources/cpu/${testName}.gb.hex" test(new CpuTestTb(testHexFilePath)).withAnnotations(annos) { c => // ^^^^^^^^^^^^^^^^^^^^^^^
これを指定しておくことで、テスト実行時のシミュレーターにVerilatorを使用してくれるようになる。
ここまでで、テストが実行できるようになったがもちろんテスト自体はFAILする。ChiselTestでは、expect
メソッドでの比較に失敗すると、次のようなメッセージが出た後に、シミュレーション自体がそのサイクルで終了するようになった。(個人的には、その次のサイクルくらいで落として欲しい。。)
bt:chisel-dmg> testOnly CpuTest -- -DwriteVcd=1 [info] compiling 1 Scala source to /home/diningyo/workspace/gameboy/chisel-dmg/target/scala-2.12/test-classes ... Elaborating design... Done elaborating. ~中略~ Exit Code: 0 [info] CpuTest: [info] Cpu [info] - should be passed 01_ld.s tests *** FAILED *** [info] CpuTestTb.io_regs_pc_data=256 (0x100) did not equal expected=257 (0x101) (lines in CpuTest.scala: 147, 135) (CpuTest.scala:123)
そろそろ、CPUの実装を本格的に進められそう。