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

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


Программа разрабатывается в среде Eclipse. Для её запуска выбираем пункт меню Tools->Nios II Software Build Tools for Eclipse.

Нам надо создать проект и BSP для него. Для этого нажимаем правую кнопку «мыши» в области Project Explorer и выбираем пункт меню New->Nios II Application and BSP from Template.

Базовый шаблон, на основе которого будет создан проект, был сделан при генерации процессорной системы. Поэтому найдём файл, содержащий его.

Также дадим проекту имя (у меня это SDRAMtest) и выберем тип проекта. Я выбрал Hello World Small. Очень хочется выбрать Memory Test, мы же делаем тест памяти, но сейчас мы занимаемся рассмотрением общего пути создания приложений. Поэтому выбираем общий вариант.

Нам создали два проекта. Первый — наш проект, второй — BSP (Board Support Package, грубо говоря, библиотеки для работы с оборудованием).

Первое, что я обычно делаю, правлю настройки BSP. Для этого я выбираю второй из созданных проектов, нажимаю правую кнопку «мыши» и выбираю пункт меню Nios II ->BSP Editor.

В редакторе всё разбито на группы:

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

Поддержка выхода и поддержка зачистки при выходе отключены, что выкидывает из кода ненужные куски огромного размера, но они отключены исходно. Эти галки отключились, так как я выбрал минимальный вариант Hello, World. При выборе других видов кода, лучше эти галки тоже снимать. Не удержусь и включу поддержку С++. Также обязательно надо снять проверку SysID, иначе ничего не будет работать. Дело в том, что делая аппаратную систему, я не добавил соответствующий блок. Остальные настройки не требуют пояснений и не изменялись. Собственно, прочие настройки я пока также не изменял. Чуть позже мы сюда ещё вернёмся, а пока нажимаем кнопку Generate. Нам создаётся новый вариант BSP на базе сделанных настроек. По окончании нажимаем Exit.

Теперь начинается самое интересное. Как собирать проект. Нажимаем привычное Ctrl+B — получаем ошибку. Расшифровки ошибки нет:

В консоли — тоже ничего разумного:

Выбираем Build Project для проекта SDRAMtest, получаем разумное объяснение:

На самом деле, нерабочие шаблоны — фирменный стиль Квартуса. Это ещё одна причина, почему я не стал выбирать Memory Test. Там всё более запущено. Здесь же понятно, в чём дело.

Сбоит вот эта функция файла alt_putstr.c:

