VHDL
2007年10月02日
VHDL TIPS 「物理タイプの使い方」
物理タイプとは、時間や電圧、電流、距離などの物理的な物を表すために使用するデータタイプだ。
VHDLには元々「time」が物理タイプとして定義されており内容はスタンダードパッケージに書かれている。
この「time」を代表とする物理タイプはpsやnsなどの単位を指定できるのが大きな特徴で、新しい単位を持ったデータタイプを作成する事も出来る。
以下の例は距離の物理タイプを新たに定義して、その距離を遅延時間に変換する関数も用意した。(遅延時間は1ns=15cmで計算)
use modelsim_lib.util.all;
package distance_pkg is
-- 距離タイプ
type distance is range 0 to integer'high
units
munit;
um = 10 munit;
mm = 1000 um;
cm = 10 mm;
m = 100 cm;
km = 1000 m;
mil = 254 munit;
inch = 1000 mil;
ft = 12 inch;
yd = 3 ft;
fm = 6 ft;
mi = 5280 ft;
lg = 3 mi;
end units;
-- 距離から遅延時間を算出
function distance2time( trace_len : distance ) return time;
end distance_pkg;
package body distance_pkg is
-- 距離から遅延時間を算出
function distance2time( trace_len : distance ) return time is
variable resolution : real;
variable delay_time : time;
begin
-- シミュレーションレゾリューションの取得
resolution := get_resolution;
-- メートル−時間変換
--1ns=15cm(150000um)
--1000000fs=150000um
--1fs=0.15um(1.5munit)
delay_time := to_time( real( distance'pos(trace_len))
/(1.5 * resolution * 1000000000000000.0));
return delay_time;
end;
end distance_pkg;
このように最小単位からの係数を変えることで、ミリやインチなどを混在して記述出来る事も便利な点だ。
また、距離を遅延時間に変換する関数は、「time」がシミュレーションのレゾリューションによって最小単位が変わる特殊な物理タイプのため、まずレゾリューションを取得してから変換している。
この距離パッケージを使うと、以前紹介したVHDL TIPS 「双方向バスの遅延モデル」の遅延値を入力する部分を、距離を入力する様に簡単に置き換えることが出来るので、ケーブルのモデリングなどに使用するとおもしろいと思う。
2007年09月26日
VHDL TIPS 「双方向バスの遅延モデル」
FPGAとメモリモデルを接続する場合等に「双方向バスでネットの遅延を挿入したい」と思った事が何度かある。
安直に「after文」でお互いを代入しても'X'になった以降はずっと'X'のままでうまくいかない。
ではどうすれば双方向バスに遅延を挿入する事が出来るのだろうか?
簡単な様で、これが結構難しい。色々と考えた末に、双方向バスの遅延モデルを作成できたので紹介する。
まずは、モデルの動作を確認した回路。
双方向の遅延モデルだが、テストで入力は使用しないので出力のみを入れた。
このPORTAとPORTBに対してテストベンチから波形を入力する。
波形を入力するテストベンチはこれ。
use IEEE.std_logic_1164.all;
entity TEST is
end TEST;
architecture BEHAVIOR of TEST is
component LINEDELAY
generic
(
linedelay : time
);
port
(
PORTA : inout std_logic;
PORTB : inout std_logic
);
end component;
signal PORTA : std_logic;
signal PORTB : std_logic;
signal PORTA_S : std_logic;
signal PORTB_S : std_logic;
begin
C_LINEDELAY : LINEDELAY
generic map
(
linedelay => 10 ns
)
port map
(
PORTA => PORTA_S,
PORTB => PORTB_S
);
process begin
PORTA <= 'Z';
PORTB <= 'Z';
wait for 100 ns;
PORTA <= '1';
wait for 100 ns;
PORTA <= '0';
wait for 100 ns;
PORTA <= 'Z';
PORTB <= '1';
wait for 100 ns;
PORTB <= '0';
wait for 100 ns;
PORTB <= 'Z';
wait for 100 ns;
PORTA <= '1';
PORTB <= '0';
wait for 100 ns;
PORTA <= '0';
PORTB <= '1';
wait for 100 ns;
PORTA <= '1';
wait for 100 ns;
PORTA <= '0';
PORTB <= '0';
wait for 100 ns;
end process;
PORTA_S <= PORTA;
PORTB_S <= PORTB;
end BEHAVIOR;
そして動作結果の波形はこんな感じ。
PORTAから出力した値は、すぐにPORTA_Sに反映されるがPORTB_Sには遅延が入っている事が分かる。
逆にPORTBから出力した値は、すぐにPORTB_Sに反映されるがPORTA_Sには遅延が入っている。
また、同時にPORTA_SとPORTB_Sの値を変化させても遅延時間分のショートが'X'で再現されている。
次に肝心なモデルのソースがこれ。
use IEEE.std_logic_1164.all;
entity LINEDELAY is
generic
(
linedelay : time
);
port
(
PORTA : inout std_logic;
PORTB : inout std_logic
);
end LINEDELAY;
architecture BEHAVIOR of LINEDELAY is
signal PORTA_X : std_logic;
signal PORTB_X : std_logic;
begin
PROCESS
VARIABLE last_time : time;
VARIABLE transact : boolean;
BEGIN
WAIT ON PORTA'TRANSACTION, PORTB_X UNTIL (last_time /= NOW or PORTB_X'EVENT);
if ( PORTA'TRANSACTION'EVENT ) then
PORTA <= 'Z';
transact := TRUE;
last_time := NOW;
else
transact := FALSE;
end if;
WAIT FOR 0 ns;
if ( transact = TRUE ) then
PORTA_X <= transport PORTA after linedelay;
end if;
PORTA <= PORTB_X;
END PROCESS;
PROCESS
VARIABLE last_time : time;
VARIABLE transact : boolean;
BEGIN
WAIT ON PORTB'TRANSACTION, PORTA_X UNTIL (last_time /= NOW or PORTA_X'EVENT);
if ( PORTB'TRANSACTION'EVENT ) then
PORTB <= 'Z';
last_time := NOW;
transact := TRUE;
else
transact := FALSE;
end if;
WAIT FOR 0 ns;
if ( transact = TRUE ) then
PORTB_X <= transport PORTB after linedelay;
end if;
PORTB <= PORTA_X;
END PROCESS;
end BEHAVIOR;
PORTA_XとPORTB_Xに、それぞれの双方向ポートの値をコピーし、それを遅延させて反対側のポートに代入している。
この双方向ポートの値をコピーする時、一度モデル側のドライバを'Z'にして本当の相手側ポートの値を取得する必要がある。
また、変な「WAIT文」、これは双方向ポートのトランザクションは時間単位あたり1度しか動作させたく無いのに対し反対側の双方向ポートをコピーした値のイベントは何回でも動作させるための条件だ。
双方向ポートのトランザクションを時間単位あたり1度しか動作させたくない理由は、自分でトランザクションを発生させているためこの条件が無いとループに陥ってしまうからである。
ただ、双方向ポートの一番最初のトランザクションで動作させているため、トランザクションが発生したデルタ時間の後のデルタ時間に値が変化するような信号ではうまく動作しない。
尚、以前紹介したVHDL TIPS 「アナログスイッチのモデリング」もそうだが、この手の手法はこの制限がつきまとってしまう。
2007年09月21日
VHDL TIPS 「属性"DRIVING_VALUE"の使用法」
VHDLでは出力ポートに割り当てた値を内部で参照する事は出来ない。
所が、VHDL93でコレを可能にするアトリビュート「DRIVING_VALUE」が追加された。
以下に使用例を紹介する。
use IEEE.std_logic_1164.all;
use IEEE.std_logic_signed.all;
entity COUNT is
port
(
CLK : in std_logic;
COUNT : out std_logic_vector(7 downto 0) := (others => '0')
);
end COUNT;
architecture rtl of COUNT is
begin
process ( CLK ) begin
if ( CLK'event and CLK = '1' ) then
COUNT <= COUNT'DRIVING_VALUE + '1'; -- 内部参照
end if;
end process;
end rtl;
これは単純な8ビットのカウンタで、クロックを入力するとカウントアップするだけの回路だ。通常は内部にカウント信号を定義してその信号をカウントアップし、出力ポートに割り当てるような記述になると思うが「DRIVING_VALUE」を使用すると出力ポートの値を直接参照してカウントアップし、そのまま出力ポートに代入する事が出来る。
便利と言えば便利だが、自分は出力ポートに出ている信号は内部参照されていないことを前提にソースコードを追いかけるので、出来るだけ使用しないようにしている。
また、使用しない決定的な理由として、XSTではサポートしていないと言う事がある。
それでも、シミュレーションモデル等で出力ポートを直接チェックしたい時には便利に使えるかもしれない。
2007年09月11日
VHDL TIPS 「shared variableの使用法」
VHDL93から「共有変数」が使用可能になりVHDL2002で使用方法が変更になった。
XSTはVHDL93をサポートしており限定的ではあるが、共有変数を使用できるみたいなのでVHDL93とVHDL2002両方の使い方を紹介する。
(ただし今回、紹介する例はXSTで合成出来ない。合成するためには同じアーキテクチャ内から共有変数にアクセスする必要がある。)
まず、VHDL93の例
shared variable SH_INT : integer;
end shared_pkg;
library IEEE;
use IEEE.std_logic_1164.all;
library work;
use work.shared_pkg.all;
entity a is
port
(
CLK : in std_logic;
DATA : out integer
);
end a;
architecture a of a is
component b
port
(
CLK : in std_logic
);
end component;
begin
u_b : b port map( CLK );
process ( CLK ) begin
if ( CLK'event and CLK = '1' ) then
DATA <= SH_INT;
end if;
end process;
end a;
library IEEE;
use IEEE.std_logic_1164.all;
library work;
use work.shared_pkg.all;
entity b is
port
(
CLK : in std_logic
);
end b;
architecture b of b is
begin
process ( CLK ) begin
if ( CLK'event and CLK = '1' ) then
SH_INT := SH_INT + 1;
end if;
end process;
end b;
この例では、2つのアーキテクチャからパッケージで宣言した共有変数にアクセスしている。
次に、VHDL2002の例
type SH_TEST is protected
-- 書き込み関数
procedure write ( data : in integer );
-- 読み出し関数
impure function read return integer;
end protected SH_TEST;
-- 共有変数をプロテクトタイプで宣言
shared variable SH_INT : SH_TEST;
end shared_pkg;
package body shared_pkg is
type SH_TEST is protected body
-- 実データ
variable buf : integer := 0;
-- 書き込み関数
procedure write ( data : in integer ) is
begin
buf := data;
end;
-- 読み出し関数
impure function read return integer is
begin
return buf;
end;
end protected body SH_TEST;
end shared_pkg;
library IEEE;
use IEEE.std_logic_1164.all;
library work;
use work.shared_pkg.all;
entity a is
port
(
CLK : in std_logic;
DATA : out integer
);
end a;
architecture a of a is
component b
port
(
CLK : in std_logic
);
end component;
begin
process ( CLK ) begin
if ( CLK'event and CLK = '1' ) then
DATA <= SH_INT.read;
end if;
end process;
u_b : b port map( CLK );
end a;
library IEEE;
use IEEE.std_logic_1164.all;
library work;
use work.shared_pkg.all;
entity b is
port
(
CLK : in std_logic
);
end b;
architecture b of b is
begin
process ( CLK ) begin
if ( CLK'event and CLK = '1' ) then
SH_INT.write( SH_INT.read + 1 );
end if;
end process;
end b;
VHDL2002では単純にアクセスするのでは無く、プロテクトタイプを宣言しデータにアクセスするための関数を定義してその関数を通してデータにアクセスする。
丁度、C++のクラスでプライベートのデータにパブリックのメソッドからアクセスするようなイメージだ。
今回、共有変数の使用例を紹介したがXSTでは同じアーキテクチャ内からでしかアクセス出来ない等の制限があり余り利用価値がない。
もし別のアーキテクチャからアクセス出来ればデバッグ時に信号を引き出す手段として有効利用できたと思う。私はコレがやりたかったのでとても残念に思う。
尚、今回の例は変数の使い方としては良くない例で、同じ時間に違うプロセスからリードライトしているのでシミュレータによってはタイミングが変わるかもしれない。あくまでも共有変数の動作を確認する目的で作成している。
2010/2/19:今更ながら少し修正、VHDL93版の"DATA <= SH_INT;"でイベントが発生しないのでCLKイベントにしました。
2007年09月04日
VHDLでFPGAのバージョン管理
皆さんはFPGAのバージョン管理はどうしていますか?
「たまにエラー出るけどこれいつのバージョンか分からない!」
「バージョンレジスタを付けたけど更新するの忘れた!」
なんて事ありませんか?
私はよくあります...
そこで!論理合成するたびに自動的にインクリメントしてくれる信号を作ってみました。これは合成時に値が決定するので回路のリソースは消費しませんし、自動なので更新を忘れることもありません。
使い方は、FPGAのトップから本パッケージの関数を呼び出すだけです。
以下に使用例を紹介します。
library IEEE;
use IEEE.std_logic_1164.all;
-- FPGAのトップファイルにこのパッケージを宣言する。
library work;
use work.ver_get.all;
entity top is
generic
(
-- シミュレーションを行う場合は、
-- テストベンチから「SIM_MODE」を1にして呼び出す。
-- 合成の時はディフォルトの0が自動的に割り当たる。
SIM_MODE : integer := 0
);
port
(
VER_OUT : out std_logic_vector(31 downto 0)
);
end top;
architecture top of top is
-- この呼び出しで論理合成する毎にインクリメントされた値を返す。
constant VERSION : std_logic_vector(31 downto 0)
:= get_version( SIM_MODE, "ver.txt" );
begin
VER_OUT <= VERSION;
end top;
動作の仕組みは、論理合成時に指定されたバージョンファイルからバージョンデータを読み出しインクリメントしたバージョンデータを書き込むだけです。
詳しくはパッケージファイルにコメントを書いていますのでそれを見てください。
パッケージはココ、
参考ファイルはココ、
バージョンファイルの参考例はココ
からダウンロードできます。
「もっと良い方法しってるよ!」って人は是非教えてください。
2007年08月29日
VHDL TIPS 「postponed processの使用法」
前回のシミュレーション・サイクルで少し触れた実行遅延プロセスを紹介する。
実行遅延プロセスはVHDL93で追加された機能で、その時刻の全てのデルタサイクルが終わった後で実行されるプロセスである。
動作をサンプルソースで見てみる事にする。
以下のソースは4ビットの同期カウンタを通常のプロセスと実行遅延プロセスでセットアップチェックを行い動作を比較した例だ。
use IEEE.std_logic_1164.all;
use IEEE.std_logic_unsigned.all;
entity PP_TEST is
end PP_TEST;
architecture behavior of PP_TEST is
signal CLK : std_logic;
signal COUNT : std_logic_vector(3 downto 0) := (others => '0');
begin
-- クロック生成
process begin
CLK <= '0';
wait for 10 ns;
CLK <= '1';
wait for 10 ns;
end process;
-- CLKでCOUNTをインクリメント
process ( CLK ) begin
if ( CLK'event and CLK = '1' ) then
COUNT <= COUNT + '1';
end if;
end process;
-- COUNTのセットアップチェック
process ( CLK ) begin
if ( CLK = '1' ) then
assert ( COUNT'last_event >= 5 ns )
report "Setup Time was not suitable."
severity WARNING;
end if;
end process;
-- COUNTのセットアップチェック
postponed process ( CLK ) begin
if ( CLK = '1' ) then
assert ( COUNT'last_event >= 5 ns )
report "Setup Time was not suitable.(postponed)"
severity WARNING;
end if;
end process;
end behavior;
これを実行すると実行遅延プロセスのアサーションだけアクティブになる。
これは、先ほども書いたが全てのデルタ遅延が処理された後でカウンタのセットアップチェックを行うためセットアップ時間が0となり5ns以上では無いのでアサートする。
一方、通常のプロセスはカウンタの値が更新される1つ前のデルタサイクルでセットアップチェックを行うためセットアップ時間が10nsとなり5ns以上なのでアサートされない。
ここで注意として実行遅延プロセスでクロックイベント等を評価した場合、デルタサイクルが無い場合を除き真が返ってくることはない。
また、実行遅延プロセスでデルタサイクルを発生させる事は禁止になっている。
簡単に言うと、実行遅延プロセスで「if ( CLK'event and CLK = '1' )」と書いても既にクロックイベントは終了しているのでこのIF文は実行される事は無い。と言う事と、実行遅延プロセスで「A <= B;」等と信号の代入を行ってはダメと言う事である。
2007年08月28日
シミュレーション・サイクル
VHDLにおけるシミュレーション・サイクルを以下に紹介する。
1.TcにTnを代入
ここで、アクティブなドライバや開始するプロセスがない、または
シミュレーションの最大時刻ならばシミュレーション終了
2.信号を更新する。
3.更新された信号にセンシティブな全てのプロセスを実行する
4.以下の中で最も近い時刻をTnに入れる
・シミュレーションの最大時刻
・ドライバがアクティブになる次の時刻
・プロセスが再開する次の時刻
この処理後、TcとTnが同じならばデルタサイクルなので
デルタサイクルを進めて1に戻る
5.実効遅延プロセスを実行して1に戻る
Tc:現在のシミュレーションサイクルの時刻
Tn:次のシミュレーションサイクルの時刻
VHDLシミュレータはこの処理を繰り返し行いシミュレーションを進めている。
これを理解出来るとデルタ遅延や実行遅延プロセス等の理解も早いと思うので是非理解したい所だ。
実行遅延プロセスについては次の機会に書こうと思う。
2007年08月23日
Hello、 world!
ここ数日、外出などで忙しかったためBlogが更新出来なかった。
平日1件のペースを維持したいが、かなり大変な事だと感じた。FPGAの部屋さんは、これを数年続けているのだから本当に尊敬する。
なつたんさんの所でVHDLのShortCodingのランキングサイトが紹介されていた。
早速VHDLでHelloWorldに参加!このおもしろさにはまってしまった。1時間ほど考えたが、これ以上小さくするのは無理なのかな?って事でechoにも参加!これ以降の問題は疲れたので終了!
やっぱりシンプルな問題がおもしろい。
2007年08月16日
VHDL TIPS 「rangeでパラメータ制約」
モジュールのパラメータチェックは「assert」を使用する事が多いが、単純な範囲制約であれば「range」を使用する事も出来る。
「range」を使用すると静的パラメータであればコンパイル時にチェックし、動的パラメータであれば範囲外になった瞬間Fatalになる。(ModelSim)
以下にサンプルソースを紹介する。
このサンプルは「integer」データタイプのシフトレジスタでシフト数と最大データ値を指定する事が出来る。
ただし、範囲制約として、シフト値は1〜10(ディフォルト5)、入出力データは指定された最大データ値(ディフォルト128)を入れている。
use IEEE.std_logic_1164.all;
-- integerデータタイプの遅延モジュール
entity INT_DLY is
generic
(
-- 遅延クロック数
COUNT : in integer range 1 to 10 := 5;
-- データの最大値
DMAX : in integer := 128
);
port
(
CLK : in std_logic;
DIN : in integer range 0 to DMAX;
DOUT : out integer range 0 to DMAX
);
end INT_DLY;
architecture behavior of INT_DLY is
-- integerの配列定義
type int_vector is array ( natural range <> ) of integer;
-- 遅延をシフトレジスタで実現するためのレジスタ配列
signal DELAY : int_vector(COUNT-1 downto 0);
begin
-- integerのシフトレジスタ
process ( CLK ) begin
if ( CLK'event and CLK = '1' ) then
DELAY(0) <= DIN;
for i in 1 to COUNT-1 loop
DELAY(i) <= DELAY(i-1);
end loop;
end if;
end process;
DOUT <= DELAY(COUNT-1);
end behavior;
このモジュールをCOUNT=11で呼び出した場合、コンパイル時にエラーとなる。
また、DMAX=128でコンパイル、シミュレーションしDINが0〜128を超えた瞬間にFatalとなりシミュレーションがストップする。
このように「range」で範囲制約を行うと使用する側から見て制約が解りやすく、ライブラリ等の共有モジュールには入れておきたい。
2007年08月13日
VHDL TIPS 「std_matchの使用法」
例えば2つのSTD_LOGICを単純に比較すると'1'や'0'の時は何も疑問なく結果が出ると思う。ただSTD_LOGICは'U'や'-'などの状態も存在する。
これらが含まれた比較を行った場合はどうなるか?
シミュレーションでは単純に=(イコール)で比較した場合は正確に同じ状態でないとイコールとはならない。
合成ツール(XST)では'-'や'X'はドントケアとして処理し最適化される。
この事から、シミュレーションと実機の動作不一致が発生する。これを回避するために「numeric_std」に含まれる「std_match」ファンクションが使用できる。
std_matchは'-'をドントケアとするだけでなく'U'などの不定値が一致した場合でもイコールとしないような動作をして実機と一致させている。
(XSTは'X'をドントケア、std_matchは不定値として処理するので動作が一致していないが、numeric_stdを参考にテーブルを少し変えるだけで簡単に新たなファンクションを作成できる)
しかし、そもそも'-'(ドントケア)と比較する事自体、意味が無いなんて思われるかもしれない。それはその通りなのだが、データがベクタタイプになると意味が出てくる。例えば、あるビットだけ比較しなくて良い場合などはその部分を'-'をドントケアとして比較すれば一度に記述する事が出来る("0-01--10"等)
以下にstd_matchの動作を紹介する。
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
entity test is
end test;
architecture behavior of test is
signal result_match : boolean := FALSE;
signal result_equ : boolean := FALSE;
signal testdata : std_logic_vector(2 downto 0) := (others => '0');
begin
-- 固定値
testdata(2) <= '1';
testdata(0) <= '0';
-- std_logicの全ての状態を繰り返す
process begin
testdata(1) <= 'U';
wait for 10 ns;
testdata(1) <= 'X';
wait for 10 ns;
testdata(1) <= '0';
wait for 10 ns;
testdata(1) <= '1';
wait for 10 ns;
testdata(1) <= 'Z';
wait for 10 ns;
testdata(1) <= 'W';
wait for 10 ns;
testdata(1) <= 'L';
wait for 10 ns;
testdata(1) <= 'H';
wait for 10 ns;
testdata(1) <= '-';
wait for 10 ns;
end process;
-- イコール(=)で比較した場合
process begin
if ( testdata = "1-0" ) then
result_equ <= TRUE;
else
result_equ <= FALSE;
end if;
wait on testdata;
end process;
-- std_matchを使った場合
process begin
if ( std_match(testdata, "1-0")) then
result_match <= TRUE;
else
result_match <= FALSE;
end if;
wait on testdata;
end process;
end behavior;
これの結果は、
このように、単純に=(イコール)で比較した場合は完全に一致した"1-0"の場合だけTRUEになり、std_matchを使用した場合はtestdata(2)とtestdata(0)だけの比較になるので常時TRUEになる。
2007年08月09日
イベントとトランザクション
VHDLにはトランザクションという陰の存在がある。
イベントもトランザクションの一部で、イベントを伴うトランザクションと伴わないトランザクションがあると考える事が出来る。
イベントは値が変化した場合に発生する。しかしトランザクションは値の代入が行われるだけで発生する。
以下の例では信号Aに対してイベントは発生しないがトランザクションは発生する。
しかし信号Bはイベントもトランザクションも発生しない。これは、Bへの代入が発生するためには信号Aに対してトランザクションでは無くイベントが必要になるためである。
process begin
wait for 1 ns;
A <= '1'
end process;
B <= A;
もう少し複雑な例をModelSimでシミュレーションして動作を見てみることにする。
以下にサンプルソースを紹介する。
これは、単純な2入力のORで1本の入力は'1'固定、もう1本は5ns毎に'1'/'0'を繰り返している。ただし'1'の固定値もトランザクションを発生させるために5ns毎に代入を繰り返している。
そして各信号のイベントとトランザクションをカウントして変化を確認する。
use IEEE.std_logic_1164.all;
use IEEE.std_logic_unsigned.all;
entity test is
end test;
architecture behavior of test is
signal DATA1 : std_logic := '1';
signal DATA2 : std_logic := '1';
signal DATA3 : std_logic := '1';
signal DATA1_TR : integer := 0;
signal DATA1_EV : integer := 0;
signal DATA2_TR : integer := 0;
signal DATA2_EV : integer := 0;
signal DATA3_TR : integer := 0;
signal DATA3_EV : integer := 0;
begin
process begin
wait for 5 ns;
DATA1 <= '1' after 1 ns;
wait for 5 ns;
DATA1 <= '1' after 2 ns;
wait for 5 ns;
DATA1 <= '1' after 1 ns;
wait for 5 ns;
DATA1 <= '1' after 2 ns;
end process;
process begin
wait for 5 ns;
DATA2 <= '0' after 1 ns;
wait for 5 ns;
DATA2 <= '1' after 2 ns;
wait for 5 ns;
DATA2 <= '0' after 2 ns;
wait for 5 ns;
DATA2 <= '1' after 1 ns;
end process;
DATA3 <= DATA1 or DATA2;
-- トランザクション発生でカウントアップ
process begin
wait on DATA1'transaction;
DATA1_TR <= DATA1_TR + 1;
end process;
process begin
wait on DATA2'transaction;
DATA2_TR <= DATA2_TR + 1;
end process;
process begin
wait on DATA3'transaction;
DATA3_TR <= DATA3_TR + 1;
end process;
-- イベント発生でカウントアップ
process begin
wait on DATA1;
DATA1_EV <= DATA1_EV + 1;
end process;
process begin
wait on DATA2;
DATA2_EV <= DATA2_EV + 1;
end process;
process begin
wait on DATA3;
DATA3_EV <= DATA3_EV + 1;
end process;
end behavior;
これの結果は、
この様になる。一つずつ見ていくと、
DATA1とDATA3のイベントは変化がない。これは先ほどの説明の通り。
DATA2のイベントとトランザクションは同じで、これは通常の動作。
DATA1とDATA2のトランザクションはディレイ値は違うが代入時に変化する。
DATA3のトランザクションはDATA2のイベントが発生した時に代入が発生するのでそのタイミングで変化する。
頭が混乱しそうになるがじっくり考えると理解できるとおもう。
2007年08月03日
VHDL TIPS 「ディファード定数の使用法」
ディファード定数という難しそうな名前の余り意味の無い機能がある。
これは、パッケージで定数の宣言だけしておき実際の値はパッケージボディで割り当てるという機能だ。通常はパッケージとパッケージボディは1:1で記述すると思うのでディファード定数にすると余計にややこしくなる。
パッケージボディでは無くアーキテクチャで値を割り当てる事が出来れば、定数宣言を強制できるという意味で、まだ使えたのかもしれないが、これは出来ずあくまでもパッケージボディ内に限られている。
無理に使おうと思えば、パッケージだけライブラリのようにしてパッケージボディはパッケージユーザに作らせ定数宣言を強制させたい場合くらいだろうか。
ただ、この場合もXSTはサポートしていないのでシミュレーションだけにしておいた方が良い。
以下に使用例を紹介する。
-- ディファード定数
constant const_int : integer;
end test_pkg;
package body test_pkg is
-- ディファード定数の定数決定
constant const_int : integer := 10;
end test_pkg;
2007年07月31日
VHDL TIPS 「incomplete typeの使用法」
VHDL TIPS 「access typeの使用法」に関連して「incomplete type」を紹介する。
ソフトウェアでは、複数のデータを管理する時、次のデータポインタを自分のデータ内に含めてチェーンさせるような事がよくある。
これを表現するには、ポインタとデータパッキングの考え方が必要だと思うが、VHDLではaccess typeとrecord typeがあるので、この点では問題はない。
ただ、VHDLではaccess typeでポインタを定義する時には、record typeの定義が必要で、record typeでデータパッキングを定義する時には、access typeの定義が必要になる。この矛盾を回避する時に「incomplete type」を使用する。
使用方法は簡単で、あとで使用する名前を先にtype宣言してしまうだけである。
以下に定義部分だけの例を紹介する。
type DATA_PACK;
-- 仮に定義した名前でポインタを定義
type PACK_PTR is access DATA_PACK;
-- 仮に定義した名前に実際の定義を割り当て
type DATA_PACK is record
next_pack : PACK_PTR;
header : std_logic_vector(7 downto 0);
data : std_logic_vector(31 downto 0);
end record;
2007年07月30日
VHDL TIPS 「パッシブプロセスの使用法」
VHDLにはパッシブプロセスという物がある。
パッシブプロセスとはエンティティ内にあるプロセスの事で、主にシミュレーションでインターフェースのチェック等を行う事が出来る。
同様のことがアーキテクチャ内でも可能だがエンティティ内で行うことにより複数のアーキテクチャをコンフィギュレーションで選択する場合などに共有できるメリットがある。
以下の例では、パッシブプロセスでデータのセットアップ/ホールドをチェックしている。
use IEEE.std_logic_1164.all;
use IEEE.std_logic_unsigned.all;
entity PASSIVE_TEST is
generic
(
SETUP : in time := 10 ns;
HOLD : in time := 5 ns
);
port
(
CLK : in std_logic;
DIN : in std_logic;
DOUT : out std_logic
);
begin
-- XSTはパッシブプロセスをサポートしていない
-- pragma translate_off
process
variable PREV_DIN : std_logic := 'U';
variable PREV_DIN_TIME : time := 0 ns;
variable PREV_CLK_TIME : time := 0 ns;
begin
-- CLKかDINに変化があれば次に進む
wait on CLK, DIN;
-- DINに変化があった場合
if ( DIN /= PREV_DIN ) then
PREV_DIN_TIME := now;
PREV_DIN := DIN;
if ( now - PREV_CLK_TIME < HOLD ) then
assert false
report "Hold Time was not suitable."
severity WARNING;
end if;
end if;
-- CLKに変化があった場合
if ( CLK'event and CLK = '1' ) then
PREV_CLK_TIME := now;
if ( now - PREV_DIN_TIME < SETUP ) then
assert false
report "Setup Time was not suitable."
severity WARNING;
end if;
end if;
end process;
-- pragma translate_on
end PASSIVE_TEST;
-- ビヘイビア1
architecture BEHAVIOR1 of PASSIVE_TEST is
begin
process ( CLK ) begin
if ( CLK'event and CLK = '1' ) then
DOUT <= DIN;
end if;
end process;
end BEHAVIOR1;
-- ビヘイビア2
architecture BEHAVIOR2 of PASSIVE_TEST is
begin
process ( CLK ) begin
if ( CLK'event and CLK = '1' ) then
DOUT <= DIN after 2 ns;
end if;
end process;
end BEHAVIOR2;
この様にパッシブプロセスとしてセットアップ/ホールドをチェックする事で、アーキテクチャ「BEHAVIOR1 」と「BEHAVIOR2 」で共有する事ができる。
ただし注意点として、パッシブプロセス内では信号(signal)定義や代入を行うことが出来ないのでprocess内でvariableを使用するかコンカレント文で直接ポートをチェックするしか無い。また、XilinxのXSTではサポートされていないのでtranslate_offする必要もある。
以下に上記例のコンフィギュレーションを含めたテストベンチを紹介する。
use IEEE.std_logic_1164.all;
use IEEE.std_logic_unsigned.all;
entity PASSIVE_TEST_TB is
end PASSIVE_TEST_TB;
architecture behavior of PASSIVE_TEST_TB is
component PASSIVE_TEST
generic
(
SETUP : in time := 10 ns;
HOLD : in time := 5 ns
);
port
(
CLK : in std_logic;
DIN : in std_logic;
DOUT : out std_logic
);
end component;
signal CLK : std_logic := '1';
signal DIN : std_logic := '0';
signal DOUT : std_logic := '0';
begin
U_PASSIVE_TEST : PASSIVE_TEST
generic map
(
SETUP => 10 ns,
HOLD => 5 ns
)
port map
(
CLK => CLK,
DIN => DIN,
DOUT => DOUT
);
process begin
CLK <= '1';
wait for 50 ns;
CLK <= '0';
wait for 50 ns;
end process;
process begin
DIN <= '0' after 3 ns; -- Hold Error;
wait for 200 ns;
DIN <= '1' after 91 ns; -- Setup Error;
wait for 200 ns;
end process;
end behavior;
-- ビヘイビア1を選択
configuration PASSIVE_TEST_CONFIG1 of PASSIVE_TEST_TB is
for behavior
for U_PASSIVE_TEST : PASSIVE_TEST use entity work.passive_test(BEHAVIOR1);
end for;
end for;
end PASSIVE_TEST_CONFIG1;
-- ビヘイビア2を選択
configuration PASSIVE_TEST_CONFIG2 of PASSIVE_TEST_TB is
for behavior
for U_PASSIVE_TEST : PASSIVE_TEST use entity work.passive_test(BEHAVIOR2);
end for;
end for;
end PASSIVE_TEST_CONFIG2;
これをModelsimでシミュレーションすると、両方のコンフィギュレーションでアサーションが有効であることが分かる。
2007年07月27日
シミュレーションデルタ
VHDLを理解する上で「シミュレーションデルタ」は重要な項目だと思う。
しかし、理解せずに設計を行っても、設計/シミュレーションが問題なく行える事が多く存在自体を知らない人もいる(かつての私...)。
しかし、これを理解出来ていないとシミュレーションと実機の動作不一致を引き起こす事がある。
以下はその例だ。
use IEEE.std_logic_1164.all;
use IEEE.std_logic_unsigned.all;
entity DD_TEST is
end DD_TEST;
architecture behavior of DD_TEST is
signal CLK0 : std_logic;
signal CLK1 : std_logic;
signal COUNT : std_logic_vector(3 downto 0) := (others => '0');
signal CNT_OK : std_logic_vector(3 downto 0) := (others => '0');
signal CNT_NG : std_logic_vector(3 downto 0) := (others => '0');
begin
-- クロック生成
process begin
CLK0 <= '0';
wait for 10 ns;
CLK0 <= '1';
wait for 10 ns;
end process;
-- クロックを複製
CLK1 <= CLK0;
-- CLK0でCOUNTをインクリメント
process ( CLK0 ) begin
if ( CLK0'event and CLK0 = '1' ) then
COUNT <= COUNT + '1';
end if;
end process;
-- COUNTをCLK0で1段遅らせる
process ( CLK0 ) begin
if ( CLK0'event and CLK0 = '1' ) then
CNT_OK <= COUNT;
end if;
end process;
-- COUNTをCLK1で1段遅らせる
process ( CLK1 ) begin
if ( CLK1'event and CLK1 = '1' ) then
CNT_NG <= COUNT;
end if;
end process;
end behavior ;
同じ「COUNT」を「CLK0 」と「CLK1 」で1クロック遅延させたのだが結果は以下のように「CNT_OK」と「CNT_NG 」で異なる。
実機では期待通りの動作を行うのでシミュレーションと実機の動作不一致が起こっている。
なぜこのような結果になるかというと、
・CLK0とCLK1は同じように見えるが、信号の代入によってシミュレータの内部では1デルタサイクルのずれ(デルタ遅延)が発生している。
・CLK0でインクリメントしたCOUNTも、CLK0に比べて1デルタサイクルずれている。
ここでCNT_NGはCNT1の立ち上がりエッジ(CLK0の立ち上がりエッジ処理の次のデルタサイクル)で処理されるが、シミュレーションサイクルの処理規定により、この時既にCOUNTが更新されてしまっている。
前例は、故意に動作不一致を起こすコードを記述したが、次の例ではVHDLの記述制約のために動作不一致を起こした例を紹介する。
use IEEE.std_logic_1164.all;
use IEEE.std_logic_unsigned.all;
library unisim;
use unisim.vcomponents.all;
-- クロックバッファを入れてクロックイネーブルを生成するモジュール
entity CLKGEN is
port
(
CLK_IN : in std_logic;
CLK_OUT : out std_logic;
CKE : out std_logic
);
end CLKGEN;
architecture behavior of CLKGEN is
signal CLK_BUFF : std_logic;
signal COUNT : std_logic_vector(3 downto 0) := (others => '0');
begin
-- Xilinxのグローバルクロックバッファ
U_BUFG : BUFG port map ( I => CLK_IN, O => CLK_BUFF );
-- VHDLの制約(portでoutに指定した信号は内部で参照出来ない)のため、
-- クロックイネーブルを作るクロックを一度、ローカル信号に入れる。
-- クロックの出力
CLK_OUT <= CLK_BUFF;
-- クロックイネーブルを作るためのカウンタ
process ( CLK_BUFF ) begin
if ( CLK_BUFF'event and CLK_BUFF = '1' ) then
COUNT <= COUNT + '1';
end if;
end process;
-- クロックイネーブル
process ( CLK_BUFF ) begin
if ( CLK_BUFF'event and CLK_BUFF = '1' ) then
if ( COUNT = (COUNT'range => '0') ) then
CKE <= '1';
else
CKE <= '0';
end if;
end if;
end process;
end behavior ;
このコンポーネントは、あるFPGAのクロック関連の回路を生成するために作られたものだ。このように関連性のある回路を一つのコンポーネントにまとめる事は頻繁に行われていると思う。
ソースコード中のコメントにもあるように、VHDLではポートに出力する信号は内部で参照出来ない制約がある。そのためこの例の場合、出力するクロックと内部で使用するクロックが1デルタサイクルずれて、このコンポーネントから出力するクロックでクロックイネーブルを使用するとシミュレーションと実機の動作不一致を起こす。(ただしこの例ではクロックイネーブルの位相は動作に関係ないので事実上問題はないが)
ちなみに、以前紹介したソースコードで
wait for 0 ns;
のような記述をしたが、これはデルタ遅延を発生させている。
2007年07月26日
慣性遅延と伝播遅延
VHDLには慣性遅延と伝播遅延の遅延モデルがある。
慣性遅延はその名前の通り慣性を持っており、慣性の値未満の幅しか持たない信号は出力には現れない。逆に慣性の値以上の幅を持った信号は、その信号幅はそのまま遅延時間後に出力に現れる。
このような振る舞いをするため、同期回路等では1クロックより大きい遅延を入れてしまうと1パルス幅の信号は出力に現れなくなってしまう。
慣性遅延は以下のように記述し、ごく一般的に使用する。
B <= A after 1000 ns;
伝播遅延は、慣性遅延と違ってどのような信号でも遅延時間後に入力がそのまま出力に現れる。
伝播遅延は以下のように記述し、配線やディレイラインのモデリングに使用する。ただしクロックに同期した信号の配線モデリングは1クロック以内の遅延値が前提だと思うので慣性遅延でも問題は起きないが、最近の高速シリアル信号等は伝播遅延を使用しないとモデリングが大変になる。(昔、小さい慣性遅延をいくつも入れた記述を見たことがある...)
B <= transport A after 1000 ns;
シミュレータ側から見ると慣性遅延は1つのイベントバッファを使いイベントが重なると上書きされるような動作を行い、伝播遅延はイベントを無限のFIFOに入れるような動作を行う。
このため遅延させる信号の最小イベント間隔が遅延時間以上の場合は慣性遅延と伝播遅延の結果に違いはない。
2007年07月24日
VHDL TIPS 「access typeの使用法」
VHDLにはアクセスタイプと言うC言語のポインタのような機能がある。
ほとんど使用する機会は無いと思うがおもしろい機能なので紹介したい。
C言語のポインタが分かる人なら以下のサンプルを見て理解できると思う。
このサンプルは4Bank構成のメモリで最初はメモリ領域を確保せず、Bank毎に最初にアクセスがあったときに初めてメモリ領域を確保している。また、リセットを入れると全てのメモリ領域を開放する。
(記述としてはジェネリックを使わず固定値にしていたり、メモリとしても非効率かつ無意味な機能があったりするが、あくまでアクセスタイプのサンプルなので...)
use IEEE.std_logic_1164.all;
use IEEE.std_logic_unsigned.all;
-- 16Wordx8Bitx4Banksの同期メモリ
entity test_accs is
port
(
CLK : in std_logic; -- クロック
RST : in std_logic; -- リセット
ADDR : in std_logic_vector(3 downto 0); -- アドレス(16Word=4Bit)
BA : in std_logic_vector(1 downto 0); -- バンクアドレス(4Bank=2Bit)
CS : in std_logic; -- チップセレクト
WE : in std_logic; -- ライトイネーブル
INDATA : in std_logic_vector(7 downto 0); -- ライトデータ
OUTDATA : out std_logic_vector(7 downto 0) -- リードデータ
);
end test_accs;
architecture behavior of test_accs is
-- データ幅8Bitのサブタイプ
subtype BUSWIDS is std_logic_vector(7 downto 0);
-- 16Wordのサブタイプ
subtype ARRAYNUM is std_logic_vector(15 downto 0);
-- 1Bankのデータタイプ
type CELLARRAY is array(ARRAYNUM'range) of BUSWIDS;
-- 1Bankのアクセスタイプ(ポインタ)
type BNKACCESS is access CELLARRAY;
-- 1Bankのアクセスタイプの4配列(ポインタの配列)
type BNKARRAY is array(3 downto 0) of BNKACCESS;
begin
process
-- アクセスタイプの4配列をインスタンス
variable BANK : BNKARRAY := (others => NULL);
variable BA_INT : integer;
variable ADDR_INT : integer;
begin
-- クロックの立ち上がりエッジで動作する
wait until CLK = '1';
-- データ型変換
BA_INT := conv_integer(BA);
ADDR_INT := conv_integer(ADDR);
-- チップセレクトがイネーブルで選択されているバンクに
-- メモリが割り当たっていない場合
if ( RST = '0' and CS = '1' and BANK(BA_INT) = NULL ) then
-- 選択されているバンクにメモリを確保する
BANK(BA_INT) := new CELLARRAY;
-- 確保したメモリ領域を0クリアする
for i in ARRAYNUM'range loop
BANK(BA_INT)(i) := BUSWIDS'(others => '0');
end loop;
end if;
-- チップセレクトがイネーブルでライトイネーブルがイネーブルの場合
if ( RST = '0' and CS = '1' and WE = '1' ) then
-- メモリにデータを書き込む
BANK(BA_INT)(ADDR_INT) := INDATA;
end if;
-- チップセレクトがイネーブルの場合
if ( RST = '0' and CS = '1' ) then
-- メモリからデータを読み出す
OUTDATA <= BANK(BA_INT)(ADDR_INT);
-- チップセレクトがイネーブルでない場合
else
-- データを弱いHIGH(プルアップ)にする
OUTDATA <= (others => 'H');
end if;
-- リセットがイネーブルの場合
if ( RST = '1' ) then
-- メモリ割り当てを解放する
for i in BNKARRAY'range loop
if ( BANK(i) /= NULL ) then
deallocate(BANK(i));
BANK(i) := NULL;
end if;
end loop;
end if;
end process;
end behavior;
まとめとして、サンプルコードでのアクセスタイプは、
type BNKACCESS is access CELLARRAY;
でアクセスタイプを定義して、
variable BANK : BNKARRAY := (others => NULL);
でアクセスタイプのインスタンスを作成し、
BANK(BA_INT) := new CELLARRAY;
でメモリ領域を割り当て、
deallocate(BANK(i));
でメモリ領域を開放する。
以上のような流れで使用している。
これに関連して今度は「incomplete type」を紹介しようと思う。
2007年07月21日
2007年07月11日
VHDLでC言語の標準関数を使う
VHDLでC言語の標準関数に近いライブラリがココにある。
制約は多いが使い勝手がよく、私はModelSimにライブラリ登録して使用している。
以下はprintfを使用した例だ。
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.std_logic_unsigned.all;
library c;
use c.stdio_h.all;
library modelsim_lib;
use modelsim_lib.util.all;
entity test_printf is
end test_printf;
architecture behavior of test_printf is
signal STV8 : std_logic_vector(7 downto 0);
begin
process begin
-- これはModelSimのファンクション
signal_force( "STV8", "00000000", 0 ns, deposit, -1 ms, 0 );
wait;
end process;
process begin
wait for 100 ns;
STV8 <= STV8 + '1';
printf("abc");
printf("def\n");
printf("std_logic_vector=0x%x(%d)\n", STV8, STV8);
end process;
end behavior;
これをModelSimでシミュレーションするとTranscriptウィンドウに次のように表示される。
# abcdef
# std_logic_vector=0x0(0)
# abcdef
# std_logic_vector=0x1(1)
〜
# abcdef
# std_logic_vector=0x12(18)
# abcdef
# std_logic_vector=0x13(19)
注意として、環境によってはModelSimでFatalエラーが出るときがある。
この時は、Fatalエラーが出た行(ライブラリの「regexp_h.vhd」内)の
t(character'pos(c)):=tset;
を次のように書き換え
tmpint := character'pos(c);
t(tmpint):=tset;
さらにprocedureの最初に次の変数定義を入れればOKとなる。
variable tmpint: integer;
これはたぶんModelSimの問題だと思う。
2007年07月06日
秀丸の強調VHDLキーワード
秀丸にはVHDLの強調キーワードが入っていないが、自作して登録する事ができる。自分がいつも使っているファイルは言語仕様だけではなく、よく使うライブラリ(std_logic_1164)等も入れている。見え方は左のようなイメージになる。
数年間使いながら少しずつ修正しているので、大分良くなっていると思う。
ファイルはココからダウンロード出来る。
Verilogもココからダウンロード出来るが、これは余り使っていないので洗練されていない。
2007年07月04日
秀丸でVHDLコンパイルマクロ
ソースコードを編集するときはいつも秀丸エディタを使っている。
このエディタは有名なので知っていると思うが本当に使いやすくておすすめである。
これを使っているとISEのエディタは使い物にならなくて外部エディタの設定を以下のように変更している。
ISEを立ち上げ、メニューから「Edit→Preferences→ISE General→Editors」を開き、Text editorのEditor:を「Custom」にしてCommand line syntax:に「hidemaru /j$2 $1」を入れる。
(関係無いが下のConstraints entryはText editorにしておいた方が良いと思う)
これでISEのソースリストでダブルクリックすると秀丸エディタが起動するようになる。
さらに便利に使えるようにModelSimでコンパイルを行いシンタックスチェックを行えるマクロを作った。
自分はこのマクロをF12に割り当て編集したら必ずチェックするようにしている。エラーがあった場合は別ウィンドウで秀丸エディタが起動しエラーのリストを表示するのでチェックしたいエラーリストの行でF10を押すとソースコードのエラー行にジャンプする。
VHDLで問題になるのはパッケージを使っている場合にパッケージを見つけられなくてエラーになってしまう。この対策のためWindowsの環境変数「TEMP」のフォルダをシンタックスチェック用のWorkフォルダとして使用しパッケージを先にシンタックスチェックする事でWorkフォルダにパッケージのコンパイルデータを作成する。その後、パッケージを使っているファイルをコンパイルすればWorkの中でパッケージを見つけられるのでエラーは発生しなくなる。
コンパイルデータをWorkに残しているので色々なファイルをコンパイルしていると「TEMP」フォルダが肥大化してくる。そこで、これを削除するマクロも作成した。自分はこのマクロをShift+F12に割り当てている。
使用頻度は多くないが、同じ名前のパッケージで内容が違う場合等は特に注意する必要がある。
使用方法:
VHDLファイルを開いた状態(拡張子がVHDである必要がある)でシンタックスチェックマクロを実行する。(拡張子がVの時はVerilogコンパイルも行える)
エラーがあっても無くても結果のウィンドウが開くのでそれを確認する。
エラーの場合はチェックしたいエラーリストの行でTAGジャンプが出来る。
シンタックスチェックをしたデータは環境変数「TEMP」に残るのでシンタックスチェックデータ削除マクロを実行すれば削除出来る。(パッケージデータを残しておくと後で便利なので削除しない方が使い勝手がよい)
環境設定:
Windowsの環境変数に「TEMP」を設定する。(標準で設定されていると思うが)
注意:
このファイルの使用は自由ですが無保証です。
(本当に適当に作ってあります)
ダウンロード:
シンタックスチェックマクロ
シンタックスチェックデータ削除マクロ
2007年07月03日
VHDL TIPS 「アナログスイッチのモデリング」
システム全体のシミュレーションを行おうと思ったときアナログスイッチのモデリング方法がわからず色々調べて一応完成した。完成して動作すれば何てことは無いが結構悩んだ。
動作はCONTROLが'1'の時はPORTAとPORTBは双方向で接続しCONTROLが'0'の時は切断する。
use IEEE.std_logic_1164.all;
entity ANALOG_SW is
port
(
PORTA : inout std_logic;
PORTB : inout std_logic;
CONTROL : in std_logic
);
end ANALOG_SW;
architecture BEHAVIOR of ANALOG_SW is
begin
PROCESS
VARIABLE last_time : time;
BEGIN
WAIT ON PORTA'TRANSACTION, PORTB'TRANSACTION,
CONTROL'TRANSACTION UNTIL last_time /= NOW;
last_time := NOW;
PORTA <= 'Z';
PORTB <= 'Z';
WAIT FOR 0 ns;
if ( CONTROL = '1' ) then
PORTA <= PORTB;
PORTB <= PORTA;
end if;
END PROCESS;
end BEHAVIOR;
2006年03月02日
レコードタイプと合成後のシミュレーション
2006年02月28日
VHDL TIPS 「generateの使用法」
あるバスに対して同じ回路を複数個用意したり、ある回路の有効/無効を切り替えたりしたい事がある。こんなときにgenerateを使用する。
同じ回路を複数個用意する場合の例
OBUF_ADR : OBUF port map ( I => ADR(LCNT), O => X_ADR(LCNT) );
end generate;
上記の例では内部のADRバスに対してXilinxのプリミティブライブラリのOBUFを割り当ててX_ADRとしてピンに出力している。
回路の有効/無効を切り替える場合の例
ASYNC : if ( SYNC = 1 ) generate
process ( CLK ) begin
if ( CLK'event and CLK = '1' ) then
if ( SEL_DELAY = '1' ) then
S0_SYSCMD <= L_SYSCMD1;
else
S0_SYSCMD <= L_SYSCMD2;
end if;
end if;
end process;
end generate;
SYNC : if ( SYNC /= 1 ) generate
process ( SEL_DELAY, L_SYSCMD1, L_SYSCMD2 ) begin
if ( SEL_DELAY = '1' ) then
S0_SYSCMD <= L_SYSCMD1;
else
S0_SYSCMD <= L_SYSCMD2;
end if;
end process;
end generate;
上記の例では、動作クロックを上げたい時には遅延を少なくするために同期回路を有効にし、動作レイテンシを少なくしたい時には非同期回路を有効にする。この切り替えは「SYNC」の0/1を変更するだけで出来る。(C言語の#ifの様な使い方)
このように、ライブラリを設計する時等にgenerateでバス幅や回路レイテンシを指定できるようにすれば利用しやすくなる。
2006年02月27日
VHDL TIPS 「genericの使用法」
回路をライブラリとして再利用するように考えた時、機能の設定をパラメータで渡したいと思うことがある。こんなときにgenericを使用する。
例えばシフトレジスタでディレイ値を指定する場合の使用例
library IEEE;
use IEEE.std_logic_1164.all;
entity BITDELAY is
generic
(
-- 呼び側で指定されないときのディフォルト値を指定できる --
COUNT : integer := 4
);
port
(
CLK : in STD_LOGIC;
RST : in STD_LOGIC;
CKE : in STD_LOGIC;
DIN : in STD_LOGIC;
DOUT : out STD_LOGIC
);
end BITDELAY;
architecture rtl of BITDELAY is
signal L_DELBUFF : std_logic_vector(0 to COUNT-1);
begin
process ( RST, CLK ) begin
if ( RST = '1' ) then
for LPCNT in 0 to COUNT-1 loop
L_DELBUFF(LPCNT) <= '0';
end loop;
elsif ( CLK'event and CLK = '1' ) then
if ( CKE = '1' ) then
L_DELBUFF(0) <= DIN;
for LPCNT in 1 to COUNT-1 loop
L_DELBUFF(LPCNT) <= L_DELBUFF(LPCNT-1);
end loop;
end if;
end if;
end process;
DOUT <= L_DELBUFF(COUNT-1);
end rtl;
-- genericを使う --
DELAY10 : BITDELAY generic map ( 10 )
port map ( CLK, RST, CKE, DIN, DOUT );
-- genericを使わない(ディフォルト値を使用) --
DELAYDEF : BITDELAY port map ( CLK, RST, CKE, DIN, DOUT );
DELAY10はDINが10クロック遅れてDOUTに出力される。
DELAYDEFはDINが4クロック遅れてDOUTに出力される。
ちなみに、上記の例ではxilinxのSRL16は生成されないので回路規模が大きくなってしまう。SRL16を使用する場合はリセットを使わない様にする必要がある。
2006年02月24日
VHDL TIPS 「レコードタイプの使用法」
VHDLには、関連したデータをレコードタイプで1つのデータにまとめることが出来る。
これを使用すると、バラバラだった信号をまとめて管理することが出来てソースの見通しが良くなり修正も容易になる。SDRAMの制御信号などは良い例だと思う。
ただし制限として、レコードタイプをポートに割り当てるときに一意にin/out/inoutが決定してしまうので、inとoutが混在する場合はinとout用にレコードタイプを分けるしかない。
(inoutにする方法もあるが、ポートマップを見ただけでは信号の方向がわからなくなるので、使用しないようにしている)
使用例
type SDRAM_IF is record
A : std_logic_vector(13 downto 0);
BA : std_logic_vector(1 downto 0);
CKE : std_logic;
CS_N : std_logic;
RAS_N : std_logic;
CAS_N : std_logic;
WE_N : std_logic;
DQM : std_logic_vector(3 downto 0);
end record;
-- 信号宣言 --
signal SDRAM_CONT1 : SDRAM_IF;
signal SDRAM_CONT2 : SDRAM_IF;
signal RF_CMD : std_logic_vector(3 downto 0);
-- 以下使用方法 --
process ( RST, CLK ) begin
if ( RST = '1' ) then
-- カンマで分けて一度に代入することも出来る --
SDRAM_CONT2 <= ((others => '0'), (others => '0'), '0',
'0', '0', '0', '0', (others => '0'));
elsif ( CLK'event and CLK = '1' ) then
-- 同じデータタイプは一度に全て代入できる --
SDRAM_CONT2 <= SDRAM_CONT1;
end if;
end process;
-- 信号名と要素をピリオドで各要素に代入できる --
SDRAM_CONT1.CS_N <= RF_CMD(0);
SDRAM_CONT1.RAS_N <= RF_CMD(1);
SDRAM_CONT1.CAS_N <= RF_CMD(2);
SDRAM_CONT1.WE_N <= RF_CMD(3);
xilinxのxstはレコードをサポートしており、最上位階層に使用した時のピンの名前は信号名と要素の名前をアンダーバーでつなげた形になる。
例えば上記のSDRAM_CONT2のピン配置をUCFで行うと次の様になる。
NET "SDRAM_CONT2_RAS_N" LOC = "D14" ;
2006年02月23日
VHDL TIPS 「条件付信号代入文と選択信号代入文の使用法」
process文以外でシンプルに条件式を記述する方法がある。
(個人的にはわかりづらいのと思うので、あまり使う機会は無いが...)
使用例:
signal ENB : integer;
signal DIN : std_logic_vector(63 downto 0);
signal DOUT : std_logic_vector(63 downto 0);
with ENB select
DOUT <= DIN when 0,
(others => '0') when 1,
(others => '1') when 2,
(others => 'Z') when others;
DOUT <= DIN when ENB = 0
else (others => '0') when ENB = 1
else (others => '1') when ENB = 2
else (others => 'Z');
上記の条件付信号代入文例と選択信号代入文例は同じ動作をする。
2006年02月22日
VHDL TIPS 「リダクション演算子の使用法」
VHDLのライブラリにリダクション演算子と言うものがある。リダクション演算子はバスの全ての信号に対して論理演算したいときなどに便利に使える。
使用方法
use ieee.std_logic_misc.all;
-- 信号宣言 --
signal ABUS : std_logic_vector(31 downto 0);
signal RES : std_logic;
-- リダクション演算 --
RES <= and_reduce(ABUS);
上記の様に記述すると「RES」には、ABUS(0)からABUS(31)を全てANDした結果が入る。
使用例
RES <= or_reduce(ABUS);
RES <= nand_reduce(ABUS);
RES <= nor_reduce(ABUS);
RES <= xor_reduce(ABUS);
RES <= xnor_reduce(ABUS);
2006年02月21日
VHDL TIPS 「rangeの使用法」
アトリビュートの[range]を使うとベクタサイズが変更になる場合などに変更箇所が少なくてすむようになる。
使用方法
signal TEST : std_logic_vector(TVS-1 downto 0);
if ( TEST = (TEST'range => '1') ) then
TEST <= TEST + '1';
end if;
上記の様に記述すると「TEST'range」は単純に(3 downto 0)に置き換えられる。
使用例
...
for i in TEST'range loop
...
type TESTARRAY is array(TEST'range) of std_logic_vector(1 downto 0);
2006年02月10日
VHDL TIPS 「FFの初期値」
FPGAはコンフィグレーション後のFFの初期値をプログラムできる。この初期値は、セット/リセットやクロックが入っていない状態での値で、以下の様に設定できる。
signal SL : std_logic := '1';
信号定義時に初期値が入っていないと非同期リセットの値がそのまま初期値になる。(非同期リセットが入っていない場合の初期値は保証されない)