Обновление для простого SDRAM контроллера

По давней академической традиции начало календарного года у большинства студентов совпадает с промежуточной аттестацией, которая является своеобразным итогом нескольких предыдущих месяцев напряжённой учёбы. Мне тоже пришлось на время забыть о Марсоходе и погрузиться в экзаменационную рутину, но, слава роботам, сессия закончилась и я могу вернуться к более приятным вещам.

Прежде всего, я решил подготовить небольшое обновление простого контроллера SDRAM. Основным недостатком существующей версии этого контроллера является отсутствие поддержки автоматической регенерации ячеек памяти – это незаметно, если регулярно обращаться ко всему объёму используемой оперативной памяти, однако, если требуется отложить данные "в долгий ящик", можно столкнуться с проблемой их постепенной порчи.

Производитель микросхемы памяти предусмотрел два способа регенерации, из которых задействовать можно только AUTO REFRESH (таковы особенности схемотехники платы Марсоход2). Micron рекомендует подавать 4096 команд AUTO REFRESH в течении 64 миллисекунд, причём каждая команда регенерирует какую-то одну строку соответственно внутреннему счётчику. Для применения команды необходимо, чтобы все активные строки и банки были закрыты, а после команды следует выждать некоторое время (66 наносекунд для микросхемы, установленной на Марсоход2).

Вот мой обновленный контроллер:

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity sdram_controller is
	generic (
				--memory frequency in MHz
				sdram_frequency	: integer := 50
				);
	port (
			--ready to operation
			ready						: out std_logic := '0';
			--clk
			clk						: in std_logic;
			--read
			rd_req					: in std_logic;
			rd_adr					: in std_logic_vector(21 downto 0);
			rd_data					: out std_logic_vector(15 downto 0);
			rd_valid					: out std_logic;
			--write
			wr_req					: in std_logic;
			wr_adr					: in std_logic_vector(21 downto 0);
			wr_data					: in std_logic_vector(15 downto 0);
			--SDRAM interface
			sdram_wren_n			: out std_logic := '1';
			sdram_cas_n				: out std_logic := '1';
			sdram_ras_n				: out std_logic := '1';
			sdram_a					: out std_logic_vector(11 downto 0);
			sdram_ba					: out std_logic_vector(1 downto 0);
			sdram_dqm				: out std_logic_vector(1 downto 0);
			sdram_dq					: inout std_logic_vector(15 downto 0);
			sdram_clk_n				: out std_logic
			);
end entity;

architecture rtl of sdram_controller is


	signal adr						: std_logic_vector(21 downto 0)	 := (others => '0'); 			--selected address
	signal adr_reg					: std_logic_vector(21 downto 0)	 := (others => '0');				--selected address register
	signal state					: std_logic_vector(2 downto 0)	 := "100";							--state machine register
	signal sdram_cmd				: std_logic_vector(2 downto 0)	 := (others => '1');				--command register
	signal wr_data1				: std_logic_vector(15 downto 0)	 := (others => '0');				--write data pipe stage1
	signal wr_data2				: std_logic_vector(15 downto 0)	 := (others => '0'); 			--write data pipe stage2
	signal rd_pipe_valid			: std_logic_vector(3 downto 0)	 := (others => '0');	
	signal rd_now					: std_logic := '0';
	signal rd_selected			: std_logic := '0';
	signal wr_selected			: std_logic := '0';
	signal rd_cycle				: std_logic := '0';
	signal same_row_and_bank	: std_logic := '0';
	signal sdram_dq_oe			: std_logic := '0';					--output enable
	signal init_cnt				: integer := 0;						--initialization counter value
	signal refresh_cnt			: integer := 0;						--refresh counter
	
	--sdram commands
	constant cmd_loadmode		: std_logic_vector(2 downto 0) := "000";
	constant cmd_refresh			: std_logic_vector(2 downto 0) := "001";
	constant cmd_precharge		: std_logic_vector(2 downto 0) := "010";
	constant cmd_active 			: std_logic_vector(2 downto 0) := "011";
	constant cmd_write			: std_logic_vector(2 downto 0) := "100";
	constant cmd_read				: std_logic_vector(2 downto 0) := "101";
	constant cmd_nop				: std_logic_vector(2 downto 0) := "111";
	
	
	--Timing parameters
	constant tRAS			: integer :=	((sdram_frequency * 44)/1000)+1;		--ACTIVE-to-PRECHARGE command
	constant tRC			: integer :=	((sdram_frequency * 66)/1000)+1;		--ACTIVE-to-ACTIVE command period
	constant tRCD			: integer :=	((sdram_frequency * 20)/1000)+1;		--ACTIVE-to-READ or WRITE delay
	constant tRFC			: integer :=	((sdram_frequency * 66)/1000)+1;		--AUTO REFRESH command period
	constant tRP			: integer :=	((sdram_frequency * 20)/1000)+1;		--PRECHARGE command period
	constant tRRD			: integer :=	((sdram_frequency * 15)/1000)+1;		--ACTIVE bank a to ACTIVE bank b command
	constant tWR			: integer :=	((sdram_frequency * 15)/1000)+1;		--WRITE recovery time
	constant tINIT			: integer :=	(sdram_frequency * 10)+1;				--minimal initialization time
	constant tREF			: integer :=	((sdram_frequency * 1560)/1000)+1;	--REFRESH period (for row)
	
