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

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

ゲームボーイを作る(8) - テストROMビルドのsbtタスク化

スポンサーリンク

ゲームボーイを作るその8。前回に引き続き、ハードウェアの検証環境を整備していく。と書きながら、今回はバイナリ→HEX変換のタスク化の話。

テストROMバイナリ→テストHEXファイル変換

前回書いたとおり、CPUと接続するメモリはChiselのメモリではなく、VerilogのRTLをブラックボックスで使うことに決めた。 この場合、テストのROMデータはVerilog$readmemhでロードすることになるので、ビルドしたバイナリデータを$readmemhで読み込める形に変換してやる必要がある。

今回の裏目標として「可能な限りsbtで全部やってみる」というのがあるので、このあたりの変換処理もScalaで書いてみようと思う。

Scalaを使ってバイナリ→HEX変換処理

Scalaでバイナリデータを1byteずつ読み込んで書き出す、というコードを実装したのが次のコードだ。 とりあえず、ここではsbtからrunMainで実行できるようにAppトレイトを使っている。

import java.io.PrintWriter
import java.nio.file.{Files, Paths}

object Convert extends App {
  val path = args(0)
  val byteArray = Files.readAllBytes(Paths.get(path))

  val outPath = path + ".hex"
  val pw = new PrintWriter(outPath)

  byteArray.zipWithIndex.foreach {
    case (i, idx) => {
      val sep = if ((idx % 0x10) == 0xf) "\n" else " "
      pw.write(f"$i%02x${sep}")
    }
  }

  pw.close()
}

ちなみにこのコードを書いている時に、次の記事を見つけた。Chiselが移行するのはまだ先の話だとは思うけど、mainを明示的に書くようにしよう。。(上のコードはサンプルということでこのまま)

sbtのタスク実装

テストコードを作成or修正した際に、毎回ビルドとバイナリの変換処理を実行するのは面倒なので、最初のうちにsbtのタスクとして実装しておく。

ひとまず、ビルド対象とするテストコードはsrc/test/resources/cpu以下の*.sに絞る。

wla-bgを使ったビルドから、HEXファイルの生成までを実装したタスクが次のコードになる。後でもう少し整理予定。

lazy val convertBin2Hex = taskKey[Unit]("Convert test binary to Hex data")

convertBin2Hex := {
  import java.io.PrintWriter
  import java.io.File
  import java.nio.file.{Files, Paths}
  import scala.util.matching.Regex
  import scala.sys.process._

  val path = "src/test/resources/cpu"
  val fileList = (new File(path)).listFiles()

  fileList.withFilter { file =>
    // get file extension
    val fileName = file.getName()
    val extenstion = try {
      Some(fileName.substring(fileName.lastIndexOf(".")))
    } catch {
      // 拡張子無しのファイルだと例外が送出されるので
      // その例外のみキャッチして、Noneにする
      case _: IndexOutOfBoundsException => None
    }

    // 例外時はNoneになってる→getOrElseで空文字扱い
    if (extenstion.getOrElse("") == ".s") true else false
  }.foreach { testSourcefile =>
    // build test ROM code
    val workDir = testSourcefile.getParent
    val romObjPath = s"${workDir}/test.o"

    val romObj = new File(romObjPath)

    if (romObj.exists == true) {
      println(s"${romObj.getPath} is exists, so remove it")
      romObj.delete
    }

    // compile
    val wlaBuildResult = s"wla-gb -o ${romObjPath} ${testSourcefile.getPath}" !!

    // link
    val linkFilePath = s"${workDir}/linkfile"
    val gbFilePath = s"${workDir}/${testSourcefile.getName}.gb"
    val wlaLinkResult = s"wlalink -d -v -S ${linkFilePath} ${gbFilePath}" !!

    // convert binary to hex
    val byteArray = Files.re
    adAllBytes(Paths.get(gbFilePath))
    val outPath = gbFilePath + ".hex"
    val pw = new PrintWriter(outPath)

    byteArray.zipWithIndex.foreach {
      case (i, idx) => {
        val sep = if ((idx % 0x10) == 0xf) "\n" else " "
        pw.write(f"$i%02x${sep}")
      }
    }

    pw.close()
  }
}

これをsbtのシェルから実行すると、`src/test/resources/cpu/01_ld.s.gb.hex"が生成される。

sbt:chisel-dmg> convertBin2Hex
src/test/resources/cpu/test.o is exists, so remove it
link = src/test/resources/cpu/linkfile
gbFilePath = src/test/resources/cpu/01_ld.s.gb
-------------------------------------------------
---                   ROM                     ---
-------------------------------------------------
ROM bank 0 (16375 bytes (99.95%) free)
  - Free space at $0000-$00ff (256 bytes)
  - Free space at $0108-$0147 (64 bytes)
  - Free space at $0149-$3fff (16055 bytes)
ROM bank 1 (16384 bytes (100.00%) free)
  - Free space at $0000-$3fff (16384 bytes)
-------------------------------------------------
---                   RAM                     ---
-------------------------------------------------
No .RAMSECTIONs were found, no information about RAM.
-------------------------------------------------
---           HEADERS AND FOOTERS             ---
-------------------------------------------------
No headers or footers found.
-------------------------------------------------
---                 SUMMARY                   ---
-------------------------------------------------
ROM: 32759 bytes (99.97%) free of total 32768.
RAM: No .RAMSECTIONs were found, no information about RAM.
hexFilePath = src/test/resources/cpu/01_ld.s.gb.hex
[success] Total time: 0 s, completed 2021/07/22 22:23:03

前々回に”linkfileを流用した”と書いたのだが、sbtのタスクでビルドしようとすると、オブジェクトファイルのパスが合わなくてエラーになったので、次のように修正した。

  • src/test/resources/cpu/linkfile
[objects]
src/test/resources/cpu/test.o

ほんとは雑にレジスタの実装をやって、動かすところまで持って行きたかったんだけど、長くなってきたのでここで一区切り。 次こそはレジスタの実装を。。。