e-tipsmemo

ごった煮

Extract splite from NES file in Rust

懲りずにエミュレータを作りたいと考えている。
ファミコンエミュレータの創り方 - Hello, World!編 - - Qiita
HelloWorldするために、NESファイルを読み込みたい。


Qiitaの記事にもあるようにウォーミングアップとしてスプライトを表示したい。
GitHub - katakanan/nes-rom-reader

グラフィックライブラリ選び

NES本体自体も同じグラフィックライブラリで描くと思うので慎重選びたい。

OpenGLをラップしたようなcrateを利用しているプロジェクトもあるが、少しばかり速度を犠牲にしても、描写周りで消耗したくなかったので、ゲームエンジンライブラリを検討した。
(完成後に速さがたりなかったら入れ替えることも検討したい)

ゲームエンジンライブラリ選び?

Amethystとpistonとggezというのが検索上位だったが、Amethystもggezもゲームループを意識する感じでは無さそうなのが逆に邪魔になるかもというのと、サンプルプログラムが一瞬で動いたこともあって、piston_windowsで行くことにした。

またpiston_windowは使ったことのあるimage crateに依存しており、
image::ImageBufferをTextureに突っ込んでそのまま描写できて便利ということなので、それを使う。

カセット読み込み

INES - Nesdev wiki
INESのフォーマットは、少なくともmapper0だけに限れば、とても単純だと思う。

16バイト読んで指定バイトをサイズとしてprogram romとcharacter romを読む。

let mut header = [0u8; 16];

match f.read(&mut header) {
	Ok(read_size) => {
		if read_size != 16 {
			return Err(Error::new(ErrorKind::Other, "Failed to read header"));
		}
	}
	Err(_) => {
		return Err(Error::new(ErrorKind::Other, "Failed to read header"));
	}
}

let magic =
	String::from_utf8(header[0..3].to_vec()).unwrap_or("Magic is wrong!".to_string());

if magic != "NES" {
	return Err(Error::new(ErrorKind::Other, magic));
}

let prgrom_size = (header[4] as usize) * 1024 * 16;
let chrrom_size = (header[5] as usize) * 1024 * 8

エラーハンドリングはかなり適当でとにかく動けばヨシ!

スプライト

スプライト情報があれば、それを順番に表示する。

実際には4色セットを示すパレット番号が存在して、
メモリ上のパレット領域にパレット番号が格納されている。
描写時に描写位置に対応するパレット領域のアドレスからパレット番号を読み込んで、
その番号に対応する4色を使ってスプライトを色づけるが、
HelloWorldでは0~2色目は黒、3色目は白とした。

NES研究室 - サンプル
のHelloWorldのスプライトを表示したもの
f:id:katakanan:20210606213234p:plain

Nestest.nesのスプライトを表示したもの
f:id:katakanan:20210724195028p:plain

よさそう。
吸出し装置はnestestが通ったりしたら必要になると思う。

tasmotaを使用してみる

ちょっとネットワークに接続したデバイスを作りたいと思い、
手元あったESPxxxxマイコンの開発環境がどれくらい変化したかを確認することにした。

ESP8266EXが入っているWROOM-02を使いやすくしてあるモジュールで試す。
ESP−WROOM−02開発ボード: 無線、高周波関連商品 秋月電子通商-電子部品・ネット通販

Arduino IDE

以前使った時には、Arduino IDEを利用してArduinoのライブラリなどを利用することができた。
それは今回も変わらなかった。
IDEがアップデートされて、2.0-betaでは補完などが利用できるようになっているらしい。(が、試していない。)
f:id:katakanan:20210711195015p:plain

Tasmota/ESPhome

一般的にESPxxマイコンでやりたいことを5割ぐらいGUIでコンフィグすることで構成できるようにするっぽい環境も登場していた。


とりあえず Tasmotaを使う。

Tasmotaインストール

Getting Started - Tasmota
に沿って進める。
Pythonでの書き込みがかいてあるが、
それは飛ばして、Tasmotizerという書き込みアプリを利用する。

WROOM-02のボードをUSBで接続し、ドライバなんかを入れたらATコマンドが使用できる状態になっているはず。
そのあと、Tasmotizerを実行する。

COMポートとReleaseから一番ベーシックなファームウェアを選択する。
f:id:katakanan:20210711200409p:plain
f:id:katakanan:20210711201006p:plain