begin

	state_machine:process(clk)
	begin
		if rising_edge(clk) then
			case state is
				when "000" =>	if ((rd_req = '1') or (wr_req = '1')) then	--if read or write request 
										sdram_cmd <= cmd_active;						--then activate sdram
										sdram_ba <= adr(21 downto 20);				--and open this bank
										sdram_a <= adr(19 downto 8);					--and this row
										sdram_dqm <= "11";
										state <= "001";									--go to "read or write" state after all
									else
										sdram_cmd <= cmd_nop;							--if no requests then no operation needed
										sdram_ba <= (others => '0');
										sdram_a <= (others => '0');
										sdram_dqm <= "11";
										state <= "000";
									end if;
									
				when "001" =>	if (rd_selected = '1') then
										sdram_cmd <= cmd_read;							--run read if read needed
									else
										sdram_cmd <= cmd_write;							--...or write
									end if;
									sdram_ba <= adr_reg(21 downto 20);
									sdram_a(9 downto 0) <= "00" & adr_reg(7 downto 0);
									sdram_a(10) <= '0';
									sdram_dqm <= "00";
									--if row address do not change, repeat prev operation
									if ((rd_selected = '1' and rd_req = '1' and same_row_and_bank = '1') or (rd_selected = '0' and wr_req = '1' and same_row_and_bank = '1')) then
										state <= "001";
									else --else open new bank and row
										state <= "010";
									end if;
									
				when "010" =>	sdram_cmd <= cmd_precharge; --closing row
									sdram_ba <= (others => '0');
									sdram_a <= (10 => '1', others => '0');
									sdram_dqm <= "11";
									state <= "011";
									
									
				when "011" =>	sdram_cmd <= cmd_nop;
									sdram_ba <= (others => '0');
									sdram_a <= (others => '0');
									sdram_dqm <= "11";
									if (refresh_cnt = tREF+1) then
										state <= "101";
									else
										state <= "000";
									end if;
				
				when "100" =>	if (init_cnt = tINIT+1) then --initialization
										sdram_cmd <= cmd_precharge;
										sdram_a(10) <= '1';
									elsif (init_cnt = tINIT+tRP+1 or init_cnt = tINIT+tRP+tRFC+1) then
										sdram_cmd <= cmd_refresh;
									elsif (init_cnt = tINIT+tRP+2*tRFC+1) then
										sdram_cmd <= cmd_loadmode;
										sdram_a(9 downto 0) <= "0000100111";
									elsif (init_cnt = tINIT+tRP+2*tRFC+3+1) then
										state <= "000";
									else
										sdram_cmd <= cmd_nop;
									end if;
									
				when "101" =>	sdram_cmd <= cmd_refresh;
								if ((refresh_cnt > 0) and refresh_cnt < tRFC) then
									sdram_cmd <= cmd_nop;
								elsif (refresh_cnt = tRFC) then
									state <= "000";
								end if;								
				
				when others => null;
										
			end case;
		end if;
	end process state_machine;
	
	read_priority:process(rd_req, wr_req) --read requests have priority
	begin
		rd_now <= rd_req;
	end process read_priority;
	
	process(clk)
	begin
		if rising_edge(clk) then
			if (state = "000") then
				rd_selected <= rd_now;
			end if;
		end if;
	end process;
	
	address_select:process(clk, rd_cycle)
	begin
		if rising_edge(clk) then
			adr_reg <= adr;
		end if;
	end process address_select;
	
	output_enable:process(clk)
	begin
		if rising_edge(clk) then
			if (state = "001") then
				sdram_dq_oe <= wr_selected;
			else
				sdram_dq_oe <= '0';
			end if;
		end if;
	end process output_enable;
	
	process(clk)
	begin
		if rising_edge(clk) then
			wr_data1 <= wr_data;
			wr_data2 <= wr_data1;
		end if;
	end process;
	
	read_valid:process(clk)
	begin
		if rising_edge(clk) then
			if (state = "001" and rd_selected = '1') then
				rd_pipe_valid <= rd_pipe_valid(2 downto 0) & '1';
			else
				rd_pipe_valid <= rd_pipe_valid(2 downto 0) & '0';
			end if;
		end if;
	end process read_valid;
	
	read_data:process(clk)
	begin
		if rising_edge(clk) then
			rd_data <= sdram_dq;
		end if;
	end process read_data;
	
	init_counter:process(clk)
	begin
		if rising_edge(clk) then
			if (init_cnt < (tINIT+2*tRFC+tRP+3+1)) then
				init_cnt <= init_cnt+1;
			else
				null;
			end if;
		end if;
	end process init_counter;
	
	refresh_counter:process(clk)
	begin
		if rising_edge(clk) then
			if (refresh_cnt < tREF+1) then
				refresh_cnt <= refresh_cnt+1;
			else
				if (state = "101") then
					refresh_cnt <= 0;
				end if;
			end if;
		end if;
	end process refresh_counter;
	
	ready <= '1' when ((state = "000") or (state = "001")) else '0';
	
	adr <= rd_adr when (rd_cycle = '1') else wr_adr;	--address select
	
	same_row_and_bank <= '1' when (adr(21 downto 8) = adr_reg(21 downto 8)) else '0';
	rd_cycle <= rd_now when (state = "000") else rd_selected;
	wr_selected <= not rd_selected;
	rd_valid <= rd_pipe_valid(3);
	
	--command set
	sdram_ras_n <= sdram_cmd(2);
	sdram_cas_n <= sdram_cmd(1);
	sdram_wren_n <= sdram_cmd(0);
	
	--write
	sdram_dq <= wr_data2 when sdram_dq_oe = '1' else (others => 'Z');
	
	--sdram_clock
	sdram_clk_n <= not clk;

