Как включить и перевести пользовательские инструкции / расширения в стандартный код C / C ++, поддерживая высокую производительность - PullRequest
1 голос
/ 13 января 2012

Я разрабатываю ядро ​​обработки изображений общего назначения для ПЛИС и ASIC. Идея состоит в том, чтобы связать стандартный процессор с ним. Одна из проблем, с которыми я столкнулся, заключается в том, как это «запрограммировать» Позвольте мне объяснить: ядро ​​имеет декодер команд для моих «пользовательских» расширений. Например:

vector_addition $vector[0], $vector[1], $vector[2]    // (i.e. v2 = v0+v1) 

и многие другие. Эта операция передается процессором через шину ядру, используя процессор для циклов, не векторных операций и т. Д., Например:

for (i=0; i<15;i++)           // to be executed in the processor
     vector_add(v0, v1, v2)   // to be executed in my custom core

Программа написана на C / C ++. Ядру нужна только сама инструкция, в машинном коде

  1. код операции = vector_add = 0x12h
  2. register_src_1 = v0 = 0x00h
  3. register_src_2 = v1 = 0x01h
  4. register_dst = v2 = 0x02h

    машинный код = opcore | v0 | v1 | v2 = 0x7606E600h

(или что-то еще, просто соединение разных полей для построения инструкции в двоичном виде)

После отправки его через шину ядру ядро ​​может запрашивать все данные из памяти с выделенными шинами и обрабатывать все без использования процессора. Большой вопрос: как я могу перевести предыдущую инструкцию в ее шестнадцатеричное представление? (отправить его через шину не проблема). Некоторые варианты, которые приходят на ум:

  • Запуск интерпретированного кода (перевод в машинный код во время выполнения в процессоре) -> очень медленно, даже используя какой-то встроенный макрос
  • Скомпилируйте пользовательские разделы с помощью внешнего пользовательского компилятора, загрузите двоичный файл из внешней памяти и переместите его в ядро ​​с помощью уникальной инструкции -> трудно читаемый / понятный исходный код, плохая интеграция с SDK, слишком много разделов, если код очень сегментирован
  • JIT-компиляция -> сложная только для этого?
  • Расширение компилятора -> кошмар!
  • Пользовательский процессор, подключенный к пользовательскому ядру, для обработки всего: циклов, указателей, выделения памяти, переменных ... -> слишком много работы

Проблема заключается в программном обеспечении / компиляторах, но для тех, кто обладает глубокими знаниями в этой теме, это SoC в FPGA, основным процессором является MicroBlaze, а IP Core использует шины AXI4.

Надеюсь, я все правильно объяснил ... Заранее спасибо!

Ответы [ 3 ]

1 голос
/ 13 января 2012

Я не уверен, что полностью понимаю, но я думаю, что сталкивался с чем-то подобным раньше. Исходя из комментария к ответу Родриго, звучит так, будто в вашем коде разбросаны небольшие фрагменты инструкций. Вы также упоминаете, что возможен внешний компилятор, просто боль. Если вы комбинируете внешний компилятор с макросом C, вы можете получить что-то приличное.

Рассмотрим этот код:

for (i=0; i<15;i++)
     CORE_EXEC(vector_add(v0, v1, v2), ref1)

Макрос CORE_EXEC будет служить двум целям:

  1. Вы можете использовать внешний инструмент для сканирования ваших исходных файлов на предмет этих записей и компиляции кода ядра. Этот код будет связан с C (просто создайте файл C с двоичными битами), используя имя ref1 в качестве переменной.
  2. В C вы определите макрос CORE_EXEC для передачи строки «ref1» ядру для обработки.

Таким образом, на этапе 1 будет создан файл скомпилированных двоичных инструкций ядра, например, приведенное выше может иметь такую ​​строку:

const char * const cx_ref1[] = { 0x12, 0x00, 0x01, 0x02 };

И вы можете определить CORE_EXEC следующим образом:

#define CORE_EXEC( code, name ) send_core_exec( cx_##name )

Очевидно, что вы можете выбирать префиксы по своему усмотрению, хотя в C ++ вместо этого вы можете использовать пространство имен.

С точки зрения цепочки инструментов вы можете создать один файл для всех ваших битов или один файл для каждого файла C ++, что может быть проще для обнаружения грязных данных. Затем вы можете просто включить сгенерированные файлы в ваш исходный код.

0 голосов
/ 13 января 2012

Допустим, я собирался модифицировать ядро ​​манипулятора, чтобы добавить некоторые пользовательские инструкции, и операции, которые я хотел выполнить, были известны во время компиляции (доберется до времени выполнения через секунду).

Я бы использовалассемблер, например:

.globl vecabc
vecabc:
   .word 0x7606E600 ;@ special instruction
   bx lr

или встроенный в него синтаксис для встроенного компилятора, это усложняет задачу, если вам нужно использовать регистры процессора, например, когда компилятор c заполняет регистры в строкеассемблер, затем ассемблер собирает эти инструкции.Я считаю, что написание фактического asm и просто вставка слов в поток инструкций, как указано выше, только компилятор искажает некоторые байты как данные, а некоторые байты как инструкции, ядро ​​будет видеть их в том порядке, в котором они написаны.

Если вам нужночтобы делать вещи в реальном времени, вы можете использовать самоизменяющийся код, опять же, мне нравится использовать asm для батута.Создайте инструкции, которые вы хотите запустить где-нибудь в оперативной памяти, скажем, по адресу 0x20000000, затем попросите батут вызвать его:

.globl tramp
tramp:
    bx r0 ;@ assuming you encoded a return in your instructions

вызовите его с помощью

tramp(0x20000000);

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

0 голосов
/ 13 января 2012

Не могли бы вы перевести все свои разделы кода в машинный код в начале программы (только один раз), сохранить их в двоичном формате в блоках памяти и затем использовать эти двоичные файлы при необходимости.

Это в основном то, как работают шейдеры OpenGL, и я нахожу это довольно простым в управлении.

Основным недостатком является потребление памяти, так как в памяти есть текстовое и двоичное представление одних и тех же скриптов.Я не знаю, если это проблема для вас.Если это так, есть частичные решения, такие как выгрузка исходных текстов после их компиляции.

...