library ieee; use ieee.std_logic_1164.all; entity dec is port ( a: in std_logic_vector(2 downto 0); x: out std_logic_vector(7 downto 0) ); end dec; architecture arch of dec is begin process (a) begin case a is when "000" => x <= "00000001"; when "001" => x <= "00000010"; when "010" => x <= "00000100"; when "011" => x <= "00001000"; when "100" => x <= "00010000"; when "101" => x <= "00100000"; when "110" => x <= "01000000"; when "111" => x <= "10000000"; when others => x <= "XXXXXXXX"; end case; end process; end arch;この中では、architecture記述の中で、プロセス文(process)[p.36]というものを 使っています。 このプロセス文は、非常によく使うものですので、ぜひ覚えておきましょう。 プロセス文は、カッコ内に書いた変数(センシティビティ・リスト、と呼ぶ)の 値が変化したときに、その中が実行されます。 このデコーダの場合は、出力xが変化するのは、変数aが変化したとき、ですから、 aをセンシティビティ・リストに入れてあります。
この場合に実行される内容は、aの値に応じて出力xの値を変える、 ということですので、それをcase文[p.43]を用いて記述しています。 VHDLのcase文はC言語などのcase文と似ていて、aの値がwhenで指定した値に 応じて、「=>」の右側の処理(値の代入)が行われます。 例えばa="001"の場合は、x(1)のみが1の、x="00000010"としています。 ちなみに、入力が"000"〜"111"の場合以外は、不定値である"X"を 代入しています。
このようにして入力が3ビットのデコーダは記述できるわけですが、 この調子でいけば、何ビットのデコーダでも、同じように記述できそうです。 これが、HDLを用いた論理回路設計の特徴といえます。 従来の論理回路の設計手法では、入力が3ビットのデコーダの論理回路図[p.63]を 設計したとしても、入力が4ビットのデコーダが必要になったら、 またゼロから作り直しになってしまいます。
library ieee; use ieee.std_logic_1164.all; entity enc is port ( a: in std_logic_vector(7 downto 0); x: out std_logic_vector(3 downto 0) ); end enc; architecture arch of enc is begin process (a) begin if (a(0) = '1') then x <= "1000"; elsif (a(1) = '1') then x <= "1001"; elsif (a(2) = '1') then x <= "1010"; elsif (a(3) = '1') then x <= "1011"; elsif (a(4) = '1') then x <= "1100"; elsif (a(5) = '1') then x <= "1101"; elsif (a(6) = '1') then x <= "1110"; elsif (a(7) = '1') then x <= "1111"; else x <= "0000"; end if; end process; end arch;この例では、プロセス文の中で、if文[p.42]を使って、出力の値を 決定しています。 具体的には、a(0)から順番に1があるかを探していき、 あったところでxを決定しています。
このエンコーダの論理回路図は[p.65]の図のようになりますが、 ビット数を変える場合でも、VHDL記述であれば楽チンですね。
library ieee; use ieee.std_logic_1164.all; entity sel is port ( a, b, c, d: in std_logic; s: in std_logic_vector(1 downto 0); x: out std_logic ); end sel; architecture arch of sel is begin process (a, b, c, d, s) begin case s is when "00" => x <= a; when "01" => x <= b; when "10" => x <= c; when "11" => x <= d; when others => x <= 'X'; end case; end process; end arch;case文が使われていますね。 ここでプロセス文のセンシティビティ・リストに、a, b, c, dに加えて sも入っていることに注意しておきましょう。 (その理由を考えておきましょう)
library ieee; use ieee.std_logic_1164.all; entity cmp is port ( a, b: in std_logic_vector(7 downto 0); gt, lt, eq: out std_logic; ); end cmp; architecture arch of cmp is begin process (a, b) begin gt <= '0'; lt <= '0'; eq <= '0'; if (a > b) then gt <= '1'; elsif (a < b) then lt <= '1'; else eq <= '1'; end if end proess; end arch;この例では、まずgt, lt, eqを最初に0にしておき、 その後、該当するもののみを1にする、という処理を書いてあります。 この場合、プログラミング言語の場合は、gtなどの変数がまず0になり、 その後1になるわけですが、VHDLで記述しているのは回路ですので、 実際に、まずgtが0になったあとで1になる、ということは起こらず、 結果として(この場合はプロセス文が終わった時点で)gtの値が どうなっているか、だけが意味を持つことに注意しておきましょう。
library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_arith.all; use ieee.std_logic_unsigned.all; entity add is port ( a, b: in std_logic_vector(7 downto 0); ci: in std_logic; co: out std_logic; x: out std_logic_vector(7 downto 0) ); end add; architecture arch of add is begin process (a, b, ci) variable tmp: std_logic_vector(8 downto 0); begin tmp := ("0" & a) + ("0" & b) + ("00000000" & ci); co <= tmp(8); x <= tmp(7 downto 0); end process; end arch;第2回の最後のほうで、4ビット加算器の 動作記述の例をみていましたが、それとほぼ同様です。 (ちなみにこの例では、記述の仕方は「加算を行う」ということを 書いている動作記述なのに、architecture名がarchになっています。 実はarchitecture名は、必ずしも記述内容と一致する必要はなく、 自分でわかる名前(文字列)でかまいません。 ただ、この例では、「加算器の動作記述」ですので、 本当は"architecture behv of add is..."と書いておいたほうが、 あとあと混乱しにくいとは思います。)
この調子でいけば、何ビットの加算器でもかけそうですね。 ちなみに、加算結果を途中で入れている変数tmpは、vector形式で 8 downto 0、つまり9ビットになっています。 この理由を考えてみましょう。
※教科書のVHDL記述には書いてありませんが、 演習で使っているXilinx ISEでは、 最初の方のおまじないのところに、 use ieee.std_logic_unsigned.all; が必要なようです。
library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_arith.all; entity shift is port ( a: in std_logic_vector(7 downto 0); d: in std_logic; n: in_std_logic_vector(2 downto 0); x: out std_logic_vector(7 downto 0); ); end shift; architecture arch of shift is begin process (a, d, n) begin if (d = '1') then x <= sh_left(a, to_integer(n)); else x <= sh_right(a, to_integer(n)); end if; end process; end arch;この例では、sh_left, sh_rightというものが使われていますが、 これは使っているライブラリであるieee.std_logic_arithで 定義されているもので、それぞれ指定したビット数だけ 左、右へシフトする、というものです。 こういうのが最初からあると便利ですね。 シフトするビット数は、整数型で指定する必要があるので、 これもライブラリの中で定義されているto_integerを使っています。 ちなみにシフトするビット数nが3ビットである理由を考えてみましょう。
※このsh_left, sh_rightは、演習で用いているXilinx ISEでは 使用できないようです。 いまのところ、Xilinx ISEでは、指定したビット数だけシフトする関数は ないようです・・・(そんなはずはないような気がするのですが・・・) このシフタは、VHDL記述の例としてだけ見ておいてください。
library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_arith.all; entity mult is port ( a, b: in std_logic_vector(7 downto 0); x: out std_logic_vector(15 downto 0); ); end mult; architecture arch of mult is begin process (a, b) begin x <= a * b; end process; end arch;ここから、複雑な乗算器の実際の論理回路は、 論理合成ツールが自動的に作ってくれるわけです。
library ieee; use ieee.std_logic_1164.all; entity parity is port ( a: in std_logic_vector(7 downto 0); x: out std_logic; ); end parity; architecture arch of parity is -- bit reduction xor function br_xor(a: std_logic_vector) return std_logic is variable tmp:std_logic := '0'; begin for i in a'range loop tmp := tmp xor a(i); end loop return(tmp); end br_xor; begin process (a) begin x <= br_xor(a); end_process; end arch;ちょっとこれは複雑な記述です。 まず、VHDLでは、任意のビット数のXOR(排他的論理和)を求める 演算子がないので、関数br_xorを作っています。 XORを求める数値のビット数が決まっているのであれば、
x <= a(0) xor a(1) xor ...のように羅列をしてもいいのですが、せっかくですので、 何ビットの数値でもXORを求められる関数br_xorをfunction以下で定義しています。 (ちなみに「--」以下はコメント) ここでは、配列aの全ビットの添え字(a'range)に対して、 それまでのXOR結果が入っているtmpと、当該ビットa(i)とのXORを 順番に求め、最後にtmpを返す、としています。
そして回路parityの実体のところでは、この関数br_xor()を呼んでいるだけ、 です。 このようにVHDLでは、「関数」としてfor文を書いても、 実際には1ビットずつXORを順番に求めていくのではなく、 あくまでも作られる論理回路としては、[p.78]の図のように XORゲートが並んでいて、一気に結果が求められる回路、となることに 注意しましょう。 (つまりクロックごとに1ビットずつXORを求める、という回路には 通常はなりません) つまり、あくまでもVHDLで記述しているのは、「回路の機能」であり、 「回路の実際の動作」ではない、ということに注意しておきましょう。