デュアルポートRAMをFIFOにする

デュアルポートRAMをFIFOにする

 まず、FIFOとは何か。という解説をします。FIFOはFirst In, First Outの頭文字から来ていますが、日本語では先入れ先出しです。品出しの方法としても知られていると思います。

 FIFOの基本構造を次に示します。

 FIFOには入力と出力があり、入力していく順番に出力します。シフトレジスタと似ていますが、出力側(リード側)はリードする好きなタイミングでアクセスする。というのがシフトレジスタとちがいます。

 たとえば、上図のように16ビット幅のデータを8個の深さで構成されたFIFOは、1個でもライトしていれば、リードが可能です。シフトレジスタの場合、深さが8であれば、8個ライトしなくては、リード側にデータがで出力されません。しかも、ライトのタイミングで1個溢れたデータがリードされるのがシフトレジスタです。

 FIFOはメモリの中に1個でもデータがあれば、好きなタイミングでリードできます。1個あるかないか? というのは一般的にエンプティーフラグで検出できます。

 逆にいつまでもリードしないでいると、FIFOの中にデータがどんどん溜まります。上図の場合では8個までは蓄えますが、9個目で溢れます。この溢れたデータは消失します。したがって、ライトする側もFIFOの中身の個数を気にしながらライトする必要があります。FIFOが満タンかどうか? を調べるフラグは一般的にFIFOフル(FULL)フラグが用意されています。

 FIFOは様々な用途がありますが、多いのはデータのバッファに使う例です。たとえば、音楽データを再生しようとした場合、一定の速度でデータをアナログに変換する必要があります。このような場合、一定速度でリードするようにFIFOを構成しておき、ライト側はハードウェアを高速で動かして、データを加工したり、復調したりできます。また、FIFOによって、一定速度の回路と高速の回路に分ける事ができるので、設計者を分けるのにわかりやすい。等のメリットがあります。

 FIFOにも多くの種類があります。標準的なFIFOは入力と出力のクロックもデータの幅も同じものです。

標準的なFIFO

  次にクロックが違うタイプのFIFOを紹介します。

入力と出力のクロックが違うFIFO

 この場合、フラグがどちらのクロックに同期しているのかを理解して使う必要があります。

 さらに複雑なFIFOとして、入力と出力のデータ幅が違うタイプがあります。

入力と出力のデータ幅が違うFIFO

 上の例では、16ビット入力で出力が8ビットになります。したがって、読み出すレートは入力の倍の速度が必要になります。

 このようにFIFOは非常に多くのバリエーションがありますが、FPGAを使って内部にFIFOを設計する場合、メーカーが用意しているIPを使うようにします。FIFOはメモリの一種なわけですが、最近のFPGAにはハード的にメモリブロックが内蔵されています。この内蔵されたメモリブロックを利用してFIFOを作るのが速度的にもリソース的にも最適化されます。

 しかしながらメーカーのIPを使用してしまうと、他社メーカーのFPGAに移植するのが面倒になります。できなくは無いのですが、非常に面倒です。

 そこで、小さなFIFOを使う場合には、VHDLやVerilogで作ることがあります。今回、デュアルポートRAMの応用として、クロックが1系統のFIFOを設計します。クロックが2系統のFIFOもVHDL/Verilogで記述できなくは無いのですが、FPGAに実装できない事が多いので、メーカー製のIPを使用するのが無難です。

--  TITLE:					"Fifo_16b_8d.vhd"
--  MODULE NAME:			
--  PROJECT CODE:			
--  AUTHOR:					 (xxxx@nakaharagiken.com)
--  CREATION DATE:			2021.1.24
--  SOURCE:            		
--  LICENSE:           		Copyright (c) 2021 nakaharagiken.com
--  DESCRIPTION:            16bit 8deep のfifo 
--  NOTES TO USER:			
--  SOURCE CONTROL:			
--  REVISION HISTORY:  	    v0.0	2021.1.24
--
--
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.std_logic_unsigned.all;
use IEEE.std_logic_arith.all;
use ieee.numeric_std.all;

