FPGA ile SDR SDRAM Kontrolü

DRAM(Dynamic Random Access Memory)'ler SRAM'lere göre verileri mantık hücreleri yerine kondansatörlerde tutarlar. Bu nedenle üretimlerinde SRAM'lere göre çok daha az transistör kullanılır. Daha az transistör kullanılması ile die üzerine daha fazla hafıza hücresi eklenebilmektedir. DRAM'lerin üretim maliyeti hafıza kapasitesine göre SRAM'lerden çok daha düşük olmasından dolayı DRAM'ler yaygın olarak kullanılır.

DRAM'lerde veriler kondansatörlerde tutulduğunda dolayı verilerin kaybolmaması için belirli aralıklarla bütün kondansatörlerin yeniden şarj edilmesi gerekir. Bu işleme Yenileme denir. Yenileme işlemleri DRAM'in çalışma süresinin bir miktarını harcar. DRAM'ler ile SRAM'lerin arasındaki en büyük farklardan biride yenileme işlemidir.

DRAM'de veriler bank'larda tutulur. Her bank satırlardan, her satırda sütunlardan oluşur.

Satır ve bank adresi RAS(Row Address Strobe) sinyali ile, sütun ise CAS(Column Address Strobe) sinyali ile seçilir. Satır adresinin seçilmesi ile satırdaki bütün sütunlar sense amplifier'a yüklenir. Yükleme işlemi \mathbf{t}_{\mathbf{RCD}} (Row Command Delay) sürede gerçekleşir. Bu işlem ACT (Activate) olarak adlandırılır.

Sense amplifier'dan verilere erişilmesi \mathbf{t}_{\mathbf{CL}} (Column Latency) sürede gerçekleşir.

Satır ile yapılacak işlemler bittiğinde sense amlifier'daki verilerin satıra geri yüklenmesi gerekir. Bu işlem PRE (Precharge) olarak adlandırılır ve \mathbf{t}_{\mathbf{RP}} (Row Precharge Delay) sürede gerçekleşir.

Kondansatörlerdeki yükün azalıp veri kaybına yol açmaması için belirli aralıklarla bütün satırlardaki kondansatörlerin yeniden doldurulması gerekir. Her bir satırın yenileme işlemi \mathbf{t}_{\mathbf{RC}} (Row Cycle Delay) sürede gerçekleşir.

FPGA Üzerinde SDR SDRAM Kontrolcüsü Tasarımı

VHDL Kodu:

------------------- SDRAM_Controller.vhd -----------------
--                     Bertan Taşkın
--                       1.7.2017
--
-- SDR SDRAM Kontrolcüsü. SDRAM'e ait parametreler değiştirilerek
-- farklı RAM entegreleri kontrol edilebilir. 
--
-- Okuma işleminde okunaca veri sayısı PORT_BURST ile belirlenir.
-- Toplam okunacak veri sayısı = 2^PORT_BURST. Okunan veriler sıralı
-- biçimde okunur. 
--
-- Yazma işlemi tek tek yapılır. Yazılacak Row ve Bank açık ise
-- yazma işlemi 1 cycle'da gerçekleşir.
--
-- PORT_BUSY '1' olduğunda, PORT_ADDRESS ve PORT_DATAIN sinyallerinin
-- değiştirilmemesi gerekir. Aksi halde okuma ve yazma işlemleri hatalı
-- gerçekleşir.
--
-- PORT_ADDRESS, (Bank & Row & Column) şeklinde parçalara bölünür.
--


--Kütüphaneler
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
USE ieee.numeric_std.ALL; 
use ieee.math_real.all;

