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'); |
左の記述順に、入力された値を並べてみました。 (--はコメントアウト部) 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 <= (others => (others => '0'));
この記述で、128個全てに初期値"00000000"を与えることが出来る。
この方法は更に知られていないかも。
とりあえずこんなもん。