Интерфейс HyperBus был разработан компанией Cypress в 2014 году. В настоящее время уже многие компании выпускают микросхемы использующие HyperBUS. Это и микросхемы памяти и флэш память и микроконтроллеры с поддержной этого интерфейса.
Основное преимущество микросхем памяти с интерфейсом HyperBUS это маленький размер корпуса (24 Balls TFBGA, 5x5-1 Ball Footprint), небольшое количество сигналов для управления и передачи данных. Всё это упрощает разработку миниатюрных устройств использующих HyperRAM память.
Поизучаем HyperRAM.
Основные используемые сигналы это
1) RESET#, сброс микросхемы в исходное состояние, активный низкий уровень сигнала;
2) CK и CK#, дифференциальная пара тактовой частоты;
3) CS#, начинает транзакцию записи или чтения, активный низкий;
4) DQ[7:0], восьмибитная двунаправленная шина данных, данные передаются и по фронту и по спаду, DDR (Double Data Rate);
5) RWDS, двунаправленный сигнал имеющий несколько значений в различных ситуациях.
Это основные сигналы, есть еще вспомогательные, но их рассматривать не будем.
Итого, получается основных сигналов 13. Это действительно не много. Теоретически микросхема может работать и с одним тактовым сигналом CK, тогда нужно только 12 сигналов интерфейса, но похоже что это будут только микросхемы с питанием +3.3V. Микросхемы с питанием +1.8V кажется требуют именно дифференциальной пары. И еще я заметил такую особенность: у микросхем с питанием +3.3V как правило будет небольшая ёмкость, скажем 32 мегабита, врядли больше.
Я занялся изучением интерфейса HyperRAM потому, что в некоторых микросхемах FPGA от компании GOWIN оказывается внутри есть встроенные микросхемы HyperRAM. В микросхеме FPGA GW1NR-LV9QN88PC6/I5, этот чип стоит на нашей плате Марсоход3GW2, встроены даже две микросхемы HyperRAM. В документации GOWIN идет отсылка к документации на микросхему Winbond W955D8MBYA.
Должен сказать, что у нас уже был пример для платы Марсоход3GW2, который использует встроенную в GOWIN чип FPGA память PSRAM. Это был проект фреймбуффера. Так вот эта память PSRAM (Pseudo Static RAM) и есть HyperRAM. В нашем проекте фреймбуффера использовалось ядро контроллера PSRAM от GOWIN. Как точно оно работает нам неизвестно, а исходники зашифрованы, то есть их как бы и нет.
Сейчас я бы хотел разобраться с сигналами интерфейса HyperBUS и написать свой контроллер. Получится ли это?
Ну прежде всего вопрос: а как из модуля Verilog FPGA обращаться к встроенной в чип HyperRAM памяти? Оказывается, что по сути так же, как если бы эти микросхемы памяти были распаяны на печатной плате и подключались бы к FPGA. То есть в модуле верхнего уровня дописываем несколько строк:
module top(
input CLK, KEY0, KEY1,
input [7:0] ADC_D,
input [7:0] FTD,
input [7:0] FTC,
input FTB0,
output FTB1,
output ADC_CLK,
output [7:0] LED,
inout [19:0] IO,
output TMDS_CLK_N,
output TMDS_CLK_P,
output [2:0] TMDS_D_N,
output [2:0] TMDS_D_P,
//HyperRam interface
output [1:0] O_psram_ck, // Magic ports for PSRAM to be inferred
output [1:0] O_psram_ck_n,
inout [1:0] IO_psram_rwds,
inout [15:0] IO_psram_dq,
output [1:0] O_psram_reset_n,
output [1:0] O_psram_cs_n
);
И вот уже у нас определены сигналы, которыми нужно управлять.
Теперь читаем документацию и разбираемся, как работает интерфейс HyperBUS.
На первый взгляд кажется, что интерфейс не очень сложный.
Например, вот запись в HyperRAM:
Нужно выполнить несколько шагов:
- начиная транзакцию установить сигнал CS# в ноль;
- начать подавать тактовую частоту на CK и CK#;
- далее подаются в режиме DDR (Double Data Rate) восьмибитные данные DQ[7:0] всего 6 байт, это команда-адрес (операция CA, Command-Address), которая содержит описание типа операции чтение или запись, память или регистр, а так же адрес операции;
- тем временем, память во время CA выставляет сигнал RWDS в ноль или единицу. Это будет определять через сколько тактов мы должны начать реальную передача данных, это определяет LATENCY;
- после завершения ожидания LATENCY, контроллер памяти выдает данные на DQ[7:0], в режиме DDR, в общем так же, как и передавалась команда CA;
- во время записи контроллер так же может выставлять RWDS сигнал, который будет маскировать запись отдельных байт. Если RWDS=0, то этот байт будет записан.
По поводу LATENCY нужно сказать особо.
Передача может начинаться либо через число LATENCY тактов (если RWDS=0 во время CA), либо через число 2*LATENCY (если RWDS=1 во время CA). По умолчанию после сброса RESET# число LATENCY=6. Надо заметить, что запись в регистры всегда следует без LATENCY вообще. Таким образом, сразу после сброса можно первой же командой записи в регистр управления установить нужные параметры HyperRAM интерфейса. Ну а LATENCY почему так хитро сделана? Я думаю из за того, что эта память динамическая и требует регенерации. Регенерация скрыта от пользователя микросхемы, но происходит сама собой. Поэтому такую память и называют псевдо статической, Pseudo Static RAM, PSRAM. Но иногда при обращении к памяти требуется чуть чуть подождать, чтобы завершился цикл регенерации. Поэтому наше обращение к памяти может оказаться коротким, а может чуть длиннее. Коротких обращений статистически существенно больше, чем длинных.
Интересно, что во время передачи команды-адреса и во время передачи данных и RWDS они являются выровненными по центру против тактовой частоты CK/CK#.
Чтение из HyperRAM памяти происходит практически так же, только контроллер памяти теперь не должен управлять линиями DQ[7:0] и RWDS после передачи CA. Смотрите:
Теперь сама микросхема памяти управляет этими сигналами DQ[7:0] и RWDS, а контроллер должен только слушать и запоминать принятые данные. Собственно сигнал RWDS из памяти определяет валидность данных на шине. Если RWDS=1, то нужно хватать это слово на шине. Интересно, что при чтении из памяти DQ[7:0] и RWDS выровнены по фронту CK/CK#. В документации на Winbond микросхемы это явно не написано, только нарисованно на временных диаграммах, но вот к примеру в документации на HyperRAM микросхемы Infineon это уже явно указано.
На временных диаграммах приведенных выше я выделил красным цветом этот факт, что передача и приём ведутся разными сдвинутыми по фазе тактовыми частотами.
По сути дела мы имеем дело с двумя клоковыми доменами. Да, частота у них одинакова, но фазы разные.
Ну что? Кажется всё понятно и можно писать на Verilog свой контроллер HyperRAM?
Ну честно сказать, когда я брался за эту задачу, то мне она показалась совсем не сложной. А потом я как-то и призадумался. Есть определенные проблемы. Попробую рассказать о них. Проблема не одна, их несколько.
Самая главная проблема, из которой проистекают остальные, - это наличие двух частот в контроллере.
В самом деле, ещё раз посмотрите на временные диаграммы. Как так получается, что выходящие данные должны быть выровнены по центру (center-aligned)? Как такое можно вообще сделать?
А еще лучше начать с другого вопроса: как включать тактовую частоту CK/CK# во время активного CS#=0?
Честно говоря не знаю, будет ли память работать с постоянно включенными CK/CK#? В документации есть такая фраза "The clock is not required to be free-running. The clock may remain idle while CS# is HIGH". Перевод такой: "Тактовая частота не обязательно должна всё время присутствовать, её можно останавливать когда CS# неактивен, находится в высоком уровне сигнала".
То есть я так понимаю, что простейший случай это все таки включить CK/CK# и никогда не выключать (free-running). Но всё таки? А если делать по правилам, то как включать и выключать тактовую частоту? Очевидно, что просто поставить гейт AND на тактовую частоту это плохая идея. Выход логики в FPGA не бывает global-clock. Только выход регистра. Какие еще есть варианты?
Первый вариант, это подавать на контроллер частоту в два раза выше, чем требуется и работать на этой частоте, а выходную частоту получить делителем на триггере, но уже с управляющим гейтом. Вот так:
Такую схему можно описать на Verilog HDL вот так:
reg clk_enable = 1'b0;
reg clk_half = 1'b0;
always @(posedge clk)
clk_half <= clk_enable ? ~clk_half : 1'b0;
И даже можно просимулировать в icarus verilog и посмотреть как происходит деление тактовой частоты и её включение и выключение сигналом clk_enable:
И тут вроде бы даже всё хорошо, примерно то, что нам нужно для контроллера HypeкRAM. Тактовая частота управляется по нашему желанию, то есть сигнал CK/CK# считаем у нас уже есть, и даже clk_enable это уже почти желаемый нами CS#.
Однако, обратите внимание, что в этой концепции мне не чем синхронизировать передачу данных команды-адреса CA. Точнее сказать так: чтобы выполнить условие center-aligned data по отношению к тактовой частоте clk_half, я должен отправлять данные по спаду тактовой частоты clk. А это уже сильно нехорошо. Вся схема в идеале должна работать либо только по фронту либо только по спаду. Иначе компилятору будет трудно выполнить временной анализ и будет трудно правильно разместить логику в FPGA.
Есть второй вариант управления тактовой частотой.
В этом варианте мне не нужна входная тактовая частота в 2 раза выше, чем рабочая частота на HyperRAM.
Я могу использовать стандартный выходной компонент GOWIN ODDR вот так:
ODDR oddr_(
.CLK(clk),
.D0(1'b0),
.D1(clk_enable),
.TX(),
.Q0(clk_out),
.Q1()
);
Компонент ODDR передает два входных бита D0 и D1 по очереди по фронту и по спаду входной тактовой частоты. Если D0 всегда равно нулю, то при clk_enable=0 на выходе так же всё время будет ноль. А если поднять clk_enable в единицу, то на выходе уже получится меандр. Таким образом, у меня может быть управляемая тактовая частота. Правда в этой идее плохо то, что такой элемент ODDR вносит задержку, а в случае с GOWIN вносит существенную задержку в 3 такта. Еще этого не хватало.
В документации на Gowin микросхемы https://cdn.gowinsemi.com.cn/UG289E.pdf дается такая схема и фременные диаграммы:
Зачем они умудрились в ODDR ставить первые 4 триггера на D0 и D1 остается загадкой.
Тем не менее, здесь опять остается вопрос, ну буду я управлять выходной частотой CK/CK# сигналом clk_enable через ODDR, а как же передавать данные center-aligned? Опять в этой концепции с этим возникает вопрос. Можно на модуль контроллера из PLL подавать две частоты сдвинутые по фазе на 90 (или 270?) градусов и использовать первую частоту для выдачи clk для памяти, а на второй сдвинутой частоте передавать данные. Тут если подумать опять возникает проблема. Какая часть схемы работает на первой частоте, а какая на второй? Опять возникают два клоковых домена. Нельзя просто создать к примеру clk_enable пользуясь clk1 и ею управлять частотой clk2. B этом HyperRAM контроллере повсюду встречаются такие подводные камни.
Дополнительный пример. Во временных диаграммах приведённых в документации начало счёта Latency указано от спада тактовой частоты CK. То есть казалось бы делая логику по простому я должен установить счетчик latency по negedge частоты, которая подается на память. Но дело в том, что данные то передаются на другой частоте, которая сдвинута по фазе. В этом клоковом домене мы не сможем просто использовать значение счетчика latency из того клока, который подается на память. Ведь тогда нарушится весь временной анализ.
В общем, здесь есть над чем подумать и решение видимо не очень тривиальное должно быть.
Есть еще один неприятный момент, который связан уже непосредственно с FPGA Gowin.
Как я показал ранее передача данных и приём на шине HyperBUS ведутся на разных частотах, сдвинутых по фазе на 90 градусов. Как оказалось, я не смогу поставить на передачу компонент ODDR и на приём компонент IDDR и подать на них разные сигналы тактовой частоты. Компилятор Gowin выдаёт ошибку на такое дело. Это очень жаль. В FPGA Altera это бы работало и думаю показывало бы хорошие результаты. А здесь такое не получится. Вы можете найти на github альтернативный проект для платы TangNano9K: https://github.com/zf3/psram-tang-nano-9k.git Здесь так же реализован HyperRAM контроллер, но дело в том, что автор здесь как раз использует две тактовые частоты сдвинутые по фазе, но на ODDR и IDDR подает один и тот же сигнал тактовой частоты, что я считаю неправильным и не соответствующим тех. документации. Думаю автор наверное и хотел подать разные тактовые частоты, сдвинутые по фазе, да не смог из-за особенностей чипа Gowin или из-за особенностей компилятора.
После довольно долгих размышлений я решил, что приём данных я буду делать через имеющиеся готовые компоненты GOWIN IDDR, а передачу буду делать без ODDR, но на свой контроллер я буду подавать частоту в два раза выше, чем требуется, а потом буду делить её.
Сразу скажу, что я считаю, что я не решил всех проблем с разными клоковыми доменами, но попытался хотя бы свести их к минимуму.
Мой проект к плате Марсоход3GW2 находится на github https://github.com/marsohod4you/Marsohod3GW/tree/Marsohod3GW2_GW1NR-LV9QN88PC6I5/_hyperram
Сам модуль контроллера HyperRAM находится в файле hrc.v
Я постарался сделать его как можно проще, пусть даже его возможности искуственно урезаны. Например, с этим контроллером вы не сможете сами писать или читать регистры микросхемы HyperRAM. Можно читать или писать только память.
После подачи сигнала сброса на контроллер он выжидает время power-up. Оно должно быть не менее 150usec. Затем контроллер сам пишет в управляющий регистр и записывает туда желаемое значение LATENCY (определяется параметром Verilog модуля) и включает динамический режим LATENCY.
После этого контроллер отпускает сигнал busy и можно выполнять транзакции чтения или записи памяти. Тут я пока так же сделал упрощение и сделал только блочные передачи по 4 слова, 8 байт. Адреса должны быть выровнены по 4м словам.
Рассмотреть подробнее происходящие процессы можно с помощью симуляции. Я использую самый простой Icarus Verilog.
$ iverilog -o tbtest tb.v hrc.v iddrx.v s27kl0641.v
$ vvp tbtest
VCD info: dumpfile out.vcd opened for output.
Try write 0
OK at Addr 00000004 0000aa55
OK at Addr 00000005 0000ab56
OK at Addr 00000006 0000ac57
OK at Addr 00000007 0000ad58
Try write 1
OK at Addr 00000104 0000ae59
OK at Addr 00000105 0000af5a
OK at Addr 00000106 0000b05b
OK at Addr 00000107 0000b15c
Try write 2
OK at Addr 00000204 0000b25d
OK at Addr 00000205 0000b35e
OK at Addr 00000206 0000b45f
OK at Addr 00000207 0000b560
Try write 3
OK at Addr 00000304 0000b661
OK at Addr 00000305 0000b762
OK at Addr 00000306 0000b863
OK at Addr 00000307 0000b964
Try read 0
OK read Addr 00000004 aa55
OK read Addr 00000005 ab56
OK read Addr 00000006 ac57
OK read Addr 00000007 ad58
Try read 1
OK read Addr 00000104 ae59
OK read Addr 00000105 af5a
OK read Addr 00000106 b05b
OK read Addr 00000107 b15c
Try read 2
OK read Addr 00000204 b25d
OK read Addr 00000205 b35e
OK read Addr 00000206 b45f
OK read Addr 00000207 b560
Try read 3
OK read Addr 00000304 b661
OK read Addr 00000305 b762
OK read Addr 00000306 b863
OK read Addr 00000307 b964
Я нашел в интернете Verilog модель HyperRAM памяти Infinion s27kl0641 и использую эту модель для симуляции.
Тестбенч пишет в память 4 раза по 4 слова. Затем читает 4 раза по 4 слова. Сравнивает прочитанное с ожидаемым. Работает.
Более подробно все сигналы можно рассмотреть в GtkWave диаграммах:
Здесь видны первая команда записи в конфигурационный регистр сразу после сброса контроллера и первая запись блока в память. Конечно, на этой картинке трудно рассмотреть всё подробно. Но вы можете сами запустить симулятор и после этого в GtkWave рассматривать любые сигналы в любом масштабе.
Если же говорить о моём тестовом проекте для платы Марсоход3GW2 в целом, то он у меня несколько шире, хотя и остаётся довольно примитивным.
Весь проект для платы Марсоход3GW2 делает следующее:
- при включении инициализирует HyperRAM чип;
- ожидает приёма команды от компьютара через последовательный порт, baudrate=12Мбит;
- каждая команда это 12 байт, первые 4 байта адрес, остальные 8 байт это записавыемые данные. Старший бит первого байта определяет это команда записи или чтения.
- если происходит запись, то просто записывает 8 байт по указанному адресу, если чтение, то считывает 8 байт и отправляет их на компьютер так же через последовательный порт.
Еще я написал две тестовых программы на питоне. Одна передает через последовательный порт в плату Марсоход3GW2 файл указанный в командной строке, эти данные записываются в HyperRAM чипа FPGA. А вторая программа считывает из платы из памяти HyperRAM данные и записывает их в файл.
Теперь можно реально писать и читать данные в HypeRAM память. Вот тест проверка:
$ python write2hyperram.py COM11 testfile
File length: 1984
COM11
$ python read2hyperram.py COM11 testfile-read 1984
COM11
$ diff testfile testfile-read
Записанный и потом прочитанный файлы должны быть одинаковы. Это будет значить что все транзакции с памятью и запись и чтение выполнены корректно.Таким образом, можно удостоверится в работоспособности контроллера.
Путем экспериментов я выяснил, что мой проект работает надежно и устойчиво на частоте 140МГц, на HyperRAM 70МГц. При частоте проекта 150МГц (на HyperRAM 75МГц) проект может подвисать.
Возможно контроллер может быть улучшен. Наверное можно попытаться добиться более высоких частот. И, как я выше сказал, было бы хорошо и на приём использовать компонент IDDR и на передачу использовать компонент ODDR. В FPGA Gowin мне это не удалось из за того, что тактовые частоты на них нужны разные. В FPGA Altera это должно сработать и должно улучшить проект.
Можно просимулировать мой проект в целом. Для этого нужно выполнить такие команды:
$ iverilog -o fulltb tb_via_serial.v serial.v ctrl.v s27kl0641.v hrc.v iddrx.v
$ vvp fulltb
$ gtkwave out.vcd
В программе GtkWave можно увидеть процесс инициализации памяти записью в конфигурационный регистр (выделенно желтым), передачу в последовательный порт команды записи 12 байт и саму запись в память (выделенно синим), команда чтения через последовательный порт (фиолетовым), считанные данные 8 байт возвращаются через последовательный порт (красным).
Приведённый скриншот конечно малоинформативен. Но если запустите GtkWave сами, то сможете детально рассмотреть любой сигнал проекта.
В целом, я считаю, что разобрался с работой памяти HyperRAM, хотя некоторые моменты, такие как сдвинутые по фазе тактовые частоты на передачу и приём, всё же вызывают вопросы и требуют особого внимания.
Подробнее...