Вычисление логарифма
Анатолий Сергиенко
Тот, кто еще три десятилетия назад изучал теоретические основы электротехники, должен был в совершенстве владеть логарифмической линейкой. Потому, что надо было много умножать, а в ту пору цена калькулятора равнялась цене сотни экземпляров 7-значной таблицы логарифмов. Логарифмическая линейка демонстрировала фокус, как простое сложение отрезков на логарифмических шкалах дает функции произведения, деления, извлечения корня, преобразования координат — что называется, ловкость рук.
По аналогии с логарифмической линейкой, в начале эры компьютеров были попытки реализации дорогостоящих операций сложения, деления, извлечения корня на основе функции логарифма, реализовнной, например, таблично.
Сейчас функция логарифма часто встречается в специализированных вычислителях. Например, при цифровой обработке сигналов логарифмическая шкала используется для сравнения уровней сигналов в децибелах, в простых алгоритмах сжатия сигналов, некоторых алгоритмах нелинейной обработки сигналов, таких как кепстральная обработка. Поэтому в данной заметке рассмотрены некоторые способы реализации этой функции.
В спецвычислителях выгодно вычислять функцию логарифма по основанию 2.
Во-первых, если аргумент x — целое число, то логарифм приближенно равен числу значащих цифр p. То есть,
log2(x) = log2( 2p * xm ) = p + log2(xm),
где 1 > xm > 0.5 — мантисса числа.
Во-вторых, специалисту по цифровой обработке сигналов привычно оценивать уровень сигнала в децибелах как приблизительно равным 6 * p, если x — это амплитуда сигнала или приблизительно равным 3 * p, если x — мощность сигнала.
На вышекуказанном свойстве основаны следующие алгоритмы вычисления логарифма. Алгоритмы описаны на VHDL с применением библиотек IEEE.std_logic_1164 и IEEE.STD_logic_arith.
Модель PARITY предназначена для приближенного вычисления логарифма, то есть для вычисления числа значащих цифр p.
Она определяет номер старшего значащего разряда n — значного двоичного целого числа x. По сути это схема приоритетного шифратора, поэтому она называется PARITY.
entity LOG2 is generic(n: integer:=15; m: integer:= 4); port (X: in STD_LOGIC_VECTOR(n-1 downto 0); Y: out STD_LOGIC_VECTOR(m-1 downto 0)); end LOG2; architecture PARITY of LOG2 is begin process(X) variable p:STD_LOGIC_VECTOR(m-1 downto 0); begin p:= CONV_STD_LOGIC_VECTOR(n,m); for i in 1 to n loop exit when x(n-i)='1'; p:= UNSIGNED(p)-1; end loop; Y<=p; end process; end PARITY;
Вначале операнд-вектор p принимает значение числа n разрядов данного X. Для этого используется функция CONV_STD_LOGIC_VECTOR(n,m), преобразующая целое число разрядов n в m - разрядный вектор. Затем в цикле разряды вектора X последовательно проверяются, начиная со старшего, пока не будет найдена цифра 1. Одновременно из p вычитается 1. В результате, p становится равным номеру старшей значащей 1 в числе x, то есть приближенному значению логарифма.
Несмотря на сравнительную простоту описания алгоритма (попробуйте эту задачу решить с помощью логических функций), он дает приличные результаты при синтезе логической схемы. Правда, не все компиляторы-синтезаторы распознают правильно идиому таких циклов, поэтому необходимы эксперименты с каждым конкретным синтезатором. В таблице 1 приведены некоторые результаты синтеза архитектуры PARITY синтезатором SYNPLIFY для ПЛИС серии XC4000XLA-0.7.
Таблица 1.
Разрядность , n | 7 | 15 | 24 | 31 | 48 |
Аппаратные затраты, LUT | 4 | 10 | 24 | 27 | 55 |
Период вычислений, нс | 10 | 13.2 | 16.6 | 13.9 | 17 |
Для более точного вычисления логарифма его можно представить как:
log2(x) = log2( 2p * xm ) = p-1 + log2(xl),
где xl = 2 * xm; 2 > xl ≥ 1.
Тогда старшие m разрядов результата будут составлять разряды числа p-1, а младшие разряды - разряды положительной функции log2(xl). Функцию log2(xl) можно реализовать таблично, например, следующим процессом:
LOGTABL: process(xl) begin case xl is when "0000" => log<="0000"; when "0001" => log<="0010"; when "0010" => log<="0011"; when "0011" => log<="0100"; when "0100" => log<="0101"; when "0101" => log<="0111"; when "0110" => log<="1000"; when "0111" => log<="1001"; when "1000" => log<="1010"; when "1001" => log<="1011"; when "1010" => log<="1011"; when "1011" => log<="1100"; when "1100" => log<="1101"; when "1101" => log<="1110"; when "1110" => log<="1111"; when "1111" => log<="1111"; when others => end case; end process;
где xl - 4 старшие значащие разряды исходного числа, log приблизительно равен log2(xl).
Для выделения разрядов xl необходимо построить сдвигатель, на входы которого подаются входное число X и номер старшей значащей единицы p.
Алгоритм работы сдвигателя можно совместить с алгоритмом вычисления числа p следующим образом:
signal xl:STD_LOGIC_VECTOR (3 downto 0); . . . SHIFT:process(X) variable xs:STD_LOGIC_VECTOR (n-1 downto 0); variable p:STD_LOGIC_VECTOR(m-1 downto 0); begin xs:=X; p:= CONV_STD_LOGIC_VECTOR(n-1,m); for i in 1 to n-1 loop exit when x(n-i)='1'; xs:=xs(n-2 downto 0)&'0';--сдвиг влево p:=unsigned(p)-1; end loop; xl<=xs(n-2 downto n-5); end process;
Полученная в результате архитектура DSPLOG приведена в приложении 1.
В таблице 2 показаны некоторые результаты синтеза архитектуры DSPLOG синтезатором SYNPLIFY для ПЛИС серии XC4000XLA-0.7.
Таблица 2.
Разрядность , n | 8 | 16 | 24 | 32 | 48 |
Аппаратные затраты, LUT | 26 | 66 | 109 | 147 | 233 |
Период вычислений, нс | 19.8 | 22.4 | 23.9 | 25 | 27 |
Синтезированная структура показана на рис. 1. На входе и выходе устройства включены регистры для обеспечения высокой пропускной способности и повторяемости при реализации в ПЛИС. Устройство имеет 3 уровня логики. Первый уровень составляют схемы И сравнения соседних разрядов, второй уровень - два приоритетных коммутатора для определения веса p старшей единицы и сдвига четырех старших значащих разрядов, третий уровень - таблица логарифмов LOGTABL. Точности результатов этой схемы (7-9 двоичных разрядов) достаточно для многих приложений цифровой обработки сигналов.
Если на вход архитектуры PARITY подавать квадрат числа х, то удвоится количество входных разрядов, зато разрядность результата увеличится на 1. Это означает, что удвоится точность результата. При q-кратном последовательном возведении в квадрат входного данного, количество точных разрядов результата увеличивается на q. Для упрощения таких вычислений возводить в квадрат можно только несколько старших значащих разрядов нормализованного операнда, выполняя слежение за масштабом результата.
На этом свойстве основан следующий алгоритм вычисления логарифма, который является модернизацией алгоритма, приведенного в [1].
entity LOG2 is generic(n: integer:=16; --input width m: integer := 4; -- m+q =output width q:integer :=12); port (X: in STD_LOGIC_VECTOR(n-1 downto 0); Y: out STD_LOGIC_VECTOR(m+q-1 downto 0)); end LOG2; architecture LOGPRECISE of LOG2 is begin LOGQUAD:process(X) variable xs:STD_LOGIC_VECTOR(n-1 downto 0); variable xl:STD_LOGIC_VECTOR(q downto 0); variable xq:STD_LOGIC_VECTOR(2*q+1 downto 0); variable p:STD_LOGIC_VECTOR(m-1 downto 0); variable yl:STD_LOGIC_VECTOR(q-1 downto 0); begin xs:=X; p:=CONV_STD_LOGIC_VECTOR(n-1, m); for i in 1 to n loop exit when xs(n-1)='1'; xs:=xs(n-2 downto 0)&'0'; p:=unsigned(p)-1; end loop; xl:=xs(n-1 downto n-q-1); for i in 1 to q loop xq:=UNSIGNED(xl)*UNSIGNED(xl); if xq(2*q+1)='1' then yl(q-i):='1'; xl:=xq(2*q+1 downto q+1); else yl(q-i):='0'; xl:=xq(2*q downto q); end if; end loop; Y<=p&yl; end process; end LOGPRECISE;
Этот алгоритм может быть странслирован в логическую схему. Но для добавления q точных разрядов будут синтезированы q последовательно включенных умножителей.
Так, схема для вычисления 12 - разрядного логарифма от 16 - разрядного целого имеет затраты 1100 LUT и задержку 200 нс. Если алгоритм преобразовать так, чтоб он выполнялся на одном регистре сдвига, счетчике и умножителе за n+q итераций, то аппаратурные затраты уменьшатся в q раз.
В отличие от других алгоритмов вычисления логарифма, данный алгоритм не содержит никаких констант, кроме начального состояния переменной p.
Кроме того, количество точных разрядов результата m+q < n ничем не ограничено, так что алгоритм может быть использован при вычислениях со сверхвысокой точностью.
В приложении 2 представлен этот же алгоритм, реализованный как функция LOG2 для целых аргументов. Эта функция оформлена в пакете, который может быть помещен в рабочую библиотеку. Так что пользователь может в любом месте использовать вызов этой функции в своих программах, не помещая тело функции в описание архитектуры. Функция LOG2 дает 31 точный двоичный разряд результата, не считая ошибки в младшем разряде, что по крайней мере точнее, чем при вычислениях с плавающей запятой с одинарной точностью.
Конечно, проще всего использовать пакет MATH_REAL из библиотеки IEEE и сослаться на его функцию:
function LOG2(X: in REAL) return REAL;
но большинство дискретных моделей работает с целыми числами, а не с вещественными и для вычисления логарифма необходимо подключать пакет MATH_REAL и преобразовывать формат целых чисел в формат с плавающей запятой и обратно. Так что использование предлагаемой функции логарифма для целых чисел может быть удобнее.
Литература
1. Благовещенский Ю.В., Теслер Г.С. Вычисление элементарных функций на ЭВМ. Киев: Техніка. -1977. - 207с.
Приложение 1.
--^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -- FILE: LOG2.VHD -- PROJECT: Logarithmic Function Unit -- AUTHOR: Anatoli Sergyienko -- Email: aser@comsys.kpi.ua -- -- FUNCTION: - synthesable model for -- calculating the binary logarithm function -- from the positive integer data. -- CONSTRAINTS: the input data X is in range: -- 1 to 2**n-1, -- generics 2**(m-1)log<="0000"; when "0001" => log<="0010"; when "0010" => log<="0011"; when "0011" => log<="0100"; when "0100" => log<="0101"; when "0101" => log<="0111"; when "0110" => log<="1000"; when "0111" => log<="1001"; when "1000" => log<="1010"; when "1001" => log<="1011"; when "1010" => log<="1011"; when "1011" => log<="1100"; when "1100" => log<="1101"; when "1101" => log<="1110"; when "1110" => log<="1111"; when "1111" => log<="1111"; when others => end case; end process; RGS:process(CLK,log,yh) begin if CLK='1' and CLK'event then Y<=yh & log; xi<=X; end if; end process; end DSPLOG;
Приложение 2.
--^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -- FILE: LOG_FUNC.VHD -- PROJECT: Logarithmic function -- AUTHOR: Anatoli Sergyienko -- Email: aser@comsys.kpi.ua -- -- FUNCTION: - calculating the binary logarithmic -- function from the positive integer data. -- CONSTRAINTS: the input data X is in range: -- 1 to 2**31-1, -- the output data is equal to 2**25*log2(X), -- when X= 0 then the result is 0, -- and error report is given. -- Maximum error is equal to 1 l.s.b. --^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ library IEEE; use IEEE.std_logic_1164.all; use IEEE.std_logic_arith.all; package LOG_FUNC is function LOG2(X: INTEGER) return INTEGER; end LOG_FUNC; library IEEE; use IEEE.std_logic_1164.all; use IEEE.std_logic_arith.all; package body LOG_FUNC is function LOG2(x: INTEGER) return INTEGER is variable xs: STD_LOGIC_VECTOR (30 downto 0); variable xp: STD_LOGIC_VECTOR (26 downto 0); variable xq: STD_LOGIC_VECTOR (53 downto 0); variable p: STD_LOGIC_VECTOR (4 downto 0); variable yp: STD_LOGIC_VECTOR (25 downto 0); begin assert not(x=0) report "operand has value 0" severity ERROR; xs:=CONV_STD_LOGIC_VECTOR(x, 31); yt:="11110"; for i in 1 to 31 loop exit when xs(30)='1'; xs:=xs(29 downto 0)&'0'; p:=unsigned(p)-1; end loop; xp:=xs(30 downto 4); for i in 1 to 26 loop xq:=UNSIGNED(xp) * UNSIGNED(xp); if xq(53)='1' then yp(26-i):='1'; xp:=xq(53 downto 27); else yp(26-i):='0'; xp:=xq(52 downto 26); end if; end loop; if x=0 then return 0; else return CONV_INTEGER(UNSIGNED(p & yp)); end if; end; end package body;