entity Fifo_16b_8d is
port (
	clock:				in		std_logic;						-- system clock
	nreset:				in		std_logic;						-- asynchronous reset 							____reset___|~~~normal~~~
	clear:				in		std_logic;						-- クリア										____|~|____________
	datain:				in		std_logic_vector(15 downto 0);	-- 入力データ
	dataen:				in		std_logic;						-- データイネーブル								____|~|___________________
	almostfull:			out		std_logic;						-- fifo満タン-1のフラグ							_______|~afull~~~|________
	full:				out		std_logic;						-- fifo満タン
	empty:				out		std_logic;						-- fifo空
	writepermit:		out		std_logic;						-- ライト許可
	dataout:			out		std_logic_vector(15 downto 0);	-- 出力データ
	datareq:			in		std_logic;						-- 出力データリクエスト							____|~~request~|__________
	readpermit:			out		std_logic;						-- リード許可									____|~~~許可~~~|_______
	sdram_req:			out		std_logic;						-- SDRAM W_req信号			
--	sdram_rw_ack:		in		std_logic;						-- SDRAM rw_req信号
	TP_counter:			out		std_logic_vector(3 downto 0)	-- テストピンデータ数
);
end entity Fifo_16b_8d;

architecture rtl of Fifo_16b_8d is

---------------------------------------------------------------
--
-- デュアルポートRAM
--
---------------------------------------------------------------
component DPRAM_16b_8d is
port (
	clock:				in		std_logic;						-- system clock
	nreset:				in		std_logic;						-- asynchronous reset 							____reset___|~~~normal~~~
	addressIn:			in		std_logic_vector(2 downto 0);	-- 入力アドレス
	datain:				in		std_logic_vector(15 downto 0);	-- 入力データ
	dataen:				in		std_logic;						-- データイネーブル								____|~|___________________
	addressOut:			in		std_logic_vector(2 downto 0);	-- 出力アドレス
	dataout:			out		std_logic_vector(15 downto 0);	-- 出力データ
	datareq:			in		std_logic						-- 出力データリクエスト							____|~~request~|__________
);
end component;



---------------------------------------------------------------
-- SIGNALS
---------------------------------------------------------------
-- 配列の宣言
subtype WORD_TYPE is std_logic_vector(15 downto 0);					-- 配列の幅
type    WORD_ELEM is array(0 to 7) of WORD_TYPE;					-- 配列の要素数
signal  rMemory:    WORD_ELEM;  									-- 信号の宣言

signal	rwcount:		std_logic_vector(2 downto 0);				-- ライトポインタ
signal	rrcount:		std_logic_vector(2 downto 0);				-- リードポインタ
signal	rtcount:		std_logic_vector(3 downto 0);				-- 総合カウンタ FULLを検出するため、1bit多くしておく
signal	woutput:		std_logic_vector(15 downto 0);				-- 出力データ
signal	wrwflag:		std_logic_vector(1 downto 0);				-- リードライトフラグ


begin

-----------------------------------------------------------
--
-- デュアルポートRAM
--
-----------------------------------------------------------
inst_DPRAM_16b_8d : DPRAM_16b_8d
port map(
	clock			=>	clock,			--	in		std_logic;						-- system clock
	nreset			=>	nreset,			--	in		std_logic;						-- asynchronous reset 							____reset___|~~~normal~~~
	addressIn		=>	rwcount,		--	in		std_logic_vector(2 downto 0);	-- 入力アドレス
	datain			=>	datain,			--	in		std_logic_vector(15 downto 0);	-- 入力データ
	dataen			=>	dataen,			--	in		std_logic;						-- データイネーブル								____|~|___________________
	addressOut		=>	rrcount,		--	in		std_logic_vector(2 downto 0);	-- 出力アドレス
	dataout			=>	woutput,		--	out		std_logic_vector(15 downto 0);	-- 出力データ
	datareq			=>	datareq			--	in		std_logic						-- 出力データリクエスト							____|~~request~|__________
);



-----------------------------------------------------------
--
-- ライトアドレス管理
--
-----------------------------------------------------------
process (clock,nreset) begin
	if nreset = '0' then
        rwcount <= (others => '0');
	elsif clock' event and clock = '1' then
		if (clear = '1') then
   	     rwcount <= (others => '0');
		else
			if (dataen = '1') then
		        rwcount <= rwcount + '1';							-- デュアルポートRAMのポインタを進める
			end if;
		end if;
	end	 if;
