- BrainTools - https://www.braintools.ru -

Подключаем к ПЛИС оперативную память SDRAM

На страницах всевозможных статей было написано, что управление микросхемой памяти [1] SDRAM это очень сложно. Отчасти это верно, есть масса тонкостей. Процесс освоения новичкам осложнён отсутствием примеров на русском языке. Вашему вниманию [2] предлагается небольшой пример, как можно подключить оперативную память к ПЛИС. Эти заметки для новичков, таких как и я. А следовательно не торопитесь, проверяйте всё что будете использовать. Особенно реализацию платы, если она у вас самодельная. Опытным пользователям можно не читать (разве что из спортивного интереса [3]). Не буду увлекаться теорией, кому нужно читайте литературу или хотя бы спросите искусственный интеллект [4] (он неплохо может расписать что к чему).

В общих чертах, необходимо провести инициализацию микросхемы (см. документацию на микросхему) и в дальнейшем подавать команды (это сигналы CS, RAS, CAS, WE). Повторюсь, почитайте литературу. Для тех кто совсем не в теме краткое резюме по работе с SDRAM.

Каждые 64 мс необходимо выполнить перезаряд строки памяти. Какой строки, решает сама микросхема, главное команду ей дать (плюс-минус не сильно позже указанного интервала). Так как команды выполняются значительно быстрее этого срока, то не страшно если пришло время регенерации, а выполняется команда. Можно подождать.

Дальше для чтения записи, выполняем активацию строки (память организованна в виде строк и столбцов), на шине адрес строки и команда активации. Далее команда (например) чтения столбца, на шине адрес столбца и команда чтения. Далее чтение данных и в завершении команда перезаряда (после доступа данные нужно перезаписать см. литературу). Естественно между командами выдерживаются заданные паузы (см. документацию).

В составе макета имеем четыре переключателя, четыре светодиода, две кнопки (сброс и старт) и микросхема памяти 8МБ HY57V641620FTP-H (4-ре банка по мегабайту 16-ти битных слов). Алгоритм работы макета следующий. По нажатию кнопки читаем состояние переключателей (только трёх т.к. на плате кнопки параллельны с переключателем), это будет адрес ячейки памяти и данные. Выполняем запись данных по этому адресу (используем только три младших разряда). Выполняем чтение по этому адресу и выводим результат на светодиоды. Всё довольно просто.

Светодиоды.

Светодиоды.
Переключатели.

Переключатели.
Микросхема SDRAM.

Микросхема SDRAM.

Переходим к важным тонкостям. Во первых, так как работа с микросхемой происходит на высокой частоте, то длинна дорожек на печатной плате, может оказать существенное влияние на работу (т.к. дорожки обладают паразитной индуктивностью).

Поясняю, мы выдаём команду по фронту синхроимпульса и предполагаем, что сигналы на входе микросхемы установятся мгновенно. Но это не так, что бы быть уверенным, что к моменту фронта синхросигнала уже все сигналы установились предлагается следующий трюк. Тактировать микросхему памяти синхросигналом смещённым по фазе на 90 градусов (это общий случай). Этот сдвиг подбирается экспериментально (по крайней мере в любительских схемах). У меня стабильно работает в диапазоне сдвига фаз от 25 до 35 градусов (использую 30).

Во вторых на ПЛИС Altera есть рекомендация компилятору размещать выходные элементы прямо возле выходной ножки. В настройках выводов нужно указать “Fast Input Register On” и “Fast Output Enable Register On”.

В третьих все выводы на микросхему памяти в режим 3.3-V LVTTL. Не забываем [5] все неиспользуемые ножки ПЛИС перевести в третье состояние (по умолчанию так и есть, но проверить не помешает). Далее код на verilog. Несколько сумбурный и много строчный, я говорил, что я сам новичок.

