Handel-Cについて

※これはVisionグループOBのfukudaさんのページから持ってきました。 fukudaさんのwiki→http://www.am.ics.keio.ac.jp/members/fukuda/wiki/index.php?Kyoshow

Celoxica社*1のHandel-Cは、C言語をハードウェア向けに拡張した*2言語である。
開発環境およびコンパイラは、同社のDK3を利用する。

回路を意識せずに、処理したいことを記述することによってLSIを設計することができる。
また、合成の対象をFPGAに絞っているため、プロセス技術や細かいタイミングに起因する
トラブルがユーザの設計に影響を与える可能性が小さい。
Handel-Cで書かれたソースコードはVerilogHDLやVHDL,EDIFに変換される。

ただし、性能や回路面積についてはVerilogHDLやVHDLで直接記述したほうが 良い結果が得られる。

●DK3による開発フロー

  1. プロジェクトの作成
  2. ソースコードの記述
  3. デバッグ
    「Build」→「Set Active Configuration」で「Debug」を指定する。
    デバッグモードでビルド(F7)すると、次のようなデバッグ機能が利用可能になる。
    (Verilog HDLやVHDLへ変換するときは、デバッグコードはコメントアウトする必要あり)
    1. ソースコードデバッグ
      変数の値を表示しながら、ステップ実行を行ったり(F11)、
      ブレークポイントの指定等を行うことができる。
    2. プラグインの利用
      Handel-Cと外部の接続を定義するinterface文を使うと、
      デバッグ時に入出力信号をVisual C++などで作成したDLLと結合することができるらしい。
    3. chanin/chanout
      chanin/chanoutを利用して、外部ファイルから入力を行ったり
      結果をファイルへ出力したりする。
      (例)
        > chanin  input  with {infile = "ina.txt"};   //ファイルから入力
        > chanout output with {outfile = "out.txt"};  //ファイルへ出力
        >
        > input  ? x; 
        > output ! 5; 
      または、
       > chanout 8 stdout;  //ディスプレイに表示させたい
        > stdout ! 5;
    4. C/C++関数のコール
      C/C++で書いた関数をそのまま使用できる。
      その際、Handel-Cコードには下のような宣言が必要になる。
       > extern "C"  // 外部関数がC++なら、 extern "C++" になる
       > {
       >     int printf(const char *fmt, ...);  //標準ライブラリも利用できる
       >     unsigned short user_func(void);    //自分で定義したのも利用できる
       > }
  4. Logic estimation
    「Build」→「Set Active Configuration」で「EDIF」を指定。
    「Project」→「Settings」の「Linker」タブで、「Generate estimation info」にチェックを入れる。
    これでビルドすると、EDIFフォルダ内にクリティカルパスやLUT数の情報がHTML形式で保存される。
  5. VerilogHDL(VHDL)へのコンパイル
    「Build」→「Set Active Configuration」で「Verilog(VHDL)」を指定。
    これでビルドすれば、Verilog(VHDL)フォルダ内に*.v(vhd)ファイルが生成される。

●Handel-Cのサンプル

とりあえずVerilogやEDIFに変換できるサンプルコードを下に置いておきます。

set family = XilinxVirtexII;
set part = "xc2v6000bf957-4";

set clock = external;
set reset = external;

void main (void)
{
   unsigned int 4  DOut;
   interface port_in(unsigned int 4 DIn1) DataIn1();
   interface port_in(unsigned int 4 DIn2) DataIn2();
   interface port_out() DataOut(DOut);

   DOut = DataIn1.DIn1 + DataIn2.DIn2;
}

