C シミュレーションを使用したコードの検証

Vitis HLS フローでの検証には、大きく分けて 2 つのプロセスが含まれます。

  • C プログラムが正しく必要な機能をインプリメントするかどうかを確認する合成前の検証。
  • 生成後の RTL コードが問題なく動作するかどうかを確認する合成後の検証。

どちらのプロセスもシミュレーション (「C シミュレーション」および「C/RTL 協調シミュレーション」) と呼ばれます。

合成前に、合成する関数を C シミュレーションを使用したテストベンチで検証する必要があります。C テストベンチには、Vitis HLS プロジェクトで合成される関数を呼び出す最上位関数 main() が含まれます。テストベンチには、その他の関数を含めることもできます。理想的なテストベンチには、次のような特徴があります。

  • テストベンチにはセルフチェック機能があり、合成される関数からの結果が正しいかどうかを検証する。
  • 結果が正しい場合はテストベンチから main() に値 0 を返し、正しくない場合は 0 以外の値を返す。

Vitis HLS の GUI で Run C Simulation ツールバー ボタン をクリックすると、次の図に示す C Simulation Dialog ダイアログ ボックスが開きます。

1: C Simulation Dialog ダイアログ ボックス

C Simulation Dialog ダイアログ ボックスには、次のオプションが含まれます。

Launch Debugger
C コードをコンパイルし、[Debug] パースペクティブを開きます。[Debug] パースペクティブから左上の [Synthesis] パースペクティブ ボタンをクリックすると、合成表示に戻ります。
Build Only
ソース コードとテストベンチをコンパイルしますが、シミュレーションは実行しません。このオプションは、コンパイル プロセスをテストして、シミュレーション前にビルドに関する問題を解決するために使用します。コマンド シェルからシミュレーションを実行可能な csim.exe ファイルが生成されます。
Clean Build
コードをコンパイルする前に既存の実行ファイルおよびオブジェクト ファイルをプロジェクトから削除します。
Optimizing Compile
デフォルトではデザインはデバッグ情報をイネーブルにした状態でコンパイルされ、そのコンパイルを解析およびデバッグできるようになっています。[Optimizing Compile] オプションを使用すると、デザインをコンパイルする際により高いレベルの最適化エフォートが使用されますが、デバッガーで必要とされる情報は追加されません。これにより、コンパイル時間は増加する可能性がありますが、シミュレーション実行時間は削減されます。
ヒント: Launch DebuggerOptimizing Compile オプションを同時に使用することはできません。C Simulation Dialog ダイアログ ボックスでいずれかをオンにすると、もう一方がオフになります。
Enable Pre-Synthesis Control Flow Viewer
[Pre-Synthesis Control Flow] ビュー に説明する合成前の制御フロー レポートを生成します。
Input Arguments
テストベンチの main() 関数で必要となる入力を指定します。
[Do not show this dialog box again]
C Simulation Dialog ダイアログ ボックスが表示されなくなります。
ヒント: ProjectProject Settings をクリックし、[Simulation settings] を選択すると、C Simulation Dialog ダイアログ ボックスを再び表示されるようになります。
ダイアログ ボックスで OK をクリックすると、C コードがコンパイルされて、C シミュレーションが実行されます。シミュレーションの実行中、コンソールにテストベンチからの printf 文が表示されます。シミュレーションが問題なく完了すると、コンソールに次のメッセージが表示されます。
INFO: [SIM 211-1] CSim done with 0 errors.
INFO: [SIM 211-3] *************** CSIM finish ***************
Finished C simulation.
シミュレーションが正常に実行されなかった場合は、エラーが返されます。
@E Simulation failed: Function 'main' returns nonzero value '1'.
ERROR: [SIM 211-100] 'csim_design' failed: nonzero return value.
INFO: [SIM 211-3] *************** CSIM finish ***************

Launch Debugger オプションを選択すると、自動的に [Debug] パースペクティブに切り替わり、次の図に示すようなデバッグ環境が開きます。シミュレーションが開始し、コードをステップ実行して関数を確認およびデバッグできます。これは完全なデバッグ環境で、コードをステップインおよびステップオーバーしたり、ブレークポイントを指定したり、コードで変数の値を設定したりできます。

2: C デバッグ環境
ヒント: Synthesis パースペクティブ ボタンをクリックすると、標準の合成ウィンドウに戻ります。

テストベンチの記述

