第8章
CPUの合成
これまでの章では、RV64IのCPUを作成してパイプライン化しました。今までは動作確認とテストはシミュレータで行っていましたが、本章では実機(FPGA)でCPUを動かします。
8.1 FPGAとは何か?
集積回路を製造するには手間と時間とお金が必要です。FPGAを使うと、少しの手間と少しの時間、安価に集積回路の実現をお試しできます。
FPGA(Field Programmable Gate Array)は、任意の論理回路を実現できる集積回路のことです。ハードウェア記述言語で設計した論理回路をFPGA上に設定することで、実際に集積回路を製造しなくても実機で論理回路を再現できます。
「任意の論理回路を実現できる集積回路」は、主にプロダクトターム方式、またはルックアップ・テーブル方式で構成されています。本書ではルックアップ・テーブル(Lookup Table, LUT)方式のFPGAを利用します。
X | Y | A |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
LUTとは、真理値表を記憶素子に保存しておいて、入力によって記憶された真理値を選択して出力する回路のことです。例えば、2つの入力X
とY
を受け取ってA
を出力する論理回路(表8.1)は、図8.2の回路で実現できます。ここでマルチプレクサ(multiplexer, MUX)とは、複数の入力を選択信号によって選択して出力する回路のことです。
図8.2では、記憶素子のデータをY
によって選択し、さらにX
によって選択することで2入力1出力の真理値表の論理回路を実現しています。入力がN個で出力がM個のLUTのことをN入力M出力LUTと呼びます。
ルックアップ・テーブル方式のFPGAは、多数のLUT、入出力装置、これらを相互接続するための配線によって構成されています。また、乗算回路やメモリなどの部品はFPGAよりも専用の回路で実現した方が良い*1ので、メモリや乗算回路の部品が内蔵されていることがあります。
[*1] 例えばメモリは同じパターンの論理回路の繰り返しで大きな面積を要します。メモリはよく利用される回路であるため、専用の回路を用意した方が空間的な効率が改善される上に、遅延が少なくなるという利点があります
本書では2つのFPGA(Tang Nano 9K、PYNQ-Z1)を使用して実機でCPUを動作させます。2024年11月12日時点ではどちらも秋月電子通商で入手できて、Tang Nano 9Kは3000円くらい、PYNQ-Z1は50000円くらいで入手できます。
8.2 LEDの制御
大抵のFPGAボードにはLEDがついています。本章では2つのテストプログラム(LEDを点灯させる、LEDを点滅させる)によって、CPUの動作確認を行います。
LEDはトップモジュールのポートを経由して制御します(図8.4)。ポートとLEDの接続方法は合成系によって異なるため、それらの接続方法は後で考えます。CPUからLEDを制御するには、メモリ経由で制御する、CSRによって制御するなどの方法が考えられます。本書ではLEDを制御するためのCSRを実装して、CSRをトップモジュールのポートに接続することでLEDを制御します。
8.2.1 CSRにLED制御用レジスタを実装する
RISC-VのCSRのアドレス空間には、読み込みと書き込みができるCSRを自由に定義できる場所(0x800
から0x8FF
)が用意されています*2。これの先頭アドレス0x800
をLEDの制御用レジスタのアドレスとして実装を進めます。
[*2] The RISC-V Instruction Set Manual Volume II: Privileged Architecture version 20240411 Table 3. Allocation of RISC-V CSR address ranges.
まず、CsrAddr型にLED制御用レジスタのアドレスを追加します(リスト8.1)。
書き込みマスクはすべて書き込み可にします(リスト8.2)。
LEDの制御用レジスタをcsrunitモジュールのポートに定義します。CSRの幅はUIntXです(リスト8.3)。
rdata
とwmask
にLEDの制御用レジスタの値を割り当てます(リスト8.4)。
リセット時にLEDの制御用レジスタの値を0に設定します(リスト8.5)。
LEDの制御用レジスタへの書き込み処理を実装します(リスト8.6)。
8.2.2 トップモジュールにLEDを制御するポートを実装する
LEDはトップモジュールのポートを経由して制御します(図8.4)。そのため、トップモジュールにLEDを制御するポートを作成して、csrunitのLEDの制御用レジスタの値を接続します(リスト8.7、リスト8.8、リスト8.9、リスト8.10)。LEDの個数はFPGAによって異なるため、とりあえずXLEN(=64)ビットのポートを定義します。
CSRの読み書きによってLED制御用のポートを制御できるようになりました。
8.2.3 テストを作成する
LEDを点灯させるプログラム
LEDを点灯させるプログラムを作成します(リスト8.11、リスト8.12)。CSRRWI命令で0x800
に12('b01100
)を書き込みます。
LEDを点滅させるプログラム
LEDを点滅させるプログラムを作成します(リスト8.13、リスト8.14)。これはちょっと複雑です。
リスト8.13は次のように動作します。
- x1に1000000(
(244 << 12) + 576
)を代入する - x2に0を代入
- x2がx1と一致するまでx2に1を足し続ける
- LEDのCSRをx3に読み取り、1を足した値を書き込む
- 1 ~ 4を繰り返す
これにより、LEDの制御用レジスタは一定の時間ごとに0
→1
→2
と値が変わっていきます。
8.3 FPGAへの合成① (Tang Nano 9K)
Tang Nano 9KをターゲットにCPUを合成します。使用するEDAのバージョンは次の通りです。
- GOWIN FPGA Designer V1.9.10.03
- Gowin Programmer Version 1.9.9
8.3.1 合成用のモジュールを作成する
Tang Nano 9KにはLEDが6個実装されています(図8.5)。そのため、LEDの制御には6ビット必要です。それに対して、topモジュールのled
ポートは64ビットであるため、ビット幅が一致しません。
Tang Nano 9Kのためだけにtopモジュールのled
ポートのビット幅を変更すると柔軟性がなくなってしまうため、topモジュールの上位に合成用のモジュールを作成して調整します。
src/top_tang.veryl
を作成し、次のように記述します(リスト8.15)。top_tangモジュールのled
ポートは6ビットとして定義して、topモジュールのled
ポートの下位6ビットを接続しています。
8.3.2 プロジェクトを作成する
新規プロジェクトを作成します。GOWIN FPGA Designerを開いて、Quick StartのNew Project...
を選択します。選択したら表示されるウィンドウでは、FPGA Design Projectを選択してOKを押してください(図8.6)。
プロジェクト名と場所を指定します。プロジェクト名はtangnano9k、場所は好きな場所に指定してください(図8.7)。
ターゲットのFPGAを選択します。GW1NR-LV9QN88PC6/I5
を選択して、Nextを押してください(図8.8)。
プロジェクトが作成されました(図8.9)。
8.3.3 設定を変更する
プロジェクトのデフォルト設定ではSystemVerilogを利用できないため、設定を変更します。
ProjectのConfigurationから、設定画面を開きます(図8.10)。
SynthesizeのVerilog LanguageをSystem Verilog 2017
に設定します。同じ画面でトップモジュール(Top Module/Entity)を設定できるため、core_top_tang
を指定します(図8.11)。
8.3.4 設計ファイルを追加する
Verylのソースファイルをビルドして、生成されるファイルリスト(core.f
)を利用して、生成されたSystemVerilogソースファイルをプロジェクトに追加します。
Gowin FPGA Programmerでファイルを追加するには、ウィンドウ下部のConsole画面でadd_file
を実行します。しかし、add_file
はファイルリストの読み込みに対応していないので、ファイルリストを読み込んでadd_file
を実行するスクリプトを作成します(リスト8.16)。
ウィンドウの下部にあるConsole画面で、次のコマンドを実行します(リスト8.17、図8.12)。VerylをWSLで実行してGOWIN FPGA DesignerをWindowsで開いている場合、ファイルリスト内のパスをWindowsから参照できるパスに変更する必要があります。
ソースファイルを追加できました(図8.13)。
8.3.5 制約ファイルを作成する
物理制約
top_tangモジュールのclk、rst、ledポートを、それぞれTang Nano 9Kの水晶発振器、ボタン、LEDに接続します。接続の設定には物理制約ファイルを作成します。
新しくファイルを作成するので、プロジェクトを左クリックしてNew Fileを選択します(図8.14)。
物理制約ファイルを選択します(図8.15)。
名前はtangnano9k.cst
にします(図8.16)。
物理制約ファイルには、次のように記述します(リスト8.18)。
IO_LOC
で接続する場所の名前を指定します。
場所の名前はTang Nano 9Kのデータシートで確認できます。例えば図8.17と図8.18から、LEDは10
、11
、13
、14
、15
、16
に割り当てられていることが分かります。また、LEDが負論理(1
で消灯、0
で点灯)であることが分かります。水晶発信器とボタンについても、データシートを見て確認してください。
タイミング制約
FPGAが何MHzで動くかをタイミング制約ファイルに記述します。
物理制約ファイルと同じようにAdd Fileを選択して、タイミング制約ファイルを作成します(図8.19)。
名前はtiming.sdc
にします(図8.20)。
タイミング制約ファイルには、次のように記述します(リスト8.19)。
Tang Nano 9Kの水晶発振器は27MHzで振動します。そのため、create_clock
でclk
ポートの周期を37.037
ナノ秒(27MHz)に設定しています。
8.3.6 テスト
LEDの点灯を確認する
まず、LEDの点灯を確認します。
インポートされたtop_tang.sv
のtopモジュールをインスタンス化している場所で、MEMORY_FILEPATH
パラメータの値をtest/led.hex
のパスに設定します(リスト8.20)。
ProcessタブのSynthesizeをクリックし、合成します(図8.21)。
そうすると、合成に失敗して図8.22のようなエラーが表示されます。
これは、Tang Nano 9Kに搭載されているメモリ用の部品の数が足りないために発生しているエラーです。この問題を回避するために、eeiパッケージのMEM_ADDR_WIDTH
の値を10
に変更します*3。メモリの幅を変更したら、Verylファイルをビルドしなおして、もう一度合成します。
[*3] 適当な値です
合成に成功したら、Place & Route
を押して、論理回路の配置配線情報を生成します(図8.23)。それが終了したら、Tang Nano 9KをPCに接続して、Gowin Programmerを開いて設計をFPGAに書き込みます(図8.24)。
Tang Nano 9Kの中央2つ以外のLEDが点灯していることを確認できます。図8.5の左下のボタンを押すと全てのLEDが点灯します。
LEDの点滅を確認する
MEMORY_FILEPATH
パラメータの値をtest/led_counter.hex
のパスに設定します(リスト8.21)。
合成、配置配線しなおして、設計をFPGAに書き込むとLEDが点滅します*4。図8.5の左下のボタンを押すと状態がリセットされます。
8.4 FPGAへの合成② (PYNQ-Z1)
PYNQ-Z1をターゲットにCPUを合成します。使用するEDAのバージョンは次の通りです。
- Vivado v2023.2
初めてPYNQ-Z1を使う人は、PYNQのドキュメントやACRiの記事を参考に起動方法を確認して、Vivadoにボードファイルを追加してください。
8.4.1 合成用のモジュールを作成する
PYNQ-Z1にはLEDが6個実装されています(図8.26)。本章ではボタンの上の横並びの4つのLED(図8.26右下)を使用します。
「8.3.1 合成用のモジュールを作成する」とおなじように、led
ポートのビット幅を一致させるためにPYNQ-Z1の合成のためのトップモジュールを作成します。
src/top_pynq_z1.veryl
を作成し、次のように記述します(リスト8.22)。top_pynq_z1モジュールのled
ポートは4ビットとして定義して、topモジュールのled
ポートの下位4ビットを接続しています。
8.4.2 プロジェクトを作成する
Vivadoを開いて、プロジェクトを作成します。Quick StartのCreate Projectを押すと、図8.27が出るのでNextを押します。
プロジェクト名とフォルダを入力します(図8.28)。好きな名前と場所を入力したらNextを押します。
プロジェクトの形式を設定します(図8.29)。RTL Projectを選択して、Do not specify sources at this time
にチェックを入れてNextを押します。
ターゲットのFPGAボードを選択します(図8.30)。今回はPYNQ-Z1がターゲットなので、Boardsタブに移動してPYNQ-Z1を選択します。PYNQ-Z1が表示されない場合、ボードファイルをVivadoに追加してください。
概要を確認して、Nextを押します(図8.31)。
プロジェクトが作成されました(図8.32)。
8.4.3 設計ファイルを追加する
Verylのソースファイルをビルドして、生成されるファイルリスト(core.f
)を利用して、生成されたSystemVerilogソースファイルをプロジェクトに追加します。
Vivadoでファイルを追加するには、ウィンドウ下部のTcl Console画面でadd_file
を実行します。しかし、add_file
はファイルリストの読み込みに対応していないので、ファイルリストを読み込んでadd_file
を実行するスクリプトを作成します(リスト8.23)。
ウィンドウの下部にあるTcl Console画面で、次のコマンドを実行します(リスト8.24、図8.33)。VerylをWSLで実行してVivadoをWindowsで開いている場合、ファイルリスト内のパスをWindowsから参照できるパスに変更する必要があります。
ソースファイルが追加されました(図8.34)。
8.4.4 Verilogのトップモジュールを作成する
VerylファイルはSystemVerilogファイルに変換されますが、VivadoではトップモジュールにSystemVerilogファイルを使用できません。この問題を回避するために、Verilogでtop_pynq_z1モジュールをインスタンス化するモジュールを記述します(リスト8.25)。
core_top_v.v
をadd_filesでプロジェクトに追加します(リスト8.26)。
8.4.5 ブロック図を作成する
Vivadoではモジュール間の接続をブロック図によって行えます。設計したモジュールをブロック図に追加して、クロックやリセット、LEDの接続を行います。
ブロック図の作成とトップモジュールの設定
画面左のFlow NavigatorでCreate Block Design
を押してブロック図を作成します(図8.35)。
名前は適当なものに設定します(図8.36)。
Sourcesタブに作成されたブロック図が追加されるので、右クリックしてCreate HDL Wrapper...
を押します(図8.37)。
そのままOKを押します(図8.38)。
ブロック図がVerilogモジュールになるので、Set as Top
を押して、これをトップモジュールにします(図8.39)。
ブロック図がトップモジュールに設定されました(図8.40)。
ブロック図の設計
Diagram画面でブロック図を組み立てます。
まず、core_top_vモジュールを追加します。適当な場所で右クリックして、Add Module...
を押します(図8.41)。
core_top_vを選択して、OKを押します(図8.42)。
core_top_vが追加されるので、ledポートをクリックしてMake External
を押します(xilinx/bd/8
)。
led_0ポートが追加されました(図8.44)。これがブロック図のoutputポートになります。
led_0を選択して、左側のExternal Port PropertiesのNameをledに変更します。
次に、+ボタンを押してZYNQ7 Processing System、Processor System Reset、Clocking Wizardを追加します(図8.46、図8.47)。
上にDesigner Assistance Available
と出るので、Run Block Automation
を押します(図8.48)。
図8.49のようになっていることを確認して、OKを押します。
図8.50のようにポートを接続します。接続元から接続先にドラッグすることでポートを接続できます。また、proc_sys_reset_0のext_reset_inをMake Externalしてrstを作成してください。
ZYNQ7 Processing Systemのoutputポートには、100MHzのクロック信号FCLK_CLK0
が定義されています。これをそのままcore_top_vに供給しても良いですが、現状のコードではcore_top_vが100MHzで動くように合成できません。そのため、Clocking Wizardで50MHzに変換したクロックをcore_top_vに供給します。
clk_wiz_0をダブルクリックして、入力を50MHzに変換する用に設定します。clk_out1のRequestedを50に変更してください。また、Enable Optional ~のresetとlockedのチェックを外します(図8.51)。
clk_wiz_0が少しコンパクトになりました(図8.52)。
8.4.6 制約ファイルを作成する
ブロック図のrst、ledを、それぞれPYNQ-Z1のボタン(BTN0)、LED(LD0、LD1、LD2、LD3)に接続します。接続の設定には物理制約ファイルを作成します。
pynq.xdc
を作成し、次のように記述します(リスト8.27)。
リスト8.27では、rstにD19を割り当てて、led[0]、led[1]、led[2]、led[3]にR14、P14、N16、M14を割り当てます(図8.53)。ボタンは押されていないときに1
、押されているときに0
になります。LEDは1
のときに点灯して、0
のときに消灯します。
8.4.7 テスト
LEDの点灯を確認する
ブロック図のcore_top_v_0をダブルクリックすることで、core_top_vモジュールのMEMORY_FILEPATH
パラメータを変更します。パラメータにはテストのHEXファイルのパスを設定します(図8.54)。LEDの点灯のテストのためにtest/led.hex
のパスを入力します。
PROGRAM AND DEBUGのGenerate Bitstream
を押して合成と配置配線を実行します(図8.55)。
合成が完了したらOpen Hardware Manager
を押して、開かれたHARDWARE MANAGERのOpen Target
のAuto Connect
を押してPYNQ-Z1と接続します(図8.56)。
Program device
を押すと、PYNQ-Z1に設計が書き込まれます。
LEDが点灯しているのを確認できます(図8.58)。BTN0を押すとLEDが消灯します。
LEDの点滅を確認する
core_top_vモジュールのMEMORY_FILEPATH
パラメータの値をtest/ledcounter.hex
のパスに変更して、再度Generate Bitstream
を実行します。
Hardware Managerを開いてProgram deviceを押すとLEDが点滅します*5。BTN0を押すと状態がリセットされます。