使用するESPマイコンモジュールによっては、フラッシュメモリの容量の関係で入らない可能性もあるらしい?

WROOM02はIO0をGNDにして、起動することで書き込みモードになる。
書き込みモードで、Tasmotizerの”Tasmotize!”をクリックする。

ちゃんと書き込めたら、WROOM02がAPモードになってアクセスできるようになっているので、
あとはInitial Configurationを進める
https://tasmota.github.io/docs/Getting-Started/#initial-configuration

WROOM-02を家のLANを構成しているWifiなどに接続させて、同一LANからWROOM-02に接続する。
IPアドレスは、Tasmotizerで"Get IP"をクリックすることでわかる。
f:id:katakanan:20210711210839p:plain

Lチカ via LAN

初期設定では以下の画面になっていると思われる。
f:id:katakanan:20210711201412p:plain
このコンフィグではsonoffというAC100VをLAN経由でON-OFFできるデバイスのための設定で、
中に、ESP82xxが使用されており、GPIO12(IO12)にリレーが接続されているようだ。
これかもしれない。(実物を持っていないのでわからない)
SONOFF BASICR2-WiFi DIY Wireless Smart Switch | SONOFF Official

ここにLEDを接続すれば"Toggle"ボタンで、LEDのON-OFFを切り替えられる。

コンフィグをいじる

では自分の設定で、ポートはどういじるのかという話で、
Main Menu-->Configuration-->Configure Templateから1個だけ?設定を保存できるらしい。

f:id:katakanan:20210711202317p:plain
f:id:katakanan:20210711202418p:plain

先ほどのsonoff Basic設定を見ると、GPIO12がRelayに設定されている。
f:id:katakanan:20210711202549p:plain

Generic (18)をベースに、
GPIO15をPWM 1
GPIO12をRelay 1
としてみる。Saveする。(LEDという設定もあるが、そのときはWebUIからではなくMQTTなどのリクエストで点灯可能?)
f:id:katakanan:20210711203414p:plain

そうしたら作成したModuleを選択する。
f:id:katakanan:20210711203014p:plain
f:id:katakanan:20210711203036p:plain
内部のサーバーが再起動したのち、Main menuのGUIが変わっている。
とりあえずわかりにくいが、以下のようなUIが生成されていた(実際には赤文字などはない)
f:id:katakanan:20210711203748p:plain

Toggleを押すことで、GPIO12 or 15のON/OFFが可能で、Statusのところはそれによって切り替わる。
スライドバーはPWMのDutyっぽいのを変更していると思われ、GPIO15に接続したLEDの明るさが変化する。波形は見ていない。

この設定は、Configure OtherのTemplateの文字列が同等のことを表現しているらしいので、これをコピーして別のtasmotaに入れれば、同じIO設定になると思われる。

感想

そもそもとして、Templateに自分が行いたいESPマイコンの動作と同じものがなければこれを使うのはそこまで楽ではないと思う。
コンフィグの幅としてはESPマイコンがESP86xxかESP32でIOの接続が違うとかその程度の差が吸収できる程度なのではないかと思う。

さらにここまでやって思ったのはWebから操作されるUIが生成されるのは便利だと思うが、別にそれは使いやすいわけではなかった。
(GPIOがどちらかわからない。ただしこれをArduino IDEで書くにのは大変そう)

実際にはMQTTやhttpのGETアクセスを受け取ることができ、GPIOが操作されたり、値を返したりすることができる。
よってLAN内の、Google HomeやAlexa、Home Assistantなどがそのリクエストを発行し、操作するというのが想定された使い方のようだ。(WebUIはおまけ?)
Google HomeもAlexaも持っていないのでわからない。MQTT Clientを入れればためせる。

MQTTリクエストが来た時のIR LEDの点灯パターンが違うとかになってくると、自分でファームウェアをビルドしなおす必要があるらしい。
UsersWiki : Tasmota で三菱エアコンを完全コントロール [nao-pon/blog/2021-02-07] - XOOPS マニア


本当はWiFiではなくRJ45でEthernet経由のESP32マイコンを簡単に利用したいのだが、それは可能なのか?不明
Support for ESP32 and Ethernet in TASMOTA | esp8266hints

Parse indent sensitive grammar in Rust with Pest

以前、パーサーを雑に比較した。
e-tipsmemo.hatenablog.com