Vitis HLS デザイン フローを使用する場合、適切に記述されていない C 関数を合成して、インプリメンテーションの詳細を解析して関数が正しく機能しない原因をつきとめるのは時間の無駄です。そのため、高位合成の最初の手順は、適切に記述されたテストベンチを使用してシミュレーションを実行し、RTL コードを生成する前に C 関数が正しいことを検証することです。適切なテストベンチを記述すると、C 関数を RTL シミュレーションよりも短時間で実行できるので、生産性が上がります。C を使用してアルゴリズムを開発して合成前に検証する方が、RTL コードを開発してデバッグするよりもはるかに高速です。

Vitis HLS ではこのテストベンチを使用して、C シミュレーションがコンパイルされて実行されます。コンパイルする際に Launch Debugger オプションをオンにすると、C デバッグ環境を開いて、C シミュレーションを詳細に解析できます。Vitis HLS では、Vitis HLS での C/RTL 協調シミュレーション に説明されているように、このテストベンチを使用して合成の RTL 出力が検証されます。

テストベンチには、main() 関数と、必要なサブ関数が含まれます。これらのサブ関数は、Vitis HLS での合成に指定されている最上位関数に含まれません。main 関数は、スティミュラスを供給して合成用の関数を呼び出し、その出力を消費して検証することにより、合成用の最上位関数が正しく機能するかどうかを検証します。

重要: C シミュレーションを使用したコードの検証 に説明されているように、C シミュレーションを起動するときにテストベンチに入力引数を指定できますが、シミュレーション中にテストベンチにユーザーがインタラクティブに入力することはできません。Vitis HLS の GUI にはコマンド コンソールがないので、テストベンチ実行中にユーザー入力を受信することはできません。

次に、セルフチェック テストベンチの重要な機能を示すコード例を示します。

int main () { 
  //Esablish an initial return value. 0 = success
  int ret=0;

  // Call any preliminary functions required to prepare input for the test.
  …
  …// Call the top-level function multiple times, passing input stimuli as needed.
  for(i=0; i<NUM_TRANS; i++){
     top_func(input, output);
  }

  // Capture the output results of the function, write to a file
  …
  // Compare the results of the function against expected results
  ret = system("diff --brief  -w output.dat output.golden.dat");
  
  if (ret != 0) {
        printf("Test failed  !!!\n"); 
        ret=1;
  } else {
        printf("Test passed !\n"); 
  }
  …
  return ret;
}

テストベンチは最上位関数を複数のトランザクションで実行し、さまざまなデータ値を適用して検証する必要があります。テストベンチは、実行されるさまざまなテストでのみ有効です。また、Vitis HLS での C/RTL 協調シミュレーション に説明されているように、RTL シミュレーション中に II を計算する場合は、テストベンチで複数のトランザクションを供給する必要があります。

このセルフチェック テストベンチでは、関数の結果 output.datoutput.golden.dat の既知の良い結果と比較されます。これは、セルフチェック テストベンチの 1 例です。最上位関数を検証する方法は多数あるので、コードに適切なテストベンチを記述する必要があります。

Vitis HLS デザイン フローでは、main() 関数の戻り値は次のとおりです。

  • 0: 結果が正しいことを示します。
  • 0 以外: 結果が正しくないことを示します。

テストベンチから 0 以外の値が返されることがあります。複雑なテストベンチには、エラーのタイプによって異なる値を返すものもあります。C シミュレーションまたは C/RTL 協調シミュレーション後にテストベンチから 0 以外の値が返されると、Vitis HLS でエラーまたはシミュレーション エラーがレポートされます。

ヒント: main() 関数の戻り値はシステム環境により解釈されるので、移植性と安全性のため、戻り値を 8 ビットの範囲に制約することをお勧めします。

シミュレーションの結果の質は、使用するテストベンチによります。テストベンチが正しい結果を返すことを確認するのはユーザーの責任です。テストベンチで 0 が返されると、シミュレーションで何が発生していたとしても、Vitis HLS ではシミュレーションで問題は検出されなかったと判断されます。

テストベンチ例

ザイリンクスでは、合成用の最上位関数はテストベンチとは分けて記述し、ヘッダー ファイルを使用する方法をお勧めします。次に、HLS プロジェクトの最上位関数 hier_func が次の 2 つのサブ関数を呼び出すデザインのコード例を示します。

  • sumsub_func: 加算および減算を実行。
  • shift_func: シフトを実行。

