FPGAにおけるデュアルポートRAMの設計

FPGAにおけるデュアルポートRAMの設計

 前回RAMの設計をしました。それを応用して、デュアルポートRAMの設計に発展させます。

 デュアルポートRAMは一つのRAMに対して、ライト側とリード側で独立して信号を与えるタイプのRAMです。イメージ的には下図のようになります。

 このようにポートは2つありますが、RAMは一つのメモリです。応用範囲は多く、FIFOや画像メモリといったタイプの物はこのデュアルポートRAMのアドレス管理を複雑にしたものと言えます。

 デュアルポートRAM(またはデュアルポートメモリ)も、通常はFPGAメーカーが用意したIPを使います。IPを使う使わないの判断条件については、「FPGAにおけるRAMの設計」を参照してください。

 デュアルポートRAMは入力側と出力側で独立してアドレス、データがありますので、次のような入出力信号になります。市販のデュアルポートRAMでは、ライトもリードも2ポートある。というのが一般的かも知れません。今回はリードとライトが独立したメモリとして定義します。

 データが入力側と出力側で分かれているのは、前回のRAMでも同じ設計をしました。デュアルポートRAMではアドレスの指定が入力と出力で別々にできるようになっています。今回も16ビット幅で8ワードのメモリで設計します。

 デュアルポートRAMのソースコードを示します。

--  TITLE:					"DPRAM_16b_8d.vhd"
--  MODULE NAME:			
--  PROJECT CODE:			
--  AUTHOR:					 (xxxx@nakaharagiken.com)
--  CREATION DATE:			2020.12.31
--  SOURCE:            		
--  LICENSE:           		Copyright (c) 2020 nakaharagiken.com
--  DESCRIPTION:            16bit 8deep のデュアルポートRAM
--  NOTES TO USER:			
--  SOURCE CONTROL:			
--  REVISION HISTORY:  	    v0.0	2020.12.31	
--
--
--
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 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 entity DPRAM_16b_8d;

architecture rtl of DPRAM_16b_8d is

---------------------------------------------------------------
-- 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	routput:	std_logic_vector(15 downto 0) := (others => '0');		-- 出力データ

begin

-----------------------------------------------------------
--
-- RAM
--
-----------------------------------------------------------
process (clock,nreset) begin
	if nreset = '0' then
        for i in 0 to 7 loop
            rMemory(i) <= (others => '0');
        end loop;
	elsif clock' event and clock = '1' then
		if (dataen = '1') then									-- ライト
			rMemory(CONV_INTEGER(addressIn)) <= datain;
		end if;
		if (datareq = '1') then									-- リード
			routput <= rMemory(CONV_INTEGER(addressOut));
		end if;
	end	if;
end process;

dataout <= routput;


end rtl;

 RAMとほとんど同じソースコードです。addressInとaddressOutで独立して、ライトするアドレス、リードするアドレスを指定できるようになっています。

 60行目でライト側のアドレスでメモリに記録。63行目でリード側のアドレスで読んでいます。

 ライト・リードで1クロックの遅延がありますので、ライトとリードを同時に行った場合には1現象前のデータがリードされます。データが壊れる事はありません。

 このような独立性の高いハードウェアを検査するのは難しくなります。テストベンチでは、ライトとリードを分けて記述するようにします。

--  TITLE:					"TB_DPRAM_16b_8d.vhd"
--  MODULE NAME:			Test Bench
--  PROJECT CODE:			
--  AUTHOR:					
--  CREATION DATE:			
--  REVISION HISTORY:  	    
--  SOURCE:            		
--  LICENSE:           		Copyright (c) 2020 nakaharagiken.com
--  DESCRIPTION:            
--  NOTES TO USER:      	
--  SOURCE CONTROL:  	    DPRAM_16b_8dのテストベンチ
--
--
--
--
--
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
use ieee.std_logic_arith.all;


entity TB_DPRAM_16b_8d is
end TB_DPRAM_16b_8d;

architecture sim of TB_DPRAM_16b_8d is


-- 回路記述部
-------------------------------------------------------------
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;


constant SYSCLK_PERIOD : time := 100 ns; -- 10MHz


-- 信号の宣言
signal	rclock:				std_logic;
signal	rnreset:			std_logic;							--	__reset___|~~~normal~~~
signal	rdatain:			std_logic_vector(15 downto 0);	-- 入力データ
signal	rdataen:			std_logic;						-- データイネーブル								____|~|___________________
signal	raddressIn:			std_logic_vector(2 downto 0);	-- 入力アドレス
signal	raddressOut:		std_logic_vector(2 downto 0);	-- 出力アドレス
signal	wdataout:			std_logic_vector(15 downto 0);	-- 出力データ
signal	rdatareq:			std_logic;						-- 出力データリクエスト							____|~~request~|__________

signal	j,k:				integer range 0 to 7;

begin

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



-- 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

	j <= 0;
	rdataen <= '0';
	rdatain <= (others => '0');
	
	wait for (SYSCLK_PERIOD * 50);
	wait for (SYSCLK_PERIOD / 2);

	for i in 0 to 500 loop
		wait for (SYSCLK_PERIOD / 4);
		raddressIn <= CONV_std_logic_vector(j,3);
		rdatain <= CONV_std_logic_vector(i,16);
		rdataen <= '1';
		wait for (SYSCLK_PERIOD);
		rdataen <= '0';
		wait for (SYSCLK_PERIOD * 2);
		wait for (SYSCLK_PERIOD / 4);

		if (j < 7) then
			j <= j+1;
		else
			j <= 0;
		end if;
	end loop;
	wait;

end process;

process begin
	k <= 0;
	rdatareq <= '0';
	
	wait for (SYSCLK_PERIOD * 3);
	wait for (SYSCLK_PERIOD / 2);

	for i in 0 to 500 loop
		wait for (SYSCLK_PERIOD / 4);
		raddressOut <= CONV_std_logic_vector(k,3);
		wait for (SYSCLK_PERIOD * 3);
		wait for (SYSCLK_PERIOD / 4);

		rdatareq <= '1';
		wait for (SYSCLK_PERIOD);
		rdatareq <= '0';
		if (k < 7) then
			k <= k+1;
		else
			k <= 0;
		end if;
	end loop;

	wait;

end process;
end sim;

 93行目からがライト側の記載で、122行目がリード側の記載です。ライトの頻度よりもリードの頻度を下げており、途中でdataenとdatareqが交差するタイミングが出るようにしてあります。

 このようなデュアルポートRAMをそのまま使う事はほとんどないと思いますが、デュアルポートRAMのアドレス管理を行うことでFIFOに発展させることができますし、同期ではありますが、リングバッファ的に使う事でレート変換も可能になります。

 特に本ブログではテストベンチのソースコードを求めてご覧くださる方が多いようですので、デュアルポートメモリのような独立したアドレス発生のテストベンチ例は参考になると思います。

 最後に、テストベンチの結果を載せておきます。