Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Migration VDSP++->bfin-elf
Форум разработчиков электроники ELECTRONIX.ru > Цифровая обработка сигналов - ЦОС (DSP) > Сигнальные процессоры и их программирование - DSP
dxp
Всем привет!

* * *

Преамбула.

Некоторое время назад у меня случился переезд с Windows на Linux (смена основной рабочей платформы). И если с софтом для обычной работы/досуга никаких (кроме привыкания) серьёзных проблем нет, то с САПРами всё далеко не так шоколадно. Основная трудность в том, что у некоторых из них нет Linux версий. Отчасти обходным путём может быть использование виртуальной машины, но это не лучший вариант — от Windows тут избавиться не удаётся, а использование становится ещё менее удобным. Поэтому естественно желание применять нативный софт.

Для меня основной проблемой стало то, что на Windows использовался ADI VisualDSP++/Blackfin. На виртуалке это всё поднялось (в том числе и коннект с железкой через ICE-100B), т.ч. как запасной вариант годилось, но хотелось, повторяю, нативного пакета. Особого выбора тут нет — это ADI GNU Toolchain (GCC Blackfin в разных комплектациях). Особенность данного тулчейна в том, что он не предназначен для обычной работы в bare-metal окружении как тот же VDSP++, он предназначен для запуска uClinux на платформе Blackfin.

Исходя из этого, содержание ADI GNU Toolchain'а составляет вариант для сборки приложений под uClinux — называется bfin-uclinux, набор библиотек и опций для всего этого и вариант для сборки С-приложения uboot — называется он bfin-elf. Поскольку uClinux меня не интересует, а нужен инструмент для работы в bare-metal окружении на С++, то выбор пал на bfin-elf.

При освоении данного тулчейна пришлось столкнуться с рядом трудностей, которые были более-менее успешно преодолены, а рабочие проекты портированы с VDSP++ на bfin-elf. Одной из таких трудностей стало то, что не всё необходимое для работы в bare-metal режиме на С++ содержалось в самом тулчейне (bfin-elf), а кое-что оказалось неприемлемого качества. Поэтому в результате этой работы было скомплектовано несколько библиотек (подробнее тут).


* * *

Амбула.

Вся тема возникла из-за того, что штатно (т. е. «из коробки») упомянутый тулчейн не позволяет полноценно работать. Он не включает в себя средства для регистрации обработчиков исключений (прерываний), не поддерживает вызов конструкторов глобальных объектов и имеет некоторые другие недостатки. Собственно, это обусловлено тем, что пакет сырой, «недоделанный» - по сути он предназначался для для того, чтобы собрать uboot, никто на нём разрабатывать bare-metal с использованием C++ приложения не собирался.

Вторым отягчающим обстоятельством является то, что пакет основан на версии gcc 4.3.5, которая является уже довольно старой, и не видно инициатив к развитию (попросту говоря, на данный инструмент «забили»). sad.gif

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

* * *

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

* * *

Вызов конструкторов глобальных объектов в нынешних GCC пакетах осуществляется с помощью функции __libc_init_array из библиотеки тулчейна. Такая функция там есть. Вот только её вызова в basiccrt.S и других стартапах из состава bfin-elf нет.

И второй момент: __libc_init_array вызывает пользовательскую функцию _init(), которая по замыслу предназначена для выполнения низкоуровневой инициализации. Но вот далеко не всегда такая функция нужна, а пользователь вынужден её определять. По-хорошему, такая функция должна была бы лежать в стандартной библиотеке рядом с __libc_init_array, объявленная с атрибутом weak, но этого там не сделано. Поэтому такая weak функция помещена в упомянутую библиотеку.

Кроме того, добавлен файл crt.S, который есть минимально необходимый код стартапа:
  • настройка регистров процессора;
  • перевод в режим супервизора на нижнем приоритете;
  • обработчики исключений по умолчанию;
  • обнуление секции .bss;
  • и, конечно, вызов конструкторов глобальных объектов (__libc_init_array).