end process;

-----------------------------------------------------------
--
-- リードアドレス管理
--
-----------------------------------------------------------
process (clock,nreset) begin
	if nreset = '0' then
        rrcount <= (others => '0');
	elsif clock' event and clock = '1' then
		if (clear = '1') then
   	     rrcount <= (others => '0');
		else
			if (datareq = '1') then
		        rrcount <= rrcount + '1';							-- デュアルポートRAMのポインタを進める
			end if;
		end if;
	end	 if;
end process;


-----------------------------------------------------------
--
-- データ数の管理
--
-----------------------------------------------------------
wrwflag <= dataen &amp; datareq;		-- リードとライトの条件を1つにまとめる

process (clock,nreset) begin
	if nreset = '0' then
        rtcount <= (others => '0');
	elsif clock' event and clock = '1' then
		if (clear = '1') then
   	     rtcount <= (others => '0');
		else
			case wrwflag is
				when "11" => rtcount <= rtcount;				-- リードとライト同時アクセスの時、カウンタはUPしない
				when "01" => rtcount <= rtcount - '1';			-- リードアクセスのみの時、カウンタを-1する
				when "10" => rtcount <= rtcount + '1';			-- ライトアクセスのみの時、カウンタを+1する
				when others => rtcount <= rtcount;				-- アクセスが無い時、何もしない
			end case;
		end if;
	end	 if;
end process;


-- 以下、各種フラグの作成
almostfull <= '1'  when (rtcount >= "0110") else '0';
full <= rtcount(3);
empty <= '1' when (rtcount = "0000") else '0';
writepermit <= '0' when (rtcount >= "1000") else '1';
readpermit <= '1' when (("0000" < rtcount) and (rtcount <= "1000")) else '0';
sdram_req <= rtcount(3);														-- SDRAM W_req信号
dataout <= woutput;																-- 出力データー

TP_counter <= rtcount;

end rtl;

 デュアルポートRAMを使っていますので、その解説はこちらを参照してください。デュアルポートRAMをそのまま使っていますので、本ソースコードはアドレス管理とフラグ生成のみになっています。

 FIFOとしてのテストベンチを示します。

--  TITLE:					"TB_Fifo_16b_8d.vhd"
--  MODULE NAME:			Test Bench
--  PROJECT CODE:			
--  AUTHOR:					 (xxxx@nakaharagiken.com)
--  CREATION DATE:			
--  REVISION HISTORY:  	    
--  SOURCE:            		
--  LICENSE:           		Copyright (c) 2021 nakaharagiken.com
--  DESCRIPTION:            
--  NOTES TO USER:      	
--  SOURCE CONTROL:  	    Fifo_16b_8dのテストベンチ
--
--
--
--
--
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
use ieee.std_logic_arith.all;
--use std.TEXTIO.all;					--	textio package
--use ieee.std_logic_textio.all;		--	textio package


entity TB_Fifo_16b_8d is
end TB_Fifo_16b_8d;

architecture sim of TB_Fifo_16b_8d is


-- 回路記述部
-------------------------------------------------------------

component Fifo_16b_8d is
port (
	clock:				in		std_logic;						-- system clock
	nreset:				in		std_logic;						-- asynchronous reset 							____reset___|~~~normal~~~
	clear:				in		std_logic;						-- クリア										____|~|____________
	datain:				in		std_logic_vector(15 downto 0);	-- 入力データ
	dataen:				in		std_logic;						-- データイネーブル								____|~|___________________
	almostfull:			out		std_logic;						-- fifo満タン-1のフラグ							_______|~afull~~~|________
	full:				out		std_logic;						-- fifo満タン
	empty:				out		std_logic;						-- fifo空
	writepermit:		out		std_logic;
	dataout:			out		std_logic_vector(15 downto 0);	-- 出力データ
	datareq:			in		std_logic;						-- 出力データリクエスト							____|~~request~|__________
	readpermit:			out		std_logic;
	sdram_req:			out		std_logic;						-- SDRAM W_req信号			
	TP_counter:			out		std_logic_vector(3 downto 0)	-- テストピンデータ数
);
end component;


constant SYSCLK_PERIOD : time := 50 ns; -- 20MHz


