第2回: ハードウエア記述言語と論理回路

論理回路(少し復習)

論理回路について、次のキーワードがピンとくる程度に 復習をしておきましょう。
キーワード: ブール代数、ANDゲート、ORゲート、インバータ(NOTゲート)、 真理値表、積和標準形、カルノー図

ハードウエア記述言語による論理回路の設計

[p.7〜] ←※[]内は、対応する教科書のページ数を示します。
通常、論理回路を設計するためには、 作りたいものの機能をブール代数や論理式で表現し、 それを真理値表やカルノー図などを用いて簡略化を行い、 論理回路を設計します。(図2.1) その後、作った論理回路が正しいか、シミュレーションを行います。 このとき、正しい動作をしているかを確認するためには、 ある入力を与えて、その回路の出力が予定通りか、を チェックすることになりますが、 このとき、チェックのために与える入力の信号(値)のことを 「テストパタン」と呼びます。 そしてテストパタンを与えたときの出力が、 意図したものと異なる場合は、論理回路の設計に戻り、 設計しなおしをしたり、間違いを探すことになります。

この方法の問題は、どうしてもミスが入りやすく、またミスを見つけにくい、 ということです。 これは、特に作りたい回路が大きくなると、顕著な問題となります。

これを解決するために考えられたのが、 作りたい回路を言語で書こう、という方法で、 そのための言語をハードウエア記述言語 (Hardware Description Language; HDL)と呼びます。 具体例は後ほど見ることにしますが、 HDLを用いた論理回路の設計では、単体の論理ゲートのことや カルノー図による簡略化のことは気にする必要はありません。 その分、「どういう動作をさせるのか」に集中することができます。 設計した後は、やはり同じようにテストパタンを与えて シミュレーションをするわけですが、 仮に出力が意図したものと異なる場合でも、 もともと「こういう動作をする回路」ということを記述して 回路を設計していますので、比較的ミスを見つけやすい、という 大きな特長があります。

ただしHDLで記述したものは、あくまでも、回路の動作、ですので、 最終的には、それを「論理合成」と呼ばれる過程を経て、 論理ゲートからなる論理回路へと変換する必要があります。 しかし、この論理合成は、コンピュータ上のソフトウエアが ほとんど自動的にやってくれます。 論理合成は、ブール代数に基づいて、動作の記述を 論理回路へと機械的に変換していく作業ですが、 作業量自体はとても多いものです。 しかしこのような、単純だけど機械的で分量が多い作業は、 人間よりもコンピュータは非常に得意ですので、 人間が手動でやるよりも、はるかに間違いが少なく、 正しい論理回路へと変換してくれます。

これが、HDLを用いた論理回路の設計の流れと特長になります。

ハードウエア記述言語の種類

[p.14〜] 歴史的な経緯で、HDLにはいくつかあります。 コンピュータ上のプログラミング言語に、C言語、Javaなどがあるのと 同じことですね。 その中でも広く用いられているのが、 VerilogHDLとVHDLです。 このうち、この授業では、VHDLを扱うことにします。

ハードウエア記述言語による論理回路の記述例

こういうものは、具体例を先に見たほうがいいと思うので、 具体的な記述を見てみることにしましょう。

半加算器

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となっています。

ここで、論理式の簡略化にはまったく気を使う必要はなく、 ただ作ろうしている回路を論理式で書いているだけ、といことに 注意しましょう。 つまり、論理回路の設計をしているのですが、 論理回路図から書くのに比べると、ミスが入りにくくなっています。 またあとから修正するのも楽そうです。

全加算器

[p.18〜] もう1つ、この半加算器を2つ使って、全加算器をつくってみましょう。 2個の半加算器と1個のORゲートを図3.3のように接続すると、 全加算器になります。 これをVHDLで書いてみると次のようになります。
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という信号をつなぐ、という 意味になります。

4ビット加算器

[p.19〜] 全加算器を4個つなぐと、4ビット加算器になります。 同様にやってみましょう。
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本の信号、となります。

4ビット加算器の動作記述

上の例では、半加算器→全加算器→4ビット加算器、と順番に つくってきました。 しかし、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に接続しなおしています。 文法の細かいところは、いまはあまり気にせず、 全体の流れをとらえるようにしてください。
配布資料(VHDL記述集)
この回のソボクな疑問集
戻る