2004/11/5 【ソフトウエア編TOPに戻る】
前回まではフリップフロップの基本動作についてのVHDLを勉強してきましたが、今回は少しフリップフロップから離れてエンコーダ(ENC)回路とデコーダ(DEC)回路について見てみることにしましょう。エンコーダとは「コード化する」、デコーダとは「コードから復元する」といった意味になるでしょうか。コード化とは「ある一定の約束にしたがったデータ変換」といったところです。コード化といってもその具体的な内容や方式は様々で非常に広範囲に用いられる言葉です。ENC回路、DEC回路は通常、リアルタイムなデータ変換を行います。したがってフリップフロップを通さず、NAND回路やNOR回路などのロジックの組み合わせだけで構築するのが普通でしょう。
今回はCPUのアドレス制御などに用いられるアドレスデコーダの事例と、BCD(2進バイナリコード)−ENC/DEC回路についてご紹介して見ることにしましょう。CPLDが実際に活躍するのは、これらのENC/DEC回路としての用途が一番多いかもしれません。
414−1.アドレスデコーダ回路
アドレスデコーダ回路とは、CPUなどから特定の周辺デバイスを選択してアクセスさせたい場合、周辺デバイスを有効にするCS(チップセレクト)端子をアクティブにするための回路です。ここで、下記のようなCPUと周辺デバイスを事例として考えて見ましょう。上側の図がCPUと周辺デバイスのアドレス信号の接続で、下側の図がアドレスマップです。ここでは、8ビットのアドレス空間を持つCPUと、内部に4個の制御レジスタを持つ周辺デバイスであると仮定しています。CPUからは、周辺デバイスに対してなんらかの制御を行うため、周辺デバイス内の4個の制御レジスタに自由にアクセスさせたいと思います。

