MC6800 ROMモニタ自作 その9 〜Windows上の簡易エミュレータとGitHub Actionsを整える〜

前回までで、SBC6800 実機上ではフェーズ4のローダまわりまで動くようになってきました。
ただ、実機確認はどうしても手間がかかります。ROM を焼いて差し替えて、Tera Term からコマンドを打って、必要なら S-Record や Intel HEX を流して、という流れになるので、ちょっとした修正のたびに回すには少し重いです。
そこで今回は、別の生成AIに手伝ってもらって作った SBC6800 の簡易エミュレータを見直しつつ、Windows 上でちゃんと動くように整えて、さらに GitHub Actions で自動実行できるところまで持っていきました。実機の代わりにはなりませんが、ROM モニタの日常的な確認にはかなり便利になりそうです。
今回やりたかったこと
- Windows 上で簡易エミュレータがちゃんと動くこと
- GitHub Actions で smoke test を自動実行できること
エミュレータ自体はすでに動くものがありましたが、Windows で実際に回してみると、timeout 時の出力消失、cp932 と相性の悪い文字、EOF 後の待ち続けなど、細かいところで少しずつ詰まりました。
Windows 向け smoke test を整えた
tests/test_smoke.py では、ROM モニタに対して最低限これくらいは見よう、という項目を一通り通します。
- 起動して
*とプロンプトが出る DコマンドでダンプできるMコマンドで値を書き換えられるGコマンドで実行してSWIで戻れるLコマンドで S-Record を読めるLコマンドで Intel HEX を読める- 不正コマンドで
?が出る
`==================================================
SBC6800 emulator smoke tests
==================================================
[PASS] test_boot_prompt
[PASS] test_dump_command
[PASS] test_modify_and_dump
[PASS] test_go_swi_return
[PASS] test_srec_load
[PASS] test_ihex_load
[PASS] test_error_display
Result: 7 passed, 0 failed`
timeout まわりで少しハマった
最初の実装では、subprocess.run() が timeout すると、途中まで正常に出ていた stdout を全部捨ててしまっていました。そのせいで、実際には * や ] がちゃんと出ていても、テスト側から見ると何も出ていないように見えてしまいます。
なので timeout 時は、TimeoutExpired が持っている途中経過の stdout / stderr をそのまま返すようにしました。これだけでも切り分けはかなりやりやすくなりました。
絵文字はやめて ASCII にした
Windows のコンソール環境では、✅ や ❌ をそのまま出すと UnicodeEncodeError になることがあります。なので今回は素直に [PASS] と [FAIL] に寄せました。派手さはありませんが、このほうが安定しています。
入力スクリプトの EOF で終了するようにした
入力ファイルを食わせて自動テストする場合、入力を使い切ったあとにエミュレータが終了してくれないと困ります。そこで、スクリプト入力モードのときだけ EOF 到達で終了するようにしました。
`def read_status(self):
"""ACIA ステータスレジスタを読む"""
if self._exit_on_eof and self._input_data is not None and self._input_pos >= len(self._input_data):
raise SystemExit(0)
status = ACIA_STAT_TDRE
if self._has_input():
status |= ACIA_STAT_RDRF
return status`
fixture ROM と最新 build を使い分けた
ローカルでちょっと確認したいだけなら fixture ROM は便利です。ただし GitHub Actions では、最新ソースからビルドしたものを検証しないと意味がありません。なので最終的には、ローカル簡易確認では fixture fallback を許可しつつ、CI では REQUIRE_BUILD_ROM=1 を付けて build/mc6800-monitor.bin を必須にする形にしました。
GitHub Actions も入れた
workflow の流れは次のとおりです。
- Python をセットアップ
third_party/asl/asw-1.42-Beta.zipを展開asl.exeとp2bin.exeでbuild/mc6800-monitor.binを生成python tests/test_smoke.pyを実行
ASL は外部サイトから毎回取りに行くのではなく、固定版 ZIP を third_party/asl/ に置く形にしました。外部依存を減らしたかったのと、毎回 curr を引くような不安定さを避けたかったからです。
githubのリポジトリのActionsはこちら↓↓↓↓で結果が見れます
GitHub Actions が最初うまく動かなかった
最初の失敗は、ASL ZIP の展開後ディレクトリ名を workflow 側で決め打ちしていたことでした。そこで、asw-* を前提にせず、展開後に asl.exe を再帰探索して、そこからルートを決める形に変えました。
さらにその次は、PowerShell 的には文字列になっている値に対して .FullName を読んでしまい、結果として空文字列を環境変数へ書いてしまう、というところでも引っかかりました。このへんはかなり地味ですが、ひとつずつ潰していくしかないですね。
checks が 2 本見える理由
PR には次の 2 本が見えます。
Windows Emulator Smoke / smoke (push)Windows Emulator Smoke / smoke (pull_request)
これは workflow が push と pull_request の両方を監視しているからです。ブランチへ push した時点の確認と、PR として main に対して評価したときの確認がそれぞれ走っています。
ローカル手順もドキュメント化した
Windows エミュレータと CI の手順は docs/testing/windows_emulator_ci.md にまとめて、README からも辿れるようにしました。
ここで大事なのは、「ローカルで普通にビルドする流れ」と「CI と同じ条件を再現する流れ」は分けて考えた方が分かりやすい、ということです。
普通にローカルでビルドする流れ
普段のビルドは Makefile を使えば十分です。たとえば Windows でも PATH が通っていれば make bin で ROM バイナリを作れますし、make で S-record と Intel HEX をまとめて作ることもできます。
`make bin`
Windows でこのリポジトリ内のツールを使ってビルドする流れ
今回は Windows 用の ASL 一式を third_party/asl/ に置いたので、Windows 環境でもこのリポジトリの中にあるツールだけでビルドできます。つまり、グローバルに別途 asl を入れていなくても、展開済み ASL があればそのまま mc6800-monitor.bin を作れます。
`Remove-Item -Recurse -Force build -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force build | Out-Null
$include = "$pwd\include;$pwd\src"
& '.\third_party\asl\asw-1.42-Bata\bin\asl.exe' -q -L -olist build\mc6800-monitor.lst -o build\mc6800-monitor.p -i $include src\main.asm
& '.\third_party\asl\asw-1.42-Bata\bin\p2bin.exe' build\mc6800-monitor.p build\mc6800-monitor.bin -q`
CI と同じことをローカルで再現する流れ
GitHub Actions では、固定版の ASL ZIP を展開して、そこから build/mc6800-monitor.bin を作ったうえで smoke test を回しています。なので、ローカルでも CI と同じ条件で見たいなら、まず最新ソースから build/mc6800-monitor.bin を作って、そのあと REQUIRE_BUILD_ROM=1 を付けて smoke test を回します。
`$env:REQUIRE_BUILD_ROM='1'
python tests/test_smoke.py`
ここで REQUIRE_BUILD_ROM=1 を付けると fixture ROM ではなく、必ず build/mc6800-monitor.bin を使うようになります。つまり、ただのローカル確認なら fixture fallback あり、CI と同じ確認をしたいなら build 生成物必須、という分け方です。
今回のまとめ
- SBC6800 簡易エミュレータの Windows 動作を確認した
- smoke test を Windows 向けに整理した
- timeout 時の途中出力を保持するようにした
- EOF で終了できるようにして自動テストを安定化した
- GitHub Actions で最新ビルド + smoke test を回すようにした
- ASL の固定版 ZIP を
third_party/asl/に置く形にした - 手順をドキュメント化して README から辿れるようにした
今後の予定
次は、このエミュレータをもう少し育てて、もっと細かい自動テストやコマンドごとの回帰確認につなげたいところです。実機でしか分からないことは当然ありますが、そこへ行く前の段階を PC 上で回せるだけでも、開発の流れはかなり軽くなりますね。