●特徴

  • bit幅の指定
    好きなbit幅を指定することができる。
    >  unsigned int 8 x;
    キャストによってbit幅を変えるのは無理みたい。
    また、次のようにbitを選択することができる。
    (例)
    >  unsigned int 8 x;
    >  unsigned int 1 y;
    >  unsigned int 5 z;
    >
    >  x = 0b01001001;
    >  y = x[4];       //0b0
    >  z = x[7:3];     //0b01001
  • Handel-C特有の演算子
    • <- (Take LSBs)
    • \\ (Drop LSBs)
      最下位の数bitだけ残したり、切り落としたりする。
      (例)
      >  unsigned int 8 x;
      >  unsigned int 4 y;
      >  unsigned int 4 z;
      >
      >  x = 0b11000111;
      >  y = x <- 4;     // 0b0111 
      >  z = x \\ 4;     // 0b1100
    • @ (Concatenation)
      複数の変数を連結する。
      (例)
      >  unsigned int 8 x;
      >  unsigned int 4 y;
      >  unsigned int 4 z;
      >
      >  y = 0xC;
      >  z = 0x7;
      >  x = y @ z;     // 0xC7
      また、0をパディングするために用いられる。
      (例)
      >  unsigned int 8  x;
      >  unsigned int 8  y;
      >  unsigned int 16 z;
      >
      >  z = (0 @ x) * (0 @ y);  // 上位8bitを0で埋める
  • Timing
    1代入文には、必ず1クロックと定められている。
    delayは何もせずに1クロック消費し、リソースの競合を避けるために使われる。
    また、チャネルを用いた通信にも1クロックかかる。
    ==や<、>=などの比較にはクロックはかからない。
  • 並列処理
    なにも指定しなければ、暗黙的にシーケンシャルな回路が生成される
    (seq文が省略されていると見なされる)。
    並列処理を行いたいときはpar文を用いる。
    (例)
    >  par
    >  {
    >    x = 1;
    >    {
    >      delay;
    >      x = 2;
    >    }
    >  }
    par01.jpg
    早く処理が終わった分岐部分は、最も遅い分岐の処理が終わるのを待つ。
  • Channel
    並列に処理している分岐間でのデータの受け渡しにはチャネルが用いられる。
    >  Channel ! Expression;  // 送信
    >  Channel ? Variable;    // 受信
    送信側は受信側が準備できるまで待機する。逆に、受信側も送信側が準備できるまで待機する。 
    (例)
    >  par
    >  {
    >    {
    >      a = b;
    >      c = d;
    >      link ! x;
    >    }
    >    link ? y;
    >  }
    この場合、タイミングは下表のようになる。
    CycleBranch 1Branch 2
    1a = b;delay
    2c = d;delay
    3Channel outputChannel input
  • 変数の共有について
    変数のスコープは、下図のようになっている。
    scope01.jpg
    上のようなスコープだと、並列処理をしている複数のプロセスが
    一つの変数を同時に変更するようなコードが書けてしまう(コンパイルが通ってしまう)。
    こんなときはprialtを使うとよい。
    (例)
    >  prialt {
    >    case chan1 ? y:
    >      //statement
    >      break;
    >    case chan2 ? y:
    >      //statement
    >      break;
    >    default:  // なくてもよい
    >      //statement
    >      break;
    >  }
    prialtは、最初に準備のできたチャネルを選択して通信を行う。
    複数のチャネルが同時に準備できた場合は、上に書かれたものが優先的に選択される。
    defaultなしのprialtの場合、どれかのチャネルが準備できるまで待機しつづける。
    defaultありのprialtの場合、どのチャネルも準備できていないなら、defaultの
    処理を行い、prialtから抜ける。

●ファンクションとマクロ

Return value?Typed return values?Called by reference?Shared hardware?
FunctionsCan haveYESNOYES
Arrays of functionsCan haveYESNOYES
Inline functionsCan haveYESNONO
Preprocessor macrosCan haveNOYESNO
Macro expressionsMust haveNOYESNO
Shared expressionMust haveNOYESYES
Macro proceduresNoneNOYESNO
  • ファンクションとマクロの例(変数を1.5倍する)
    • Function
      void f_sesqui (int *d, int s) // "shared" function without return
      {
        *d = s;
        *d += ((*d) >> 1);
      }
      
      int rf_sesqui (int s) // "shared" function with return 
      {
        int ret;
        ret = s;
        ret += (ret >> 1);
        return ret;
      }
    • Array of function
      void af_sesqui [5] (int *d, int s)
      {
        *d = s;
        *d += ((*d) >> 1);
      }
    • Inline function
      void inline if_sesqui (int *d, int s)
      {
        *d = s;
        *d += ((*d) >> 1);
      }
    • Preprocessor macro
      #define de_sesqui (s) ((s) + ((s) >> 1))
      #define dp_sesqui (d,s) ((d) = (s) + ((s) >> 1))
    • Macro expression
      macro expr me_sesqui (s) = s + (s >> 1);
    • Shared expression
      shared expr se_sesqui (s) = s + (s >> 1);
    • Macro procedure
      macro proc mp_sesqui (d, s)
      {
        d = s;
        d += (d >> 1);
      }
    • 呼び方
      {
        int 5 x, y;
        x = 10;
      
        f_sesqui (&y, x);      // Function without return
        y = rf_sesqui (x);     // Function with return
        
        af_sesqui[2] (&y, x);  // Array of Function
      
        if_sesqui (&y, x);     // Inline Function
      
        y = de_sesqui (x);     // Preprocessor macro with return
        dp_sesqui (y, x);      // Preprocessor macro without return
      
        y = me_sesqui (x);     // Macro expression
        y = se_sesqui (x);     // Shared expression
      
        mp_sesqui (y, x);      // Macro procedure
      }
  • 注意点
    次の記述は許されていない。
    >  y = f(g(x));
    >  y = f(x) + g(z);
    また、再帰も使えない。
  • ハードウェアの共有について
    ファンクションを用いてハードウェアの共有を行えば、回路の面積を小さく抑えられる。
    並列処理中に、異なるブロックで同じファンクションを使ってはならない。
    その場合は関数の配列やインライン関数を用いる。→回路は増大する。
  • クロックについて
    Macro expression と Shared expression は、変数に代入された時に1クロックかかる。
    それ以外は、処理内容に応じたクロック数が必要になる。