entity SDRAM_Controller is
	port(Clk, Enable : in std_logic;                       --Clock ve Clock Enable Girişi
		RAMCLK, CKE : out std_logic;                       --SDRAM'in Clock ve Clock Enable Çıkışları
		CS, RAS, CAS, WE : out std_logic;                  --SDRAM'in CS, RAS, CAS ve WE Çıkışları
		DQMH, DQML : out std_logic;                        --SDRAM'in DQM Çıkışları
		BA : out std_logic_vector(1 downto 0);             --SDRAM'in Bank Çıkışları
		ADDRESS : out std_logic_vector(12 downto 0);       --SDRAM'in Adres Çıkışı
		DATA : inout std_logic_vector(15 downto 0);        --SDRAM'in Data Hattı
		
		PORT_ADDRESS : in std_logic_vector(23 downto 0);   --İşlem Yapılacak Adres
		PORT_DATAIN : in std_logic_vector(15 downto 0);    --Yazma İşlemi için Data Girişi
		PORT_DATAOUT : out std_logic_vector(15 downto 0);  --Okuma İşlemi için Data Çıkışı  
		PORT_RW, PORT_ENABLE : in std_logic;               --Okuma yada Yazma Yapılacağını Belirten Girişler
		PORT_BURST : in std_logic_vector(2 downto 0);      --Okuma Uzunluğu Girişi, Okunacak Veri Sayısı = 2^Burst
		PORT_BUSY, PORT_READY : out std_logic);		       --Meşgul Çıkışı ve Veri Hazır Çıkışı
end SDRAM_Controller;

architecture Behavioral of SDRAM_Controller is

	--IS42S16160G'ye göre ayarlanmış zamanlama parametreleri
	--(2-2-2-6)
	------------------- SDRAM Parametreleri -------------------
	constant Freq : integer := 100000000;  --Çalışma Frekansı

	constant CL : integer := 2;            --CAS Gecikmesi
	constant tRCD : integer := 2;          --ACT -> RW Gecikmesi
	constant tRP : integer := 2;           --PRE -> ACT Gecikmesi
	constant tRAS : integer := 6;          --ACT -> PRE Gecikmesi
	
	constant tRC : integer := 8;           --REF Süresi (tRP + tRAS)
	constant tREF : integer := 64;         --Yenileme Periyodu (ms cinsinden)
	------------------------------------------------------------
	
	--Mode Registeri
	constant ModeRegisterCASLatency : std_logic_vector(2 downto 0) := std_logic_vector(to_unsigned(CL, 3));
	constant ModeRegister : std_logic_vector(12 downto 0) := "000000" & ModeRegisterCASLatency & "0000";
	
	--Kaç Cycle'da bir yenileme işleminin yapılacağı belirten sabit
	constant RefCycle : integer := tREF * (Freq / 1000) / 8192;
	
	--Başlangıç işlmeleri için gerekli olan 100us'nin kaç cycle'a denk
	--geldiğini belirten sabit
	constant InitializeDelay : integer := Freq / 10000;
	
	--Yenileme yapılmasının gerektiğini belirten sinyaller
	signal NeedRefresh : std_logic := '0';
	signal ForceRefresh : std_logic := '0';
	
	--Aktif olan row ve bank
	signal ActiveRow : std_logic_vector(12 downto 0) := (others=>'0');
	signal ActiveBank : std_logic_vector(1 downto 0) := (others=>'0');
	
	--Herhangi bir bankın aktif olup olmadığını belirten sinyal
	signal AllBankPrecharged : std_logic := '0';
	
	--State Machine komutları
	type COMMAND_TYPE is (NOP, BST, RD, WR, ACT, PRE, REF, MRS);
	type STATE_TYPE is (IDLE, INITIALIZE, READ_DATA, WRITE_DATA, REFRESH);
	
	--DATA hattının kontrol eden tri-state buffer
	signal DataBuffer : std_logic := '1';
	signal DataS : std_logic_vector(15 downto 0);
	
	--İlk enerji verildiğinde gerekli başlangıç işlemleri yapılmalıdır
	signal NeedInitialize : std_logic := '1';
	
	signal BurstInt : integer range 0 to 127;