先ず上の回路図を見てみましょう。CPUのアドレスバスは8ビットです。このうちのA0とA1は直接周辺デバイスのA0とA1に接続しており、A2〜A7は何やらNOT回路とAND回路を経由してから周辺デバイスのCS端子に接続されています。ここで、CS端子が1(アクティブ)になるためにはどういう条件が必要かを考えて見ましょう。上の回路図をみて頂ければ分かりますね? AND回路は全ての入力端子が1の場合だけ出力が1になるのでした。そして、その入力にはNOT回路を経由している信号もあります。つまり、以下の条件の時に、CS信号は1(アクティブ)になります。
A7,A6,A5,A4,A3,A2,A1,A0 = 1100 00−−
A1とA0を−−としているのは、チップセレクトをアクティブにするための条件として関係ないことを示しています。つまり上記の回路の場合、CPUから見てアドレスが
1100 0000 (COh)から1100 0011(C3h)までの間チップセレクトがアクティブになり、これがデバイスにアクセスするためのアドレス空間になります。分かりますか? その様子を示しているのが回路図の下のアドレスマップになります。CPUのアドレスバスは8ビットなので、全アドレス空間はFFh(0を入れて256番地分)になります。そのうちのC0h〜C3h(192〜195番地)が、この周辺デバイスにアクセスするためのアドレスになります。
で、この時のNOT回路とAND回路の組み合わせのことをアドレスデコーダ回路と呼んでいます。
ところで、「なんでこれがデコーダなのだ?」と思われるかもしれませんね。もともと、CPUのアドレス信号自体が沢山のアドレス指定を行うためのコードであると考えることができます。例えば上の事例で言うと、CPUから外部に対して256番地分の指定ができれば良いのですから、一番単純な方法としては256本の信号線を出してあげて、アクセスしたいアドレスの信号線1本だけを1(アクティブ)にしてあげればよいことになります。でも考えて見て下さい。そんなに沢山の端子を出すよりも、アドレスを2進数で数えてあげる(コード化する)ことによって、たったの8本の端子で済んでしまうのです。つまりCPUのパッケージも小さくて済み、値段も安くなることでしょう。ここにコード化することの意味があります。それよりも、仮に24ビット幅のアドレス空間をコード化しなかった場合1600万本以上の端子が必要になってしまい、既に現実的ではありませんね。アドレスデコーダ回路は、このコード化されたアドレスバスの信号から、本来必要なアドレス指定用のチップセレクト信号1本を取り出してあげる役目を果たします。これは立派なデコーダですよね。(^^
414−1−1.アドレスデコーダ回路の記述 その1 (データフローレベル)
では、アドレスデコーダをVHDLで記述してみましょう。前項でご紹介した回路図のアドレスデコーダ部分だけを取り出し、VHDLを記述するため準備したのが下記のブロック図です。 もう特に説明は必要ありませんね。上の回路図そのまんまです。ただし、A1,A0はデコーダ回路には関係ないため、ブロック図にも含まれていません。

実はこれに似たような回路は既にご紹介していて、410.VHDLことはじめ(AND回路)のページでもう少しだけシンプルな事例を扱っていました。今回も基本的にはそれと同じです。 VHDLプログラムはここをクリックするとダウンロードできます。ADR_DEC1.VHD
| ------------------------------------------- --ADR_DEC1 2004.10.11 -- RIKIYA ------------------------------------------- library ieee; use ieee.std_logic_1164.all; ------------------------------------------- entity ADR_DEC1 is port( A2,A3,A4,A5,A6,A7 : in std_logic; CS_OUT : out std_logic ); end ADR_DEC1; ------------------------------------------- architecture RTL of ADR_DEC1 is begin CS_OUT <= (not A2) and (not A3) and (not A4) and (not A5) and A6 and A7; end RTL; |
では、簡単に要点だけをご説明しましょう。
entity ADR_DEC1 is
port( A2,A3,A4,A5,A6,A7 : in std_logic;
CS_OUT
: out std_logic );
end ADR_DEC1;
このエンティティ文では、上記のブロック図の通りの入出力端子を宣言しているだけです。
CS_OUT <= (not A2)
and (not A3)
and (not A4)
and (not A5)
and A6
and A7;
一行で書くと、CS_OUT <= (not A2) and (not A3) and (not A4) and (not A5) and
A6 and A7; です。
アーキテクチャ文のこの部分が、アドレスデコーダ回路の動作を記述している部分です。全てandで接続されていますが、A2,A3,A4,A5にはnotが付いています。これは回路図のまんまですね。つまりこれはデータフローレベルのVHDL記述と言えます。
さて、今回の事例ではアドレス線を6本分しか扱わないため簡単な記述で済んでいますが、これが20〜30本といった本数になってくると、さすがにVHDLの記述も煩雑になってきそうです。そんなときに、もっとすっきりとしたプログラムを書きたいものですね。そこで出てくるのが、動作レベルでのVHDL記述です。
414−1−2.アドレスデコーダ回路の記述 その2 (動作レベル)
今回も同じ回路をVHDLで記述してあげますが、ちょっと考え方が変わります。今回は「どんなときに出力を1にしたいか?」という動作に着目した記述になります。今回の例で言うと、A7,A6,A5,A4,A3,A2 = 1100 00 という条件が揃ったときだけ出力を1にしたいのでした。ブロック図でいうと以下のようになります。

では、このプログラムを見てみましょう。ここをクリックするとダウンロードできます。ADR_DEC2.VHD
| ---------------------------------------------------- --ADR_DEC2 2004.10.11 -- RIKIYA ---------------------------------------------------- library ieee; use ieee.std_logic_1164.all; entity ADR_DEC2 is port( ADR_IN : in std_logic_vector(5 downto 0); CS_OUT : out std_logic ); end ADR_DEC2; ---------------------------------------------------- architecture RTL of ADR_DEC2 is begin process(ADR_IN) begin if (ADR_IN = "110000") then CS_OUT <= '1'; else CS_OUT <= '0'; end if; end process; end RTL; |
ここでも要点だけをご説明しましょう。
entity ADR_DEC2 is
port( ADR_IN : in std_logic_vector(5 downto 0);
CS_OUT : out std_logic );
end ADR_DEC2;
今回のエンティティ文では、前回のように入力端子を1個ずつ宣言するのではなく、in std_logic_vector(5 downto 0);というように6ビット幅のバスとして宣言しています。CS_OUTは前回と変わらずです。
process(ADR_IN)
begin
if (ADR_IN = "110000") then
CS_OUT <= '1';
else
CS_OUT <= '0';
end if;
end process;
次に、アーキテクチャ文の中では、if文を使って「6ビット幅のADR_INという入力データが110000だったら、CS_OUTを1にしろ」「そうでなければCS_OUTを0にしろ」という動作を行わせます。そして、if文を実行させるタイミングとして、 process(ADR_IN) begin 〜 end process;によって「ADR_INに変化が起きた時」ということを指定しています。
前出のデータフローレベルでの記述と比べてどうでしょう。動作的に見て、どちらが分かりやすいでしたか?
414−2.BCD−ENC回路(2進バイナリコードへの変換回路)
BCD−ENC(BCDエンコーダ)回路とは、例えば10ビットの入力端子があったとき、1ビット目だけが"1"の時に2進数で1を、2ビット目だけが"1"の時に2進数で2を出力するようなエンコーダです。つまり、1から10まで番号が付いた押ボタンをどれかひとつだけ押すと、その番号に相当した2進数を出力してくれるような回路であると言えます。
真理値表で表すと以下のようになります。
| 入力 | 出力 | |||||||||
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | BCD4ビット |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 0 0 0 |
| 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 0 0 1 |
| 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 0 1 0 |
| 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 0 1 1 |
| 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 1 0 0 |
| 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 1 0 1 |
| 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 1 1 0 |
| 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 1 1 1 |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 0 0 0 |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 0 0 1 |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 0 1 0 |
VHDL設計のためのブロック図としては、以下のようになります。ただし、ここでは実際のテスト回路の都合から、入力のビット数を8ビットまでに制限してみました。

では、このプログラムを見てみましょう。ここをクリックするとダウンロードできます。BCD_ENC1.VHD
| ------------------------------------------------- -- BCD_ENC1 2004.10.11 -- RIKIYA ------------------------------------------------- library ieee; use ieee.std_logic_1164.all; entity BCD_ENC1 is port( IN_DATA : in std_logic_vector(7 downto 0); OUT_DATA : out std_logic_vector(3 downto 0) ); end BCD_ENC1; ------------------------------------------------- architecture RTL of BCD_ENC1 is begin process(IN_DATA) begin case IN_DATA is when "00000000" => OUT_DATA <= "0000"; when "00000001" => OUT_DATA <= "0001"; when "00000010" => OUT_DATA <= "0010"; when "00000100" => OUT_DATA <= "0011"; when "00001000" => OUT_DATA <= "0100"; when "00010000" => OUT_DATA <= "0101"; when "00100000" => OUT_DATA <= "0110"; when "01000000" => OUT_DATA <= "0111"; when "10000000" => OUT_DATA <= "1000"; when others => OUT_DATA <= "1111"; end case; end process; end RTL; |
さて、これはもう解説の必要もないでしょう。case文によって、真理値表そのままの各場合に相当する出力データのパターンを記述しているだけです。ひとつだけ真理値表にない記述があります。それは when others => OUT_DATA <= "1111"; ですね。真理値表に記載されている以外の入力信号の組み合わせがあった場合、出力データを全て1にするための記述です。
414−3.BCD−DEC回路(2進バイナリコードからの復調回路)
次はBCD−DEC(BCDデコーダ)回路を見てみましょう。今度は前項のBCDエンコーダと逆の動作になります。つまり、4ビットの2進数データをもとに、10ビットの特定のビットだけをアクティブにします。2進数データが入力されると、1から10まで番号が付けられたランプのうち、そのデータが示す数に対応したランプを点灯させてくれるような回路であると言えます。
真理値表で表すと以下のようになります。
| 入力 | 出力 | |||||||||
| BCD4ビット | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 0 0 0 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 0 0 0 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 0 0 1 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 0 0 1 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 0 1 0 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
| 0 1 0 1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
| 0 1 1 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
| 0 1 1 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
| 1 0 0 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
| 1 0 0 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
| 1 0 1 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
VHDL設計のためのブロック図としては、以下のようになります。ただし、ここでも実際のテスト回路の都合から、出力のビット数を8ビットまでに制限してみました。

では、このプログラムを見てみましょう。ここをクリックするとダウンロードできます。BCD_DEC1.VHD
| ------------------------------------------------- -- BCD_DEC1 2004.10.11 -- RIKIYA ------------------------------------------------- library ieee; use ieee.std_logic_1164.all; entity BCD_DEC1 is port( IN_DATA : in std_logic_vector(3 downto 0); OUT_DATA : out std_logic_vector(7 downto 0) ); end BCD_DEC1; ------------------------------------------------- architecture RTL of BCD_DEC1 is begin process(IN_DATA) begin case IN_DATA is when "0000" => OUT_DATA <= "00000000"; when "0001" => OUT_DATA <= "00000001"; when "0010" => OUT_DATA <= "00000010"; when "0011" => OUT_DATA <= "00000100"; when "0100" => OUT_DATA <= "00001000"; when "0101" => OUT_DATA <= "00010000"; when "0110" => OUT_DATA <= "00100000"; when "0111" => OUT_DATA <= "01000000"; when "1000" => OUT_DATA <= "10000000"; when others => OUT_DATA <= "11111111"; end case; end process; end RTL; |
さて、これも解説の必要はないでしょう。ここで、前項のBCD_ENC1のVHDLのcase文と見比べて見て下さい。データの内容がお互いに左右で入れ替わっているだけですね。これだけでエンコーダとして動くか、デコーダとして動くかが決定されています。つまり、BCDエンコーダ/デコーダとしての変換機能に限らず、もっと好き勝手なデータ変換機能を、このcase文によって作り出すことができるのです。 たとえば、電卓数字としてなじみ深い7セグメントLEDを点灯させるためのデータ変換なども、この延長線上で簡単に動作させることができます。
414−4.優先順位付きBCD−ENC回路(プライオリティ動作)
さて、もう一度BCD−ENC回路の登場です。しかし、こんどの動作は前項のものとはちがいます。先ずは真理値表を見て下さい。
| 入力 | 出力 | |||||||||
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | BCD4ビット |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 0 0 0 |
| 1 | - | - | - | - | - | - | - | - | - | 0 0 0 1 |
| 0 | 1 | - | - | - | - | - | - | - | - | 0 0 1 0 |
| 0 | 0 | 1 | - | - | - | - | - | - | - | 0 0 1 1 |
| 0 | 0 | 0 | 1 | - | - | - | - | - | - | 0 1 0 0 |
| 0 | 0 | 0 | 0 | 1 | - | - | - | - | - | 0 1 0 1 |
| 0 | 0 | 0 | 0 | 0 | 1 | - | - | - | - | 0 1 1 0 |
| 0 | 0 | 0 | 0 | 0 | 0 | 1 | - | - | - | 0 1 1 1 |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | - | - | 1 0 0 0 |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | - | 1 0 0 1 |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 0 1 0 |
表中の−は1でも0でも関係ないという意味です。前項の真理値表と何がちがうか分かりますか?
例えば、前項のBCD-ENC1では1番目と3番目の入力が同時に1だった場合、出力は想定されるパターン以外ということで1111にしてしまっていました。つまり入力が無効ということです。では、今回のBCD-ENC2ではどうかというと、1番目と3番目の入力が同時に1だった場合は、1番目の入力が優先的に認識されて、0001を出力します。3番目と5番目だったら3番目が優先です。つまり、若い番号のほうがより優先順位が高いということになります。このような優先順位付きの動作をプライオリティと呼びます。
このようなプライオリティ動作をcase文のVHDLで記述しようとすると、各入力の組み合わせを全て記述しなければならず、実質上不可能です。そこで、このような場合にはif文のVHDLで記述すると簡単に表現することができます。
では、このプログラムを見てみましょう。ここをクリックするとダウンロードできます。BCD_ENC2.VHD
| ------------------------------------------------- -- BCD_ENC2 2004.10.11 -- RIKIYA ------------------------------------------------- library ieee; use ieee.std_logic_1164.all; entity BCD_ENC2 is port( IN_DATA : in std_logic_vector(7 downto 0); OUT_DATA : out std_logic_vector(3 downto 0) ); end BCD_ENC2; ------------------------------------------------- architecture RTL of BCD_ENC2 is begin process(IN_DATA) begin if (IN_DATA(0) = '1') then OUT_DATA <= "0001"; elsif (IN_DATA(1) = '1') then OUT_DATA <= "0010"; elsif (IN_DATA(2) = '1') then OUT_DATA <= "0011"; elsif (IN_DATA(3) = '1') then OUT_DATA <= "0100"; elsif (IN_DATA(4) = '1') then OUT_DATA <= "0101"; elsif (IN_DATA(5) = '1') then OUT_DATA <= "0110"; elsif (IN_DATA(6) = '1') then OUT_DATA <= "0111"; elsif (IN_DATA(7) = '1') then OUT_DATA <= "1000"; else OUT_DATA <= "0000"; end if; end process; end RTL; |
では、要点だけを簡単に解説しましょう。
if (IN_DATA(0) = '1') then OUT_DATA <= "0001";
これは、「IN_DATAの0ビット目が1だったら、OUT_DATA を 0001 にしろ」という意味です。つまり、この時点で条件に合致した場合は1ビット目以降の入力データを見ません。どんな入力だろうが関係ないのです。
elsif (IN_DATA(1) = '1') then OUT_DATA <= "0010";
これは、「もし違う場合、IN_DATAの1ビット目が1だったら、OUT_DATA を 0010 にしろ」という意味です。以下同様に下の行に行くほど優先順位が低くなっていきます。
else OUT_DATA <= "0000";
最後のこれは、「どれにも合致しなかった場合、OUT_DATA を 0000 にしろ」という意味で、これは入力が全て0の場合に相当します。
VHDLは全てを複数箇所で同時処理ということが前提ですが、if文の場合はこのように、上から下に条件による判断が働いて処理に優先順位が付くのです。
今回はENC/DEC回路によるデータ変換について簡単にご紹介してみました。この他にもパリティビットを付加する処理なども簡単に行うことができますし、アイデア次第でもっと複雑な変換も簡単に実現させてしまうことができます。これらの動作を汎用のロジックICを組み合わせて作っていた時代に比べると、デバイスの数、基板上必要とする面積、配線の手間、動作検証、仕様変更の簡便さなど、全てにおいて非常に効率が良くなっています。是非活用してみて下さい。