001回
プログラム概要

key word :
パッケージ呼び出し , entity , architecture , 組み合わせ回路 , process文 , 異なるビット間での接続 , 二次元配列


ごちゃごちゃとした説明はかったるいので、そこら辺りはどこかの書籍に任せるとして、実際の記述とその説明を交えることによって簡単に説明を。



・基本構成

プログラム概要 記述例 -- 4bit加算器

--------------------------------------------------
パッケージ呼び出し


--------------------------------------------------

--------------------------------------------------
entity エンティティ名 is
 ポート宣言など


end エンティティ名;
--------------------------------------------------


--------------------------------------------------
architecture アーキテクチャ名 of エンティティ名 is
 各種宣言
 begin
  本体
end アーキテクチャ名;
--------------------------------------------------


-- パッケージ呼び出し
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.std_logic_unsigned.all;


-- エンティティ(入出力の宣言)
entity adder is
 port(a, b: in std_logic_vector (3 downto 0);
   q: out std_logic_vector (3 downto 0);
   );
end adder;



-- アーキテクチャ(回路の動作部)
architecture RTL of adder is
begin
 q <= a + b;

end RTL;



おおまかに書くとこんな感じ。
追々、内容を説明していきます。



・パッケージ呼び出し

記述例 解説

library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.std_logic_unsigned.all;


 各種演算素子や関数などを定義した「パッケージ」を呼び出す部分。
実設計上、各ブロックの記述の先頭で必ず呼び出す。
PICでいう、#include<P16F873>みたいなもの。

VHDLで標準となる仕様のファイルが属するライブラリはIEEEでパッケージがstd_logic_1164
最後の.allってのは、その中のを全部使うっていう意味。



-例-
std_logic_unsignedパッケージ内の
 CONV_INTEGER(A)
  意味:Aをstd_logic_vector型からinteger型へ変換

あっ、ちなみに拡張子は *.vhd です。





・entity エンティティ

記述例 解説

entity adder is
 port(a, b: in std_logic_vector (3 downto 0);
   q: out std_logic_vector (3 downto 0);
   );
end adder;

 外部とのインターフェイスを記述。
エンティティ名と、ファイル名は同一の方が後に分かりやすい。回路内容が分かる様な名前を。
また、VHDL の予約語と重なったりin、outで宣言した名前と重なったりするとエラーとなるので注意。

一応、一つのエンティティに対して複数のアーキテクチャを持つことができる。

entity内部では
<ポート信号名> : <入出力の方向 ><型>
で信号を定義する。

<ポートの信号名>
 今まで使ってきた clk_en等の自由な名前

<入出力の方向>
 verilogで言うinput outputで、inとout

<型>
 !重要! 型が一致した信号もののみ演算や接続が可能
一致しないと文法エラー。

-型の種類-
std_logic    という型で、1本の信号線
std_logic_vectorという型で、複数本の信号線、
std_logic_vector (7 downto 0); の様に記述する。
downtoで降順、toで昇順。実設計では専らdownto

std_logic_vector (7 downto 0);
ってのが、verilogでの[7:0]
正直、この記述がverilogと比較して面倒。
まぁ、一度書いてしまえばコピペなんですが。




・architecture アーキテクチャ

記述例 解説

architecture RTL of adder is
begin
 q <= a + b;
end RTL;

 内部の動作を記述。回路の本体。
entity内で入出力宣言された信号は、新たに型を宣言しなくても使用可能。(一応)
エンティティ名には、先のエンティティで宣言したのと同じ名前を。
アーキテクチャ名には、回路ならRTL、シミュレーション用のテストベンチならSIMと書くのが良いかも。

architecture RTL of adder is
begin
 q <= q + b;
end RTL;

 例外がこのケース。
入力された信号同士を足し合わせて出力に代入するのは問題ないけれど、
出力信号を内部で参照して演算する場合は、アウト。

architecture RTL of adder is
signal q_reg : std_logic_vector (3 downto 0);
begin
 q <= q_reg;
 q_reg <= q_reg + b;
end RTL;

そこで一旦内部用の信号を宣言してそれにて演算。
その結果を出力に放り込む。

ちなみに、この記述はちょっと拙い。
q_regの初期値が定まっていないから。
後のprocess文にて、きちんと説明しなおします。

verilogと比べて、型の一致を気にしなくちゃいけないし、out宣言だけじゃ出力信号を内部で使用できないのが辛い。