begin

	--RAM, Clk ile aynı frekansda
	RAMCLK <= Clk;
	
	--RAM'in clock sinyali sürekli aktif
	CKE <= '1';
	
	--RAM'in DATA bufferı sürekli aktif
	DQMH <= '0';
	DQML <= '0';
	
	--DATA hattını kontrol eden tri-state buffer
	with DataBuffer select
		DATA <= (others=>'Z') when '1',
					DataS when others;
	
   --İstenen burst sayısının integer hali	
	BurstInt <= 2**to_integer(unsigned(PORT_BURST));
	
	--Bütün işlmelerin yapıldığı process
	process(Clk, Enable)
	variable Command : COMMAND_TYPE := NOP;
	variable State : STATE_TYPE := IDLE;
	
	variable InitializeCnt : integer range 0 to 5 := 0;
	variable StateCnt : integer range  0 to 3 := 0;  
	variable DelayCnt : integer range 0 to InitializeDelay - 1 := 0;
	variable RefreshCnt : integer range 0 to RefCycle - 1 := 0;
	
	--Sayaçlar
	variable tRCCnt : integer range 0 to tRC - 1 := 0;
	--tRC_Complete sinyali resetlendiğinde tRC sayacı aktif olur
	variable tRC_Complete : std_logic := '1';
	
	variable tRPCnt : integer range 0 to tRP - 1 := 0;
	variable tRP_Complete : std_logic := '1';
	
	variable tRCDCnt : integer range 0 to tRCD - 1 := 0;
	variable tRCD_Complete : std_logic := '1';
	
	variable tRASCnt : integer range 0 to tRAS - 1 := 0;
	variable tRAS_Complete : std_logic := '1';
	
	variable CLCnt : integer range 0 to CL := 0;
	variable CL_Complete : std_logic := '1';

	variable BurstCnt : integer range 0 to 127 := 0;
	variable BurstCnt2 : integer range 0 to 127 := 0;
	
	variable ReadBuffer : std_logic := '0';
	begin
	
		--Okuma işlemi Clk sinyalinin yükselen kenarı ile yapılır
		if rising_edge(Clk) then
			if ReadBuffer = '1' then
				PORT_DATAOUT <= DATA;
				PORT_READY <= '1';
			else
				PORT_READY <= '0';
			end if;
		end if;
	
		--Diğer bütün işlemler Clock sinyalinin düşen kenarı ile tetiklenir
		if falling_edge(Clk) and Enable = '1' then
				
			
			---------------  CAS Sayacı -------------------
			if CL_Complete = '0' then
				--CAS gecikmesi bitene kadar bekle
				if CLCnt < CL - 1 then
					CLCnt := CLCnt + 1;
				else
					--DATA hattından BurstInt kadar veri oku
					if BurstCnt2 < BurstInt then
						--Okuma bufferı aktif
						ReadBuffer := '1'; 
						
						--Eğer son veri okunuyor ise bitiş işlemlerini yap
						if BurstCnt2 = BurstInt - 1 then
							BurstCnt2 := 0;
							PORT_BUSY <= '0';
							State := IDLE;
							StateCnt := 0;
							CLCnt := 0;
							CL_Complete := '1';
						--Değil ise Burst sayacı arttırılır
						else
							BurstCnt2 := BurstCnt2 + 1;
						end if;					
					end if;			
				end if;
			else
				ReadBuffer := '0'; 
			end if;
			-----------------------------------------------
				
         --------------- Komutların Çözülmesi -----------
			if State = IDLE then
				--Başlangıç işlemleri
				if NeedInitialize = '1' then
					State := INITIALIZE;
					PORT_BUSY <= '1';
					
				--Yenilemeye zorla
				elsif ForceRefresh = '1' then
					State := REFRESH;
					PORT_BUSY <= '1';
					
				--Yazma
				elsif PORT_ENABLE = '1' and PORT_RW = '0' then
					State := WRITE_DATA;
					PORT_BUSY <= '1';
					
				--Okuma
				elsif PORT_ENABLE = '1' and PORT_RW = '1' then
					State := READ_DATA;
					PORT_BUSY <= '1';
					
				--Yenileme
				elsif NeedRefresh = '1' then
					State := REFRESH;
					PORT_BUSY <= '1';
					
				--Yapılması gereken bir komut yok ise IDLE'de kal
				else
					State := IDLE;
				end if;
			end if;
			-------------------------------------------------
			
			--Boşta
			if State = IDLE then
				Command := NOP;
				DataBuffer <= '1';
			--Başlangıç İşlemleri
			elsif State = INITIALIZE then			
				DataBuffer <= '1';
				
				--100us Bekleme
				if InitializeCnt = 0 then
					Command := NOP;
					if DelayCnt < InitializeDelay - 1 then
						DelayCnt := DelayCnt + 1;
					else
						InitializeCnt := InitializeCnt + 1;
					end if;
					
				--PRE
				--Bütün banklar kapatılır
				elsif InitializeCnt = 1 then
					Command := PRE;
					--Bütün banklar
					ADDRESS(10) <= '1';
					--tRP sayacı aktif edilir
					tRP_Complete := '0';
					InitializeCnt := InitializeCnt + 1;
					
				--REF
				--2 kere yenileme işlemi yapılır
				elsif InitializeCnt = 2 then
					--tRP sayacının tamamlanması beklenir
					if tRP_Complete = '0' then
						Command := NOP;
					else
						Command := REF;
						tRC_Complete := '0';
						InitializeCnt := InitializeCnt + 1;	
					end if;					
				--REF
				elsif InitializeCnt = 3 then
					if tRC_Complete = '0' then
						Command := NOP;
					else
						Command := REF;
						tRC_Complete := '0';
						InitializeCnt := InitializeCnt + 1;	
					end if;
					
				--MOD
				--Mode registeri ayarlanır
				elsif InitializeCnt = 4 then
					if tRC_Complete = '0' then
						Command := NOP;
					else
						Command := MRS;
						ADDRESS <= ModeRegister;
						BA <= "00";
						InitializeCnt := InitializeCnt + 1;
					end if;
					
				--NOP
				elsif InitializeCnt = 5 then
					Command := NOP;
					NeedInitialize <= '0';
					State := IDLE;
					PORT_BUSY <= '0';
				end if;	
			
			--Yenileme İşlemleri
			elsif State = REFRESH then
				
				--PRE
				--Açık olan bütün banklar kapatılır
				if StateCnt = 0 then
					if tRAS_Complete = '0' then
						Command := NOP;
					else
						Command := PRE;
						ADDRESS(10) <= '1'; StateCnt := StateCnt + 1;
						AllBankPrecharged <= '1';
						tRP_Complete := '0';
					end if;	
					
				--REF
				--Yenileme işlemi yapılır
				elsif StateCnt = 1 then
					if tRP_Complete = '0' then
						Command := NOP;
					else
						Command := REF;
						tRC_Complete := '0';
						StateCnt := StateCnt + 1;
					end if;
					
				--NOP
				--Yenileme işlemi bitinceye kadar herhangi bir işlem
				--yapılmaz
				elsif StateCnt = 2 then
					Command := NOP;
					if tRC_Complete = '1' then
						--Yenilenmeye zorlanmış ise yenileme işlemi 
						--bir kez daha yapılır
						if ForceRefresh = '1' then
							StateCnt := 1;
							ForceRefresh <= '0';
						else
							StateCnt := 0;
							NeedRefresh <= '0';
							State := IDLE;
							PORT_BUSY <= '0';
						end if;					
					end if;
				end if;
				
			--Okuma İşlemleri
			elsif State	= READ_DATA then
			
				if StateCnt = 0 then
					if AllBankPrecharged = '0' then
					
						--PRE
						--Okunacak row yada bank aktif degil ise aktif olan
						--bütün banklar kapatılır
						if ActiveRow /= PORT_ADDRESS(21 downto 9) or ActiveBank /= PORT_ADDRESS(23 downto 22) then
							if tRAS_Complete = '0' then
								Command := NOP;
							else
								Command := PRE;
								ADDRESS(10) <= '1';
								AllBankPrecharged <= '1';
								tRP_Complete := '0';
								StateCnt := 1;
							end if;
			
						--RD
						--Okunacak row ve bank aktif ise okuma komutu gönderilir
						else
							--BurstInt kadar okuma komutu gönderilir.
							--Her komutta adres değeri 1 arttırılır
							if BurstCnt < BurstInt then
								Command := RD;
								ADDRESS(8 downto 0) <= std_logic_vector(unsigned(PORT_ADDRESS(8 downto 0)) + to_unsigned(BurstCnt, 9));
							   
								--Gönderilen ilk komut ise CL_Complete sinyali resetlenir ve
								--CAS sayacı aktif edilir
								if BurstCnt = 0 then
									CL_Complete := '0';
								end if;
								
								--Son komutta 
								if BurstCnt = BurstInt - 1 then
									BurstCnt := 0;
									StateCnt := 3;
								else
									BurstCnt := BurstCnt + 1;
								end if;
								
							end if;							
						end if;
						
					--ACT
					--Okunacak row ve bank aktif edilir
					else					
						Command := ACT;
						ADDRESS <= PORT_ADDRESS(21 downto 9);
						BA <= PORT_ADDRESS(23 downto 22);
						--tRCD ve tRAS sayaçları aktif edilir
						tRCD_Complete := '0';
						tRAS_Complete := '0';
						ActiveRow <= PORT_ADDRESS(21 downto 9);
						ActiveBank <= PORT_ADDRESS(23 downto 22);
						AllBankPrecharged <= '0';
						StateCnt := 2;
					end if;
					
				--ACT
				--Okunacak row ve bank aktif edilir
				elsif StateCnt = 1 then
					if tRP_Complete = '0' then
						Command := NOP;
					else
						Command := ACT;
						ADDRESS <= PORT_ADDRESS(21 downto 9);
						BA <= PORT_ADDRESS(23 downto 22);
						tRCD_Complete := '0';
						tRAS_Complete := '0';
						ActiveRow <= PORT_ADDRESS(21 downto 9);
						ActiveBank <= PORT_ADDRESS(23 downto 22);
						AllBankPrecharged <= '0';
						StateCnt := 2;
					end if;
					
				--RD
				--Okuma komutu gönderilir
				elsif StateCnt = 2 then
					if tRCD_Complete = '0' then
						Command := NOP;
					else
						if BurstCnt < 2**to_integer(unsigned(PORT_BURST)) then
							Command := RD;
							ADDRESS(8 downto 0) <= std_logic_vector(unsigned(PORT_ADDRESS(8 downto 0)) + to_unsigned(BurstCnt, 9));
							if BurstCnt = 0 then
								CL_Complete := '0';
							end if;
							if BurstCnt = 2**to_integer(unsigned(PORT_BURST)) - 1 then
								BurstCnt := 0;
								StateCnt := 3;
							else
								BurstCnt := BurstCnt + 1;
							end if;
						end if;
					end if;
					
				--CAS
				--Okuma işlemlerinin bitmesi beklenir
				elsif StateCnt = 3 then
					--DATA hattı yüksek empedans konumunda
					DataBuffer <= '1';
					Command := NOP;
					StateCnt := 3;
				end if;
			
			--Yazma İşlemleri
			elsif State = WRITE_DATA then

				if StateCnt = 0 then
					if AllBankPrecharged = '0' then
					
						--PRE
						--Yazılacak row yada bank aktif degil ise aktif olan
						--bütün banklar kapatılır
						if ActiveRow /= PORT_ADDRESS(21 downto 9) or ActiveBank /= PORT_ADDRESS(23 downto 22) then
							if tRAS_Complete = '0' then
								Command := NOP;
							else
								Command := PRE;
								ADDRESS(10) <= '1';
								AllBankPrecharged <= '1';
								tRP_Complete := '0';
								StateCnt := 1;
							end if;
							
						--WR
						--Yazılacak row ve bank aktif ise yazma komutu gönderilir
						else
							Command := WR;
							DataBuffer <= '0';
							ADDRESS(8 downto 0) <= PORT_ADDRESS(8 downto 0);
							DataS <= PORT_DATAIN;
							StateCnt := 0;
							State := IDLE;
							PORT_BUSY <= '0';							
						end if;
						
					--ACT
					--Yazılacak row ve bank aktif edilir
					else					
						Command := ACT;
						ADDRESS <= PORT_ADDRESS(21 downto 9);
						BA <= PORT_ADDRESS(23 downto 22);
						tRCD_Complete := '0';
						tRAS_Complete := '0';
						ActiveRow <= PORT_ADDRESS(21 downto 9);
						ActiveBank <= PORT_ADDRESS(23 downto 22);
						AllBankPrecharged <= '0';
						StateCnt := 2;
					end if;
					
				--ACT
				--Yazılacak row ve bank aktif edilir
				elsif StateCnt = 1 then
					if tRP_Complete = '0' then
						Command := NOP;
					else
						Command := ACT;
						ADDRESS <= PORT_ADDRESS(21 downto 9);
						BA <= PORT_ADDRESS(23 downto 22);
						tRCD_Complete := '0';
						tRAS_Complete := '0';
						ActiveRow <= PORT_ADDRESS(21 downto 9);
						ActiveBank <= PORT_ADDRESS(23 downto 22);
						AllBankPrecharged <= '0';
						StateCnt := 2;
					end if;
					
				--WR
				--Yazma komutu gönderilir
				elsif StateCnt = 2 then
					if tRCD_Complete = '0' then
						Command := NOP;
					else
						Command := WR;
						DataBuffer <= '0';
						ADDRESS(8 downto 0) <= PORT_ADDRESS(8 downto 0);
						DataS <= PORT_DATAIN;
						StateCnt := 0;
						State := IDLE;
						PORT_BUSY <= '0';						
					end if;				
				end if;
				
			end if;
				
			------------------- Komutlar ------------------------
			--No Operation
			if Command = NOP then
				CS <= '0'; RAS <= '1'; CAS <= '1'; WE <= '1';
			--Burst Stop
			elsif Command = BST then
				CS <= '0'; RAS <= '1'; CAS <= '1'; WE <= '0';
			--Read	
			elsif Command = RD then
				CS <= '0'; RAS <= '1'; CAS <= '0'; WE <= '1';
			--Write
			elsif Command = WR then
				CS <= '0'; RAS <= '1'; CAS <= '0'; WE <= '0';
			--Bank Active
			elsif Command = ACT then
				CS <= '0'; RAS <= '0'; CAS <= '1'; WE <= '1';
			--Precharge
			elsif Command = PRE then
				CS <= '0'; RAS <= '0'; CAS <= '1'; WE <= '0';
			--Refresh
			elsif Command = REF then
				CS <= '0'; RAS <= '0'; CAS <= '0'; WE <= '1';
			--Mode Register Set
			elsif Command = MRS then
				CS <= '0'; RAS <= '0'; CAS <= '0'; WE <= '0';
			end if;
			-----------------------------------------------------
		
			-----------  Yenileme Sayacı  -----------------
			if RefreshCnt < RefCycle - 1 then
				RefreshCnt := RefreshCnt + 1;
			else
				RefreshCnt := 0;
				if NeedRefresh = '0' then
					NeedRefresh <= '1';
				--Önceki yenileme işlemi henüz gerçekleşmedi ise
				--yenilemeye zorla
				else
					ForceRefresh <= '1';
				end if;
			end if;
			-----------------------------------------------
			
			---------------  tRC Sayacı -------------------
			if tRC_Complete = '0' then
				if tRCCnt < tRC - 1 then
					tRCCnt := tRCCnt + 1;
				else
					tRCCnt := 0;
					tRC_Complete := '1';
				end if;
			end if;
			-----------------------------------------------
			
			---------------  tRP Sayacı -------------------
			if tRP_Complete = '0' then
				if tRPCnt < tRP - 1 then
					tRPCnt := tRPCnt + 1;
				else
					tRPCnt := 0;
					tRP_Complete := '1';
				end if;
			end if;
			-----------------------------------------------
			
			---------------  tRCD Sayacı ------------------
			if tRCD_Complete = '0' then
				if tRCDCnt < tRCD - 1 then
					tRCDCnt := tRCDCnt + 1;
				else
					tRCDCnt := 0;
					tRCD_Complete := '1';
				end if;
			end if;
			-----------------------------------------------
			
			---------------  tRAS Sayacı ------------------
			if tRAS_Complete = '0' then
				if tRASCnt < tRAS - 1 then
					tRASCnt := tRASCnt + 1;
				else
					tRASCnt := 0;
					tRAS_Complete := '1';
				end if;
			end if;
			-----------------------------------------------
					
		end if;
			
	end process;
	
end Behavioral;

Quartus II ile oluşturulmuş RTL şeması:

Kaynak kullanımı:

EP4CE22F17C6 (Cyclone IV, -6 Speed Grade) için Fmax:

Çalışma Esnasında Signal Tap II ile Alınan Veriler

Kurulum:

  • IS42S16160G SDRAM
  • 100Mhz Clock Frekansı
  • 2-2-2-6 (\mathbf{CL}-\mathbf{t}_{\mathbf{RCD}}-\mathbf{t}_{\mathbf{RP}}-\mathbf{t}_{\mathbf{RAS}})
  • Burst uzunluğu = 4

Okuma

Farklı Row Okuma

Seri Yazma

Okumadan Yazmaya Geçiş

Normal Yenileme

Yenilemeye Zorlama

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.