こんにちは、パレイド技術部の夏目です。
ファミリーベーシックの実験を続けています。今回はファミリーベーシックでファミリーベーシックの実装が可能かという実験です。より汎用的には、ファミリーベーシックのプログラムだけで、ファミコンのエミュレータが実装可能か、と言う実証実験です。
結論としては、6502(CPU) だけなら完成しました。公式151命令が実機で動く。ただ、そこで止まりました。動かなかったのではない。RAMの限界で、プログラムを置く場所が無かったのです。
行き詰まって周辺を調べ直したら、いろいろと考えさせられる事実に突き当たりました。
ぶつかった問題は全部、ファミコンが発売された時点で既に解かれていました。
——ただ、その話は長くなるので後編にまわします。前編ではまず、作って、詰まるところまでを記録します。

本記事はローカル LLM による自動執筆パイプラインで生成されました。現段階ではクラウド AI(Claude 等)の補助や人間の編集が介在していますが、pareido.jp では最終的に AI が自律的にコンテンツを制作できる仕組みの構築を目指しています。
やろうとしたこと
Family BASIC は 1984 年のファミコン用カセットで、キーボードが付いてくる。BASIC が書ける。
そこで NES のエミュレータを書く。実用性はなく、ロマンを求めての思考実験。禁じ手もひとつ決めました。
機械語を書かない。BASIC で書く。
最低限必要な範囲で、PEEK と POKE はアリとしました。また、ROM に既にある機械語ルーチンを CALL で呼ぶのも、OS の API を呼ぶのと同じだと考えて許しました。後述しますが、LOAD / SAVE の仕様の壁でそうせざるを得なかった、というのが事実です。
1 台では能力も容量も足りないことは最初から分かっていたので、複数台をデータレコーダー経由で(SAVE/LOAD)で繋ぐ。エミュレーションとはいえ、使うのは 3.5mm のケーブルと純正の命令だけにした。実機でやろうと思えばできる、という状態を保つためでした。
CPU は完成
6502 の命令は 151 種類あります。素朴に IF の羅列で実装すると、 1 命令あたり 56.3 バイトで、全部で 8.6 キロバイト。1 台のファミリーベーシックには載りません。
そこで命令の並びを見ました。6502 のオペコードは aaabbbcc という形をしていて、3 つに割れます。
C = O MOD 4 グループ
M = (O/4) MOD 8 アドレッシングモード
F = O/32 演算
分割してから ON GOTO で飛ばす。すると、掛け算が足し算になる。 151 通りの分岐が、3 × 8 の表になる。

さらに削る余地もあります。アドレッシングの計算は 3 つのグループで共有でき、違うのは飛び先の対応表 1 行だけ。演算の共通の末尾(結果からフラグを立てて次の命令へ)は 1 か所だけ置いて全員そこへ合流させ、CPY と CPX は CMP の後半にそのまま合流できました。スタックは「積む」「降ろす」「ステータスバイトを 6 つのフラグに展開する」の 3 つの副ルーチンにして、PHA PHP PLA PLP JSR RTS RTI の全部で共有する形としました。
結果、1 命令あたり 19.3 バイト。161 行、2,921 バイトに収まりました。
これで動くことは、極小の 63 バイトのゲストプログラムで確かめられました。分岐(前向きと後ろ向き)、スタックの積み降ろし、サブルーチン呼び出し、レジスタ間の転送、比較、BIT、ジャンプ——ひととおり通る道を作って走らせ、16 項目すべてが期待値と一致しました。
| 検査した項目 | 結果 |
|---|---|
| ゲストのメモリ 5 か所 | 全一致 |
| レジスタ A / X / Y | 全一致 |
| フラグ N / Z / C / V / I / D | 全一致 |
| スタックポインタ | 255(積み降ろしが釣り合った) |

