NANDだけを使ってCPUを実装!

2026-05-18

コンピュータシステムの理論と実装という、NANDゲートだけを組み合わせて最終的にテトリスを実装するという本があって、それに習ってCPUを実装してみた。 以前ALUまで作ったので、その続き(8年越し!)

  • 書籍は第2版が出版されたが、参照してるのは古い版

成果物

ページを開く

  • プログラムは書籍の図4-2、1から100までの合計を求めるもの
  • CLK(クロック)のボタン押下でステップ実行
    • OSC(オシレータ)につなぎ直すことで自動実行も可能
  • CPUをダブルクリックで内容を見れる
  • アセンブルした.hackファイルをロードできる

レジスタとメモリ

ALU(算術論理演算装置)は組み合わせ回路なのである意味シンプルだった。 でもそれだけだとデータを記憶させることができないため、計算を積み重ねられない。 それを可能にするのが順序回路で、フリップフロップを用いることができる。

実際にはNOR論理ゲートを組み合わせてRSフリップフロップを再現できる。 それを使ってDフリップフロップ(レベルトリガ、エッジトリガ)、それを複数にまとめてレジスタやメモリ(RAM)を作る。

  • 書籍の「3章 順序回路」で説明
    • D型フリップフロップを「この複雑さは抽象化して取り扱うことにした」として、実装まで踏み込まないのが不満だ

CPU

レジスタやメモリを扱えるようになったらさらに高度なデバイス、CPU(中央演算装置)を作ることができる。 書籍の「5章 コンピュータアーキテクチャ」で紹介されるCPUは以下の通り:

  • レジスタ:ADの2つで、各16ビット
  • プログラムカウンタ:15ビット
  • 命令:A命令とC命令の2つのみ
    • A命令:Aレジスタに直値をセット
    • C命令:ALU演算した結果を出力先(A|D|M)にセットし、条件によりジャンプ
      • Aレジスタに代えて、Aレジスタが指すメモリ内容(M)をオペランドに変更するフラグあり
  • 命令用のROM領域はコードで読み書きできるRAMとは分かれている

CPUの実装

書籍ではざっくりとした図とちょっとした解説だけなので、「これで実装させるのはスパルタだな〜」とちょっと怯んだ。 でも命令タイプが2通りしかないためデコードが簡単(デコードと呼べるほどもない): A命令はAレジスタにセットするだけ。 C命令はALUがそのまま扱えるような構成になっている。

命令長も1ワード固定なので命令のフェッチやプログラムカウンタの増加も固定処理となり、複雑な機構はまったくいらない。 無条件・条件付きジャンプは条件が成り立ったらプログラムカウンタに値をセット、そうでなければインクリメントすればよい。

簡単に実装できるようにうまく機能を絞った構成になっている。

ジャンプ処理

ALUの計算結果がゼロか、または負かの判定結果が出力されるのでそれを使う。

0より大きいかどうはか、「ゼロじゃなく、負でもない」で判定できる。 で<, =, >の3条件が命令のjump3ビットで有効かどうかをそれぞれ判定して、どれか1つでも成り立つ場合にはPCの値にAをロードさせるようにする。

動作タイミングは?

クロックが立ち上がる際に処理が進む際に、値の更新タイミングとかを気にする必要があるのかどうか心配だったが、実際には必要なかった。

1つ前のクロックを進めた際にPCが次のアドレスに更新される。 するとクロックを下ろす前にすでに命令メモリに入るアドレスも変わり、次の命令が読み出されることになり、デコードも処理されてALUも更新された状態になる。 で次のクロックの立ち上がりでようやくレジスタやメモリに書き込まれるというわけ。

順序回路なのはレジスタやメモリ(への書き込み)だけで、その他の回路は組み合わせ回路なので即座に反映されていて、クロックを進める前にもすでに次の状態になっているのがミソだった。

プログラムカウンタの仕様修正

コンピュータにはリセット機能がある。 しかしリセットを有効にするだけではダメで、クロックを進めない限りプログラムカウンタ(PC)がリセットされないという問題がある。

押した瞬間にリセットしたかったのだけど1ビット記憶にエッジトリガーフリップフロップを使っていること自体が問題なので、対応するにはそこから直す必要がある。

ブラウザ上で動かす実装に関して

回路のシミュレータは以前と同じsimcirjs(の改造版)を使用。

すべてをNANDから組み立てる、ということでフリップフロップも回路として組むことは一応できたのだけどRAMの容量を増やすととてつもなく処理が遅くなってしまった。 NANDから実現できることはわかったので、カスタムデバイスで実装して速度を稼ぐようにした。

あとは状態を調べられるようにするために生成されたデバイスを取得できるようにして、値が変化した時のイベントリスナーを登録してレジスタの値を見れるようにした。 プログラムカウンタから現在実行する命令が得られるので、逆アセンブルしておいた内容と合わせるようにする。 レジスタやRAM内容も確認できるよう表示。

展望

書籍ではその後、アセンブラ、バーチャルマシン、高水準言語コンパイラ、OSと、重めの内容が延々と続くんだけど、ソフトウェア部分は他でも経験あるので書籍通りにたどるのはいったん終了ってことでいいかな。

いちおう「CPU」を作ったとはいえ、機能的にはまだまだなのでもう少しなんとかしたい:

  • 「バス」を使って多数のレジスタを扱えるように
  • 高度なアドレッシング
  • より複雑な命令セット・可変長命令で、複数サイクル必要な命令
  • 6502?果てはRISC-V!?

リンク