はじめに
先日 twitter でちょっと触れたのですが(こちら)、もしかしたら VHDL/Verilog-HDL で論理回路の設計を始めたばかり人(特に今までコンピュータープログラムをしてきた人)はここで混乱するのかもしれないと思って、もうちょっと詳しく説明してみたいと思います。
なお、この記事では VHDL と Verilog-HDL をまとめて HDL と記述しています。
HDLの for-loop は"個数"を指定するのに対してコンピュータープログラムの for-loop は"回数"を指定する
現在のコンピューターはフォンノイマン型と言われています。
フォンノイマン型コンピューターは "固定数" の演算器を逐次的に使用することで複雑な処理を実行します。
演算機の数はハードウェアで固定なため、ソフトウェア(プログラム)で変更出来るのは演算器を使用する順番と "回数" だけです。
そして、コンピュータープログラムの for-loop は、演算を何回実行するの "回数" を指定します。
それに引き換え、HDL はその名前の通り、ハードウェアがどうなっているかを記述するための言語です。
HDL の for-loop は、演算を行う回路を何個用意するかの "個数" を指定するのです。
動作中に"回数"を変更するのは簡単だが"個数"を変更するのは難しい
"個数" と "回数" の違いは、動作中に数(個数or回数)を変更するのが簡単か難しいかに影響します。
一般的に、"回数" を変更するのは "個数" を変更するのに比べて簡単です。
例えば、スーパーマーケットのレジを想像してください。
スーパーマーケットを訪れるお客さんの数は日によって違います。
その際、1個のレジをお客さんの人数回だけ動かすのと、お客さんの人数だけレジを用意するのと、どっちが簡単かと言えば、やっぱり前者でしょう。
そもそも、その日にどのくらいお客さんが訪れるのか判らないので何台レジを用意すれば良いか判らない上に、レジの機械やスペースや人員を揃えるのも必要です。
動作中(この例ではスーパーマーケットの開店中)に"個数"を変更するのは難しいのです。これは FPGA や ASIC などの論理回路も同様です。
HDLの for-loop で指定する個数は論理合成時に決定しておかなければならない
HDL では for-loop は実装する回路の "個数" を指定すると説明しましたが、前に説明した通り動作中に "個数" を変更するのは難しいことです。
動作中に "個数" を変更するのが難しいため、現在の HDL コンパイラは、for-loop で指定できる "個数" は定数でなければならないという制約を設計者に課しています(一部お高いツールでは例外もあります)。
つまり、次のような記述は不可です。
なぜなら、ループの数(下の例ではNUM)が外部から変更可能な信号に依存しているからです。
これではレジを何台用意しておけばいいのか判りません。
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 を記述すれば良いのでしょうか。
大きく分けて次の3つです
- コンピューターと同じように1個の演算器を指定された "回数" だけ稼働する
- 想定される最大分の演算器を用意しておき指定された "個数" だけを稼働する
- 1と2の組み合わせ
1. コンピューターと同じように1個の演算器を指定された"回数"だけ稼働する
ただし、この方法では1ステップで1回の演算しかしないため、演算を完了するのに "回数" 分だけのステップ数が必要になります。
HDL では for-loop は "個数" を指定するため "回数" を指定することは出来ません。for-loop を使用しない記述が必要になります。
一般的に HDL で "回数" を指定する時は、次のような状態遷移(ステートマシン)記述をします。
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 分数珠つなぎになる場合があります。あくまでも参考例として見てください。
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個の演算器を指定された "回数" だけ稼働した場合、指定した回数分の時間が必要です。
スーパーマーケットの例では、たくさんのお客さんが訪れると、捌ききれないかもしれません。
また、想定される最大分の演算器を用意しておき指定された "個数" だけを稼働する場合、最大分の演算器が必要なためその分リソースが必要です。
例えば一日に訪れるお客さんの人数分のレジが並んだスーパーマーケットなんか想像するだけで笑えます。
通常のスーパーマーケットでは、1台~数十台のレジを用意しておき、それらを必要な "回数" だけ稼働することで柔軟に対応しています。
HDL でもそのように記述してみましょう。
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 は格段に 空間的リソース の自由度が高いので、従来の時間的リソースに限定されたコンピュータープログラム言語で記述するのはとても困難だと感じています。個人的には 物理的リソース もプログラミングできるような新しい記述言語が欲しいところです。