`timescale 1ns / 1ps
/* !!! тактирование для SDRAM (сдвиг +30 град.) ЗАРАБОТАЛО!!! */
/*
    По нажатию на кнопку KEY4 фиксируем состояние переключателей CKEY[1..3],
	   выдаём команду записи по адресу {9'd0, CKEY3, CKEY2, CKEY1},
	   записываем данные {13'd0, CKEY3, CKEY2, CKEY1}.
	 После этого читаем содержимое памяти по тому же адресу и
	   выводим младшие три бита на светодиоды.
    Микросхема на плате HY57V641620FTP-H
*/
module TestSDRAM(
		(* chip_pin = "76"  *) output SD_A00, // Адрессная шина
		(* chip_pin = "77"  *) output SD_A01,
		(* chip_pin = "80"  *) output SD_A02,
		(* chip_pin = "83"  *) output SD_A03,
		(* chip_pin = "68"  *) output SD_A04,
		(* chip_pin = "67"  *) output SD_A05,
		(* chip_pin = "66"  *) output SD_A06,
		(* chip_pin = "65"  *) output SD_A07,
		(* chip_pin = "64"  *) output SD_A08,
		(* chip_pin = "60"  *) output SD_A09,
		(* chip_pin = "75"  *) output SD_A10,
		(* chip_pin = "59"  *) output SD_A11,
		
		(* chip_pin = "73"  *) output SD_BS0, // Выбор банка
		(* chip_pin = "74"  *) output SD_BS1,
		
		(* chip_pin = "42"  *) output SD_LDQM, // Маскирование младшего байта
		(* chip_pin = "55"  *) output SD_UDQM, // Маскирование старишего байта
		
		(* chip_pin = "87"  *) output LED1,
		(* chip_pin = "86"  *) output LED2,
		(* chip_pin = "85"  *) output LED3,
		(* chip_pin = "84"  *) output LED4,
		
		(* chip_pin = "58"  *) output SD_CKE,  // Разрешение тактирования
		
		/* Тактовый сигнал сдвинутый по фазе на 90 грд. для компенсации задержек
		из-за длинны дорожек на плпте. */
		(* chip_pin = "43"  *) output SD_CLK,  

		(* chip_pin = "72"  *) output SD_CS,   // Выбор чипа
		(* chip_pin = "71"  *) output SD_RAS,  // Строб строки
		(* chip_pin = "70"  *) output SD_CAS,  // Строб колонки
		(* chip_pin = "69"  *) output SD_WE,   // Разрешение записи
		
		(* chip_pin = "23"  *) input clk,   // тактирование 50МГц
		(* chip_pin = "25"  *) input rst_n, // сброс

		(* chip_pin = "88"  *) input CKEY1, // Задаём число для записи в ОЗУ 16-bit
		(* chip_pin = "89"  *) input CKEY2, // {13'd0, CKEY3, CKEY2, CKEY1}
		(* chip_pin = "90"  *) input CKEY3,

		/* нажатие - команда записи в ОЗУ,
		после записи чтение и вывод на светодиоды */
		(* chip_pin = "91"  *) input KEY4_n,

		(* chip_pin = "28" *) inout SD_D00, // Шина данных
		(* chip_pin = "30" *) inout SD_D01,
		(* chip_pin = "31" *) inout SD_D02,
		(* chip_pin = "32" *) inout SD_D03,
		(* chip_pin = "33" *) inout SD_D04,
		(* chip_pin = "34" *) inout SD_D05,
		(* chip_pin = "38" *) inout SD_D06,
		(* chip_pin = "39" *) inout SD_D07,
		(* chip_pin = "54" *) inout SD_D08,
		(* chip_pin = "53" *) inout SD_D09,
		(* chip_pin = "52" *) inout SD_D10,
		(* chip_pin = "51" *) inout SD_D11,
		(* chip_pin = "50" *) inout SD_D12,
		(* chip_pin = "49" *) inout SD_D13,
		(* chip_pin = "46" *) inout SD_D14,
		(* chip_pin = "44" *) inout SD_D15
		);
	
	// Состояния конечного автомата top
   localparam ST_TOP_IDLE        = 2'd0,
              ST_TOP_WRITE_MEM   = 2'd1,
			  ST_TOP_READ_MEM    = 2'd2,
			  ST_TOP_OUT_LED     = 2'd3;
	//-----------------------------------

	wire clk_sys;    	// 50 MHz (0 deg)
    wire clk_sdram;  	// 50 MHz (30 deg)
	wire pll_locked; 	// 0 - PLL не стабильно, 1 - стабильно
	
	wire Button_Reset;// Кнопка сброс
	wire reset;			// Это общий сброс. Кроме кнопки, влияет состояние PLL.
	wire start;			// Кнопка запуска процесса записи/чтения
	wire SD_Ready;		// Готовность контроллера принимать команды
	
	wire [11:0] addr_from_cntrl;
	reg cmd_write, cmd_read;

	reg [1:0] top_state;
	reg [2:0] rLed;

	/* Это входные данные для контроллера SDRAM,
		  т.е. здесь указывается адрес по которому
		  хотим читать либо писать. А контроллер,
		  на основании этой информации сформирует сигнал на внешние ноги. */
	reg [11:0] addr_mem;

	/* Здесь хранится результат чтения переключателей,
		исходная информация для шины данных (запись в память) */
	reg [15:0] data_to_ram;

	reg  [15:0] dq_out_ioe;
	reg  [15:0] dq_oe_ioe;
	reg  [15:0] dq_in_ioe;
	// ------------------------------------------------------------------------

	// Синхронная передача сигналов на периферию (внутри IOE)
	always @(posedge clk_sys) begin
	  dq_out_ioe  <= data_to_ram;
	  dq_oe_ioe   <= {16{cmd_write}};
	  // Фиксация входных данных из памяти
	  	dq_in_ioe[0]  <= SD_D00;  dq_in_ioe[1]  <= SD_D01;
		dq_in_ioe[2]  <= SD_D02;  dq_in_ioe[3]  <= SD_D03;
		dq_in_ioe[4]  <= SD_D04;  dq_in_ioe[5]  <= SD_D05;
		dq_in_ioe[6]  <= SD_D06;  dq_in_ioe[7]  <= SD_D07;
		dq_in_ioe[8]  <= SD_D08;  dq_in_ioe[9]  <= SD_D09;
		dq_in_ioe[10] <= SD_D10;  dq_in_ioe[11] <= SD_D11;
		dq_in_ioe[12] <= SD_D12;  dq_in_ioe[13] <= SD_D13;
		dq_in_ioe[14] <= SD_D14;  dq_in_ioe[15] <= SD_D15;
	end

	// Побитовое назначение Tri-state буферов напрямую на физические пины inout
	assign SD_D00 = dq_oe_ioe[0]  ? dq_out_ioe[0]  : 1'bZ;
	assign SD_D01 = dq_oe_ioe[1]  ? dq_out_ioe[1]  : 1'bZ;
	assign SD_D02 = dq_oe_ioe[2]  ? dq_out_ioe[2]  : 1'bZ;
	assign SD_D03 = dq_oe_ioe[3]  ? dq_out_ioe[3]  : 1'bZ;
	assign SD_D04 = dq_oe_ioe[4]  ? dq_out_ioe[4]  : 1'bZ;
	assign SD_D05 = dq_oe_ioe[5]  ? dq_out_ioe[5]  : 1'bZ;
	assign SD_D06 = dq_oe_ioe[6]  ? dq_out_ioe[6]  : 1'bZ;
	assign SD_D07 = dq_oe_ioe[7]  ? dq_out_ioe[7]  : 1'bZ;
	assign SD_D08 = dq_oe_ioe[8]  ? dq_out_ioe[8]  : 1'bZ;
	assign SD_D09 = dq_oe_ioe[9]  ? dq_out_ioe[9]  : 1'bZ;
	assign SD_D10 = dq_oe_ioe[10] ? dq_out_ioe[10] : 1'bZ;
	assign SD_D11 = dq_oe_ioe[11] ? dq_out_ioe[11] : 1'bZ;
	assign SD_D12 = dq_oe_ioe[12] ? dq_out_ioe[12] : 1'bZ;
	assign SD_D13 = dq_oe_ioe[13] ? dq_out_ioe[13] : 1'bZ;
	assign SD_D14 = dq_oe_ioe[14] ? dq_out_ioe[14] : 1'bZ;
	assign SD_D15 = dq_oe_ioe[15] ? dq_out_ioe[15] : 1'bZ;

	/* Вывод содержимого addr на выходные ножки
		  содержимое addr меняет контроллер SDRAM	*/
	assign {SD_A11, SD_A10, SD_A09, SD_A08,
			  SD_A07, SD_A06, SD_A05, SD_A04,
			  SD_A03, SD_A02, SD_A01, SD_A00} = addr_from_cntrl;

	/*-------------------------------------------------------------------------
	   Из одного входного тактового сигнала формируется два:
			-первый копия входного,
			-второй смещённый по фазе на -90 градусов. Это необходимо
			для корректной работы микросхемы памяти на высокой частоте.
			Так как не всегда все дорожки на плате одинаковые и короткие,
			что приводит к задержкам и несогласованной с контроллером работе. */
	PLL_SDRAM_OFF PLL1(.inclk0(clk),	// Входной тактовый сигнал
						.c0(clk_sys),	// тактирование для всех, кроме SDRAM
						.c1(clk_sdram),// тактирование для SDRAM (сдвиг +30 град.)
						.locked(pll_locked));	// 0- сигнал не стабилен
	//-------------------------------------------------------------------------

	/* Контроллер реализован в виде автомата. На основании входных данных
			формирует сигналы управления, с учётом необходимых задержек. */
	Controller_SDRAM Controller_50MHz(
		.sdram_cmd_out({SD_CS, SD_RAS, SD_CAS, SD_WE}),
		.sdram_ba({SD_BS1, SD_BS0}),		// Номер банка
		.sdram_a(addr_from_cntrl),			// Адрес на шину
		.ready(SD_Ready),					// 1- готов к приёму команд
		.cmd_read(cmd_read),
		.cmd_write(cmd_write),
		.bank(2'd0),
		.row(12'd0),
		.col(addr_mem[7:0]),	// Адрес от "меня", куда писать/читать
		.clk(clk_sys),
		.rst(reset)
	);	

	// Выводим сдвинутый тактовый сигнал наружу на микросхему
	// через специализированный DDIO примитив Intel/Altera.
	// Обычный assign вызовет джиттер и фазовый сдвиг, ломающий Fast I/O.
	altddio_out #(
		.width(1)
	) sdram_clk_ddio_buf (
		.datain_h(1'b1),
		.datain_l(1'b0),
		.outclock(clk_sdram),
		.dataout(SD_CLK)
	);
	assign SD_LDQM = 1'd0; // Не используем маскирование
	assign SD_UDQM = 1'd0; // Не используем маскирование
	assign SD_CKE  = 1'd1; // Всё время тактирование разрешено

	Button BT_R(.TTrigQ(Button_Reset), 	// 1 - сброс
					.X(rst_n),			// Кнопка сброс, 0 - сброс
					.C(clk_sys));
					
	Button BT_K(.TTrigQ(start),	// Чтение переключателей, з/ч памяти, вывод
					.X(KEY4_n),		// Кнопка Старт
					.C(clk_sys));

	assign reset = Button_Reset | ~pll_locked;

	assign LED1 = reset ? 1'd1 : rLed[0];
	assign LED2 = reset ? 1'd1 : rLed[1];
	assign LED3 = reset ? 1'd1 : rLed[2];
	assign LED4 = 1'd1;

	always @(posedge clk_sys or posedge reset)
	begin
		if (reset) begin
			rLed <= 3'd7;
			top_state <= ST_TOP_IDLE;
			addr_mem <= 12'd0;
			cmd_write <= 1'd0; // Снять команду записи
			cmd_read <= 1'd0;  // Снять команду чтения
		end else begin

			cmd_write <= 1'd0;
			cmd_read  <= 1'd0;

			case (top_state)
				ST_TOP_IDLE: begin
					if (start) begin
						top_state <= ST_TOP_WRITE_MEM;
					end
				end
					
				ST_TOP_WRITE_MEM: begin
					if (SD_Ready) begin
						/* Читаем состояние переключателя - это адрес ячейки
							и данные одновременно */
						addr_mem <= {9'd0, CKEY3, CKEY2, CKEY1};
						data_to_ram <= {13'd0, CKEY3, CKEY2, CKEY1};
						cmd_write <= 1'd1; // Выдать команду записи
						top_state <= ST_TOP_READ_MEM;
					end
				end

				ST_TOP_READ_MEM: begin
					if (SD_Ready) begin
						cmd_read <= 1'd1; // Выдать команду чтения
						top_state <= ST_TOP_OUT_LED;
					end
				end

				ST_TOP_OUT_LED: begin
				/* Ожидаю готовность от контроллера, когда есть готовность, значит
					сигналы на шине установились */
					if (SD_Ready) begin
						rLed <= dq_in_ioe[2:0];
						top_state <= ST_TOP_IDLE;
					end
				end

				default: top_state <= ST_TOP_IDLE;
			endcase
		end
	end
endmodule

Основной модуль.

`timescale 1ns / 1ps
/* Код для кнопок */
module Button(output reg TTrigQ,
              input X,
				  input C);

	initial TTrigQ <= 1'd1;
	
	reg [18:0]CTQ; // счётчик подавления дребезга контактов
	reg XQ, RSTrigQ, BQ;
	
	wire FY = !RSTrigQ & BQ; // схема выделения фронта
	
	always @(posedge C)
	begin
		XQ <= !X;
		/* &CTQ - все биты счётчика равны единице, т.е. максимум, даёт единицу
	      |CTQ - все биты счётчика равны нулю, т.е. минимум, даёт ноль	*/
		if (XQ & ~&CTQ) CTQ <= CTQ + 1'd1;
			else if (!XQ & |CTQ) CTQ <= CTQ - 1'd1;
		
		if (&CTQ) RSTrigQ <= 1'd1; // счётчик досчитал до максимум, запоминаем 1
			else if (~|CTQ) RSTrigQ <= 1'd0; // счётчик досчитал до минимума, запоминаем 0
		
		BQ <= RSTrigQ;
		//if (FY) TTrigQ <= !TTrigQ; // по фронту переключаем тригер
		TTrigQ <= FY;
	end