データ型はヘッダー ファイル ファイル (hier_func.h) で定義されています。関数のコードは次のとおりです。

#include "hier_func.h"

int sumsub_func(din_t *in1, din_t *in2, dint_t *outSum, dint_t *outSub)
{
 *outSum = *in1 + *in2;
 *outSub = *in1 - *in2;
}

int shift_func(dint_t *in1, dint_t *in2, dout_t *outA, dout_t *outB)
{
 *outA = *in1 >> 1;
 *outB = *in2 >> 2;
}

void hier_func(din_t A, din_t B, dout_t *C, dout_t *D)
{
 dint_t apb, amb;

 sumsub_func(&A,&B,&apb,&amb);
 shift_func(&apb,&amb,C,D);
}

最上位関数には複数のサブ関数を含めることができますが、合成できるのは 1 つの最上位関数のみです。複数の関数を合成するには、それらをサブ関数として 1 つの最上位関数にまとめます。

次に示すヘッダー ファイル (hier_func.h) に、マクロの使用方法と、typedef 文を使用することでコードの移植性および読みやすさを向上できることを示します。

ヒント: 任意精度 (AP) 型 には、typedef 文で任意精度型を使用できること、それにより最終的な FPGA インプリメンテーションでエリアとパフォーマンスの両方を向上するために変数のビット幅を調整できることが説明されています。
#ifndef _HIER_FUNC_H_
#define _HIER_FUNC_H_

#include <stdio.h>

#define NUM_TRANS 40

typedef int din_t;
typedef int dint_t;
typedef int dout_t;

void hier_func(din_t A, din_t B, dout_t *C, dout_t *D);

#endif

上記のヘッダー ファイルには、NUM_TRANS などの #define 文が含まれています。これは hier_func 関数には必要ありませんが、同じヘッダー ファイルを含むテストベンチ用に含まれています。

次に、hier_func デザインのテストベンチを定義するコードを示します。

#include "hier_func.h"

int main() {
 // Data storage
 int a[NUM_TRANS], b[NUM_TRANS];
 int c_expected[NUM_TRANS], d_expected[NUM_TRANS];
 int c[NUM_TRANS], d[NUM_TRANS];

  //Function data (to/from function)
 int a_actual, b_actual;
 int c_actual, d_actual;

  // Misc
 int retval=0, i, i_trans, tmp;
 FILE *fp;

 // Load input data from files
 fp=fopen(tb_data/inA.dat,r);
 for (i=0; i<NUM_TRANS; i++){
 fscanf(fp, %d, &tmp);
 a[i] = tmp;
 } 
 fclose(fp);

 fp=fopen(tb_data/inB.dat,r);
 for (i=0; i<NUM_TRANS; i++){
 fscanf(fp, %d, &tmp);
 b[i] = tmp;
 } 
 fclose(fp);

 // Execute the function multiple times (multiple transactions)
 for(i_trans=0; i_trans<NUM_TRANS-1; i_trans++){

 //Apply next data values
 a_actual = a[i_trans];
 b_actual = b[i_trans];

  hier_func(a_actual, b_actual, &c_actual, &d_actual);

 //Store outputs
 c[i_trans] = c_actual;
 d[i_trans] = d_actual;
 }

 // Load expected output data from files
 fp=fopen(tb_data/outC.golden.dat,r);
 for (i=0; i<NUM_TRANS; i++){
 fscanf(fp, %d, &tmp);
 c_expected[i] = tmp;
 } 
 fclose(fp);

 fp=fopen(tb_data/outD.golden.dat,r);
 for (i=0; i<NUM_TRANS; i++){
 fscanf(fp, %d, &tmp);
 d_expected[i] = tmp;
 } 
 fclose(fp);

 // Check outputs against expected
 for (i = 0; i < NUM_TRANS-1; ++i) {
 if(c[i] != c_expected[i]){
 retval = 1;
 }
 if(d[i] != d_expected[i]){
 retval = 1;
 }
 }

 // Print Results
 if(retval == 0){
 printf(    *** *** *** *** \n); 
 printf(    Results are good \n); 
 printf(    *** *** *** *** \n); 
 } else {
 printf(    *** *** *** *** \n); 
 printf(    Mismatch: retval=%d \n, retval); 
 printf(    *** *** *** *** \n); 
 }

 // Return 0 if outputs are corre
 return retval;
}

