O HAI THIS BLOG PURPZIEZ 2 B UZED AZ MAH PLESIOUS MEM. :)

2008/08/23

I CAN HAS VHDL? #3

VHDLについて、テキトーに書いてみるべす、第三回。

dataflow的スタイルにおける欠点を克服する新たなスタイルはないものか?

アレな界隈で知られている非dataflow的なスタイルの一つに、two-process手法と呼ばれるスタイルがある。このスタイルはどんな単一クロック同期回路に適用出来るモノで、この手のデザインは様々な目的で開発されるデザインの中でも大部分を占める。テキトーなコードを書いたことがある人なら、どんなスタイルを使ってもイイコトとワルイコトがあるのは分かると思うけれど、取り敢えず、dataflow的スタイルとtwo-process手法を比較してイイコトだけ挙げてみると、
  • 一様なアルゴリズム記述
  • 抽象化レベルの向上
  • 可読性の向上
  • 明確な逐次実行
  • デバックの簡易化
  • シミュレーション速度の向上
  • シミュレーションモデルと合成可能なモデルが同一
ってな感じで、どこかのエラい人が営業の為にならべる美辞麗句の様で笑える。知っていて得をするかは保証しかねるが、多分損はしないので話を続けるべす。

旧いdataflow的スタイルからtwo-process手法への移行は、実際のトコロ、気が抜ける位に単純なコーディングスタンスの違いを克服することで、そんなに難しくなかったりする。変えるトコは
  • 一つのentityに対して、二つだけのprocessな文
  • 抽象化レベルの高い逐次実行でアルゴリズムを記述
  • port(必要ならgenericも)とsignalの宣言が全てrecord
の三つだけ。

何度も言っているけど、VHDLとCみたいなプログラム言語との大きな違いは、concurrentな文とprocessな文がコーディングされている順序ではなくて、イベントによってスケジューリングされるトコロな訳で、その辺りの事情は元々ハードウェア設計目的で作られた言語故に当然なんだな。で、我々のオツムはconcurrentな文とprocessな文が或る量を超えると全体の動作を把握することが困難になる。人にも依るだろうけど、だいたい50くらいまでで全員お手上げになるっちゅー話。一方で、逐次実行なプログラムだと、どんなに量が増えようと上から下まで順番に実行される訳で、実行順序で混乱するなてこたぁ、あんまりない。勿論、マルチプロセスやらマルチスレッドなら話は別だけど。言ってみれば、あらゆるVHDLなコードはマルチスレッドな感じのプログラミングそのものな訳で、dataflow的スタイルでそのスレッドっぽいものを増やせば増やすほど、メンドイことになるって寸法。

可読性を向上し、一様なアルゴリズム記述を提供すると言う要求に対して、two-process手法は一つのentityに対して、二つだけのprocessな文を使用する。一つ目のprocessな文には全てを非同期式組み合わせ回路として、二つ目のprocessな文には全てを同期式順序回路として。以後、ここでは前者をcombinational、後者をsequentialと呼ぶことにする。two-process手法の提示する構造を使うことによって、あるentityで実現すべきアルゴリズムは、combinationalな方に逐次実行な文として記述され、sequentialな方にはレジスタっぽいモノが残る。クロックをCLK、入力をD、出力をQ、現在の内部状態をr、次の内部状態をgとすると、

combinational
+-----------------+
D ------>| Q <= f_q(D, r); |------> Q
| |
+--->| g <= f_g(D, r); |----+ g
| +-----------------+ |
| +-----------------+ |
r +----| |<---+
| r <= g; |
CLK ---->| |
+-----------------+
 sequential

っつー感じになる。おー、これはタマゲタぜよ、制御屋さんなら何て事ぁない、タダの状態方程式/出力方程式のブロック図の出来上がり。sequentialなトコロはまさにz^-1ですな、センセー。

あんまり良い例じゃないけど、簡単なモノじゃないと分かりにくいので、とりあえず、ロードとイネーブル付きの蝶テキトーなカウンタをtwo-process手法で書くとこんなんになる。
     1 library ieee;
