На страницах всевозможных статей было написано, что управление микросхемой памяти SDRAM это очень сложно. Отчасти это верно, есть масса тонкостей. Процесс освоения новичкам осложнён отсутствием примеров на русском языке. Вашему вниманию предлагается небольшой пример, как можно подключить оперативную память к ПЛИС. Эти заметки для новичков, таких как и я. А следовательно не торопитесь, проверяйте всё что будете использовать. Особенно реализацию платы, если она у вас самодельная. Опытным пользователям можно не читать (разве что из спортивного интереса). Не буду увлекаться теорией, кому нужно читайте литературу или хотя бы спросите искусственный интеллект (он неплохо может расписать что к чему).
В общих чертах, необходимо провести инициализацию микросхемы (см. документацию на микросхему) и в дальнейшем подавать команды (это сигналы CS, RAS, CAS, WE). Повторюсь, почитайте литературу. Для тех кто совсем не в теме краткое резюме по работе с SDRAM.
Каждые 64 мс необходимо выполнить перезаряд строки памяти. Какой строки, решает сама микросхема, главное команду ей дать (плюс-минус не сильно позже указанного интервала). Так как команды выполняются значительно быстрее этого срока, то не страшно если пришло время регенерации, а выполняется команда. Можно подождать.
Дальше для чтения записи, выполняем активацию строки (память организованна в виде строк и столбцов), на шине адрес строки и команда активации. Далее команда (например) чтения столбца, на шине адрес столбца и команда чтения. Далее чтение данных и в завершении команда перезаряда (после доступа данные нужно перезаписать см. литературу). Естественно между командами выдерживаются заданные паузы (см. документацию).
В составе макета имеем четыре переключателя, четыре светодиода, две кнопки (сброс и старт) и микросхема памяти 8МБ HY57V641620FTP-H (4-ре банка по мегабайту 16-ти битных слов). Алгоритм работы макета следующий. По нажатию кнопки читаем состояние переключателей (только трёх т.к. на плате кнопки параллельны с переключателем), это будет адрес ячейки памяти и данные. Выполняем запись данных по этому адресу (используем только три младших разряда). Выполняем чтение по этому адресу и выводим результат на светодиоды. Всё довольно просто.



Переходим к важным тонкостям. Во первых, так как работа с микросхемой происходит на высокой частоте, то длинна дорожек на печатной плате, может оказать существенное влияние на работу (т.к. дорожки обладают паразитной индуктивностью).
Поясняю, мы выдаём команду по фронту синхроимпульса и предполагаем, что сигналы на входе микросхемы установятся мгновенно. Но это не так, что бы быть уверенным, что к моменту фронта синхросигнала уже все сигналы установились предлагается следующий трюк. Тактировать микросхему памяти синхросигналом смещённым по фазе на 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.
Кому нужен был рабочий пример, наслаждайтесь.
PS: Упустил один важный нюанс, в коде он отмечен. Как нужно выводить наружу сдвинутый по фазе тактовый сигнал для SDRAM. Альтеровская "функция" altddio_out.
Комментарии (6)

GooseWing
29.06.2026 02:48А есть советы как подступиться к изучению ПЛИС с нуля? Последний раз щупал альтеру на лабах в универе (в симуляторе на временных диаграммах да прошивка готового фильтра фнч, фвч) и по сути можно начинать сначала. Гайды, статьи хорошие? Литературу не сильно люблю, но тоже пойдет.

JackKatch Автор
29.06.2026 02:48Я смотрел уроки на Ютуб-канале ПЛИСоводство. Думаю там основная база есть.

rot97
29.06.2026 02:48Рекомендую книгу А. Б. Романов, Ю. А. Панчул Цифровой синтез. Практический курс.

yamifa_1234
29.06.2026 02:48Нужно указать источники которые вы использовали, чтобы другие смогли найти дополнительную информацию при изучении.
Ещё интересно о режиме работы. У вас одна команда за раз выполняется или есть возможность писать несколько слов последовательно?
ZillahGiovanni
Любопытно, а на чем это можно повторить?
Судя по тексту: какая-то Альтера, но их несколько.
Что за модуль SDRAM, просто микруха?
Лепится проводочками?
Может это делалось на какой то девборде?
А на какой?
В целом наверное интересная статься, если найдутся ответы на все эти вопросы...
JackKatch Автор
У меня вот эта плата https://habr.com/ru/articles/749298/ Cyclon 4. Главная цель статьи, что бы все кто пожелает могли это повторить где угодно. SDRAM видно на картинке, HY57V641620FTP-H.