Разработка простейшей «прошивки» для ПЛИС, установленной в Redd, и отладка на примере теста памяти

Как-то у меня не получается выражать свою мысль коротко. Прошлой осенью возникло желание рассказать поподробнее про освоенную мною архитектуру PSoC, что вылилось в цикл статей про неё. Сейчас я участвую в подготовке аппаратной части для нашего комплекса удалённой отладки Redd, о котором рассказывалось здесь, и хочется выплеснуть накопившийся опыт в текстовом виде. Пока не уверен, но мне кажется, что снова получится не одна статья, а цикл. Во-первых, так я задокументирую выработавшиеся приёмы разработки, которые могут быть кому-то полезны как при работе с комплексом, так и в целом, а во-вторых, концепция ещё новая, не совсем устоявшаяся. Возможно, в процессе обсуждения статей появятся какие-то комментарии, из которых можно будет что-то почерпнуть, чтобы расширить (или даже изменить) её. Поэтому приступаем.

Длинное введение

Я не очень люблю теоретизировать, предпочитая выкладывать сразу какие-то практические вещи. Но в начале первой статьи, без длинного введения никуда. В нём я обосную текущий подход к разработке. И всё будет вертеться вокруг одного: человеко-час — очень дорогой ресурс. И дело не только в сроках, отведённых на проект. Он физически дорог. Если он тратится на разработку конечного продукта, ну что ж поделать, без этого никуда. Но когда он тратится на вспомогательные работы, это, на мой взгляд, плохо. Помню, был у меня спор с одним разработчиком, который говорил, что изготовив прототипы самостоятельно, он сэкономит родной компании деньги. Я же привёл довод, что он на изготовление потратит примерно 3 дня. То есть 24 человеко-часа. Берём его зарплату за эти часы, добавляем социальный налог, который «платит работодатель», а также аренду офиса за эти часы. И с удивлением видим, что заказав платы на стороне, можно получить меньшие затраты. Но это я так, утрирую. В целом, если можно избежать трудозатрат, их надо избегать.

Что такое разработка «прошивок» для комплекса Redd? Это вспомогательная работа. Основной проект будет жить долго и счастливо, он должен быть сделан максимально эффективно, с отличной оптимизацией и т.п. А вот тратить силы и время на вспомогательные вещи, которые уйдут в архив после окончания разработки, расточительно. Именно с оглядкой на этот принцип велась разработка аппаратуры Redd. Все функции, по возможности, реализованы в виде стандартных вещей. Шины SPI, I2C и UART реализованы на штатных микросхемах от FTDI и программируются через штатные драйверы, без каких-либо изысков. Управление релюшками реализовано в формате виртуального COM-порта. Его можно доработать, но по крайней мере, всё сделано для того, чтобы такого желания не возникало. В общем, всё стандартное, по возможности, реализовано стандартным образом. Из проекта в проект разработчики просто должны быстренько писать типовой код для PC, чтобы обращаться к этим шинам. Техника разработки на С++ должна быть очевидна для тех, кто разрабатывает программы для микроконтроллеров (про некоторые технические детали поговорим в другой статье).

Но особняком в комплексе стоит ПЛИС. Она добавлена в систему для случаев, если понадобится реализовывать какие-либо нестандартные протоколы с высоким требованием к быстродействию. Если таковые требуются — для неё «прошивку» делать придётся. Вот про программирование ПЛИС и хочется поговорить особо, как раз всё с той же целью — сократить время разработки вспомогательных вещей.

Чтобы не запутать читателя, сформулирую мысль в рамке:

Не обязательно в каждом проекте вести разработку для ПЛИС. Если для работы с целевым устройством хватает шинных контроллеров, подключённых напрямую к центральному процессору, стоит пользоваться ими.
ПЛИС добавлена в комплекс для реализации нестандартных протоколов.

Структурная схема комплекса

Давайте рассмотрим структурную схему комплекса

В нижней части схемы расположился «вычислитель». Собственно, это стандартный PC с ОС Linux. Разработчики могут писать обычные программы на языках Си, C++, Python и т.п., которые будут исполняться силами вычислителя. В правой верхней части расположены штатные порты типовых шин. Слева располагается коммутатор стандартных устройств (SPI Flash, SD карта и несколько слаботочных твердотельных реле, которые могут, например, имитировать нажатие кнопок). А по центру размещается именно та часть, работу с которой и планируется рассмотреть в данном цикле статей. Её сердцем является ПЛИС класса FPGA, из которой на разъём выходят прямые линии (могут использоваться как дифференциальные пары либо обычные небуферизированные линии), линии GPIO с задаваемым логическим уровнем, а также шина USB 2.0, реализуемая через микросхему ULPI.

