Вычислить e^x для значений с плавающей точкой в System Verilog?
Я строю нейронную сеть, работающую на ПЛИС, и последняя часть головоломки - это аппаратная функция сигмоида. Это либо:
1/(1 + e^-x)
или же
(atan(x) + 1) / 2
К сожалению, х здесь является значением с плавающей запятой (real
значение в SystemVerilog).
Есть ли какие-либо советы о том, как реализовать любую из этих функций в SystemVerilog?
Это действительно сбивает меня с толку, поскольку обе эти функции являются сложными, и я даже не знаю, с чего начать их реализацию из-за дополнительной сложности использования значений с плавающей запятой.
6 ответов
Довольно сложно реализовать подобные нелинейные функции на аппаратном уровне, а кроме того, арифметика с плавающей запятой еще более затратна. Определенно лучше (и рекомендуется) работать с арифметикой с фиксированной точкой, как упоминалось в ответах ранее. Количество битов точности в арифметике с фиксированной запятой будет зависеть от точности вашего результата и устойчивости к ошибкам.
Для аппаратных реализаций любую нелинейную функцию можно аппроксимировать как кусочно-линейную функцию и использовать подход к реализации на основе ПЗУ, как описано в предыдущих ответах. Количество точек выборки, которые вы берете из нелинейной функции, определяет вашу точность. Чем больше сэмплов вы сохраните, тем лучше будет аппроксимация функции, которую вы получите. Часто в аппаратном обеспечении количество сэмплов, которые вы можете хранить, может быть ограничено объемом доступной вам быстрой/локальной памяти. В этом случае для оптимизации ресурсов памяти можно добавить немного дополнительных вычислительных ресурсов и выполнить линейную интерполяцию для вычисления необходимых значений.
Я только что закончил это с помощью Vivado HLS, который позволяет писать схемы на C. Вот мой C-код.
#include math.h
void exp(float a[10],b[10])
{
int i;
for(i=0;i<10;i++)
{
b[i] = exp(a[i]);
}
}
Но возникает вопрос, что невозможно создать неклассифицированную матрицу. Может быть, есть другой способ, который я не знаю.
Один более простой способ для этого - создать память / массив для этой функции. Однако этот вариант может быть крайне неэффективным.
x
должен быть входным адресом для памяти, а значение в этом месте может быть выходом функции.
Предположим, значение вашей функции заключается в следующем. (Это всего лишь пример)
x = 0 => f(0) = 1
x = 1 => f(0) = 2
x = 2 => f(0) = 3
x = 3 => f(0) = 4
Таким образом, вы можете создать для этого массив, в котором будут храниться выходные значения.
int a[4] = `{1, 2, 3, 4};
Вам действительно нужно для этого плавающее число? Достаточно ли фиксированной точки?
Учитывая (atan(x) + 1) / 2, вполне вероятно, что единственными полезными значениями x являются те, где показатель степени довольно мал. (если показатель большой, ваш ответ - пи / 2).
atan числа с фиксированной точкой может быть довольно легко вычислено в HW; есть сердечные методы (см. https://zipcpu.com/dsp/2017/08/30/cordic.html) и прямые методы; см., например, https://dspguru.com/dsp/tricks/fixed-point-atan2-with-self-normalization/
Как вы, похоже, понимаете, тип real не синтезируется. вам нужно работать с целочисленной мантиссой типа и показателем целочисленного типа отдельно и объединять их, когда вы закончите, отслеживая знак. Как только вы позаботитесь о (e^-x), все остальное должно быть простым.
попробуйте эту страницу для быстрого объяснения: https://www.geeksforgeeks.org/floating-point-representation-digital-logic/
и поиск по "цифровому дизайну с плавающей запятой" для получения дополнительных объяснений / примеров.
Потоки проектирования ПЛИС, в которых нацелено оборудование (ПЛИС), обычно не поддерживают числа с плавающей запятой в матрице ПЛИС. Чаще используется фиксированная точка с ограниченной точностью.
Подход с фиксированной точкой ограниченной точности:
используйте Matlab для создания массива выборок для вашей математической функции, чтобы максимальное значение было +/- .99999. Для 8-битной точности (на самом деле 7 со знаковым битом) умножьте эти числа на 128, округлите до десятичной точки и опустите дробную часть. Запишите эти числа в текстовый файл в шестнадцатеричном формате с дополнением до двух. В SystemVerilog вы можете реализовать ПЗУ, используя этот текстовый файл. Используйте $readmemh() для считывания этих чисел в переменную стиля памяти (имеющую как упакованное, так и распакованное измерение). Ссылка на руководство:
https://projectf.io/posts/initialize-memory-in-verilog/ .
Теперь у вас есть ПЗУ с образцами вашей функции с ограниченной точностью.
Раздел 21.4 Загрузка данных массива памяти из файла в спецификации SystemVerilog предоставляет определение для $readmh(). Вот этот документ:
https://ieeexplore.ieee.org/document/8299595
Если вам нужна плавающая запятая, одна из возможностей - использовать программное ядро процессора с модулем с плавающей запятой, реализованным в матрице FPGA, и запускать программное обеспечение на этом ядре. Основной интерфейс с остальной структурой FPGA через физическую шину, такую как обработка пары axi4. См.
Https://www.xilinx.com/products/design-tools/microblaze.html, чтобы начать работу.
Этот рабочий процесс сильно отличается от обычного проектирования FPGA и использует другие инструменты. Компилятор C или C++ с математическими библиотеками (tan, exp, div и т. Д.) Будет использоваться вместе с ядром процессора.
Еще одна возможность для фиксированной точки - это ПЛИС с жестким ядром процессора. Xilinx Zynq - один из них. Это сложный и мощный подход. Бесплатная бесплатная книга содержит информацию о том, как использовать Zynq
http://www.zynqbook.com/ .
Этот рабочий процесс еще более сложен, чем подход с программным ядром, потому что Zynq - более сложная платформа (жесткий процессор и FPGA интегрированы в один чип).