Обнуление секции .bss потребовалось потому, что тулчейн генерирует эту секцию незагружаемой (флаг ALLOCATE есть, флага LOAD нету), поэтому при загрузке программы в процессор с помощью gdb данная секция оказывается необнулённой со всеми вытекающими.

* * *

В тулчейн bfin-elf из VDSP++ портировано некоторое количество интринсиков (builtins.h), но, к сожалению, далеко не все, и реализация некоторых не вполне корректна. В частности, мне не хватило доступа к регистру CYCLES (такой интринсик был добавлен), а интринсики cli/sei содержат баг, который приводит к малопонятному сообщению об ошибке (типа, недопустимый символ с индексом nnnn в потоке).

Баг уже известный, суть его сводится к тому, что в реализации, которая сделана с помощью inline assembler, используется паттерн 'r', который соответствует R0-R7 и P0-P5 регистрам процессора, в то время, как инструкции cli/sei допускают только регистры данных, т. е. R0-R7, поэтому правильный паттерн для этих функций не 'r', а 'd'. Оба интринсика так же добавлены в описываемую библиотеку в виде встраиваемых функций __cli/__sei.

* * *

Тулчейн bfin-elf v4.3.5 содержит очень гадкий баг: при некоторых обстоятельствах в обработчике прерываний портится один из регистров (я налетел на порчу регистра I0): регистр используется в теле обработчика, но не сохраняется/восстанавливается в прологе/эпилоге. Это приводит к очень неприятным, непредсказуемым и трудноуловимым глюкам при работе программы. На эту тему составлен багрепорт, но полагаю, что исправляться это не будет.

В качестве обходного пути: обработчики прерываний делать простыми и, главное, избегать в них циклов. Тогда проблем не возникает.

* * *

Из состава VDSP++ подтянут файл с низкоуровневыми функциями настройки pll/clock (pll_set_system_vco/pll_set_system_clocks).

* * *

