プログラム言語の中には、楽に書く、ということに著しく向いていない言語、
というものがある。真っ当な言語は、逆に、楽に書くことを目的としていると
思うわけだが、そうではない、何か別の目的をもって言語が設計されていて、
そのために極めて書くことが難しくなっているものである。
そういう言語には色々例があるが、一つの典型は VHDL である。
-- fo_unit
-- top level for FO FPGA
--
-- Version 0.0 1998/9/16 J. Makino
-- MAKUTIL ADD_PARITY 36
LIBRARY ieee;
USE ieee.std_logic_1164.all;
-- USE ieee.std_logic_unsigned.all;
ENTITY fo_unit IS
PORT(
rst : IN STD_LOGIC;
CLK : IN STD_LOGIC;
din0 : in std_logic_vector(35 downto 0);
din1 : in std_logic_vector(35 downto 0);
din2 : in std_logic_vector(35 downto 0);
din3 : in std_logic_vector(35 downto 0);
vdin : in std_logic_vector(3 downto 0);
active : in std_logic_vector(3 downto 0);
ef : in std_logic_vector(3 downto 0);
hf : in std_logic_vector(3 downto 0);
fifo_re : out std_logic_vector(3 downto 0);
dwait : in std_logic;
dout : out std_logic_vector(35 downto 0);
vdout : out std_logic;
ndout : out std_logic;
dwaitout : out std_logic_vector(3 downto 0);
active_out : out STD_LOGIC;
ipwe : IN STD_LOGIC;
ipdin : IN std_logic_vector(15 downto 0)
);
END fo_unit;
ARCHITECTURE a OF fo_unit IS
component ip_snoop
port(
rst : IN STD_LOGIC;
CLK : IN STD_LOGIC;
we : IN STD_LOGIC;
din : IN std_logic_vector(15 downto 0);
n : out std_logic_vector(7 downto 0);
calc_start : out std_logic
);
end component;
component fo_mode_control
port(
rst : IN STD_LOGIC;
CLK : IN STD_LOGIC;
calc_start : IN STD_LOGIC;
n : IN std_logic_vector(7 downto 0);
pend : IN std_logic;
handshake_mode : out std_logic;
reduction_mode : out std_logic;
reduction_rst : out std_logic
);
end component;
component fo_reduce
port(
CLK : IN STD_LOGIC;
rst : IN STD_LOGIC;
din0 : IN std_logic_vector(35 downto 0);
din1 : IN std_logic_vector(35 downto 0);
din2 : IN std_logic_vector(35 downto 0);
din3 : IN std_logic_vector(35 downto 0);
ef : in std_logic_vector(3 downto 0);
active : in std_logic_vector(3 downto 0);
re : out std_logic_vector(3 downto 0);
DOUT : OUT std_logic_vector(35 downto 0);
active_out : out STD_LOGIC;
dsout : OUT STD_LOGIC;
pend : OUT STD_LOGIC
);
end component;
component fo_handshake_control
port(
rst : IN STD_LOGIC;
CLK : IN STD_LOGIC;
handshake_mode : in std_logic;
din0 : in std_logic_vector(35 downto 0);
din1 : in std_logic_vector(35 downto 0);
din2 : in std_logic_vector(35 downto 0);
din3 : in std_logic_vector(35 downto 0);
vdin : in std_logic_vector(3 downto 0);
active : in std_logic_vector(3 downto 0);
ef : in std_logic_vector(3 downto 0);
hf : in std_logic_vector(3 downto 0);
fifo_re : out std_logic_vector(3 downto 0);
dwait : in std_logic;
dout : out std_logic_vector(35 downto 0);
vdout : out std_logic;
ndout : out std_logic;
dwaitout : out std_logic_vector(3 downto 0)
);
end component;
signal n : std_logic_vector(7 downto 0);
signal re_handshake : std_logic_vector(3 downto 0);
signal dout_handshake : std_logic_vector(35 downto 0);
signal dsout_handshake : std_logic;
signal dwaitout_handshake : std_logic_vector(3 downto 0);
signal re_reduce : std_logic_vector(3 downto 0);
signal dout_reduce : std_logic_vector(35 downto 0);
signal dsout_reduce : std_logic;
signal pend : std_logic;
signal handshake_mode : std_logic ;
signal reduction_mode : std_logic ;
signal reduction_rst : std_logic ;
signal vdout_handshake : std_logic ;
signal ndout_handshake : std_logic ;
signal calc_start : std_logic;
BEGIN
fifo_re <= re_reduce when reduction_mode = '1'
else re_handshake when handshake_mode = '1'
else "1111";
dwaitout <= dwaitout_handshake when handshake_mode = '1'
else "0000";
process(clk)
begin
if(clk'event and clk='1') then
if handshake_mode = '1' then
vdout <= vdout_handshake;
ndout <= ndout_handshake;
dout <= dout_handshake ;
else
vdout <= '0';
ndout <= dsout_reduce ;
dout <= dout_reduce;
end if;
end if;
end process;
u_ip: ip_snoop port map(
rst=>rst,
CLK=>CLK,
we=>ipwe,
din=>ipdin,
n=>n,
calc_start => calc_start);
u_mode: fo_mode_control port map(
rst=>rst,
CLK=>CLK,
calc_start => calc_start,
n=>n,
pend=>pend,
handshake_mode=>handshake_mode,
reduction_mode=>reduction_mode,
reduction_rst=>reduction_rst);
u_handshake: fo_handshake_control port map(
rst=>rst,
CLK=>CLK,
handshake_mode=>handshake_mode,
din0=>din0,
din1=>din1,
din2=>din2,
din3=>din3,
vdin=>vdin,
active=>active,
ef=>ef,
hf=>hf,
fifo_re=>re_handshake,
dwait=>dwait,
dout=>dout_handshake,
vdout=>vdout_handshake,
ndout=>ndout_handshake,
dwaitout=>dwaitout_handshake);
u_reduce: fo_reduce port map(
CLK=>CLK,
rst=>reduction_rst,
din0=>din0,
din1=>din1,
din2=>din2,
din3=>din3,
ef=>ef,
active=>active,
re=>re_reduce,
DOUT=>DOUT_reduce,
active_out=>active_out,
dsout=>dsout_reduce,
pend=>pend);
END a;
上のソースは、私が 10年くらい前に書いた、GRAPE-6 のボード上の FPGA 内
の回路の一部である。
ここでは、 founit という回路ブロック(まあ、普通の言語だと関数ですが、
実際にこれは回路そのものの記述)のソースファイルである。まず、なんだか
冗長性が高い、という気がするであろう。
なお、 "--" で始まるのはコメントである。 use は、他の言語にもあるパッ
ケージの宣言である。まず、
ENTITY fo_unit IS
PORT(
rst : in STD_LOGIC;
din0 : in std_logic_vector(35 downto 0);
...
);
END fo_unit;
をみてみよう。これは、この回路の外側につながるピン(入出力ポート)の宣言
だが、 entity, is, port, end と書かないといけない予約語がやたら多く、
さらに入出力信号の型名がやたら長いことがわかる。信号の型はどうせこの
std_logic というものとその配列しかないんだから、
fo_unit
PORT(
rst : in ;
din0 : in (35:0);
...
);
というくらいですんで欲しいものである。次に、 ARCHITECTURE 以下をみてみよ
う。最初にある
component ip_snoop
port(
rst : IN STD_LOGIC;
CLK : IN STD_LOGIC;
we : IN STD_LOGIC;
din : IN std_logic_vector(15 downto 0);
n : out std_logic_vector(7 downto 0);
calc_start : out std_logic
);
end component;
からしばらく続く component 文は、この回路の中で使う回路の宣言である。
つまり、 VHDL では、どういうわけか、ある親回路の中で使う回路は、そのイン
ターフェース、つまり C だとプロトタイプ宣言みたいなものが、親回路の実
体の記述の中に全部はいっている必要がある。しかも、VHDL にはプリプロセッ
サも include 文もないので、 cpp とかを使うわけでなければ、上の
ip_snoop の定義は ip_snoop.vhd と fo_unit.vhd の2つに、微妙に違う形で
発生することになる。これは論外で、もうちょっとまともな方法があるべきで
あろう。
もちろん、package 構文を使って、中で使う回路部品を全てライブラリにいれ
ればよいはずだが、これはこれで今度はそのために冗長な記述が必要になる。
なお、実際に ip_snoop を fo_unit の中で使っているのは
u_ip: ip_snoop port map(
rst=>rst,
CLK=>CLK,
we=>ipwe,
din=>ipdin,
n=>n,
calc_start => calc_start);
で、ここでは 名前=>名前 の形で fo_unit 内の信号線と ip_snoop のピンの
対応を表現している。実際にはここまで冗長な宣言が必要なわけではなくて、
u_ip: ip_snoop port map(rst,CLK,ipwe,ipdin,n,calc_start);
と順番による宣言も可能である。こっち使うべきだった気がするが、他があま
りに冗長なので上の記法を使ってしまっている。
なお、これは本質的には回路の記述、つまり回路図のテキスト表現である、と
いうことを考えると、そもそもこのような形で宣言すること自体が冗長である。
というのは、多くの場合に回路図では2つの部品の間を線でむすぶ。なので、
それは
部品A のポート a を部品 B のポートb につなぐ
ということが表現されればよい。しかし、 VHDL では
線にC という名前をつける。型も宣言する
部品A のポート a を線 C につなぐ
部品B のポート b を線 C につなぐ
という形になり、恐ろしく冗長な表現になってしまっている。
というわけで、 VHDL というのはどうしようもない言語で、簡潔でわかりやすいプログラムを書くのは極めて困難である。だが、だからといって、例えば
fifo0 : emi_wrfifo port map (
wrclk => cp_clk,
rdclk => phy_clk,
aclr => fifoclr,
data => emd,
rdreq => ddr2_wdata_req,
wrreq => wr0,
q => wrdata_reg(287 downto 216),
rdempty => wr0_emp);
fifo1 : emi_wrfifo port map (
wrclk => cp_clk,
rdclk => phy_clk,
aclr => fifoclr,
data => emd,
rdreq => ddr2_wdata_req,
wrreq => wr1,
q => wrdata_reg(215 downto 144),
rdempty => ddr2_wrdataemp);
fifo2 : emi_wrfifo port map (
wrclk => cp_clk,
rdclk => phy_clk,
aclr => fifoclr,
data => emd,
rdreq => ddr2_wdata_req,
wrreq => wr2,
q => wrdata_reg(143 downto 72),
rdempty => wr2_emp);
fifo3 : emi_wrfifo port map (
wrclk => cp_clk,
rdclk => phy_clk,
aclr => fifoclr,
data => emd,
rdreq => ddr2_wdata_req,
wrreq => wr3,
q => wrdata_reg(71 downto 0),
rdempty => wr3_emp);
というような記述をしてもよい、というわけでもない。これはとある人が実際に書いていたものである。この場合には
gen_fifo: for i in 0 to 3 generate
fifo : emi_wrfifo port map (
wrclk => cp_clk,
rdclk => phy_clk,
aclr => fifoclr,
data => emd,
rdreq => ddr2_wdata_req,
wrreq => wr(i),
q => wrdata_reg(287-i*72 downto 216-i*72),
rdempty => wr_emp(i));
end generate;
あるいは
gen_fifo: for i in 0 to 3 generate
fifo : emi_wrfifo port map (cp_clk, phy_clk, fifoclr, emd, ddr2_wdata_req,
wr(i), wrdata_reg(287-i*72 downto 216-i*72), wr_emp(i));
end generate;
といった形にするべきである。ここで書くとまた繰り返しになってしまうが、
基本的なことは、
繰り返しがある時には計算機にやらせる
ということである。これにより、
-
コードが短くなり、理解しやすくなる
-
修正の時に間違いがはいる確率が減る
-
そもそも修正の必要が減る
些細なことに聞こえるかもしれないが、割合重大な問題点として、修正前のコー
ドではインデントがおかしく、おかしいものがそのままコピペされている、と
いうことにも注目して欲しい。これは、Emacs を使っているなら一瞬でできる
インデントの自動修正を適切に使っていない、ということであり、そのように
して読みにくく、結果としてデバッグもメンテも困難なコードを書いているわ
けである。
なお、VHDL 自体の冗長性を避けるようなプリプロセッサを作ったとしたら、
最初のコードがどれくらい短くなりそうか、の例が以下である。
-- fo_unit
-- top level for FO FPGA
--
-- Version 0.0 1998/9/16 J. Makino
-- MAKUTIL ADD_PARITY 36
LIBRARY ieee;
USE ieee.std_logic_1164.all;
-- USE ieee.std_logic_unsigned.all;
ENTITY fo_unit
PORT(
in: rst,
CLK,
din0(35:0),
din1(35:0),
din2(35:0),
din3(35:0),
vdin(3:0),
active(3:0),
ef(3:0),
hf(3:0);
out: fifo_re(3:0);
in: dwait;
out: dout(35:0),
vdout,
ndout,
dwaitout(3:0),
active_out;
in: ipwe,
ipdin(15:0)
);
END;
ARCHITECTURE a OF fo_unit IS
signal n : (7:0);
signal re_handshake : (3:0);
signal dout_handshake : (35:0);
signal dsout_handshake;
signal dwaitout_handshake : (3:0);
signal re_reduce : (3:0);
signal dout_reduce : (35:0);
signal dsout_reduce;
signal pend;
signal handshake_mode ;
signal reduction_mode ;
signal reduction_rst ;
signal vdout_handshake ;
signal ndout_handshake ;
signal calc_start ;
BEGIN
fifo_re <= re_reduce when reduction_mode = '1'
else re_handshake when handshake_mode = '1'
else "1111";
dwaitout <= dwaitout_handshake when handshake_mode = '1'
else "0000";
process(clk)
begin
if(clk'event and clk='1') then
if handshake_mode = '1' then
vdout <= vdout_handshake;
ndout <= ndout_handshake;
dout <= dout_handshake ;
else
vdout <= '0';
ndout <= dsout_reduce ;
dout <= dout_reduce;
end if;
end if;
end process;
u_ip: ip_snoop port map( rst,CLK,ipwe,ipdin,n, calc_start);
u_mode: fo_mode_control port map(rst,CLK, calc_start,n,pend,handshake_mode,
reduction_mode,reduction_rst);
u_handshake: fo_handshake_control port map(
rst,CLK,handshake_mode,din0,din1,din2,din3,vdin,active,
ef,hf,re_handshake,dwait,dout_handshake,vdout_handshake,
ndout_handshake,dwaitout_handshake);
u_reduce: fo_reduce port map(
CLK,reduction_rst,din0,din1,din2,din3,ef,active,re_reduce,
DOUT_reduce,active_out,dsout_reduce,pend);
END a;
ここでは、
-
component 文はそっちのソースから自動生成
-
std_logic とかも自動追加
をする、と仮定した。これだけで、行数で半分、バイト数で 1/3 にできる。
とはいえ、上のような話を某大学の准教授先生にしたら、「私は VHDL を出す
Ruby プログラムを書いてるので、、、」とのたまわっていた。それはまあ、、、
また、emacs 用の vhdl-mode では、 component 文の自動生成・チェックといっ
た機能があったりする。が、これは、結果的に長いソースができてしまうこと
には変わりない、という問題は残る。アウトラインモードと合わせて使えばと
かそういう話はあるが、人のコードを読む時には短いことが重要であるので、
それはあまり本質的な解決とはいいがたい。