結局Left-recursionは書き方で何とか対応できる範囲だと思われるので
Introducing pest into glsl and hindsight about nom vs. pest (part 1) – phaazon.net

(それ以外はどうしようもない)
インデントセンシティブなパース方法についてテストしていく。

pestでindent sensitiveな文法(pythonのような)をハンドルするには。
pest/lists.pest at master · pest-parser/pest · GitHub
PEEK_ALLとPUSHとDROPを使えばいいらしい。


まずはindentをスペースかタブが連続したものとして定義しておく。
(あとは適当なものを定義する)

indentation = _{(" " | " ")+}
id = {ASCII_ALPHANUMERIC+}
oneline = { (!" " ~ ANY)* }

今回の場合、同じインデントの深さを持つ連続するonelineが1つのブロックとして認識されてほしいのでLeft-recursiveの書き換えと組み合わせて

oneline_first = _{oneline}
oneline_continue = _{PEEK_ALL~oneline}
onelines = _{oneline_first~(" "~oneline_continue)*}
oneline_children = {PEEK_ALL~PUSH(indentation)~onelines~DROP}

oneline_firstが1個とoneline_continueが複数並ぶと書き換えることで、left-recursiveを避ける。

同じ要領で、インデントされた連続するmoduleを定義する。

moduleはonline_childrenを持つ。

circuit = {"circuit "~id~" :"~"\n"~module_children~"\n"?~EOI}
module = {"module "~id~" :"~("\n"~oneline_children)?}
modules = _{module_first~("\n"~module_continue)*}

module_first = _{module}
module_continue = _{PEEK_ALL~module}

module_children = {PEEK_ALL~PUSH(indentation)~modules~DROP}

これに

circuit hoge :
 module piyo :
  foo
  bar
 module baz :
  one
  two


こんな入力をいれたら

よさそう?
f:id:katakanan:20210624211033p:plain
pest. The Elegant Parser

まだこれだけではwhen ~ else ~のようなインデントの位置が戻るが、1つのブロックとなっている文法には対応できていないがそれはまた後でやる。

インデントスコープを区切る言語/設定ファイルは作る側にとっては楽かもしれんが、パースすることを考えたら面倒くさいんじゃないかなと思う。が、割と溢れている気がする。
とにかく自分の使いたいことを実現できそうなパーサーがあってよかった。

TD4 実機作成

TD4の実機を組み上げた。

ICを入れる前に洗浄した時の写真
f:id:katakanan:20210622194037j:plain
PCBは2018年のコミケで買ったものを使っている。

コミケのときに付属していた冊子
https://dip-factory.booth.pm/items/2089614

こちらが最新のよう
https://booth.pm/ja/items/2093868

画像を見た感じ、黒基板は青基板と違ってシルクの位置が調節されており、はんだ付けした部品が別の部品のシルクを隠してしまうといったことが無いように変更されているようだ。

トラブル?

はんだ付けを完了した直後は、クロックが変で、クロックの立上りと立ち下がりの両方で、プログラムカウンタが動いていたり、
プログラムカウンタに2, 3個の立ち上がりが入力されているような動きをしていた。(PC出力値が3→6→9→…となったり)

NOTについている入力保護の100kOhmが大きすぎるのか
とりあえず1kOhmにしたところちゃんと動くようになった。
1kOhmを裏にはんだ付け(並列)。
f:id:katakanan:20210622193827j:plain
これでクロックの立上りだけで、プログラムカウンタがカウントされるようになった。

オシロスコープでProgram Counterの入力を見ると、1.15Hzと10.7Hzだった。
f:id:katakanan:20210622191330j:plain
遅すぎてトリガーがかからない
f:id:katakanan:20210622191320j:plain

ギャラリー

f:id:katakanan:20210622193855j:plain
74HC138だけ急遽、秋月で買ったのでTI製ではない。

f:id:katakanan:20210622193749j:plain
眩しいのでLED拡散キャップをつけてもいいと思う。

時間がない人にオススメ

TD4 Emulator GUI in Rust iced

TD4のエミュレータを作った
e-tipsmemo.hatenablog.com

が、それだけではつまらないので、GUIを被せた。
RustのGUIライブラリicedを使用した。
シンプルそうだったからというのが選んだ理由。
GitHub - katakanan/td4-gui

Ticker

