はじめに
ソフトウェア関連が主な研究や仕事で使う状況で,またにVHDLで開発をしないと,,という場合で,一度は一通り学習した経験がある人向けです.FPGAの専門家ではないので,間違いなど遠慮なくご指摘ください.
エディタ
python ユーザーであれば,sublime text で VHDL が読めたらよいな,,と思うかもしれませんが,できるようです.
の通りにやったら私の環境(Mac Mojave)では動きました.
基本事項のおさらい
VHDLのシミュレータ
手前味噌ですが,
にまとめてみました.他にも,Vivado や ModelSim などもあります.
演算の基本
- 論理演算子(andやorなど) は bit のみ使用可
- 算術演算子 (+,-,*,/など)は integer のみで使用可
異なるデータタイプで算術演算や論理演算を行う場合は注意が必要.例えば,トリガーをかけたい場合は,パルス信号に引き算を行い,その正負で判断するわけだが,それは算術演算で行う.AD変換後の生データは整数値では入ってこないはずで,どこかで整数に変換する必要があり,conv_integerやto_integerが必要になる.
というのが昔の一般論で,今では,std_logic_vector に対して + などをダイレクトに使うが,いくつか注意事項があるので,細かいことを下記などを参照されたい.
bit_vector と std_logic_vector の違い
にある通り.bit_vectorは「0」と「1」しか使えないのに対して,std_logic_vectorは「0」「1」以外に [X,L,H,W,Z,U,-] を指定でき,それぞれ [ストロング、弱L、弱H、不定値、ハイインピーダンス、初期化しない、ドントケア項]である.conv_integer は,bit_vector には使えない.
整数に変換する conv_integer という関数を使う場合は,
use ieee.std_logic_unsigned.all;
use ieee.std_logic_signed.all;
のどちらを冒頭に記述するかで,符号付きか符号なしの conv_integer として働くかが変化することに注意が必要.
の湯浅君の記事がよくまとまってます.
conv_integer は今は,
にあるように,to_integer を使うようです.
integer
integer | std_logic_vector | |
---|---|---|
初期化 | <=0 | <=(others=>'0') |
値 | 整数値 | ""で囲んだバイナリ列 |
定義方法 | integer range 0 to WIDTH-1 | std_logic_vector(WIDTH-1 downto 0) |
Process文って何だっけ.
process 文は,「順次処理文」と呼ばれ,受けから順に実行され,同時処理文と対をなす概念である.だけなら,わかりやすいのであるが,「上から順に実行」が misleading というか,何とも誤解を招きやすい日本語に思われる.「上から順に実行」されるのは,「処理する手順」であって,信号が変化するタイミングではない,のである.
信号が変化するタイミング?とは,ここで2種類あるのがまたややこしい,,
process(A, B, C, D)
begin
D <= A;
X <= B + D;
D <= C;
Y <= B + D;
end process;
の場合は,
X<= B + C;
Y<= B + C;
となる.
process文は上から順に評価されて最後(end process)に同時に値が代入されるため,D<=Aの後に,D<=Cが実行されるので,D<=Cを最終結果としてX<=B+D, Y<=B+Dが実行される.
一方,
process(A, B, C)
begin
D := A; -- 信号Aを変数Dに代入
X <= B + D;
D := C; -- 信号Cを変数Dに代入
Y <= B + D;
end process;
の場合は,
X <= B + A
X <= B + C
となる.これは,DにAが代入されてから,それがソフトウェア的なイメージで次のX<=B+Dに使われて,その次の,D:=Cで,DにCが代入されて,Y<=B+Dが実行される,というイメージである.
[オブジェクトクラス]
(http://arch.cs.kumamoto-u.ac.jp/project/kite/3days/dic/VHDL/object.html),[process文内のsignalとvariable](https://ameblo.jp/middleisland88/entry-11090340548.html)を参照のこと.
直感的には,上から代入されていく variable の方が,順次処理というイメージに近い気がする.信号の割り当て(<=)は,process文の中では,センシティビティーリストのイベント発生によって同時に代入される.そのため,process文は「順次処理」,process文以外は「同時処理」と理解していると,あれどっちだっけ?と思ってしまう.
連結
VHDLのデータの型,配列,型変換
がたいへんよくまとめってるので,これを読みましょう.
VHDLでの配列は,
type データ型名 is array 範囲 of 元の型名;
で定義できる.例えば,std_logic_vector の型定義はIEEEのstd_logic_1164の中で,
type std_logic_vector is array (Natural range <>) of std_logic;
と定義されている.Natural range <> の "<>" は要素数が不定ですよ,という意味です.
例えば,自分で type を宣言して,変数を宣言する場合は,
type BIT_VECTOR is array(INTEGER range <>) of BIT;
variable MY_VECTOR : BIT_VECTOR(5 downto -5);
となる.この配列の長さ,先頭,最後などを得るには,下記のような書き方をすればよく,上記の MY_VECTOR : BIT_VECTOR(5 downto -5) の場合には,
Attribute Expression | Value |
---|---|
MY_VECTOR'left | 5 |
MY_VECTOR'right | -5 |
MY_VECTOR'high | 5 |
MY_VECTOR'low | 5 |
MY_VECTOR'length | 11 |
MY_VECTOR'range | (5 down to -5) |
MY_VECTOR'reverse_range | (-5 to 5) |
となる.詳細は,Array Types を参照されたい.
VHDLでの定数
初期化が必要で,初期値の代入は ":=" で行う.
constant const : std_logic := "0";
component の使用方法
component文が何?って場合は,VHDLで下位ブロックを呼び出す方法は2種類あって,下記がとてもわかりやすいので,まずは一読ください.
要するに,一つは,library名.entity名で指定して呼び出す方法と,もう一つが component 文を使って呼び出す方法で,どっちを使っても良いが,一長一短ある.
component文はモジュールのパラメータや入出力を書き下すため,モジュールの実体(entity文)とcomponent文で呼び出す際の2箇所で全く同じインターフェースが記述されるため,同じものは2回かかない,というソフトウェアの書き方に反して,2重管理となってしまう.(逆に言えば,冗長な分だけ,依存関係は vivado が勝手に階層構造を解釈してくれるので便利,ということかな
component 文は,別ファイルに書かれたモジュールを呼び出すもので,pythonでimportして呼び出す感覚に近いかもしれない.)
generic の使用方法
generic は,component 文でインターフェースの定義を書き下したあとで,architecture と begin の中に,
component ad_iobuf
generic (
DATA_WIDTH : Integer : 16);
);
port (
dio_t : in std_logic_vector(DATA_WIDTH-1 downto 0);
dio_i : in std_logic_vector(DATA_WIDTH-1 downto 0);
dio_o : out std_logic_vector(DATA_WIDTH-1 downto 0);
dio_p : inout std_logic_vector(DATA_WIDTH-1 downto 0)
);
end component;
のように記載する.これは,python で言うところの,def ad_iobuf(data_width=16) のように,初期値を与えて定義された関数,の状態である.つまり,component 宣言はオブジェクトや関数の定義と同じなので,一回だけでよい.
次に,component ad_iobuf で定義された ad_iobuf がどう使われるのかを見てみよう.architecture と begin の end の中で,"i_iobuf : ad_iobuf" で ad_iobuf というコンポーネントを呼び出し,DATA_WIDTH を 9 として ad_iobuf のインスタンスとして i_iobuf を生成する.もう一つ,DATA_WIDTH を 17 として i_iobuf_bd を生成する.つまり,名前 : コンポーネント名,で実態を生成できる.
i_iobuf : ad_iobuf
generic map(
DATA_WIDTH => 9
) port map (
dio_t => dio_sig_t,
dio_i => dio_sig_i,
dio_o => dio_sig_o,
dio_p => dio_sig_p
);
i_iobuf_bd : ad_iobuf
generic map(
DATA_WIDTH => 17
)
port map (
dio_t => gpio_t(16 downto 0),
dio_i => gpio_o(16 downto 0),
dio_o => gpio_i(16 downto 0),
dio_p => gpio_bd
);
上記を雰囲気を python で捉えるならば,ad_iobuf をクラスを思って,
i_iobuf = ad_iobuf(dio_sig_t, dio_sig_i, dio_sig_o, dio_sig_p, DATA_WIDTH=9)
i_iobuf_bd = ad_iobuf(gpio_t(16 downto 0), gpio_o(16 downto 0), gpio_i(16 downto 0), gpio_bd, DATA_WIDTH=9)
という感じ.
record タイプ
C言語でいう構造体で,複数の変数が一体となってしか登場しないものをまとめておくと,コードの量もへり,可読性もよくなるようである.
など参照.
使い方は,
library ieee;
use ieee.std_logic_1164.all;
package example_record_pkg is
-- Outputs from the FIFO.
type t_FROM_FIFO is record
wr_full : std_logic; -- FIFO Full Flag
rd_empty : std_logic; -- FIFO Empty Flag
rd_dv : std_logic;
rd_data : std_logic_vector(7 downto 0);
end record t_FROM_FIFO;
-- Inputs to the FIFO.
type t_TO_FIFO is record
wr_en : std_logic;
wr_data : std_logic_vector(7 downto 0);
rd_en : std_logic;
end record t_TO_FIFO;
constant c_FROM_FIFO_INIT : t_FROM_FIFO := (wr_full => '0',
rd_empty => '1',
rd_dv => '0',
rd_data => (others => '0'));
constant c_TO_FIFO_INIT : t_TO_FIFO := (wr_en => '0',
wr_data => (others => '0'),
rd_en => '0');
end package example_record_pkg;
などである.
package 文
C言語でいうところの,定義の書かれたヘッダーファイル,という感じ.
-- Package Declaration Section
package example_package is
constant c_PIXELS : integer := 65536;
type t_FROM_FIFO is record
wr_full : std_logic;
rd_empty : std_logic;
end record t_FROM_FIFO;
component example_component is
port (
i_data : in std_logic;
o_rsult : out std_logic);
end component example_component;
function Bitwise_AND (
i_vector : in std_logic_vector(3 downto 0))
return std_logic;
end package example_package;
-- Package Body Section
package body example_package is
function Bitwise_AND (
i_vector : in std_logic_vector(3 downto 0)
)
return std_logic is
begin
return (i_vector (0) and i_vector (1) and i_vector (2) and i_vector (3));
end;
end package body example_package;
ただし,VHDLでのpackageの使用方法 を見る限り,ヘッダーファイルの依存関係でハマりそうなので,複雑な入れ子関係などは避けた方が無難なようである.