Утилита для генерации загрузочного образа bfin-elf-ldr имеет следующие недостатки:

  • она тупо грузит все секции, какие есть — например, если в SDRAM находится здоровенная секция, которую не нужно инициализировать, секция всё равно будет загружена, что порождает большой размер файла загрузочного образа и увеличивает время загрузки;
  • загрузочный образ оказывается непригодным для использования в некоторых случаях — конкретно, для загрузки в режиме SPI Slave (описано тут: https://ez.analog.com/thread/71256?start=0&tstart=0), т. к. загрузчик процессора имеет фичу, которая ориентирована на мультизагрузочную конфигурацию образа (загрузчик из состава VDSP++ всегда делает мультизагрузочную конфигурацию, поэтому проблем не создаёт). Фича нигде не описана.


Для обхода этой проблемы написан загрузчик, свободный от этих недостатков: помещает в LDR файл только секции с флагом LOAD и генериует LDR файл в виде мультизагрузочного образа. Загрузчик написан на языке python. Взять его можно тут. Использовать можно как из командной строки, так и из сборочного скрипта. Требует утилит bfin-elf-objdump и bfin-elf-objcopy, обе есть в составе bfin-elf.

* * *

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



P.S. При прохождении этого нелёгкого пути очень большую помощь мне оказал AHTOXA. Как технического плана при погружении в мир GCC, так и морально-психологического, выслушивая нытьё на тему «что ж оно всё такое кривое-горбатое?!..» sm.gif Выражаю ему и Сергею Борщу признательность и благодарность.
Fat Robot
Колоссальная работа.

Скажите, есть ли у вас какие-то оценки результата сборки при переходе на gcc?
Как изменился footprint?
Есть ли заметные изменения в производительности?
dxp
QUOTE (Fat Robot @ Mar 2 2016, 14:53) *
Скажите, есть ли у вас какие-то оценки результата сборки при переходе на gcc?
Как изменился footprint?

Да, конечно, это в первую очередь оценивалось, т.к. у меня ориентация на то, что программа должна помещаться во внутренней памяти программ. Общая оценка такая: front-end компилятора очень хорош, код общего назначения оптимизирует отлично - всё что можно "просчитать" на этапе сборки, он делает, в итоге код (тестовый) из нескольких вызовов (правда простых) функций с какой-то тупой логикой (ну, просто для теста там всякая бессмысленная арифметика делалась) в сгенерённом коде выливался всего в пару инструкций. Это поначалу вводило в подозрение - что это, де, за фигня, поди ошибка, но тщательный разбор показывал, что действительно это нагромождение кода по факту выливается в декремент на два, что в итоге компилятор и выдал.

Полагаю, это благодаря тому, что gcc front-end является весьма высококачественным и с точки зрения поддержки средств языка (С++), и с точки зрения оптимизации логики.

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

Конкретно на чём bfin-elf проигрывает vdsp++:
  • использование аппаратных циклов;
  • оптимизация конвейера;

Если vdsp++ втыкает lsetup(...) LCn = ... каждом случае, то bfin-elf это делает куда менее охотно даже при опции компилятора -funsafe-loop-optimizations. Какая логика им рулит, для меня пока осталось неясным. Конечно, аппаратный цикл чаще всего лучше, чем цикл с явным переходом по инструкции ветвления. Лучше и по производительности, и по размеру кода.

С конвейером vdsp++ производит очень жёсткие оптимизации - переставляет инструкции так, что там вручную понять, что к чему относится, всегда было непростой задачей, что очень затрудняло отладку по коду. Меня по началу это выводило, а потом когда я писал свой кусок на асме, то налетел на предупреждения о простаивании конвейера, дескать, из-за того, что вот загрузил адрес в P-регистр, и в следующей инструкции уже пытаюсь обращаться по нему, а адрес-то по конвейеру ещё не доехал до аппаратуры, которая осуществляет адресацию, поэтому конвейер stall 3 такта... Думаю, вот чего он будет простаивать, я пока что-то другое могу поделать... И тут замечаю, что я и сам начинаю вот точно так же переставлять инструкции в потоке. В общем, зауважал компилятор.

bfin-elf такой виртуозности не достиг, у него код более линейный, может он и переставляет там что-то, но я как-то не заметил - отладка кода куда проще - всё читается сходу. Что заметил у этого компилятора: он хорошо "держит" контекст, т.е. как бы "помнит" какие ресурсы он на что использовал и в каком они состоянии. Например, смотрю фрагмент кода (кода бился с этим гадким багом с порчей регистра в прерывании), вижу, там почему-то адрес массива не грузится в P-регистра, а делается операция декремента этого регистра на какое-то значение. Думаю, что это - баг, что-ли - прога-то вылетает, вот на любое место подозрение.

Стал разбираться, оказалось, что всё корректно: регистр был ранее загружен значением адреса, связанным с адресом упомянутого массива, и компилятор просто "помнит" это и "знает" эту связь, а получить нужное значение адреса выгоднее одной арифметической операцией с P-регистром, чем явно грузить половинки адреса - это две инструкции, и они, вдобавок, ещё и "толстые" - каждая тащит 16 бит значения адреса.

Т.е. нельзя сказать, что кодогенератор совсем уж тупой - вот такие вещи он делает очень прилично. Возможно, это связано с тем, что это оптимизации общего плана, и тут gcc уже не одну "собаку съел", а вот с вещами, "завязанными" на аппаратуру конкретного процессора (конвейер, аппаратные циклы) у него послабее.

По факту портировал два проекта, финальные размеры кода (в байтах):

CODE
Project#    1      2
vdsp++:   20288  66194
bfin-elf: 19748  67880

Очень сильно на это влияет качество библиотек. Первый проект, когда в первый раз запустил, размер кода был что-то под 45 килобайт! Я даже офигел поначалу. Потом стал смотреть в мапе, что же там столько жрёт. Оказалось это библиотека плавающей точки и библиотека форматированного вывода.

С плавающей точкой удалось разобраться относительно легко - оказалось, в составе bfin-elf есть та самая оптимизированная либа плавающей точки из vdsp++ (очень хорошая реализация), просто её надо было правильно подключить: там есть опция gcc "-mfast-fp", которая должна была бы это сделать, я её передал компилятору, и толку не было. А оказывается её надо линкеру передавать. Сам фронт-энд gcc, видимо, умеет правильно конфигурировать вызовы, но я сборку организую как раздельные вызовы компилятора, ассемблера, линкера, вот передавал не тому тулу. Это нигде не освещается, опция gcc и всё. Ошибочно полагал, что при этой опции компилятор будет генерировать другие имена библиотечных функций. Но нет, имена те же, просто линкер подсовывает другую либу, что, в общем, проще и логичнее. Только бы документировать этот момент почётче.

А та библиотека, которую он по умолчанию вставляет, это, по ходу, тянуто с РС, там всё очень неоптимально. Для сравнения: функция умножения плавающей точки имеет размер 1.7 кбайта в то время как оптимизированная библиотека эту же функцию реализует что-то в 270 байт. Вот так вот и разница набегает.

Оптимизированная либа не совсем полноценна - она не все требования стандарта выполняет (проверок на NaN нету, ещё что-то подобное), но для наших применений это, как правило, несущественно. Тем более, что на vdsp++ используется она же.

Форматированный вывод тоже достаточно безобразно сделан. Поэтому была подтянута сторонняя реализация - она есть в упомянутой в предыдущем посте библиотеке на гитхабе.

Ну, и во втором проекте используются немного контейнеры stl, что тянет за собой алокаторы, т.е. работу со свободной памятью. Реализация из bfin-elf тоже никакая, поэтому тоже была подтянута сторонняя библиотека.

В с этими либами результат такой, как показан выше. Собственно, идея с этой коллекцией библиотек отсюда и поизросла.

QUOTE (Fat Robot @ Mar 2 2016, 14:53) *
Есть ли заметные изменения в производительности?

Явных тестбенчей не проводил, единственное, что замерял - это время передачи управления между процессами в scmRTOS, в случае vdsp++ оно было 1.5 мкс (200 МГц тактовая проца), на bfin-elf - 1.6 мкс. Т.е. чуть похуже, но не фатально.

В общем, мой вывод: несмотря на все трудности, жить можно, результат на bfin-elf не лучше, но и почти не хуже. Опасения за качество кодогенерации, можно сказать, не подтвердились. Больше всего напрягает то, что тулчейн, видимо, не развивается.
Fat Robot
Титанический труд. Спасибо.
Если подводить итог: вы полностью переезжаете на gcc или для старых проектов остаетесь на vdsp?
dxp
QUOTE (Fat Robot @ Mar 10 2016, 13:35) *
Титанический труд. Спасибо.

И вам спасибо за оценку. sm.gif

QUOTE (Fat Robot @ Mar 10 2016, 13:35) *
Если подводить итог: вы полностью переезжаете на gcc или для старых проектов остаетесь на vdsp?

Полностью. У меня на данный момент реально всего пара-тройка долгоиграющих проектов с vdsp++, которые актуально продолжать. Если что-то потребуется ещё - портирование само по себе достаточно простое, когда уже известны и обойдены трудности и есть весь набор инструментов, включая сборочный скрипт, утилиту для наглядного вывода информации о потреблении ресурсов, утилиту загрузчика, библиотеки поддержки... В этих условиях, IMHO, нет особой разницы по сложности между "начать новый проект" и "портировать имеющийся".
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2024 Invision Power Services, Inc.