スタックポインタが 255 に戻ったのが個人的には嬉しかった。PHA と PLA、PHP と PLP、JSR と RTS が全部動作して、差し引きが釣り合っている、ということ。
速度も測ってみました。開発環境で、約 18 命令/秒(実機換算)。ファミリーベーシックの実機に換算すると、起動(389,691 命令)だけで約 6.0 時間、ファミコンの 1 フレーム(10,404 命令)で約 9.6 分となります。遅い。それは最初から織り込んでいました。速度は度外視して、実測した時間をそのまま出すと決めていましたが、やはり遅い。
面白かったのは、命令の内訳を変えても速度がほとんど変わらなかったこと。分岐と 1 バイト命令ばかりのループで 18.3 命令/秒、メモリを読み書きするループで 18.5 命令/秒。アドレッシングの計算と PEEK/POKE を余分に通っているのに、1 命令あたりの費用が同じ。つまりフェッチと分解の前段が支配的で、命令ごとの後段は誤差に埋もれている。設計が効いた場所と効かなかった場所が、数字で見えた。
器が足りない
Family BASIC V3 が持っているメモリは 4,086 バイト。
これは起動直後に PRINT FRE と打てば出る。そして起動直後は、その 4,086 バイト全部が BASIC のもの。ユーザーが自由に使える領域は 0 バイトから始まります。上限を持っているのはゼロページの $03/$04 で、起動時の値は $6FFF。CLEAR という命令でこれを下げると、下げたぶんがユーザー領域になる。
エミュレーションに必要となるプログラムを大きくすれば、ゲスト(ファミリーベーシックの上で動くファミリーベーシックの上で動くプログラム)に渡せるメモリが減ります。

インタプリタは、本文が 2,921 バイト、変数と GOSUB のスタックが 129 バイトで、合わせて 3,050 バイト。これが収まるいちばん下の CLEAR を選ぶと、ゲストに渡せるのは 1,024 バイト。これが上限です。
一方、スーパーマリオブラザーズを起動からタイトル画面まで(464 万命令)走らせて、実行機に何バイト常駐させれば、データレコーダー経由で連結された他のファミリーベーシックとの処理を何回跨ぐのか、を測ってみました。
| 実行機に常駐 | ページフォルト | テープ時間 |
|---|---|---|
| 1 KB | 44,875 回 | 11.2 日 |
| 4 KB | 22,190 回 | 5.5 日 |
| 12 KB | 65 回 | 23 分 |

12 KB が閾値。 そこを超えると、必要なものが自機上の手元にある状態になって、テープを跨ぐ回数が 65 回まで落ちます。
そして用意できるのは 1 KB。表のいちばん上の行だ。11.2 日。
ここで、書き方を工夫すれば何とかなるのではないか、と考えました。が、考えて、すぐにやめました。インタプリタが 0 バイトでも足りないのは自明です。
器であるRAMは 4,086 バイトしかない。作業集合は 12 KB。エミュレータ本体が一切場所を取らないと仮定しても、常駐できるのは 4,086 バイトが上限で、それは表の「4 KB」の行にあたる。22,190 回、5.5 日。
8 秒ぶんのゲームプレイに 5.5 日。これがあらゆる実装の理論上の最良値。
言語の問題でも、書き方の問題でもない。RAMの容量が最大の問題でした。
逃げ道を全部試した
行き詰まったので、外部記憶を使って何とか実装できないかを順に検討しました。
先に結論を書きます。評価軸は「用意(構想)した手段が、1台の常駐量を増やすか」です。速さではない。 転送を 100 倍速くしても、常駐量が 1 KB のままなら 44,875 回は 44,875 回のまま。
マシン語を書いてよいことにしたらどうか。 まず、ホスト自身が 6502 であることに気づいた。機械語を書いてよいなら、ゲストの 6502 命令はそのまま実行できる。エミュレータを機械語で書く理由は「ホストと違うメモリマップを見せる」「ホストより大きい記憶を見せる」の 2 つしかなく、器が 4,086 バイトである以上、その唯一の理由が成立しない。インタプリタが 3,050 バイトから 600 バイトに縮んだとしても、ゲストに渡せるのは 3,400 バイト程度。崖の 12 KB には遠い。考えながら、ふと気が付きます。そもそも、これを言い出したらエミュレーションの意味がありません。できないのではなく、やっても得るものが無かった。
ターボファイルはどうか。 アスキーが 1987 年に出した外部のバッテリバックアップ RAM で、初代で 8 KB ある。拡張コネクタに挿して、ソフトからはシリアルの装置として見える。これも効かない。理由は容量でも速度でもなく、CPU から番地で見えないからだ。シリアルの外部記憶なので、実行機が抱えられるメモリは 4,086 バイトのまま変わらない。44,875 回は減らない。 下がるのは 1 回あたりの費用だけで、壁の手前に留まる。(容量 8 KB では 12 KB の作業集合そのものが入らないし、ファミベのキーボードも拡張コネクタを使うので、そのまま両方は挿せない可能性が高い。それ以前の問題である。)
ディスクシステムだけが壁を突破できる。 RAM アダプタは $6000〜$DFFF の 32,768 バイトを持っている。ファミリーベーシックの 8 倍です。12 KB の作業集合が丸ごと入る。ディスクは片面 65,500 バイト。キーボードとも競合しない——キーボードは拡張コネクタ、RAM アダプタはカートリッジスロットで、口が違う。