デザイン ファイルとテストベンチ ファイル

Vitis HLS では RTL 検証に C テストベンチが再利用されるので、テストベンチおよび関連ファイルを Vitis HLS に追加するときにテストベンチとして指定する必要があります。テストベンチに関連付けられたファイルは、次のようなファイルです。

  • テストベンチによりアクセスされるファイル。
  • テストベンチが正しく動作するために必要なファイル。

テストベンチ例に示す inA.dat および inB.dat などが、このようなファイルの例です。これらのファイルは、Vitis HLS プロジェクトにテストベンチ ファイルとして追加する必要があります。

Vitis HLS プロジェクトでテストベンチを指定するために、デザインとテストベンチを別のファイルに分ける必要はありませんが、推奨はされます。これを、テストベンチ例 と同じコードで新しい最上位関数を定義した新しい例で示します。この例では、関数 sumsub_funcVitis HLS プロジェクトで最上位関数として定義されています。

ヒント: 最上位関数は、ProjectProject Settings をクリックして Synthesis を選択し、Top Function で新しい最上位関数を指定すると変更できます。

sumsub_func 関数を最上位関数として定義すると、sumsub_func を呼び出すその上の関数 hier_func がテストベンチに含まれます。同じレベルの shift_func 関数もテストに必要な部分なので、テストベンチに含まれます。これらの関数は最上位関数 sumsub_func と同じコード ファイルに含まれますが、テストベンチの一部となります。

1 つのファイルにテストベンチとデザインを結合

デザイン ファイルとテストベンチを 1 つのデザイン ファイルに含めることもできます。次の例は、テストベンチ例 と同じ hier_func 関数ですが、テストベンチの最上位関数、サブ関数、main 関数などすべてが 1 つのファイルにまとめられている点が異なります。

重要: テストベンチとデザインが 1 つのファイルになっている場合は、そのファイルをデザイン ファイルとテストベンチ ファイルの両方として Vitis HLS プロジェクトに追加する必要があります。
#include <stdio.h>

#define NUM_TRANS 40

typedef int din_t;
typedef int dint_t;
typedef int dout_t;

int sumsub_func(din_t *in1, din_t *in2, dint_t *outSum, dint_t *outSub)
{
 *outSum = *in1 + *in2;
 *outSub = *in1 - *in2;
}

int shift_func(dint_t *in1, dint_t *in2, dout_t *outA, dout_t *outB)
{
 *outA = *in1 >> 1;
 *outB = *in2 >> 2;
}

void hier_func(din_t A, din_t B, dout_t *C, dout_t *D)
{
 dint_t apb, amb;

 sumsub_func(&A,&B,&apb,&amb);
 shift_func(&apb,&amb,C,D);
}

int main() {
 // Data storage
 int a[NUM_TRANS], b[NUM_TRANS];
 int c_expected[NUM_TRANS], d_expected[NUM_TRANS];
 int c[NUM_TRANS], d[NUM_TRANS];

 //Function data (to/from function)
 int a_actual, b_actual;
 int c_actual, d_actual;

 // Misc
 int retval=0, i, i_trans, tmp;
 FILE *fp;
 // Load input data from files
 fp=fopen(tb_data/inA.dat,r);
 for (i=0; i<NUM_TRANS; i++){
 fscanf(fp, %d, &tmp);
 a[i] = tmp;
 } 
 fclose(fp);

 fp=fopen(tb_data/inB.dat,r);
 for (i=0; i<NUM_TRANS; i++){
 fscanf(fp, %d, &tmp);
 b[i] = tmp;
 } 
 fclose(fp);

// Execute the function multiple times (multiple transactions)
for(i_trans=0; i_trans<NUM_TRANS-1; i_trans++){

 //Apply next data values
 a_actual = a[i_trans];
 b_actual = b[i_trans];

 hier_func(a_actual, b_actual, &c_actual, &d_actual);
    
 //Store outputs
 c[i_trans] = c_actual;
 d[i_trans] = d_actual;
 }

 // Load expected output data from files
 fp=fopen(tb_data/outC.golden.dat,r);
 for (i=0; i<NUM_TRANS; i++){
 fscanf(fp, %d, &tmp);
 c_expected[i] = tmp;
 } 
 fclose(fp);

 fp=fopen(tb_data/outD.golden.dat,r);
 for (i=0; i<NUM_TRANS; i++){
 fscanf(fp, %d, &tmp);
 d_expected[i] = tmp;
 } 
 fclose(fp);

 // Check outputs against expected
 for (i = 0; i < NUM_TRANS-1; ++i) {
 if(c[i] != c_expected[i]){
 retval = 1;
 }
 if(d[i] != d_expected[i]){
 retval = 1;
 }
 }

 // Print Results
 if(retval == 0){
 printf(    *** *** *** *** \n); 
 printf(    Results are good \n); 
 printf(    *** *** *** *** \n); 
 } else {
 printf(    *** *** *** *** \n); 
 printf(    Mismatch: retval=%d \n, retval); 
 printf(    *** *** *** *** \n); 
 }

 // Return 0 if outputs are correct
 return retval;
}

