MSXのメガロムの活用方法

### MSXのメガロムについて

数年前に書き換え可能のメガロム基板を手に入れたもののまったく活用できていません。
基板はこんな感じで容量は1Mbit(128KB)と4Mbit(512KB)があります。
メガロムコントローラはKONAMI形式のものです。(SCCは実装されていない）
![scc-rom.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288443/85f02742-e08d-7e1f-cbd6-48a80f446a97.jpeg)


大容量を活かしたゲームを作ってみたいと思うもののメモリアクセス方法が面倒なのです。
昔の人はどのようにソフトウェア開発していたのか気になりますが情報はほとんど公開されていません。
今回はメモリアクセス方法の調査と実際の開発方法を検討してみます。

---
### メガロムとは何か？

MSXやファミコンなどのゲームカセットで大容量を実現した技術になります。
大体1986年ごろに登場しました。
MSXやファミコン、セガSG1000などゲームソフトの配布はROMカートリッジが一般的でした。
当初は16KBや32KBのROMが主流です。
そもそも8ビットCPUはメモリ空間が64KBしかなくその中にROMを配置する関係で小容量のROMカートリッジが流通していました。
もっとも83年前後はROMそのものも高かったので当時としては必然でもあったのです。
でもグラフィックや凝ったプログラムを実現しようとするとメモリがすぐに足りなくなってしまいます。
MSXではページ１とページ２の合計32KBを使ったものが当時の最大容量でした。
ファミコンの場合はプログラムROM32KB+キャラクタROM8KBの合計40KBだったようです。

CPU的に64KBのメモリ空間しかないのに、それ以上の大容量をどのようにして扱うのか？

答えはバンク切り替えになります。
MSX的にはメモリマッパーに近いもので任意のバンクを特定のメモリ空間とスイッチします。
MSXのメガROMは1～8Mbitがありました。
理論的には16KB*256=4MB(32Mbit)も行けると思うのですが、それを実現した人がいるかは判りません。

---
### メガロムの種類

詳しくは以下のサイトに書いてあります。

https://www.msx.org/wiki/MegaROM_Mappers

日本で使われていた代表的方式は
- ASCII8(8KB単位)
- ASCII16(16KB単位)
- コナミSCC(8KB単位)
あたりでしょう。

実際のゲームでは8KB単位で切り替えるか、16KB単位で切り替えるかの2択になります。

8KB単位の場合は次のメモリ空間になります。

|ページ(8KB)|
|:--|
|4000h～5FFFh|
|6000h～7FFFh|
|8000h～9FFFh|
|A000h～BFFFh|

16KB単位の場合は次の感じですね。
|ページ(16KB)|
|:--|
|4000h～7FFFh|
|8000h～BFFFh|

---
### メガロムのアクセス方法

特定のメモリにセグメント番号を書き込むことでバンクを切り替えます。
注意する点はメモリマッパーと違ってスロットをROMが挿してあるところに切り替えてメモリアクセスする必要があります。

ASCII8の場合は次のようになってますね。

|ページ(8KB)|スイッチングアドレス|初期値|
|:--|:--|:--|
|4000h～5FFFh|6000h|0|
|6000h～7FFFh|6800h|0|
|8000h～9FFFh|7000h|0|
|A000h～BFFFh|7800h|0|

コナミSCCの場合は次のようです。

|ページ(8KB)|スイッチングアドレス|初期値|
|:--|:--|:--|
|4000h～5FFFh|5000h|0※|
|6000h～7FFFh|7000h|1|
|8000h～9FFFh|9000h|2|
|A000h～BFFFh|B000h|3|

※0固定

スイッチングアドレスはASCIIとコナミで互換性ないのですが、フラッシュメモリにアクセスするにはASCII形式の方がいいですね。
データを書き込むアドレスとスイッチングアドレスが重なっていると都合が悪いので。
手元にあるコナミ互換のフラッシュメモリは書き込み時にA000hのbit7を設定してモードを切り替える仕掛けを作っているようです。


---
### アクセス実験

早速メガロムアクセス用のテストプログラムを作りたいのですが、どのように作るか？
ここはいつも使い慣れているWebMSXを使います。
WebMSXはメガロムエミュレータも実装しているので簡単な実験ができるからです。(blueMSXでもいいけど）

まず準備はテスト用のROMを作ります。
ROMイメージは次のようにしました。

|配置アドレス|設定データ|
|:--|:--|
|4000h-400fh|41h,42h,0,0,…|
|4010h-5fffh|1,1,…|
|6000h-7fffh|1,20h,0,1,…|
|8000h-Bfffh|2,40h,0,2,…|
|8000h-Bfffh|2,60h,0,2,…|
|C000h-ffffh|3,80h,0,3,…|
|C000h-ffffh|3,A0h,0,3,…|
|10000h-11fffh|4,C0h,0,4,…|
|12000h-13fffh|4,E0h,0,4,…|

先頭に41h,42hを付けないとROMカートリッジとして認識できないのでつけています。
そのあと2000hごとに目印の値を入れています。

マシン語でメモリアクセスするのでWebMSXのASMには以下を入力します。

```
RDSLT EQU 000Ch
WRSLT EQU 0014h
org 0c000h
jp read
jp write
WDATA:DB 0

read:
PUSH HL
pop ix
PUSH HL
ld l,(ix+2)
ld h,(ix+3)
ld a,00000001b
CALL RDSLT
POP IX
LD (IX+2),A
XOR A
LD (IX+3),a
RET

write:
PUSH HL
pop ix
PUSH HL
ld l,(ix+2)
ld h,(ix+3)
LD A,(WDATA)
LD E,A
ld a,00000001b
CALL WRSLT
POP IX
XOR A
LD (IX+2),A
LD (IX+3),a
RET

```

BASICは以下のようにします。
```
10 clear200,&hbfff:definta-z:width32
20 bload"program.bin"
30 defusr=&hc000:defusr1=&hc003
40 for p=0to11
50 poke&hc006,p
60 a=usr1(&h7001):'KONAMI
70 a=usr1(&h6801):'ASCII 8K
80 print"page=";p;"DATA=";
90 fori=0to3
100 a=usr(&h6000+i)
110 print hex$(a);",";
120 NEXT:PRINT
130 NEXT
```
今回はスロット１にROMカートリッジ固定とします。
マシン語は以下に配置してBASICから呼び出します。

C000h=引数のアドレスから1バイト読み込んで値を返す。
C003h=引数のアドレスに1バイト書き込む。
C006h=書き込むデータ。C003hを呼び出す前に設定する。

このプログラムは6000h～7FFFhの部分をバンク切り替えしています。
64KBのダミーROMのため8KBが8個あり指定値は0～7が有効です。

実際の取得結果から正しく読めていることが確認できます。

![実行結果.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288443/6dc9bf60-f5f9-bb69-0261-15a6ca03bfca.png)


なおBASICの60行がコナミ設定、70行がアスキー設定ですが両方実行するとどっちのエミュレータ設定でも動作してしまいます。

![メガロム設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288443/4a39949c-119d-50da-d41d-df57e8c1f973.png)



---
### 利便性のいい方法はないか？

切り替え方法は理解したのですが、実際のゲームではどのように利用したらいいか。
いくつかの方法を考えます。


##### １．プログラムからダイレクトに切り替える

例えばプログラムはページ１(16KB)のみに配置し、ページ２(16KB)にグラフィックやサウンドなどのデータを切り替えて画面表示したり音を鳴らしたりする。
マシン語でガリガリ書いていく人はこの方式が単純であると思うが、プログラムが16KBに収まらなかったり、マップなどのデータが大きくて計算が多い場合は向かないかもしれない。

##### ２．ROM内データをメモリにロードする

たぶんこの方式はMSX2では多いと思う。
昔やったことですが、死霊戦線というゲームでゲーム中にポーズをかけてカートリッジを抜いてもすぐにはフリーズしなかった。
この状態画面切り替えなどでデータ読むときはカートリッジないとフリーズするので、ROM上のプログラムを直接実行しているのではなく、ROMをディスクの替わりとしてRAMに展開しているのだと思う。

同様なことを実践するには、ROMからメモリに転送するルーチンを作ったり拡張BASICを作ったりで対応できるはず。
この方式はメモリを多く使うのでMSX1では無理かもしれない。

この方式の難点は開発中はどのようにデバッグするか。
いちいちROMに書かなくても実行できるようにしないといけない。
もしかしたらDISKBASIC互換の命令を作るかもしれないし、マッパーメモリを仮想的なROMとみなして実行するかもしれないし。

マッパーメモリが256KBある機種を持っていれば、192KB分のマッパーをページ２切り替えてエミュレートするとか。
・・と言っても、やはりRAM/ROM切り替えの実装は面倒そうである。

##### ３．データ専用のROMイメージを作る

あらかじめROMに収録するデータはROMで固めてスロット２に挿しておく。
プログラムはディスク上で作るか、ROMイメージをスロット１に挿して動作検証をする。
2番との違いはメモリマッパーではないので8KB単位でスイッチングできること。プログラムのワークエリアはページ2も使えることになります。

ROMイメージの中でディスクのFATと同じようなファイル管理をしたりデータ圧縮解凍したりできるともっと利便性が上がりそうです。

---
### で、実際に作るのか？

いきなりメガロムではなくノーマルなロムでゲーム作ります。
ロムゲームのコツが掴めたら大作に挑戦したいですね。