【Vivado HLS】平均計算を高位合成する(パイプライン化して連続で結果出力)

前回までは平均計算は行えていましたが、毎クロック計算結果を得ることは出来ていませんでした。

【Vivado HLS】平均計算を高位合成する(任意精度データ型ap_int/ap_fixed)

簡単なタイミングチャートを書いてみます。
old 平均計算 タイミングチャート

図を見ると、”(D1+D2)/2″や”(D3+D4)/2″が計算できていません。この処理形式では計算に必要なデータが全て入力されてから結果を出力するためこのような結果になっています。

そこで、今回は毎クロック計算結果を得るにはどうすればよいかを調べてみました。目標は、以下のようなタイミングチャートを得ることです。
new 平均計算 毎クロック結果を得る

今回は全体的に、以下ページを参考にさせて頂いています。
Vivado HLS 2014.4 で AXI4-Stream をテストする2(1次元のフィルタのテスト1、C++ソースコードの公開)

ソースコード/ディレクティブ

初めに今回のソースコードとディレクティブを記載します。

・ソースコード

#define N 4

#include "hls_stream.h"

void average(hls::stream<int>& data, hls::stream<int>& ave);
#include "average.h"
// 連続して平均計算結果を出力
void average(hls::stream<int>& data, hls::stream<int>& aveStream)
{
  static int temp[N]; // static指定により関数が終了しても初期化されない
  // シフトレジスタ記述
  average_label0:for (int i = 0; i < N-1; i++) {
    temp[i] = temp[i+1];
  }
  temp[N-1] = data.read();
  // 平均を計算して出力
  aveStream.write((temp[0] + temp[1] + temp[2] + temp[3]) / N);
}
#include <stdio.h>
#include <stdlib.h>
#include "average.h"

int main()
{
  // モジュールテスト用
  int testData;
  hls::stream<int> testStream;
  hls::stream<int> aveStream;
  // 比較値計算用
  int cmpShift[N] = {0};
  int cmpAve = 0;
  hls::stream<int> cmpAveStream;
  // 結果比較用
  int count = 0;
  int aveResult;
  int cmpAveResult;

  srand(10);

  for (int i = 0; i < 4*N; i++) {
    // テストデータ生成
    testData = (int)rand();                 // ランダムデータを生成
    testStream.write(testData);             // 入力用ストリームに格納
    printf("i=%d: test=%d\n", i, testData); // テストデータ標準出力
    // 4データの平均をとる
    for (int j = 0; j < N-1; j++) {
      cmpShift[j] = cmpShift[j+1];  // 古いデータをN-1個分保持しておく
    }
    cmpShift[N-1] = testData;       // 新しいデータを格納
    
    cmpAve = 0;
    for (int j = 0; j < N; j++) {
      cmpAve += cmpShift[j];        // N個の総和をとる
    }
    cmpAveStream.write(cmpAve / N); // 平均値を計算して比較値保持用ストリームに格納
  }

  // 4*N個のデータストリームが入力された想定でのシミュレーション
  for (int i = 0; i < 4*N; i++) {
    average(testStream, aveStream);
  }

  // 結果比較
  while (!aveStream.empty()) {          // 全出力結果を比較
    aveResult = aveStream.read();       // 計算結果を取得
    cmpAveResult = cmpAveStream.read(); // 比較値を取得
    // 取得結果を標準出力
    printf("i=%d: cmp=%d ave=%d\n", count, cmpAveResult, aveResult);
    // 結果が異なったらエラー
    if (aveResult != cmpAveResult) {
      printf("error:\n");
      return 1;
    }
    count++;
  }

  printf("Test End\n");

  return 0;
}

・ディレクティブ
Vivado HLS 高位合成 PIPELINE ARRAY_PARTITION UNROLL

新しい要素

PIPELINEディレクティブ

処理のパイプライン化のために指定します。これを指定することで、独立したタスクをパイプライン化するよう回路(具体的にはステートマシン)を組んでくれます(今回でいうと、入力データのラッチ、計算、結果出力の3処理)。今までは各処理を順次処理中にデータ入力が行われていたため、初めの問題が発生したわけです。

このパイプライン化、初めはイメージが湧かなかったのですが、以下スライド資料の説明(p52~)と、自分で高位合成して生成されたvhdファイルのステートマシン動作を確認して、何となく分かった気になりました。

Vivado hls勉強会2(レジスタの挿入とpipelineディレクティブ) - SlideShare

static修飾子

関数内部にてstatic修飾子がついた変数は、関数終了後にも値が保持されます。関数を何度も繰り返し、且つ前回の信号状態を保持しておきたいような場合には指定が必要そうです。

今回は4データの平均値を計算しますが、1回の関数呼び出しでは1データしか入力されません。そのため過去3回の入力データを保持する必要があるので、関数内部の配列"temp"に適用しています。

シフトレジスタ

今回の目的を達成する回路をVHDLで組むとしたら、シフトレジスタを用意して入力を順番にラッチ、必要なデータに対して平均計算を実施すると思います。簡易タイミングチャートを示します。

シフトレジスタ タイミングチャート

シフトレジスタをVivado HLSで実装する場合には作法があるらしく、それにのっとりました。前述の参考ページや、ページ中のXilinx資料(https://japan.xilinx.com/support/documentation/application_notes/j_xapp793-memory-structures-video-vivado-hls.pdf)を参考にしています。

次の2つはシフトレジスタ実装の作法中に現れるディレクティブです。

ARRAY_PARTITIONディレクティブ

1つの配列を複数の要素に分割します。今回は配列をシフトレジスタとして使用するため、関数内部の配列"temp"に適用し、4つのレジスタに分割しています。

UNROLLディレクティブ

for文を空間展開します。今回はfor文にて行われているシフトの代入処理を並列化したいため、完全に展開します。

シミュレーション波形

レイテンシはありますが、目的の波形が得られました。

Vivado HLS 高位合成 PIPELINE UNROLL

シェアする

  • このエントリーをはてなブックマークに追加

フォローする