В этой статье я хотел поделиться опытом тестирования своего контроллера динамической памяти на ПЛИС.
В первой части я рассказываю про отладочную плату, которую использовал, и с какими ограничениями при этом столкнулся. Показываю подход к построению схемы тестирования и принципы контроля и обнаружения ошибок. Привожу интересные на мой взгляд фрагменты кода на VHDL с пояснениями к ним. Вторую часть статьи я посвятил оценке реальной производительности контроллера при работе с памятью. В ней я привел результаты измерений быстродействия для потоковой записи и чтения при различных настройках и режимах работы контроллера.
И в самом конце для большей наглядности – примеры взаимодействия контроллера с памятью, снятые в отладчике во время работы. А также описание механизма адресации памяти.
1. Кратко про контроллер SDRAM
Разработанный мною контроллер SDRAM – это аппаратно-независимый IP-блок. Он предназначен для имплементирования внутрь ПЛИС. Код контроллера целиком написан на языке VHDL без использования каких-либо посторонних зависимостей. Тем самым я постарался обеспечить возможность использования данного IP-блока в ПЛИС любых производителей.

Структурно контроллер состоит из ядра (CORE), логики (LOGIC), арбитра потоков (ARBITER), набора буферов FIFO и блока чтения (READ). Более подробно про контроллер можно прочитать здесь: fpga-lab.ru/html/ip/ip_sdram.html.
Код контроллера я отлаживал в «Modelsim». Некоторые из перечисленных выше модулей сами по себе были законченными и протестированными IP-блоками (FIFO и Арбитр). На их моделирование можно было не тратить время. Для других модулей писались тестбенчи. Затем запускалось их моделирование. В конце – общий тестбенч для всего контроллера целиком, плюс отдельный файл упрощенной модели памяти.
Моделирование – это конечно же хорошо, но только одного его будет недостаточно. Полноценно работоспособность контроллера необходимо подтверждать на «реальном железе». Нужно подключить его к микросхеме памяти.
2. Тестовый стенд
В качестве такого «реального железа» для проверки контроллера я использовал отладочную плату «DE10-Lite» производства «Terasic».

Центральный элемент платы – это микросхема ПЛИС «MAX10» (10M50DAF484C7G). Она содержит около 50 000 логических элементов (LUT) и 1600 Kbit блочной памяти (BRAM). Внутрь этой ПЛИС загружается прошивка, содержащая контроллер памяти, блоки для его тестирования и модули вывода результатов.
На отладочной плате установлена микросхема динамической памяти «ISSI IS42S16320F-7TL». Ёмкость памяти 64 мегабайта, ширина шина данных – 2 байта. Управлением этой памятью как раз и занимается тестируемый контроллер.
К сожалению, на данной отладочной плате отсутствует Ethernet. Поэтому вывод результатов тестирования производился на монитор, подключенный по VGA.
3. Тестовая прошивка
Теперь подробно про прошивку ПЛИС, собранную для тестирования контроллера. Как я указал выше, на отладочной плате установлена ПЛИС «MAX10» производства «Intel» (а теперь это снова «Altera»). Поэтому для написания кода и сборки проекта использовалась среда «Quartus Prime», версии 20.1.
Структура прошивки, и ее взаимодействие с компонентами на отладочной плате приведена ниже:

Работа прошивки полностью автоматизирована. Если установлены соответствующие переключатели на плате, то блоки-генераторы «Gen1» и «Gen2» непрерывно и независимо друг от друга формируют тестовые последовательности (потоки). В любой момент можно отключить каждый из генераторов, а затем включить заново. Сами тестовые последовательности состоят из пакетов данных для записи в память и запросов чтения. Они подаются на проверяемый контроллер «SDRAM CTRL». Там они предварительно буферизируются, затем обрабатываются и в результате контроллер формирует транзакции записи или чтения на шине памяти.
Кроме того, пакеты от генераторов тестовых потоков поступают еще и на блоки контроля «WR Check1» и «WR Check2». Эти блоки проверяют правильность сформированных данных. Точно такие же блоки контроля я использую и для проверки прочитанных из памяти данных. Это блоки «RD Check1» и «RD Check2» на схеме. Они непрерывно отслеживают пакеты с выхода контроллера.
Информация о работе контроллера передается в текстовом виде на монитор по интерфейсу VGA. За формирования кадров отвечает видеоподсистема (VIDEO).
Все модули кроме видеоподсистемы работают на частоте шины памяти, которая задается внутренним PLL. Для видеоподсистемы выбрана фиксированная частота – 50 МГц.
3.1 Генерация тестовых последовательностей
Для формирования тестовых последовательностей (потоков) я создал отдельный модуль генерации (файл “packets4smc_gen.vhd”). Он подключается к контроллеру в необходимом мне количестве экземпляров (задано константой GEN_NUM). В данном случае это количество равно двум. Каждый генератор подключен к своему отдельному буферу FIFO.
pack_gen : for i in 0 to GEN_NUM-1 generate
pack_gen_inst : entity work.packets4smc_gen
Generic map (
BASE_ADDR => ARR_BASE_ADDR(i),
DEPTH => DEPTH,
WR_DATA_PWR => WR_DATA_PWR
)
Port map (
. . .
);
end generate;
Два генератора позволяют полноценно проверить взаимодействие всей цепочки модулей контроллера: FIFO–Арбитр–Логика. И при этом задействуют относительно небольшое количество блоков BRAM внутри ПЛИС. Блочную память приходится экономить, так как в MAX10 ее не так уж и много. Кроме того часть этой память в дальнейшем задействуется для отладчика. Да и видеоподсистема кое-что занимает.
Каждый модуль генерации формирует тестовые потоки в отдельном диапазоне адресов памяти. За это отвечают настроечные параметры BASE_ADDR и DEPTH. Адресное пространство равномерно поделено между двумя генераторами:
– первый генератор (Gen1) – 0x0000000…0x0FFFFFF;
– второй генератор (Gen2) – 0x1000000…0x1FFFFFF.
За логику работы алгоритма генерации отвечает конечный автомат. Он начинает свою работу из состояния ожидания. Имеет циклы чтения и записи, а также состояние паузы.

