- BrainTools - https://www.braintools.ru -
На страницах всевозможных статей было написано, что управление микросхемой памяти [1] SDRAM это очень сложно. Отчасти это верно, есть масса тонкостей. Процесс освоения новичкам осложнён отсутствием примеров на русском языке. Вашему вниманию [2] предлагается небольшой пример, как можно подключить оперативную память к ПЛИС. Эти заметки для новичков, таких как и я. А следовательно не торопитесь, проверяйте всё что будете использовать. Особенно реализацию платы, если она у вас самодельная. Опытным пользователям можно не читать (разве что из спортивного интереса [3]). Не буду увлекаться теорией, кому нужно читайте литературу или хотя бы спросите искусственный интеллект [4] (он неплохо может расписать что к чему).
В общих чертах, необходимо провести инициализацию микросхемы (см. документацию на микросхему) и в дальнейшем подавать команды (это сигналы 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. Не забываем [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
Нажмите здесь для печати.