ゲームボーイを作るその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
ほんとは雑にレジスタの実装をやって、動かすところまで持って行きたかったんだけど、長くなってきたのでここで一区切り。 次こそはレジスタの実装を。。。