高位合成でどこまで攻められるか?

最近、画像関連のフィルター処理系のRTLを書きつつ考えているのが、高位合成でどこまで攻めることができるのか?

攻めるとは、高位合成を使ってどこまで自分が考えるRTLにまで近づけることができるか?である。

OpenCVは高位合成できるかね?

例えば、OpenCVで次のようにラプラシアンフィルターを書いたとする。

  cv::Laplacian(src, dst1, src.depth(), kernel_size, scale, delta);

OpenCVいいよね。 原理知らなくても、こう書けば一発でエッヂを表示する画像が取得できる。 画像処理のアルゴリズムを考えるのにはOpenCVを使うとすごく便利。

ちなみに有名なレナ嬢でやってみた。

結果は次のようになる。

もし、素直にOpenCVを高位合成したらどうなるんだろう?

動作に必要なものは全て高位合成するとしたら・・・。もちろん、libopencv_xxxxなどのライブラリも動作必要な部分は高位剛性対照とする。

今のところ、たぶん、高位合成するという解は間違いだと思っている。

将来、デバイス性能や高位合成の処理性能が上がったらそうでも無いかもしれないけど・・・。

フィルター処理系の原理は?

OpenCVそのままでは高位合成できなさそうだから、原理を理解してOpenCVの関数と同じになるようにしてみよう。

OpenCVで書いたコードは3x3のフィルターを使用しているのでコードは次のように書いた。

  /*
    計算によるラプラシアン・フィルター
   */
  int x, y;
  short data;

  for( y = 0; y < src.size().height; y++ ){
    for( x = 0; x < src.size().width; x++ ){
      if(
         ( x > 0 ) && ( x < src.size().width -1 ) &&
         ( y > 1 ) && ( y < src.size().height -1 )
         ){
        data =
          ( src.data[ ( y - 1 ) * src.size().width + ( x - 1 ) ] * ( 0) ) +
          ( src.data[ ( y - 1 ) * src.size().width + ( x     ) ] * ( 1) ) +
          ( src.data[ ( y - 1 ) * src.size().width + ( x + 1 ) ] * ( 0) ) +
          ( src.data[ ( y     ) * src.size().width + ( x - 1 ) ] * ( 1) ) +
          ( src.data[ ( y     ) * src.size().width + ( x     ) ] * (-4) ) +
          ( src.data[ ( y     ) * src.size().width + ( x + 1 ) ] * ( 1) ) +
          ( src.data[ ( y + 1 ) * src.size().width + ( x - 1 ) ] * ( 0) ) +
          ( src.data[ ( y + 1 ) * src.size().width + ( x     ) ] * ( 1) ) +
          ( src.data[ ( y + 1 ) * src.size().width + ( x + 1 ) ] * ( 0) );
        if( data > 255 ) data = 255;
        if( data < 0 ) data = 0;

        dst2.data[ y * src.size().width + x ] = data;
      }
    }
  }

ちなみにこれも結果を表示してみると次のようになる。

OpenCVの関数と結果は同じだね。

この原理をどう書けば高位合成でパイプライン回路になるか?

ここからが本題です。

ベタ書きしたラプラシアンフィルターであれば、高位合成することは可能だ。 ただ、この状態で高位合成しても単にメモリからデータを取得して、ステートマシンをぐるぐる回す回路が生成されるだろう。 少なからず、synverllならそうなってしまう。

このフィルター処理系であれば、横計算をパイプラインで行って、いったん、RAMへ格納してから縦計算をすると完全なパイプライン処理で行えてclock per pixelで処理することができる。 これぐらいのパイプラインのRTLで書いてもそんなに難しくないし、工数もそんなにかからない。

ここまでできる高位合成があれば、たぶん、Verilog HDLやVHDLは捨てるかな?

じゃぁ、こういうパイプラインにするためにシーケンシャル処理な言語(CやC++とか)で表現するにはどうすればいいだろうか?

考えてみたけど、一言で・・・

無理!

それらしい、構成を書くことはできるんだけど、それを高位合成しようとすると必ずループとメモリの矛盾が発生してパイプラインと判断できなくなって、結局、ステートマシンになってしまう。

なんか、高位合成を含めハードウェアのことを考えながら、ソースコードを書いていこうとすると、かえって工数がかかるような気がする。

一長一短だが、高位合成は時と場合によって使うか使わないか考えたほうが良い。

たぶん、これを考えるのが一番難しいかな?

実験に用いたソースコード

せっかくなので実験に用いたソースコードを載せる。

これは次のバージョンで確認した。

項目 バージョン
環境 Ubuntu 16.04 LTS Beta
OpenCV 3.1.0
コンパイラ gcc 5.3.1
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/features2d.hpp>
#include <opencv2/imgproc.hpp>

