2004/09/26 【ソフトウエア編TOPに戻る】
前回の410.VHDLことはじめ(AND回路)では、最も簡単なVHDLプログラムの成り立ちをご紹介しました。今回は、VHDLを記述する上で2種類の違った考え方をご紹介することにしましょう。それはデータフローレベルと動作レベルの2つです。
データフローレベルというのはロジック回路の接続をそのままVHDLで記述する方法です。一方、動作レベルというのは回路図は関係なく入力に対して希望する出力の変化を記述する方法です。場合によって両者を使い分けたり、また両方を組み合わせながら活用していくことになると思います。今回は最も基本的なフリップフロップであるRS(リセット・セット)−FF(フリップフロップ)をモデルにしながら、それぞれでの記述方法を見ていくことにしましょう。
■411−1.RS−FFとは...
先ず始めに、そもそもRS−FF(リセット・セット フリップフロップ)とは何なのかということからおさらいしてみましょう。
下記の図は非同期型のRS−FFです。左側がフリップフロップとしての記号で、右側がその中味の配線です。NOR回路が2回路で構成されています。

動作としては下記のように表されます。
| R | S | 出力Qの状態 |
| 0 | 0 | 出力状態を保持し、変化なし |
| 1 |
0 |
0にリセット |
| 0 |
1 |
1にセット |
| 1 | 1 | 出力確定せず。この組み合わせは禁止入力 |
※出力Qバー(QB)は、出力Qとは常に反転した出力となります。ただし、S、Rともに1の禁止入力状態ではその限りではありません。
RS−FFには、S(セット)入力とR(リセット)入力の2つの入力端子があり、出力としてQとQバーがあります。Rに信号が入力されるとQ出力は0にリセットされ、Sに信号入力があるとQ出力が1になります。このとき、Sの信号入力が無くなってもQ出力の1は保持されます。つまり、S入力に信号が入力されたことを記憶しておくことが出来るというわけで、それがフリップフロップの最大の特徴です。
■411−2.データフローレベルでの記述
それでは、RS−FFをデータフローレベルのVHDLで記述してみましょう。データフローレベルでは、ロジック回路の接続をそのままVHDL上で記述するのでした。なので、上図の右側、NORロジックが2回路接続されている方の回路図と見比べながら進めてみましょう。
ここをクリックするとダウンロードできます。RS_FF1.VHD VHDLプログラムの基本的な成り立ちについては、410.VHDLことはじめ(AND回路)をご参照下さい。
| ------------------------------------------ -- RS_FF1 2004.08.13 -- RIKIYA -- DATAFLOW LEVEL ------------------------------------------ library ieee; use ieee.std_logic_1164.all; entity RS_FF1 is port( R,S : in std_logic; Q,QB : out std_logic); end RS_FF1; ------------------------------------------ architecture RTL of RS_FF1 is signal NODE_Q , NODE_QB : std_logic; begin NODE_Q <= R nor NODE_QB; NODE_QB <= S nor NODE_Q; Q <= NODE_Q; QB <= NODE_QB; end RTL; |
それでは、要点だけ解説致します。
entity RS_FF1 is
port( R,S : in std_logic;
Q,QB : out std_logic);
end RS_FF1;
ここはエンティティ文です。つまり、RS-FF1という器(うつわ)の入出力端子の宣言をしています。
architecture RTL of RS_FF1 is
signal NODE_Q , NODE_QB : std_logic;
begin
NODE_Q <= R nor NODE_QB;
NODE_QB <= S nor NODE_Q;
Q <= NODE_Q;
QB <= NODE_QB;
end RTL;
ここは、アーキテクチャ文です。RS_FF1という器の中の、RTLという名前の動作を記述しています。
signal NODE_Q , NODE_QB : std_logic;
アーキテクチャ文の中にもsignal文が出てきました。これは、エンティティとして外から見える端子ではなく、内部処理を行うために必要なポイントを宣言しています。ちょうど実際の電子回路でいうところのテストポイントみたいなものです。ここではNODE(ノード)と呼ぶことにします。NODEはarchitecture宣言とbeginの間で宣言します。ここでは、NOR1の出力部分としてNODE_Qを、NOR2の出力部分としてNODE_QBを宣言しています。NODEのsignal宣言では、INやOUTなどの信号の入出力種別の定義は行いません。ここでは、NOR1の出力部をNODE_Q、NOR2の出力部をNODE_QBとして使うことにします。

