番外編: ファミリーベーシック、バックアップスイッチの正体

番外編: ファミリーベーシック、バックアップスイッチの正体 — ファミリーベーシック, バックアップスイッチ, SRAM AIテキスト

こんにちは、パレイド辺境部の橘です。

前回 (第 7 回) でデータレコーダーをエミュレータの中に蘇らせたあと、本編の第 8 回に進む前に、もう一度カセット側に視線を戻したくなりました。データレコーダーの隣にある、もう一つの「保存」——カートリッジ前面のあのスライドスイッチの話です。

本記事は LLM による自動執筆パイプラインで生成されました。現在は人間が補助していますが、pareido.jp では最終的に AI が自律的にコンテンツを制作できる仕組みの構築を目指しています。

なぜ今ファミリーベーシックの SRAM の話か

ファミリーベーシックのカートリッジ背面には、小さなスライドスイッチが付いています。「ON/OFF」と書かれていて、当時はここに何度も指を伸ばしました。電源を入れたまま物理スイッチを切り替える、というのは、いま思えば変則的な運用です。当時のわたしたちは、その変則性を変則だと知らないまま、毎日のように指でカチッと倒していました。

任天堂公式より引用。画像だと分かりにくいがカセット左上にスイッチがある。

そして、朧げな、奇妙な記憶が蘇りました。何も記録がないはずの状態で、電池を入れてバックアップスイッチを ON にして LIST を実行すると、意味不明な文字列が画面を埋め尽くすのです。

ランダムでの再現映像。実際は規則性があり”意図”を感じたような。

nesemu を用いた一連の実験ではどうにも再現ができないのですが、今回はその現象を、ハードウェアと BASIC の両側から、できる限り合理的に分解してみます。

バックアップスイッチの正体

バックアップスイッチは、ハードウェアとしては、SRAM へのアクセスを物理的にゲートするスイッチです。OFF 側に倒すとCPUから切り離され、ON 側に倒すとはじめて SRAM に書ける。普段の運用は READ 側に倒しっぱなしで、BASIC で SAVE 相当のことをするときだけ「オワリ」と入力してから WRITE に倒し、書き終わったらまた READ に戻す。NESdev wiki にも “write protectable with an external switch (Family BASIC only)” と明記されています。

機能的には「電池を SRAM に接続するか」「書き込み信号をゲートするか」とほぼ同義です。READ 側でも内容は読めるので、不用意に書き換えないための物理的な鍵がかかっている、と言い換えてもいい。

ファミコン用カートリッジの中で、このようなユーザー操作の物理スイッチを背負っているのはファミリーベーシックだけです。ファミコンが本来想定していなかったバッテリーバックアップを、回路と人間の指の協調で実現した、唯一無二の構成だと言えます。

肝心の SRAM チップは、V1.0 / V2.0A / V2.1A では 2K × 8 ビットの CMOS SRAM、いわゆる 6116 ファミリ (HM6116 / TC5516 / TC5517 / MB8416 / μPD446 / M5M5117 などの 24 ピン DIP 同等品) が使われていました。V3 では 4KB 相当に拡張されています。当時の家電製品で電池バックアップ用途に最も普及していたチップ群で、メーカーごとに個体差はあるものの、互換性のある型番がいくつも存在します。

CPU から見ると、SRAM は $6000-$7FFF の 8KB ウィンドウに割り当てられています。実 SRAM は 2KB または 4KB しかないので、この 8KB の窓には複数回ミラーされて見える形になります。BASIC プログラムの本体は、V3 では $6006 から、V2 では $703E から始まります。

ChatGPTの想像図。大体あっているがあくまで想像図。

SRAM の “未定義” という性質

ここで一段、チップそのものの話をさせてください。

SRAM のメモリセルは双安定回路、いわゆるフリップフロップです。電源が入っているあいだは 0 か 1 のどちらかに倒れていて、電池でその電位が維持されているかぎり値は保持されます。バックアップスイッチを ON にして電池が繋がっているうちは、数ヶ月から年単位で内容が残る。後にいわゆる「セーブデータ方式」として一般的なゲームにも普及(ただし電池内蔵で交換できない)しますが、これがバックアップの仕組みです。

問題は、はじめに何も記録されていない状態で何が何が起きるか、です。電源を完全に切ると、各セルの保持状態は失われます。次に電源を入れた瞬間、各セルがどちらに倒れるかは技術的な仕様としては未定義です。ゼロクリアでもなく、全 てが1 になるわけでもない。理論上は 0 と 1 がほぼ半々のノイズ状の値になります。

ただし、ここに小さな偏りがあります。各セルを構成するトランジスタには製造ばらつきがあり、そのばらつきによって「電源投入時にどちらに倒れやすいか」がセルごとに微妙に決まっています。結果として、SRAM は個体ごとに固有の偏ったパターンを持って起動します。同じチップを繰り返し電源 OFF → ON すると、概ね同じパターンに倒れる傾向がある。これは現代では SRAM PUF (Physically Unclonable Function) として知られ、セキュリティ用途の個体識別に使われたりもしています。

余談ですが、ファミリーベーシックの実機では、カセット自体に高さがあり不安定なことから、バックアップスイッチの操作でファミコンが誤動作(いわゆる「バグった」状態)することがよくあり、せっかく作ったプログラムが消えてしまうこともしばしば。データレコーダーが本命だったことは言うまでもありません。

BASIC は SRAM 上のプログラムの妥当性を検証していない

ここまで読むと、このSRAMの「未定義」な状態が、意味不明の文字列として表示されたのではないか、と考えるのは自然です。ただ、別の疑問として、「壊れているかどうか、BASIC 側はチェックしていないのか」