Icedは周期を指定してイベントを起こす機能が備わっているのでそれを使う。
サンプル StopWatch
この周期でエミュレータのfetch-decode-exec実行を回している。

    fn subscription(&self) -> Subscription<Message> {
        match self.state {
            State::Idle => Subscription::none(),
            State::Active => {
                time::every(std::time::Duration::from_millis(self.period)).map(|_| Message::Tick)
            }
        }
    }

Run-Stop-Step-Resetボタン

CPUの作り方の実機でもResetボタンとStepボタンがあった気がする。

        let run = Button::new(&mut self.run, Text::new("Run"))
            .padding(10)
            .on_press(Message::Run)
            .style(self.theme);

        let step = Button::new(&mut self.step, Text::new("Step"))
            .padding(10)
            .on_press(Message::Step)
            .style(self.theme);

        let stop = Button::new(&mut self.stop, Text::new("Stop"))
            .padding(10)
            .on_press(Message::Stop)
            .style(self.theme);

        let reset = Button::new(&mut self.reset, Text::new("Reset"))
            .width(Length::from(300))
            .padding(10)
            .on_press(Message::Reset)
            .style(self.theme);

Slider

1Hz動作だと遅いので、
Tickerの周期を変更できるようになっている。
10ミリ秒だとTickerの割り込みが多くなって、ボタン操作に影響が出ている気がする。
(UIと処理を別スレッドにしたりする必要があるが面倒なのでしない)

        let slider = Slider::new(
            &mut self.slider,
            100.0..=1000.0,
            self.period as f64,
            Message::SliderChanged,
        );

ビット操作

一番大変?だった?
まず最初はチェックボックスでこれを行おうとしたが、チェックボックスはメッセージにチェックされているかどうかと言うboolの値が1個しか送れないので、
チェックボックスに同じメッセージを設定すると、どのアドレスの何ビット目なのかを判別できない。

checkboxの引数の型がF: 'static + Fn(bool) となっている。
icedのライブラリ内

    pub fn new<F>(is_checked: bool, label: impl Into<String>, f: F) -> Self
    where
        F: 'static + Fn(bool) -> Message,
    {
        Checkbox {
            is_checked,
            on_toggle: Box::new(f),
            label: label.into(),
            width: Length::Shrink,
            size: <Renderer as self::Renderer>::DEFAULT_SIZE,
            spacing: Renderer::DEFAULT_SPACING,
            text_size: None,
            font: Renderer::Font::default(),
            text_color: None,
            style: Renderer::Style::default(),
        }
    }

Buttonではon_eventのメッセージにはEnumで定義されたメッセージ with Tupleを与えられるので、
順にaddr:usize、bit:u8、現在の状態:boolと決めておく。

#[derive(Debug, Clone, Copy)]
pub enum Message {
    Reset,
    Tick,
    Run,
    Step,
    Stop,
    RomEdit(usize, u8, bool),
    InputEdit(u8, bool),
    SliderChanged(f64),
}

ボタンをnewするときに同時にボタンの見た目を指定できるので、exampleにあったLightテーマとDarkテーマを1と0で切り替えて使う。
それをbit数だけ並べる。
method chainでbuttonのarrayを作ってRowにpushする。
(Rowはコンテンツを左から横にレイアウトしていく)

impl InputHalfByte {
    pub fn crate_layout(&mut self, value: &u8) -> Row<Message> {
        self.buttons(value)
            .into_iter()
            .fold(Row::new().spacing(1), |row, button| row.push(button))
    }

    fn buttons(&mut self, value: &u8) -> Vec<Button<Message>> {
        self.bit_state
            .iter_mut()
            .enumerate()
            .map(|(i, state)| {
                let bit = value & (0x01 << i) != 0;
                let btn = Button::new(state, bit2text(bit))
                    .on_press(Message::InputEdit(i as u8, bit))
                    .style(bit2style(bit));
                btn
            })
            .rev()
            .collect::<_>()
    }
}

romも8bitにしたものをColumnで並べる。

lifetimeやら所有権やらでここが一番よくわからなかった。
registerごとにgetとか実装したほうが良かったのか?
(エラーが出ないように修正しただけなので結局よくわかっていない)

LED表示

レジスタの情報の表示。

