DE0-NanoでCPU自作を目指す(1) - Quartusプロジェクト作成からLED点灯まで
自作CPUをFPGA上で動かす、という目標を立てたまま数年放置していたのですが、重い腰を上げてDE0-Nanoを引っ張り出してきました。この連載では、Quartusのプロジェクト作成からSOF書き込みまでの一連の流れを、自分の手を動かしながら記録していきます。
最終的なゴールは簡易CPUです。ただし最初から大きなものを作ろうとすると必ず迷子になるので、今回はDIPスイッチの状態をLEDにそのまま出すだけの、拍子抜けするくらい地味な回路から始めます。
今回作るもの
DE0-Nanoに乗っている4個のDIPスイッチを、そのまま8個のLEDの下位4bitに出力します。それだけです。クロックは配線だけ用意しておいて、今回はまだ使いません。
なぜこんな最小構成から始めるかというと、以前FPGAを触り始めたころ、いきなりUARTとメモリコントローラを同時に書こうとして訳が分からなくなった経験があるからです。LED、スイッチ、クロック、レジスタ、メモリ、UARTと部品を一つずつ確認していくほうが、結局は近道だと感じています。

DE0-Nanoのスペックは今更ですが、一応おさらいしておきます。50MHzクロック、緑色LED×8、プッシュボタン×2、DIPスイッチ×4、32MB SDRAM。今回使うのはLEDとDIPスイッチだけで、残りは後半で登場する予定です。
回路の中身
入力は4bit、出力は8bitのうち下位4bitだけを使います。上位4bitは0固定にしています。理由は単純で、あとでカウンタなどを追加したときに「どのLEDが何を表しているか」を混同したくないからです。境界を先に決めておくと、後から信号が増えても迷いにくくなります。
トップレベルのポートにclk_50を残してあるのも同じ理由です。今は使いませんが、次回以降でカウンタを作るときにここを触ることになります。
Verilogコード
01_top.v
module de0_nano_cpu(
input wire clk_50,
input wire [3:0] sw,
output wire [7:0] led
);
assign led[3:0] = sw;
assign led[7:4] = 4'b0000;
endmoduleこれだけです。拍子抜けするほど短いのですが、ここで押さえておきたいのはassign文の意味です。ソフトウェアの代入と違って、これは「配線そのもの」を表しています。sw[0]が変化した瞬間にled[0]も変化します。クロックもレジスタも介さない、純粋な組み合わせ論理です。
正直なところ、Verilogを最初に触ったとき「assignって代入なのに、なぜ毎フレーム実行されないんだ」と混乱した記憶があります。プログラミング言語の癖でコードを上から下へ実行される手続きだと思って読んでしまうと、この後always @(posedge clk)が出てきたあたりで確実につまずきます。回路は「同時に存在する配線とレジスタの集合」であり、クロックエッジごとにレジスタだけが更新される、という理解に切り替える必要があります。
Quartusでの手順
Quartus Prime Liteで新規プロジェクトを作成する。

プロジェクトの作成場所とプロジェクト名、トップモジュール名を設定する。

デバイスの情報を指定する。

コードを書く。
Ctrl + Shift + N でPin Plannerまたは.qsfファイルを使用してピン割り当てを行う。
Complie → Designを実行する。
※Analysis & Synthesis → Fitter → Assembler~の順に実行されます。

Programmerで書き込む(Auto Detect → Start)

