LoginSignup
10
2

More than 3 years have passed since last update.

VHDLの基本を忘れてしまった python ユーザーの備忘録

Last updated at Posted at 2020-08-14

はじめに

ソフトウェア関連が主な研究や仕事で使う状況で,またに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種類あるのがまたややこしい,,

onlysignal
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が実行される.

一方,

withvariable
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が実行される,というイメージである.
オブジェクトクラスprocess文内のsignalとvariableを参照のこと.

直感的には,上から代入されていく 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の使用方法 を見る限り,ヘッダーファイルの依存関係でハマりそうなので,複雑な入れ子関係などは避けた方が無難なようである.

10
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
2