Продолжение введения про подход к программированию ПЛИС

При разработке высокопроизводительной управляющей логики для ПЛИС обычно первую скрипку играет его величество конечный автомат. Именно на автоматах удаётся реализовать быстродействующую, но сложную логику. Но с другой стороны, автомат разрабатывается медленнее, чем программа для процессора, а его модификация — тот ещё процесс. Существуют системы, упрощающие разработку и сопровождение автоматов. Одна из них даже разработана нашей компанией, но всё равно процесс проектирования для сколь-либо сложной логики получается не быстрым. Когда разрабатываемая система является конечным продуктом, имеет смысл подготовиться, спроектировать хороший управляющий автомат и потратить время на его реализацию. Но как уже отмечалось, разработка под Redd — это вспомогательная работа. Она призвана облегчать процесс, а не усложнять его. Поэтому было принято решение, что основным будет разработка не автоматных, а процессорных систем.

Но с другой стороны, при разработке аппаратуры самый модный на сегодняшний день вариант, ПЛИС с ядром ARM, был отвергнут. Во-первых, по ценовым соображениям. Макетная плата на базе Cyclone V SoC стоит средне дорого, но, как ни странно, отдельная ПЛИС намного дороже. Скорее всего, цена на макетные платы демпинговая, чтобы заманить разработчиков на использование данных ПЛИС, а платы продаются штучно. На серию придётся брать отдельные микросхемы. Но кроме того, есть ещё и «во-вторых». Во-вторых, когда я проводил опыты с Cyclone V SoC, оказалось, что не так эта процессорная система и производительна, если речь идёт об единичных обращениях к портам. Пакетные — да, там работа идёт быстро. А в случае единичных обращений при тактовой частоте процессорного ядра 925 МГц, можно получить обращения к портам на частоте единицы мегагерц. Всем желающим я предлагаю повызывать штатную функцию постановки данных в FIFO блока UART, которая проверяет переполненность очереди, но вызывать её, когда очередь заведомо пуста, то есть, операциям ничего не мешает. Производительность у меня вышла от миллиона до пятисот тысяч вызовов в секунду (разумеется, работа с памятью при этом шла на нормальной скорости, все кэши были настроены, даже вариант функции, не проверяющий FIFO на переполненность, работал быстрее, просто в обсуждаемой функции имеются обильно перемешанные записи и чтения из портов). Это FIFO! Вообще-то, FIFO придумано для того, чтобы туда бросить данные и забыть! Быстро бросить! А не с производительностью, менее одной мегаоперации в секунду при тактовой частоте процессора 925 МГц…

Виной всему латентность. Между процессорным ядром и аппаратурой располагается от трёх мостов и более. Причём скорость доступа к портам зависит от контекста (несколько записей подряд будут идти быстро, но первое же чтение остановит процесс до полной выгрузки закэшированных данных, слишком много записей подряд — также замедлятся, так как исчерпаются буферы записи). Наконец, осмотр трасс, накопленных в отладочном буфере, показал, что у архитектуры Cortex A один и тот же участок может исполняться за различное число тактов из-за сложной системы кэшей. В сумме, глядя на все эти факторы (цена, просадки быстродействия при работе с аппаратурой, нестабильность скорости доступа к аппаратуре, общая зависимость от контекста), было решено не ставить такую микросхему в комплекс.

Эксперименты с PSoC фирмы Cypress показали, что там ядро Cortex M даёт более предсказуемые и повторяемые результаты, но логическая ёмкость и предельная рабочая частота этих контроллеров не соответствовали ТЗ, поэтому их тоже отбросили.

Было решено установить недорогую типовую ПЛИС Cyclone IV и рекомендовать использование синтезируемого процессорного ядра NIOS II. Ну, а при необходимости — вести разработки с использованием любых других методов (автоматы, жёсткая логика и т.п.).

Отдельно упомяну (и даже выделю этот абзац), что основной процессор комплекса — это x86 (x64). Именно он является центральным процессором системы. Именно на нём исполняется основная логика комплекса. Процессорная система, о которой речь пойдёт ниже, призвана просто обеспечивать логику работы аппаратуры, «прошиваемой» в ПЛИС. Причём эта аппаратура реализуется только в том случае, если разработчикам не хватает штатных модулей, подключённых напрямую к центральному процессору.