endmodule

Кнопка.

`timescale 1ns / 1ps
/* Сигналы маскирования (SD_LDQM, SD_UDQM) не используем */

/* Принцип работы логики регенерации
   Счетчик интервала: Внутренний таймер непрерывно отсчитывает 780 тактов.
	
	Как только интервал истекает, выставляется скрытый флаг
		запроса refresh_req = 1.
   
	Арбитраж: Этот флаг имеет наивысший приоритет. Когда автомат находится
		в состоянии ST_IDLE, он проверяет этот флаг раньше, чем запросы
		от пользователя (cmd_read/cmd_write).
   
	Сброс флага: Перейдя в состояние регенерации ST_REFRESH,
		автомат сбрасывает этот флаг, выполняет команду CMD_REFRESH,
		выдерживает паузу tRFC (4 такта) и возвращается в ST_IDLE.

   Предотвращение коллизий: Метод проверки флага refresh_req строго
		в состоянии ST_IDLE абсолютно безопасен. Контроллер никогда не прервет
		активную операцию чтения или записи посередине. Он корректно закончит
		транзакцию пользователя, выполнит команду PRECHARGE (закроет строку),
		вернется в ST_IDLE и только затем уйдет на регенерацию.

   Запас по времени: На частоте 50 МГц полный цикл чтения или записи
		с закрытием строки занимает около 6-7 тактов. Даже если запрос
		refresh_req придет в самом начале чтения, регенерация задержится
		всего на ~140 нс, что ничтожно мало по сравнению с критическим
		окном удержания данных.
   */
module Controller_SDRAM(
	//-------------------------------------------------------------------------
	// Интерфейсы к IOE верхнего уровня
	output reg  [3:0]  sdram_cmd_out, // Команда наружу {CS, RAS, CAS, WE}
	output reg  [1:0]  sdram_ba,		 // Номер банка
	output reg  [11:0] sdram_a,		 // Адрес
	//-------------------------------------------------------------------------
	
	//-------------------------------------------------------------------------
	// Интерфейс пользователя (ПЛИС)
	output reg         ready,    // Готовность принять команду
	
	input  wire        cmd_read, // Команда чтения
	input  wire        cmd_write,// Команда записи
	input  wire [1:0]  bank,	  // Номер банка
	input  wire [11:0] row,		  // Номер строки
	input  wire [7:0]  col,		  // Номер столбца
	//-------------------------------------------------------------------------
	
	input  wire        clk,        // 50 МГц (период 20 нс)
	input  wire        rst         // Сброс
);

	// Состояния конечного автомата
   localparam ST_INIT_NOP  = 4'd0,
              ST_INIT_PRE  = 4'd1,
              ST_INIT_REF  = 4'd2,
              ST_INIT_MRS  = 4'd3,
              ST_IDLE      = 4'd4,
              ST_ACTIVATE  = 4'd5,
              ST_WRITE     = 4'd6,
              ST_READ      = 4'd7,
              ST_PRECHARGE = 4'd8,
              ST_REFRESH   = 4'd9; // Новое рабочее состояние регенерации
				  
	// Команды SDRAM {CS, RAS, CAS, WE}
   localparam CMD_NOP      = 4'b0111,
              CMD_PRECHARGE= 4'b0010,
              CMD_REFRESH  = 4'b0001,
              CMD_LOAD_MODE= 4'b0000,
              CMD_ACTIVATE = 4'b0011,
              CMD_READ     = 4'b0101,
              CMD_WRITE    = 4'b0100;

   // Параметры задержек для 50 МГц
   localparam WAIT_200US = 14'd10000; // 200mks = 10000 * (1 / (50 * 10^6))
	/* Time of Row Precharge - завершения команды PRECHARGE
	(закрытие текущей строки в банке памяти).
	Контроллер обязан "замереть" в состоянии WAIT_TRP на количество тактов,
	равное или превышающее паспортное значение
	(t_RP = 15нс) для данной частоты.	*/
   localparam WAIT_TRP   = 14'd2;
	
   localparam WAIT_TRFC  = 14'd4; // Регенерация 60нс
   localparam WAIT_TMRD  = 14'd2; // Установка ModeRegister 2 такта
   localparam WAIT_TRCD  = 14'd2; // Активация чтение запись 15нс
	
	// --- ЛОГИКА REFRESH COUNTER ---
   // 15.6 мкс / 20 нс = 780 тактов. Счет ведем от 0 до 779.
   localparam REFRESH_INTERVAL = 10'd779;
	
	reg [9:0] refresh_timer; // Счетчик интервала 15.6 мкс
   reg       refresh_req;   // Триггер-запрос на регенерацию
	
	always @(posedge clk or posedge rst) begin
		if (rst) begin
			refresh_timer <= 10'd0;
         refresh_req   <= 1'b0;
      end else begin
			if (refresh_timer >= REFRESH_INTERVAL) begin
				refresh_timer <= 10'd0;
            refresh_req   <= 1'b1; // Время истекло, взводим флаг запроса
         end else begin
				refresh_timer <= refresh_timer + 1'b1;
         end

         // Сбрасываем запрос только тогда, когда FSM физически зашел
			// в состояние регенерации
         if (state == ST_REFRESH && delay_cnt == 0) begin
				refresh_req <= 1'b0;
         end
		end
   end
   // ------------------------------
	
	reg [3:0]  state;
   reg [13:0] delay_cnt;
   reg [1:0]  init_ref_cnt; // Счетчик авторегенераций при старте

	// Конечный автомат управления переходами
	always @(posedge clk or posedge rst) begin
		if (rst) begin
			state        <= ST_INIT_NOP;
			delay_cnt    <= 14'd0;
			init_ref_cnt <= 2'd0;
			ready        <= 1'b0;
		end else begin
			case (state)
				// --- Стадия инициализации ---
				ST_INIT_NOP: begin
				/* Пауза 200мкс */
				  if (delay_cnt >= WAIT_200US) begin
						state     <= ST_INIT_PRE;
						delay_cnt <= 14'd0;
				  end else begin
						delay_cnt <= delay_cnt + 1'b1;
				  end
				end
				
				ST_INIT_PRE: begin
				  if (delay_cnt >= WAIT_TRP) begin
						state     <= ST_INIT_REF;
						delay_cnt <= 14'd0;
				  end else begin
						delay_cnt <= delay_cnt + 1'b1;
				  end
				end
				
				ST_INIT_REF: begin
				  if (delay_cnt >= WAIT_TRFC) begin
						delay_cnt <= 14'd0;
						if (init_ref_cnt >= 2'd2) begin
							 state <= ST_INIT_MRS;
						end else begin
							 init_ref_cnt <= init_ref_cnt + 1'b1;
						end
				  end else begin
						delay_cnt <= delay_cnt + 1'b1;
				  end
				end
				
				ST_INIT_MRS: begin
				  if (delay_cnt >= WAIT_TMRD) begin
						state     <= ST_IDLE;
						delay_cnt <= 14'd0;
				  end else begin
						delay_cnt <= delay_cnt + 1'b1;
				  end
				end
				
				// --- Рабочий цикл и Арбитраж ---
				ST_IDLE: begin
				  ready <= 1'b1;
				  delay_cnt <= 14'd0; // Явно держим счетчик в 0, пока отдыхаем!!!!!!!!!!!!!!!
				
				  if (refresh_req) begin
						// Приоритет №1: принудительная регенерация памяти
						state <= ST_REFRESH;
						ready <= 1'b0;
				  end else if (cmd_read || cmd_write) begin
						// Приоритет №2: команды пользователя, если нет запроса регенерации
						state <= ST_ACTIVATE;
						ready <= 1'b0;
				  end
				end
				
				ST_ACTIVATE: begin
				  if (delay_cnt >= WAIT_TRCD - 1) begin
						delay_cnt <= 14'd0;
						state     <= cmd_write ? ST_WRITE : ST_READ;
				  end else begin
						delay_cnt <= delay_cnt + 1'b1;
				  end
				end
				
				ST_WRITE: begin
					delay_cnt <= 14'd0; // Обнуляем для будущего цикла PRECHARGE!!!!!!!!!
					state <= ST_PRECHARGE;
				end
				
				ST_READ: begin
					delay_cnt <= 14'd0; // Обнуляем для будущего цикла PRECHARGE!!!!!!!!!
					state <= ST_PRECHARGE;
				end
				
				ST_PRECHARGE: begin
				  if (delay_cnt >= WAIT_TRP) begin
						delay_cnt <= 14'd0;
						state     <= ST_IDLE; // Возврат в IDLE, где сразу проверится refresh_req
				  end else begin
						delay_cnt <= delay_cnt + 1'b1;
				  end
				end
				
				// --- Новое состояние: Периодический Auto Refresh в процессе работы ---
				ST_REFRESH: begin
				  if (delay_cnt >= WAIT_TRFC) begin
						delay_cnt <= 14'd0;
						state     <= ST_IDLE; // Регенерация завершена, возвращаемся в ожидание
				  end else begin
						delay_cnt <= delay_cnt + 1'b1;
				  end
				end
				
				default: begin
					state <= ST_INIT_NOP;
					delay_cnt <= 14'd0; //!!!!!!!!!!!!!!!!!!!!!!!!!!
				end
			endcase
		end
	end
	
	// Формирование команд на шину
	always @(*) begin
		sdram_ba  = bank;
		sdram_a   = 12'd0;
		
		case (state)
			ST_INIT_NOP:   sdram_cmd_out = CMD_NOP;
			ST_INIT_PRE: begin
				 sdram_cmd_out = CMD_PRECHARGE;
				 sdram_a       = 12'b0100_0000_0000; // A10=1 (Precharge All)
			end
			ST_INIT_REF:   sdram_cmd_out = CMD_REFRESH;
			ST_INIT_MRS: begin
				 sdram_cmd_out = CMD_LOAD_MODE;
				 sdram_a       = 12'b0000_0010_0000; // CL=2, BL=1 чтение/запись пакетом, длиной 1 слово
			end
			
			ST_IDLE:       sdram_cmd_out = CMD_NOP;
			
			ST_ACTIVATE: begin
				 sdram_cmd_out = CMD_ACTIVATE;
				 sdram_a       = row;
			end
			ST_WRITE: begin
				 sdram_cmd_out = CMD_WRITE;
				 sdram_a       = {4'b0000, col}; // A10=0 (без авто-пречарджа)
			end
			ST_READ: begin
				 sdram_cmd_out = CMD_READ;
				 sdram_a       = {4'b0000, col}; // A10=0
			end
			ST_PRECHARGE: begin
				 sdram_cmd_out = CMD_PRECHARGE;
				 sdram_a       = 12'b0000_0000_0000; // A10=0 (закрыть только текущий банк)
			end
			
			// Выдача физической команды авторегенерации на шину памяти
			ST_REFRESH: begin
				 sdram_cmd_out = CMD_REFRESH;
			end
			
			default:       sdram_cmd_out = CMD_NOP;
		endcase
	end
endmodule

Контроллер SDRAM.

Кому нужен был рабочий пример, наслаждайтесь.

Автор: JackKatch

Источник [6]


Сайт-источник BrainTools: https://www.braintools.ru

Путь до страницы источника: https://www.braintools.ru/article/32357

URLs in this post:

[1] памяти: http://www.braintools.ru/article/4140

[2] вниманию: http://www.braintools.ru/article/7595

[3] интереса: http://www.braintools.ru/article/4220

[4] интеллект: http://www.braintools.ru/article/7605

[5] забываем: http://www.braintools.ru/article/333

[6] Источник: https://habr.com/ru/articles/1053032/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1053032

www.BrainTools.ru

Rambler's Top100