pub fn led4bit(halfbyte: &u8) -> Row<Message> {
    (0..4)
        .into_iter()
        .rev()
        .fold(Row::new().spacing(1), |row, i| {
            row.push(circle::Circle::new(
                10.0,
                bit2color(&((halfbyte & (0x01 << i)) != 0)),
            ))
        })
}

icedのexampleにあったcircleを改造したものを並べている。

完成したもの

いろいろとこだわれるところはあると思うが、これでおしまい。
バグに気づいたら直すかもしれない。
f:id:katakanan:20210531214050p:plain

感想

今更だけどRegとProgramRomが同じ階層にあるのはおかしかった。(動作には問題ないが)

エミュレータ本体を作るよりもGUIをいじるところが一番手間がかかってしまった。
Styleの色合いが微妙な感じだがそこを拘るのは更に大変そうなので気が向いたら
別のGUIアプリケーションを作るときには、お洒落にしたい。
icedはCross platformなのでwasmにすればブラウザでも動くと思う。

WSLでGUIの開発(調査)

RustとWSLでGUIの開発出来たら、Visual Studioはいらないよね。
と思って、色々試行錯誤したこと、調べたことのメモ。

前提

Windowsである。
WSLgが動く。

試行1. 普通に開発する(ものによる)

そもそもとして、RustのGUIライブラリのicedというものを使用してGUIアプリケーションを作成しようとしていた。
WSLgによって、LinuxGUIが見えるようになると聞いていたので、入れてみたが、icedが使用するRustのGUIライブラリであるwinitが動かなかった。

cargo run --package game_of_life
    Finished dev [unoptimized + debuginfo] target(s) in 0.11s
     Running `target/debug/game_of_life`
Error: GraphicsAdapterNotFound

ほかのGUIライブラリを使用したら動くかもしれい。
例えばpistonは動いたが、最大化で再描写がうまくいっていなかった。
ライブラリ側の対応がなされていくと思う。

試行2. x86_64-pc-windows-gnuでクロスコンパイル

Rustにはcrossというクロスコンパイルツールがあるので簡単にクロスコンパイルができる。
Mingwをインストールする必要がある。
また、その際、Dockerを要求されるので、

  • Docker for WindowsをWSL2連携機能を有効にしてインストールする。
  • ユーザーをdockerグループに追加する。

を行う必要があった。
そして、

cross run --target x86_64-pc-windows-gnu

とすればよい。
生成されたexeを起動しても、"libstdc++6.dllがない"と言われて起動できない。
f:id:katakanan:20210530215420p:plain
(おそらくWindows側にもmingwをインストールすれば起動できるはずだと思うが試していない。)
静的リンクしてみても変わらず。

これを使っても同じだった気がする。
https://github.com/est31/msvc-wine-rust

試行3. x86_64-pc-windows-msvcを使う(ダメ)

Linuxだけでは開発できなさそうだと思ったので、
普通にx86_64-pc-windows-msvcをターゲット指定してみた。
rustにlink.exeがないといわれるので、Visual StudioのBuildToolだけをインストールして、
C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30037\bin\Hostx64\x64
にあるところを、.config/config.tomlに指定すると途中までうまくいきそうになるが、
winsock2.hがないといわれてダメ。

これを使っても同じだった気がする。
https://github.com/strickczq/msvc-wsl-rust

結果

最終的にVisual Studio CommunityのBuildToolは入れてしまったので、
WindowsにもRustup-init.exeで環境をインストールした。
そしてWSLからWindowsにインストールしたcargo.exeを呼び出すという方法でアプリケーションをビルドした。(WSLの意味がない)

WSLのfishに以下のようなaliasを指定して、VScodeのビルドタスクを設定した。

alias wincargo='/mnt/c/Users/${USER}/.cargo/bin/cargo.exe'
{
	"version": "2.0.0",
	"tasks": [
		{
			"type": "shell",
			"command": "wincargo",
			"args": [
				"run",
				"--target",
				"x86_64-pc-windows-msvc"
			],
			"label": "Rust: wincargo run - hoge",
			"group": {
				"kind": "build",
				"isDefault": true
			},
			"problemMatcher": [
				"$rustc"
			]
		}
	]
}

まったく役に立たない記事を書いてしまった。

TD4 Emulator in Rust

思い立ったのでTD4のエミュレータを書いた。
GitHub - katakanan/td4-emu

命令セット

TD4の命令の定義部分。
オリジナルで定義されている命令以外はNOPということにする。