/*  * Uses the ALT_DRIVER_WRITE() macro to call directly to driver if available.  * Otherwise, uses newlib provided fputs() routine.  */ int  alt_putstr(const char* str) { #ifdef ALT_SEMIHOSTING     return write(STDOUT_FILENO,str,strlen(str)); #else #ifdef ALT_USE_DIRECT_DRIVERS     ALT_DRIVER_WRITE_EXTERNS(ALT_STDOUT_DEV);     return ALT_DRIVER_WRITE(ALT_STDOUT_DEV, str, strlen(str), 0); #else     return fputs(str, stdout); #endif #endif } 

Починим мы её как-нибудь потом (для этого надо усложнять аппаратную систему). Сегодня она нам просто не нужна. Заменяем её тело на return 0. Проект начинает собираться.

Прекрасно. Мы уже имеем готовый elf файл (пусть он и не выполняет никаких полезных функций) и имеем аппаратуру, в которую можно залить hex файл (помните, как мы сообщили компилятору Quartus, что данные ОЗУ надо загружать из него, настраивая Onchip RAM?). Как нам преобразовать elf в hex? Очень просто. Встаём на рабочий проект, нажимаем правую кнопку «мыши», выбираем пункт меню Make Targets->Build:

В появившемся окне сначала пробуем выбрать первый вариант (mem_init_install):

Ничего при этом не произойдёт. Но из выданного нам сообщения об ошибке, мы узнаем, как следует доработать проект для Квартуса:

../SDRAMtest_bsp//mem_init.mk:230: *** Deprecated Makefile Target: 'mem_init_install'. Use target 'mem_init_generate' and then add mem_init/meminit.qip to your Quartus II Project.  Stop. 

Выберем build уже для цели mem_init_generate, после чего (именно после!!!) добавим указанный qip файл в проект аппаратуры (мы уже научились добавлять файлы, когда добавляли в проект процессорную систему).

Ну, и сам hex файл также можно пощупать руками. Вот он:

Прекрасно. У нас есть всё, чтобы начать наполнение программы реальной функциональностью. Идём в файл hello_world_small.c. Честно говоря, меня немного коробит от работы с чистым Си. Поэтому я его переименую в cpp. А чтобы ничего не испортилось, добавлю в имеющийся текст волшебное заклинание:

То же самое текстом:

extern "C" { #include "sys/alt_stdio.h" }  int main() {    alt_putstr("Hello from Nios II!n");    /* Event loop never exits. */   while (1);    return 0; } 

Важно после замены типа файла выполнить операцию Clean Project, иначе компилятор будет выдавать сообщения об ошибках, что файл *.c не найден на основании каких-то закэшированных сведений.

Тест памяти мы сделаем простейшим. У меня не стоит задачи научить читателя правильно тестировать микросхему ОЗУ. Мы просто поверхностно убедимся, что шины адреса и данных не залипают и не имеют обрывов. То есть, запишем в каждую ячейку все нули и адрес ячейки. Наша задача — сделать рабочий код, а всё остальное (иные константы заполнения и задержки для проверки, что данные регенерируются) — это детали реализации, которые усложняют текст, но не меняют его сути.

Начнём с простого и естественного вопроса: «А где в адресном пространстве у нас располагается SDRAM?» Помнится, мы вызвали функцию автоматического назначения адресов, но даже не глянули, какие адреса были фактически назначены. На самом деле, мы и сейчас не будем заглядывать именно туда. Вся необходимая информация есть в файле:
…SDRAMtest_bspsystem.h.

В итоге, получаем такой код:

extern "C" { #include "sys/alt_stdio.h" #include  #include "../SDRAMtest_bsp/system.h" #include  }  int main() { 	bool bRes = true; 	volatile static uint32_t* const pSDRAM = (uint32_t*)NEW_SDRAM_CONTROLLER_0_BASE; 	static const int sizeInDwords = NEW_SDRAM_CONTROLLER_0_SPAN / sizeof (uint32_t);  	// Зануляем 	for (int i=0;i

Собираем hex файл (напоминаю, что через этот диалог):

После чего компилируем аппаратуру в среде Quartus и входим в программатор, чтобы загрузить получившуюся «прошивку» с получившимся инициализированным программным кодом в кристалл.

Вливаем «прошивку» и получаем сообщение, что система будет жить, только пока она подключена к JTAG (особенности лицензии на ядро SDRAM в бесплатной среде разработки). Собственно, для случая Redd, это не критично. Там этот JTAG всё время имеется.

Результаты теста ОЗУ можно увидеть на разъёме, контакты C21 (успешно) или B21 (ошибка). Подключаемся к ним осциллографом.

Оба выхода — в нуле. Что-то тут не так. Осталось понять, что именно. На самом деле, неработоспособная программа — это просто замечательно, ведь сейчас мы начнём осваивать JTAG-отладку. Наводимся на проект, выбираем Debug As->Nios II Hardware.

Первый раз система не находит аппаратуру. Выглядит это так (обратите внимание на красный крестик в выделенном заголовке вкладки):

Переключаемся на вкладку Target Connection и устанавливаем флажок Ignore Mismatching System ID. Во время последующих опытов мне иногда также приходилось ставить Ignore Mismatched System Timestamp. На всякий случай, выделю его на рисунке не красной, а жёлтой рамкой, чтобы подчеркнуть, что сейчас его устанавливать не обязательно, но если кнопка Debug не активируется, возможно, пришла пора его установить.

Применяем изменения (Apply), затем нажимаем Refresh Connections (эта кнопка спряталась, её надо проскроллировать):

В списке появляется отладчик, можно нажимать Debug

Нас остановили на функции main(). При желании, можно поставить точку останова в конце алгоритма и проверить, дойдёт ли программа до неё:

Запускаем программу:

Точка останова не сработала. Остановим программу и посмотрим, где она исполняется.

Всё плохо. Программа явно вылетела. Расставляя последовательно точки останова, после чего останавливая (красной кнопкой «Стоп») и перезапуская программу (кнопкой с жуком), находим проблемный участок.

Умирает всё при исполнении данной строки для самого первого элемента:

То же самое текстом:

	for (int i=0;i

С точки зрения С++, здесь всё чисто. Но если открыть дизассемблированный код, видно, что там уж больно однородные команды. Похоже, код сам себя затёр. А сделать он это мог, если компоновщик положил его в SDRAM (содержимое которой этот код и затирает).

Останавливаем отладку, закрываем отладочную перспективу.

Идём в редактор BSP, в котором мы были в начале этой статьи, но на вкладку Linker Script. Если бы я поправил эту вкладку в самом начале, мне бы не удалось показать методику входа в JTAG-отладку (и подчеркнуть всю её мощь по сравнению с простым отладочным выводом, ведь факт затёртого кода при JTAG-отладке бросился в глаза сам). Так и есть. Добрый компоновщик кладёт всё в память, размер которой побольше.

Перенаправляем всё в onchip_memory… Сейчас у нас SDRAM — это просто кусок памяти, работоспособность которой нам пока что никто не гарантировал. Нельзя её отдавать под какие-либо автоматизированные действия компилятора.

Пересобираем bsp, пересобираем проект. Надо ли снова делать образ памяти и перегружать ПЛИС? Потом, для полуавтономной работы, будет надо, но пока идёт отладка — нет. Новый вариант программы будет сразу же загружен в ОЗУ при начале нового сеанса отладки. Но чтобы при следующей загрузке ПЛИС не потребовалось бы запускать отладчик, сделать по окончании отладки новый HEX файл и собрать «прошивку» ПЛИС с ним желательно.

Для нового кода точка останова достигнута, результат теста — true:

Осциллограф повеселел: жёлтый луч взлетел в единицу.

Тест пройден. Давайте немного похулиганим и попутно проверим быстродействие системы. Сделаем финал таким:

	// Выдаём результат в GPIO 	if (bRes) 	{ 		while (true) 		{ 			IOWR_ALTERA_AVALON_PIO_DATA (PIO_0_BASE,0x01); 			IOWR_ALTERA_AVALON_PIO_DATA (PIO_0_BASE,0x00); 		} 	} else 	{ 		while (true) 		{ 			IOWR_ALTERA_AVALON_PIO_DATA (PIO_0_BASE,0x02); 			IOWR_ALTERA_AVALON_PIO_DATA (PIO_0_BASE,0x00); 		} 	} 

В результате, имеем на осциллографе такой меандр (всё было подключено через длинный шлейф, поэтому по второй линии появилась емкостная наводка, не обращайте на неё внимание):

Получается частота примерно 8.3 МГц при тактовой частоте 50 МГц без каких-либо тонких настроек процессорного ядра и оптимизации программного кода. То есть, доступ к портам идёт на частоте чуть выше 16 МГц, что соответствует трети системной частоты. Не то, чтобы это было совсем отлично, но всяк лучше, чем 4 МГц при тактовой частоте 925 МГц для Cyclone V SoC… Нет, сам процессор NIOS II работает в разы медленнее, чем ARM ядро пятого Циклона, но процессор, как я уже говорил, у нас в системе и x64 имеется, здесь же нам больше нужно ядро, обеспечивающее логику работы железа. А логика эта обеспечивается именно через работу с портами. Если работа с портами идёт медленно, то всё остальное будет регулярно простаивать, ожидая окончания работы шин. Выявленные характеристики — это предел доступа процессора к портам, но не работы аппаратной части в целом. Однако, как реализовывать работу в целом, рассмотрим в следующей статье.

Заключение

В статье показано, как создать и настроить проект на С++ для простейшей процессорной среды, разработанной для комплекса Redd. Показаны методы доступа к аппаратуре, а также методика JTAG отладки. Скачать полученный при написании статьи аппаратно/программный комплект, можно здесь.

 
Источник

Похожие

Меню