[Debug] パースペクティブの使用

変数と式の値は、[Debug] パースペクティブで直接確認できます。次の図に、各変数の値の確認方法を示します。[Variables] ビューでは、変数の値を変更して、たとえばその変数を特定のステートに指定したりできます。

3: 変数の確認


式の値は、Expressions ビューを使用して確認できます。

4: 演算式の確認


C シミュレーションの出力

C シミュレーションが終了すると、solution フォルダー内に csim フォルダーが作成されます。このフォルダーには、次のものが含まれます。
  • csim/build: C シミュレーションに関連するすべてのファイルが含まれます。
    • テストベンチで読み込まれるすべてのファイルがこのフォルダーにコピーされます。
    • C 実行ファイル csim.exe はこのフォルダーで作成および実行されます。
    • テストベンチにより出力されるすべてのファイルがこのフォルダーに作成されます。
    • csim/obj: コンパイル済みソース コードのオブジェクト ファイル (.o) およびソース コード ビルドの make 依存ファイル (.d) が含まれます。
  • csim/report: C シミュレーションのビルドおよび実行のログ ファイルが含まれます。

[Pre-Synthesis Control Flow] ビュー

[Run C Simulation] ダイアログ ボックスから、オプションで合成前の制御フロー グラフ (CFG) を生成できます。ダイアログ ボックスで Enable Pre-Synthesis Control Flow Viewer チェック ボックスをオンにしてレポートを生成します。生成できたら、次の手順でレポートを開きます。

Pre-Synthesis Control Flow ビューは、関数内のホット スポット (演算負荷の高い制御構造) を見つけたり、結果を改善または最適化するためにプラグマまたは指示子を適用するのに便利です。CFG には、次の図に示すように、C コードの制御フローが表示され、最上位関数を視覚化できます。CFG には、ループのトリップカウントなどのスタティック プロファイリングと、デザインを解析するためのダイナミック プロファイリングも含まれます。

5: Pre-Synthesis Control Flow ビュー

上の図に示すように、Pre-Synthesis Control Flow ビューには複数の要素が含まれます。

  • 左上の関数呼び出しツリー。
  • 中央の制御フロー グラフ (CFG)。
  • 右上のソース コード ビューアー。
  • 下部のコンソール エリアの [Loop] ビュー (CFG ビューアーとクロスプローブ)。

1 つのビューでサブ関数またはループを選択すると、その他のビューでもそれが選択されます。これで、コードの階層をすばやくナビゲートできます。関数呼び出しを展開すると、ループおよび条件文の制御構造を確認できます。制御構造をクリックして、ソース コードを表示します。

関数ツリーで関数をダブルクリックすると、CFG のコード階層のそのレベルが開き、1 回クリックすると、単にその関数が選択されます。Expand function call コマンドを使用して CFG を展開すると、指定した階層の関数レベルを表示できます。

関数呼び出しツリーの Search フィールドに入力して、入力したテキストに一致する結果をハイライトすることもできます。これを使用すると、コード内をすばやくナビゲートできます。

CFG を使用すると、さまざまな制御パスで実行される関数またはサブ関数の回数がレポートされるので、関数のダイナミック動作を解析しやすくなります。ループは計算量が多くなる原因となることがよくあります。[Loops] ビューには、アクセス時間、ループ反復数 (トリップカウント) の合計および平均などの統計が表示されます。ループに関連するこの情報は、[Loop] ビューからクロスプローブできます。ループをクリックすると、ソース コードおよび制御構造の両方がハイライトされます。

メモリ操作も CFG ビューアーにアノテートでき、パフォーマンスを最適化できる可能性がある別のエリアを特定できます。