end rtl;

Изменения в контроллере нельзя назвать масштабными. Прежде всего, появился счётчик, который отмеряет периоды между командами AUTO REFRESH. Надо заметить, что для перехода автомата в состояние "регенерации", на котором базируется работа контроллера, должно выполниться два условия: счётчик должен накопить определённое значение, и должна быть применена операция закрытия активной строки/банка. Автомат будет находиться в состоянии "регенерации" столько, сколько необходимо для соблюдения рекомендуемых производителем временных интервалов.

Алгоритм работы с контроллером тоже немного изменился – теперь следует больше внимания уделять выходу ready. На нём формируется высокое логическое состояние, когда контроллер готов воспринимать сигналы со входов (запросы на чтение и запись, данные, адреса), и низкое, когда производятся операции инициализации, закрытия строк и регенерации. На мой взгляд, это должно сделать взаимодействие с контроллером более удобным.

Наконец, я добавил настраиваемый параметр sdram_frequency – на его основе высчитываются основные задержки микросхемы в тактах синхронизирующего сигнала. Этот параметр следует указывать в МГц, причём я бы не рекомендовал выходить за пределы 100 МГц – на такой частоте стабильная работа контроллера и памяти уже не гарантируется.

Честно говоря, у меня пока нет возможности верифицировать работу контроллера на 100%, поэтому я надеюсь на обратную связь – о найденных ошибках и прочих "неопрятностях" можно писать прямо в комментариях к этой статье или мне на электронную почту.

PS: эта статья - продолжение моей другой работы Простейший SDRAM-контроллер на VHDL

 

Добавить комментарий