ここでは、順序回路の1つである「2ビット同期カウンタ 」を例にして、EDAツールによる回路設計手順を復習し、かつ「クロック」の概念を学びます。2ビット同期カウンタでは、入力に応じて2桁の出力が「00→01→10→11→00‥‥」と変化します。この回路をVHDLで記述し、回路設計を行います。EDAツールによる設計は組合わせ回路と同様、以下の手順を経て行われます。
まずはテキストに書かれている通りに実行して、設計手順を復習して下さい。細かな手順は省略してあるので、組合せ回路のホームページを見て下さい。
第1回目で行ったように、まずは環境設定を行って下さい。
第1回目と同様に、ここでもVHDLによる回路動作の記述はmuleなどのエディタ上で行います。以下は「2ビット同期カウンタ回路」のVHDL記述ファイル「counter2.vhd」を示しています。
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 ビットの記憶素子に対応するシグナルを 「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; |
上記の回路が正しく動作するかどうかを調べるために、アナライズとシミュレーションをしてみましょう。手順は第1回目と同様です。また環境設定を変更する必要はありません。ただし、もしディレクトリを変えている場合は、カレントディレクトリに設定ファイル「.synopsys_vss.setup」を用意してください。なお前回と同様、「mkdir work」で「work」というディレクトリを作成しておく必要があることに注意して下さい。また最終行に改行を入れることを忘れないでください。
「vhdlan [ファイル名]」 でVHDL記述ファイルをシミュレーションのための中間ファイルに変換します。本実験では、「counter2.vhd」と「test-counter2.vhd」についてアナライズを行います。
vhdlan counter2.vhd |
test_counter2.vhdのアナライズ
vhdlan test_counter2.vhd
「vhdldbx」で起動します。
シミュレータの最下行に必要なコマンド入力をして、回路が正常に動作しているかを評価して下さい。
「run [時間]」でシミュレーションを実行します。
シミュレーションによって、回路が正常に動作することを確認したら、次は「論理合成」によって回路を最適化します。もしディレクトリを新たに作っているのであれば、カレントディレクトリに設定ファイル「.synopsys_dc.setup」を用意します。最終行に改行を入れることを忘れないでください。
「design_analyzer」で起動します。
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; |
上記のVHDLファイルでは、「timer」という数をカウントする変数を用意し、それを1つづつインクリメントしています。clock, clk, timerは以下の図のような関係になっています。
counter2_fpga.vhdにおけるclock, clk, timerの関係
上記のファイルをMAX+PlusII を用いて以下の手順で UP1 ボードにダウンロードし動作させます。
前述した2 ビットカウンタ「counter2.vhd」では、状態関数を直接に記述しました。しかし有限オートマトンの状態遷移を状態名で記述し、状態割り当てを自動的に行なわせることもできます。以下に状態遷移を利用した2ビットカウンタ「counterst.vhd」の例を示します。これは4 状態カウンタともいうべきもので、「s0, s1, s2, s3」の値をとる数え挙げ型「state_type」を定義し、「state_type」型の変数「st」に対する代入で状態遷移を表しています。なお状態を表わす変数は整数型などでもよいです。
|
|
-- 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; |
上記のように、状態毎の動作を書くには「case」文が便利です。状態を表わす変数を条件として用い、遷移はこの変数への代入として明示的に記述します。出力は条件付き代入文で記述されていて、この部分は「st」の変化で起動される組合せ回路となります。
なお複数のプロセス (あるいはプロセス外の代入文) を記述した場合、各プロセスは並列に動作します。ポートやシグナルの参照はどのプロセスでもできますが、前述のように、1つのポートやシグナルへの代入は単一のプロセスに限られます。
上記の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; |