コンピュータープログラマーだった人が VHDL/Verilog-HDL で for-loop を記述する際の注意点

## はじめに

先日 twitter でちょっと触れたのですが([こちら](https://twitter.com/ikwzm/status/746823500392849409))、もしかしたら VHDL/Verilog-HDL で論理回路の設計を始めたばかり人(特に今までコンピュータープログラムをしてきた人)はここで混乱するのかもしれないと思って、もうちょっと詳しく説明してみたいと思います。
なお、この記事では VHDL と Verilog-HDL をまとめて HDL と記述しています。

## HDLの for-loop は"個数"を指定するのに対してコンピュータープログラムの for-loop は"回数"を指定する

現在のコンピューターはフォンノイマン型と言われています。
フォンノイマン型コンピューターは __"固定数"__ の演算器を逐次的に使用することで複雑な処理を実行します。
演算機の数はハードウェアで固定なため、ソフトウェア(プログラム)で変更出来るのは演算器を使用する順番と __"回数"__ だけです。
そして、コンピュータープログラムの for-loop は、演算を何回実行するの __"回数"__ を指定します。

それに引き換え、HDL はその名前の通り、ハードウェアがどうなっているかを記述するための言語です。
HDL の for-loop は、演算を行う回路を何個用意するかの __"個数"__ を指定するのです。

## 動作中に"回数"を変更するのは簡単だが"個数"を変更するのは難しい

__"個数"__ と __"回数"__ の違いは、動作中に数(個数or回数)を変更するのが簡単か難しいかに影響します。
一般的に、__"回数"__ を変更するのは __"個数"__ を変更するのに比べて簡単です。

例えば、スーパーマーケットのレジを想像してください。
スーパーマーケットを訪れるお客さんの数は日によって違います。
その際、１個のレジをお客さんの人数回だけ動かすのと、お客さんの人数だけレジを用意するのと、どっちが簡単かと言えば、やっぱり前者でしょう。
そもそも、その日にどのくらいお客さんが訪れるのか判らないので何台レジを用意すれば良いか判らない上に、レジの機械やスペースや人員を揃えるのも必要です。

動作中(この例ではスーパーマーケットの開店中)に"個数"を変更するのは難しいのです。これは FPGA や ASIC などの論理回路も同様です。

## HDLの for-loop で指定する個数は論理合成時に決定しておかなければならない

HDL では for-loop は実装する回路の __"個数"__ を指定すると説明しましたが、前に説明した通り動作中に __"個数"__ を変更するのは難しいことです。

動作中に __"個数"__ を変更するのが難しいため、現在の HDL コンパイラは、for-loop で指定できる __"個数"__ は定数でなければならないという制約を設計者に課しています(一部お高いツールでは例外もあります)。

つまり、次のような記述は不可です。
なぜなら、ループの数(下の例ではNUM)が外部から変更可能な信号に依存しているからです。
これではレジを何台用意しておけばいいのか判りません。

```VHDL:For_Loop_Bad_Sample.vhd
entity For_Loop_Bad_Sample
    generic (
        MAX_NUM       : positive := 1000000
    );
    port (
        CLK           : in  std_logic;
        RESET         : in  std_logic;
        GO            : in  std_logic;
        NUM           : in  integer range 0 to MAX_NUM;
         :
        (略)
         :
   );
end For_Loop_Bad_Sample;
architecture RTL of For_Loop_Bad_Sample is
    type   PAYMENT_ARRAY  is array (integer range <> ) of integer;
    signal payment_queue  :  PAYMENT_ARRAY(0 to MAX_NUM-1);
    signal total          :  integer;
begin
    process (CLK) 
        variable temp     :  integer;
    begin
        if (CLK'event and CLK = '1') then
            if (RESET = '1') then
                total <= 0;
            elsif (GO = '1') then
                temp := 0;
                for i in 0 to NUM-1 loop
                    temp := temp + payment_queue(i);
                end loop;
                total <= temp;
            end if;
        end if;
    end process;
     :
    (略)
     :
end RTL;
```

## HDLで動作中に回数/個数を変更する場合の記述方法

では、動作中に __回数__ / __個数__ を変更するような場合はどのように HDL を記述すれば良いのでしょうか。
大きく分けて次の３つです

1. コンピューターと同じように１個の演算器を指定された __"回数"__ だけ稼働する
2. 想定される最大分の演算器を用意しておき指定された __"個数"__ だけを稼働する
3. 1と2の組み合わせ

### 1. コンピューターと同じように１個の演算器を指定された"回数"だけ稼働する

ただし、この方法では１ステップで１回の演算しかしないため、演算を完了するのに __"回数"__ 分だけのステップ数が必要になります。

HDL では for-loop は __"個数"__ を指定するため __"回数"__ を指定することは出来ません。for-loop を使用しない記述が必要になります。

一般的に HDL で __"回数"__ を指定する時は、次のような状態遷移(ステートマシン)記述をします。

```VHDL:For_Loop_Sample_1.vhd
entity For_Loop_Sample_1
    generic (
        MAX_NUM       : positive := 1000000
    );
    port (
        CLK           : in  std_logic;
        RESET         : in  std_logic;
        GO            : in  std_logic;
        NUM           : in  integer range 0 to MAX_NUM;
         :
        (略)
         :
   );
end For_Loop_Sample_1;
architecture RTL of For_Loop_Sample_1 is
    type   STATE_TYPE     is (IDLE_STATE, RUN_STATE);
    signal state          :  STATE_TYPE;
    type   PAYMENT_ARRAY  is array (integer range <> ) of integer;
    signal payment_queue  :  PAYMENT_ARRAY(0 to MAX_NUM-1);
    signal index          :  integer range 0 to MAX_NUM-1;
    signal total          :  integer;
begin
    process(CLK) begin
        if (CLK'event and CLK = '1') then
            if (RESET = '1') then
                state <= IDLE_STATE;
                index <=  0 ;
                total <=  0 ;
            else
                case state is
                    when IDLE_STATE =>
                        if (GO = '1') then
                            state <= RUN_STATE;
                            index <= 0;
                            total <= 0;
                        end if;
                    when RUN_STATE =>
                        if (index < NUM) then
                            state <= RUN_STATE;
                            index <= index + 1;
                            total <= total + payment_queue(index);
                        else
                            state <= IDLE_STATE;
                        end if;
                    when others =>
                            state <= IDLE_STATE;
                end case;
            end if;
        end if;
    end process;
     :
    (略)
     :
end RTL;
```

### 2. 想定される最大分の演算器を用意しておき指定された"個数"だけを稼働する

大胆な方法ですが、こういうやり方もあります。

最大分がどのくらいか、それに対する動作中の稼働率などを精査する必要がありますが、もし正確に見積もることが出来て、かつリソース(FPGAの場合はLUTやBRAMの数)が十分であれば効果的な手法です。ただし、想定される最大分だけのリソースを使うので注意が必要です。

また、この例はあまりよくありません。詳しくは説明しませんが、ツールによっては加算器が MAX_NUM-2 分数珠つなぎになる場合があります。あくまでも参考例として見てください。

```VHDL:For_Loop_Sample_2.vhd
entity For_Loop_Sample_2
    generic (
        MAX_NUM       : positive := 1000000
    );
    port (
        CLK           : in  std_logic;
        RESET         : in  std_logic;
        GO            : in  std_logic;
        NUM           : in  integer range 0 to MAX_NUM;
         :
        (略)
         :
   );
end For_Loop_Sample_2;
architecture RTL of For_Loop_Sample_2 is
    type   PAYMENT_ARRAY  is array (integer range <> ) of integer;
    signal payment_queue  :  PAYMENT_ARRAY(0 to MAX_NUM-1);
    signal total          :  integer;
begin
    process(CLK)
        variable temp     :  integer;
    begin
        if (CLK'event and CLK = '1') then
            if (RESET = '1') then
                total <= 0;
            elsif (GO = '1') then
                temp  := 0;
                for i in 0 to MAX_NUM-1 loop
                    if (i < NUM) then
                        temp := temp + payment_queue(i);
                    end if;
                end loop;
                total <= temp;
            end if;
        end if;
    end process;
     :
    (略)
     :
end RTL;
```

### 3. 1 と 2 のハイブリッド

1個の演算器を指定された __"回数"__ だけ稼働した場合、指定した回数分の時間が必要です。
スーパーマーケットの例では、たくさんのお客さんが訪れると、捌ききれないかもしれません。

また、想定される最大分の演算器を用意しておき指定された __"個数"__ だけを稼働する場合、最大分の演算器が必要なためその分リソースが必要です。
例えば一日に訪れるお客さんの人数分のレジが並んだスーパーマーケットなんか想像するだけで笑えます。

通常のスーパーマーケットでは、１台~数十台のレジを用意しておき、それらを必要な __"回数"__ だけ稼働することで柔軟に対応しています。
HDL でもそのように記述してみましょう。

```VHDL:For_Loop_Sample_3.vhd
entity For_Loop_Sample_3
    generic (
        REG_NUM       : positive := 8;
        MAX_NUM       : positive := 1000000
    );
    port (
        CLK           : in  std_logic;
        RESET         : in  std_logic;
        GO            : in  std_logic;
        NUM           : in  integer range 0 to MAX_NUM;
         :
        (略)
         :
   );
end For_Loop_Sample_3;
architecture RTL of For_Loop_Sample_3 is
    type     STATE_TYPE     is (IDLE_STATE, RUN_STATE);
    signal   state          :  STATE_TYPE;
    type     PAYMENT_TYPE   is array (0 to REG_NUM-1) of integer;
    type     PAYMENT_ARRAY  is array (integer range <> ) of PAYMENT_TYPE;
    constant QUEUE_SIZE     :  integer := (MAX_NUM+REG_NUM-1)/REG_NUM;
    signal   payment_queue  :  PAYMENT_ARRAY(0 to QUEUE_SIZE-1);
    signal   index          :  integer range 0 to QUEUE_SIZE;
    signal   remain         :  integer range 0 to MAX_NUM;
    signal   total          :  integer;
begin
    process(CLK)
        variable acc        : integer;
    begin
        if (CLK'event and CLK = '1') then
            if (RESET = '1') then
                state  <= IDLE_STATE;
                index  <= 0;
                remain <= 0;
                total  <= 0;
            else
                case state is
                    when IDLE_STATE =>
                        if (GO = '1') then
                            state  <= RUN_STATE;
                            index  <= 0;
                            remain <= NUM;
                            total  <= 0;
                        end if;
                    when RUN_STATE =>
                        acc := 0;
                        for i in 0 to REG_NUM-1 loop
                            if (i < remain) then
                                acc := acc + payment_queue(index)(i);
                            end if;
                        end loop;
                        total <= total + acc;
                        if    (remain > REG_NUM) then
                            state  <= RUN_STATE;
                            index  <= index + 1;
                            remain <= remain - REG_NUM;
                        else
                            state  <= IDLE_STATE;
                            remain <= 0;
                        end if;
                    when others =>
                        state <= IDLE_STATE;
                end case;
            end if;
        end if;
    end process;
     :
    (略)
     :
end RTL;
```

## まとめ

* HDLの for-loop は __"個数"__ を指定するのに対してコンピュータープログラムの for-loop は __"回数"__ を指定する
* 動作中に __"回数"__ を変更するのは簡単だが __"個数"__ を変更するのは難しい
* HDLの for-loop で指定する __"個数"__ は論理合成時に決定しておかなければならない

## 余談(というか雑感)

以降は単なる与太話です。

フォンノイマン型コンピューターは"固定数"の演算器を逐次的に使用することで複雑な処理を実行します。
つまり、フォンノイマン型コンピューターは __空間的リソース__ の自由度を無くして、__時間的リソース__ のみ自由にプログラム出来るようにしています。
__空間的リソース__ は動的に変更するのが難しいのですが、__時間的リソース__ ならば動的に変更するのはそう難しくありません。
この特性をいかして、ソフトウェアの作りやすさを追求したのがフォンノイマン型コンピューターでした。
そして、現在のコンピュータープログラム言語のほとんどは、このころのフォンノイマン型コンピューターを前提に作られています。

しかし、単一プロセッサで性能が十分ならばそれで良かったのですが、昨今のように マルチコアとか HPCとか GPU とか FPGA とか、__時間的リソース__ のプログラミングだけでなく __空間的リソース__ のプログラミングまで必要になる時代になると、従来のコンピュータープログラム言語では対処するのが難しくなりました。

現在のコンピュータープログラム言語で __空間的リソース__ のプログラミングを行うには次の方法がとられています。

* コンピュータープログラム言語の仕様はそのまま(つまり __時間的リソース__ に限定)で、コンパイラで __空間的リソース__ のプログラムも頑張ってもらう
* コンピュータープログラム言語の基本的仕様はそのままで、ディレクティブなどを使ってコンパイラの __空間的リソース__ のプログラムを補助する
* コンピュータープログラム言語の仕様はそのままで、ライブラリやフレームワークで対応する
* __物理的リソース__ もプログラミングできるような新しいコンピュータープログラム言語を作る

特に FPGA は格段に __空間的リソース__ の自由度が高いので、従来の時間的リソースに限定されたコンピュータープログラム言語で記述するのはとても困難だと感じています。個人的には __物理的リソース__ もプログラミングできるような新しい記述言語が欲しいところです。