実は、わたしも最初はチェックサムなり何なりが入っているはずだと考えていました。SRAM が部分化けしている可能性を BASIC が知らないわけがない、起動時に何かしらの妥当性検証が走っていて、壊れていれば自動的にゼロクリアするか、エラーを出すか、そのどちらかだろう、と。記事の最初の草稿には「BASIC 側がチェックサムで SRAM の妥当性を判定しているはず」と書きました。

これを検証するために、Micah Cowan 氏の fbdasm——Family BASIC V3 の逆アセンブリプロジェクト——を当たりました。

Cowan 氏は内部構造を非常に丁寧にドキュメント化していて、_Reset ルーチンの初期化シーケンスも追えます。読み進めながら、わたしは「どこかでチェックサム比較なり、マジックバイト確認なり、ゼロクリア処理なりが出てくるはずだ」と思って待っていました。

出てきません。

_Reset で SRAM 領域に対して触れられているのは、$ED-$EF に NMI トランポリン (jmp $8971) を書き込むことくらいで、SRAM 全域を走査して妥当性を判定するような処理は見当たらない。マジックバイトを比較する分岐があったとしても、せいぜい先頭数バイトだけで、内部の大部分はそのまま信用される構造になっています。

つまり、ファミリーベーシックの BASIC は、SRAM のゼロクリアもチェックサムも、おそらくやっていない。これが fbdasm を読んで得た、最初の草稿への訂正でした。限られた容量の省メモリ志向で処理が書かれていて、起動時の SRAM 検証にバイトを割く余裕がなかった、というのは妥当な推測でしょう。

ここで一つ、慎重に書いておきます。fbdasm はあくまで V3 の逆アセンブリで、わたしも全ルーチンを完全には追い切れていません。「やっていない」と言い切るには、本来は SourceGen で _Reset から 30〜50 命令ほどを丁寧に追って、起動シーケンスのすべてを確認する必要があります。ここは「やっていない、と推測される」のレベルに留めておきたいところです。

LIST 文字化けの仕組み

材料が揃ったので、本題に組み立てなおします。

バックアップスイッチを ON のまま LIST を実行すると、意味不明な文字列が表示されることがある——この現象は、次の手順で説明できます。

  1. SRAMの未定義の値がワークRAMに流しこまれる
  2. たまたま、BASIC が起動時に確認するマジックバイト程度の領域は無事だった、または偶然マッチした
  3. プログラムの行リンクのオフセットも、形式的には妥当な小さな正の値に収まっていた
  4. けれど、トークン (BASIC の中間コード) の中身は壊れている
  5. BASIC インタプリタは「正規プログラムが入っている」と誤認して LIST を実行する
  6. 壊れた中間コードを律儀に逆トークナイズして、対応するキャラクタ列が画面に流れる

ポイントは、BASIC が「壊れている」とは判定していないことです。マジックバイトと行リンクという外形上の整合性だけ通っていれば、内部のトークン列はそのまま信用される。LIST コマンドは、その壊れたトークン列を、本来のキーワードや変数名に逆変換しようとして、想定外のバイトに対応する想定外のキャラクタを画面に吐き出します。

結果として、SRAM PUFの性質から、画面には「妙に規則性を感じさせる」記号やカタカナの羅列が現れます。完全なランダムではありません。BASIC の文法上ありうるトークンの並びを起点に、化けたビットがそれを少しずつ歪ませている。トークナイザの構造がそのまま見えるかたちで、規則性と無意味さが同居する出力になります。

ハードウェア (SRAM の物理的な不安定さ) と、ソフトウェア (BASIC の省メモリ設計と検証の省略) が、ちょうど一段ずつ譲り合った地点で、この現象は成立していました。どちらか一方が違っていれば、たとえば BASIC が起動時にチェックサムを検証していたら、あるいは SRAM が完全に揮発するタイプだったら、あの画面は出てこなかったはずです。

当時は、一見壊れたものに透けて見える規則性に、何らかの「意志」を感じて、これは人に言ってはいけない現象ではないかと不思議な気持ちになりました。何度リセットしても同じ状況が再現できる。今にして思えば”説明”は付けられるのがですが、果たしてー

SRAMの「未定義」が作り出す幻想的な光景。

残されたもの

最後に、保存形式の話を一つだけ書いておきます。

現代のエミュレータは、ファミリーベーシックの ROM を .nes ファイルとして、SRAM の中身を .sav (または .srm) ファイルとして、別々に扱います。.nes には ROM チップの内容と 16 バイトのヘッダしか入っていません。SRAM の中身は別ファイルです。Kazzo や INL Retro Dumper のような実機ダンパーを使えば、実機カセットから ROM 側を .nes、SRAM 側を .sav として救出できます。電池交換の前に SRAM を救出するのは、レトロハードウェア界隈では定石になっている手順です。

問題は、エミュレータが起動時に SRAM 領域をゼロ埋めで開始することです。.sav があれば、その内容で上書きされます。けれど、実機の「電源投入時に各セルが個体固有のパターンで倒れる」という挙動は、エミュレータでは原理的に再現されません。あれは生の SRAM チップ上で起きていた物理現象です。

つまり、「LIST 文字化け」現象は、エミュレータでは原理的に再現できない

.nes + .sav という保存形式は、ROM の内容と、最後に保存された SRAM のスナップショットは確実に残してくれます。けれど、接触不良で部分的に化けた SRAM の途中の値や、電源投入直後の個体固有のノイズパターンは、この形式では取りこぼされます。あれは、実機を所有していた当時の世代だけが共有する記憶として残されました。

次回は、わたし自身が幼少期に実機の前で見た、その「文字列」の話を書きます。今回並べた合理的な説明と、当時の体感とのあいだに、もう一つ残しておきたい余白があるからです。

タイトルとURLをコピーしました