
2026/04/18 21:41
1960 年代に登場したユニバーク(Univac)コンピュータ上でマインクラフトサーバーを運営し、他にも多くのことを試みる
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
専門チームが、複雑な現代ソフトウェア——NES エミュレータ、OCaml インタープリタ、BASIC、暗号素子(Curve25519 および AES)、ウェブサーバー、Minecraft ログインクライアントなど——を、稀有な 1960 年代製 UNIVAC 1219B で成功裏に動作させた。この機械は、Johns Hopkins 大学から Vintage Computer Federation のメンバーによって救出され、残存する 2 台の UNIVAC 1219 のうち現在稼働している唯一の装置である。米海軍によるレーダー信号解析および砲兵方向指令のために当初構築された本機は、現代ネットワークではなく Model 35 テータイプ(stdin/stdout)を介してインターフェースする。システムは 18 ビットの単語を使用し、一の補算術理を採用しており、メモリーは 10 バンクにわたり全体で単語 40,960(90KB)である。デバッグには、IO チャンネル 4 で発生する虚偽の割り込みを処理する必要があり、そのためには当該チャンネルを切り離し、Teensy というシリアルアダプタを使用してデータロードを行った。パフォーマンスが本来存在しない状態を実現するために、開発者は Rust ベースの RISC-V エミュレータを構築し、ハードウェア速度の 400 倍で動作させて C コードのコンパイルを可能にし、さらにカスタムの RISC-V から UNIVAC アセンブリへのバックエンドを実装した(〜40 回の UNIVAC 操作ต่อ 1 回の RISC-V 操作)。またジャンプテーブル、死コード除去、OCaml マクロシステムを採用し、これにより追加で約 30 倍のパフォーマンス向上が実現され、NES のフレーム描画時間が数時間から数分に短縮された。チームはさらに、当該システムのユニークな 18 ビット一の補算築のためのカスタム GCC/LLVM ターゲットを構築し、38 つの基本的な RISC-V インストラクションを UNIVAC アセンブリで実装するとともに、検証のために差分ファッザとトレーサーを使用した。これにより、PPP/IP/TCP をシリアル経由でホストした Minecraft ログインサーバーを成功裏に稼働させ、TTY において carriage return を複数回送信することで「overstrike」ASCII アートも出力することに成功した。Nathan、Duane、Bill、Steven、TheScienceElf のメンバーによる約 8 ヶ月間の作業(2025 年 4 月の VCF East 訪問を含む)を通じて、このオープンソース・プロジェクトは現在、極端な組み込みシステムおよびレガシーコンピューティングの制約に関する重要な参照源として機能している。
本文
ご紹介します!私が 1960 年代に製造された UNIVAC 1219B というコンピューター上で Minecraft サーバーを稼働させているところを見てください:
- 初回フレームのレンダリング:ピンボール (Pinball) のゲームを模擬する NES エミュレーターの出力です。
- ……そして、オーバータイプ(overstrike)技法を用いて印刷された自撮り画像もこちらです。
他にも非常に多くの驚くべき試みを私たちは行いました:
- OCaml プログラム(信じ難いですが、本当に実行されています!)
- ウェブサーバー
- Curve25519 暗号化および AES 暗号化の実装
- BASIC インタプリター
- ELIZA(初期のチャットボット)
- オレゴン・トレイルやワードル (Wordle)、バットルシップなどのゲーム
そして他にもっとあります!これらすべてを、わずか 90 KB の RAM を搭載した動作クロック 250 kHz という 1960 年代のコンピューター上で実現いたしました。私はこのような分野に情熱을注いでいます!異端な場所でコードを実行したり、技術的な限界を押し破ったりすることに中毒症状を起こしています。このプロジェクトは私がこれまでに取り組んできた中で最も野心的なものであり、8 ヶ月間の私の個人での作業と他の貢献者の方々の尽力によって成し遂げられました。
プロジェクトのソースコードはこちらで公開されています。また、TheScienceElf 氏による同プロジェクトに関するビデオもご覧ください!
UNIVAC は不思議なマシンです
UNIVAC 1219B は極めてユニークなマシンであり、現代のプログラミング手法に対してほぼすべての点で非協力的です:
- 18 ビットワード:メモリアドレスと値そのものが 18 ビット!これは 2 のべき乗でもない奇妙な大きさです。
- 一の補数算術:現代のコンピューターでは有符号整数を表すために二の補数法を使用しますが、この機械は一の補数法を採用しています。ただし、符号付きゼロ周りの動作には面倒な違いがあり、それらをリバースエンジニアリングして理解する必要がありました。
- 限られたレジスタ数:36 ビットのレジスタ A は AU:AL のように個々のビットをアドレス指定可能です。それに合わせて 18 ビットの B レジスタも用意されています。
- わずかなメモリ容量:合計 40,960 ワード(約 90 KB)しかメモリがありません。これにコード自体と実行時に必要とするすべてのデータが割り当てられます。
- バンク化されたメモリ:この 40,960 ワードは 10 つのバンクに分かれており、命令を参照する際に事前にどのバンクを選択するかを設定する必要があります。
当時は海軍向けにレーダー信号を受信し砲兵指揮をサポートするために開発されました。まさに工学の驚異的な傑作です。(左側に写っているのがこのコンピューター本体で、右側には現在は部分的に動作可能な磁気テープユニットが配置されています。)
近くにあるのはテレタイプ機(TT)であり、これが我々のコンピューターとのインターフェースでした。キーボードを入力すると紙に出力され、コンピューターの回答も同様に印刷されます。これが発行される標準入力 (stdin) と出力 (stdout) です。
現在、世界に存在する UNIVAC 1219 はわずか 2 機だけで、どちらもベテラン・コンピューター・フデーション (VCF) の有志によってジョンズ・ホプキンス大学から救い出されました。このうちの 1 機だけが今も動作しています。
プロジェクト着手前まで、存在していたすべてのプログラムは手書きで UNIVAC アセンブリ言語で作成されていました。今回は C ランタイム環境を整え、最終的に C コードをコンパイルして実行するという革新的な挑戦を行いました!
VCF East 2025 における初出會
私は 2025 年 4 月、VCF East のイベントに参加する機会がありました。その際、ビル氏とスティーブン氏がそのマシン上でデモプログラムを実行中でした。ドゥエンズ氏、ビル氏、そしてスティーブン氏は過去 10 年にわたりこの機械の救援と復元作業に多大な努力を注いでのいました。
実際に眼前でこの機械を目の当たりにすることは本当に心動かされました:点滅するランプ、テレタイプ機の打鍵音、オイルの匂い……私はそこで直ちに「これに何か驚くべきコードを実行させる必要がある」と決意しました。単純なFizzBuzz 以上のものを!私が狙っていたのは NES エミュレーターであり、OCaml の実行でした。このハードウェアの限界はどこまで推し進められるのでしょうか。
アセンブラとエミュレーターの必要性
UNIVAC アセンブリ言語のアセンブラと、生成されたプログラムを実行するエミュレーターの作成が最初の実践課題です。幸運にもドゥエンズ氏は以前 BASIC(!)で UNIVAC アセンブラを作成し、VB.NET でエミュレーターを開発していました。
VCF イベント終了後すぐに、TheScienceElf 氏は卓越したマニュアルのスキャン資料を参照しつつ、そしてドゥエンズ氏の既存実装を基準として Rust を用いて新しいアセンブラとエミュレーターを開発する取り組みを開始しました。
Rust のエミュレーターの性能は驚異的で、実際の UNIVAC ハードウェアの 400 倍、VB.NET エミュレーターの 40,000 倍という速さを実現しました。その速度は後述するフェイズテストを推進するために不可欠でした。
当時の両方のエミュレーターはハードウェア精度とは完全には一致していませんでしたが、開発を開始するには十分でした。
Wee:C コンパイラへの第一歩
エミュレーターの構築が可能になった後、次はどうやって C コードを実行させることができるのかを考えました。C コンパイラを証明する最も高速なアプローチは、私が以前に作成したプロジェクト「Wee」を利用することでした。Wee は非常に小さな命令セットアーキテクチャで、私はこれを過去に C を異なった環境へコンパイルするために利用しました。
Wee は機能しましたが、性能は酷いものでした。単純な FizzBuzz プログラムですら約 27,000 ワード(総メモリの約 67%)を消費し、最初の 100 行の計算に整整な 1 分かかりました。私は本物の複雑なプログラムを実行することを目標としていたので、これは明らかに実現不可能でした。
RISC-V エミュレーターへの変更
Wee よりも賢明なアプローチが必要です。多くの選択肢がありますが、私の主要な目標を明確化します:
- 本格的で規模の大きな興味深いプログラムを実行したい。GitHub から直接コンパイルし、その出力を機械に実行させてほしい。
- 自分のメンタルヘルスを保ちたい。
本物のコンパイラが必要(LLVM や GCC など)
現実的なプログラムを実行する目標達成のために以下の要素が不可欠です:
- 完全な C 標準ライブラリ(今回は picolibc を使用)。
- ソフトウェアフロート化など、すべての型と演算が機能する環境。浮動小数点数、倍精度浮動小数点、32 ビット整数、64 ビット整数、すべてを含むもの。UNIVAC はこれらをネイティブにサポートしないのに、これらの機能が動作する必要があるためです。
- ダッドコードエリミネーションとサイズ最適化。90 KB の非常に限られたスペースを効率的に利用する必要があります。
- 複数言語への対応(C だけでなく、Rust、C++、Zig など)。
UNIVAC への直接コンパイルは現実的ではない
UNIVAC 向け LLVM や GCC バックエンドを作成するのは想像以上に困難であり、私の「メンタルヘルス維持」という目標に反してしまいます。一の補数算術、18 ビットワード、バンク化されたメモリなど、現代的なコンパイラへの統合は極めて痛み伴います。
たとえ直接コンパイルが可能でも、C int が 18 ビットの一の補数整数となるため、実用的なプログラムとの互換性に問題が生じます。C 仕様に従う限り(少なくとも C23 までは二の補数の強制がありません)、これは可能ですが、実際のコードは通常 32 ビット以上の二の補数を前提としているため、市販プログラムの動作が破綻します。
したがって、RISC-V など既に GCC がサポートしているターゲットアーキテクチャを採用するのが最適です。具体的には、GCC で C を RISC-V にコンパイルし、UNIVAC アセンブリで書かれた RISC-V エミュレーターによってそのアーキテクチャを実行させるというアプローチとなります。
この方式の利点は以下のように整理できます:
- 一度きりの開発:エミュレーターを一度実装すれば、二度と UNIVAC アセンブリを見る必要がなくなります。
- フォールテストへの対応:ランダムな RISC-V プログラムを生成し、自製エミュレーターと参照エミュレーターで実行してレジスタ状態を比較することで、エミュレーターの正確性に高い自信を持てます。
- 進捗のドーパミン:何年も前に見たブログ記事に触発されました。実装中に段階的な達成感を得るようなプロジェクト設計が推奨されるとのことです。全体を一気に実装してから最後にテストを行う場合、何か機能している様子を見る前に燃え尽きてしまう可能性があります。RISC-V の基本命令セットには実装すべき重要なものが 38 つしかなく、明確なゴールが存在します。実装を一つずつ進められ、フォールテストでパスすれば達成感を得られます。
- 緊密なバイナリ:1 つの RISC-V 命令を効率的に 2 つの UNIVAC ワード(36 ビット)に符号化できます。これにより限られたメモリ空間を有効活用します。将来的には圧縮拡張の実装や独自の圧縮方法への対応も可能です。
エミュレーションは遅いですが、それは問題ありません
この方式の欠点は、各命令のデコードとエミュレーションに掛かる実行時間(オーバーヘッド)です。最適化を行っても、RISC-V 1 命令を実行するのに約 40 つの UNIVAC 命令が必要です。つまり、動作クロック 250 kHz の UNIVAC は約 6 kHz の RISC-V マシンのような挙動を示します。
しかし、このパフォーマンスは既に十分優れています!現実的な複合プログラムを実行する際の真の障壁はメモリの制限(40 KB)にあります。このエミュレーション手法は、その利点を活かしつつ最高のメモリ効率を実現します。
ツールチェーンの大まかなフロー:
- C コードを記述
- GCC を使って RISC-V へコンパイル
- 各命令を UNIVAC 効率的な形式(RISC-V 命令 1 つあたり 2 ワード)へ再符号化
- 再符号化された命令をエミュレーターのソースコードに追加
- プログラムを
テープ形式としてアセンブルし、マシンに読み込み.76
RISC-V エミュレーターを実装するには約 1,000 行の UNIVAC アセンブリが必要となります。これを行うには良質なツールチェーンが不可欠です。実際の開発に着手する前にもう数週間を準備に費やし、以下の作業を行いました:
- Emacs メジャーモードの実装
- OCaml を用いた RISC-V の解析、エミュレーション、再符号化のためのツール(双方向フォールテスト対応)
- 地面真実となるミニ RV32IMA との差分比較を行うフォールタスク
- Lithium(C コード最適化ツールの移植版)を使用した効率的なテストケース削減機能
この投資は大きく報われました。
Claude Code は UNIVAC アセンブリを書き切れませんでした
Claude Code は優秀です。ドキュメントを提示すると、Emacs メジャーモード全体の記述まで行ってくれました。OCaml を書く際にコード編集タスクで頻繁に利用しています。ただし残念なことに、ドキュメント、エミュレーター、差分フォールタスクを用意しても、UNIVAC アセンブリの記述では失敗してしまいました。非難する必要はありません。UNIVAC アセンブリは本当に奇妙な言語です。
あらゆる試みにもかかわらず、この時点で Claude Code は UNIVAC の特性(一の補数算術、左シフトが丸め回り、右シフトが算術的であること、0 に対する CPAL 命令の特別な振る舞いなど)を内部化できませんでした。
私はそれでも記述できました!
全てのプログラマには「鎖を締めて粘り強く取り組む」必要がある時があります。そのため、袖をまくって数日間で、基本セットに含まれる 38 個の RISC-V 命令を実装するために必要な約 1,000 行の UNIVAC アセンブリを手入力して記述しました。正直言って非常に楽しい体験となりました!
- Emacs メジャーモードはシンタックスハイライトと、各命令の実行タイミングを表示するヘルプテキストを提供します。
- フォールテストがバグを検出し、瞬時最小再現ケースを削減してくれました。フォールテストでパスすればその命令について満足して次のタスクへ進んでいきました。この段階では効率性よりも正確性を重視しました。
最初の C プログラムが動作しました!
すべてのフォールテストがパスした時点で、初めての実行する C プログラムを実行しました。「ほぼ」成功しましたが、RISC-V メモリアドレスを UNIVAC メモリアドレスに変換する方法に小さなバグが含まれていました。フォールタスクを更新してこのバグを検知させ、修正することで、その時点から全ての C プログラムが正しく動作するようになりました!フォールタスク作成した過去への自分の感謝の念は底知れません。
これは素晴らしい瞬間でした。FizzBuzz が動きました。BASIC インタプリターも動作しました。smolnes(NES エミュレーター)さえも正常に動作していました!
ただし、実機の UNIVAC でピンボールの初回フレームをレンダリングするには 20 時間かかります(エミュレーターの 3 分と比較すると)。博物館で半日過ごすことのできる時間がありません。NES アイデアは諦める必要がありますか?決してそんなことはありません。これからの最適化に期待しましょう!
性能向上:30 倍高速化
UNIVAC エミュレーターは、実機上でプログラムを実行する推定時間を追跡しています。これを最適化のメトリクスとして利用します。
特に注目した数値は以下の通りです:
- すべてのフォールテストプログラムの実行時間(すべての命令に関する平均指標)
- NES デモ(実際に高速化にこだわったベンチマーク)
実行時処理を符号化作業へシフト
最も重要な最適化は、RISC-V 命令を UNIVAC に対して最も効率的な形式へ再符号化する作業です。RISC-V 命令は 32 ビットで構成されています。再符号化プロセスではこの 32 ビット命令を取得し、変換処理を行って最終的に 2 つの UNIVAC ワードに書き込みます。
RISC-V 仕様に触れると、イミディエイト値の符号化方法が驚くほど巧妙であることがわかりました:ビット順序は乱されているのです!
……えっ?(参照元)
ソフトウェアエミュレーターではこの乱されたビットを再構成するために多数のサイクルをシフト演算やマスキングに費やす必要があります。ハードウェア実装にはこれが有利かもしれませんが、私たちはこれらサイクルを節約できません。したがって、事前にビット順序を整理し、UNIVAC エミュレーターの入力として適切な順序で書き込むことが賢明です。
オペコードについても同様です。RISC-V 命令の実装方法を決定する際に、命令内の非連続なビットを確認する必要が生じることがあります。私たちの符号化方式では、各命令に便利で明確なオペコード番号を割り当てています。
イミディエイトの再整理に加え、インストラクションハンドラが直ちに処理する作業があれば、それらを命令自体に埋め込みます。例えば、一部のハンドラは即座に「イミディエイト × 2」を計算する必要がありますが、代わりに「イミディエイト × 2」を直接保存すれば効率的です。
最も極端な例として SRLI や SRAI 命令があります。UNIVAC では可変シフト量が扱えないため、ランタイムで自己修飾コードのようにシフト命令を動的に生成し実行する必要があります。しかし、この UNIVAC 命令の作成自体も事前に行うことが可能です。SRLI/SRAI の場合、UNIVAC 命令をペイロードに直接包装し、後で使用のために RAM に書き出すことで実現します。
これらの変換技術的に言えば、自己修飾コードに依存する RISC-V プログラムへの対応力を失いますが、その代償に見合うほどの大規模な高速化を得るため、このトレードオフは妥当です。
ホットパスの高速化
UNIVAC 上でも古典的な最適化手法が適用できます:
- ダッドコードの削除。ここで私が工夫したのは、フォールテストが引き続き通過する状態で可能な限り多くの UNIVAC 命令をエミュレーターから除去するため、テストケース削減器を再利用したこと。無用なコードが見つかりました!
- ジャンプテーブルの実装。オペコードに基づいたジャンプテーブルを利用したディスパッチ方式が最も効率的であることが確認できました。
- 命令の順序付けとレジスタライビティ。メモリへの保存・読み込み回数を減らすほど効果的です。
- インライン実装。サブルーチンコールのジャンプ+リターンオーバーヘッドを避けるため、小型関数をインライン化します。
- OCaml マクロシステムによるインライン管理
コードのインライン化は速度向上に寄与しますが、コピー&ペーストでコードを管理するのが困難になる可能性があります。OCaml のマクロシステムによって解決できます:トリプル反転記号(``)内の OCaml コードをファイル内に直接挿入する機能です。面白いですね 🐪
コード重複削減の例:
OCaml を用いて 32 エントリを持つルックアップテーブルを生成する例:
高速システムコールの追加と適切なコンパイラフラグの使用
多くの C プログラムは起動時に .bss セクション内のグローバル変数をゼロにする必要があります。この作業には時間がかかります。UNIVAC アセンブリで迅速に実装するため、memclear システムコールを追加しました。
また、.noinit リンカー注釈を追加して、初期化が不要な大きなグローバルバッファを .bss から除外する最適化を行いました。
コンパイラフラグについては、-O3 が有効ですが、-Os と比較すると顕著な差は見られません。UNIVAC にはキャッシュやブランチ predictors などの先進的なハードウェアがないため、コンパイラの最適化の余地が限られます。
Claude Codeによる並列微最適化
包括的なフォールタスクと数値メトリクスを備えた環境は、LLM がプロジェクトで優れた成果を出すための理想的な環境です。
初期実装には命令順序付けやダッドコードなど、改善余地の大きい部分が多々ありました。Claude Code ワークフローでは、10 つのサブエージェントを並列に生成し、それぞれの独立したワークツリー内で異なる最適化アイデアを探求・テストさせる手法を採用しました。
主エージェントは、可メンテナンス性や品質に関する基準を満たすかどうかもチェックしつつ、各サブエージェントの結果を統合しました(「すべてをインライン化する」などの要求は除外)。最終結果をレビューし、複雑性と可メンテナンス性のトレードオフを検討した上でマージを行っていました。
このアプローチは大変有効でした。多数の反復を通じて、この手法単体で約 20% の総速度向上を実現しました。
LLM が何か壊してフォールタスクが検知できない場合、フォールタスクを強化する必要がある場面もありました。ここで「バイブ最適化におけるムーンの法則」と提唱します:
LLM がプログラムを最適化する際、システムの一部がテストによってコード化されていない場合、その部分にバグが導入される可能性が高まります。
Claude Codeによる乗算ハンドラの Python 実装
一部の C プログラムで大幅な高速化を得る別の方法は、エミュレーター内で乗算命令を実装することです。RISC-V ベースセットには乗算命令がなく、コンパイラはアドとシフトの組み合わせで使用 workaround を採用します。
Claude Code にこのタスクを任せるのは大きな挑戦です。一の補数 18 ビット演算を用いて、2 の補数 32 ビット乗算を実現する必要があります。フォールテストを行っても、実行経路を追跡、ドキュメント、1,000 行以上の高品質なアセンブリサンプル、多数の並列試行を提供しても、Claude Code は失敗しました。
そこで以下のアイデアを思いつきました:UNIVAC 算術命令それぞれに対して Python 関数を実装し、これらの関数を用いて Claude Code に 32 ビット乗算の実装を依頼します。フォールテストも同様に提供します。
このアプローチが有効な理由は:
- Claude Code は Python に精通している
- ネストされた式記述が可能
- 変数への結果格納やヘルパー関数の記述が可能
- 標準的な Python デバッグ技術を適用できる
十分な並列性と時間をかければ、Python スクリプトの記述を完了することができました!
次にタスクを簡素化:Python プログラムを UNIVAC アセンブリへ翻訳。そして成功しました!
乗算ハンドラは約 676 行の不可解な UNIVAC アセンブリで構成され、エミュレーターの全コードの約 43% を占めます。粗悪な怪物ですが、素性判定や楕円曲線暗号など乗算を多用するプログラムにおいて 6 倍の高速化を実現するため、当面は維持します。
30 倍の性能向上まとめ
総合的に見ると、NES の1 フレーム処理時間が約 20 時間から約 40 分まで短縮され(30 倍の向上!)、博物館でのランチ中の実行が可能になりました。
遂に目標達成のため、TheScienceElf と私はドゥエンズ氏、ビル氏、スティーブン氏へ連絡し、これまでの成果を報告しました。訪問以来話をしていないこともあり、プロジェクトに多大な時間を投資した後、コンピューターの破損が心配でした。
メールを送信し、UNIVAC 1219 Rust エミュレーター、C ツールチェーン、そして実際のプログラムの実行能力を発表しました。「博物館を訪れて試してみませんか?」という問いかけに対して、すべての方が大喜び!1 月の博物館訪問計画が固まりました。
旅行直前、ドゥエンズ氏の UNIVAC に関する 25 年以上の経験を活用して技術的疑問への回答を求めました。一の補数の例外ケース、IO チャンネル設定、TTY キャラクターエンコーディング、ブートロードプロセスなどについて詳しくご教示いただきました。感謝申し上げます!
博物館訪問#1:ハードウェアデバッグとコード読み込み
遂に当日がやってきました。TheScienceElf、スティーブン氏、ビル氏と共に1 月の朝に博物館へ向かいました。ドゥエンズ氏はリモートで待機していました。UNIVAC を起動しましたが問題が発生しました。「WAIT」ランプが点灯しました。
WAIT ランプはチャンネル 4 の不正アクティビティにより点灯しています。
この状態でコンピューターは命令を実行しませんでした。長らく既知の問題であり、過去の解決策は機械が温まるのを待つことでした。30 分間待ってもランプが消えず希望を失いましたが、ドゥエンズ氏に助けを求めました。ビル氏、ドゥエンズ氏、スティーブン氏はマニュアルの回路図を追跡し、最終的に IO チャンネル 4 を完全に切断する判断をしました。これで成功!中断ランプは消えました。チャンネル 4 に不良ハードウェアがあり、これが不正なアクティビティと中断の原因だったと考えられます。
次に楽しいタスク:プログラム読み込み方法の確認です。通常の手順は以下の通りです:
- フロントパネルのボタンとレバーを手動操作して約 30 命令をメモリに記録。これは紙テープブートローダープログラムで、紙テープリーダーからの読み込みに対応。
- LECPAC テープロールを読み込み機に装着。LECPAC はデバッグやプログラム読み込みに役立つユーティリティプログラム。
- ボタン操作とレバー設定で LECPAC をチャンネル 7(シリアル IO)に設定。ドゥエンズ氏の Teensy プロジェクトによって UNIVAC の並列 IO インターフェースをシリアルへ変換し、ノートパソコンとの通信を実現しました。
- LECPAC リーディングルーチンを実行してシリアルからプログラムを読み込む!
ドゥエンズの UNIVAC IO <-> シリアルアダプター。Teensy による標準シリアルポートの実装により、UNIVAC とのノートパソコンからの通信が可能になりました。
しかしステップ 4 で問題が発生:ゴミデータが読み込まれていました。USB ケーブル、シリアルケーブル、ノートパソコンのあらゆる組み合わせを試しましたが、何ら改善が見られませんでした。
ドゥエンズ氏が送付してくれた 8 命令だけのデバッグ用プログラムを使用してシリアル入力をテストしました。フロントパネルを手動で入力し、シリアルチャンネルからの文字を受け取ってレジスタに格納し、ディスプレイ上のランプに表示させる動作を確認しました。
このプログラムを試しながらシリアル設定を変化させ、最終的に「A」を送信して AL レジスタに正しく表示される配置を見つけました!
既知で正常に動作するドゥエンズ氏の「Hunt the Wumpus(ワンプスへの冒険)」プログラムをロードし、ノートパソコンからのシリアル読み込みテストを行いました。成功!しかし自社のツールチェーンから生成したプログラムは読み込めませんでした。なぜでしょう?
Wumpus と対照的に比較してテープファイルを確認すると、テープファイルの先頭ゼロパディングが必要だったことに気づきました(理由不明)。この修正により、プログラムが正常にメモリにロードされるようになりました!
真摯な瞬間です。C「Hello World」プログラムの開始アドレスを PC レジスタに設定し、「RUN」スイッチを押しましたが……何もおきませんでした。理解できない理由でプログラムは停止しました。もう一つのプログラム(円周率計算)を実行すると、円周率ではなく無意味な数値列が表示されました:
明らかに円周率ではない。
フロントレバーを操作して約 20 命令ずつステップ進め、エミュレーターのトレースと比較しました。全体的には良好でしたが、ハードウェアとローダーで一日費やしたため、発生の原因を特定する時間切れとなりました。LECPAC を用いてコアダンプを取得しオフライン分析のため帰宅しました(この機械は実際にコアメモリを使用しているのです!)。
素晴らしい成果です!ハードウェア問題を修正し、プログラム読み込み方法を確立しました。次回からはソフトウェアデバッグのみとなり、これが私の得意分野です。
フォールテストとトレースによるエミュレーターとハードウェアの一致確認
2 ヶ月後の再訪をスケジュールしました。その間どのような準備ができるでしょうか?コンピューターからゴミデータが噴き出されている場合、どう対処すべきでしょうか?
Rust エミュレーターがハードウェアと一致していることに確信を持つ必要があります。ここで私の得意としたプログラムを開発しました:
フォールテスト用「フィンガープリント」生成プログラム
UNIVAC アセンブリで記述した診断プログラムを作成しました。各算術命令(ADDAL, ADDA, SUBAL, SUBA など)を擬似ランダム入力を用いて数百回実行し、結果をハッシュ値に累積してテレタイプに表示します。このハッシュ値は命令の振る舞いに対するフィンガープリントです。異なる実装上同じプログラムを実行した際、一致するハッシュ値は実装間の合意を示し、異なる場合は発生のどこかで差異が存在することを意味します。
出力は1行ごとにオペコードとオクタルハッシュが表示されます:
ADDAL: 614424 223254 ADDA: 020656 635560 ADDAB: 401323 107167 SUBAL: 633336 720540 SUBA: 235365 124723 ...
問題が発生する場合は、ハッシュ値が異なる理由と入力条件を特定する必要があります。そこでソフトウェアトラサーを活用します:
UNIVAC 命令ごとの実行トレイサー
私が記述した最もユニークなプログラムです。UNIVAC アセンブリで書かれたこのソフトウェアトラサーは、別の UNIVAC プログラムを1 つずつ命令を実行し、各ステップ後に完全なマシン状態(PC, 命令, AU, AL, B, SR, ICR)をシリアルポートに出力します。目的は、この出力をエミュレーターのトレースと比較して、差異が発生する正確なタイミングと原因を特定することです。
このソフトウェアトラサーの記述方法は頭を回転させるほど困難でしたが、要点はトラサー自身がターゲットプログラム内の PC を管理し、各ステップでターゲットから現在の命令をコピーして自身のメモリに保存します。完全なマシン状態を保存し、コピーした命令を実行した後再度状態を保存して結果を表示します。ジャンプ命令のようにトラサーハンドラへのポインタ変更が必要ですが、CPU 自身がジャンプ条件を評価するため、条件分岐ロジックは再実装しません。
次の数命令の実行状況を示すテーブル:
| PC | INSN | AU | AL | B | SR | ICR |
|---|---|---|---|---|---|---|
| 050000 | 340007 | 000000 | 000000 | 000000 | 000000 | 000000 |
| 050007 | 507300 | 000000 | 000000 | 000000 | 000000 | 000000 |
| 050010 | 507200 | 000000 | 000000 | 000000 | 000000 | 000000 |
| ... |
このトラサーをインタラクティブプログラムとして GDB と連携させることも可能です。ブレークポイント設定、メモリ検査、レジスタ操作、ステップ実行などが完全に実現できます。
博物館訪問#2:動作するプログラムの初体験
最初の訪問から 1 ヶ月後、伝説的なデバッグプログラムを装備して再度博物館へ訪れました。基本的なプリミティブの実現可能性を確認する必要があります。テレポートYPEに正しくテキスト出力できるでしょうか?
UNIVAC アセンブリの手書き"HI"プログラムから始めました。初回試行で成功!次に命令フィンガープリントリングプログラムを実行しました。ハッシュ値がストリーム表示され、期待通りエミュレーターとの差異が確認できました。36 ビットの加算・減算の 4 つの命令は異なるフィンガープリントを出力していました。
フィンガープリントリングプログラムでは各命令についてハッシュ値を表示します。
Claude Code をハードウェアフィンガープリントに投入し、マニュアルの様々な解釈に対してブルートフォース攻撃を行いました。最終的に一致する結果を得るまで試行錯誤しました。
エミュレーターとの差異を修正した後、エミュレーター上でアセンブリ円周率プログラムを実行しましたが、ハードウェアと同じゴミ値が出力されました!!!これはエミュレーターが現在正確であることを意味します。ゴミを見せたことがこんなに嬉しいことはありませんでした!
この時点で 36 ビット操作の新しい解釈に対応するために pi プログラムと RISC-V エミュレーターを修正しました。 ...そして瞬く間に、すべてのプログラムが動作するようになりました。Hello World, FizzBuzz, オレゴン・トレイル, BASIC, Figlet, ELIZA, OCaml による C_of_ocaml ソルバー、AES 暗号化、野球、ブラックジャック、エニグマ暗号化機と解読機、ワードルなど、すべて正常に動作!ソフトウェアトラサーさえ不要になりました!
UNIVAC 上で動作した最初の C プログラムは Hello World と FizzBuzz です。
ELIZA セッション:「 elucidate your thoughts(あなたの思索を明らかにしてください)」
インタラクティブ BASIC インタプリターが素性判定プログラムを実行中:
ランチ中の間に NES エミュレーターを開始しました。最初の可視フレームが印刷されるのを興奮しながら確認しました!
さらに ASCII テーブルをテレポートYPEにダンプし、その文字セットを確認する機会を得ました:
TTY 上のオーバータイプアートによる自撮り
この旅は大成功だったので、プロジェクトの最も野心的な目標である「Minecraft サーバーホスティング」を試す時間がありました。エミュレーターで動作確認済みであることを知っている Proof of Concept (PoC) を持参しました。
ネットワーク構成:Raspberry Pi が PPPd ブリッジとして Mac と UNIVAC シリアル間を接続しています。
PPP と TCP のハンドシェイクは成功しましたが、エンドツーエンドのデータ転送には至りませんでした。
UNIVAC 上のネットワーキング
私が最初に夢想したのは、UNIVAC に NES エミュレーターを実行させることでした。その達成後、さらに高みを目指して同機上で Minecraft サーバーをホスティングしようと挑戦しました。これは今までの最も「呪われた」アイデアであり、技術的に非常に困難ですが、これまでに蓄えたツールと知識が必要となります。
何らかの cheating をしないことが重要です。目標は以下の通りです:
- PoC 段階ではクライアントのログインに成功さえすれば十分。Minecraft ログインプロトコルの実装のみが必要です。
- 面白いロジックはすべて UNIVAC 上で処理します。cheating はなし。
アプローチは PPP を介して IP パケットを UNIVAC にフォワードし、UNIVAC が PPP/IP/TCP/Minecraft プロトコルすべてを実行することです。私の設定では、Mac ノートパソコンが Pi のポートに接続され、pppd を通じてシリアル経由で UNIVAC に IP パケットを転送します。Mac 自体に直接 pppd を実行できる可能性がありますが、Linux との親和性が高いので Pi を中継に使用しました。
UNIVAC がこれらのプロトコルを実行する仕組みは何でしょうか?90 KB のメモリで完全な TCP 実装のコードを収められるかどうか疑わしいのに、まして実行するのは困難です。
ここで重要なアイデアが登場:TCP のエラーハンドリングをすべて排除します。同時に 1 つだけの接続があるとし、パケットが全順序到着すると仮定すれば、TCP は極めて単純になります。つまり TCP を UDP と等しく扱えば、UNIVAC で実行可能です。
Minecraft ログイン実装は bareiron から派生しています。これら要素を組み合わせれば、ブロックのないワールドにログインし、落下して死亡した後に切断される動作が可能になります。
なぜ前回の訪問では機能しなかったのでしょうか?UNIVAC が受信パケットを保存せず地上に捨てる(ドロップ)ことが原因ではないかと仮説を立てました。完全に TCP エラーハンドリングを実装していれば問題にならなかったはずですが、そうではなく実施できなかったため、これを防ぐことが極めて重要でした。
この修正には UNIVAC の並列 IO 機能への直接的な介入が必要です。UNIVAC の IO インターフェースは DMA(直接メモリアクセス)に似ています:ハードウェアがバッファ内のバイトをメモリ上の指定場所へ書き込みます。「Continuous Data Mode (CDM)」モードでは、バッファが満たれた際に DMA をバッファの先頭から再起動させることができます。
これによりリングバッファプリミティブが実現できます。プログラムの最後に入力されたバイトを追跡でき、4 KB のバッファサイズ以内なら他のチャンネルでのデータ処理や送信にも関与しても、データの損失を防げます。
博物館への復帰前の downtime に TheScienceElf はエミュレーテッド TTY の精度向上に取り組んでいました。スクリーンショットを共有してくださり、行末の文字をオーバータイプした表示(博物館訪問時に新規改行忘れで発生)が正しく再現されていました:
当時のパートナーと相談し、同じ文字を何回もオーバータイプすることで高解像度 ASCII アートを実現するアイデアを出しました。変数を制御できるほど解像度が向上します(残念ながらこのアイデアには 50 年遅れました)。
Model 35 TTY では次の行へ移動するにはカリージョンリターン (\r) と newline (\n) を送ります。\r はカーソルを左端に戻し、\n は1 行下へ移動します。のみ \r を送ると既存のテキストの上に新しい文字を書き込むことができます。
画像から文字列に変換してテレポートYPE に送信する Python スクリプトを作成しました。アルゴリズムは以下の通り:
- 印刷可能な Model 35 キャラクターをインクカバレッジのビットマップにレンダリング
- ターゲット画像をキャラクター位置ごとのグリッドへ分割
- セルごとに、知覚的誤差を最も低減する文字を貪欲選択。最大オーバータイプ回数まで反復。インクの重なり時にはビール・ランベルト則 (0.5 -> 0.75 -> 0.875) に従ってピクセル暗さを設定。画像で検出されたエッジは誤差計算に高い重みを与えます
- Floyd-Steinberg 拡散を用いて残留誤差を隣接セルへ分散
画像に目を向けると、重ね書きされた複数の文字がより暗さ+テクスチャに寄与していることがわかります:
博物館訪問#3:Minecraft、ウェブサーバー、そして自撮り
最終的な博物館訪問では、UNIVAC はハードウェア問題なしで直ちにオンライン化しました。良い一日の予感がしました。
日中は持参した実験プログラムを実行:
- 加算・減算の -0 と +0 のすべてのケースをテスト。非キャリーパスにおいて -0 を +0 に正規化する点で典型的一の補数スキームから逸脱していることを確認
- メモリチェックを行い正確に 40,960 ワードであることを確認
持参した他のプログラムも動作:TheScienceElf の pi とオイラープログラム、私の SHA-256 プログラム、スティーブンの Battleship プログラム。
SHA-256 ユーティリティが 3 つの入力 (HELLO WORLD, YAY, UNIVAC) をハッシュ処理中:
今こそ真の瞬間です。並列 IO の変更が機能するでしょうか?Minecraft が動作するのでしょうか?
常通り単純から始め、プリミティブを構築します。リングバッファ統計情報を表示する単純なテストプログラムを持っており、マニュアル理解の確認を行いました。シリアルで期待通りの動作を確認できました。書き込みポインタがバッファ内で循環していました。
次にテストすべきは比較的低複雑度のウェブサーバープログラムです。Minecraft よりも簡単でした。緊張感を覚えながらロードし PPP を接続しましたが……うまくいきませんでした!接続できませんでした。心が沈みました。ランチ時間だったので食事とブレインストーミングのために外出しました。出発前にオーバータイプ ASCII アートプログラムを起動し、数十分の印刷時間を予想していましたが、結果は素晴らしいものでした!
戻るとウェブサーバーを再ロードして再度試みることに……そしてなんと動作しました!最初の試行で何らかの設定ミスがあったのでしょうか?curl で問題なくアクセスできました。ブラウザで開くと…
信じられない!UNIVAC シリアル経由での PPP/IP/TCP が正常に動作し、現代コンピューターから取得したウェブページをサービスしています。信じられませんでした。(余計な"H"がなぜ出現するか不明です。恐らく Chrome が "favicon.ico" を検索する追加リクエストに関係しているかもしれません。謎 :) )
今こそ真の瞬間。Minecraft の件はどうでしょうか?
プログラムをロードし、Minecraft クライアントを開始しました。UNIVAC IP に設定して接続ボタンをクリック……驚くべきことに最初の試行でログイン成功!
喜びのあまり月面に浮かぶような気分でした。数ヶ月のデバッグと知的なハックにより、不可能と思われたことを実現(少なくとも TheScienceElf 氏は不可能だと考えていました :)))できました。
残りの午後、ビデオ撮影用のフットージを取得したり、過去 8 ヶ月の成果を祝ったりして過ごしました。
結論
どんなに素晴らしい旅でした。私が「可能ではない」と思っていたプロジェクトが最も楽しいものです。
ここで実現したすべてのことが技術的には 60 年前にも可能だったと考えると、タイムマシンで紙テープ(プログラム付き)を送り届けるのが面白いでしょう。当時の人々は頭を狂わせます!
このような成果を実現してくださった皆様へ心から感謝申し上げます:ドゥエンズ氏、ビル氏、スティーブン氏、そして TheScienceElf 氏。VCF スタッフの皆様にも素晴らしい体験をご提供いただきありがとうございます!本当に感激しました。
ご執筆ありがとうございました!ソースコードはこちらで公開されています。