はじめに
FPGA や ASIC 等で異なるクロックで動作する回路同士でデータをやりとりする場合には、非同期 FIFO (Asynchronous First-In First-Out) を使って安全にデータを受け渡すことがあります。
非同期 FIFO を設計する際にはグレイコードを使う方法がよく使われますが、この記事ではグレイコードを使わない非同期 FIFO の実装方法について、何回かにわけて説明します。
- 概要編
- Syncronizer 編
- Pending Register 編 (この記事)
Pending Register の概要
位置付け
グレイコードを使わない非同期 FIFO では、Pending Register は Syncronizer の前段に位置します。(下の図の赤字部分)
Fig.1 グレイコードを使わない非同期 FIFO での Pending Register の位置付け
目的
グレイコードを使わない非同期 FIFO では、同期化回路(Syncronizer) によって、書き込み側から「書き込んだデータ量」を読み出し側に伝え、読み出し側から「読み出したデータ量」を書き込み側に伝えます。同期化回路(Syncronizer) は、異なるクロックで動作する回路同士で情報を安全にやりとりするために、request/acknowlege によるハンドシェイクをしています。詳細は次の記事を参照してください。
この同期化回路(Syncronizer) の問題点は、ハンドシェイク中は新たなデータを転送できないことです。
Pending Register は、Syncronizer がハンドシェイク中で転送出来ない期間の情報を保持しておきます。ただし、単純に保持しているのではなく、場合によっては数を加算するなどの工夫をしています。
動作例
例えば、非同期 FIFO の書き込み側は次のように、ハンドシェイク中はライトした数をペンディングレジスタに保持しておき、ハンドシェイク可能な状態になってからまとめて送信していします。
下の図の例では、書き込み回数(WRITE 信号がアサートされた回数)を読み出し側に伝えています。ペンディングレジスタは Syncronizer がハンドシェイク中(I_READY=0)の時にWRITE 信号がアサートされた時はその回数を保持しておいて I_DATA に出力しています。Syncronizer はハンドシェイク終了後に再度ハンドシェイクをしてペンディングされていた書き込み回数を読み出し側に伝えています。
Fig.2 書き込み側のペンディングレジスタのタイミングチャート例
Pending Register の詳細
ライセンスとヘッダ
-----------------------------------------------------------------------------------
--! @file syncronizer_input_pending_register.vhd
--! @brief SYNCRONIZER INPUT PENDING REGISTER :
--! @version 0.1.2
--! @date 2012/9/10
--! @author Ichiro Kawazome <ichiro_k@ca2.so-net.ne.jp>
-----------------------------------------------------------------------------------
--
-- Copyright (C) 2012 Ichiro Kawazome
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions
-- are met:
--
-- 1. Redistributions of source code must retain the above copyright
-- notice, this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in
-- the documentation and/or other materials provided with the
-- distribution.
--
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-- "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-- OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-- LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-- DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-- THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--
-----------------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
-----------------------------------------------------------------------------------
--! @brief SYNCRONIZER INPUT PENDING_REGSISTER
--! 異なるクロックで動作するパスを継ぐアダプタ(SYNCRONIZER)の入力側レジスタ.
--! * SYNCRONIZER の入力側(I_DATA,I_VAL)に接続し、
--! SYNCRONIZERが入力不可の際(I_RDY='0')に
--! 一時的に入力データを保存(ペンディング)しておくためレジスタ.
--! * ペンディングする際の方法はジェネリック変数OPERATIONで指示する.
--! OPERATION = 0 の場合は常に新しい入力データで上書きされる.
--! OPERATION = 1 の場合は入力データとペンディングデータとをビット単位で
--! 論理和して新しいペンディングデータとする.
--! OPERATION = 2 の場合は入力データとペンディングデータとを加算して
--! 新しいペンディングデータとする.
-----------------------------------------------------------------------------------
entity SYNCRONIZER_INPUT_PENDING_REGISTER is
パラメーター(ジェネリック変数)
Pending Register は、DATA_BITS と OPERATION という二つのパラメーターを持っています。DATA_BITS は I_DATA と O_DATA のビット幅を指定します。OPERATOR はデータの扱いを決めます。これにしては後述はします。
VHDL では次のように記述しています。
generic (
DATA_BITS : --! @brief DATA BITS :
--! データ(IDATA/ODATA)のビット幅を指定する.
integer := 8;
OPERATION : --! @brief PENDING OPERATION :
--! ペンディング(出力待ち)時に次のIVALがアサートされた時に
--! データをどう扱うを指定する.
--! * OPERATION = 0 の場合は常に新しい入力データで上書きされる.
--! * OPERATION = 1 の場合は入力データ(IDATA)と
--! ペンディングデータとをビット単位で論理和して
--! 新しいペンディングデータとする.
--! 主に入力データがフラグ等の場合に使用する.
--! * OPERATION = 2 の場合は入力データ(IDATA)と
--! ペンディングデータとを加算して
--! 新しいペンディングデータとする.
--! 主に入力データがカウンタ等の場合に使用する.
integer range 0 to 2 := 0
);
入出力ポート
Pending Register と Synconizer との接続は次の図のようにします。
Fig.3 Pending Register と Syncronizer の接続
Pending Register の O_DATA ポート、O_VAL ポート、O_RDYポートはそれぞれ Syncronizer の I_DATA ポート、I_VAL ポート、I_RDY ポートに接続します。
port (
-------------------------------------------------------------------------------
-- クロック&リセット信号
-------------------------------------------------------------------------------
CLK : --! @brief CLOCK :
--! クロック信号
in std_logic;
RST : --! @brief ASYNCRONOUSE RESET :
--! 非同期リセット信号.アクティブハイ.
in std_logic;
CLR : --! @brief SYNCRONOUSE RESET :
--! 同期リセット信号.アクティブハイ.
in std_logic;
-------------------------------------------------------------------------------
-- 入力側 I/F
-------------------------------------------------------------------------------
I_DATA : --! @brief INPUT DATA :
--! 入力データ.
in std_logic_vector(DATA_BITS-1 downto 0);
I_VAL : --! @brief INPUT VALID :
--! 入力有効信号.
--! * この信号がアサートされている時はI_DATAに有効なデータが
--! 入力されていなければならない。
in std_logic;
I_PAUSE : --! @brief INPUT PAUSE :
--! * 入力側の情報(I_VAL,I_DATA)を、出力側(O_VAL,O_DATA)に
--! 出力するのを一時的に中断する。
--! * この信号がアサートされている間に入力された入力側の情報(
--! I_VAL,I_DATA)は、出力側(O_VAL,O_DATA)には出力されず、
--! ペンディングレジスタに保持される。
in std_logic;
-------------------------------------------------------------------------------
-- 入力側(PENDING) I/F
-------------------------------------------------------------------------------
P_DATA : --! @brief PENDING DATA :
--! 現在ペンディング中のデータ.
out std_logic_vector(DATA_BITS-1 downto 0);
P_VAL : --! @brief PENDING VALID :
--! 現在ペンディング中のデータがあることを示すフラグ.
out std_logic;
-------------------------------------------------------------------------------
-- 出力側 I/F
-------------------------------------------------------------------------------
O_DATA : --! @brief OUTPUT DATA :
--! 出力データ.
--! * SYNCRONIZERのI_DATAに接続する.
out std_logic_vector(DATA_BITS-1 downto 0);
O_VAL : --! @brief OUTPUT VALID :
--! 出力有効信号.
--! * SYNCRONIZERのI_VALに接続する.
--! * この信号がアサートされている時はO_DATAに有効なデータが
--! 出力されていることを示す.
out std_logic;
O_RDY : --! @brief OUTPUT READY :
--! 出力許可信号.
--! * SYNCRONIZERのI_RDYに接続する.
in std_logic
);
end SYNCRONIZER_INPUT_PENDING_REGISTER;
内部信号たち
内部信号は次のように定義しています。
out_valid 信号はそのまま O_VAL ポートから出力され、out_data 信号はそのまま O_DATA ポートから出力されます。
pend_valid 信号は、ペンディングされていることを示すフラグ、pend_data 信号はペンディング中のデータを保持するレジスタです。
-----------------------------------------------------------------------------------
-- アーキテクチャ本体
-----------------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
architecture RTL of SYNCRONIZER_INPUT_PENDING_REGISTER is
-------------------------------------------------------------------------------
--! @brief 出力側に出力するデータ有効信号.
-------------------------------------------------------------------------------
signal out_valid : std_logic;
-------------------------------------------------------------------------------
--! @brief 出力側に出力するデータ.
-------------------------------------------------------------------------------
signal out_data : std_logic_vector(DATA_BITS-1 downto 0);
-------------------------------------------------------------------------------
--! @brief データが出力側に出力されていないことを示すフラグ.
-------------------------------------------------------------------------------
signal pend_valid : std_logic;
-------------------------------------------------------------------------------
--! @brief 出力側に出力できない時にデータを保持しておくレジスタ.
-------------------------------------------------------------------------------
signal pend_data : std_logic_vector(DATA_BITS-1 downto 0);
begin
出力信号の生成
I_VAL ポートが '1' か pend_valid 信号が '1' の場合、out_valid 信号は '1' になります。
-------------------------------------------------------------------------------
-- 常に I_PAUSE=0 かつ O_RDY=1 が成立するような場合は pend_valid, pend_data
-- はすべて0になり、その場合は out_valid, out_data は次のように簡略化されるよ
-- うに記述している.
-- out_valid <= I_VAL; out_data <= I_DATA
-------------------------------------------------------------------------------
out_valid <= '1' when (I_PAUSE = '0' and (I_VAL = '1' or pend_valid = '1')) else '0';
process (I_VAL, I_DATA, pend_valid, pend_data) begin
if (I_VAL = '1') then
case OPERATION is
when 2 => out_data <= std_logic_vector(unsigned(I_DATA) + unsigned(pend_data));
when 1 => out_data <= I_DATA or pend_data;
when others => out_data <= I_DATA;
end case;
elsif (pend_valid = '1') then
out_data <= pend_data;
else
out_data <= I_DATA;
end if;
end process;
また、I_VAL ポートが '1' の場合 I_DATA と pend_data とで OPERATION で指定された演算を行います。
OPERATION が 0 の場合は次の図のように I_DATA の値をそのまま上書きします。
Fig.4 OPERATION=0 のタイミングチャート
OPERATION が 1 の場合は次の図のように I_DATA の値と pend_data の値を 論理和した値を出力します。
Fig.5 OPERATION=1 のタイミングチャート
OPERATION が 2 の場合は次の図のように I_DATA の値と pend_data の値を 加算した値を出力します。
Fig.6 OPERATION=2 のタイミングチャート
ペンディングレジスタ
後段の syncronizer がハンドシェイク中(O_RDY 信号が'0'のとき)は、前述の out_valid 信号が '1' の時は、out_data の値を pend_data に保持し、pend_valid に '1' を設定します。
-------------------------------------------------------------------------------
-- 少し変則的で判りにくい記述だが、out_data を再利用することで回路がなるべく
-- 簡単になるようにしている.
-- 常に I_PAUSE=0 かつ O_RDY=1 が成立するような場合は pend_valid, pend_data
-- はすべて0になるようにしている.
-------------------------------------------------------------------------------
process (CLK, RST) begin
if (RST = '1') then
pend_valid <= '0';
pend_data <= (others => '0');
elsif (CLK'event and CLK = '1') then
if (CLR = '1') or
(O_RDY = '1' and I_PAUSE = '0') or
(pend_valid = '0' and I_VAL = '0') then
pend_valid <= '0';
pend_data <= (others => '0');
else
pend_valid <= '1';
pend_data <= out_data;
end if;
end if;
end process;
ポート出力
内部信号をポートから出力します。
-------------------------------------------------------------------------------
-- 出力情報をモジュール外部に出力する.
-------------------------------------------------------------------------------
O_VAL <= out_valid;
O_DATA <= out_data;
-------------------------------------------------------------------------------
-- ペンディング情報をモジュール外部に出力する.
-------------------------------------------------------------------------------
P_VAL <= pend_valid;
P_DATA <= pend_data;
end RTL;