В состоянии ожидания всегда проверяется готовность внутреннего FIFO контроллера памяти к приему данных (сигнал “smc_rdy”). И только при наличии свободного места в FIFO, автомат генерации пакетов может перейти к циклу записи или чтения. Включение самих циклов (сигналами “wr_ena” и “rd_ena”) осуществляется переключателями на отладочной плате.
---- Ожидание готовности FIFO и внешних разрешений
when st_idle =>
if (smc_rdy) then
if (wr_ena) then
state <= st_wr_init;
elsif (rd_ena) then
state <= st_rd_bytes;
end if;
end if;
Цикл формирования пакетов для записи в память состоит из последовательности нескольких действий:
---- WRITE to SDRAM ----
when st_wr_init =>
state <= st_wr_bytes;
when st_wr_bytes =>
state <= st_wr_check;
when st_wr_check =>
state <= st_wr_create;
when st_wr_create =>
state <= st_wr_send;
when st_wr_send =>
if (ok_cnt_word) then
state <= st_wr_end;
end if;
when st_wr_end =>
if (ok_addr) then
state <= st_wr_fin;
elsif (smc_rdy) then
state <= st_wr_addr;
end if;
when st_wr_addr =>
state <= st_wr_bytes;
when st_wr_fin =>
if (not rd_ena) then
state <= st_pause;
elsif (smc_rdy) then
state <= st_rd_bytes;
end if;
Цикл записи начинается с состояния st_wr_init, в котором формируется случайное начальное значение первого байта данных. На его основе затем будет вычисляться последовательность байтов для всего цикла записи.
Далее определяется размер пакета в байтах (в состоянии st_wr_bytes). В общем случае он будет случайным. Но при этом ни один байт пакета не должен выходить за заданный диапазон адресов. Это проверяется в состоянии st_wr_check. В случае выхода за диапазон размер принудительно уменьшается. После вычисляется величина пакета в словах выходной шины данных генератора. Я реализовал генератор таким образом, чтобы выходные пакеты могли быть переданы словами по 1, 2, 4, 8, 16 либо 32 байта за такт. В данном случае я выбрал самый «нагруженный» вариант, и выдаю по 32 байта данных за такт. При этом размер последнего слова в байтах может быть любым (зависит от длины пакета).
После того как определены параметры пакета, в состоянии st_wr_send выполняется его передача. Вместе с байтами данных указывается и адрес. Если были записаны все адреса памяти, то цикл записи завершается (st_wr_fin). Далее следует либо цикл чтения, либо пауза. Иначе в состоянии st_wr_addr определяется адрес для следующего пакета и последовательность записи повторяется.
Далее – цикл чтения. Код для его состояний выглядит следующим образом:
---- READ from SDRAM ----
when st_rd_bytes =>
state <= st_rd_check;
when st_rd_check =>
state <= st_rd_send;
when st_rd_send =>
state <= st_rd_end;
when st_rd_end =>
if (ok_addr) then
state <= st_rd_fin;
elsif (smc_rdy) then
state <= st_rd_addr;
end if;
when st_rd_addr =>
state <= st_rd_bytes;
when st_rd_fin =>
state <= st_pause;
Цикл чтения во многом схож с записью. Таким же образом определяется размер пакета в байтах. Только теперь вместо потока данных на контроллер памяти передается однотактная команда чтения. Она содержит адрес и количество байт, которые необходимо прочитать, начиная с этого адреса. После цикла чтения всегда производится переход в состояние паузы. Это просто техническое состояние, введенное мною для пошаговой отладки.
И еще несколько слов про количество байт записи и чтения. Хотя оно и формируется псевдослучайным образом (на сдвиговом регистре с обратной связью), но при этом всегда будет четным. Это связано с разрядностью шины памяти микросхемы SDRAM, установленной на отладочной плате. Она составляет 16 бит. При тестировании необходимо чтобы во время записи все данные в памяти всегда перезаписывались. Четное количество байт гарантирует это.
if (ce_bytes_num) then
-- определение количества байт
if (use_fixed_bytes_num) then
bytes_num <= bytes_num_fixed;
else
bytes_num <= resize(unsigned(lfsr(10 downto 0)) & 1ux"1", bytes_num'length);
end if;
ok_addr <= '0';
elsif (upd_bytes_num) then
-- проверка на выход пакета за разрешенный диапазон адресов
if (bytes_num/MEM_BUS_WIDTH >= DEPTH-1 - offset_addr) then
bytes_num <= to_unsigned((DEPTH-1 - offset_addr)*MEM_BUS_WIDTH+1, bytes_num'length);
ok_addr <= '1';
end if;
end if;
* размер пакета в байтах считается как «bytes_num+1»
Также я добавил возможность задать несколько фиксированных значений для количества байт (2, 4, 8, 16 … 4096), используя переключатели и кнопки на плате.
3.2 Контроль ошибок
Поиск и обнаружение ошибок – это и есть основная задача тестовой прошивки. Ошибкой я считаю такое событие, когда какой-либо байт данных, прочитанный из микросхемы SDRAM, отличается от записанного в нее. Если возникает хотя бы одна такая ситуация – значит контроллер работает неверно. И фактически, он становится бесполезным. В хорошем контроллере ошибок быть не должно!
Отслеживание ошибок я осуществлял следующим образом. Так как модули генерации («Gen1», «Gen2») создают непрерывные потоки байтов при записи в память, следовательно такие же непрерывные потоки должны быть и при чтении из нее. Вот именно они и проверяются модулями контроля «RD Check1» и «RD Check2».
---- Контроль правильности приема данных
process(clk, n_arst)
begin
. . .
-- проверяем в момент прихода данных и только для своего диапазона адресов
if (ce_data and addr_valid) then
--сравнение с последним значением (прошлый такт или прошлый пакет)
if (ignore) then
test_ok_vect(0) <= '1';
elsif (unsigned(data(0)) = unsigned(test_last_val)+1ux"1") then
test_ok_vect(0) <= '1';
else
test_ok_vect(0) <= '0';
end if;
--сравнение остальных значений
if (data'length > 1) then
for i in 1 to data'length-1 loop
if (ce_lb = '1' and i-1 >= lb_ptr) then
test_ok_vect(i) <= '1';
elsif (unsigned(data(i)) = unsigned(data(i-1))+1ux"1") then
test_ok_vect(i) <= '1';
else
test_ok_vect(i) <= '0';
end if;
end loop;
end if;
end if;
В правильном потоке данных каждый следующий байт должен быть больше предыдущего строго на единицу. То есть верная последовательность будет например: «…253, 254, 255, 0, 1, 2…» и так далее по кругу для всего заданного диапазона адресов. Это и проверяет приведенный выше код.
Есть только одно исключение – это байт для начального адреса диапазона. Ведь каждый раз он генерируется случайно. Поэтому он не сравнивается с предыдущим байтом (который расположен по последнему адресу). Для первого байта формируется сигнал «ignore».
addr_valid <= '1' when (addr >= BASE_ADDR and addr < BASE_ADDR+DEPTH) else '0';
addr_first <= '1' when (addr = BASE_ADDR) else '0';
process(clk, n_arst)
begin
. . .
ignore <= rts and addr_first;
В двух вышеприведенных фрагментах кода присутствуют такие сигналы как «rts», «ce_lb», «lb_ptr». Это сигналы выходной шины контроллера, по которой он выдает прочитанные из памяти данные. Тип для этой шины описан в отдельном пакете, и я использую его не только в контроллере памяти, но в прочих своих IP-модулях.
---- record для выходной шины (out) данных FIFO
type br_bus_o is record
rts : std_logic; -- ready to start (за такт перед данными)
data : array_vector8(31 downto 0); -- выходные данные (массив байт)
ce_data : std_logic; -- 1 - признак данных
ce_lb : std_logic; -- 1 - признак последнего байта
lb_ptr : unsigned(4 downto 0); -- указатель для последнего байта в пакете
pack_bytes : unsigned(15 downto 0); -- количество байт данных в пакете
extra : array_vector8(23 downto 0); -- дополнительная информация к пакету
end record;
Еще один важный момент. А что если я пропускаю ошибки при контроле? Вдруг модуль контроля сам работает неверно? Чтобы исключить такую ситуацию, я добавил возможность внесения ошибок в исходный генерируемый поток по нажатию кнопки на плате. Таким образом, внося ошибки при записи, и видя при этом ошибки чтения, я могу убедиться в корректной работе логики контроля.
3.3 Видеоподсистема
Кратко про видеоподсистему. На отладочной плате отсутствуют интерфейсы для обмена данными с компьютером. Нет ни Ethernet, ни даже UART. Поэтому для вывода информации я использовал VGA-выход. Пришлось формировать и передавать видео поток на монитор.
Генерация видео состоит из нескольких этапов:
1. Создание горизонтальной и вертикальной развертки – 800x600, 30 Гц. Это разрешение было выбрано из-за удобства. Для его получения нужна тактовая частота 50 МГц, а генератор такой частоты как раз установлен на отладочной плате.
2. Формирование строк. Изображения символов хранятся в отдельном блоке, организованном как ROM. Размер поля каждого символа – 8 бит по ширине и 12 бит по высоте. Кодовая таблица – ASCII Win-1251. Исходя из кодов символов строки, производится извлечение изображений из ROM. Затем они накладываются на основной кадр. В данном случае, основное изображение кадра – это просто светло-синий фон.
3. В завершении, каждый пиксель (RGB 8:8:8) видеокадра выдается на ЦАП, установленный на плате. Следует заметить, что ЦАП очень простой, выполнен на резисторах. Реально он использует только 4 бита для каждой цветовой компоненты. Но для вывода текста на монитор этого более чем достаточно.

На дисплей выводится отдельно для каждого потока следующая информация:
скорости записи и чтения (мегабайты в секунду);
количество выполненных циклов записи и чтения. Один цикл – это проход по всем адресам диапазона;
количество обнаруженных ошибок.
Также показывается суммарное быстродействие контроллера. Кроме того я добавил информацию об управлении платой. Без нее я постоянно путался и забывал какие переключатели за что отвечают ?
4. Быстродействие контроллера
После того, как я реализовал логику отслеживания ошибок, стало интересно оценить производительность контроллера при работе с SDRAM. В прошивке я добавил набор простых 32-х битных счетчиков. Они считают количество байт данных, переданных или принятых за одну секунду.
Для начала – быстродействие в зависимости от тактовой частоты шины памяти. Для этого контроллер был сконфигурирован со следующими настройками:
ширина шины данных (DQ) – 2 байта;
CAS Latency = 3;
REF Latency = 10;
REF Period = 64 мс;
-
количество потоков – 2.
Параметры генерации потоков были такими:
запись диапазона адресов, затем чтение этого диапазона;
размер пакетов записи случайный, от 2 до 4096 байт;
количество читаемых байтов за один запрос случайное, от 2 до 4096 байт.

Нужные тактовые частоты получал на внутреннем PLL ПЛИС. Правда, для этого каждый раз приходилось пересобирать прошивку. Значения были следующими:
67 МГц (15 нс),
100 МГц (10 нс),
125 МГц (8 нс),
143 МГц (7 нс).
Микросхема памяти (IS42S16320F-7TL), установленная на плате, не может работать на частоте выше чем 143 МГц. Поэтому быстродействие для 167 МГц (6 нс) я оценивал моделированием в Modelsim.
Как и ожидалось, зависимость скорости работы от частоты практически линейная. Более того, как видно из графика, уже при тактовой частоте 143 МГц быстродействие составляет 278 Мбайт/с. Это примерно 2,2 Гбит/с. При такой скорости работы становится возможным организовать полноценную буферизацию потокового видео FULL HD 30 fps. А с учетом того, что на одну шину память можно подключить сразу две (и даже более) микросхемы, то и 60 fps легко достижимы. И это на таком простом и дешевом типе памяти, которому уже 30 лет!
Далее – влияние размера пакетов на скорости записи и чтения. Я зафиксировал тактовую частоту равной 125 МГц. Изменял только количество байтов данных в пакетах (2, 4, 8, 16 … 4096 байт). Здесь не пришлось пересобирать прошивку каждый раз заново. Я добавил возможность менять количество байт по нажатию кнопки на плате.
Параметры контроллера были следующими:
ширина шины данных (DQ) – 2 байта;
CAS Latency = 2;
REF Latency = 10;
REF Period = 64 мс;
количество потоков – 1.
Сначала для записи в память:

Затем чтение из памяти:

Было очевидно, что чем меньше размер пакета, тем большее влияние будут оказывать накладные расходы при работе с памятью. Такие как выставление адресов на шину, команды закрытия строк и цикл реконфигурации. Но с конкретными цифрами становится возможно наглядно оценить их влияние. Стало ясно, что начиная примерно с 256 байт, увеличение размера пакета не особо сказывается на быстродействии.
5. Пример работы отладчика
В данном пункте я решил привести несколько временных диаграмм работы контроллера при операциях записи и чтения. Они наглядно показывают взаимодействие контроллера с микросхемой SDRAM. Все диаграммы сняты с помощью отладчика «SignalTap» во время работы ПЛИС.
Запись пакета:

В верхней части диаграммы шина «MUX_DATA. Это выход внутреннего мультиплексора контроллера. На ней – записываемый пакет, размером 32 байта. Он должен быть записан в память, начиная с адреса 0x0AB6870.
В нижней части диаграммы – сигналы, идущие на шину памяти. Это не непосредственно сама шина, а сигналы с выхода контроллера. Они формируют последовательность команд. Сначала идет «ACTIVE» (nRAS=0, nCAS=1, nWE=1). Затем через два такта на третий – команда «WRITE» (nRAS=1, nCAS=0, nWE=0). Вместе с командой «WRITE» передается первое слово данных, а далее и все остальные. Запись завершается командой «BURST STOP» (nRAS=1, nCAS=1, nWE=0).
Также на диаграмме можно подробно рассмотреть принцип адресации памяти. В случае микросхемы SDRAM (32Mx16) диапазон адресов – 0x0000000…0x1FFFFFF. Это 25 бит. Ниже – описание адресного пространства, взятое из документации на микросхему:

Для пакета записи, приведенного на диаграмме, адрес начинается со значения 0x0AB6870.
Биты [9..0] передаются вместе с командой «WRITE». Это адрес столбца (Column Addres), он равен 0x070.
Биты [22..10] – вместе с командой «ACTIVE». Это адрес строки (Row Address), он равен 0x0ADA.
Два самых старших бита [24..24] указывают на один из четырех банков (Bank Address). В данном примере – ‘01’. Они передаются вместе с обеими командами «ACTIVE» и «WRITE».
Для операции чтения последовательность команд будет похожей. «ACTIVE» (nRAS=0, nCAS=1, nWE=1), затем «READ» (nRAS=1, nCAS=0, nWE=1), в конце – «BURST STOP» (nRAS=1, nCAS=1, nWE=0). Принцип адресации аналогичен записи.

В нижней части приведенной диаграммы можно увидеть последовательность сигналов «rts», «ce_lb», «lb_ptr». Это сигналы выходной шины контроллера для прочитанных из памяти данных. Про них я подробно писал в п.3.2. Видно, что данные идут пакетами, с каждым пакетом передается адрес, по которому он был прочитан. Здесь это адрес 0x0CCAF00.
И в конце пример как выглядит непрерывный поток записи пакетов произвольной длины:

Вместо заключения
Реализация законченных IP-ядер для ПЛИС требует достаточно большего объема работ. Кроме непосредственно написания исходных кодов модулей, нужно изучать стандарты и документацию, продумывать структуру и архитектуру. Необходимо составлять тестовые последовательности (тестбенчи) и скрипты для их выполнения. Моделировать работу средствами симуляции, а затем анализировать результат. А далее запускать IP-ядро на реальном железе. Подключать отладчик (а может даже и осциллограф), искать баги и сбои, анализировать результаты измерений. И затем вносить исправления в исходный код. Снова моделировать, запускать, отлаживать… Огромная трудоемкость. В итоге оказывается, что написание исходного кода – это максимум процентов пять из затраченного на всю работу времени. Может даже и того меньше.
Но в последнее время некоторые из нейро-энтузиастов утверждают, что всю эту обширную инженерную работу сможет выполнить некая "волшебная" нейросеть. Для чего понадобится написать лишь пару-тройку промптов для неё. Они считают, что в обозримом будущем и вовсе исчезнет надобность в разработчиках и программистах. Ну что же, посмотрим, как ИИ будет работать в отладчике и брать в свои "виртуальные руки" щупы от реального осциллографа. А если нейросеть в состоянии только генерировать код, то ее использование теряет всякий смысл. Ведь написание кода – это лишь малая и далеко не самая сложная часть работы инженера.
Хотя, скорей всего я просто совершенно не умею писать правильные промпты…
Комментарии (19)

checkpoint
12.01.2026 10:00У Вас проект открытый ? Умеет ли контроллер работать с DDR ? Вы не пробовали собирирать свой проект с помощью Yosys (OSS CAD) ?

ClockEnable Автор
12.01.2026 10:00Проект может быть выложу у себя на сайте. но пока не уверен. Если он на текущем месте работы будет использоваться, тогда не смогу выложить
Это только для SDR SDRAM. Может быть чуть позже DDR буду смотретьYosys (OSS CAD)
с этой вещью дела никогда не имел

checkpoint
12.01.2026 10:00OSS CAD это опенсорсный набор тулов для синтеза на базе Yosys. Если есть задача сделать проект независимым от производителя микросхем, то не плохо бы портировать его на Yosys.
Я почему спросил. В микросхемах ПЛИС от Gowin GW1NR (платки серии Tang Nano) есть встроенная SDRAM на 64 Мбит. Хочу задействовать эту SDRAM. Есть проприетарный блок SDRAM контроллера от производителя, но я использую Yosys и проприетарных IP-блоков стараюсь всячески избегать.

ClockEnable Автор
12.01.2026 10:00да, надо мне будет подробнее почитать про Yosys, спасибо.
я кстати тоже не люблю применять проприетарные блоки. В случае смены производителя будет огромная проблема

yamifa_1234
12.01.2026 10:00Интересная статья, было приятно прочитать. Жаль что нет однозначного решения об размещения исходников контроллера памяти.
Есть ряд вопросов и мыслей:Я когда то тоже делал контроллер SDRAM, но как показала практика он работает только с той микросхемой под которую ее делал, несмотря на то что основные параметры между собой схожи. Полагаю у вас так же?
Реализовать текстовый режим на экране наверное сложно было? картинка генерировалась налету или сначала в буфере ПЛИС(хотя откуда там столько места под 800х600)?
Вы упомянули что весь проект работает на 50МГц, или я не правильно понял и на этой частоте только VGA ? Если весь проект на 50МГц то и sdram тоже?
У вас в контроллере количество потоков 2, но SDRAM одна.... зачем два потока если все равно сводить все в один канал? Или у вас заложен механизм независимой работы с двумя микросхемами?
Вы написали что микросхема не может работать выше 143МГц, но вы пробовали поднять частоту и прогнать тесты? были ли ошибки? Я в своей статье делал подобный тест и память справлялась, хотя для нее(моего варианта) рабочая частота была заявлена в 133МГц, я же запускал на 150МГц, но чисто ради эксперимента.

ClockEnable Автор
12.01.2026 10:00Я писал код контроллера полностью аппаратно независимым. То есть внутри контроллера нет никаких привязок к Альтере. PLL и IO-буферы вынесены наружу. Поэтому код контроллера должен собираться под микросхемы любых производителей. Некоторые части этого контроллера, например FIFO, работают в железе на ПЛИС Xilinx и Efinix.
Видео - самое простое. VGA - там вообще только импульсы синхронизации. А символы текста хранятся в блочной памяти, организованной как ROM. Самое трудоемкое для меня было нарисовать эти символы. Сама картинка (синий фон) генерятся налету. И налету накладываются строки символов. Они хранятся в небольшом буфере на блочной памяти. 1 байт - 8 пикселей.
50 МГц - это только видео. Контроллер памяти - любая частота, задаю на PLL. Максимальная у меня - 143 МГц.
Многопоточность - это как раз та самая фишка контроллера. Да шина памяти одна, но доступ к ней можно дать нескольким абонентам. Можно до 16 независимых абонентов подключить к контроллеру. Причем каждый на своей собственной тактовой частоте. И как раз внутренний арбитр контроллера организует их мультиплексирование на шину памяти. И каждый такой абонент может писать в память или читать независимо от других абонентов. И ему не нужно знать состояние контроллера или другого абонента. Нужно только проверять заполненность FIFO, на которое он подключен.
Да, я пробовал чуть выше частоту давать. Если при 143 МГц, а точнее при 7 наносекундах все нормально, то уже примерно при ~6.9-6.8 нс начинаются ошибки. Несколько ошибок на цикл.

yamifa_1234
12.01.2026 10:00я имел ввиду микросхему самой памяти SDRAM, ваш контроллер будет работать только для ISSI IS42S16320F-7TL , верно?
Получается вы регулярно обновляете буфер с актуальной строкой? и затем много кратно(и повторно) вычитываете символы пока не закончится строка из них?

ClockEnable Автор
12.01.2026 10:00для любой микросхемы типа SDR SDRAM. С любой шириной шины данных DQ. Ну по крайней мере в теории)
Не совсем, буфер строк перезаписывается только если я даю команду записать строку символов в буфер строк. При перезаписи извлекается символ из ROM и переносится в буфер строк. А вычитывается буфер строк каждый кадр

yamifa_1234
12.01.2026 10:00Изучил вопрос касательно совместимости контроллера SDRAM для IS42S16320F и MT48LC16M16A2. В инициализации памяти есть стадия "LOAD MODE REGISTER" и она различается между чипами, точнее значения в этой стадии.

ClockEnable Автор
12.01.2026 10:00
Вот из даташита взял описание для MODE REGISTER. Мне казалось, что это вообще из стандарта на SDRAM. Для всех производителей должно быть одинаково. Но может я не прав.
В любом случае всегда надо на даташит опираться для конкретной микросхемы.
yamifa_1234
12.01.2026 10:00полез я тут изучать подробнее состояние "LOAD MODE REGISTER " для этих микросхем, и обнаружил что я ошибся. все выглядит так что механизмы работы идентичные.
Нужна проверка на практике

mark_ablov
12.01.2026 10:00А какой latency получился? У меня на обычной SRAM 16нс для разовой операции чтения (250Мгц). Пока хватает, но посматриваю на варианты по-быстрее.

ClockEnable Автор
12.01.2026 10:00Если про CAS_LATENCY, то это стандартные 2 или 3 такта. В зависимости от микросхемы и частоты.
Если общая latency для контроллера - то это 4 такта задержки при извлечении данных (или команды чтения) из ФИФО, и еще 2 такта на формирование адреса на шину памяти
old_bear
Нисколько не принижая трудоёмкость процесса разработки, хочу заметить что использовать модель памяти куда проще чем с осциллографом или SignalTap-ом возиться. Производители предоставляют достаточно полные модели, которые исправно ругаются на нарушение всевозможных таймингов и прочая. Так что добавив в симуляцию задержки пинов ПЛИС и дорожек на плате получаем практически полное покрытие штатных режимов функционирования.
Искать подозрительные несоответствия ожидаемой и реальной производительности, понятно что проще на реальном железе. Но найдя конкретный режим, где что-то не то происходит, опять же проще в симуляции его воспроизвести и покурить wave-ы чем весьма ограниченный по времени выборки вывод того же SignalTap-а.
ClockEnable Автор
Моделирование - это только первый этап отладки. Но проблема в том, что 1 секунда моделирования занимает несколько минут реального времени. Много не намоделировать. А в железе - прогон можно запускать на часы или даже дни. Подтверждать работоспособность нужно в железе на практике.
Поэтому методика отладки должна быть такая:
Моделирование отдельных модулей.
Моделирование всей сборки (IP-блока)
Прогон в железе.
Без третьего пункта - в серию изделия пускать нельзя.
old_bear
С такой последовательностью я конечно согласен. Просто из вашей фразы
создаётся ощущение что вы отладку в железе основным приоритетом считаете, в то время как железо обычно используется для подтверждения работоспособности и нахождения режимов которые потом в симуляторе отлаживаются.
Понятно, что если косяк в аналоговой части, то без осциллографа уже не обойтись, но обычно это реже случается по сравнению с косяками в цифровой части и в идеале проверяется независимо от RTL.
ClockEnable Автор
Само-собой вначале моделирование. Здесь вы полностью правы. В первом пункте статьи я это кратко упомянул. Просто не стал подробно расписывать, иначе объем статьи был бы слишком большим.
А про осциллограф - это наша железячная реальность :) В серии каких только косяков не встречал. Проблемы с питанием, лишние дорожки на плате. Не те номиналы или даже элементы, непропаи и прочее...
qwe101
...и не всё железо ведёт себя, как написано. Нет, вообще-то верно, но как только сложнее, чем демоплата...