この記事では、DRAM ベースの設計のためのシミュレーション方法を解説していきます。説明では、Synopsys 社のシミュレータ VCS を使用し、前回の記事で実機の FPGA ボードで動かしたテストプログラムと RISC-V ソフトプロセッサのシミュレーションを行います。
ソースコード:
- テストプログラム: https://github.com/thiemchu/dram-arty-a7
- RISC-V ソフトプロセッサ: https://github.com/thiemchu/rvcorep
概要
FPGA の設計において、シミュレーションは不可欠です。簡単な回路を設計する場合は、一発で実機で動かせることがありますが、ほとんどの場合、それは非常に難しいです。シミュレーションにより、設計された回路の動作検証やデバッグが簡単になります。例えば、シミュレーションで回路の実行ログを記録することにより、発生した問題を特定してトレースすることができます。
この記事では、MIG ベースの DRAM コントローラと DRAM の挙動をエミュレーションするモジュールを提供して、 DRAM ベースの設計のためのシミュレーション環境を実現します。
開発されたコードは、理論的にどの Verilog HDL シミュレータでも使えますが、今回はシミュレーション時間を考慮して Synopsys 社のシミュレータ VCS を選択しました。大学などの研究・教育機関に所属する方は、 d.lab-VDEC に申し込んで利用することができます。RISC-V ソフトプロセッサ RVCoreP の元のコード (DRAM なしの設計) は iverilog (Icarus Verilog) というオープンソースの Verilog HDL シミュレータを使いましたが、DRAM の使用を追加した後にシミュレーション速度が非常に遅くなりました。
テストプログラムのシミュレーション
テストプログラムのシミュレーション用のコードは rtl/sim/
ディレクトリにあります。
dram_emulator.v
: MIG ベースの DRAM コントローラと DRAM の挙動をエミュレーションするモジュールを定義しますtop_emulator.v
: テスト回路の挙動をエミュレーションするモジュールを定義します
DRAM のシミュレーション
先に述べたように、DRAM のシミュレーションのコードは dram_emulator.v
に記述されています。ここで定義されている DRAM モジュールのユーザインタフェースの入出力ポート (書き読み、ステータスのポート) は、実の DRAM コントローラのラッパ (rtl/dram/dram.v
) で定義されている DRAM モジュールのユーザインタフェースの入出力ポートと同じです。
MIG の出力クロック信号(前回の記事の図1の 83.33MHz クロック信号)は dram_emulator.v
の次のようにエミュレーションされています。
reg dram_clk = 0;
always #15 dram_clk = ~dram_clk;
シミュレーションなので、こちらのクロックの周期 (= 15 * 2 = 30) を自由に変更してもかまいません。
テスト回路のためのクロック信号 (前回の記事の図1の clk_wiz_0
の出力 100MHz クロック信号) は top_emulator.v
で次のように定義され、dram_emulator.v
に渡されています。
reg clk = 0;
always #60 clk = ~clk;
こちらの周期も自由に変更してもかまいません。
シミュレーション用の DRAM モジュールのパラメータは次のようになります。
APP_DATA_WIDTH
,APP_MASK_WIDTH
: 前回の記事まで説明してきた MIG の同名のパラメータと同じです。Arty A7-35T FPGA ボードの DRAM の場合は、APP_DATA_WIDTH = 128
,APP_MASK_WIDTH = 16
となります。DRAM_SIZE
: シミュレーションの DRAM のサイズ(バイト単位)です。DRAM_SIZE
はAPP_DATA_WIDTH/8
の倍数に設定する必要があります。開発環境によりますが、一般にDRAM_SIZE
を非常に大きいく設定することができません。DRAM_BURST_LENGTH
: バースト長(このシリーズの2回目の記事で説明されています)。実の DRAM コントローラのように、シミュレーションコードはバースト長=8のみをサポートしています。バーストモードの動作は2回目の記事の説明のように実装されています。APP_ADDR_WIDTH
:DRAM_SIZE
,DRAM_BURST_LENGTH
,APP_DATA_WIDTH
によって決められます。lang-verilog parameter APP_ADDR_WIDTH = $clog2((DRAM_SIZE * 8) / APP_DATA_WIDTH) + $clog2(DRAM_BURST_LENGTH) + 1;
上記の計算は MIG のAPP_ADDR_WIDTH
の意味に合わせるためです。
シミュレーションの DRAM の書き読みは dram_emulator.v
内の次のメモリの書き読みで実現されています。
reg [APP_DATA_WIDTH-1 : 0] mem[MEM_DEPTH-1 : 0];
ここで、MEM_DEPTH = (DRAM_SIZE * 8) / APP_DATA_WIDTH
です。DRAM の中身を初期化する必要がある場合は、Verilog の $readmemh
関数などを使用することが便利です。
現在の実装では、書き込みと読み出しのレイテンシはランダムで決めています。これらのレイテンシは dram_emulator.v
内のパラメータ RANDOM_RANGE
で調整できます。
テスト回路のシミュレーション
テスト回路のシミュレーションのコードは top_emulator.v
に記述され、クロック信号生成部分、データ出力部分、DRAM チップの入出力ポートの宣言以外は、元のコード (top.v
) と同じです。
書き込みデータと読み出しデータのサンプルをシリアル通信でホスト PC に送るのではなく、 Verilog のシミュレーション用の $write
関数でコンソールに出力します。
実行結果
- コンパイルのコマンド(
rtl/
ディレクトリで):make
コンパイルのために必要となるファイルのリストはsimsrc
ファイルで書かれています。 - 実行のコマンド(
rtl/
ディレクトリで):make run
- 実行結果:
RISC-V ソフトプロセッサのシミュレーション
上記のテストプログラムの回路は比較的に簡単なので、この記事の著者は、一発でシミュレーションを通して実機で動かすことができました。しかし、RISC-V ソフトプロセッサは複雑な回路なので、動作検証やデバッグの時間がかかりました。DRAM ベースの設計のためのシミュレーション環境がなければ、開発が非常に難しいと考えられます。
RISC-V ソフトプロセッサのシミュレーション用のコードは sim/
ディレクトリにあります。
dram_emulator.v
: テストプログラムのシミュレーションで使われたdram_emulator.v
ファイルと同じですdata_memory_emulator.v
:dram_emulator.v
で定義されたモジュールを用いて、データメモリをエミュレーションしますmain_emulator.v
: クロック信号生成部分と DRAM チップの入出力ポートの宣言以外は、元のmain.v
と同じですtop.v
: シミュレーションのトップモジュールを定義します。このファイルは RVCoreP の元のコードに基づいていて、検証プログラムをverification/test/
,verification/bench/
,verification/embench/
の*.mem
ファイルからロードして命令メモリとデータメモリを初期化するのと、プロセッサの性能を見積もるためのいくつかのカウンタの実装などを含みます。
シミュレーションの前に設定する必要があるパラメータ
前回の記事でも説明しましたが、シミュレーションの前に、config.vh
で次のパラメータを設定する必要があります。
MEM_FILE
: 検証プログラムへのパス(verification/test/
,verification/bench/
,verification/embench/
の*.mem
ファイル)MEM_SIZE
:verification/test/
での検証プログラム:MEM_SIZE
を 1024*4 (4KB) に設定する必要がありますverification/bench/
での検証プログラム:MEM_SIZE
を 1024*32 (32KB) に設定する必要がありますverification/embench/
での検証プログラム:MEM_SIZE
を 1024*64 (64KB) に設定する必要があります
他に、main_emulator.v
で DRAM_SIZE
(シミュレーションの DRAM のサイズ) を MEM_SIZE
以上に設定する必要があります。現在のコードで DRAM_SIZE
を 1024*64 にしていますので、verification/
内のどの検証プログラムにも対応できますが、より大きな検証プログラムを実行するために、 DRAM_SIZE
を増やさないといけません(検証プログラムの開発については、こちらをご参照ください)。
実行結果
- コンパイルのコマンド:
make
コンパイルのために必要となるファイルのリストはsimsrc
ファイルで書かれています。 - 実行のコマンド:
make run
- 実行結果:
これらの検証プログラム実行結果での実行サイクル数 (elapsed clock cycles) と IPC (Instructions Per Cycle) が元の RVCoreP の実行結果と異なる理由は、データメモリのアクセスに複数のサイクルを要する DRAM で実装したためです。テストプログラムの節で説明しましたが、dram_emulator.v
で定義されたパラメータ RANDOM_RANGE
を変更して DRAM のアクセスレイテンシを調整することができます。これにより、DRAM のアクセスレイテンシのプロセッサの性能への影響を解析することが可能となります。
verification/bench/
, verification/embench/
(特に verification/embench/
)内の検証プログラムのシミュレーションは非常に時間がかかります。例えば、この記事の著者の開発環境 (Core i9 9900K CPU, 64GB DDR4 メモリ) では、verification/embench/wikisort.mem
をシミュレーションするために30分以上かかりました。
結論
今回は、DRAM ベースの設計のためのシミュレーション方法を説明しました。
5回の記事のシリーズを通して、みなさんが DRAM の基本的な知識を深め、自身のプロジェクトのための DRAM コントローラを作れることを願っています。
東工大 Thiem Van Chu