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;前者をGCNT.vhdl、後者をBENCH_GCNT.vhdlとして、GHDLとGTKWaveでモニョることにしますか。workディレクトリを作って、analyzeして、elaborate。
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;
GHWなwaveファイルを吐くオプションを付けて100[us]くらいrunさせてー、GTKWaveでモニョモニョ。
$ 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
$ ./BENCH_GCNT-BENCH --stop-time=100us --wave=BENCH_GCNT-BENCH-100us.ghw
$ gtkwave BENCH_GCNT-BENCH-100us.ghw
「dataflow的スタイルと大して変わらなくね?」とか思ったアナタ、まだtwo-process手法の一つ目しかやっていませんよ?と言う感じで次回に続くのであったのであった。
# 日本語サイコー。 :P