この方法の問題は、どうしてもミスが入りやすく、またミスを見つけにくい、 ということです。 これは、特に作りたい回路が大きくなると、顕著な問題となります。
これを解決するために考えられたのが、 作りたい回路を言語で書こう、という方法で、 そのための言語をハードウエア記述言語 (Hardware Description Language; HDL)と呼びます。 具体例は後ほど見ることにしますが、 HDLを用いた論理回路の設計では、単体の論理ゲートのことや カルノー図による簡略化のことは気にする必要はありません。 その分、「どういう動作をさせるのか」に集中することができます。 設計した後は、やはり同じようにテストパタンを与えて シミュレーションをするわけですが、 仮に出力が意図したものと異なる場合でも、 もともと「こういう動作をする回路」ということを記述して 回路を設計していますので、比較的ミスを見つけやすい、という 大きな特長があります。
ただしHDLで記述したものは、あくまでも、回路の動作、ですので、 最終的には、それを「論理合成」と呼ばれる過程を経て、 論理ゲートからなる論理回路へと変換する必要があります。 しかし、この論理合成は、コンピュータ上のソフトウエアが ほとんど自動的にやってくれます。 論理合成は、ブール代数に基づいて、動作の記述を 論理回路へと機械的に変換していく作業ですが、 作業量自体はとても多いものです。 しかしこのような、単純だけど機械的で分量が多い作業は、 人間よりもコンピュータは非常に得意ですので、 人間が手動でやるよりも、はるかに間違いが少なく、 正しい論理回路へと変換してくれます。
これが、HDLを用いた論理回路の設計の流れと特長になります。
library ieee; use ieee.std_logic_1164.all; entity half_adder is port ( a, b : in std_logic; s, co : out std_logic); end half_adder; architecture arch of half_adder is signal w0, w1, w2: std_logic; begin w0 <= a and b; w1 <= not w0; w2 <= a or b; s <= w1 and w2; co <= w0; end arch;これは、半加算器をVHDLで記述したものです。 (半加算器は、2つの1桁の2進数(つまり0か1)の加算をする回路のことで、 加算結果がs、桁上がりがc0となります。)
順番に見ていくことにしましょう。 最初の2行は、「おまじない」と思ってください。
続いて、エンティティ(entity)を記述してある部分があり、 その後に、アーキテクチャ(architecture)を記述している部分があります。 VHDLで論理回路を記述するときは、 このエンティティとアーキテクチャの両方を書く必要があります。
エンティティの記述部分では、いまから記述しようとしている回路の 入力と出力(「ポート」と呼びます)を記述します。 書き方のルールは、この例に従うことにして、 この場合は、aとbという名前の2つの入力と、 sとcoという名前の2つの出力があることになります。
続くアーキテクチャの記述では、それらの入力と出力の関係、 つまり、作ろうとしている論理回路の中身について記述します。 この例では、まずw0, w1, w2という名前の信号を使うことを宣言して、 続いて、w0, w1, w2と入力・出力との関係を記述しています。 ブール代数の基本になる論理積、論理和、否定が、それぞれ and, or, notとなっています。
ここで、論理式の簡略化にはまったく気を使う必要はなく、 ただ作ろうしている回路を論理式で書いているだけ、といことに 注意しましょう。 つまり、論理回路の設計をしているのですが、 論理回路図から書くのに比べると、ミスが入りにくくなっています。 またあとから修正するのも楽そうです。
library ieee; use ieee.std_logic_1164.all; entity full_adder is port ( a, b, ci: in std_logic; s, co: out std_logic); end full_adder; architecture arch of full_adder is component half_adder port ( a, b: in std_logic; s, co: out std_logic); end component; signal w0, w1, w2: std_logic; begin co <= w1 or w2; i0: half_adder port map (co=>w1, s=>w0, a=>a, b=>b); i1: half_adder port map (co=>w2, s=>s, a=>w0, b=>ci); end arch;この例では、アーキテクチャ記述の最初で、 部品として使う半加算器の入出力を、component文の中に記述しています。 もちろん、この半加算器の「実体」は、別のところに記述しておく 必要があります。 しかし、このように、「半加算器そのもの」と「半加算器を使うところ」を 分けることで、論理回路の設計が、ずっと見通しのよいものになります。
アーキテクチャ記述の中では、2個の半加算器half_adderに、 どのように信号をつなぐか、を記述しています。 この中の半加算器のように、あるアーキテクチャ記述の中で 使われている他の論理回路のことを「インスタンス」と呼びます。 この例では、i0, i1という名前の2個の半加算器をインスタンスとして 使っていることになります。 このインスタンスへの信号の接続では、この例のように、=>を使って、 インスタンスのポートと、そこにつなぐ信号とを対応させることができます。 この例では、co=>w1という記述によって、 例えばhalf_adderのcoというポートに、w1という信号をつなぐ、という 意味になります。
library ieee; use ieee.std_logic_1164.all; entity adder4 is port ( a, b: in std_logic_vector(3 downto 0); ci : in std_logic; s: out std_logic_vector(3 downto 0); co: out std_logic); end adder4; architecture arch of adder4 is component full_adder port ( a, b, ci: in std_logic; s, co: out std_logic); end component; signal w0, w1, w2: std_logic; begin i0: full_adder port map(co=>w0, s=>s(0), a=>a(0), b=>b(0), ci=>ci); i1: full_adder port map(co=>w1, s=>s(1), a=>a(1), b=>b(1), ci=>w0); i2: full_adder port map(co=>w2, s=>s(2), a=>a(2), b=>b(2), ci=>w1); i3: full_adder port map(co=>co, s=>s(3), a=>a(3), b=>b(3), ci=>w2); end arch;ここで、std_logic_vectorというのが出てきました。 これは、何本かの信号線をまとめて扱う、配列のようなもので、 この例では、aは実際にはa(3)〜a(0)の4本の信号、となります。
library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_arith.all; entity adder4 is port ( a, b: in std_logic_vector(3 downto 0); ci : in std_logic; s: out std_logic_vector(3 downto 0); co: out std_logic); end adder4; architecture behv of adder4 is begin process (a, b, ci) variable tmp: std_logic_vector(4 downto 0); begin tmp := ("0"&a) + ("0"&b) + ("0000"&ci); co <= tmp(4); s <= tmp(3 downto 0); end process end behv;アーキテクチャ記述のところで、archではなくbehvと書いて、 回路の動作(behavior)を記述する、動作記述であることを示しています。 そしてtmp(4〜0)という変数を使って、 tmpにa, b, ciを加算した結果を入れ、 それをco, sに接続しなおしています。 文法の細かいところは、いまはあまり気にせず、 全体の流れをとらえるようにしてください。