問題は「ディスクシステムでファミリーベーシックは動くのか」です。結論、公式には出ていません。 ディスクシステム版のファミリーベーシック(V4.0 相当)は構想にあったようですが、製品化はされなかった。
ところが、非公式には実現されていた。三才ブックスの『バックアップ活用テクニック』誌上で、ある記事が紹介されています。ファミリーベーシックで小さなプログラムを書き、BASIC のシステム ROM 領域をカセットテープに保存する。それをトンカチエディター(ディスクの中身を 16 進で書き換える市販ツール)でディスクに書き込む。できたのは、約 16K バイトのプログラムメモリを持ち、ディスクのセーブとロードが使えるファミリーベーシック。ハードウェアの改造は要らない。ソフトだけで実現しています。
使われた道具は当時の非ライセンス市販品です。トンカチエディターはアイ・ツーが 1987 年 7 月 1 日に出したディスクシステム用の改造ツールで、ファミコン向けの改造ツールとしてはこれが最初らしい。同社の『子育てゴッコ』というバックアップツールと組で使うよう案内されていた——「本物のディスクを書き換えるのは危険だから、まず複製を取って、そちらを改造」ということですね。
この手法なら可能性があります。ディスクベーシックのプログラムメモリが約 16K バイト。インタプリタが 3,050 バイト。残りが約 13K バイト。崖が 12 KB。越える。5.5 日が 23 分になる。
また、ディスクシステムでは CHR が RAM になります。ファミベでは CHR は ROM で、512 枚のタイルを総当たりで調べたら「1 セルを 2×2 ドットに使う」ための四分割タイルが 16 種類中 2 つしか無かった。だから1 セル = 1 ドットが唯一の道で、256×240 を出すには 672 セルの画面が 92 台、格子にして 100 台要るだろう、という計算をしていました。CHR が RAM なら、タイルが好きに書ける。100 台を用意する理由も消えます。
ここで少し立ち止まります。これはファミリーベーシックだけでエミュレーションしているといえるのか?壁を越える唯一の道は、越えた瞬間に企画の意義を曇らせる。 しかもそれは技術の限界ではない。企画が自分に課した制約の限界である。制約が企画を作っていたことが、制約を外してみて初めて分かりました。
前編のまとめと、後編へ
前編でやったことを整理します。
- 6502 は BASIC だけで書き上げられた。 公式151命令、161 行、2,921 バイト。16 項目すべてが期待値と一致
- しかし置く場所が無い。 1 台の全メモリは 4,086 バイト。ゲストに渡せるのは 1,024 バイト
- これは実装の問題ではない。 インタプリタが 0 バイトでも足りず、あらゆる実装の最良値が「8 秒の再生に 5.5 日」
- 逃げ道は 1 本しかなく、それを通ると企画が終わる
ここで前提を守って、目的のほうを変えることにしました。「汎用のファミコン ROM を動かす」をやめて、多数の単純な機械が、1 台では絶対にできないことを作り出せるかを問い直します。
後編では、その問い直しの結果と、行き詰まって周辺を調べたら、ぶつかった問題が全部 1971〜1985 年に解かれていたという話を書きます。
付録: 前編で使った実測値
| 値 | |
|---|---|
| Family BASIC V3 の全メモリ | 4,086 バイト(起動直後のユーザー領域は 0) |
| BASIC で書いた 6502 | 公式151命令 / 161 行 / 2,921 バイト |
| 1 命令あたり | 19.3 バイト(素朴な形は 56.3 バイト) |
| 変数と GOSUB スタック | 129 バイト |
| ゲストに渡せる上限 | 1,024 バイト |
| 実行速度 | 約 18 命令/秒(実機換算) |
| 汎用 ROM の閾値 | 12 KB(65 回・23 分)/1 KB なら 44,875 回・11.2 日 |
| あらゆる実装の最良値 | 8 秒の再生に 5.5 日 |
ディスクベーシックに関する記述の出典は次のとおり。
- ファミリーベーシックと80年代当時の状況
- wizforest 日記 2019/02/21(ディスクベーシックの作り方)
- トンカチエディター(Wikipedia)