Процесс разработки и отладки «прошивок»

Если комплекс Redd работает под управлением ОС Linux, то это не значит, что и разработка должна вестись в этой ОС. Redd — это удалённый исполнитель, а разработку следует вести на своей ЭВМ, какая бы там ОС ни стояла. У кого стоит Linux — тем проще, но кто привык к Windows (когда-то я очень не любил WIN 3.1, но на работе заставили, а где-то ко временам WIN95 OSR2 произошло привыкание, и теперь с этим бороться бесполезно, проще принять), те могут продолжать вести разработку в ней.

Так как у меня с Линуксом дружба не задалась, пошаговых инструкций настройки среды под ним я не дам, а ограничусь общими словами. Кто с этой ОС работает — тому этого будет достаточно, а остальным… Поверьте, проще обратиться к системным администраторам. Я, в итоге, так и сделал. Но тем не менее.

Следует скачать и установить Quartus Prime Programmer and Tools той же версии, что и ваша среда разработки. При несовпадении версий, могут быть сюрпризы. Я потратил целый вечер, чтобы постичь этот факт. Поэтому просто скачивайте средство той же версии, что и среда разработки.

После установки, войдите в каталог, куда установилась программа, подкаталог bin. Вообще, самым главным файлом должен быть jtagconfig. Если запустить его без аргументов (кстати, у меня упорно требовали вводить ./jtagconfig и только так), то будет выдан список программаторов, доступных в системе и подключённых к ним ПЛИС. Там должен быть USB Blaster. И первая проблема, которую подкидывает система, не хватает прав доступа для работы с USB. Как решить её, не прибегая к помощи sudo, описано здесь: radiotech.kz/threads/nastrojka-altera-usb-blaster-v-ubuntu-16-04.1244

Но вот список устройств отображается. Теперь следует написать:

./jtagconfig --enableremote 

после чего запустится сервер, доступный по сети отовсюду.

Всё бы ничего, но системный фаервол не даст никому этот сервер увидеть. А проверка на Гугле показала, что у каждого вида Линуксов (коих много) порты в фаерволе открываются по-своему, причём надо произнести такое количество заклинаний, что я предпочёл обратиться к админам.
Также стоит учитывать, что если jtagd не был прописан в автозапуск, то при открытии удалённого доступа, вам скажут, что невозможно установить пароль. Чтобы этого не произошло, jtagd обязательно должен быть запущен не средствами самого jtagconfig, а до него.

В общем, шаманство на шаманстве. Давайте я просто зафиксирую тезисы:

  • в системе должен быть открыт входящий порт 1309. Какой протокол, я до конца не понял, для надёжности, можно открыть и tcp, и udp;
  • при запуске jtagconfig без аргументов должен отображаться USB Blaster и подключённая к нему ПЛИС, а не сообщение об ошибке;
  • перед открытием удалённой работы обязательно должен быть запущен jtagd с достаточными правами. Если ранее уже запустился jtagd с недостаточными правами, его процесс следует завершить перед новым запуском, иначе новый запуск не состоится;
  • собственно удалённый доступ открывается строкой
    jtagconfig --enableremote 

Есть, конечно, и аналогичный путь, который проходится через GUI интерфейс, но логичнее делать всё пакетно. Поэтому я описал пакетный вариант. Когда все эти тезисы выполнены (а мне их выполнили сисадмины), на своей машине запускаем программатор, видим сообщение об отсутствии оборудования. Нажимаем Hardware Setup:

Переходим на вкладку JTAG Settings и нажимаем Add Server:

Вводим сетевой адрес Redd (у меня это 192.168.1.100) и пароль:

Убеждаемся, что соединение прошло успешно.

Я потратил три майских праздника на достижение этого, а затем всё решили админы.

Переключаемся на вкладку Hardware Settings, раскрываем выпадающий список и выбираем там удалённый программатор:

Всё, теперь им можно пользоваться. Кнопка Start разблокирована.

Первая «прошивка»

Ну что ж. Чтобы статья имела реальную практическую ценность, давайте разберём простейшую «прошивку», сделанную с применением вышеописанных методов. Самое простое, что мне довелось реально реализовать для комплекса, это тест микросхемы SDRAM. Вот на этом примере и потренируемся.

