使ったことがなくて、クロスコンパイラがあるCPUのエミュレータを作って楽しむシリーズ、今回はTMS9995です。

マニュアル
https://archive.org/details/bitsavers_tiTMS9900T_3790070

16ビットですが、メジャーな8086や68000とは一味違います。
ビッグエンディアンで、MSB側がビット0と定義されています。
レジスタはPC,WP,STの3つしかありません。
実際のレジスタはWPが指すメモリ上の16ビットx16本という仕組みです。
バイトデータを扱うときは、レジスタの上位が有効、下位は保存されます。これは変わっていますね。
古いCPUのシフト命令は1ビットだけというのが多いですが、レジスタで指定したビット数シフトすることもできます。
# バレルシフタではありません
注意が必要なのが、シフトカウントが0の場合、16回シフトすることです。これも変わっています。
余談ですが、エミュレータを作り始めたときは上記のものではなくTMS9900のマニュアルを見ました。
INSTRUCTIONSの部分が6ページしかなく、前回のCDP1802に比べてあっさりしすぎていて戸惑いました。

インストール

ディレクトリ/opt/cc9995を作ります。自分が書き込めるようにしておけばインストール時のsudoは不要です。

まずはbinutils

https://github.com/kwhr0/Fuzix-Bintools

からCodeのZIPファイルをダウンロードします。
unzip Fuzix-Bintools-main.zip
cd Fuzix-Bintools-main
make
sudo mkdir /opt/cc9995/bin
sudo mv *9900 /opt/cc9995/bin/
次にコンパイラです。

https://github.com/kwhr0/pcc-tms9995

からCodeのZIPファイルをダウンロードします。
unzip pcc-tms9995-main.zip
cd pcc-tms9995-main
autoconf
./configure --target=tms9995-fuzix
make
途中、cc2のところでエラーで終了しますが、必要なファイルはできているので問題ありません。
sudo mkdir /opt/cc9995/lib
sudo mv cc/ccom/tms9995-fuzix-ccom /opt/cc9995/lib/tms9995-ccom
sudo mv cc/cpp/tms9995-fuzix-cpp /opt/cc9995/lib/tms9995-cpp
さらにサポートファイルをインストールします。
/opt/cc9995/binにパスを通しておきます。

https://github.com/kwhr0/pcc-tms9995-support

からCodeのZIPファイルをダウンロードします。
unzip pcc-tms9995-support-main.zip
cd pcc-tms9995-support-main
make
sudo make install
もう一つ、逆アセンブラをインストールします。

https://github.com/endlos99/xdt99

からCodeのZIPファイルをダウンロードします。
unzip xdt99-master.zip
mv xdt99-master /opt/cc9995/xdt99
以上で準備完了です。

コンパイラのクセ

あまり更新されていないコンパイラでは、バグを避けて書く必要があります。
作業中に遭遇したものについて以下に記します。

関数の引数は最初の二つがr4,r5で、あとはスタックに積まれます。
...を使った場合もこれは変わらないので、printfのように二つ目に...を使うと正しく動きません。

シフト演算子の右項がcharサイズだと内部エラーが出ました。
また、右項が定数ではなく値が0の場合、16回シフトしてしまいます。
(後述のエミュレータではTMS9995の仕様の方を改変して、0回シフトにしています)

long変数を使ったとき、保存すべきレジスタを破壊するコードを吐いたことがありました。

ヘッダのインクルードを忘れるなどでプロトタイプ宣言がなくてもエラーが出ないので、
引数がcharサイズの場合、intとみなしてコンパイルされます。
他のプロセッサだとこれでも動く場合がありますが、
TMS9995はバイトサイズだとレジスタ上位を使うのでレジスタ渡しの場合バグになります。

バイトサイズの変数と定数を比較する場合、
unsigned char c;で
c < 0x80
はなぜか8ビット符号付きの比較になります。
c < 0x80U
と、見慣れない書き方をすると動きます。

他に、リンカがライブラリの中のオブジェクトを見つけられずにエラーになることがありました。

エミュレータ

タイマや内蔵RAMを除く、CPUコアのみ実装しています。
また、シリアルI/Oなどの、コンパイラが吐かないインストラクションを省略しています。
クロック数は全てノーウェイトのオフチップアクセスとしてカウントしています。

後述のサンプルのために、エミュレータの画面を縦長の96x160ドット、モノクロにしました。
VRAMは2kバイト弱のダブルバッファとし、I/O込みでF000-FFFFを使います。
サウンドはSN76489A相当です。ちょっと仕様は違いますが元々はTMS9919というチップだったそうです。
ゲームコントローラに対応しています。カーソルキーとX,Zキーで代用できます。

https://github.com/kwhr0/emu9995

からCodeのZIPファイルをダウンロードします。
unzip emu9995-master.zip
cd emu9995-master
make
とし、 出来上がったemu9995をパスの通ったところに置いておきます。

サンプル

xevious8001を移植しました。
xevious8001は描画部分をZ80アセンブラで書いたのですが、VRAM構成が違うのでCで書き直しました。
その他の大部分はコンパイラのバグを避けつつ、ほぼそのまま使えました。
最初はビットマップにBGを描く処理が一番重かったのですが、エミュレータのタイムプロファイラを見ながら最適化しました。

初版のプロファイル
56.7% memmove
32.8% spriteDraw:sprite.o
 4.5% spriteUpdate:sprite.o
 4.5% spriteFrame:sprite.o
 1.5% behaviorChr:chr.o
memmoveはBGのスクロールです。
最適化後は以下のようになりました。
45.0% spriteDraw:sprite.o
20.0% memmovew:base.o
 6.7% bgDraw:bg.o
 5.0% spriteUpdate:sprite.o
 3.7% cret
 3.6% spriteHits:sprite.o
 2.8% get1bit:bg.o
 2.0% center0
 1.5% spriteVisible:sprite.o
 1.5% spriteFrame:sprite.o
 1.4% behaviorChr:chr.o
memmoveのワード限定版を作ったり、転送量を減らす工夫をしたりしました。
spriteDrawの割合が他が軽くなったぶん相対的に増えていますが、まぁこんなもんかなと。

https://github.com/kwhr0/xevious9995

からCodeのZIPファイルをダウンロードします。
unzip xevious9995.zip
cd xevious9995-master
make
でa.outが生成されます。
map.bmpの圧縮が雑なのでメモリは3kバイトくらいしか空いていません。
emu9995 -c 10 a.out
で10MHzで動きます。これくらいで処理落ちしませんでした。
Z80では描画処理、サウンドの割込みルーチンをアセンブラ化しても20MHz程度でないと処理落ちしていました。

sp.cの#define NOHITを1にすると、衝突しても自機を四角く表示するだけで減りません。
操作しなくても一通り眺めることができます。
game.cの#define WAITFRAMEを0にすると、VSyncを待たなくなるので指定したクロックで最高速で走ります。

M1 Macで最大1500MHz程度まで上げられました。