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

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

ゲームボーイを作る(12) - レジスタ表示の改善

スポンサーリンク

ゲームボーイを作るその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レジスタを表示している。CpuRegBundleを継承したものなので、クラス内で定義されている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}

実際の信号の値だけを見ることができて、デバッグの効率が少しは良くなるかな?と思う。