●雑記

  • main関数について
    main関数の引数と戻り値はvoidと決まっている。
    main関数はクロックと関連付けられ、異なるクロックで動作するパーツが
    他に存在する場合は、複数のmain関数が必要になる。
  • familyとpart
    ターゲットとするデバイスは何か
    >  set family = XilinxVirtexII;
    >  set part = "xc2v6000-4bf957"; 
    partは、「Project」→「Setting」の「Chip」タブで見れる。
  • clockとreset
    クロックとリセットの信号を外から引っ張ってくる
    >  set clock = external;
    >  set reset = external;
    ISEを通すには必要。
  • interface
    >  signal unsigned char OutData;
    >  interface bus_in(unsigned InputData)  InputPort();
    >  interface bus_out()     OutputPort( OutData );
    Verilogに落としたときに、この記述がモジュールの入出力になります。
    これがないと中身のある回路ができないぽいです

●知っていると便利

  • Macroの便利なオペレータ
    • Select
    • ifselect
    • let ... in

●良いコードの書き方

  • タイミングに関して
    どんなに複雑なステートメントでも、ちゃんと組み合わせ回路を生成してくれる。
    例えば以下の2つのコードは両方とも1クロックサイクルで実行される。
    >  x = y;
    >  x = (((y * z) + (w * v)) << 2) <- 7;
    しかし、遅延が大きくなって動作周波数が落ちるので
    簡単な論理を並列に動作させるように工夫すべきである。
    • divisionやmodulo,multiplicationは大きな論理が形成される。
      シフト演算やパイプライン処理でなんとかするべき
    • wide adderはcarry rippleのために深い論理を形成する。
      short adderを複数利用し、多段にする方がよい
    • greater thanやless thanも深い論理を生成する。
      ==や!=で代用する方法を考えるべき
    • 複雑な式は多段にすること
      (例)
      >  x = a + b + c + d + e + f + g + h;
      は、以下のように書き換えるとよい。
      >  par
      >  {
      >    temp1 = a + b;
      >    temp2 = c + d;
      >    temp3 = e + f;
      >    temp4 = g + h;
      >  }
      >  par
      >  {
      >    temp1 = temp1 + temp2;
      >    temp3 = temp3 + temp4;
      >  }
      >  x = temp1 + temp3;
    • Empty statement
      次のようなコードは、遅延が大きくなるため、避けるべき
      >  if ( a > b )
      >    x++;
      >  if ( b > c )
      >    x++;
      >  if ( c > d )
      >    x++;
      これらの比較がひとつもhitしない場合、全ての比較が1クロック内で行われなければならない。
      1クロックで1つの比較が行われるように、下のように書き換えた方がよい
      >  if ( a > b ) {
      >    x++;
      >  } else {
      >    delay;
      >  }
      >  if ( b > c ) {
      >    x++;
      >  } else {
      >    delay;
      >  }
      >  if ( c > d ) {
      >    x++;
      >  } else {
      >    delay;
      >  }

●CとHandel-Cの比較メモ

  • Statements
    両方CのみHandel-Cのみ
    switchpar
    do...whiledelay
    while?
    if...else!
    forprialt
    breakseq
    continueifselect
    return
    goto
    assert
  • Type
    両方CのみHandel-Cのみ
    intdoublechan
    unsignedfloatram
    charunionrom
    longwom
    shortmpram
    enumsignal
    registerchanin
    staticchanout
    externundefined
    structinterface
    volatile<>
    voidinline
    consttypeof
    auto
    signed
    typedef
  • Expression
    両方CのみHandel-Cのみ
    * (pointer indirection)sizeofselect(...)
    & (address of)width(...)
    他、全ての演算子が使えます@
    \\
    <-
    [:]
    let...in

(souichi)

(・ω・)364ノシ


*1 もともとHandel-CはOxford大学の1グループが開発していて、それが独立して設立されたのがCeloxicaらしい
*2 ANSI-Cの文法に、HoareのCSPの理論を加えたらしい

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2019-08-29 (木) 00:03:48