Имеется ряд любительских ядер для поддержки SDRAM, но они все включаются как-то хитро. А учёт всех хитростей — это трудозатраты. Мы же попробуем воспользоваться готовыми решениями, которые можно вставить в вычислительную систему NIOS II, поэтому воспользуемся стандартным ядром SDRAM Controller Core. Само ядро описано в документе Embedded Peripherals IP User Guide, причём достаточно много места в описании посвящено сдвигу тактовых импульсов для SDRAM относительно тактовых импульсов ядра. Приводятся сложные теоретические выкладки и формулы, но что делать особо не сообщается. О том, что делать, можно узнать из документа Using the SDRAM on Altera’s DE0 Board with Verilog Designs. По ходу разбора я применю знания из этого документа.

Я буду вести разработку в бесплатной версии Quartus Prime 17.0. Акцентирую на этом внимание, так как при сборке, мне сообщают, что в будущем, ядро SDRAM Controller будет выкинуто из бесплатной версии. Если в вашей среде разработки это уже произошло, никто не мешает скачать бесплатную 17-ю версию и установить её на виртуальную машину. Основную работу вести там, где вы привыкли, а прошивки для Redd с SDRAM — в 17-й версии. Ну, это если вы пользуетесь бесплатными вариантами. Из платных никто выкидывать это пока не грозился. Но я отвлёкся. Создаём новый проект:

Назовём его SDRAM_DEMO. Имя следует запомнить: я собираюсь вести сверхбыструю разработку, поэтому на верхнем уровне должна будет оказаться сама процессорная система, без каких-либо Verilog-прослоек. А чтобы это произошло, имя процессорной системы должно будет совпадать с именем проекта. Так что запомним его.

Согласившись со значениями по умолчанию на нескольких шагах, доходим до выбора кристалла. Выбираем применённый в комплексе EP4CE10E22C7.

На следующем шаге я чисто по привычке выбираю моделирование в ModelSim-Altera. Сегодня мы не будем ничего моделировать, но всё может пригодиться. Лучше выработать такую привычку и следовать ей:

Проект создан. Сразу же идём в создание процессорной системы (Tools->Platform Designer):

Нам создали систему, содержащую модуль тактирования и сброса:

Но как я уже упоминал, для ядра SDRAM требуется особое тактирование. Поэтому штатный модуль безжалостно выкидываем

И вместо него добавляем блок University Program->System and SDRAM Clock for DE-series boards:

В свойствах выбираем DE0-Nano, так как вдохновение для схемы включения SDRAM черпалось из этой макетной платы:

Начинаем набивать нашу процессорную систему. Разумеется, первым делом в неё следует добавить само процессорное ядро. Пусть это будет Processor And Peripherals->Embedded Processors->NIOS II Processor.

Для него пока не заполняем никаких свойств. Просто нажимаем Finish, хоть у нас и образовался ряд сообщений об ошибках. Пока что отсутствует оборудование, которое позволит эти ошибки устранить.

Теперь добавляем собственно SDRAM. Memory Interfaces and Controllers->SDRAM->SDRAM Controller.

Здесь нам придётся подзадержаться на заполнении свойств. Выбираем ближайшую похожую по организации микросхему из списка и нажимаем Apppy. Её свойства попадают в поля Memory Profile:

Теперь меняем разрядность шины данных на 16, число адресных строк — на 13, а столбцов — на 9.

Времянки я пока не правлю, возможно, в будущем данная рекомендация будет изменена.
Процессорная система подразумевает программу. Программа должна где-то храниться. Мы будем проводить проверку микросхемы SDRAM. На данный момент мы не можем ей доверять. Поэтому для хранения программы, добавим память на основе блочного ОЗУ ПЛИС. Basic Functions->On Chip Memory->On-Chip Memory (RAM or ROM):

Объём… Ну пусть 32 килобайта.

Эта память должна откуда-то загружаться. Чтобы это происходило, установим флажок Enable non-default initialization file и введём какое-нибудь осмысленное имя файла. Скажем, firmware.hex:

Статья выходит уже и так сложной, поэтому не будем её перегружать. Просто будем выводить физический результат теста в виде линий PASS/FAIL (а логический результат мы увидим при моей любимой JTAG отладке). Для этого добавим порт GPIO. Processors and Peripherals->Peripherals->PIO (Parallel IO):

В свойствах выставляем 2 бита, ещё я люблю устанавливать флажок для индивидуального управления битами. Тоже — просто привычка.

У нас получилась вот такая система с кучей ошибок:

Начинаем их устранять. Для начала разведём тактирование и сброс. У блока тактирования и сброса надо выбросить входы наружу. Для этого имеются поля, на которых написано «Дважды щёлкните для экспорта»:

Щёлкаем, но даём более-менее короткие имена.

Ещё надо выбросить наружу тактовый выход SDRAM:

Теперь sys_clk разводим на все тактовые входы, а reset_source — на все линии сброса. Можно аккуратно попадать «мышкой» в точки, соединяющие соответствующие линии, а можно навестись на соответствующий выход, нажать правую кнопку «мыши», а затем — в выпадающем меню перейти в подменю Connections и выбрать связи там.

Дальше связываем шины воедино. Data Master подключаем ко всем шинам всех устройств, а Inctruction Master — почти ко всем. К шине PIO_0 его подключать не требуется. Оттуда инструкции считываться точно не будут.

Теперь можно разрешить конфликты адресов. Для этого выбираем пункт меню System->Assign Base Addresses:

А когда у нас появились адреса, можно назначить и вектора. Для этого идём в свойства процессорного ядра (наводимся на него, нажимаем правую кнопку «Мыши» и выбираем пункт меню Edit) и настраиваем там вектора на Onchip Memory. Просто выбираем этот тип памяти в выпадающих списках, цифры подставятся сами.

Ошибок не осталось. Но осталось два предупреждения. Я забыл заэкспортировать линии SDRAM и PIO.

Как уже мы это делали для блока сброса и тактирования, дважды щёлкаем по требуемым ножкам и даём им как можно более короткие (но понятные) имена:

Всё, больше нет ни ошибок, ни предупреждений. Сохраняем систему. Причём имя должно совпадать с именем проекта, чтобы процессорная система стала элементом верхнего уровня в проекте. Ещё не забыли, как он у нас назывался?

Ну, и нажимаем самую главную кнопку — generate HDL.

Всё, процессорная часть создана. Нажимаем Finish. Нам напоминают, что неплохо бы добавить эту процессорную систему к проекту:

Добавляем:

И там, при помощи кнопки Add, добиваемся такой картинки:

SIP файл ещё не создался. Да и не нужен он нам в рамках этой статьи.

Уффф. Первый шаг сделан. Производим черновую сборку проекта, чтобы система выяснила иерархию проекта и используемые ножки. Ошибки компиляции не страшные. Просто в бесплатной версии среды были созданы ядра, которые работают только пока подключён JTAG адаптер. Но в комплексе Redd он всегда и подключён, так как разведён на общей плате, то есть нам нечего бояться. Так что эти ошибки мы игнорируем.

Теперь вернёмся к описанию ядра SDRAM. Там сказано, что линия CKE не используется и всегда подключена к единице. На самом деле, в рамках комплекса ножки ПЛИС не просто дорогой, а драгоценный ресурс. И глупо было бы разводить ножку, которая всегда в единице (причём на плате DE0-NANO она также не разведена). Была бы Verilog-прослойка, соответствующую цепь можно было бы оборвать там, но я экономлю время (нервный смех, глядя на объём уже получившегося документа, но без экономии вышло бы ещё больше). Поэтому нет прослойки. Как быть? Идём в Assignment Editor. Именно в него, так как в Pin Planner, судя по описаниям, подобной функциональности нет.

Там пока что ещё нет ни одной линии. Хорошо. Создаём новую

Выбираем вот такую пиктограмму:

В системе поиска задаём нажимаем List и в результатах поиска находим нашу CKE:

Добавляем её в правый столбец, нажимаем OK.

Получаем такой список:

В жёлтом поле щёлкаем по раскрывающемуся списку и находим Virtual Pin. Выбираем. Желтизна переехала в другую клетку:

Там выбираем On:

Всё, желтизны больше нет. А цепь у нас теперь отмечена, как виртуальная, а значит, не требующая физической ножки. Поэтому мы можем её не назначать на физический вывод ПЛИС. Закрываем Assignment Editor, открываем Pin Planner. Можно назначить ножки, сверяясь с рисунком, а можно взять список из файла *.qsf, входящего в состав проекта, который я приложу к статье.

Всё, закрываем Pin Planner, выполняем чистовую компиляцию проекта. Аппаратная часть готова, переходим к разработке программной части для полученной процессорной системы. Но статья получилась такая огромная, что это мы сделаем в следующий раз.

 
Источник

Похожие

Меню