ピン番号はボードのリビジョンによって変わることがあるため、手元のDE0-Nano付属資料か、Terasicのユーザーマニュアルを見ながら割り当てるようにしてください。ここを疎かにすると、Fitterまで通ったのに実機ではまったく反応しない、という一番心が折れるパターンにハマります。
今回のコードで使うポートについては、DE0-Nano(Cyclone IV E / EP4CE22F17C6)の標準的な割り当ては以下の通りです。IO規格はすべて3.3-V LVTTLです。
信号名 | ピン番号 |
clk_50 | PIN_R8 |
sw[0] | PIN_M1 |
sw[1] | PIN_T8 |
sw[2] | PIN_B9 |
sw[3] | PIN_M15 |
led[0] | PIN_A15 |
led[1] | PIN_A13 |
led[2] | PIN_B13 |
led[3] | PIN_A11 |
led[4] | PIN_D1 |
led[5] | PIN_F3 |
led[6] | PIN_B1 |
led[7] | PIN_L3 |
Pin PlannerにひとつずつLocationを打ち込んでもいいですが、慣れるまではQSFに直接書いてしまうほうが速いです。プロジェクトの.qsfファイルに以下を追記すればまとめて割り当てられます。
set_location_assignment PIN_R8 -to clk_50
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to clk_50
set_location_assignment PIN_M1 -to sw[0]
set_location_assignment PIN_T8 -to sw[1]
set_location_assignment PIN_B9 -to sw[2]
set_location_assignment PIN_M15 -to sw[3]
set_location_assignment PIN_A15 -to led[0]
set_location_assignment PIN_A13 -to led[1]
set_location_assignment PIN_B13 -to led[2]
set_location_assignment PIN_A11 -to led[3]
set_location_assignment PIN_D1 -to led[4]
set_location_assignment PIN_F3 -to led[5]
set_location_assignment PIN_B1 -to led[6]
set_location_assignment PIN_L3 -to led[7]IO_STANDARDの指定を省略するとデフォルト値が使われてしまうことがあるので、気になる場合はsw/led側にもset_instance_assignment -name IO_STANDARD "3.3-V LVTTL"を1本ずつ足しておくと安心です。
自分が最初にハマったのは、トップモジュール名の指定を忘れていたパターンでした。ファイルは追加していたのに合成対象に入っておらず、コンパイルは通るのにピン割り当て画面に信号が一つも出てこなくて、小一時間悩んでしまいました。Project Navigatorでトップエンティティが正しく設定されているか、最初に確認する癖をつけておくとよいと思います。
実機での確認
書き込み後、DIPスイッチを動かすと対応するLEDが変わります。……はずだったのですが、最初の書き込みではLEDが期待と逆に光ってしまい、「あれ?」となりました。DE0-NanoのLEDはアクティブHigh(1で点灯)ですが、ボードによってはアクティブLowの実装もあるので、資料で極性を確認しておくことをおすすめします。
確認の順番としては、以下のように進めています。
入力を固定して出力を見る
1bitずつ入力を変えて、対応するLEDだけが変わるか確認する
(クロックを使う回路が出てきたら)リセット直後、1クロック後、連続操作後の値を確認する
反応がないときは、いきなりコードを疑うのではなく、トップモジュール名・未接続ピン・ピン割り当ての順に確認すると早く原因にたどり着けます。大体この3つのどれかで詰まっていることが多いです。
シミュレーションを行う場合、組み合わせ回路であれば全入力パターンを短いテストベンチで流すだけで十分です。今回のような4bit入力であれば16パターンしかないため、波形を見るまでもなく目視でも十分確認できます。
つまずきやすいところ
Quartusで最初につまずくのは、正直なところVerilogの文法よりもプロジェクト設定であることが多いです。ファイルを追加したつもりでもトップモジュール名がずれていて合成対象外になっている、ピン割り当てを忘れて論理合成は通ったのにボードのLEDに繋がっていない、というのが定番のパターンです。
もう一つ、今回のコードには出てきませんが先に触れておくと、ボタンやスイッチを直接クロックとして使わないことも重要です。機械式スイッチはチャタリングしますし、そもそもFPGA内部のクロックネットワークに直接繋ぐようなピンでもありません。ボタンは通常の入力として受け、50MHzクロックに同期させてから使うようにします。これを最初に守っておくと、後々レジスタやFSMを設計するときの安定性が大きく変わってきます。
次回予定
次回は、DIPスイッチを入力としてAND、OR、NOT回路を作ります。
今回まではスイッチの状態をLEDへそのまま出していましたが、次回からは入力された値に論理演算を加え、別の値を出力する回路へ進みます。
AND、OR、NOTは単体では非常に単純な回路ですが、後に作る加算器、比較器、マルチプレクサ、命令デコーダ、ALUの基礎になります。ここからは、各回で作った回路を部品として残し、最終的なCPUへ少しずつ組み込んでいきます。
今回作った回路は本当に小さなものですが、これがメモリマップI/Oの出力先になったり、MUXの選択信号になったりと、後々のCPU設計にそのまま繋がっていく部品になります。焦らず一つずつ潰していきたいと思います。
略語一覧
略語 | 正式名称 | 補足 |
FPGA | Field-Programmable Gate Array | 論理回路を後から書き換えられる半導体デバイス |
CPU | Central Processing Unit | 中央演算処理装置。今回の連載の最終ゴール |
SOF | SRAM Object File | QuartusがFPGAへ書き込むためのビットストリームファイル |
DIP | Dual In-line Package | 2列のピンを持つ部品形状。今回はDIPスイッチとして使用 |
LED | Light Emitting Diode | 発光ダイオード |
ALU | Arithmetic Logic Unit | 算術論理演算ユニット。加減算や比較を行う回路 |
MUX | Multiplexer | 複数信号から1つを選択して出力する回路 |
FSM | Finite State Machine | 有限状態機械。状態遷移で動作を制御する回路 |
UART | Universal Asynchronous Receiver/Transmitter | 調歩同期式のシリアル通信インターフェース |
SDRAM | Synchronous Dynamic Random Access Memory | クロック同期型のDRAM。DE0-Nanoに32MB搭載 |
PC | Program Counter | 次に実行する命令のアドレスを保持するレジスタ |
I/O | Input/Output | 入出力 |
bit | Binary Digit | 2進数1桁分のデータ単位 |
opcode | Operation Code | 命令の種類を表すビット列 |