-- 信号の宣言
signal	rclock:				std_logic;
signal	rnreset:			std_logic;							--	__reset___|~~~normal~~~
signal	rdatain:			std_logic_vector(15 downto 0);	-- 入力データ
signal	rdataen:			std_logic;						-- データイネーブル								____|~|___________________
signal	walmostfull:		std_logic;						-- fifo満タン-1のフラグ							_______|~afull~~~|________
signal	wempty:				std_logic;						-- fifo空
signal	wwritepermit:		std_logic;
signal	wdataout:			std_logic_vector(15 downto 0);	-- 出力データ
signal	rdatareq:			std_logic;						-- 出力データリクエスト							____|~~request~|__________
signal	wreadpermit:		std_logic;
signal	wTP_counter:		std_logic_vector(3 downto 0);	-- テストピンデータ数
signal	rclear:				std_logic;						-- クリア										____|~|____________
signal	wfull:				std_logic;
signal	wsdram_req:			std_logic;

begin

inst_Fifo_16b_8d : Fifo_16b_8d
port map(
	clock				=>	rclock,					--	in		std_logic;						-- system clock
	nreset				=>	rnreset,				--	in		std_logic;						-- asynchronous reset 					____reset___|~~~normal~~~
	clear				=>	rclear,					--	in		std_logic;						-- クリア										____|~|____________
	datain				=>	rdatain,				--	in		std_logic_vector(15 downto 0);	-- 入力データ
	dataen				=>	rdataen,				--	in		std_logic;						-- データイネーブル								____|~|___________________
	almostfull			=>	walmostfull,			--	out		std_logic;						-- fifo満タン-1のフラグ							_______|~afull~~~|________
	full				=>	wfull,					--	out		std_logic;						-- fifo満タン
	empty				=>	wempty,					--	out		std_logic;						-- fifo空
	writepermit			=>	wwritepermit,			--	out		std_logic;
	dataout				=>	wdataout,				--	out		std_logic_vector(15 downto 0);	-- 出力データ
	datareq				=>	rdatareq,				--	in		std_logic;						-- 出力データリクエスト							____|~~request~|__________
	readpermit			=>	wreadpermit,			--	out		std_logic
	sdram_req			=>	wsdram_req,				--	out		std_logic;						-- SDRAM W_req信号			
	TP_counter			=>	wTP_counter				--	out		std_logic_vector(3 downto 0)	-- テストピンデータ数
);



-- Clock Driver
process begin
	rclock <= '1';
	wait for SYSCLK_PERIOD / 2;
	rclock <= '0';
	wait for SYSCLK_PERIOD / 2;
end process;

-- reset
process begin
	rnreset <= '0';
	wait for (SYSCLK_PERIOD * 2);
	rnreset <= '1';
	wait;
end process;


process begin
	rclear <= '0';
	wait;
end process;


process begin

	rdataen <= '0';
	rdatain <= (others => '0');

	wait for (SYSCLK_PERIOD * 50);
	wait for (SYSCLK_PERIOD / 2);

	for i in 0 to 100 loop

		rdatain <= CONV_std_logic_vector(i,16);
		wait for (SYSCLK_PERIOD / 4);
		rdataen <= '1';
		wait for (SYSCLK_PERIOD);
		rdataen <= '0';
		wait for (SYSCLK_PERIOD);
		if (wfull = '1') then
			wait on wfull;
		end if;
		wait for (SYSCLK_PERIOD * 2);
	end loop;
	wait;

end process;



process begin
	rdatareq <= '0';

loop
	wait until wsdram_req = '1';
	wait for (SYSCLK_PERIOD * 10);
	wait for (SYSCLK_PERIOD / 4);
	rdatareq <= '1';
	wait for (SYSCLK_PERIOD * 8);
	rdatareq <= '0';
	wait for (SYSCLK_PERIOD);
end loop;

end process;


end sim;

 今回はデュアルポートRAMの応用としてFIFOを設計しました。ソースコードを読むとFIFOの動作が理解できると思います。

 本文で書きましたが、FIFOは基本的にメーカーが用意したIPを使うようにします。FIFOはデバッグが難しいのと、バグが発生する頻度が高い部品なので、メーカーが用意している動作保証された物を使うようにするべきだと思います。