Previous ToC Next

7. プログラムを楽に書く(その3)。(2009/6/16)

プログラム言語の中には、楽に書く、ということに著しく向いていない言語、 というものがある。真っ当な言語は、逆に、楽に書くことを目的としていると 思うわけだが、そうではない、何か別の目的をもって言語が設計されていて、 そのために極めて書くことが難しくなっているものである。

そういう言語には色々例があるが、一つの典型は 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;
ここでは、

をする、と仮定した。これだけで、行数で半分、バイト数で 1/3 にできる。

とはいえ、上のような話を某大学の准教授先生にしたら、「私は VHDL を出す Ruby プログラムを書いてるので、、、」とのたまわっていた。それはまあ、、、

また、emacs 用の vhdl-mode では、 component 文の自動生成・チェックといっ た機能があったりする。が、これは、結果的に長いソースができてしまうこと には変わりない、という問題は残る。アウトラインモードと合わせて使えばと かそういう話はあるが、人のコードを読む時には短いことが重要であるので、 それはあまり本質的な解決とはいいがたい。
Previous ToC Next