NODE_Q <= R nor NODE_QB;
さて、NODE_Qの値を決定する上記の式ですが、「R端子とNODE_QBのNORをNODE_Qに代入する」と言っています。これは正に回路図上のNOR1に関する接続をそのまま記述しているものですね。
NODE_QB <= S nor NODE_Q;
次も同様です。「S端子とNODE_QのNORをNODE_QB代入する」と言っています。これも回路図上のNOR2に関する接続を記述しているものです。
Q <= NODE_Q;
QB <= NODE_QB;
最後に、各NODEの値をQとQBの端子から出力させます。
このように、回路図の接続のまんま記述していくのがデータフローレベルによるVHDLです。
CPUプログラミングに慣れた方は、ちょっと疑問に思うでしょう。 「NODE_QBが先か、NODE_Qが先か...」「NODE_QBの値が確定する前に、NODE_Q <= R nor NODE_QB;なんていう処理をして良いのか...?」 などなど。 しかし、これが複数同時処理のハードウエアを記述するということなのです。プログラムを上から順番にひとつずつ実行していくCPUプログラミングとは根本的に考え方が違います。この考え方には慣れるしかありません。
ちなみに、NODEを使わずに下記のようなアーキテクチャ文にしてみるとどうなるでしょう?
architecture RTL of RS_FF1 is
begin
Q <= R nor QB;
QB <= S nor Q;
end RTL;
意図する内容は同じですが、上記のVHDLをコンパイルしようとするとエラーになります。出力端子の状態を直接内部処理に利用することはできません。面倒でも、いったんNODEで内部処理用のポイントを作り、それを利用してロジック処理を行う必要があります。
■411−3.動作レベルでの記述 その1 (if文)
次に、RS−FFを動作レベルで記述してみましょう。 動作レベルでは、入力に対する出力の変化の様子を記述するものなので、RS−FFの動作表に注目しながら進めて見ることにしましょう。ここをクリックするとダウンロードできます。RS_FF2.VHD
| ------------------------------------------ -- RS_FF2 2004.08.13 -- RIKIYA -- DOUSA LEVEL if BUN ------------------------------------------ library ieee; use ieee.std_logic_1164.all; entity RS_FF2 is port( R,S : in std_logic; Q,QB : out std_logic); end RS_FF2; ------------------------------------------ architecture RTL of RS_FF2 is begin process(R,S) begin if (R = '1' and S = '0') then -- RESET Q <= '0'; QB <= '1'; elsif (R = '0' and S = '1') then -- SET Q <= '1'; QB <= '0'; elsif (R = '1' and S = '1') then -- KINSHI Q <= '0'; QB <= '0'; end if; end process; end RTL; |
それでは、ここでも要点だけ解説しましょう。
entity RS_FF2 is
port( R,S : in std_logic;
Q,QB
: out std_logic);
end RS_FF2;
上のエンティティ文はエンティティ名が変わっただけで、先ほどのデータフローレベルの場合と同じです。
architecture RTL of RS_FF2 is
begin
process(R,S)
begin
if (R = '1'
and S = '0') then -- RESET
Q
<= '0'; QB <= '1';
elsif (R =
'0' and S = '1') then -- SET
Q
<= '1'; QB <= '0';
elsif (R =
'1' and S = '1') then -- KINSHI
Q
<= '0'; QB <= '0';
end if;
end process;
end RTL;
こちらのアーキテクチャ文では、if文を使っています。「もし入力が〜なら、出力は〜にする」という記述で、ロジックの動作を決定させるためのものです。VHDLは基本的には複数箇所の同時処理を行う記述ですが、if文の中は、あくまで上から順に条件に照らして行き、合致したところの処理を実行します。今回は下記の表をif文で表現することになります。
■RS-FF動作表
| R | S | 出力Qの状態 |
| 0 | 0 | 出力状態を保持し、変化なし |
| 1 |
0 |
0にリセット |
| 0 |
1 |
1にセット |
| 1 | 1 | 出力確定せず。この組み合わせは禁止入力 |
それでは、順番に見ていきましょう。
process(R,S)
process文では、括弧の中に記述している信号(ここではRとS)に変化があったタイミングで、直後のbegin と end process; の間を処理を実行します。今回はRかSの入力信号に変化があった時だけ、その後のif文が実行されることになります。
if (R = '1'
and S = '0') then -- RESET
Q
<= '0'; QB <= '1';
上記のif文は、先ずはじめにRにリセット入力があった場合のことを記述しています。Q出力を0に、QB出力を1にして初期状態にセットさせています。これはRS-FF動作表の2行目の動きを記述しています。
elsif (R =
'0' and S = '1') then -- SET
Q
<= '1'; QB <= '0';
次に、Sにセット入力があった場合のことを記述しています。Q出力を1に、QB出力を0にして、初期状態に対して出力を変化させています。これはRS-FF動作表の3行目の動きを記述しています。elsif文は、直前のif文もしくはelsif文で条件が合致しなかった場合に条件の判定を受け、条件が合致すれば内容が実行されます。C言語などでは
else if と書きますが、VHDLでは elsif なので注意しましょう。
elsif (R =
'1' and S = '1') then -- KINSHI
Q
<= '0'; QB <= '0';
次に、SとRの両方とも1の入力が合った場合。つまり禁止入力状態のことを記述しています。ロジック的には出力が確定しませんが、ここではQ出力、QB出力ともに0になるように設定しています。RS-FF動作表の4行目の動きに相当します。ちなみに、VHDLでは「不定出力」というのもちゃんと記述することができます。Q <= 'X'; QB <= 'X';というように大文字のエックスにすると「不定出力」の扱いになります。ただし、禁止入力とは言え、入力条件によって決まった出力状態が再現されたほうが、実機での動作検証においても有利なので、あえてエックスは使わないことにしました。
さて、プログラム上の記述は以上でおしまいですが、RS-FF動作表の1行目の動き、つまり「出力状態を保持して変化なし」に相当するVHDL記述がありませんね。単純に言ってしまえば、出力状態を変化させないため、記述する必要がない。ということになります。
動作レベルのVHDL記述では、内部のロジック回路がどのような接続になっているのかということは関係ないことがお分かり頂けましたか?入力の状態に対する出力信号の変化がしっかり決まっていれば、回路など分からなくても簡単に実現させることができるということです。
■411−4.動作レベルでの記述 その2 (case文)
ついでにもうひとつの動作レベルを記述してみましょう。今度はif文ではなくcase文です。これも入力に対する出力の変化の様子を記述することには変わりありませんので、RS−FFの動作表に注目しながら進めて見ることにしましょう。ここをクリックするとダウンロードできます。RS_FF3.VHD
| ------------------------------------------ -- RS_FF3 2004.08.13 -- RIKIYA -- DOUSA LEVEL case BUN ------------------------------------------ library ieee; use ieee.std_logic_1164.all; entity RS_FF3 is port( R,S : in std_logic; Q,QB : out std_logic); end RS_FF3; ------------------------------------------ architecture RTL of RS_FF3 is signal R_S : std_logic_vector(1 downto 0); begin R_S <= R & S; -- 2 BIT DATA NI SURU process(R_S) begin case R_S is when "01" => Q <= '1'; QB <= '0'; -- SET when "10" => Q <= '0'; QB <= '1'; -- RESET when "11" => Q <= '0'; QB <= '0'; -- KINSHI when others => null; -- NANI MO SHINAI end case; end process; end RTL; |
ここでも要点だけ解説いたします。
entity RS_FF3 is
port( R,S : in std_logic;
Q,QB
: out std_logic);
end RS_FF3;
エンティティ文は、エンティティ名が変わっただけで、前出までのものと変わりありません。
architecture RTL of RS_FF3 is
signal R_S : std_logic_vector(1 downto 0);
begin
R_S <= R & S; -- 2 BIT DATA NI SURU
process(R_S)
begin
case R_S is
when
"01" => Q <= '1'; QB <= '0'; -- SET
when
"10" => Q <= '0'; QB <= '1'; -- RESET
when "11" => Q <= '0'; QB <= '0'; -- KINSHI
when
others => null; -- NANI MO SHINAI
end case;
end process;
end RTL;
今度のアーキテクチャ文は、case文を使って場合分けによる動作の記述を行っています。先ほどのif文に比べて記述がすっきりしていて、ちょっと見やすい感じを受けます。今回、case文を使うに当たって、ちょっとしたテクニックが利用されています。それは、RとSの2つの入力信号をひとつの2ビットデータにまとめているという点です。それによってcase文での場合分けの判定に利用できるようにしています。
signal R_S : std_logic_vector(1 downto 0);
上記が、内部処理用に準備する2ビットのデータレジスタを宣言しているところです。std_logic_vectorが複数のビットをバス構造としてひとつにまとめることを意味し、(1 downto 0)で左がMSB(上位ビット)、右がLSB(下位ビット)という並びにすることを宣言しています。
R_S <= R & S; -- 2 BIT DATA NI SURU
ここで、先ほど準備した2ビットのR_Sというレジスタに、&によってビットの結合を行ってRとSの入力信号の状態を格納しています。つまり、例えばRが0、Sが1の状態だった場合、R_Sレジスタの中身は"01"になります。
process(R_S)
さて、ここで先ほどの出てきたprocess文です。ここではRもしくはSに変化があるとR_Sレジスタの値も変化するため、process文の中味が実行されることになります。ここではcase文が実行されます。
■RS-FF動作表
| R | S | 出力Qの状態 |
| 0 | 0 | 出力状態を保持し、変化なし |
| 1 |
0 |
0にリセット |
| 0 |
1 |
1にセット |
| 1 | 1 | 出力確定せず。この組み合わせは禁止入力 |
さて、case文の中を、上のRS-FF動作表に照らして見てみることにしましょう。
when
"01" => Q <= '1'; QB <= '0'; -- SET
上のwhenでは、2ビットレジスタR_Sの中味が01だった場合に、Q <= '1'; QB <= '0';が実行されます。つまり、Q出力には1が、QB出力には0が出力されるので、これは動作表の3行目であるセット動作を行うということになります。
when
"10" => Q <= '0'; QB <= '1'; -- RESET
次のwhenでは、R_Sの中味が10だった場合に、Q <= '0'; QB <= '1';が実行されます。つまり、Q出力には0が、QB出力には1が出力されるので、これは動作表の2行目であるリセット動作を行うということになります。
when
"11" => Q <= '0'; QB <= '0'; -- KINSHI
その次のwhenでは、R_Sの中味が11だった場合に、Q <= '0'; QB <= '0';が実行されます。つまり、Q出力とQB出力には0が出力されるので、これは動作表の4行目である禁止入力時の動作を行うということになります。
when
others => null; -- NANI MO SHINAI
さて、その次は他とちょっと違っていますが、これは「それ以外の場合」ということでnull;、つまり何もしないということで、出力状態を変化させずに保持するという動作になるのです。
if文を使うか、case文を使うかについては組みたいロジックによって使い分ければ良いでしょう。今回のRS-FFではどちらを使ってもOKでしたが、処理の内容によってはif文でないと実現できないロジックなどもあります。
データフローレベルと動作レベルについて、お分かり頂けたでしょうか。電子回路になじみの薄い方にとっては、動作レベルのほうが親しみやすいかもしれません。また、この先ちょっと複雑な処理をさせようとした場合、内部回路や論理式を全て明らかにしてデータフローレベルだけで構築するよりも、入力条件によって変化する出力状態をきちんと決めて、動作レベルで記述したほうが分かりやすいプログラムが組めるような気がします。 これも場合によっての使い分けや、組み合わせて使うといったことが必要になると思います。