ゲームボーイを作るその12。ちょっと寄り道して、デバッグのためにレジスタ表示を改善する。
レジスタ値のデバッグ出力の改善
今、実装を進めているCPUのテスト環境では、テストベンチのトップに引き出してきたレジスタの値を、直接Chiselのテスト記述で比較する仕組みとなっている。そのために実装したメソッドがcompareReg
メソッドだ。
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"${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) }
この中でレジスタの中身を表示するためにprintln(s"${dut.io.regs.peek}")
として、CpuReg
のレジスタを表示している。CpuReg
はBundle
を継承したものなので、クラス内で定義されているChiselの信号をすべて出力してくれる。
してくれるのだが、その表示は次にように型の情報やビット幅が含まれており、いささかデバッグの際に使用するには視認性が良くない。
println(s"${dut.io.regs.peek}")
の出力(UInt<8>(X)
のX
が信号の今のデータ)
reg = CpuReg(a=GP(data=UInt<8>(0)), f=F(z=Bool(false), n=Bool(false), h=Bool(false), c=Bool(false)), b=GP(data=UInt<8>(0)), c=GP(data=UInt<8>(0)), d=GP(data=UInt<8>(0)), e=GP(data=UInt<8>(0)), h=GP(data=UInt<8>(0)), l=GP(data=UInt<8>(0)), sp=GP(data=UInt<16>(0)), pc=PC(data=UInt<16>(256))) reg = CpuReg(a=GP(data=UInt<8>(0)), f=F(z=Bool(false), n=Bool(false), h=Bool(false), c=Bool(false)), b=GP(data=UInt<8>(0)), c=GP(data=UInt<8>(0)), d=GP(data=UInt<8>(0)), e=GP(data=UInt<8>(0)), h=GP(data=UInt<8>(0)), l=GP(data=UInt<8>(0)), sp=GP(data=UInt<16>(0)), pc=PC(data=UInt<16>(257)))
Chiselの信号をprintln
などで表示した際に呼ばれているのは、各データクラスで定義されているtoString
メソッドになる。以下はUInt
クラスのtoString
メソッドの中身だ。
override def toString: String = { val bindingString = litOption match { case Some(value) => s"($value)" case _ => bindingToString } s"UInt$width$bindingString" }
最初に掲載した今のデバッグ出力と比較すると、bindingString
のマッチ式case Some(value)
が(X)
の表示に対応するようだ。このマッチの対象となっているlitOption
の定義を探してみた所litValue
というBigInt
を返却するメソッドが定義されていた。これを利用して、レジスタクラスに独自のtoString
を用意しよう。
class GP(bits: Int) extends BaseCpuReg { val data = UInt(bits.W) def write(wr_val: UInt): Unit = data := wr_val def read(): UInt = data override def cloneType: this.type = new GP(bits).asInstanceOf[this.type] override def toString(): String = f"0x${data.litValue}%02x" }
中身自体は簡単で、litValue
で取得したBigInt
をf補間子で見やすく整形しただけだ。同じような要領で、他のレジスタクラスにもtoString
を準備した。
あとはCpuReg
クラスにもtoString
を準備して、各々のレジスタクラスのtoString
を呼び出すだけでOK。
// 並べただけで、微妙なので後でキレイにする override def toString(): String = { s"""|a:${a.toString}/b:${b.toString}/c:${c.toString}/d:${d.toString}/e:${e.toString}/h:${h.toString}/l:${l.toString}/sp:${sp.toString}/pc:${pc.toString}/f:${f.toString}""".stripMargin }
このtoString
を用意した後のCpuTest
のログは次にようになった。
a:0x00/b:0x00/c:0x00/d:0x00/e:0x00/h:0x00/l:0x00/sp:0x00/pc:0x0100/f:0x00{z:0/n:0/h:0/c:0} a:0x00/b:0x00/c:0x00/d:0x00/e:0x00/h:0x00/l:0x00/sp:0x00/pc:0x0101/f:0x00{z:0/n:0/h:0/c:0} a:0x00/b:0x00/c:0x00/d:0x00/e:0x00/h:0x00/l:0x00/sp:0x00/pc:0x0102/f:0x00{z:0/n:0/h:0/c:0} a:0x00/b:0x00/c:0x00/d:0x00/e:0x00/h:0x10/l:0x00/sp:0x00/pc:0x0103/f:0x00{z:0/n:0/h:0/c:0} a:0x00/b:0x00/c:0x00/d:0x00/e:0x00/h:0x10/l:0x00/sp:0x00/pc:0x0104/f:0x00{z:0/n:0/h:0/c:0}
実際の信号の値だけを見ることができて、デバッグの効率が少しは良くなるかな?と思う。