Мне очень нравится проект Марсоход – это сообщество замечательных людей, увлеченных интересной темой. Оно состоит как из опытных разработчиков на ПЛИС, так и из начинающих, как профессиональных разработчиков, так и любителей. Кто-то шел по тернистому пути освоения программируемой логики сам, кого-то вели преподаватели в ВУЗе. Сам я отношусь ко второй категории, и считаю, что мне повезло: обучали меня схемотехнике и, в частности, ПЛИСам на кафедре автоматики и вычислительной техники (ныне компьютерных систем и программных технологий) Санкт-Петербургского Политеха, на базе которой создан официальный центр обучения Altera в России.
Не знаю, сознательно или нет, на сайте https://marsohod.org не затронута тема синтезируемого процессора Altera Nios II. Пробежавшись по темам на сайте, не обнаружил ни одного проекта на Nios II, зато нашел целую ветку, посвященную процессору Amber ARM. Считаю, что Nios незаслуженно обошли вниманием. Бытует мнение, что процессор Nios утратил свою актуальность, что в реальных проектах его никто не использует, что с появлением Altera SoC FPGA с аппаратным процессором ARM Nios исчезнет за ненадобностью и т.д. Сразу скажу, что слухи о его смерти несколько преувеличены, более того, недавно вышла новая ревизия процессора Nios II Gen 2, которая и будет развиваться, начиная с версии 14.0, однако в данном тексте речь пойдет исключительно о Nios II Gen 1.
В микросхемах Altera SoC FPGA (Cyclone V SoC, Arria V SoC, Arria 10 SoC) Nios II может быть использован как сопроцессор, выполняющий какие-то определенные функции (управление моторами, зарядом аккумуляторов, тачскрином и т.п) для того, чтобы разгрузить основное вычислительное ядро. Синтезируемый процессор Nios II в отличие от любого другого синтезируемого процессора замечателен тем, что он разработан фирмой Altera для ПЛИС фирмы Altera, соответственно позволяет получить наилучшее соотношение производительность/аппаратные затраты при реализации на Alter-овских ПЛИСах. Для связи процессора Nios II c остальными компонентами системы-на-кристалле Altera разработала собственную шину Avalon. Процесс создания системы осуществляется в среде системной интеграции Qsys.
Существуют три варианта конфигурации процессора: economy (/e), standard (/s) и fast (/f). На рисунке ниже приведены их особенности.
Для использования Nios II в конфигурациях standard и fast необходимо приобретение IP-ядра у Altera. В конфигурации economy процессор доступен бесплатно. Для первого знакомства и решения большинства задач нам его вполне достаточно. Я поставил перед собой задачу сделать максимально простую и легковесную систему.
Кроме различных конфигураций процессора, мы также располагаем большими возможностями в отладке:
Зачастую аппаратные затраты на средства отладки сравнимы с аппаратными затратами на сам процессор, а порой их превышают. Что такое "Hello World!" проект для embedded системы? Конечно же, это мигающие светодиоды. Ими мы и займемся, а для мигания светодиодами отладка не очень-то нужна, откажемся от нее в данном проекте и, таким образом, существенно упростим и облегчим систему.
Платы Марсоход2 у меня нет, есть Altera Cyclone III Starter Kit (на базе EP3C25F324C8). На ней также имеются четыре светодиода, которыми мы и будем мигать. Отличие в логической емкости для нас в данном случае несущественно. Чтобы добиться работоспособности на своей плате, все описанные в данном тексте шаги я привел для Altera Cyclone III Starter Kit, но для платы Марсоход2 они аналогичны. Окончательный проект я скомпилировал под EP3C10E144C8, назначив выводы в соответствии с разводкой платы Марсоход2. Разработка велась в Quartus II 13.0, версия 14.0, к сожалению, не поддерживает Cyclone III и Stratix III.
Итак перейдем к разработке, начнем с железа. В Quartus II создадим проект Marsohod_nios. Из Quartus II запускаем среду системной интеграции Qsys, выглядит она вот так:
Сразу видим блок типа Clock Source под названием clk_0, он транслирует внешний тактовый сигнал на блоки проектируемой системы, обеспечивая необходимый коэффициент ветвления (fanout) тактового сигнала. Также он транслирует внешний сигнал сброса. Под названием блока перечислены названия сигналов, под типом блока – их назначение. В графе Export отображается, выводится ли данный сигнал наружу и если да, то как он будет называться. В нашем случае наружу выводится вход тактовой частоты под именем clk и вход сброса под именем reset. В левом фрейме находятся разбитые по категориям IP-блоки, которые можно добавить в систему. Также можно создать свой блок (New Component) на основе HDL описания. Не все IP-блоки доступны для нашей ПЛИС, например, блок Hard Processor System мы можем использовать только в Cyclone V SoC и Arria V SoC. Для проекта нам понадобятся:
- процессор Nios II (Embedded Processors ? Nios II Processor);
- память, из которой будет исполняться программа; для этих целей вполне подойдет встроенная память ПЛИС (Memories and Memory Controllers ? On-Chip ? On-Chip Memory (RAM or ROM);
- блок параллельного ввода/вывода (PIO) для управления светодиодами (Peripherals ? Microcontroller Peripherals ? PIO (Parallel I/O).
Создадим процессор Nios II:
Выберем конфигурацию economy (Nios II/e). Замечу, что кроме логических элементов, сам процессор в любой конфигурации использует блоки внутренней памяти ПЛИС: Memory Usage (e.g Startix IV) - Two M9Ks (or equiv.). Поэтому не следует удивляться, когда компилятор Quartus II сообщит, что памяти использовано больше выделенного нами объема под команды и данные. Из оставшихся вкладок нас интересует только "JTAG Debug Module", так как в остальных вкладках находятся настройки для более продвинутых конфигураций процессора. В этой вкладке выберем No Debugger – обойдемся без отладчика в данном проекте. Внизу появятся ошибки и предупреждение. Ошибки связаны с тем, что процессору пока что еще неоткуда брать команды, а предупреждение напоминает нам о том, что у нас не будет возможности отлаживать наши программы. Нажмем кнопку Finish, увидим новые сообщения об ошибках связности, все они устранятся в процессе создания системы, так что бояться нечего.
Создадим память команд и данных для нашего процессора:
Так как мы пока что еще не знаем наверняка, сколько понадобится памяти для нашей программы, оставим значение по умолчанию: 4096 байт. Процессор 32-разрядный, соответственно ширина слова 32 бита. Поскольку нам нужна память и для команд, и для данных, она должна иметь тип RAM. Галочка "Initialize memory content" означает, что содержимое памяти будет инициализировано файлом .hex.
Создадим блок управления светодиодами – четырехразрядный порт, работающий на выход:
Мы добавили все компоненты в систему, и она выглядит так:
Займемся объединением компонентов, попутно устраняя кучу появившихся ошибок и предупреждений. Сперва подключим тактовый сигнал и сигнал сброса, порождаемые блоком clk_0, ко всем потребителям. Процесс объединения интуитивно понятен: нажимая на пустые кружочки подключаем тот или иной сигнал к тому или иному потребителю. Как я уже говорил, компоненты системы объединяются альтеровской шиной Avalon. Процессор на этой шине является ведущим (master), а память и блок PIO – ведомыми (slave). Процессор имеет отдельный интерфейс для команд (instruction_master), отдельный для данных (data_master). Объединим устройства шиной Avalon, также нажимая на кружочки: память onchip_memory2_0 подключим к обоим интерфейсам процессора, так как она является памятью и команд, и данных, а блок pio_0 к data_master. Кроме двух перечисленных Avalon-интерфейсов, процессор имеет третий интерфейс - custom_instruction_master - он предназначен для выполнения пользовательских аппаратно-ускоренных команд. Такая возможность – интересная и полезная особенность процессора Nios II. Теперь необходимо указать процессору, откуда ему выбирать команды, – вернемся в настройки процессора и смэппируем векторы сброса, исключения и останова (во вкладке JTAG Debug Module) в память on_chip_memory2_0. Теперь осталось устранить конфликты в адресном пространстве (System ? Assign Base Addresses), а также вывести наружу выходы PIO, нажмем в графе Export напротив external_connection, назовем их "leds".
Получившаяся система имеет следующий вид:
Система почти что готова, осталось пробежаться по остальным вкладкам, сохранить систему и запустить процесс генерации. Важная вкладка Clock Settings, в ней мы задаем входную тактовую частоту. На моей плате, в отличие от платы Марсоход2, она составляет 50 МГц. Кроме этой вкладки, тактовую частоту, а также настройки сигнала сброса, можно задать в окне редактирования блока Clock Source. Параметры системы можно проверить во вкладке System Inspector. Сохраним систему под названием "nios_basic.qsys", перейдем во вкладку Generation. В ней мы можем создать модель нашей системы для симуляции в ModelSim, а также тестбенч для нее, но в рамках данного проекта мы этого делать не будем, просто сгенерируем HDL-описание (неважно VHDL или Verilog) и символ .bsf.
Теперь отложим железо и займемся софтом. Запустим прямо из Qsys IDE для программирования под Nios II: Tools ? Nios II Software Build Tools for Eclipse. Создадим workspace. Я его поместил в \Marsohod_nios\software. Создадим проект "Nios II Application and BSP from Template". BSP (Board Support Package) – это набор драйверов и библиотек для нашей аппаратной системы. IDE создает его автоматически, извлекая информацию из файла "nios_basic.sopcinfo", который генерируется Qsys-ом одновременно с HDL-описанием системы. Укажем этот файл в поле "Target hardware information", введем имя проекта "hello_nios", выберем тип проекта "Blank Project". В результате, имеем два проекта в нашем workspace: hello_nios и hello_nios_bsp. Теперь необходимо запустить автоматическую генерацию нашего BSP: Правой кнопкой мыши нажимаем на проект hello_nios_bsp, Nios II ? Generate BSP. Теперь напишем наш код, он занимает всего несколько строк:
#include "system.h"
#include "altera_avalon_pio_regs.h"
int main ()
{
char leds = 0x1;
int i = 0;
while(1)
{
IOWR_ALTERA_AVALON_PIO_DATA(PIO_0_BASE, leds);
for (i=0; i<(ALT_CPU_CPU_FREQ/500); i++); // Delay
// Johnson code counter on leds
leds = ((leds<<1) & 0xE) | (!(leds>>3) & 0x1);
}
return 0;
}
Будем выводить на светодиоды счетчик Джонсона, мне нравится код Джонсона, он имеет большое значение в информатике и схемотехнике и на светодиодах выглядит красиво: четырехразрядное слово заполняется сначала единицами, затем нулями и т.д. Создавая систему, мы не добавили в нее таймеров, но для мигания светодиодами они и не нужны. Используем программную задержку, реализованную циклом for. Величина задержки получена эмпирически и зависит, в первую очередь, от тактовой частоты системы (ALT_CPU_CPU_FREQ). Макроопределение PIO_0_BASE соответствует базовому адресу блока PIO, запись по нему приводит к записи в порт. В файле system.h содержится основная информация о процессоре и периферийных блоках, в том числе определены ALT_CPU_CPU_FREQ и PIO_0_BASE. Файл altera_avalon_pio_regs.h является библиотечным файлом для работы с периферийным блоком параллельного ввода/вывода.
Общий вид программного проекта в Nios II Software Build Tools (SBT):
При попытке сделать Build проекта (одновременно с проектом осуществляется Build BSP) обнаруживаем сообщения об ошибке: полученный .elf-файл не помещается в выделенные нами 4 килобайта памяти. Для начала пойдем по самому простому пути – увеличим объем памяти до 8 килобайт – вернемся в Qsys, откроем настройки блока onchip_memory2_0 и зададим в поле "Total memory size" размер 8192. Переназначим базовые адреса, сгенерируем систему заново. Вернемся в Nios II SBT, снова сгенерируем BSP и запустим build проекта. На этот раз он прошел успешно, в результате имеем:
Info: (hello_nios.elf) 4304 Bytes program size (code + initialized data).
Info: 2408 Bytes free for stack + heap.
Оставшиеся 1480 байт используются системой для служебных целей. Теперь нам необходимо сгенерировать из .elf-файла .hex-файл, который будет использован для инициализации нашей памяти onchip_memory2_0. Эту задачу решает утилита elf2hex, проще всего ее запустить следующим образом: правой кнопкой мыши на проект, Make Targets ? Build... В окне Make Targets выбираем mem_init_generate. В результате, в проекте появляется папка mem_init, в которой, в числе прочих, есть нужный нам файл nios_basic_onchip_memory2_0.hex.
Переходим к созданию проекта в Quartus II. Добавим в проект нашу Nios-систему и подключим к ней выводы:
Выходы leds проинвертированы для удобства управления: на моей плате горящему светодиоду соответствует логический ноль, так как светодиоды объединены по схеме с общим анодом. На плате Марсоход2 светодиоды объединены по схеме с общим катодом и инверсия не нужна. Ниже представлен список файлов входящих в проект:
Файл Marsohod_nios.bdf – модуль верхнего уровня, файл nios_basic.qip – скриптовый IP Variation файл, обеспечивающий подключение всех необходимых файлов исходного описания IP-ядра, в нашем случае – Nios-системы. Файл Marsohod_nios.sdc не является обязательным, но без него TimeQuest Timing Analyzer выдает массу критических предупреждений, которые не влияют на работоспособность системы, однако портят впечатление о проекте. Данный файл содержит лишь информацию о входном тактовом сигнале clk, никаких других временных характеристик задавать для нашего проекта не нужно. Altera предлагает хитрые способы включения сгенерированного .hex-файла в квартусовский проект с использованием .qip файла, однако мне самым простым видится его копирование в папку проекта, при этом его можно добавлять в список файлов проекта, а можно и не добавлять, компилятор его в любом случае подхватит. Назначаем выводы ПЛИС: clk – тактовый сигнал с генератора, reset – кнопка, leds[3..0] – светодиоды. Конфигурируем неиспользуемые выводы "As input tri-stated with weak pull-up". Рекомендую так делать, особенно если работа ведется с малознакомой платой: порой плата может быть сделана так, что подключение неиспользуемых выходов на землю может привести к печальным последствиям.
Скомпилировав проект, получим следующий отчет:
Как видно из отчета, наша Nios-система в минимальной конфигурации заняла всего 708 логических элементов. Памяти задействовано 66 Кбит: 64 Кбит – память программ и 2 Кбит под нужды процессора. Также получаем довольно странное предупреждение:
Warning (113015): Width of data items in "nios_basic_onchip_memory2_0.hex" is greater than the memory width. Wrapping data items to subsequent addresses.
Сначала попробуем сконфигурировать ПЛИС, не обращая внимания на это предупреждение. Все получилось: светодиоды бегают, а кнопка, которую мы назначили на вход сброса, их гасит.
Теперь попробуем разобраться, что же это за предупреждение мы получили. В нем говорится о том, что ширина слов данных в нашем .hex файле больше, чем разрядность памяти, т.е. больше 32 разрядов, и что компилятор эти слова разбивает на кратное количество 32-разрядных. В результате, в память попадают все данные правильно, иначе бы ничего не заработало, однако почему же ширина слов не совпадает? Посмотрим файл nios_basic_onchip_memory2_0.hex в Nios II SBT. Все строки в нем, кроме первой и последней, имеют длину 74 символа (не считая двоеточия в начале). Строка .hex файла имеет следующий формат:
0..5 |
6..7 |
8..71 |
72..73 |
Адрес в памяти |
00 |
Данные |
Контрольная сумма |
При этом мы видим, что адрес в каждой следующей строке, больше, чем в предыдущей на 8, т.е. в каждой строке 8 32-разрядных слов (32 байта). Такое количество слов данных задано по умолчанию в утилите elf2hex. Компилятору Quartus II нужно, чтобы в каждой строчке было одно 32-разрядное слово, и адрес в каждой следующей строчке был больше, чем в предыдущей на 1. Когда мы генерируем .hex-файл, Nios II SBT запускает make-файл mem_init.mk, который в свою очередь запускает необходимую утилиту с нужными аргументами, в нашем случае это elf2hex. Для того, чтобы получить .hex-файл в подходящем квартусу формате, нам необходимо добавить аргумент командной строки "--record=4", означающий, что в одной строке 4 байта данных. По умолчанию он имеет значение 0x20, т.е. 32 байта. Найдем строчку, описывающую дополнительные аргументы командной строки для утилиты elf2hex:
elf2hex_extra_args = $(mem_no_zero_fill_flag)
И допишем туда необходимый аргумент:
elf2hex_extra_args = $(mem_no_zero_fill_flag) --record=4
Теперь удалим имеющийся .hex-файл и заново создадим его. В результате получаем файл такого формата, который требуется. Кладем файл в папку с проектом Marsohod_nios, перекомпилируем его в Quartus II. Теперь эти предупреждения не появляются.
На этом можно было бы и остановиться, однако то, что наша программа для мигания светодиодами занимает 8 килобайт памяти, никуда не годится. Постараемся максимально сократить размер исполнимого когда, для этого обратимся к настройкам BSP: правой кнопкой мыши на hello_nios_bsp, Nios II ? BSP Editor... В правой части окна видим всевозможные настройки BSP. Поскольку наша программа написана на чистом C, поддержка C++ нам не нужна – смело снимаем галочку "enable_c_plus_plus". Светодиоды бегают в бесконечном цикле, программа никогда не выходит, соответственно можно снять галочки "enable_clean_exit" и "enable_exit".
Посмотрим, насколько сократился объем исполнимого кода теперь, после отключения этих трех опций.
Info: (hello_nios.elf) 696 Bytes program size (code + initialized data).
Info: 7496 Bytes free for stack + heap.
Как видим, существенно сократился – более чем в 6 раз. Сумма размера программы и оставшегося свободного пространства нам дает 8192, т.е. больше не используется никаких зарезервированных областей памяти, и 696 байт – это реальный объем используемой памяти. Наибольший вклад в использование памяти вносит опция "enable_exit", так что если в ней нет необходимости, рекомендую ее отключать. Постараемся еще уменьшить размер программы и поместить ее в 512 байт. Опции "enable_lightweight_device_driver_api", "enable_reduced_device_drivers", "enable_small_c_library" в общем случае могут сократить объем исполнимого кода, но не в нашем, так как наш примитивный проект сам по себе не использует библиотеки, размер которых позволяют сократить эти опции. Опция "enable_sopc_sysid_check" в нашем случае неактуальна, так как мы не добавляли в аппаратную систему блок System ID, присваивающий уникальный идентификатор системе. Этот идентификатор может проверяться софтом, для защиты от подделки или каких-либо других целей.
Основные опции, позволяющие сократить объем исполнимого кода, мы просмотрели, и из очевидного остаются только уровни оптимизации компилятора для BSP (bsp_cflags_optimization). По умолчанию стоит -O0, поставим -O2 и посмотрим, что будет.
Info: (hello_nios.elf) 504 Bytes program size (code + initialized data).
Info: 7688 Bytes free for stack + heap.
Теперь наш код помещается в 512 байт. Однако оптимизацию, как известно, следует использовать с большой осторожностью. Например, попытка оптимизации самого проекта ни к чему хорошему не приведет: компилятор уберет за ненадобностью цикл for, который формирует нам задержку, и все светодиоды будут гореть постоянно. В нашем случае оптимизация BSP позволила сократить объем исполнимого кода и не повлияла на работоспособность программы. Теперь можно установить размер памяти равным 512 байт, пересобрать проект и сконфигурировать ПЛИС. В итоге, имеем законченный работоспособный проект, требующий минимум ресурсов.
Данный проект дает лишь общее представление о том, что такое Nios II и как с ним работать: примитивная программа на примитивном железе. Для создания реальных проектов на базе Nios-систем необходимо глубже разбираться в архитектуре процессора и его возможностях: отладка программ и аппаратное ускорение инструкций, обработка прерываний и работа по DMA, работа с внешней памятью и работа под ОС. Всему этому и многому другому можно научиться на курсах в официальном центре обучения Altera.
Исходники проекта Вы можете скачать на сайте официального дистрибьютора Altera - компании “ЭФО”.
Надеюсь, данная статья будет интересна пользователям Марсохода. Успехов в освоении ПЛИСов!
С уважением, Андрей Антонов, компания “ЭФО”
E-mail: Этот адрес электронной почты защищён от спам-ботов. У вас должен быть включен JavaScript для просмотра.
Подробнее...