Документация Engee

Си функция

Использование Cи кода в моделях.

Тип: CFunction

Путь в библиотеке:

/Basic/User-Defined Functions/C Function

Описание

Блок Си функция позволяет в моделях Engee использовать код на языке Си и разделяемые библиотеки, имеющие Си API.

При подключении динамической библиотеки, использующей внутренние статические переменные, стоит убедиться в том, что она предоставляет API для инициализации/деинициализации этих переменных. Соответствующие функции необходимо вызывать во вкладках StartCode и TerminateCode редактора исходного кода. В противном случае статические переменные будут сохранять свои последние значения между симуляциями, что может приводить к неправильным результатам при повторных запусках модели.

Использование

Для интеграции исходного Cи кода в модель Engee необходимо:

  • Добавить в модель блок Си функция из раздела Базовые/Пользовательские функции библиотеки блоков;

  • Нажать на кнопку Редактировать исходный код, после чего будет открыт редактор исходного кода.

c function settings

Редактор исходного кода

Рабочая область редактора исходного кода состоит из четырех вкладок:

  • OutputCode — содержит код, выполняющийся на каждом шаге расчета модели;

  • StartCode — содержит код, выполняющийся один раз при инициализации модели;

  • TerminateCode — содержит код, выполняющийся один раз при остановке модели;

  • SharedCode — содержит код, позволяющий обмениваться глобальными (разделяемыми) переменными и функциями между экземплярами блока Си функция.

В каждой из секций выполняется подсветка синтаксиса языка Cи.

В секции OutputCode расположен блок специальных комментариев, имеющих значение для симулятора и генератора кода.

Комментарии позволяют связать переменные в исходном коде с сигналами (входами и выходами) блока Си функция, а также указать дополнительную информацию, необходимую для сборки кода и его использования в модели.

c function code editor

Особенности генерации Си кода из блока Си функция

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

В данный момент генератор Cи кода не выдает дополнительную информацию для системы сборки, которая бы основывалась на директивах блока Си функция. Директивы указываются в разделе Build options настроек блока:

build options c function

Повторное использование блока

Вкладка SharedCode исходного кода блока Си функция позволяет задавать общий код, доступный для обмена данными и функциями между экземплярами блоков Си функция с одинаковым именем в параметре Имя функции.

Для того чтобы использовать функциональность SharedCode, нужно создать блок Си функция, указать для него имя в параметре Имя функции, и затем сохранить этот блок в пользовательскую библиотеку user library 1. После этого блок можно добавлять в модель в нескольких местах, где он будет сохранять доступ к общим данным и функциям. В результате при моделировании создается только одна общая разделяемая библиотека и один набор функций, который используется всеми экземплярами блока Си функция.

При генерации кода для блоков с одинаковым именем в параметре Имя функции создаются только общие функции init, step и term, а код из вкладки SharedCode добавляется в проект один раз. Это упрощает управление кодом и обеспечивает доступ к общим данным во всех экземплярах блока Си функция.

Рабочие переменные

Вкладка Рабочие параметры блока Си функция позволяет задавать статические рабочие переменные, которые остаются специфичными для каждого экземпляра блока, даже если у нескольких блоков одинаковое значение параметра Имя функции. Эти рабочие переменные, похожи на параметры, но отличаются возможностью изменять их значения из исходного кода блока во время выполнения. Такой подход позволяет хранить и изменять данные, которые используются только в пределах определенного экземпляра блока.

Для каждой рабочей переменной на вкладке Рабочие параметры указывается Имя и Размер в байтах. Внутри исходного кода эти переменные доступны через указатель на структуру Work, который присутствует в интерфейсах всех трех функций блока: step, init, и term. Пример:

work variable 1

Здесь в исходном коде используется выравнивание, где макрос is_aligned проверяет корректность выравнивания переменных. Переменные work_variable1 и work_variable3 заранее определены на вкладке Рабочие параметры и доступны через структуру Work.

Работа с массивами