#define SHOW 1

int main( int argc, char** argv )
{
  cv::Mat src, dst1;

  src = cv::imread( argv[1], cv::IMREAD_GRAYSCALE );
#if SHOW
  cv::namedWindow( "Source", CV_WINDOW_AUTOSIZE | CV_WINDOW_FREERATIO );
  cv::imshow( "Source", src );
#endif

  int kernel_size = 1;
  int scale = 1;
  int delta = 0;

  /*
    本ラプラシアン・フィルターは下記のフィルターを行う

    [ 0  1  0 ]
    [ 1 -4  1 ]
    [ 0  1  0 ]
   */

  /*
    OpenCVでのラプラシアン・フィルター
   */
  cv::Laplacian(src, dst1, src.depth(), kernel_size, scale, delta);
  cv::Mat dst2 = src.clone();

#if SHOW
  cv::namedWindow( "Laplacian1", CV_WINDOW_AUTOSIZE | CV_WINDOW_FREERATIO );
  cv::imshow( "Laplacian1", dst1 );
#endif
  cv::imwrite("00_laplacian01.png", dst1);

  /*
    計算によるラプラシアン・フィルター
   */
  int x, y;
  short data;

  for( y = 0; y < src.size().height; y++ ){
    for( x = 0; x < src.size().width; x++ ){
      if(
         ( x > 0 ) && ( x < src.size().width -1 ) &&
         ( y > 1 ) && ( y < src.size().height -1 )
         ){
        data =
          ( src.data[ ( y - 1 ) * src.size().width + ( x - 1 ) ] * ( 0) ) +
          ( src.data[ ( y - 1 ) * src.size().width + ( x     ) ] * ( 1) ) +
          ( src.data[ ( y - 1 ) * src.size().width + ( x + 1 ) ] * ( 0) ) +
          ( src.data[ ( y     ) * src.size().width + ( x - 1 ) ] * ( 1) ) +
          ( src.data[ ( y     ) * src.size().width + ( x     ) ] * (-4) ) +
          ( src.data[ ( y     ) * src.size().width + ( x + 1 ) ] * ( 1) ) +
          ( src.data[ ( y + 1 ) * src.size().width + ( x - 1 ) ] * ( 0) ) +
          ( src.data[ ( y + 1 ) * src.size().width + ( x     ) ] * ( 1) ) +
          ( src.data[ ( y + 1 ) * src.size().width + ( x + 1 ) ] * ( 0) );
        if( data > 255 ) data = 255;
        if( data < 0 ) data = 0;

        dst2.data[ y * src.size().width + x ] = data;
      }
    }
  }

#if SHOW
  cv::namedWindow( "Laplacian2", CV_WINDOW_AUTOSIZE | CV_WINDOW_FREERATIO );
  cv::imshow( "Laplacian2", dst2 );
#endif
  cv::imwrite("01_laplacian02.png", dst2);

  /*
    OpenCVのラプラシアン・フィルターと計算上のラプラシアン・フィルターの
    差分チェック

    一致しなければ黒で描写、一致した場合は白で描写
   */
  cv::Mat dst3 = src.clone();

  for( y = 0; y < src.size().height; y++ ){
    for( x = 0; x < src.size().width; x++ ){
      if(
         ( x > 0 ) && ( x < src.size().width -1 ) &&
         ( y > 1 ) && ( y < src.size().height -1 )
         ){
        if( dst1.data[ y * src.size().width + x ] != dst2.data[ y * src.size().width + x ] ){
          dst3.data[ y * src.size().width + x ] = 0;
        }else{
          dst3.data[ y * src.size().width + x ] = 255;
        }
      }
    }
  }

#if SHOW
  cv::namedWindow( "Match", CV_WINDOW_AUTOSIZE | CV_WINDOW_FREERATIO );
  cv::imshow( "Match", dst3 );
#endif
  cv::imwrite("02_match01.png", dst1);

#if 0
  /*
    データの表示
   */
  printf("Mat: size: %d x %d\n", src.size().width, src.size().height);

  int x, y;
  for( y = 0; y < src.size().height; y++ ){
    for( x = 0; x < src.size().width; x++ ){
      printf("%02x ", src.data[y*src.size().width + x]);
      if( src.size().width == x + 1){
        printf("\n");
      }
    }
  }
#endif

#if SHOW
  cv::waitKey(0);
#endif

  return 0;
}

コンパイル方法は次のようにする。

% g++ -o sample01 sample01.cpp -I/usr/local/include -L/usr/local/lib -lopencv_core -lopencv_imgcodecs -lopencv_features2d -lopencv_highgui -lopencv_imgproc

使い方は次のようにする。

% ./sample01 lena.png
write: 2016/04/14/ 23:00:00