久しぶりにプラレールで遊んで童心に戻ったら、プラレールでコンピューターを作りたくなったので簡単なCPUを作ってみました。
CPUのレールレイアウト
下の画像は4ビットCPUとして動作するプラレールのレイアウトです。かなり削ったつもりですが、約12,000レール必要です。 (2026-10-18 レイアウトを整理して少しレールを削減しました)
通常の青いレールの他に赤や緑で表示している分岐レールがありますが、後で説明します。
CPUの仕様
なるべくレール数を少なくするため、ギリギリCPUと呼べなくもない(?)程度の、微妙なラインの仕様を考えます。
- 4ビットレジスタ × 2(プログラムカウンタ、汎用レジスタ)
- オーバーフローフラグでの条件付きジャンプ
- メモリアドレス幅4ビット
- 命令長8ビット固定
上部に並んでいる8個の同じパターンが、4ビットのプログラムカウンタと汎用レジスタです。それぞれを操作できますが、可能な操作は代入とインクリメントだけです。
レジスタは2つだけで、一方はプログラムカウンタに使うため、このままでは実用的な計算はできません。命令やレジスタは追加できる余地を残してあるので、レジスタと加算命令なども追加すれば複雑な計算に対応できます。
下部に並んでいるのはプログラムメモリで、8ビット単位で読み出すようになっています。アドレスが4ビットなので、16バイトまで接続できます。今回は8バイトだけ実装しました。アドレス8以降ではレールが未接続なので停止します。シミュレータ上では SEGV イベントが表示されます。
メモリの右側には命令デコーダ用の分岐ポイントが8ビット分並んでいます。代入とインクリメントだけなので、デコード自体はとても単純です。上部のレジスタの末尾にインクリメントがオーバーフローしたかを記憶する分岐ポイントがつながっています。
列車は以下の経路を進みます。
- プログラムカウンタ(PC)からアドレスを読む
- プログラムメモリの読み込み
- 命令のデコード
- レジスタ操作
- PCを更新(通常はインクリメント、JMP時はPCにXを代入)
- 1へ
命令セット
当初は Intel 4004 互換を目指していましたが、レイアウトの規模の都合で別物になりました。
| メモリ上の値 | 命令 | 動作 |
|---|---|---|
| 00?????? | NOP | 何もしません |
| 01?????? | INC | X += 1 |
| 1000abcd | JMP | PC := abcd |
| 1010abcd | JMP | PC := abcd if overflow == 0 |
| 1100???? | JMP | PC := X |
| 1110???? | JMP | PC := X if overflow == 0 |
| 1001abcd | LD | X := abcd |
| 1011abcd | LD | X := abcd if overflow == 0 |
? の値は今回のレイアウトでは使ってないので無視されます。abcd は、a が MSB です。
INC 命令で X の値がオーバーフローすると0に戻ります。オーバーフロー時に overflow フラグは1になり、それ以外では0になります。
JMP はPCへの代入、LD はXへの代入として実装しています。どちらにもoverflowフラグによる条件付き形式があります。
プラレールのシミュレータを作る
CPUの設計やテストができそうなプラレールのレイアウトエディタやシミュレータが見つからなかったため、作成しました。ブラウザ上で動作します。
表示は簡易的で、サポートしているレールの種類は少ないですが、レイアウトを考えて列車の経路を確認する目的には使えると思います。
静的なHTMLとJavaScriptだけなので、リポジトリを clone して index.html を開くだけでも動作します。
この記事のCPUのレイアウトも examples フォルダ内にあります。
描画のパフォーマンスがあまり良くないため、1万レールを超えるレイアウトでは編集操作がもたつきます。複雑なレイアウトを快適に編集するには、以下の機能が有効です。
- Vキーを押すと、選択レールと未接続レール以外が一時的に非表示になります。編集したい範囲だけを表示できます
- 階層を分けて、1F、2F……のようにフィルタして表示します
- モジュールごとにレイアウトを作成し、複数ファイルをドロップまたは Shift+ドロップしてレイアウトをマージします
-
cmds以下にあるコマンドラインシミュレータには、エディタ上で設定したレール通過時にログを出す機能があります。プラレールのテストを書くのに便利です
プラレール上で計算を行う
最近、「自動ターンアウトレール」という便利なレールを知りました。これがあれば列車を走らせて計算できそうです。
プラレールで計算機を作りたいと夢見る人は世の中にたくさんいるので、先人の知恵にも頼ることにします。
以下のリンク先の説明がとても分かりやすいので、事前に読んでおくとプラレールの挙動が理解しやすいと思います。
基本素子
基本的には分岐ポイントを組み合わせて、必要な論理回路を組み立てます。
本当は複数の列車を走らせたいのですが、CPUを動作させるためには長い経路を繰り返し走行することになり、複数の列車を同期させる仕組みが無いと安定動作させられません。そのため、列車の速度に依存せず確実に動作させるために、1つの列車で一筆書きできる回路を設計する必要があります。
直線・カーブ・坂レール等の状態を持たないレールと、以下の3種類のレールを基本素子として論理回路を組み立てます。
ポイント切り替え
ターンアウトレールや単線複線ポイントレールを使います。
プラレールの分岐ポイントは、分岐先側から列車が進入すると向きが切り替わるため、1ビットの情報を読み書き可能な記憶素子として使えます。
分岐の組み合わせによって0/1のどちらかのレールにたどり着くように線路を接続し、論理回路を作ります。
自動ターンアウトレール
列車が通過するたびに分岐方向が交互に入れ替わるレールです。また、方向を固定する設定も持ちます。
この自動ターンアウトレールには、重要な特性があります。逆向きに進入しても、ポイントの状態が変わりません。一見なんてことのない性質ですが、通常の分岐ポイントでは(通常の使い方では)実現できません。
ただ、プラレールにはまったことがあるなら、通常の切り替えポイントの黄色いパーツを外して切り替わらないようにしたり、輪ゴムやテープなどで決めた状態に固定して使ったりしたことがある人も多いはずです(絶対に欲しくなるのに、なぜ製品化されていないのでしょう?)。
今回は、自動切り替えに設定したレールを赤、固定動作に設定したレールを緑で表示しています。
プラレール論理回路
NAND
まずは基本のNANDゲートです。
2つの入力がともに1のときだけ出力が0になるNANDゲート、記憶素子、クロックなどを適切に配線すれば、通常のコンピュータと同等の計算が可能です。
緑色のレールは切り替えポイントを固定し、逆側から列車が進入しても状態が変わらないようにしています。
左側の2つの入力列車が、それぞれ0または1のレールから進入すると、右側の列車が0または1のレールに到達するようになります。
上の画像では、左側の各入力の書き込みに使った列車を使い捨てる、富豪的な配置にしています。しかし実際には、回収用の分岐から列車を取り出して次の回路に向かわせ、1つの列車だけで動かす必要があります。
値の複製
電子回路なら配線をつなぐだけで信号を分岐させられますが、列車は分裂しないので、一筆書きで計算しなければなりません。そのため、NANDゲートを組み合わせるだけでは複雑な論理回路を構成できません。
同じ分岐ポイントから2回読み出して、自動ターンアウトレールで1回目と2回目の行き先を変えています。
内部の切り替えポイントの初期状態を注意深く設定すれば、特殊なレールがなくても実現できます。しかし、状態を列車ではリセットできず一度しか使えないので、先ほどの半加算器の記事と同じ仕組みにしました。
複数箇所からの書き込み
読み出しと同様に、書き込み時にもすべての入力経路が合流し、列車自身はどこから来たのかを忘れてしまいます。
そのため、事前に列車がどこから来たのかをレールの状態で保持しておく必要があります。
その場合も、列車が向かうべき先を処理の出力側で読み取れるように、自動ターンアウトレールを使って出力レールの本数分だけ複製して保持しておきます。
CPUを作る
プラレールで計算する方法が分かったので、CPUを作っていきます。
プログラムメモリ
プログラムメモリは、ポイント切り替えレールを並べて実装します。ROM扱いでプログラムの動的な書き換えはサポートしません。
各ビットの0と1を2本のレールで表現し、8ビットで16本のレールをメモリバスとして扱います(画像内の縦向きのレール)。
レイアウトの左寄りに縦に並んでいる8個の分岐がプログラムを保持するレールで、一番上の分岐を LSB として、上から順番に格納します。
なるべく小さくするため、各ビットの読み出し用に自動ターンアウトレールをツリー状に並べています。
読み出しは1ビットずつ行いますが、アドレスは1バイト単位で指定し、同じアドレスを8回連続で読むと1命令分が読み込まれます。
上のレイアウトの場合、LSB = 1、MSB = 8として、1、5、3、7、2、6、4、8という順序で読み出されます。そのため、8ビット目に列車が来た時点で命令のデコードを始めるようになっています。
レジスタの設計
上で述べた通り、レジスタの書き込み・読み出し経路数に比例して規模が大きくなるため、ファンイン・ファンアウト数をなるべく小さくする必要があります。そのため、レジスタに対する操作を考慮しながら、レジスタを構成する各ビットを設計します。
1ビットあたり500レールくらいです。
インクリメントと値の転送だけサポートすることにして、レジスタの各ビットを以下のような構成にしました。インクリメント操作は利用頻度が高いのと、必要なビットだけ反転させれば高速なので専用の操作にしました。
-
INCレールから列車が進入すると、インクリメントされる- 繰り上がりがあれば
INC_NEXTレールから列車が出てくるので上位ビットにつなげる - 繰り上がりがなければ
EXITレールから出てくるので、処理は終了
- 繰り上がりがあれば
- 書き込み時は一度
W_PREPAREレールに列車を入れてから、W_0またはW_1から入り直すと値が更新される-
W_PREPAREを通らずにW_0、W_1に進入した場合は、何もせず0、1の出力レールから出てくる(複数レジスタを重ねておいて、任意のレジスタに書き込むことができます)
-
- 読み込み時は下側の
READレールに列車を入れると、0、1の出力レールから出てくる
今回は4ビットですが、並べれば任意のビット数に拡張できるように設計しています。
さらに縦に重ねれば任意のレジスタ間で値の転送ができるのでインクリメントと代入はこれを並べるだけで実装できます。
CPUを組み立てる
最初の全体のレイアウトに戻ります。
各ブロックはレールが衝突しないように坂レールで階層を分けていますが、ブロック間の接続は厳密ではないため、レールが重なっています。
折りたたんで高さ方向に積み重ねることで、せめて体育館サイズにするつもりだったのですが、レイアウトを俯瞰しにくくなるので広げたままにしています。
動作確認
ブラウザのシミュレータは最大16倍速まで速度を上げられますが、それでも時間がかかります。
なので、コマンドライン版のシミュレータでテストします。
インスペクタでレールのイベントに何か書いておくと、シミュレータ上でのレール通過時にログを出せます。
node cmds/simulate.js examples/cpu/cpu.json --speed 1000 --time 36000
速度の倍率とシミュレータ内での実行時間の制限(単位は秒なので、上の例では10時間)を指定できます。
サンプルはNOP命令が並んでいるだけなので、そのまま実行すると、メモリの読み込みとプログラムカウンタのインクリメントがログに出ます。
実際のプラレールで組み立てる場合
直線レールの長さ216 mmを基準に換算すると、レイアウトの占有面積は約30 m × 40 mです。レールの総延長にするとおよそ 3 km あります。
今回は編集のしやすさとシミュレーション時の見やすさを優先して厚みは3階層分だけで作られてます。また、橋脚のことも考慮していません。実際に組み立てる場合は、うまく縦に積み重ねればかなり面積は小さくできます。特にメモリ周りの大量の直線レールは大きく減らせるはずです。お金と根気と広い部屋があれば、なんとか作れる規模になっていると思います。
最後に
あと少しでチューリング完全になる「惜しい」玩具は世の中にたくさんあるのですが、チューリング完全で、しかも現実的な規模でCPUのようなものを作れるおもちゃはプラレール以外になかなか見つかりません。
部屋の広さの都合で、残念ながら動作確認はシミュレータでしか行っていません。
現実的なレール数で4ビットコンピュータを作れることが分かりましたが、実際に動かすと1命令の実行に1時間以上かかりそうなため、よほど暇でなければ実際のプラレールでは作らないと思います。
実行速度がネックなので、複数列車で並列処理したいのですが、公式のレールで列車間の同期を取ることができなさそうです。複数列車を交互に発車させる駅はあるので、列車がそろったら同時に発車する待ち合わせ駅もあれば、とても夢が広がるのですが……。




