Найти в Дзене
Мастерю понемногу.

Изучаю LGT8F328P. Аналог Ардуино 328P с плюшками. Часть 2. Ускоритель вычислений (uDSC).

Всем доброго здравия.

Давно я не появлялся на Дзен, ни чего не писал и не публиковал. Но я продолжаю активно работать над своими проектами, изучать микроконтроллеры, программирование, активное использование ИИ. Продолжается активная работа над моим основным проектом зарядного устройства на Ардуино нано - Viktori (https://t.me/arduino_viktori/1 - группа в Телеграмм).

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

  • 16-разрядный регистр операндов DX / DY;
  • 32-разрядный аккумулятор (DX);
  • Однотактный 16-разрядный множитель (MUL);
  • 32-разрядный целочисленный арифметический и логический блок (ALU), (16/32 бит могут быть выполнены сложениями, вычитаниями и сдвигами);
  • 16-разрядная операция накопления (используется для хранения результатов в пространстве оперативной памяти);
  • Делитель 32/16, 8 циклов для завершения операции.

Полный список операций:

Операции uDSC
Операции uDSC

Все операции выполняются за один такт, только операция деления - за восемь тактов.

Функции работы с ускорителем uDSC выполнены в виде ассемблерных вставок. Что создало довольно много трудностей, пришлось маленько изучить ассемблер. Но результат того стоит, теперь можно использовать ускоритель в своих программах.

Первое что нужно сделать - это включить ускоритель записав бит в соответствующий порт:

void setup() {
DSCR = 1<<DSUEN; // Включить uDSC
}

Для большего удобства я записал коды операций в нумерованный список (пока только эти операции):

enum uDSCxy: uint8_t {
multiplication = 0x44, // умножение da = dx * dy
addition = 0x5, // сложение da = dx + dy
subtraction = 0x1, // вычитание da = dx - dy
average = 0x4C, // умножение со сдвигом da = (dx * dy) >> 1
multipl_accum = 0x46, // умножение с накоплением DA = DA + DX * DY
multipl_subtr = 0x42, // умножение с уменьшением DA = DA - DX * DY
};

Функция принимает 2 операнда 16-бит и название операции, возвращает 32-битный результат:

uint32_t uDSC(uint16_t dx, uint16_t dy, uDSCxy operation) {
uint32_t result = 0; // Создаем переменную для хранения результата
asm volatile(
"out 0x10, %[dsdx];" "\n\t" // Устанавливаем первое число (dx)
"out 0x11, %[dsdy];" "\n\t" // Устанавливаем второе число (dy)
"out 0x01, %[opc];" "\n\t" // Выполняем операцию
"in %A[result], 0x38;" "\n\t" // Читаем младшие 2 байта из 0x38 в result
"in %C[result], 0x39;" "\n\t" // Читаем старшие 2 байта из 0x39 в result
: [result] "=r"(result) // Назначаем результат в качестве выхода
: [dsdx] "r"(dx), [dsdy] "r"(dy), [opc] "r"(operation)
);
return result; // Возвращаем полученный результат
}

Программа в Arduino IDE:

// операции uDSC с двумя операндами x, y // 29 tik
enum uDSCxy: uint8_t {
multiplication = 0x44, // умножение da = dx * dy
addition = 0x5, // сложение da = dx + dy
subtraction = 0x1, // вычитание da = dx - dy
average = 0x4C, // умножение со сдвигом da = (dx * dy) >> 1
multipl_accum = 0x46, // умножение с накоплением DA = DA + DX * DY
multipl_subtr = 0x42, // умножение с уменьшением DA = DA - DX * DY
};
volatile uint32_t data; // для результата ариф. операций
uint16_t dx = 4;
uint16_t dy = 2;
void setup() {
Serial.begin(115200);
DSCR = 1<<DSUEN; // Включить uDSC
}
uint32_t uDSC(uint16_t dx, uint16_t dy, uDSCxy operation);

void loop() {
data = uDSC(x, y, multiplication); // умножение
Serial.print("uDSC: ");
Serial.print("x=");
Serial.print(x);
Serial.print(" y=");
Serial.print(y);
Serial.print(" rezult = ");
Serial.println(data); // вывод результата в серийный порт
// сравнение с обычным умножением
Serial.print("x*y : ");
Serial.print("x=");
Serial.print(x);
Serial.print(" y=");
Serial.print(y);
Serial.print(" rezult = ");
Serial.println((uint32_t)x * y); // вывод результата в серийный порт
Serial.println("..");
delay(5000); // задержка 5 секунд
x += 199; // изменяем числа
y += 123;
}

Но все познается в сравнении. В следующем скетче я сравнил время выполнения 1000 операций умножения в цикле:

Время выполнения цикла с ускорителем - 690 мкс, без него - 2480 мкс. Итого ускорение в 3,6 раза! И это еще далеко не самый эффективный алгоритм применения ускорителя вычислений.

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

Для этого функцию работы с uDSC разделил на две функции - в первой производятся загрузка данных в uDSC и вычисление, результат вычислений накапливается в регистре uDSC; вторая функция считывает значение из регистра uDSC:

Время выполнения цикла с ускорителем - 378 мкс, без него - 2480 мкс. Итого ускорение в 6,5 раз!

Ссылка на диск со скетчами из статьи.

Это лишь начало работы с ускорителем вычислений (uDSC), далее я создам полноценную библиотеку со всеми доступными функциями ускорителя.

Полезные ссылки:

Ассемблерные вставки в С коде GCC для AVR и Arduino

Справочник по командам ассемблера AVR