Когда данные передаются из Julia в код на языке Cи, то важно учитывать различия в индексации и структуре массивов. Это актуально для многомерных массивов, которые могут транспонироваться (изменять порядок индексов) при передаче. Ниже приведены основные моменты, которые необходимо учитывать при работе с массивами:

Подробнее о работе с массивами
  • Одномерные массивы — при передаче одномерного массива из Julia в Cи массив сохраняет свою структуру. Индексация в Cи начинается с 0, поэтому при обращении к элементам массива нужно учитывать этот сдвиг. Например, если в параметре блока Си функция одномерный массив записан как a = [1, 2, 3], то на языке Cи:

    #include <stdio.h>
    
    int main() {
        int a[3] = {1, 2, 3};
        printf("%d", a[2]); // Выведет 3
        return 0;
    }

    В этом примере для получения значения элемента массива с индексом 2 используется обращение a[2], что вернет значение 3.

  • Многомерные массивы — многомерные массивы могут изменять порядок индексов при передаче из Julia в Cи. В Cи коде массив будет транспонирован, что означает, что при доступе к элементам массива нужно учитывать изменение порядка индексов. Например, есть массив в Julia:

    a = [1 2 3; 4 5 6]
    
    #Julia интерпретирует это как матрицу 2x3
    2x3 Matrix{Int64}
     1 2 3
     4 5 6
    
    #доступ к элементу массива
    a [1,2]
    2

    Тогда в Си:

    #include <stdio.h>
    
    int main() {
        int a[3][2] = {
            {1, 4},
            {2, 5},
            {3, 6}
        };
        printf("%d", a[1][0]); // Выведет 2
        return 0;
    }

    Массив [1 2 3; 4 5 6] в Julia становится массивом a[2][3] в Cи, где первый индекс указывает на строку, а второй — на столбец. В данном примере на Cи для получения элемента со значением 2 используется индексация a[1][0]. Следовательно, массив в Cи фактически хранит элементы транспонированного массива Julia, что необходимо учитывать при доступе к элементам.

При работе с трехмерными и более сложными массивами индексация также может меняться. Например, если массив в Julia имеет размерность [2, 3, 4], то в Cи он будет представлен как [4][3][2]. Это необходимо учитывать при обращении к элементам массива в Cи коде.

Пример трехмерного массива в Julia:

a = reshape(1:24, 2, 3, 4)

[:, :, 1] =
 1  3  5
 2  4  6

[:, :, 2] =
 7   9  11
 8  10  12

[:, :, 3] =
13  15  17
14  16  18

[:, :, 4] =
19  21  23
20  22  24


println(a[1, 2, 3]) # Выведет 13

В С:

#include <stdio.h>

int main() {
    int a[4][3][2] = {
        {
            {1, 4},
            {2, 5},
            {3, 6}
        },
        {
            {7, 10},
            {8, 11},
            {9, 12}
        },
        {
            {13, 16},
            {14, 17},
            {15, 18}
        },
        {
            {19, 22},
            {20, 23},
            {21, 24}
        }
    };
    printf("%d", a[2][1][0]); // Выведет 13
    return 0;
}

Общее правило преобразования индексов из Julia в Cи заключается в изменении порядка:

(D1, D2, …​, Dn) → [Dn][Dn-1]…​[D1]

Это означает, что индексы в Cи записываются в обратном порядке.

Работа с шинами

В блоке Си функция поддерживается работа с шинами как на входных/выходных портах, так и в параметрах блока. Ниже описаны особенности настройки и использования шин:

Шины на портах

