惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

G
Google Developers Blog
Jina AI
Jina AI
大猫的无限游戏
大猫的无限游戏
Martin Fowler
Martin Fowler
博客园 - 司徒正美
云风的 BLOG
云风的 BLOG
C
Cybersecurity and Infrastructure Security Agency CISA
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
S
Securelist
S
Security Affairs
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
L
LINUX DO - 热门话题
博客园 - 三生石上(FineUI控件)
T
Threatpost
T
The Blog of Author Tim Ferriss
C
CERT Recently Published Vulnerability Notes
IT之家
IT之家
P
Palo Alto Networks Blog
Microsoft Azure Blog
Microsoft Azure Blog
Spread Privacy
Spread Privacy
Cyberwarzone
Cyberwarzone
腾讯CDC
L
LangChain Blog
Know Your Adversary
Know Your Adversary
C
CXSECURITY Database RSS Feed - CXSecurity.com
GbyAI
GbyAI
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
I
Intezer
T
Tor Project blog
AWS News Blog
AWS News Blog
T
Tenable Blog
NISL@THU
NISL@THU
Security Latest
Security Latest
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
H
Hackread – Cybersecurity News, Data Breaches, AI and More
人人都是产品经理
人人都是产品经理
MongoDB | Blog
MongoDB | Blog
MyScale Blog
MyScale Blog
D
DataBreaches.Net
Microsoft Security Blog
Microsoft Security Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
量子位
美团技术团队
The Cloudflare Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
罗磊的独立博客
The GitHub Blog
The GitHub Blog
阮一峰的网络日志
阮一峰的网络日志
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Stack Overflow Blog
Stack Overflow Blog

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Подключаем к ПЛИС оперативную память SDRAM
JackKatch · 2026-06-29 · via Все публикации подряд на Хабре

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

В общих чертах, необходимо провести инициализацию микросхемы (см. документацию на микросхему) и в дальнейшем подавать команды (это сигналы 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. Не забываем все неиспользуемые ножки ПЛИС перевести в третье состояние (по умолчанию так и есть, но проверить не помешает). Далее код на 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.

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