『動かしてわかるCPUの作り方10講』 第4講~第6講
前回の記事に引き続いて、『動かしてわかるCPUの作り方10講』で学習した内容をまとめていく。
今回の範囲は第4講から第6講までである。第4講では論理回路を復習し、第5講ではVHDLによる論理回路の記述を学んでいく。最後に第6講ではFPGA上でVHDLによって設計した回路を実際に動かしていく。これにより次講以降にCPUをFPGA上で実装していく準備が整っている。
前回の記事では書きそびれていたが、この記事では書籍そのものの内容を再度説明することはせず、私の気付きや実装上の工夫やつまづきについて書いていく。対象読者としては書籍を読んでいる途中の人を想定しているためである。ニッチにはなるものの、書籍の劣化コピーを作る意味は(自身の理解の定着を除いて)ないと考え、この選択に至った。
第4講
- 論理回路についての復習が目的
- 半加算器、全加算器などは大学で学んではいたものの、うろ覚えだったので良い復習になった
- フリップフロップ、とくにD-FFは頭から抜けていたので学べてよかった。D-FFは2つの意味で学んでおくと良い内容だった。
- 一つは「クロックが立ち上がるタイミングで値を固定する」という今後VHDLでの記載でもよく出てくる考え方が、最もシンプルに現れている例だというところ。タイミングを制御したくなる理由としては、現実の回路では遅延があり、その計算を待つ必要があるからである。例えばCPUにおいても、命令のフェッチとデコードなどでフェーズを分けて計算を行っている。突然クロックが出てくるとやや複雑に見えるが、どのような理由で必要になるのかがこの例を通じて理解できた
- もう一つは、回路は必然的に遅延を伴うが、それでも「クロックが立ち上がるタイミングで値を固定する」というピンポイントな制御をどう実現できるかを学べる点である。D-FFではマスタースレーブ方式を取っており、「固定したい値」を事前にマスター部で計算しておいたうえで、スレーブ部がクロックが立ち上がったタイミングでその値を固定する。遅延が生じる計算をマスター部で事前に行っておくことで、所望の制御を可能にしている。このような「クロックの立ち上がり前に事前に計算しておく」という発想は今後も出てくるし、この意味で重要な項目だと考えている。
第5講
- VHDLの説明
- FPGA上での実装については次の講で触れるため、この講自体の感想はあまりない。
- プロセス文についてはやや驚いた。なんとなく回路図をそのまま記載していくイメージを持っていたのと、序盤の回路は実際そのような書き方をしていたため、手続き型的な書き方ができるとは想定していなかった。とは言うもののあとから振り返ると、クロックに応じた処理など書きたい場面もあり、存在するのは必然的だとは思う。
第6講
今回の記事で扱っているパートの山場で、FPGA上でVHDLにて書いた回路を実際に動かしていく。動かしてみると色々エラーやバグに巻き込まれるのもあり、結構骨が折れる部分だった。
今回も実装は公開している。https://github.com/so-nakashima/fpga-cpu-implementation/tree/master/fpga_samples。同環境で学習される方がいたら参考になっていると嬉しい。
FPGA評価ボード
記事執筆時点(2025/08)においては一番バランスが良さそうだったBasys 3を使用した。書籍のおすすめのDE0-CVはすでに終売したのか手に入らなかった。
FPGA評価ボードとは何かや、どのようなものを買うべきかは以下にまとめた。Basys 3もいつまで販売が続いているかはわからず、その時その時で最適な評価ボードの選定が必要になるため、判断基準として参考になれば嬉しい。
- FPGAに回路を書き込んで実験をしていきたい
- ただ単体のFPGAは扱いにくい。FPGAに回路を書き込むための回路を電子工作で作る必要がある。また入出力のスイッチやLED、7セグメント(数字を表示するやつ)を電子工作で作る必要もある。
- 電子工作をするのはあまりにも大変なので、必要な機能が全部揃っているFPGA評価ボードを買うと楽
- 今回の書籍については、以下が有ると正常に実装できているかの確認が楽
- 入力
- 何らかのon/offがあるスイッチ(できれば16bit)
- CPUで実行するアセンブラを書き込める入力(Basys 3の場合はUSBで入力できそう)
- 出力
- LED(できれば16bit)
- 7セグメント。最低4桁。Basys 3は4桁だができれば6桁ほしかった。4桁でも16進数なら対象とする16bitのメモリやレジスタの中身を表示できるものの、6桁で10進数で表示できると楽。
- 入力
またAMD系かIntel系かで開発環境が大きく変わるのにも注意。書籍はIntel系だが、Basys3はAMD系。意外となんとかなったが、書籍のとおりには進められない部分は出てきてしまっていた。
.xdc
制約ファイルFPGA上でVHDLを実行する際には、「制約ファイル」という新しい概念が必要となる。これはVHDLで記載した机上のポートと、実際のFPGAのピンとを紐づけるものである。
VHDLでは、入出力ポートが何かと、入力から(履歴付きで)定まる出力の挙動を記載する。この際に入出力ポートは机上のもので、実際のピン配置とは対応していない。例としては、以下のような状況が現れる。
entity half_adder is
port(
a : in std_logic;
b: in std_logic;
sum: out std_logic;
carry: out std_logic
);
end half_adder;
architecture rtl of half_adder is
begin
sum <= a xor b;
carry <= a and b;
end rtl;
前半のentity
節にて入手強くポートを定義し、後半のarchitecture
節にてその挙動を定義している。重要なこととして、定義した入力ポートa,b
や出力sum, carry
が何かについては記載していない。現実にはFPGAのどのピンと対応し、評価ボードのどのスイッチやLEDと対応するかを定義する必要があるが、これはVHDLの責任範囲外となっている。
この対応関係を記載するものとして制約ファイルが出てくる。Basys3の場合は以下のような記載をする。
# トグルスイッチ
# 一番右のスイッチを a に、その隣のスイッチを b に接続
set_property PACKAGE_PIN V17 [get_ports {a}]
set_property IOSTANDARD LVCMOS33 [get_ports {a}]
set_property PACKAGE_PIN V16 [get_ports {b}]
set_property IOSTANDARD LVCMOS33 [get_ports {b}]
# LED
# 一番左の LED を sum に、その隣の LED を carry に接続
set_property PACKAGE_PIN U16 [get_ports {sum}]
set_property IOSTANDARD LVCMOS33 [get_ports {sum}]
set_property PACKAGE_PIN E19 [get_ports {carry}]
set_property IOSTANDARD LVCMOS33 [get_ports {carry}]
set_property PACKAGE_PIN E19
ではVHDLでの入出力とFPGA上でのピンとの関係を定めている。またset_property IOSTANDARD LVCMOS33 [get_ports {carry}]
ではhighの電圧(今回は3.3V)を定めている。
Basys 3のピン配置は、このPDFや公式サイトが役に立った。
開発環境
VHDLで記載した回路をBasys 3で実行するためには、Vivadoをインストールする必要がある。公式サイトに説明があるものの、微妙に古かったりVivadoのサイトがわかりにくかったりでやや大変。LLMに聞きながらインストールするのが良いと思う。
開発の流れ
大雑把に言うと以下の流れで進む。
- Vivadoでプロジェクトを作成する
- VHDL/制約ファイルを記載する
- (Optional)シミュレーションを実施してテストする
- ビットストリームを生成し、FPGAに書き込み、実機でテストする
Vivadoでのプロジェクト作成
-
File > Project > new
から作成すれば良い - 過去作成したプロジェクトは、トップ画面の
Recent Projects
から開ける - 本当はgit管理など考えると作成場所を考えたほうが良いのだろうが、よくわからず適当にやってしまった。
VHDL/制約ファイルの記載
Vivado上でVHDLを記述する事もできるものの、使い慣れたエディターに変更するほうが便利。とくにCursorなどLLMの支援を受けられるものにすると便利であった。とはいえPythonなどと比べるとそれほど精度は高くなく注意は必要だが。エディター変更についてはこの記事が参考になった。
Vivado上ではソースコードの階層構造は、エンティティの依存関係から自動で整理される。例えば、full_adder
の記述でhalf_adder
を使っていたのであれば、full_adder
の子としてhalf_adder
が表示される。このためフォルダ配置はあまり気にしすぎる必要はなさそう。実際にフォルダ上での階層構造と一致させるオプションも有るらしいが使っていないので詳細はわかっていない。代わりに気をつけておくべきは以下である。
- design/constraints/simulationの区別
- design: 回路についてVHDLで記載する。
- constraints: VHDLでの入出力ポートとFPGAのピンの対応関係を記載する
- simulation: シミュレーション用のファイルを記載する。
- design/simulationでの "top" の設定。「コンパイル対象」の意味である。ファイルを右クリックして
set as top
で設定できる。
細かな記載方法についてはGitHubにてサンプルを見ていただきたい。
シミュレーションの実行
シミュレーションについてもVHDLで記載する。クロックや入力信号については、process文を使って自分で定義する必要がある。詳しくは書籍やGitHubを参照願いたい。
シミュレーションの実行は、左側のバーのRun Simulation → Run Behaviral Simulation
で実施できる。実行できると下図のような画面が立ち上がる。時間ごとの各変数の値を確認できる。各変数の値の変化までジャンプする機能があり、これを使いながら正常に値が設定されているかをみていく。またassert
分を使ってテストを書くこともできるよう。
ビットストリームの生成とFPGAへの書き込み
-
Run Synthesis
,Run Implementation
,Generate Bitstream
を順番に実行する。いきなりGenerate Bitstream
を実行しても前2つをやってくれる。 - だいたいこのタイミングで構文エラーが見つかるので直す
-
Generate Bitstream
が成功したら、Open Hardware Manager → Auto connect
からBasys 3に接続する。この段階でBasys 3をPCにUSB接続し、電源を入れておく必要がある。 - 接続できたら
program device
からFPGAへの書き込みを実行できる - Basys 3で挙動を確認する。楽しい時間
- Basys 3の電源を落とすとデフォルトのデモプログラムに戻るのに注意。固定化させるにはフラッシュへの書き込みが必要らしいが、現時点では不要なため行っていない。
開発時につまづいたポイント
実際に手を動かすと難しいところがいくつかあったのでまとめておく。
構文について
- セミコロンの有無。ポート最後のセミコロンは不要など細かなルールが有る。
- 構文自体が不慣れでミスがあった。
end
のあとに何を書くか間違えていたなど -
std_logic_vector(3 downto 0)
とすると、上位ビットが左側なのに注意がいる。
入出力
- Basys 3での7セグメントの使い方
- active-lowである。つまり1/0が反転している。
- 4桁あるが、数字の表示については配線が共有されている。使い方としては、まず
an[0]-an[3]
で、どの桁の表示を更新したいかを選ぶ。そのうえでCA,CB,...,CG
にて各セグメントのオンオフを制御する。桁の選択はクロックで行うと良い。この際にダウンクロックしておくとちらつかない - Basys 3上での16bit整数の16新数表示については
seg7_driver
として作成した。やや作成が重いので、今後学習される方はこれに限らず既存の実装を使ってしまって良いと思う。
FPGA上での処理
-
表示上ダウンクロックしたくなる場面が有るが、基本的には特殊な回路を実装してはいけない。クロックの立ち上がり回数を数え上げ、それにより分岐する書き方をする方が良い。特殊な回路を実装すると、回路間でのクロックの同期が取れなくなってしまったり遅延が発生したりなどの問題が発生するためである。FPGA上ではクロックは専用の回路で同期してかつ遅延が少ないように伝送されているらしい。
-
process文を複数使う際には、同じ変数に複数のプロセス分からの入力になっていないか注意。状況次第では値が未確定になりバグとなる
Discussion