![](https://habrastorage.org/getpro/habr/post_images/b16/26b/e30/b1626be30781282607cfeae2e21d121d.jpg)
Введение
Для некоторых людей FPGA SoC является чем-то недоступным пониманию и данная статья должна исправить это недоразумение. Разберем создание программы с нуля, от пустого проекта, до горящего светодиода. Для начала скажу, что проект выполнялся на отладочной плате DE1-SoC, и вы можете с легкостью адаптировать его для других плат с плисами фирмы Аltera, если разберетесь с данным руководством. Начнем!
Создание прошивки FPGA
Для создания прошивки нам, очевидно, потребуется проект Quartus. Но создадим этот проект не стандартным способом (через project wizard), а через утилиту, которая шла в комплекте с платой DE1-SoC
![](https://habrastorage.org/web/3fb/8ff/ca4/3fb8ffca4d4a4794839c48ae9cfb2621.png)
Эта утилита генерирует top-level файл написанный на Verilog с объявлениями выбранных элементов. Нам потребуется CLOCK, HPS (SoC), кнопки, светодиоды. Нажимая Generate получаем проект Quartus. Главное преимущество создания проекта этим способом, это то, что нам не придется назначать пины FPGA в Pin Planner, потому что утилита сделала это за нас, тем самым сэкономила уйму времени.
![](https://habrastorage.org/web/2d7/8a6/cf6/2d78a6cf63a54840b408e873f82d9115.png)
Добавим генерированный файл к проекту.
Следующим шагом будет создание системы в QSYS. На всякий случай кратко поясню суть происходящего. Cyclone V SoC не просто FPGA, внутри ее структуры находится двухъядерный процессор Cortex-A9 с разными модулями, такими как USB порты, Ethernet, SPI, SD/MMC и т.д. По-простому, можно представить это как микроконтроллер внутри FPGA. Создавая систему в QSYS мы связываем HPS (hard processor system) систему с элементами, синтезируемыми в FPGA, используя готовые ядра (IP cores). QSYS позволяет без лишних забот соединить разные ядра через шины Avalon-MM или AMBA AXI, нам не нужно в ручную писать код, QSYS генерирует его за нас. Заходим в QSYS и выбираем во вкладке IP cores нашу HPS систему. В настройках системы нам потребуется только Lightweight H2F Bridge во вкладке FPGA interfaces.
![](https://habrastorage.org/web/108/c8e/587/108c8e587aeb46f79b9b48bd28bcfb59.png)
Во вкладке Peripheral Pins выберем SD/MMC (с карточки мы будем загружать программу) и UART. Позже расскажем об этом подробнее.
![](https://habrastorage.org/web/5df/ee1/d09/5dfee1d09d4f4abb8613a1c139d9958f.png)
Во вкладке HPS clocks оставляем все по умолчанию. Во вкладке SDRAM необходимо заполнить все поля значениями skew и так далее. На сколько я понимаю, они зависят от трассировки линий от микросхемы до SDRAM. У меня не получилось найти какого-либо документа, в котором есть эти данные для DE1-SoC, поэтому взял их из готового проекта для моей платы (это был SOC-Computer в примерах Altera University Program). Я сохранил эти настройки как персет, чтоб больше не заполнять их, вы можете видеть его в правой колонке. Далее в IP cores найдем PIO, это будут наши светодиоды и кнопки.
![](https://habrastorage.org/web/879/840/332/879840332e5e413d81fb4cf832b81423.png)
Для настроек кнопок выберем Input и ширину 4 бита так как 4 кнопки всего
![](https://habrastorage.org/web/2c2/b68/8c8/2c2b688c8fbe441b9db227db34032275.png)
Для светодиодов соответственно Output и ширина 10.
![](https://habrastorage.org/web/f54/aab/7a9/f54aab7a93524a3baa6185804c49a8b4.png)
Соединяем полученную систему как показано на рисунке. Можем изменить имена PIO чтоб было понятнее и красивей. LWH2F bridge является связующим звеном между HPS и FPGA (так же назначает мастером HPS), так как PIO — это элементы выполняющиеся в «ткани» FPGA. Кликнем два раза на надпись «Double click for export» напротив external connection, что-бы вывести из системы выводы PIO светодиодов и кнопок. Позже вы увидите в коде top-level файла генерированной системы эти выводы. Поскольку доступ к PIO, да и ко всем остальным ядрам, в HPS осуществляется по адресам, необходимо назначить их. Это можно сделать автоматически, нажав Assign base addresses.
После этого можно генерировать код системы.
![](https://habrastorage.org/web/b77/11a/d98/b7711ad980cf4759ade249c065ea7160.png)
![](https://habrastorage.org/web/b29/f08/a9f/b29f08a9fb3b47cd945e948c9b9a2912.png)
Указываем путь и желаемый язык кода. Я предпочитаю Verilog. После этого QSYS можно закрыть. В окне Quartus появляется следующее сообщение:
![](https://habrastorage.org/web/592/b66/904/592b66904af144249f385b182e9b3850.png)
Давайте сделаем то, что нас просят.
![](https://habrastorage.org/web/643/d19/0d4/643d190d4b774287be930538ad1a64a3.png)
В окне Files видим наш hps_system.qip. Раскроем его и видим top-level файл системы.
![](https://habrastorage.org/web/bfb/161/995/bfb1619954764bdd84409329fdc5aec5.png)
Все, что мы выбрали в системе QSYS теперь в этом файле. Теперь нужно просто вставить этот модуль в изначальный файл. Это и будет top-level файлом нашей прошивки FPGA.
![](https://habrastorage.org/web/b3d/538/f95/b3d538f95fcc47abac64f920b837a483.png)
В нем мы приписываем выводам HPS системы изначальные I/O пины этого файла. Напоминаю, что ничего не нужно назначать в Pin Planner, все уже сделано при создании проекта утилитой. Но необходимо назначить пины HPS, это делается следующим образом:
![](https://habrastorage.org/web/88f/f7b/b68/88ff7bb68b36435bb99f04feed69b9b6.png)
![](https://habrastorage.org/web/692/b36/16f/692b3616f547467f8ab566389e2c8930.png)
Как только это сделано можно компилировать прошивку. Важно заметить, что если были допущены какие-либо ошибки (я, например, забывал поставить точку в объявлении hps_system возле сlk_clk и hps_io_hps_io_… видно из рисунка) необходимо заново выполнять tcl скрипты. Даже если все написано правильно, и вы запустили tcl скрипты, но после запуска компиляции вы получаете ошибку, стоит попробовать еще раз запустить скрипты, ничего не меняя в коде. Мне помогало, не знаю, чем объяснить данную особенность.
![](https://habrastorage.org/web/be2/cb8/80e/be2cb880eefe4c1f94b0c94b56d27956.png)
И так, с прошивкой закончили! Теперь приступаем к созданию Preloader.
Создание Preloader
Процесс загрузки HPS имеет несколько стадий, попробуем разобраться в них. Стоит заметить, что Cortex-A9 является процессором для приложений, буква «А» в названии означает Application, и в первую очередь предназначен для работы с использованием ОС, например Linux. Поэтому, строго говоря, идея запуска Bare-Metal программ может показаться странной, но в некоторых случаях это необходимо. К тому же, естественно, такая возможность есть, но разработчику надо понимать процесс загрузки хотя бы на базовом уровне.
Сразу после включения выполняется код расположенный прямо на Flash памяти Cortex-A9 называемый BootRom. Вы не можете изменить его или даже посмотреть его содержание. Он служит для первичной инициализации и в следующем этапе передает процесс загрузки в SSBL (Second Stage Boot Loader называемый коротко Preloader). Что необходимо знать для понимания процесса — это то, что код BootRom, в первую очередь, выбирает источник загрузки Preloader, ориентируюсь на внешние физические пины BSEL. В DE1-SoC изначально выбрана конфигурация пинов для загрузки с SD карточки, и изменить это на, например, QSPI или NAND flash, не припаивая дополнительно переключателя и пары резисторов, невозможно. Поэтому в QSYS мы выбирали во вкладке Peripheral Pins пины SD карты. Есть так же вариант загрузки не из внешних источников, а из памяти, созданной в FPGA, с предварительно загруженным туда кодом.
И так после выполнения кода BootRom начинает загружаться Preloader, необходимый для настройки Clock, SDRAM и прочего. После начинает выполняться программа.
Для создания Preloader потребуется SoC EDS, уверен вы уже скачали его с сайта Intel FPGA.
![](https://habrastorage.org/web/4b5/a56/c7c/4b5a56c7cc5942eabdabbbe043ecb29c.png)
Программа работает из командной строки. Для начала создадим BSP написав соответствующую команду «bsp-editor».
![](https://habrastorage.org/web/bc3/a46/3c7/bc3a463c765e4491aca7013f60cdbc49.png)
В этом окне нажмем New HPS BSP.
![](https://habrastorage.org/web/26a/42f/ef0/26a42fef0e7643c5ad62bbca9a56d27f.png)
Необходимо указать путь к {Project directory}/hps_isw_handoff/ и нажать ОК, остальные параметры менять не нужно.
![](https://habrastorage.org/web/6c8/85e/b36/6c885eb36a0c4596a797db2a23d4234d.png)
Выбираем настройки spl.boot в котором указываем источник откуда будет загружаться Preloader. В нашем случае это SD карта, поэтому выберем BOOT_FROM_SDMMC. Будем использовать вариант загрузки Preloader и нашей программы при котором на флешке как минимум 2 раздела – раздел не отформатированный с id=A2, для Preloader, и раздел с системой FAT32, для программы. Есть другой вариант без разделения флешки на разделы, так называемый RAW format, но наш вариант на мой взгляд проще. Отформатировать таким образом флешку можно любой программой (я использовал Mini partition tools 9.2). Или можно использовать уже собранный образ в папке ...\embedded\embeddedsw\socfpga\prebuilt_images\sd_card_linux_boot_image.tar.gz и записать его на флешку через Win32DiskImager.
![](https://habrastorage.org/web/f61/bf9/80b/f61bf980bcf04f908298c6129ed03411.png)
Выбирем FAT_SUPPORT, FAT_BOOT_PARTITION 1, FAT_LOAD_PAYLOAD_NAME .img. Не будем использовать WATCHDOG_ENABLE и EXE_ON_FPGA (мы же не собираемся загружать Preloader с FPGA).
![](https://habrastorage.org/web/044/03c/663/04403c66317a4256a08e8fd7a66843fb.png)
Здесь очень тонкий момент, на котором я попался и целый месяц искал проблему, когда только знакомился с SoC. Serial Support означает, что уже Preloader будет использовать UART модуль для вывода диагностических сообщений прямо во время загрузки. Semihosting означает, что во время вывода этих диагностических сообщений, они будут автоматически выводиться в окне debugger при отладке. Использование в самой программе этой функции очень удобно, она позволяет выводить в окно debugger все, что написано в функции printf без дополнительного написания кода. Если в настройках QSYS в HPS не указать использование UART, но поставить галочку Serial Support в BSP — такой Preloader работать не будет. Если убрать Serial Support, при этом оставив Semihosting, также ничего работать не будет, по крайней мере у меня так было. Так что можете поэкспериментировать или просто оставьте галочки, если хотите, чтоб все точно работало. Нажимаем Generate, затем Exit.
![](https://habrastorage.org/web/cd7/e1a/256/cd7e1a2563fc422a8fe49a5c244d6522.png)
Теперь поменяем рабочую папку SoC EDS командой cd "<указываем полный путь до генерированных файлов BSP (по дефолту это …software/spl-bsp)>" Для сборки Preloader выполним команду make. Ожидаем. В конце процесса в паке получаем нужный файл preloader-mkpimage.bin.
Это собранный файл, в котором находятся одинаковых 4 образа Preloader. Командой mkpimage, выполненной в SoC EDS, можно разобрать этот файл на составляющие (отдельные образы), а затем собрать уже из других конфигураций (с разными образами Preloader). Другие конфигурации могут быть совершенно разными (разные системы в QSYS), то есть получаем отдельные файлы preloader-mkpimage.bin с 4 одинаковыми образами (по 64кб каждый), разобрали их на составляющие и теперь можно собрать новый preloader-mkpimage.bin с разными образами (например, с разными источниками загрузки). Это делается для надежности. Допустим так случилось, что какая-то сила не позволила загрузиться с первой попытки, с первого образа. Тогда начинается загрузка второго образа, а он, например, у нас немного другой, для аварийной ситуации. Если и этот не удалось загрузить, то переходим к третьему и так далее. Но это уже не предмет нашей задачи, скорее лирическое отступление от темы, так что продолжаем!
Вставим флешку с уже подготовленным разделом A2 и запишем на нее наш preloader-mkpimage.bin командой «alt-boot-disk-util -p -a write –d ». SoC EDS должен указывать на путь к папке где лежит файл Preloader. Все почти готово для написания программы! Лишь создадим header файлы с дефайнами QSYS элементов для удобства. Поменяем путь SoC EDS, указав к файлу прошивки, и выполним команду sopc-create-header-files .sopcinfo. На выходе получаем несколько файлов, посмотрев содержание которых станет понятно зачем они.
![](https://habrastorage.org/web/49d/975/274/49d9752742ca49d190add23a4e6c1b3f.png)
Программа
Для написания и отладки программы производитель рекомендует использовать среду DS-5 Eclipse. Рекомендуется запускать Eclipse через SoC EDS командой «eclipse&» (знак "&" в конце команды ставится для того, чтобы окно SoC EDS было активное после открытия Eclipse).
Для тех, кто уже знаком с ARM и когда-либо писал для такой архитектуры программы, сложности заканчиваются. Для незнакомых с ARM сложности продолжаются.
Создадим пустой «C» проект. Там будет окно выбора компилятора, тут каждый волен выбирать и разбираться с тем, что ему больше подходит. Мне, как новичку в ARM больше пришелся по вкусу Arm Compiler 5, главным образом из-за относительно простого синтакиса scatter файлов, используемых для размещение написанной программы в разных частях программы (один только вид синтаксиса linker script у GCC меня пугает). В настройках проекта все выглядит тривиально. Укажу лишь на данную команду.
![](https://habrastorage.org/web/32a/5cb/23f/32a5cb23fb45413e8c1ff37ad3bbaeb5.png)
Сделайте тоже самое. Это конвертирует axf формат в формат bin. Нам потребуется это позже для записи программы на флешку. Напишем уже наконец ее.
![](https://habrastorage.org/web/204/d48/16a/204d4816ad104506a11c58d5e832fda8.png)
Тут даже stdio.h не потребуется. Эта программа просто присваивает содержимое в памяти по адресу KEYS_BASE для адреса LEDS_BASE. Нажав кнопку на плате, светодиод тот час погаснет.
![](https://habrastorage.org/web/a5b/849/518/a5b8495188cf482bb5eae827ad951c30.png)
Содержание scatter файла.
Для отладки в debugger необходимо написать скрипт. Как вы помните процесс загрузки не простой. Скрипт останавливает выполнение Preloader на этапе загрузки приложения из источника и передает эту задачу компьютеру.
![](https://habrastorage.org/web/7e7/6b1/383/7e76b1383d4a4b768972133dfd5909c9.png)
Не забудьте прошить плату перед загрузкой программы и отладкой!
![](https://habrastorage.org/web/6eb/dee/ecd/6ebdeeecded042219bc3a61e72ad0236.png)
В окне отладки нужно создать новую задачу, это делается в Debug control. Я забыл сделать скриншоты на этом этапе, поэтому позаимствовал их. Примените настройки по аналогии с теми, что на картинках ниже.
![](https://habrastorage.org/web/dd7/ec7/02f/dd7ec702fb13407b8eeaa4ff83cac025.jpg)
![](https://habrastorage.org/web/3bd/4fd/d71/3bd4fdd718e34969a4f44b0dcd1833e3.jpg)
![](https://habrastorage.org/web/275/a7a/682/275a7a682b0a41729eda9b240cb85593.jpg)
Теперь после компиляции вы можете отлаживать программу, смотреть содержание регистров и проделывать массу интересных вещей.
![](https://habrastorage.org/web/b10/39d/b69/b1039db69fa944f085f4c562eb8df3c2.png)
После того, как вы убедились, что программа работает корректно, можно создать образ программы для самостоятельной загрузки. Для этого из папки Debug с проектом DS-5 возмем файл <prj_name>.bin и через SoC EDS конвертируем его в .img формат. Это делается командой «mkimage -A arm -O u-boot -T standalone -C none -a 0x00100000 -e 0x00100000 -n „baremetal image“ -d .bin .img.» где "-a" адрес куда загружать, а "-e" точка входа программы.
Точка входа также может задаваться в самом проекте, если используются вектора прерывания, у нас они не используются, поэтому не задаем. Я ориентируюсь на то, какие адреса я задавал в scatter файле, и пишу такие же в этой команде. Перед выполнением не забудьте указать путь к bin файлу в SoC EDS командой cd "<папка с файлом>". Название img файла должно совпадать с тем, что вы указали в BSP editor в FAT_LOAD_PAYLOAD_NAME.
Теперь просто скопируем файл на флешку в fat раздел, как обычный файл. Вставив флешку в DE1-SoC можно видеть выполнение программы.
Заключение
В этой статье многие моменты можно было бы рассмотреть подробнее, поскольку описанный процесс имеет множество этапов, а на каждом есть альтернативные варианты выполнения и свои особенности. Но я думаю, что на все остальные вопросы за меня прекрасно ответит список литературы. Читайте следующую статью Запуск AMP приложений на Cyclone V SoC