2 use ieee.std_logic_1164.all;
3 use ieee.std_logic_arith.all;
4
5 entity GCNT is
6 generic (
7 CW : integer range 2 to integer'high := 8
8 );
9 port (
10 iCLK : in std_logic;
11 iLD : in std_logic;
12 iEN : in std_logic;
13 iC : in std_logic_vector(CW-1 downto 0);
14 oC : out std_logic_vector(CW-1 downto 0)
15 );
16 begin
17 end entity GCNT;
18
19 architecture TP of GCNT is
20
21 signal gC : std_logic_vector(CW-1 downto 0);
22 signal rC : std_logic_vector(CW-1 downto 0) := (others => '0');
23
24 begin
25
26 P_COMB : process (iLD, iEN, iC, rC)
27 variable vC : std_logic_vector(CW-1 downto 0);
28 begin
29 if (iLD = '1') then
30 vC := iC;
31 elsif (iEN = '1') then
32 if (unsigned(rC) >= 2**CW-1) then
33 vC := (others => '0');
34 else
35 vC := unsigned(rC) + 1;
36 end if;
37 else
38 vC := rC;
39 end if;
40
41 gC <= vC;
42 oC <= rC;
43 end process P_COMB;
44
45 P_SEQ : process (iCLK)
46 begin
47 if (iCLK'event and iCLK = '1') then
48 rC <= gC;
49 end if;
50 end process P_SEQ;
51
52 end architecture TP;
はい、とってもお手軽デスネ。さらに、蝶ヤル気の無いテストベンチをでっち上げる。
     1 library ieee;
2 use ieee.std_logic_1164.all;
3
4 entity BENCH_GCNT is
5 begin
6 end entity BENCH_GCNT;
7
8 architecture BENCH of BENCH_GCNT is
9
10 component GCNT is
11 generic (
12 CW : integer range 2 to integer'high := 8
13 );
14 port (
15 iCLK : in std_logic;
16 iLD : in std_logic;
17 iEN : in std_logic;
18 iC : in std_logic_vector(CW-1 downto 0);
19 oC : out std_logic_vector(CW-1 downto 0)
20 );
21 end component GCNT;
22
23 constant cCLK_CYCLE : time := 1 us;
24
25 constant cCW : integer range 3 to integer'high := 4;
26
27 signal sCLK : std_logic := '0';
28 signal sLD : std_logic := '0';
29 signal sEN : std_logic := '0';
30
31 constant cC : std_logic_vector(cCW-1 downto 0)
32 := (cCW-1 downto 2 => '1', 1 downto 0 => '0');
33
34 signal sGCNT_oC : std_logic_vector(cCW-1 downto 0);
35
36 begin
37
38 P_CLK : process
39 begin
40 sCLK <= '0'; wait for cCLK_CYCLE/2;
41 sCLK <= '1'; wait for cCLK_CYCLE/2;
42 end process P_CLK;
43
44 P_LD : process
45 begin
46 sLD <= '0'; wait for (2**(cCW-1)-1)*cCLK_CYCLE;
47 sLD <= '1'; wait for 1*cCLK_CYCLE;
48 sLD <= '0'; wait;
49 end process P_LD;
50
51 P_EN : process
52 begin
53 sEN <= '1'; wait for cCW*cCLK_CYCLE;
54 sEN <= '0'; wait for cCW*cCLK_CYCLE;
55 end process P_EN;
56
57 U_GCNT : GCNT
58 generic map (
59 CW => cCW
60 )
61 port map (
62 iCLK => sCLK,
63 iLD => sLD,
64 iEN => sEN,
65 iC => cC,
66 oC => sGCNT_oC
67 );
68
69 end architecture BENCH;
前者をGCNT.vhdl、後者をBENCH_GCNT.vhdlとして、GHDLとGTKWaveでモニョることにしますか。workディレクトリを作って、analyzeして、elaborate。

$ mkdir work
$ ghdl -a --std=02 --workdir=./work --ieee=synopsys GCNT.vhdl
$ ghdl -a --std=02 --workdir=./work --ieee=synopsys BENCH_GCNT.vhdl
$ ghdl -e --std=02 --workdir=./work --ieee=synopsys -o BENCH_GCNT-BENCH BENCH_GCNT BENCH
GHWなwaveファイルを吐くオプションを付けて100[us]くらいrunさせてー、GTKWaveでモニョモニョ。

$ ./BENCH_GCNT-BENCH --stop-time=100us --wave=BENCH_GCNT-BENCH-100us.ghw
$ gtkwave BENCH_GCNT-BENCH-100us.ghw



「dataflow的スタイルと大して変わらなくね?」とか思ったアナタ、まだtwo-process手法の一つ目しかやっていませんよ?と言う感じで次回に続くのであったのであった。
# 日本語サイコー。 :P

2008/08/21

I CAN HAS VHDL? #2

VHDLについて、テキトーに書いてみるべす、第二回。

合成可能なVHDLモデルを設計するスタイルのうち、恐らく一番割合を占めているモノはdataflow的スタイルと呼ばれているモノである。つまり、所望の機能を実装する為に、膨大な数のsignalで繋がりを持った小さなprocessな文とconcurrentな文でコーディングするスタイル。

どんな言語でもウンコなコードを作ることが出来るけど、dataflow的スタイルで書かれた出来の悪いVHDLモデルは、読むことも中身を理解することもマジ苦痛れす。その苦痛の一端に加担しているのは、「processな文とconcurrentな文は書かれている順序では実行されず、特定の入力信号が変化した時にこれが実行される」と言う、trombikせんせーがPOEやErlangがやばいとか何故か今更騒いでいらっしゃる様な”いう゛ぇんとどりう゛ん”なkarmaをVHDL自身が持っているからとも言える。

dataflow的スタイルで書かれたコードが記述する機能を紐解くには、データの流れとして描かれたブロック、文と文との依存関係を見極める必要があるから、言っちゃなんだが、コイツぁあまり一般的なモノではない。で、「その可読性は?」と言えば、実際のところ、単なる回路図にも劣ると言う始末だったりする。どういう事なのかがあまり想像出来ないと思うけど、小分けにされたprocessな文やconcurrentな文が小さな機能を持ったブロックで、そのブロックの入出力がsignalの名前でラベルされていて、しかもそのブロック間の接続を知るには、signalの名前でsignalの接続先/接続元を探すしかないので、普通の回路図にならあるハズのwireが描かれていないと言うイメージ。

コードの書き方やそのデザインが何の目的で開発されたのかにも依るけど、dataflow的なスタイルで書くと1k越えのprocessな文を書くなんてのはザラにある訳で、コードを書いた本人以外が中身を理解することや”めんてなんす”的なことを考えると頭が痛くなることウケアイ。更には、コードを書いた本人に「このprocessな文が何を意図しているのか?」とか訊いても、「バカめ、覚えてねぇYO!!1」とか、いやマジで。

dataflow的スタイルでコーディングされると、抽象化レベルは当然低くなるし、ブロックに納められる機能は必要以上にシンプルになる傾向がある。dataflow的スタイルを貫くcoder自身が持つ、抽象化レベルの低いコードを書くっつう癖は、論理合成ツールの頭が良くなって行きつつある今においては、既に腐臭を放ち始めていると思ふ。マルチプレクサ、bitwise演算、条件代入文の様なprimitiveなモノが彼方此方に独立したprocessな文として散乱しているコードから、そのコード全体が意図するアルゴリズム的なモノ、例えば、「離散時間最適レギュレータとして構成されるブツの状態フィードバックゲインベクトルをセルフチューニングする為に離散時間リカッチ行列方程式を”りあるたいむ”で逐次計算するヒュアーの方法を実装したハードウェア行列演算回路」とか読み取るとかデバッグするとかは、そーとー難しいんじゃなかろうか。

そして、dataflow的スタイルのもう一つの注目すべき欠点は、シミュレーションが激しく遅い事だったりする。signalのassignmentはvariableのそれと比べて100倍くらい計算時間が必要だったりする。「そんなカバな?」と思うかもしれないが、signalには更新すべきattribute、つまり属性というモノが憑いているし、駆動イベントはイベントキューに突っ込まれる。結果、膨大なデルタ遅延がそのままシミュレーションに必要な時間を長ーくするのであーる。大量のconcurrentな文とprocessな文で構成されるdataflow的スタイルでコーディングされたブツはシミュレーションに要する時間の大部分をsignalの管理とprocessな文/concurrentな文の実行スケジューリングに費やしていたりする。回路規模、テストベンチの構成とスティミュラスの具合にもよるが、msオーダーのブツのシミュレーションにdayオーダーのシミュレーション時間が必要になる事だって珍しくなかったりする。

勿論、頭の良いシミュレータの最適化機能の一つとして、問題の無い範囲でsignalをvariableに変換してシミュレーションを高速化するなんてのも考えられるけれど、今のところ、頭の良いシミュレータは目玉が飛び出る位のお値段だったりする。一方で、FPGAベンダが自社デバイスの需要拡大を目的に、synthesize/translate/map/parまでやってくれる様なお得な論理合成ツールセットをほとんどタダの様な値段でバラ撒いているし、サポート無しならタダだったりするトコの方が多かったりする。そういうモノは、かなりbuggyなbin blobだったりすることもあるが、それはまた別問題。

で、二回に渡ってdataflow的スタイルを貶し続けてきた訳だが、「じゃあ、どうすりゃいいんよ?」と言う問題についてはちっとも案が提出されていないままなので、次回に続くのであったのであった。 :P
# 英語無しだと楽で良いなー。 :P

2008/08/20

I CAN HAS VHDL? #1

VHDLについて、テキトーに書いてみるべす、第一回。

時は1980年代半ば、ディジタル回路の開発、検証、シミュレーションが困難になっていた最中、米国国防総省の主導でVHSIC == very high speed integrated circuit、つまり「蝶速い集積回路」を開発するに定義されたのがVHDLの原点じゃった。だから、VHDL == VHSIC hardware description language、蝶意訳すると、「ものごっつ速い集積回路のハードウェアを記述する言語」。言語的にはAdaの上位セットで、signalと呼ばれるmessage passingの為の仕組みがある。主な目的としては、実行可能な俺様仕様の記述、そして色んなトコが提供している記述された仕様を俺様仕様と混ぜ混ぜしてもシミュレーション可能にする事だったのだった。

出たての頃は、高級な、つまり普通のコンピュータプログラムに近いbehavioralなシミュレーションだけしか出来なかった。今日、synthesis若しくはsynthesizeと呼ばれる「モデリングされたブツから回路への変換」である論理合成は、ターゲットデバイスのライブラリからゲートもしくはブロックを手でムニョヘニョしていた訳ですな。unixyでhackyな人ならやらないであろうこんな手動変換は"ひゅーまんえらー"が入り込む訳で、当然、事前にシミュレーションしているモデルでもパーになりうる。1990年代に世に出始めたVHDL論理合成ツールは、VHDLで記述されたコードをターゲットとなるデバイスのネットリストに変換するブツである。

件のVHDL論理合成ツールの登場は、ソフトウェアな人ではなく、むしろハードウェアな人がVHDLでコーディングをする事態を招いたのだった。どう言う事かと言うと、旧いハードウェアな人は回路図的な手法でVHDLなコーディングをする訳で、出来上がるコードと言うコードは悉くdataflow的だったりする。レジスタ、マルチプレクサ、加算器、状態機械の様な機能が限定されたモノが文脈的な繋がり無く細分化され、その組み合わせでデザイン全体が出来ていた。この1990年代的なスタイルは比較的小規模な回路なら今でも問題にならないし、機能的にお粗末だった時代の合成ツールはその程度のモノしか扱えなかった事もdataflow的なデザインが跳梁跋扈する産褥になっていた。

しーかし、高級言語のコンパイラが下手なhand optimizeよりも最適化が巧くなってきた様に、お粗末だったVHDL論理合成ツールも"ぱわーあっぷ"している。具体的には、言語仕様として改訂を重ねているVHDL標準の内、今や大部分の機能が合成可能な状況にある。寧ろ、そう言う流れに追いついていないVHDL論理合成ツールは既に市場から淘汰されつつあると言って良いと思ふ。

合成ツール側が進化している事実を前に、VHDLなコードを書く"ひゅーまん"は如何にあるべきか?「dataflow的なコードの方がbackward comatibleじゃんよ!!1」と言う建前を宣って、貧相な機能しか持たない前時代の合成ツールにしがみついて生きていくのか?VHDL標準の多くの機能がサポートされている"もだん"な論理合成ツールに、1990年代的なスタイルでコーディングされたコードを喰わせるだけなのか?これから更に規模が増大する事間違い無しのディジタル回路の開発現場で、dataflow的なスタイルでコーディングしていく輩は生き残れるのか?

次回に続く、多分。 :P
# めんどいので英語無し。 :P