しばらくは、アーキテクチャ内での記述に関して書きます。


以上が、回路の基本構成。
verilogのmodule から end moduleに相当。
これをテンプレにして、後は回路記述で肉付けを。




・"<="組み合わせ回路

記述例 解説

architecture RTL of or is
 signal a : std_logic;
 signal b : std_logic;
 signal z : std_logic;
 begin
  z <= a or b;
end RTL

verilogでは、FFを通すときにしか使わなかったけれど、VHDLでは値を代入する際には全て<=を用いる。

ここの記述 "<=" はブロッキング代入と言われ、Cの様な手続き型言語のように逐次実行される。
詳しい記述に関しては、また後ほど説明します。






・process文 順序回路

記述例 解説

 process (clock,reset) begin
   --clockかresetに変化があった際
  if (clock'event and clock = '1') then
    q <= d ;   --qにdを代入
    d <= q ;   --dにqを代入
    end
 end process;



 process (a ,b) --a、bに変化があるたびに
  if( a = '1' ) then  --aが1なら
   q <= a;    --qにbを代入
processは、括弧内の信号が指定された条件の変化があったときに内部に書かれた命令が実行される。
※括弧内に入っている値を、センシビティ・リストと言う。
他の動作条件を記述する際は、","を使う

verilogでは動作条件としてシステム周波数clkのposedgeを使うのが基本で、posedgeとは信号が0から1に変化した際という意味を持つ。
しかしVHDLではposedgeに相当する予約語がない。そこで
if (clock'event and clock = '1') then
このような記述を行う。
↑'eventはclockが変化した時 & clockが1になった際

FF(フリップフロップ)で使う際にはは、verilogと異なり、reg宣言をしなくて良い。

このprocess文中の "<=" は通常の手続き型言語と異なりハードウェア記述言語の特徴の一つであり、「ノン・ブロッキング代入」と言われているものである。
この記述はシミュレーション実行時に並列に評価され、下記の例の場合、clockが変化するとdとqへの代入が同時に行われる。
Cでこれと同じことをさせようとすると、qの値を一旦別に保存して、その値をdに入れなくてはいけないので、ある意味この記述は便利。

FFとして使う記述では基本的に、このセンシビティ・リスト内にはクロックとリセット以外は記述しない。してはいけない。
任意の信号の立ち上がりや立下りを利用して回路を動作させたい場合は多々あるが、それを利用してしまうと非同期の回路になり 実際に回路化する際に問題が多くなる。しかし、任意の信号の立ち上がり等を利用したい場合は多々ある。それについては後ほど 説明を追加する。



※同期リセット ※非同期リセット ※FF以外での用途

architecture RTL of counter is

signal q_reg: std_logic_vector (3 downto 0);

begin
 q <= q_reg

 process (ck) begin
  if (ck'event and ck = '1') then
   if res = '0' then
    q_reg <= "0000"
   else
    q_reg <= q_reg + "0001";
   end if;
  end if;
 end process;

ene RTL;


architecture RTL of counter is

signal q_reg: std_logic;

begin
 q <= q_reg

 process (ck,res) begin
  if res = '0' then
   q_reg <= '0'
  elsif (ck'event and ck = '1') then
   q_reg <= q_reg + '1';
  end if;
 end process;



ene RTL;

architecture RTL of segment is

 begin

 process (q) begin
  case q is
   when "0000" => seg <= "0000001";
   when "0001" => seg <= "1001111";
   when "0010" => seg <= "0010010";
   when "0011" => seg <= "0000110";
   when "0100" => seg <= "1001100";
   when "0101" => seg <= "0100100";
   when "0110" => seg <= "0100000";
   when "0111" => seg <= "0001101";
   when "1000" => seg <= "0000000";
   when "1001" => seg <= "0001100";
   when others => seg <= "0110000";
  end case;
 end process;
end RTL;

同期・非同期の違いについて見てみる。
同期では、ckが変化し、1になった際(以下posedge)に、resのLでq_regの値をリセットさせているのに対し、
非同期では、cl,resが変化した際にresがLなら値のリセット。そして、他の条件はckのposedgeで動作させている。

◎if文でクロック判別
ifは、2方向の分岐。if直後の条件式を評価し、
 真:then以降
 偽:elseがあればelse以降
を実行する。
if の条件設定部分は、( )があっても無くても良い。
予約語 event はアトリビュート(属性)と呼ぶ。信号や変数、定数などに付加される一種のデータ。eventアトリビュートは、信号の変化の有無を意味する。
今回の ck'event and ck = '1'
は、ckに変化があり、かつ1のとき == ckの立ち上がりを意味する。
同期リセットの場合は、全てckの変化によって動作を行っているが、非同期リセットの場合はクロック有無をチェックする前にリセットのチェックを行っている。
また、elseif や else if と表記するのではなく、 elsif と表記する点に注意。

◎内部参照のための信号宣言
VHDLは、内部で参照できないという制約が存在している。
つまり
q_reg <= q_reg + "0001" ;
のように内部で参照し、演算する記述がある場合、出力ポート信号を使うことができない。
q <= q + "0001" ;
と記述すると文法エラーとなる。
そこで、内部参照用に内部信号を用いる。信号の宣言にはsignalを使う。
signal :<信号名><型>

◎もう一つの記述スタイル
process begin
 wait until ck'event and ck = "1"
 if res = '1' then
  q_reg <= "0000" ;
 eles
  q_reg <= q_reg+ "1" ;
 end if;
end process;

上記のように、センシティビティリスト(process () beginnの()←のなか)のないprocess文は無限ループとなる。


・異なるbit間での接続 

記述例 解説
rap_cnt : std_logic_vector (11 downto 0);
DIN : std_logic_vector ( 7 downto 0);

 --rap_cnt <= "000000000000"
 --rap_cnt <= (others => '0');
 rap_cnt <= ("0" & DIN & "000");
 --rap_cnt <= (0 =>'1' , 1=>'1' , others => '0');

11bit10bit9bit8bit 7bit6bit5bit4bit 3bit2bit1bit0bit
0000 0000 0000
0000 0000 0000
0DIN(7)DIN(6)DIN(5) DIN(4)DIN(3)DIN(2) DIN(1)DIN(0)000
0000 0000 0011

左の記述順に、入力された値を並べてみました。  (--はコメントアウト部)
1番目の記述は、直接全ての値を記述
2番目の記述は、1番目と同じ意味で、全てに0を代入
3番目の記述は、上位ビットから順に値を代入する方法
4番目の記述は、0bit目と1bit目に1を入力すし、それ以外は0を

verilogの場合では、12bitの箇所に "= 8'h0"と異なるbit数で入力しても7-0bitの間に値を入力してくれるのだけれど、VHDLは両方が完全に一致していないといけない。
そのため、まだ12bit程度なら一番上の記述でも大丈夫だけれど、32bitとか64bitになってしまうとこの方法で記述するのは酷く手間がかかるし、任意の値を入力する際には間違える。
そこで登場なのがothers。意外と、このothersの記述は知られていなかったり・・・

subtype line is std_logic_vector (15 downto 0);
 type arr is array (0 to 127) of line;
  signal tdh : arr;

subtype line2 is std_logic_vector (15 downto 0);
 type arr2 is array (0 to 127) of line2;
  signal Rdata : arr2;

signal count : integer range 0 to 127 ;
signal A   : std_logic_vector (7 downto 0);

 tdh(count + 3)(15 downto 8) <=
  Rdata(CONV_INTEGER(A)+3)( 7 downto 0) ;
  --Rdata(CONV_INTEGER(A + "011"))( 7 downto 0) ;


もののついでというかなんと言うかで、一部のビットを代入する処理に併せて2次元配列の方法も。
細かい説明は面倒なんで、一言で言うならこの記述ではtdhとRdataは16bitのレジスタが128個並んでるってイメージ。(判り難い

tdhのcount+3番目のレジスタの15-8bitに、RdataのA+3番目のレジスタの7-0bitを代入するという式。

配列の( )内には、integerで宣言した型しか受け付けないため、countはそのまま代入しても良いが、std_logic_vectorで宣言されたAはCONV_INTEGERで一度変換しなくてはいけない。
尚、その値を変化させるときも、型が一致していないといけないので気をつけるべし。

左のコメントアウトしている箇所とそうでない箇所の動作は、同じである。



ちょっと書き忘れ。

tdh <= (others => (others => '0'));

この記述で、128個全てに初期値"00000000"を与えることが出来る。
この方法は更に知られていないかも。


とりあえずこんなもん。


[<<前へ | ↑Topへ戻る↑ | 次へ>>]