Чтобы задать шину на входном или выходном порте:

  1. В параметре Type соответствующего порта (вкладка Ports в настройках блока) выберите тип BusSignal.

  2. После этого параметр Input bus type (или Output bus type) автоматически примет форму:

    BusSignal{(), Tuple(), ()}

    Здесь следует указать:

    • Тип шины явно, например BusSignal{(:s1, :s2), Tuple(Float64, Int54), ), (}. :s1, :s2 — имена сигналов в шине; Tuple(Float64, Int64) — их типы; ), ( — их размерности;

    • Или имя переменной, содержащей описание типа шины, определенной в рабочем пространстве Engee (в окне переменных) или в Си коде.

Все типы шин должны быть заданы явно — это особенность блока Си функция.

Имена шин не должны содержать пробелов, так как они используются в Си коде, где пробелы в именах переменных недопустимы.

Шины в параметрах

Если шина используется как параметр:

  • В поле Value нужно задать шину в виде именованного кортежа, например:

    (s1 = 4.4, s2 = (b1 = 5, b))
  • Если параметр назван, например, p, то в Cи коде (исходный код блока Си функция) обращаться к элементам шины можно как к структурам:

    output1 = input1 * (p.s1 + p.s2.b1);

Основные

Количество входных портов — количество входных портов
1 (по умолчанию)

Details

Определяет количество входных портов блока. Значение параметра Количество входных портов будет соответствовать количеству входных портов.

Количество выходных портов — количество выходных портов
1 (по умолчанию)

Details

Определяет количество выходных портов блока. Значение параметра Количество выходных портов будет соответствовать количеству выходных портов.

Имя функции — идентификатор общего кода для нескольких блоков
нет (по умолчанию)

Details

Укажите строковое значение для объединения кода блоков Си функция. Блоки с одинаковым именем в параметре Имя функции будут использовать общий код и обмениваться данными через вкладку исходного кода SharedCode блока Си функция.

Если оставить поле пустым, то выбранный блок Си функция останется автономным (по умолчанию).

В параметре Имя функции можно указывать как абсолютный путь (начинается с /user), так и относительный — от текущей рабочей директории (ее можно узнать с помощью команды pwd, которая выводит полный путь к текущей папке).

Также допустимо указание только имени файла: в этом случае Engee сначала ищет файл в текущей директории, а затем — в путях поиска, описанных в Редактор пути Engee.

Шаг расчёта — интервал между шагами расчета
−1 (по умолчанию)

Details

Укажите интервал между шагами расчета как неотрицательное число. Чтобы наследовать шаг расчета, установите для этого параметра значение −1.

Ports

Вход

Input port 1 — входной порт №1
скаляр | вектор | матрица

Details

Для работы входного порта заполните/настройте следующие поля:

  • Label — имя (текстовая метка) входного порта. По умолчанию ячейка имени не заполнена (имя не задано).

  • Variable name — имя переменной входного порта в исходном Си-коде. По умолчанию input1.

    /* Этот код вызывается на каждом шаге расчета модели */
    output1 = input1 * param1;

    где input1 — имя переменной входного порта; output1 — имя переменной выходного порта; param1 — имя переменной параметра.

  • Type — тип данных переменной входного порта в исходном Cи коде. Тип данных переменной должен совпадать с типом данных сигнала. Поддерживаются базовые типы данных языка Си:

    double (по умолчанию) | float | int8_t | uint8_t | int16_t | uint16_t | int32_t | uint32_t | int64_t uint64_t | int128_t | uint128_t | bool | BusSignal

  • Size — размерность входного сигнала 1 (по умолчанию).

    В поле Size используется нотация Julia, где размер записывается в виде кортежа, например, (2, 3, 4). Однако при работе с этим портом в Cи коде необходимо учитывать изменение порядка индексов. Это значит, что если порт задан с размером (2, 3, 4), то в Cи коде переменная будет иметь размерность [4][3][2]. Это правило аналогично тому, как работают параметры: порядок индексов в Cи транспонируется по сравнению с Julia.

Выход

Output port 1 — выходной порт №1
скаляр | вектор | матрица

Details

Для работы выходного порта заполните/настройте следующие поля:

  • Label — имя (текстовая метка) выходного порта. По умолчанию ячейка имени не заполнена (имя не задано).

  • Variable name — имя переменной выходного порта в исходном Си-коде. По умолчанию output1.

    /* Этот код вызывается на каждом шаге расчета модели */
    output1 = input1 * param1;

    где output1 — имя переменной выходного порта; input1 — имя переменной входного порта; param1 — имя переменной параметра.

  • Type — тип данных переменной выходного порта в исходном Cи коде. Тип данных переменной должен совпадать с типом данных сигнала. Поддерживаются базовые типы данных языка Си:

    double (по умолчанию) | float | int8_t | uint8_t | int16_t | uint16_t | int32_t | uint32_t | int64_t uint64_t | int128_t | uint128_t | bool | BusSignal

  • Size — размерность выходного сигнала 1 (по умолчанию).

    В поле Size используется нотация Julia, где размер записывается в виде кортежа, например, (2, 3, 4). Однако при работе с этим портом в Cи коде необходимо учитывать изменение порядка индексов. Это значит, что если порт задан с размером (2, 3, 4), то в Cи коде переменная будет иметь размерность [4][3][2]. Это правило аналогично тому, как работают параметры: порядок индексов в Cи транспонируется по сравнению с Julia.

Параметры

Количество параметров — укажите число параметров
1 (по умолчанию)

Details

Число параметров, используемых в блоке.

Параметр 1 — определяет параметр как переменную
1 (по умолчанию)

Details

Определяет параметр как переменную для использования в исходном коде:

  • Имя — имя параметра, по умолчанию param1. Может быть изменено. Имена новых параметров называются param2 и далее по возрастанию.

  • Величина — значение параметра, по умолчанию 0. Может быть изменено. Значение новых параметров также по умолчанию равно нулю.

Для работы параметров необходимо зайти в редактор кода блока Си функция (Редактировать исходный код) во вкладку OutputCode и внести имя нужного параметра. По умолчанию:

/* Этот код вызывается на каждом шаге расчета модели */
output1 = input1 * param1;

где input1 — переменная входного сигнала; output1 — переменная выходного сигнала.

Рабочие параметры

Количество рабочих переменных — число рабочих переменных
0 (по умолчанию)

Details

Число рабочих переменных, используемых в блоке для хранения данных, специфичных для каждого экземпляра. Рабочие переменные доступны через структуру Work и могут использоваться в функциях init, step и term для записи и чтения данных.

Каждая переменная задается с указанием параметров Имя и Размер в байтах, что позволяет хранить данные различных типов. Если переменной присвоен больший размер, то ее можно использовать как массив.

Рабочий параметр 1 — определяет рабочую переменную
1 (по умолчанию)

Details

Определяет рабочую переменную для использования в исходном коде:

  • Имя — имя параметра, по умолчанию work_variable1. Может быть изменено. Имена новых рабочих переменных называются work_variable2 и далее по возрастанию.

  • Размер в байтах — объем памяти, выделяемый для переменной, в байтах. Этот параметр позволяет указать размер переменной, будь то скаляр или массив.

Для работы переменных необходимо зайти в редактор кода блока Си функция (Редактировать исходный код) во вкладку OutputCode и внести имя переменной.

Build options

Исходные файлы — подключение файлов исходного кода
нет (по умолчанию)

Details

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

/user/project/src/example.c

Включать каталоги — определение пути к каталогу заголовочных файлов
нет (по умолчанию)

Details

Используется для определения пути к каталогу, содержащему заголовочные файлы. Например:

/user/project/include

Библиотечные каталоги — определение пути к каталогу библиотек
нет (по умолчанию)

Details

Используется для определения пути к каталогу, содержащему разделяемые библиотеки. Например:

/user/project/third_party

Заголовочных файлы — подключение заголовочных файлов
stdint.h math.h (по умолчанию)

Details

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

example.h

Определение дополнительных директив — определение дополнительных директив #define
нет (по умолчанию)

Details

Используется для определения дополнительных директив #define. Например:

defines LOWER=0 UPPER=300 STEP=20

Библиотеки — подключение библиотек
нет (по умолчанию)

Details

Используется для подключения библиотек. Должен включать имя библиотеки вместе с расширением. Например:

libexample.so
Фактический тип данных, как и поддержка возможных типов данных, зависит от пользовательского кода внутри блока.

Дополнительные возможности

Генерация Си кода: Да