#[derive(Debug, FromPrimitive, PartialEq)]
pub enum Opecode {
    ADD2A = 0b0000,
    ADD2B = 0b0101,
    MOV2A = 0b0011,
    MOV2B = 0b0111,
    MOVA2B = 0b0001,
    JMP = 0b1111,
    JNC = 0b1110,
    INA = 0b0010,
    INB = 0b0110,
    OUTB = 0b1001,
    OUTIM = 0b1011,
    NOP,
}

ループ部分

    for _ in 0..10 {
        let (opecode, operand) = emu.fetch_decode();
        println!("{:?} {:?}", opecode, operand);
        let next_pc = emu.exec_mut(&opecode, operand);
        emu.reg.pc = next_pc;
        println!("{:?}", emu);
    }

丁寧にfetch, decode, execを分けたが、
巷のエミュレータというのはどうしているのかよく知らない。

実行部

命令名だけ見て実装した。本は斜め読みなので、間違ってるかもしれない。

            Opecode::ADD2A => {
                let tmp = self.reg.a + operand;
                self.reg.flag = (0x10 & tmp) != 0;
                self.reg.a = 0x0F & tmp;
            }
            Opecode::ADD2B => {
                let tmp = self.reg.b + operand;
                self.reg.flag = (0x10 & tmp) != 0;
                self.reg.a = 0x0F & tmp;
            }

4bitの足し算をして、繰上りは5bit目となる。
別の命令処理ではフラグをクリアする。

LED点滅

Emulator { prg: Mem { mem: [117, 144, 122, 144, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }, reg: Reg { a: 0, b: 0, flag: false, pc: 0 }, port: Port { input: 0, output: 0 } }
MOV2B 5
Emulator { prg: Mem { mem: [117, 144, 122, 144, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }, reg: Reg { a: 0, b: 5, flag: false, pc: 1 }, port: Port { input: 0, output: 0 } }
OUTB 0
Emulator { prg: Mem { mem: [117, 144, 122, 144, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }, reg: Reg { a: 0, b: 5, flag: false, pc: 2 }, port: Port { input: 0, output: 5 } }
MOV2B 10
Emulator { prg: Mem { mem: [117, 144, 122, 144, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }, reg: Reg { a: 0, b: 10, flag: false, pc: 3 }, port: Port { input: 0, output: 5 } }
OUTB 0
Emulator { prg: Mem { mem: [117, 144, 122, 144, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }, reg: Reg { a: 0, b: 10, flag: false, pc: 4 }, port: Port { input: 0, output: 10 } }
JMP 0
Emulator { prg: Mem { mem: [117, 144, 122, 144, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }, reg: Reg { a: 0, b: 10, flag: false, pc: 0 }, port: Port { input: 0, output: 10 } }
MOV2B 5
Emulator { prg: Mem { mem: [117, 144, 122, 144, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }, reg: Reg { a: 0, b: 5, flag: false, pc: 1 }, port: Port { input: 0, output: 10 } }
OUTB 0
Emulator { prg: Mem { mem: [117, 144, 122, 144, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }, reg: Reg { a: 0, b: 5, flag: false, pc: 2 }, port: Port { input: 0, output: 5 } }
MOV2B 10
Emulator { prg: Mem { mem: [117, 144, 122, 144, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }, reg: Reg { a: 0, b: 10, flag: false, pc: 3 }, port: Port { input: 0, output: 5 } }
OUTB 0
Emulator { prg: Mem { mem: [117, 144, 122, 144, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }, reg: Reg { a: 0, b: 10, flag: false, pc: 4 }, port: Port { input: 0, output: 10 } }
JMP 0
Emulator { prg: Mem { mem: [117, 144, 122, 144, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }, reg: Reg { a: 0, b: 10, flag: false, pc: 0 }, port: Port { input: 0, output: 10 } }

outputが0101と1010が交互になっているのでよさそう。

感想

エミュレータ自体は1日ぐらいで出来る程のシンプルなものだった。
ちゃんと命令をすべてテストしたわけじゃないので、バグがあるかもしれないので都度修正。
ついでに、ちょっと外側から使おうとdefault実装など入れたりした。

ほかのエミュレータにも挑戦したいかもしれない。
Rustで書いた人らはwasmなどにして、ブラウザ上で動かす&UIをつけている人もいる。

あとTD4の部品は買ってあるが実機はまだ作ってなかった。