順序回路の設計

 

 ここでは、順序回路の1つである「2ビット同期カウンタ 」を例にして、EDAツールによる回路設計手順を復習し、かつ「クロック」の概念を学びます。2ビット同期カウンタでは、入力に応じて2桁の出力が「00→01→10→11→00‥‥」と変化します。この回路をVHDLで記述し、回路設計を行います。EDAツールによる設計は組合わせ回路と同様、以下の手順を経て行われます。

  1. 環境設定
  2. 順序回路のVHDL記述(回路記述テストベンチ作成
  3. 論理シミュレーション(アナライズ、シミュレーション)
  4. 論理合成(論理最適化、ネットリストの出力)
  5. FPGAを用いた回路の実現(論理合成、マッピング、ダウンロード)
  6. 状態機械
  7. IEEE1164ライブラリを用いた記述

まずはテキストに書かれている通りに実行して、設計手順を復習して下さい。細かな手順は省略してあるので、組合せ回路のホームページを見て下さい。

   


環境設定

 第1回目で行ったように、まずは環境設定を行って下さい。

 

VHDLによる回路動作記述

 第1回目と同様に、ここでもVHDLによる回路動作の記述はmuleなどのエディタ上で行います。以下は「2ビット同期カウンタ回路」のVHDL記述ファイル「counter2.vhd」を示しています。

 

-- counter2.vhd: 2bit counter
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
         
entity counter2 is
  port (reset, clk, input: in std_logic;
        y0, y1: out std_logic);
end counter2;
         
architecture behavior1 of counter2 is
    signal r0, r1 : std_logic := '0';
begin
  y0 <= r0;
  y1 <= r1;
  P1: process (clk, reset)
    variable t0, t1 : std_logic;
  begin
    if reset = '0' then
      r1 <= '0';  r0 <= '0';
    elsif clk'event and clk = '1' then
      t0 := r0;  t1 := r1;
      if (input = '1') then
        r0 <= not t0;
        r1 <= ((not t0) and t1) or (t0 and (not t1));
      end if;
             end if;
  end process;
end behavior1;
2ビット同期カウンタのVHDL記述例「counter2.vhd」

 

 この例では、2 ビットの記憶素子に対応するシグナルを 「r0, r1」としています。またクロック 「clk」, リセット 「reset」, カウントアップするかどうかを指定する信号 「input」を入力として持っています。variable の使用例を示すために 「t0, t1」を介して 「r0, r1」を更新していますが、直接 「r0, r1」を用いても等価です。出力は 「y0, y1」で、内部状態をそのまま出力しています。「y0, y1」は出力ポートなので、代入文の右辺には使えません。

 上記の例は、典型的な「非同期リセット付き、立ち上がりエッジ同期」の順序回路の記述です。この例のように、プロセス文中の分岐の条件により代入が行なわれないことがあり得るシグナルは、以前の値を保持する必要があります。そのためこのようなシグナルは記憶素子 (フリップフロップ、レジスタ) として扱われます。特定のクロックシグナルの立ち上がり、あるいは立ち下がりのイベントによって、全ての記憶素子が動作するように記述するのです。これは「単相クロック完全同期式」の順序回路の記述と呼ばれています。記憶素子としては「リセット / プリセット付き」の「エッジトリガ式フリップフロップ」のモデルが用意されていることが多く、通常は上記の例のように、クロックの他にリセットシグナルを記述します。

 複数のクロックがある記述、個々の記憶素子が他の論理のイベントで駆動される非同期回路の記述などもVHDLとしては正しいのですが、タイミングの検証を綿密に行なう必要があること、また、デバイスによっては実現できない場合があること (FPGA など) に注意する必要があります。

 VHDLの文法上は同じ意味を持つ書き方が他にも考えられます。しかし、あまり凝った書き方をすると処理できない場合があります。そこで基本的にはこの枠組での記述、あるいは必要に応じてクロック、リセットの論理を逆にした記述が推奨されます。

 


回路のテスト

 以下に、2ビット同期カウンタのVHDL記述例「counter2.vhd」のためのテストベンチファイル「test_counter2.vhd」を示します。

-- test_counter2.vhd
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
         
entity test_counter2 is
end test_counter2;  -- no port
         
architecture test_structure of test_counter2 is
  component counter2
    port (reset, clk, input: in std_logic; 
          y0, y1: out std_logic);
  end component;
  for count1: counter2 use entity work.counter2(behavior1);
  signal clk: std_logic := '0';
  signal reset, input, y0, y1: std_logic;
begin
  reset <= '1', '0' after 5 ns, '1' after 30 ns;
  clk   <= not clk after 10 ns;
  input <= '0', '1' after 50 ns;
         
  count1: counter2 port map (reset, clk, input, y0, y1);
         
end test_structure;
2ビット同期カウンタ「counter2.vhd」のためのテストベンチファイル例「test_counter2.vhd」

  


論理シミュレーション

 上記の回路が正しく動作するかどうかを調べるために、アナライズとシミュレーションをしてみましょう。手順は第1回目と同様です。また環境設定を変更する必要はありません。ただし、もしディレクトリを変えている場合は、カレントディレクトリに設定ファイル「.synopsys_vss.setup」を用意してください。なお前回と同様、「mkdir work」で「work」というディレクトリを作成しておく必要があることに注意して下さい。また最終行に改行を入れることを忘れないでください。

 

アナライズ

 「vhdlan [ファイル名]」 でVHDL記述ファイルをシミュレーションのための中間ファイルに変換します。本実験では、「counter2.vhd」と「test-counter2.vhd」についてアナライズを行います。

vhdlan counter2.vhd
counter2.vhdのアナライズ

vhdlan test_counter2.vhd
test_counter2.vhdのアナライズ

 

シミュレータ起動

 「vhdldbx」で起動します。

 

トレースするシグナルの指定

 シミュレータの最下行に必要なコマンド入力をして、回路が正常に動作しているかを評価して下さい。

 

シミュレーション実行

 「run [時間]」でシミュレーションを実行します。

 


論理合成

 シミュレーションによって、回路が正常に動作することを確認したら、次は「論理合成」によって回路を最適化します。もしディレクトリを新たに作っているのであれば、カレントディレクトリに設定ファイル「.synopsys_dc.setup」を用意します。最終行に改行を入れることを忘れないでください。

 

デザインアナライザの起動

 「design_analyzer」で起動します。

 


FPGA を用いた回路の実現

ボード用の各種設定

library ieee;
use ieee.std_logic_1164.all;
         
package porttype is
type segarray is array (3 downto 0) of std_logic_vector(7 downto 0);
end porttype;
         
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
         
library work;
use work.porttype.all;
         
entity counter2_fpga is
generic (cycle : positive := 12587500);
port (clock, pb0 : in std_logic;
 switch8 : in std_logic;
      seg : out segarray);
end counter2_fpga;
         
architecture behavior1 of counter2_fpga is
component counter2
port (reset, clk, input: in std_logic;
      y0, y1: out std_logic);
end component;
         
signal clk : std_logic;
signal reset : std_logic;
signal input : std_logic;
signal y0, y1 : std_logic;
signal timer : integer range 0 to cycle;
         
 type digitarray is array (3 downto 0) of std_logic_vector(3 downto 0);
 signal digit: digitarray;
 signal dot: std_logic_vector(3 downto 0);
         
begin
counter2_0: counter2 port map (reset,clk,input,y0,y1);
transition_clock: process (reset, clock)
begin
 if clock'event and clock = '1' then
  if timer=cycle then
   timer <= 0;
   if clk = '0' then 
    clk <= '1';
   else
    clk <= '0';
   end if;
  else
   timer <= timer +1;
  end if;
 end if;
end process;
         
-- MAIN ---------------------------------
reset <= pb0;
input <= not switch8;
         
  digit(0) <= "000" & y0;
  digit(1) <= "000" & y1;
  digit(2) <= "0000";
  digit(3) <= "0000";
         
  dot(0) <= '0';
  dot(1) <= '0';
  dot(2) <= '0';
  dot(3) <= '0';
         
-- LED DISPLAY ---------------------------------
  LED: for I in 3 downto 0 generate
    seg(I) <=
          dot(I) & "1000000" when digit(I) = "0000" else
          dot(I) & "1111001" when digit(I) = "0001" else
          dot(I) & "0100100" when digit(I) = "0010" else
          dot(I) & "0110000" when digit(I) = "0011" else
          dot(I) & "0011001" when digit(I) = "0100" else
          dot(I) & "0010010" when digit(I) = "0101" else
          dot(I) & "0000010" when digit(I) = "0110" else
          dot(I) & "1011000" when digit(I) = "0111" else
          dot(I) & "0000000" when digit(I) = "1000" else
          dot(I) & "0010000" when digit(I) = "1001" else
          dot(I) & "0001000" when digit(I) = "1010" else
          dot(I) & "0000011" when digit(I) = "1011" else
          dot(I) & "0100111" when digit(I) = "1100" else
          dot(I) & "0100001" when digit(I) = "1101" else
          dot(I) & "0000110" when digit(I) = "1110" else
          dot(I) & "0001110" ;
  end generate;
end behavior1;
FPGA用に作り直したcounter2のテストベンチファイル「counter2_fpga.vhd」

 

 上記のVHDLファイルでは、「timer」という数をカウントする変数を用意し、それを1つづつインクリメントしています。clock, clk, timerは以下の図のような関係になっています。

 

counter2_fpga.vhdにおけるclock, clk, timerの関係

 

 

Altera MAX+PlusII

 上記のファイルをMAX+PlusII を用いて以下の手順で UP1 ボードにダウンロードし動作させます。

 

状態機械

 前述した2 ビットカウンタ「counter2.vhd」では、状態関数を直接に記述しました。しかし有限オートマトンの状態遷移を状態名で記述し、状態割り当てを自動的に行なわせることもできます。以下に状態遷移を利用した2ビットカウンタ「counterst.vhd」の例を示します。これは4 状態カウンタともいうべきもので、「s0, s1, s2, s3」の値をとる数え挙げ型「state_type」を定義し、「state_type」型の変数「st」に対する代入で状態遷移を表しています。なお状態を表わす変数は整数型などでもよいです。

 

状態遷移を用いた2ビットカウンタ「counterst.vhd」
「counter2.vhd」
-- counterst.vhd
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
         
entity counterst is 
  port (reset, clk, input: in std_logic; 
        y: out std_logic_vector(1 downto 0));
end counterst;
         
architecture behavior1 of counterst is 
  TYPE state_type is (s0, s1, s2, s3);
  signal st: state_type;
         
begin         
  transition: process (clk, reset) begin
    if reset = '0' then 
      st <= s0;
    elsif clk'event and clk = '1' then 
      if (input = '1') then 
        case st is
          when s0 =>
            st <= s1;
          when s1 =>
            st <= s2;
          when s2 =>
            st <= s3;
          when s3 =>
            st <= s0;
        end case;
      end if;
    end if;
  end process;
         
  y <= "00" when st = s0 else 
       "01" when st = s1 else
       "10" when st = s2 else
       "11";
         
end behavior1;
-- counter2.vhd: 2bit counter
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
         
entity counter2 is
  port (reset, clk, input: in std_logic;
        y0, y1: out std_logic);
end counter2;
         
architecture behavior1 of counter2 is
    signal r0, r1 : std_logic := '0';
         
begin
  y0 <= r0;
  y1 <= r1;
  P1: process (clk, reset)
    variable t0, t1 : std_logic;
  begin
    if reset = '0' then
      r1 <= '0';  r0 <= '0';
    elsif clk'event and clk = '1' then
      t0 := r0;  t1 := r1;
      if (input = '1') then
        r0 <= not t0;
        r1 <= ((not t0) and t1) or (t0 and (not t1));
      end if;
    end if;
  end process;
end behavior1;
状態遷移を用いた2ビットカウンタ「counterst.vhd」(左)と「counter2.vhd」(右)

 

 上記のように、状態毎の動作を書くには「case」文が便利です。状態を表わす変数を条件として用い、遷移はこの変数への代入として明示的に記述します。出力は条件付き代入文で記述されていて、この部分は「st」の変化で起動される組合せ回路となります。

 なお複数のプロセス (あるいはプロセス外の代入文) を記述した場合、各プロセスは並列に動作します。ポートやシグナルの参照はどのプロセスでもできますが、前述のように、1つのポートやシグナルへの代入は単一のプロセスに限られます。

 

IEEE1164 ライブラリを用いた記述

 上記の2ビットカウンタをIEEE1164 ライブラリの加算記号「+」を用いて書き直すことも可能です。以下に加算記号を用いて書き直した例「counter2ieee.vhd」を記します。

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
         
entity counter2ieee is
port (reset, clk, input: in std_logic;
      y0, y1: out std_logic);
end counter2ieee;
         
architecture behavior1 of counter2ieee is
signal r : std_logic_vector(1 downto 0) := "00";
begin
y0 <= r(0);
y1 <= r(1);
P1: process (clk, reset)
begin
  if reset = '0' then
    r <= "00";
  elsif clk'event and clk = '1' then
    if (input = '1') then
      r <= r + 1;
    end if;
  end if;
end process;
end behavior1;
加算記号を用いた2ビットカウンタ「counter2ieee.vhd」