高位合成おぼえがき

この記事は [Aizu Advent Calendar](https://adventar.org/calendars/2262)の6日目の記事です。
前記事は @stringamp さんの [畳み込み演算を用いた残響効果の理論と実装](https://qiita.com/stringamp/items/078e4f962e2073119b01?utm_campaign=email&utm_content=view_action&utm_medium=email&utm_source=public_mention)

# はじめに
この記事では高位合成に関する説明と、Vivado HLSとSystemCを使った高位合成での簡単な実装例を示します。 
前者に関しては寄せ集めの情報を自分なりにまとめたものになりますので、誤情報等を含んでいる可能性があります。
見つけた方はビシバシご指摘いただけると泣いて喜びます。

# 高位合成とはなんぞや
元来のデジタル回路は、Verilog,やVHDLに代表されるハードウェア記述言語(HDL)によってレジスタトランスファーレベル(RTL)で書かれるのが主でした。
![550px-Full-adder.svg.png](https://qiita-image-store.s3.amazonaws.com/0/108708/45770e6d-fc15-e866-3463-b5c6d8a0c1a7.png)
[RTLレベルでの加算器表現 Wikipedia - Full-adderより](https://commons.wikimedia.org/wiki/File:Full-adder.svg)

しかし、これらのHDLは記述の抽象度が低く、相対的に記述量が増えてしまうため、近年の半導体微細化技術による回路規模の増大に伴い、より抽象度が高く、少ない記述量で回路を表現可能な方法が模索されてきました。

これを解決せんとする方法が高位合成であり、
C言語等の高位言語からHDLを生成、もしくはゲートレベルのネットリストまでを生成して回路を設計までを行う技術です。

## 高位合成ツール
現状幾つもの企業、団体が高位合成用のツールを提供しています。
有名なものとしては、FPGAを対象としたEDAツールを作成しているXilinxのVivado HLSと現IntelのIntel HLS Compiler、そしてASICを対象としたEDAツールを作成しているCadenceのC-to-Sillicon Compiler、Mentor GraphicsのCatapult HLS Platformがあります。
以下に各ツールの比較を示します。

|          | Vivado HLS    | Intel HLS Compiler | C-to-Silicon Compiler | Catapult C       |
|:--------:|---------------|--------------------|-----------------------|------------------|
| 開発     | Xilinx        | Intel (Altera)     | Cadence               | Mentor Graphics  |
| 対応言語 | C/C++/SystemC | ANSI C/C++/OpenCL       | SystemC               | ANSI C/C++/SystemC |
| 価格     | 無償版あり    | 無償版あり         | 有償                  | 有償             |

<!--
### Xilinx - Vivado HLS
Xilinx社のEDAツールであるVivadoをベースとしたツール。
Eclipseをベースに採用しているため、そちらを使ったことのある人にとっては親しみやすいUIになっている。
### Intel(Altera) - Intel HLS Compiler

### Cadence - C to Sillicon Compiler
### Mentor Graphics - Catapult HLS Platform
-->
<!-- 
## 言語
### SystemC
C++のラッパライブラリ、DSL(Domain Specific Language)であり、
### NSL
NTTの開発したハードウェア記述言語であり
### Chisel 
ChiselはUCバークレーの開発した高位合成用コンパイラーで、ScalaのDSLからVerilogコードを生成することが出来る。
### MyHDL
-->

# 高位合成での実装例
次に高位合成での実装例として、画像処理に用いるアルゴリズムのCモデルとSystem Cモデル、Vivado HLSでの合成結果を示します。
## 対象アプリケーション
### Prewitt-filter
Prewitt-filterは画像処理に用いられるエッジ検出のアルゴリズムです。
![prewitt.png](https://qiita-image-store.s3.amazonaws.com/0/108708/0acb708e-cb5e-6385-03ec-60f415f84f2d.png)
詳しいアルゴリズムに関しては [物理のかぎしっぽ - Prewittフィルタでエッジ抽出する](http://hooktail.org/computer/index.php?Prewitt%A5%D5%A5%A3%A5%EB%A5%BF%A4%C7%A5%A8%A5%C3%A5%B8%C3%EA%BD%D0%A4%B9%A4%EB) を参照

5x5ピクセルのイメージを対象としたPrewitt-filterのCコードを以下に示します。


```c:prewitt-filter.c
void prewitt_filter(int *mem) {

    int i,j,k,l;
    int sumrw, sumrh;
    int mem_temp[HEIGHT*WIDTH];

    //
    for(i = 0; i < HEIGHT*WIDTH; i++) {
        mem_temp[i] = mem[i];
    }

    //prewitt filter
    for(i = 1; i < HEIGHT-1; i++) {
        for(j = 1; j < WIDTH-1; j++) {
            sumrw = sumrh = 0;
            for(k = -1; k <= 1; k++) {
                for(l = -1; l <= 1; l++) {
                    sumrw += l * mem_temp[(i+k)*WIDTH+j+l];
                    sumrh += k * mem_temp[(i+k)*WIDTH+j+l];
                }
            }
            mem[j+i*WIDTH] = mysqrt((sumrw * sumrw + sumrh * sumrh));
        }
    }
}

int mysqrt(unsigned int x){

  int a = 0, c = 0, y = 0, i = 0, t = x;
  while(t >>= 1){
    ++i;
  }
  for(i += i & 1; i >= 0; i -= 2){
    c = (y << 1 | 1) <= x >> i;
    a = a << 1 | c;
    y = y << 1 | c;
    x -= c * y << i;
    /* if(c){
      x -= y << i;
    } */
    y += c;
  }
  return a;
}

```
コード中、平方根の算出にsqrt関数を使っていないのは、一部高位合成ツールではmath.hが対応していないためです。(Vivado HLSは対応)

## 開発環境
Vivado Design Suite - HLx Edition 17.3
## SystemCコード
上記のCコードから作成したSystemCコードとして、prewitt-filterを示したのモジュール(prewitt-filter.cpp)、使用するポートを示したヘッダ(prewitt-filter.h)、テストベンチ(sc_main.cpp)をそれぞれ用意しました。

```C++:prewitt-filter.cpp
#include "prewitt.h"

#define HEIGHT 5
#define WIDTH 5

void prewitt_filter(int *mem);

int mysqrt(unsigned int x){
  int a = 0, c = 0, y = 0, i = 0, t = x;
  while(t >>= 1){
    ++i;
	wait();
  }
  for(i += i & 1; i >= 0; i -= 2){
    c = (y << 1 | 1) <= x >> i;
    a = a << 1 | c;
    y = y << 1 | c;
    x -= c * y << i;
    /* if(c){
      x -= y << i;
    } */
    y += c;
	wait();
  }
  return a;
}


void prewitt_filter(int *mem) {

	int i,j,k,l;
    int sumrw, sumrh;
    int mem_temp[HEIGHT*WIDTH];

	for(i = 0; i < HEIGHT*WIDTH; i++) {
        mem_temp[i] = mem[i];
		wait();
    }

    //prewitt filter
    for(i = 1; i < HEIGHT-1; i++) {
        for(j = 1; j < WIDTH-1; j++) {
            sumrw = sumrh = 0;
            for(k = -1; k <= 1; k++) {
                for(l = -1; l <= 1; l++) {
                    sumrw += l * mem_temp[(i+k)*WIDTH+j+l];
                    sumrh += k * mem_temp[(i+k)*WIDTH+j+l];
					wait();
                }
            }
            mem[j+i*WIDTH] = mysqrt((sumrw * sumrw + sumrh * sumrh));
			wait();
        }
    }
}

void prewitt::proc() {
    int i, j;
    int mem[25] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
	//int test_data[25];

	out_data.write(0);
	wait();

	while(true) {
		while(start.read() == false) wait();
		for(i=0;i<25;i++) {
			mem[i] = in_data.read();
			wait();
		}

		prewitt_filter(mem);
		for(i = 0; i< 25; i++) {
			out_data.write(mem[i]);
			wait();
		}
	}

}

```

```C++:prewitt-filter.h
#include <systemc.h>

SC_MODULE(prewitt) {
	sc_in_clk clock;
	sc_in<bool> reset;
	sc_in<bool> start;

	sc_in<int> in_data;
	sc_out<int> out_data;

	void proc();

	SC_CTOR(prewitt) {
		SC_CTHREAD(proc, clock.pos());
		reset_signal_is(reset, true);
	}

#ifdef NC_SYSTEMC
  public:
	void ncsc_replace_names() {
		ncsc_replace_name(clock, "clock");
		ncsc_replace_name(reset, "reset");
		ncsc_replace_name(start, "start");
		ncsc_replace_name(in_data, "in_data");
		ncsc_replace_name(out_data, "out_data");
	}
#endif
};

#ifdef __CTOS__
SC_MODULE_EXPORT(prewitt);
#endif

```

```C++:sd_main.cpp
#include <systemc.h>
#include "prewitt.h"

#ifdef CTOS_RTL
#include "prewitt_ctos_wrapper.h"
#endif

int sc_main(int argc, char** argv){

  sc_clock clock("clock", 37.6, SC_NS, 0.5, 18.8, SC_NS, true);

  sc_signal<bool> reset;
  sc_signal<bool> start;
  sc_signal<int> in_data;
  sc_signal<int> out_data;

  const int test_data[25] = {0,1,0,1,0,0,1,2,3,4,0,1,0,1,0,0,1,2,3,4,0,1,0,1,0};

#ifdef CTOS_RTL
  prewitt_ctos_wrapper i0 ("prewitt", "_rtl", "", true);
#else
  prewitt i0 ("prewitt");
#endif

  i0.clock(clock);
  i0.reset(reset);
  i0.start(start);
  i0.in_data(in_data);
  i0.out_data(out_data);

  sc_trace_file *trace_f = sc_create_vcd_trace_file("prewitt");
  trace_f->set_time_unit(1.0,SC_PS);
  sc_trace(trace_f,clock, "clock");
  sc_trace(trace_f,reset, "reset");
  sc_trace(trace_f,start, "start");
  sc_trace(trace_f,in_data, "in_data");
  sc_trace(trace_f,out_data, "out_data");

  sc_start(5, SC_NS);
  reset = false;
  start = false;

  sc_start(37.6, SC_NS);
  reset = true;
  sc_start(37.6, SC_NS);
  reset = false;
  sc_start(18.8, SC_NS);
  start = true;

  int i;
  for(i = 0; i < 25; i++){
    in_data = test_data[i];
    sc_start(37.6, SC_NS);
  }
  start = false;
  sc_start(10000, SC_NS);
  sc_close_vcd_trace_file(trace_f);
  return 0;
}
```
## 結果
シミュレータの波形より、Cプログラムと結果が正しいことを確認しました
![Payload Too Large](http://get.secret.jp/pt/file/1512562060.PNG)

# さいごに
高位合成自体はかなり前からあるのですが、実用レベルになってきたのは最近の話で、まだツールにしろ言語にしろまだ統一化がなされておらずどこから手を出せばいいか難しい状況です。現状、手っ取り早く使ってみたいのであれば、無償体験が出来てC/C++を扱えるVivado HLS、Intel HLS Compiler辺りが無難かと思います。
また、後半の実装例に関して、本当はデバイスへの実装までを行おうと思っていたのですが、研究室のサーバーメンテナンスに時間を取られてしまって断念しました。
論文提出数日前にデータを置いてるサーバーが起動しなくなるとか本当にあるんですね…
次書く機会があればそこまで対応したいです。

ここまで記事を読んでくださりありがとうございました。
アドベントカレンダー、次の方は@kyoto_cruiseさん、宜しくお願いします。

# 参照
FPGA開発日記 '高位合成言語Chiselイントロダクション(1)' -
 http://msyksphinz.hatenablog.com/entry/advent20161219
qiita 'AltHDLを雑に紹介する。' -
 https://qiita.com/tethys_seesaa/items/34c07f1a42e8bad14479
qiita 'SystemC高位合成のはじめ方' - https://qiita.com/kkumt93/items/2d8ab0170daee427f1c8