Nandをひたすら組み合わせて、CPU(TD4)をFPGAで実装しました!🎉

まず初めにややこしいので…

以下の記事には4冊の書籍が出てきます。

タイトルが似ていることもあり、まずまとめておこうと思います。

 

今回作ったCPUであるTD4はこの本で創るCPUです。 

 

  • 作ろう!CPU

上記TD4をFPGAで作る方法を解説されている本です。

 

  • 動かしてわかる CPUの作り方10講

FPGAでCPUの作り方を解説されている本です。(TD4を作るわけでは無いです)

 

  • コンピュータシステムの理論と実装

通称Nand2tetrisと呼ばれる本です。Nandゲートをひたすら組み合わせて(シミュレーション上で)コンピュータを作ります。この本については昨年完走して、↓の記事を書きました。

プログラミング初学者の私がNand2tetrisを終えるためにやったこと - みちばたのハナニラ

 できたこと


先日、Nandゲートのみを使って、TD4をFPGAで実装しました。


TD4とは、”とりあえず動作するだけの4bitCPU” の略で、"CPUの創りかた" という本で解説されているCPUです。


私の最終的な目標としてはNand2tetrisをFPGAで実装したいと思っているので、その前段階としてTD4をNandから作ってみました。

前段階とはいえ、実際作ってみるといろいろな気づきや難しい箇所があり、せっかくなので記録に残したいと思います。


"Nandゲートのみを使った" ことに関して

電子素子の一つであるNandのみを与えられたパーツとして、それを組み合わせてORやNOT、DFFなどのモジュールを作り、それらをさらに組み合わせてTD4を作りました。*1


Verilog HDLというハードウエア記述言語で書いたのですが、Nandモジュールを作る時にだけ、唯一次のように演算子( ~ や & )を使い、

assign out = ~(a & b);

あとはひたすらこのNandモジュール(関数)を呼び出してTD4を作りました。

実際はそんなことをせずとも、(あたりまえですが)もっと効率良く書けて、例えば "作ろう!CPU" では、たった69行でTD4を実装しています。

自分の場合は1600行ほど書くととになりました(テストコードを含めず)。


一言で言うとNand縛りでTD4を作ったという感じです。この非効率さは趣味ならではですね。なにより楽しかったです!

 

全体像


作ったTD4の大まかな概要としては↓になります。*2

f:id:hananirawataru:20210708204010p:plain


"CPUの創りかた"  にも同じように回路図があるのですが、こうして図で起こしてみて、初めて全体像がクリアになりました。


"CPUの創りかた" ではICチップで記載されていることが多いですが、ここでは自分が作ったモジュール名(nand2tetris風)で記載しています。

各モジュールに関して


Register


NandでDFFを作り、それを4個束にして4bitのRegisterとしました。TD4では、resetとloadは、Lowの時に1を割り当てられている負理論なので、そこに注意しました。(Nand2tetrisでは、Highの時に1が割り当てられている正理論だったので)
DFFに関しては、Nand2tetrisでは与えられていたパーツのため内部構造は不勉強でしたが、"作ろう!CPU" にDFFの詳しい解説があり、実際にNandと(Nandから作った)Notで作ることができました!


特に "作ろう!CPU" の "7.4 DFFの状態偏移図" が大変わかりやすく、DFFのテストの際にとても参考になりました。


Nand2tetrisの頃から、DFFって理解がなんだか難しいな、と感じていたのですが、自作のDFFにクロックを入力して、正しい挙動になることをテストしていったことで、DFFをより理解できたように思います。

 

ROM


書籍を参考に、4bit16入力として与えた各信号(プログラム)を、address(4bit)で、指定の命令が吐き出されるようにしました。

f:id:hananirawataru:20210708204929p:plain

 

ALU

TD4の場合ALUは、4bitのAdderなので、Nand2tetrisのAdderを参考に作りました。

 

 

Decoder

書籍の通り作ればOKでした。(改めて "CPUの創りかた" のDecodeのあたりを読むと、みるみるICが減っていって気持ちいいですね)

 

PC(プログラムカウント)

4bitのRegisterとAdderを使い、1clockごとに1ずつ足しされた出力になるようつなぎました。基本的にNand2tetrisで作ったものと同じでしたが、ここでもloadとresetが負理論なことに注意しました。

 

分周について


私のFPGA50MHzで動作するため、"CPUの創りかた" に合わせて1Hzに落としました。
以下のようにDFFを数珠繋ぎにして周波数を落としていっただけですが、最後のDFFに信号が届かないと、その間出力がずっと不定になるのが、どうにも落ち着かず、どうやってすべてをDFFを一度にresetするか、ここが一番頭を捻りました。

 

しばし頭を捻って、図の下の部分のように、最初のクロック入力をセレクタで選んで、resetが0(負理論なので)の時に全部のDFFに入れて解決でした。

(clock信号を直接DFFに入れないことに何故か抵抗がありました)

f:id:hananirawataru:20210708205840p:plain

最終形


最終的にはシンプルですが次のような形になりました。

f:id:hananirawataru:20210709200818p:plain

outputはFPGAに搭載されているLEDにつなぎ、inputはやはりFPGAに搭載されているスライドスイッチに繋いでいます。

 

ふりかえり


あえてNandゲートから全てを組み合わせてパーツを作っていくことで、一つ一つのパーツについて改めてよく知ることができたと感じます。
1パーツ作るごとに、テスト用のコード(仮の入力をあたえる)を書いて、Modelsimというシミュレータでテストしていき、大きなバグに悩まされることなく進めることができました。


冒頭にも書きましたが、やはり読んでみるだけではなくて作ってみるといろいろと理解が進みますね

 

今後は・・・

今後はNand2tetrisをFPGAで実装するのを見越して、このTD4をもう少し改善させようと思っています

 

  • 命令コード(data 8bit)を7セグメントディスプレイに表示

ROMから吐き出される8bitのコード(プログラム)を、せっかくFPGAに7セグメントディスプレイがついているので、そこに2桁の16進数で表示したいと思います。

 

  • ROMを改善

いまはプログラムの0と1を、直接ワイヤーにlowとhighを与える形で定義していますが、さすがに今後のことを考えるとイマイチなので、FPGAのメモリ領域(メモリブロック)を使って実装したいと思います。

 

  • SDカードから読み出し

私のFPGAはSDカードが使えるため、最終的にはSDカードにプログラムを書いて、それをROMにまず読み込ませてから、プログラムが走るようにしたいと思います。

  

まだまだNand2tetrisをFPGAで実装するには遠い道のりですが、少しずつでも進めていきたいと思います!

*1:これは嘘である。確かに私が書いたVerilog HDLのファイルではNandモジュールだけしか呼び出していないが、FPGAに書き込む前にコンパイルされた段階でNandのみのままとは限らず、むしろもっと効率の良い回路に置き換わっていると思われる。

*2:クロック信号とリセット信号は省略しています。正確にはRegisterとPCの全てにクロック信号とリセット信号が入ります