この記事では、前回まで解説してきた MIG ベースの DRAM コントローラのテストプログラムと先端の RISC-V ソフトプロセッサへの応用を説明していきます。
ソースコード:
- テストプログラム: https://github.com/thiemchu/dram-arty-a7
- RISC-V ソフトプロセッサ: https://github.com/thiemchu/rvcorep
- Clocking Wizard ベースのクロック生成スクリプト: https://github.com/thiemchu/clkwiz
説明では、現時点 (2020年8月) ACRi ルームで使用可能な Vivado 2019.2 を使用します。ターゲットの FPGA ボードは Arty A7-35T です。
テストプログラムと RISC-V ソフトプロセッサの両方のシミュレーションコードを github リポジトリで公開していますが、これは次回の記事のトピックスです。今回は FPGA の実装に注目します。
テストプログラム
下図はテストプログラムの全体の構成を示しています。
テスト回路
まず、テスト回路 (User Design) を説明します。図に示すように、この回路は Random Data (RandData) Generator, Address Generator, State Machine, Data Transmitter という4つのビルディングブロックから構成されます。ソースコードの次のファイルで実装されています。
- RandData Generator:
rtl/data_generator.v
- Address Generator:
rtl/addr_generator.v
- State Machine:
rtl/top.v
- Data Transmitter:
rtl/data_transmitter.v
Random Data Generator は DRAM の書き込みデータ (wdata – write data) と DRAM から読み出したデータを検証するためのデータ (vdata – validation data) を生成します。どちらのデータも xorshift128 plus という疑似乱数生成器で生成されます。Xorshift128 plus の実装は rtl/common/xorshift128plus.v
ファイルにあります。
疑似乱数生成器なので同じシードを提供すれば、同じ乱数系列が得られます。テストプログラムはこの特性を利用して DRAM に書き込んだデータ (wdata) と全く同じデータ (vdata) を生成し、読み出しデータとの一致検証を行います。
Address Generator は DRAM の書き込みアドレス (waddr – write address) と読み出しアドレス (raddr – read address) を生成します。書き込みデータと読み出しデータの一致検証をするために、書き換えが発生しない書き込みアドレスの系列と同じ読み出しアドレスの系列を生成する必要があります。
State Machine は DRAM の書き込みと読み出しのタイミングを制御し、書き込みデータと読み出しデータのサンプル (sample w/rdata – sample write/read data) を Data Transmitter 経由でホストPCに送ります。まず、DRAM の初期化 (キャリブレーション、calibration) が完了したら、Address Generator と RandData Generator によって生成されたシーケンシャルアドレス、乱数のデータで書き込みを行います。次に、DRAM の全てのアドレスの書き込みが終わったら、読み出しフェーズに入ります。検証をするために、読み出しフェーズで生成するアドレスの系列を書き込みアドレスの系列と同じにします。RandData Generator は書き込みフェーズと同じ乱数のデータの系列を生成します。これらのデータと DRAM から読み出されたデータを比較することにより、DRAM の書き込みと読み出しが正確に行われたかが分かります。
Data Transmitter はホスト PC とのシリアル通信を実現します。シリアル通信の実装は rtl/common/serial.v
ファイルにあります。通信の速度 (baud rate) は serial.v
で定義されている SERIAL_WCNT
というパラメータで調整できます。テスト回路の動作周波数を f (MHz)、通信の速度を b (MegaBaud) とすると、SERIAL_WCNT
を f/b に設定します。例えば、f, b がそれぞれ 100MHz と 1MegaBaud の場合、SERIAL_WCNT
を100に設定する必要があります。
クロック信号の設計
前回の記事で説明したように、Arty A7-35T ボード上の DRAM を動かすために、MIG に2つのクロック信号を提供する必要があります。1つ目は MIG の入力クロック (166.67MHz) です。2つ目は 200MHz の参照クロックです。図1に示すように、これらのクロックは clk_wiz_1
という clocking wizard IP コアで生成します(ソースコードの rtl/clk_wiz_1/
にあります)。clk_wiz_1
の入力は Arty A7-35T ボード上の水晶振動子 (crystal oscillator) からの 100MHz クロック信号です。
前回の記事でも説明しましたが、MIG は、自分自身の制御のために、外に1つのクロック信号を出力しています。このクロック信号の動作周波数は MIG の構成時に設定した DRAM チップの動作周波数に依存します。現在の設定では、DRAM チップの動作周波数は 333.33 MHz で、MIG によって出力されるクロック信号の動作周波数は 333.33/4 = 83.33 MHz となっています。
アプリケーション回路 (この場合テスト回路) の動作周波数を自由に変更するために、clocking wizard IP コアclk_wiz_0
を設けます。clk_wiz_0
は MIG の出力クロック信号を受けて、アプリケーション回路のクロック信号を生成します。ここで、アプリケーション回路 (テスト回路) のクロック信号は 100MHz とします。
clk_wiz_0 の生成には Vivado の GUI (グラフィカルユーザインタフェース) で簡単に実現できますが、後の節先端の RISC-V ソフトプロセッサへの応用で説明するように、Vivado の batch mode (コマンドラインモード) を利用する場合は以下のスクリプトが非常に便利です。
詳細は上記の github リポジトリの README で書かれていますが、clk_wiz_0 の生成コマンドは以下の通りです(動作周波数の小数部の桁数を3にしています)。
./clkwiz.sh aa35 n 83.333 100.000 20192
開発環境によって実行する前に clkwiz.sh
内の Vivado のパスを適切に編集する必要があります。
初回の記事で説明したように、異なる動作周波数のドメイン間のデータ転送のために、非同期 FIFO (Asynchronous FIFO)を利用します。
実行結果
fpga/
にある Vivado プロジェクトファイル (*.xpr ファイル) を開いて、回路の論理合成をしたら、fpga/dram_arty_a7.runs/impl1/
に Top.bit
という FPGA をプログラムするためのビットストリーム (bitstream) ファイルが生成されます。
次に、FPGA ボードとホスト PC がシリアル通信をするための USB ケーブルで接続された状態で、PC 上に TeraTerm や GTKTerm などのシリアルポートターミナルを起動して (rtl/common/serial.v
ファイルの SERIAL_WCNT
パラメータで) 設定した通信の速度 (baud rate) で FPGA ボードのシリアル通信のポートを開きます。
上記の Top.bit
ファイルを FPGA にプログラムすれば、下図の出力画面が表示されます。
ACRi ルームでの Vivado とシリアル通信の使用方法はここで確認できます。
RISC-V ソフトプロセッサへの応用
概要
RISC-V は最近開発された命令セットアーキテクチャ (ISA – Instruction Set Architecture) であり、非常に注目を集めています。
RISC-V はバイト (8ビット) 単位、ハーフワード (16ビット) 単位、ワード (32ビット) 単位のメモリアクセス命令を持ちます。特にプロセッサコアがオンチップ Block RAM (BRAM) でのキャッシュの機構を使わず直接に DRAM をアクセスする場合には様々な DRAM のアクセスパターンが発生しており、慎重に DRAM コントローラを設計する必要があります。
下記では、先端の RISC-V ソフトプロセッサを改良して、開発した DRAM コントローラの有用性を示します。Github のリポジトリのソースコードを参照しながら説明をします。
RVCoreP: ベースラインの RISC-V ソフトプロセッサ
RVCoreP は東工大の宮崎らによって開発されている FPGA 向けの5段パイプライン RISC-V ソフトプロセッサです。RVCoreP を説明する論文は以下のリンクからダウンロードできます。
RVCoreP は非常に速いですが、命令メモリ (Instruction Memory) とデータメモリ (Data Memory) の両方は FPGA オンチップ BRAM で実装されています。初回の記事でも説明しましたが、BRAM の容量が非常に限られています。例えば、Arty A7-35T ボードが搭載する FPGA は50個の 36Kb BRAM (トータルで 1,800Kb < 0.25MB) しか持ちません。そのため、RVCoreP は小さなメモリ容量を求めるアプリケーションのみを実行することができます。
下図は RVCoreP の概要を示しています。
実行の手順を説明します。まず、プログラムローダ (Program Loader) はシリアル通信経由でホスト PC から実行のプログラムを受け取って命令メモリとデータメモリを初期化します。初期化が終わったら、プロセッサコアの実行が始まります。プログラムの出力はシリアル通信で (Uart Tx モジュールによって) ホスト PC に送られます。そのために、RVCoreP はメモリマップドI/Oを利用しています。詳しい説明を省略しますが、RVCoreP の実装では、ロード命令で、ロードアドレスのビット15とビット30 (ビットのインデックスは0からスタートとします) が1だったら、通常のデータメモリのアクセスではなく、シリアルポートにデータを転送することを意味します。
データメモリを DRAM で実装
ここで、オフチップ DRAM を使用してデータメモリを実装することにより、RVCoreP の機能を強化します。そのために、DRAM コントローラ及びプロセッサーコアと DRAM コントローラを統合するためのロジックの追加のほか、プロセッサーコアを次のように変更しました。
- データメモリがアクセスされたときにアサート (1に設定) されるストール (stall) 信号を追加します。 BRAM へのアクセスには1クロックサイクルしかかからないため、元のプロセッサコアにはこの信号が要りません (パイプラインのストールが発生しないからです)。
- データメモリのリードイネーブル (read enable) 信号を追加します。 この信号が必要となる理由は、データメモリからデータをロードするかしないかによってメモリアクセスステージの長さが異なるためです (ロードする場合、メモリアクセスステージの長さは1クロックサイクルではなく複数のクロックサイクルです)。 元のプロセッサコアでは、データメモリがアクセスされるかどうかに関係なく、メモリアクセスステージの長さは常に1クロックサイクルなので、正しい動作にはライトイネーブル (write enable) 信号のみが必要です。
下図は DRAM ベースのデータメモリを持つ RVCoreP の全体の構成を示しています。
DRAM コントローラ、クロック生成部分は図1と同じです。プログラムの実行手順は元の RVCoreP (図3) と同様です。
ソースコードの構成
上記のテストプログラムを論理合成するために、Vivado の一般の GUI モードを使いましたが、ここで、batch mode (コマンドラインモード) を紹介します。リモートサーバで Vivado を使う場合は非常に便利です。
記事の最初で述べましたが、シミュレーションコードも提供していますが、詳しい説明は次回の記事にします。
Github リポジトリのソースコードの構成は次のようになります。
clk_wiz_1/
,common/
,dram/
: DRAM コントローラの実装sim/
,Makefile
,simsrc
: シミュレーション用のコードconfig.vh
はシステムのいくつかのパラメータを定義します (後述)data_memory.v
: DRAM を用いたデータメモリの実装proc.v
: プロセッサコアの実装uart.v
: プログラムローダとシリアル通信の実装main.v
: システムのトップモジュール(プロセッサコア、命令メモリ、データメモリ、プログラムローダ、Uart Tx、clk_wiz_1 を統合するためのモジュール)constraints_io.xdc
,constraints_timing.xdc
: FPGA の入出力ポート (I/O) 制約とタイミング制約。タイミング制約は論理合成の Implementation ステージのみに使われますvivado.sh
,vivado.tcl
,vivado_slurm.sh
: Vivado の batch mode を用いて論理合成をするためのスクリプトverification/
: 検証プログラム(検証プログラムの開発については、こちらをご参照ください)*.bin
実機の FPGA ボードでプログラムを実行するためのファイル*.mem
シミュレーションでプログラムを実行するためのファイル
config.vh で定義されている重要なパラメータ
MEM_FILE
: 検証プログラムへのパス (このパラメータはシミュレーションのみで使われています)MEM_SIZE
: 検証プログラムによって適切に設定する必要がありますverification/test/
での検証プログラム:MEM_SIZE
を 1024*4 (4KB) に設定する必要がありますverification/bench/
での検証プログラム:MEM_SIZE
を 1024*32 (32KB) に設定する必要がありますverification/embench/
での検証プログラム:MEM_SIZE
を 1024*64 (64KB) に設定する必要があります
SERIAL_WCNT
: シリアル通信の速度を調整するためのパラメータ(上記のテストプログラムと同様です)
論理合成
- Slurm workload mamanger がある開発環境:
./vivado_slurm.sh <#threads> <walltime(hour(s))> <vivado version (e.g., 20183, 20191, etc.)>
例えば、次のコマンド
./vivado_slurm.sh 8 3 20192
は8つの並列 CPU スレッド、最大3時間の Vivado 2019.2 の論理合成ジョブを作成します。
注意: vivado_slurm.sh
での Vivado のパスを開発環境に合わせる必要があります。
- Slurm workload manager がない開発環境:
./vivado.sh <#threads> <vivado version (e.g., 20183, 20191, etc.)>
例えば、次のコマンド
./vivado.sh 8 20192
は8つの並列 CPU スレッドを用いて Vivado 2019.2 の論理合成を行います。
注意: vivado_slurm.sh
スクリプトのように、Vivado のパスを開発環境に合わせる必要があります。
生成されたビットストリームファイル (main.bit
)、ハードウェア使用量、タイミングのレポートは vivado/
にあります。
プロセッサコア用のクロック信号の名前は user_design_clk
であり、constraints_timing.xdc
で定義されています。このクロックがタイミング制約 (現在 100MHz にしています) を満たしているかどうかは vivado/vivado.log
か、vivado/main_timing_routed.rpt
、vivado/main_timing_summary_routed.rpt
で確認できます。一般に、クロックのタイミング制約を満たさなければ、回路が正常に動きません。
検証プログラムの実行
まず、vivado/main.bit
で FPGA をプログラムします。次に、verification/
にある python スクリプト (serial_rvcorep.py
) を用いて検証プログラムを FPGA ボードに送ります。例えば、次のコマンド
python3 serial_rvcorep.py 1 test/test.bin
は test/test.bin
という検証プログラムを 1MegaBaud のシリアル通信の速度で FPGA ボードに送ります。実行結果は下図のようになります。
現在の設計では、連続の検証プログラムを実行することができません。別の検証プログラムを実行するために、FPGA を再度プログラムする必要があります。
結論
今回は、DRAM コントローラのテストプログラムと先端の RISC-V ソフトプロセッサへの応用を解説しました。リモートサーバの開発環境のための Vivado の便利な使い方 (batch mode) も紹介しました。
次回は、DRAM ベースの設計のためのシミュレーション方法を説明していきます。
東工大 Thiem Van Chu