Автор: quatoa
Вычисление двоичного логарифма итерационным методом на ПЛИС¶
Вниманию читателя предлагается алгоритм вычисления логарифма по основанию 2, предоставляется исходный код RTL-блока, анализируется вычислительная точность и ресурсоемкость реализации.
В математических вычислениях и цифровой обработке сигналов на ПЛИС часто приходится прибегать к вычислению логарифма. Например, для преобразования мощности из мВт в дБм требуется вычисление логарифма по основанию 10:
Вычисление логарифма является достаточно актуальной задачей, разработчики применяю разные методы вычисления [1]:
- табличный метод;
- итерационный метод;
- CORDIC метод;
- метод на основе рядов Тейлора.
Методы различаются точность, сложностью реализации и задержкой вычисления. Вниманию предлагается реализация итерационного метода вычисления логарифма по основанию 2 с фиксированной точкой. Вычисление \(log_2(X)\) проще в реализации, чем вычисление натурального \(ln(X)\) или десятичного \(log_{10}(X)\). А преобразовать к логарифму с нужным основанием всегда можно всего через одно умножение: \(log_AB=log_CB ⁄ log_CA\) .
В статье описывается алгоритм вычисления с примерами, предоставляется исходный код RTL-блока, анализируется вычислительная точность и ресурсоемкость реализации.
Алгоритм¶
Представим входное значение \(X\) через мантиссу \(m\) и экспоненту \(e\) следующим образом:
Логарифм от \(X\) может быть представлен, как сумма целой \(e\) и дробной \(log_2m\) составляющих
Тогда алгоритм вычисления можно представить блок-схемой на рисунке 1. Сначала параллельно вычисляется экспонента \(e\) и мантисса \(m\), затем из мантиссы \(m\) логарифмическим преобразованием находится \(L = log_2(m)\), и в конце результаты суммируются.
Вычисление экспоненты \(e\) и мантиссы \(m\) можно представить блок-схемой на рисунке 2. Процесс вычисления \(e\) и \(m\) выполняется следующим образом:
- Определяется диапазон входного значения \(Х\) – больше 1 или нет.
- Если больше 1, то \(Х\) итерационно делится на 2 до тех пор, пока не - станет меньше 2. Число итераций непосредственно определяет - экспоненту \(e\). Если меньше 1, то \(Х\) итерационно умножается на 2 до - тех пор, пока не станет больше 1. Число итераций определяет - экспоненту \(e\) с инверсией знака.
- Мантисса \(m\) является \(X\) после итераций умножения или деления. Получив мантиссу \(m\) можно вычислить \(log_2(m)\) посредством логарифмического преобразования в соответствии с блок-схемой на рисунке 3. Процесс логарифмического преобразования выполняется следующим образом:
Запускается итерационный процесс с фиксированным числом шагов, равным требуемой разрядностью дробной части. На каждой итерации мантисса \(m\) возводится в квадрат. Если мантисса \(m\) больше или равна 2, то \(m\) делится на 2 и к результату \(L\) суммируется \(2^{-i}\), где \(i\) – номер итерации; иначе - ничего. По окончании итерационного процесса результатом логарифмического преобразования будет \(L = log_2(m)\).

Рисунок 1 – Блок-схема вычисления log2(x)

Рисунок 2 – Блок-схема вычисления экспоненты и мантиссы

Рисунок 3 – Блок-схема логарифмического преобразования
Пример №1¶
Дано:¶
- Входное значение X = 10,23456789
- Точность (разрядность дробной части) – 10 бит.
Решение:¶
-
Вычисление \(e\) и \(m\) Поскольку \(X > 1\), значит производится деление. \(e = 0\).
Итерация \(i\) Входное \(X\) Экспонента \(e\), мантисса \(m\) 1 \(X = X/2 = 10,23456789/2 = 5,1172>2\) \(e = e+1 = 1\) 2 \(X = X/2 = 2,5586 > 2\) \(e = e + 1 = 2\) 3 \(X = X/2 =1,2793 < 2\) \(e = e + 1 = 3\) \(m = 1,2793\) Экспонента \(e = 3\).
Мантисса \(m = 1,2793\).
-
Логарифмическое преобразование
Итерация \(i\) Входное \(X\) Экспонента \(e\), мантисса \(m\) 1 \(m = m^2 = 1,2793^2=1,6366<2\) \(L = 0\) 2 \(m = m^2 = 2,6786>2\) \(m=m/2=1,3393\) \(L = L+2^{-i}=0+2^{-2}=0,25\) 3 \(m = m^2 = 1,7938<2\) \(L = 0,25\) 4 \(m = m^2 = 3,2177 >2\) \(m=m/2=1,6088\) \(L = L+2^{-4}=0,3125\) 5 \(m = m^2 = 2,5884 >2\) \(m=m/2=1,2942\) \(L = L+2^{-5}=0.34375\) 6 \(m = m^2 = 1,6750 <2\) \(L = 0,34375\) 7 \(m = m^2 = 2,8058 >2\) \(m=m/2=1,4029\) \(L = L+2^{-7}=0,3515625\) 8 \(m = m^2 = 1,9681 <2\) \(L = 0,3515625\) 9 \(m = m^2 = 3,8736 > 2\) \(m=m/2=1,9368\) \(L = L+2^{-9}=0,353515625\) 10 \(m = m^2 = 3,7512 >2\) \(m=m/2=1,8756\) \(L = L+2^{-10}=0,3544921875\) \[ L=log_2m=0,3544921875 \] -
Сумма
Результатом \(log_2(X)\) итерационным методом является сумма экспоненты \(e\) и \(L\)
\[ Y = log_2X = e + L = 3+ 0,3544921875 = 3,3544921875 \]Погрешность \(err = |log_2X_{true} - log_2X_{approx} | = 0,000886\), но про нее будет далее.
Пример №2¶
Дано:¶
- Входное значение \(X = 0,0666\)
- Точность (разрядность дробной части) – 12 бит.
Решение:¶
-
Вычисление \(e\) и \(m\)
Поскольку \(X < 1\), значит производится умножение. \(e = 0\).
Цикл:
Итерация \(i\) Входное \(X\) Экспонента \(e\), мантисса \(m\) 1 \(X = X*2 = 0,666*2 = 0,1332<1\) \(e = e-1 = -1\) 2 \(X = X*2 = 0,2664<1\) \(e = e - 1 = -2\) 3 \(X = X*2 =0,5328<1\) \(e = e - 1 = -3\) 4 \(X = X*2 =1,0656>1\), значит конец цикла \(e = e - 1 = -4\) \(m = 1,0656\) Экспонента \(e = -4\). Мантисса \(m = 1,0656\).
-
Логарифмическое преобразование
Цикл из 12 итераций. \(L = 0\).
Итерация i Мантисса \(m\) Результат \(L\) 1 \(m = m^2 = 1{,}0656^2 = 1{,}1355 < 2\) $\(L = 0\)$ 2 \(m = m^2 = 1{,}2893 < 2\) $\(L = 0\)$ 3 \(m = m^2 = 1{,}6624 < 2\) $\(L = 0\)$ 4 \(m = m^2 = 2{,}7638 > 2\) \(m = m/2 = 1{,}3819\) $\(L = L + 2^{-4} = 0{,}0625\)$ 5 \(m = m^2 = 1{,}9096 < 2\) $\(L = 0{,}0625\)$ 6 \(m = m^2 = 3{,}6467 > 2\) \(m = m/2 = 1{,}8233\) $\(L = L + 2^{-6} = 0{,}078125\)$ 7 \(m = m^2 = 3{,}3247 > 2\) \(m = m/2 = 1{,}6623\) $\(L = L + 2^{-7} = 0{,}0859375\)$ 8 \(m = m^2 = 2{,}7634 > 2\) \(m = m/2 = 1{,}3817\) $\(L = L + 2^{-8} = 0{,}08984375\)$ 9 \(m = m^2 = 1{,}9092 < 2\)$ $\(L = 0{,}08984375\)$ 10 \(m = m^2 = 3{,}6451 > 2\) \(m = m/2 = 1{,}8225\) $\(L = L + 2^{-10} = 0{,}0908203125\)$ 11 \(m = m^2 = 3{,}3217 > 2\) \(m = m/2 = 1{,}6608\) $\(L = L + 2^{-11} = 0{,}09130859375\)$ 12 \(m = m^2 = 2{,}7585 > 2\) \(m = m/2 = 1{,}6608\) $\(L = L + 2^{-12} = 0{,}091552734375\)$ \[ L=log_2m=0,091552734375 \] -
Сумма
Результатом \(log_2(X)\) есть сумма экспоненты \(e\) и \(L\)
\[ Y = log_2X = e + L = -4 + 0,091552734375 = -3,908447265625 \]Погрешность \(err = |log_2X_{true} - log_2X_{approx} | = 0,000113\), но про нее будет далее.
Реализация¶
На основе представленного итерационного алгоритма разработан RTL-блок log2Calc.vhd и тестовый стенд tb_log2Calc на языке VHDL. Параметры и порты описаны в таблицах 1 и 2.
Таблица 1 – Входные параметры блока log2Calc.vhd
| Название | Тип | Описание |
|---|---|---|
DW_IN |
natural |
Разрядность входного вектора данных |
DW_IN_FRAC |
natural |
Разрядность дробной части входного вектора данных, она же точность |
DW_OUT |
natural |
Разрядность выходного вектора данных |
DW_OUT_FRAC |
natural |
Разрядность дробной части выходного вектора данных, она же точность |
Параметры DW_IN, DW_IN_FRAC и DW_OUT_FRAC задаются пользователем, а разрядность выходного вектора DW_OUT задается функцией calc_dw из log2Calc_pkg на основе указанные выше трех параметров. RTL-блок не работает в конвейерном режиме, т.е. для вычисления одного значения требуется некоторое количество тактов, которое определяется функцией calc_latency из log2Calc_pkg.
Таблица 2 – Входные и выходные порты блока log2Calc.vhd
| Название | Тип | Направление | Описание |
|---|---|---|---|
clk |
std_logic |
Вход | Тактовый сигнал |
rst |
std_logic |
Вход | Асинхронный сброс |
valid_i |
std_logic |
Вход | Сигнал готовности входных данных |
data_i |
std_logic_vector(DW_IN - 1 downto 0) |
Вход | Входные данные |
rdy_o |
std_logic |
Выход | Сигнал готовности приема входных данных |
valid_o |
std_logic |
Выход | Сигнал готовности выходных данных |
data_o |
std_logic_vector (DW_OUT - 1 downto 0) |
Выход | Выходные данные |
В тестовом стенде в соответствии со структурной схемой на рисунке 4 выполняются два процесса:
- процесс
input_procвыполняет формирование входных данных на тестируемый блок с полным перебором входных значенийXstd; -
процесс
output_procполучает выходные данныеYstdиз тестируемого блока и эталонное значениеYin, вычисляет погрешность. Точность вычисления оценивается на основе: -
абсолютная погрешность
- относительная погрешность
- относительная погрешность в дБ
где \(Y_{in}\) – ожидаемый результат в формате real, \(Y_{out}\) – вычисленный результат в формате real, полученный из вектора \(Y_{std}\).

Рисунок 4 – Структурная схема тестового стенда
Пример №3¶
DW_IN = 12, DW_IN_FRAC = 10, DW_OUT_FRAC = 14, что соответствует формату входных данных ufix12_10 и выходных fixX_14.
На рисунке 5 изображен результат вычисления RTL-блоком log2Calc.vhd, который представляет собой график \(Y = log_2(X)\). Поскольку входные данные представлены в формате ufix12_10, то они принимают значения от 0 до 4 с точностью \(2^{-10}\).

Рисунок 5 – График вычисленной функции Y = log2(X)
На рисунке 6 изображены графики трех видов рассматриваемых погрешностей. Наиболее информативной является относительная погрешность в дБ, на основе ее и будет оцениваться точность RTL-блока. Как видно из графика (см. рисунок 7) погрешность вычисления значительно возрастает в близи входного значения \(X = 1\). Это объясняется тем, что \(log_2(\sim 1)\approx 0\), т.е. возрастает отношение погрешности \(|Y_{in}-Y_{out}|\) к ожидаемому значению \(Y_{in}\) из-за того, что погрешность в целом постоянная (см. первый график на рисунке 6), а \(Y_{in}\) мала. Максимальная относительная погрешность на рисунке 7 составляет -27,2 дБ в окрестности входного значения \(Х = 1\).
Помимо этого, наблюдается возрастание погрешности при входных значениях больше \(Х > 2\). Это объявляется тем, что при \(X > 2\) начинает отрабатывать вычисление экспоненты и, вероятно, это вычисление вносит свою дополнительную погрешность.

Рисунок 6 – График абсолютной, относительной и относительной в дБ погрешности

Рисунок 7 – График относительной погрешности в дБ
Точность¶
Проведен сравнительный анализ погрешности при разной точности входных и выходных данных, результаты представлены в таблице 3. Из таблицы можно сделать вывод, что сравнительно малая погрешность (-27 дБ) обеспечивается, когда точность выхода больше точности входа минимум на 4 разряда. Помимо этого, не рекомендуется устанавливать входную точность менее 10 разрядов.
Таблица 3 – Сравнение относительной погрешности (дБ) при разной точности входных и выходных данных
| Формат выходных данных | вход в формате ufix12_10 |
вход в формате ufix14_12 |
вход в формате ufix16_14 |
|---|---|---|---|
| fixX_10 | 0* | -** | - |
| fixX_12 | -15,5 | 0 | - |
| fixX_14 | -27,2 | -15,6 | 0 |
| fixX_16 | -39,3 | -27,2 | -15,6 |
| fixX_18 | -51,3 | -39,3 | -27,2 |
| fixX_20 | -63,1 | -51,3 | -39,3 |
| fixX_22 | -63,1 | -63,4 | -51,3 |
() – 0 дБ означает, что амплитуда абсолютной погрешности равна или сопоставима с амплитудой вычисленного значения (*) – абсолютная погрешность больше вычисленного значения, не рабочая конфигурация
Ресурсоемкость¶
Ресурсоемкость RTL-блока в зависимости от параметров представлена ниже в таблицах 4. Сравнение проводилось на ПЛИС Xilinx Artix-7 в среде разработки Vivado 2018.3. RTL-блок не использует блочную память BRAM. Из приведенных результатов ресурсоемкости можно заметить, что при точности выходных данных более 20 бит снижается количество требуемых LUT и добавляется одно DSP48E1. Это объясняется тем, что некоторые операций на LUT при возросшей разрядности синтезатор считает, что оптимальнее выполнять на DSP48E1.
Таблица 4 – Сравнение ресурсоемкости
| Формат выходных данных | вход в формате ufix12_10 (LUT/FF/DSP) |
вход в формате ufix14_12 (LUT/FF/DSP) |
вход в формате ufix16_14 (LUT/FF/DSP) |
|---|---|---|---|
| fixX_10 | 102/90/1 | 110/94/1 | 118/98/1 |
| fixX_12 | 108/96/1 | 116/98/1 | 122/102/1 |
| fixX_14 | 112/100/1 | 117/102/1 | 123/104/1 |
| fixX_16 | 122/109/1 | 128/111/1 | 133/113/1 |
| fixX_18 | 185/115/1 | 190/117/1 | 194/119/1 |
| fixX_20 | 136/121/2 | 135/123/2 | 142/125/2 |
| fixX_22 | 141/127/2 | 145/129/2 | 151/131/2 |
Вывод¶
Представленный RTL-блок вычисления двоичного логарифма в формате точности с фиксированной точкой допустимо применять в разработке ПО под ПЛИС. Рекомендуется, чтобы точность выходных данных была больше входных минимум на 4 разряда. RTL-блок является кроссплатформенным.
Литература¶
[1] A New Hardware Implementation of Base 2 Logarithm for FPGA - A.M.Mansour - 2015
Код модуля и тестбенч¶
----====================================================================================================================
----=========== PKG
----====================================================================================================================
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
use ieee.math_real.all;
package log2Calc_pkg is
-- расчет выходной разрядности
function calc_dw(
dw_in : natural;
dw_in_frac : natural;
dw_out_frac : natural
) return natural;
-- определение задержки
function calc_latency(
dw_out : natural;
dw_out_frac : natural
) return natural;
end package log2Calc_pkg;
package body log2Calc_pkg is
----------------------------------------------------------------
-- расчет выходной разрядности
----------------------------------------------------------------
function calc_dw(
dw_in : natural;
dw_in_frac : natural;
dw_out_frac : natural
) return natural is
variable dw_in_int : natural;
variable dw_out_int : natural;
variable value : natural;
begin
dw_in_int := dw_in - dw_in_frac;
if (dw_in_int >= dw_in_frac) then
dw_out_int := natural(ceil(log2(real(dw_in_int))));
else
dw_out_int := natural(ceil(log2(real(dw_in_frac))));
end if;
value := dw_out_int + 1 + dw_out_frac;
return value;
end function calc_dw;
----------------------------------------------------------------
-- определение задержки
----------------------------------------------------------------
function calc_latency(
dw_out : natural;
dw_out_frac : natural
) return natural is
variable dw_out_int : natural;
variable value : natural;
begin
dw_out_int := dw_out - dw_out_frac;
value := 2 ** (dw_out_int - 1) + (dw_out_frac + 1) + 2;
return value;
end function calc_latency;
end package body log2Calc_pkg;
----====================================================================================================================
----=========== ENTITY
----====================================================================================================================
LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.numeric_std.all;
use work.log2Calc_pkg.all;
ENTITY log2Calc IS
generic(
DW_IN : natural := 16;
DW_IN_FRAC : natural := 14;
DW_OUT_FRAC : natural := 22;
DW_OUT : natural := work.log2Calc_pkg.calc_dw(16, 14, 22)
);
PORT(
clk : in std_logic;
rst : in std_logic;
valid_i : in std_logic;
data_i : in std_logic_vector(DW_IN - 1 downto 0); -- unsigned
rdy_o : out std_logic;
valid_o : out std_logic;
data_o : out std_logic_vector(DW_OUT - 1 downto 0) -- signed
);
END log2Calc;
ARCHITECTURE rtl OF log2Calc IS
constant DW_IN_INT : natural := DW_IN - DW_IN_FRAC;
constant DW_OUT_INT : natural := DW_OUT - DW_OUT_FRAC;
type state_t is (IDLE,
EXPONENT,
MANTISSA,
RESULT
);
signal STATE : state_t;
signal zero : std_logic;
signal order : boolean; -- true - [1,..) / false - (0, 1)
constant LEN_CNT_EXP : natural := 2 ** (DW_OUT_INT - 1) + 1;
signal cnt_exp : integer range 0 to LEN_CNT_EXP - 1;
signal buf : integer range 0 to LEN_CNT_EXP - 1;
constant STD_1 : unsigned(DW_IN downto 0) := to_unsigned(integer(1.0 * real(2 ** DW_IN_FRAC)), DW_IN + 1); -- ровно 1
constant STD_2 : unsigned(DW_IN downto 0) := to_unsigned(integer(2.0 * real(2 ** DW_IN_FRAC)), DW_IN + 1); -- ровно 2
signal shift : unsigned(DW_IN downto 0);
signal exp_rdy : std_logic;
signal exp_sh : signed(DW_OUT - 1 downto 0); -- значение целой части без дробной
signal exp : signed(DW_OUT - 1 downto 0); -- значение целой части с учетом выделения бит под дробную (дробная по нулям)
function max_nat(a : natural;
b : natural
)
return natural is
begin
if (a > b) then
return a;
else
return b;
end if;
end function max_nat;
constant DW_NORM_FRAC : natural := max_nat(DW_IN_FRAC, DW_OUT_FRAC);
constant DW_NORM : natural := DW_NORM_FRAC + 2; -- [0,4)
signal norm : unsigned(DW_NORM - 1 downto 0);
constant STD2_NORM : unsigned(DW_NORM - 1 downto 0) := to_unsigned(integer(2.0 * real(2 ** DW_NORM_FRAC)), DW_NORM); -- ровно 2
signal cnt_mant : integer range 0 to DW_OUT_FRAC;
signal mant : std_logic_vector(DW_OUT - 1 downto 0);
begin
log2_proc : process(clk, rst)
variable shift2_v : unsigned(DW_NORM - 1 downto 0);
variable mult2_v : unsigned(2 * DW_NORM - 1 downto 0);
begin
if (rst = '1') then
STATE <= IDLE;
zero <= '0';
order <= true;
cnt_exp <= 0;
buf <= 0;
exp_rdy <= '0';
exp_sh <= (others => '0');
exp <= (others => '0');
shift <= (others => '0');
norm <= (others => '0');
cnt_mant <= 0;
mant <= (others => '0');
rdy_o <= '0';
valid_o <= '0';
data_o <= (others => '0');
elsif rising_edge(clk) then
case STATE is
--====================================================================================================================
--=========== ожидание, определение больше 1 или нет
--====================================================================================================================
when IDLE =>
valid_o <= '0';
if (valid_i = '1') then
if (unsigned(data_i) = 0) then
zero <= '1';
else
zero <= '0';
end if;
rdy_o <= '0';
buf <= 0;
cnt_exp <= 1;
shift <= unsigned('0' & data_i);
exp_rdy <= '0';
exp_sh <= (others => '0');
norm <= (others => '0');
cnt_mant <= 0;
mant <= (others => '0');
if (unsigned('0' & data_i) < STD_1) then -- в диапазоне (0,1)
order <= false;
else -- в диапазоне [1,..)
order <= true;
end if;
STATE <= EXPONENT;
else
rdy_o <= '1';
end if;
--====================================================================================================================
--=========== вычисление целой части
--====================================================================================================================
when EXPONENT =>
if (cnt_exp = LEN_CNT_EXP - 1) then
cnt_exp <= 0;
STATE <= MANTISSA;
else
cnt_exp <= cnt_exp + 1;
end if;
buf <= cnt_exp;
if (order) then -- больше 1 [1,..)
-- смещаем вправо (делим на 2) до тех пор, пока не будет меньше 2
shift <= shift_right(shift, 1); -- /2
if (shift <= STD_2 and exp_rdy = '0') then
exp_rdy <= '1';
exp_sh <= resize(to_signed(buf, DW_OUT_INT), DW_OUT);
if (DW_IN_FRAC = DW_NORM_FRAC) then
norm <= unsigned(shift((DW_IN_FRAC + 2) - 1 downto 0));
else
norm(DW_NORM - 1 downto DW_NORM - (DW_IN_FRAC + 2)) <= unsigned(shift((DW_IN_FRAC + 2) - 1 downto 0));
norm(DW_NORM - (DW_IN_FRAC + 2) - 1 downto 0) <= (others => '0');
end if;
end if;
else -- меньше 1 (0, 1)
-- смещаем влево (умножаем на 2) до тех порт, пока не будет больше 1
shift <= shift_left(shift, 1); -- *2
if (shift >= STD_1 and exp_rdy = '0') then
exp_rdy <= '1';
exp_sh <= resize(-to_signed(buf, DW_OUT_INT), DW_OUT);
if (DW_IN_FRAC = DW_NORM_FRAC) then
norm <= unsigned(shift((DW_IN_FRAC + 2) - 1 downto 0));
else
norm(DW_NORM - 1 downto DW_NORM - (DW_IN_FRAC + 2)) <= unsigned(shift((DW_IN_FRAC + 2) - 1 downto 0));
norm(DW_NORM - (DW_IN_FRAC + 2) - 1 downto 0) <= (others => '0');
end if;
end if;
end if;
--====================================================================================================================
--=========== вычисление дробной части
--====================================================================================================================
when MANTISSA =>
if (cnt_mant = DW_OUT_FRAC) then
cnt_mant <= 0;
-- overflow
if (exp_rdy = '1') then
exp <= shift_left(exp_sh, DW_OUT_FRAC);
else
exp <= to_signed(integer(-(DW_IN_INT - 1) * 2 ** (DW_OUT_FRAC)), DW_OUT);
end if;
rdy_o <= '1';
STATE <= RESULT;
else
cnt_mant <= cnt_mant + 1;
end if;
if (norm >= STD2_NORM) then
mant(DW_OUT_FRAC - cnt_mant) <= '1';
shift2_v := shift_right(norm, 1);
mult2_v := shift2_v * shift2_v;
else
mult2_v := norm * norm;
end if;
norm <= unsigned(mult2_v(DW_NORM - 1 + DW_NORM_FRAC downto DW_NORM_FRAC));
--====================================================================================================================
--=========== суммирование целой и дробной частей
--====================================================================================================================
when RESULT =>
valid_o <= '1';
if (zero = '1') then -- если вход нуль, то выдаем минимально возможное значение
data_o(DW_OUT - 1) <= '1';
data_o(DW_OUT - 2 downto 0) <= (others => '0');
else
data_o <= std_logic_vector(exp + signed(mant));
end if;
exp_rdy <= '0';
exp_sh <= (others => '0');
norm <= (others => '0');
mant <= (others => '0');
STATE <= IDLE;
end case;
end if;
end process;
END rtl;
Тестбенч:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
USE IEEE.NUMERIC_STD.all;
use ieee.math_real.all;
use std.textio.all;
use std.env.all;
entity tb_log2Calc is
generic(
DW_IN : natural := 12;
DW_IN_FRAC : natural := 10;
DW_OUT_FRAC : natural := 10
);
end entity;
architecture beh of tb_log2Calc is
procedure clk_wait(
signal clk : in std_logic;
constant th : integer
) is
begin
for i in 0 to th - 1 loop
wait until rising_edge(clk);
end loop;
end procedure clk_wait;
-- переводит число типа real в std_logic_vector длины dw и точности dw_frac
function real2std(data : real;
dw : integer;
dw_frac : integer;
sign : boolean
) return std_logic_vector is
begin
if (sign) then
return std_logic_vector(to_signed(integer(data * real(2 ** dw_frac)), dw));
else
return std_logic_vector(to_unsigned(integer(data * real(2 ** dw_frac)), dw));
end if;
end;
-- переводит число типа std_logic_vector длины dw и точности dw_frac в real
function std2real(data : std_logic_vector;
dw_frac : integer;
sign : boolean
) return real is
begin
if (sign) then
return real(to_integer(signed(data))) / real(2 ** dw_frac);
else
return real(to_integer(unsigned(data))) / real(2 ** dw_frac);
end if;
end;
--====================================================================================================================
--=========== вычисление целой части y = log2(x)
--====================================================================================================================
constant DW_IN_INT : natural := DW_IN - DW_IN_FRAC; -- разрядность целой части входных даннных
constant DW_OUT : natural := work.log2Calc_pkg.calc_dw(DW_IN, DW_IN_FRAC, DW_OUT_FRAC); -- разрядность выходных данных
constant LATENCY : natural := work.log2Calc_pkg.calc_latency(DW_OUT, DW_OUT_FRAC); -- вычислительная задержка блока
--====================================================================================================================
--=========== сигналы
--====================================================================================================================
constant PERIOD : time := 10 us;
signal clk : std_logic := '0';
signal rst : std_logic := '1';
signal en : std_logic;
-- signal cnt : integer range 0 to NUM_DATA - 1;
signal y_in : real := 0.0;
signal y_in_std : std_logic_vector(DW_OUT - 1 downto 0) := (others => '0');
signal x_valid : std_logic := '0';
signal x_std : std_logic_vector(DW_IN - 1 downto 0) := (others => '0');
constant ONES : std_logic_vector(DW_IN - 1 downto 0) := (others => '1');
signal x : real := 0.0;
signal rdy_o : std_logic;
signal rdy_z : std_logic;
signal y_out_valid : std_logic;
signal y_out_std : std_logic_vector(DW_OUT - 1 downto 0);
signal y_out : real := 0.0;
constant DW_ERR : natural := 32;
constant DW_ERR_FRAC : natural := 20;
signal err_abs : real := 0.0;
signal err_relative : real := 0.0;
signal err_relative_dB : real := 0.0;
signal err_abs_std : std_logic_vector(DW_ERR - 1 downto 0) := (others => '0');
signal err_relative_std : std_logic_vector(DW_ERR - 1 downto 0) := (others => '0');
signal err_relative_dB_std : std_logic_vector(DW_ERR - 1 downto 0) := (others => '0');
begin
-- clk, rst
proc_clk : process
begin
wait for PERIOD / 2;
clk <= not clk;
end process;
proc_rst : process
begin
rst <= '1';
wait for 15.2 * PERIOD;
rst <= '0';
wait;
end process;
--====================================================================================================================
--=========== формирование данных на блок
--====================================================================================================================
input_proc : process(clk, rst)
begin
if (rst = '1') then
en <= '1';
x_valid <= '0';
x_std <= (others => '0');
elsif rising_edge(clk) then
rdy_z <= rdy_o;
if (en = '1') then
if (rdy_z = '0' and rdy_o = '1') then
if (x_std = ONES) then
en <= '0';
x_valid <= '0';
else
x_std <= std_logic_vector(unsigned(x_std) + 1);
x_valid <= '1';
end if;
else
x_valid <= '0';
end if;
end if;
y_in <= log2(x);
end if;
end process;
x <= std2real(x_std, DW_IN_FRAC, false);
y_in_std <= real2std(y_in, DW_OUT, DW_OUT_FRAC, false);
--====================================================================================================================
--=========== тестируемый блок
--====================================================================================================================
inst_log2 : entity work.log2Calc
generic map(
DW_IN => DW_IN,
DW_IN_FRAC => DW_IN_FRAC,
DW_OUT_FRAC => DW_OUT_FRAC,
DW_OUT => DW_OUT
)
port map(
clk => clk,
rst => rst,
valid_i => x_valid,
data_i => x_std,
rdy_o => rdy_o,
valid_o => y_out_valid,
data_o => y_out_std
);
--====================================================================================================================
--=========== сравнение вычисленного с ожидаемым, вывод погрешности вычисления
--====================================================================================================================
y_out <= std2real(y_out_std, DW_OUT_FRAC, true);
output_proc : process(clk, rst) is
variable err_abs_v : real;
variable err_rel_v : real;
variable err_rel_dB_v : real;
begin
if (rst = '1') then
elsif rising_edge(clk) then
err_abs_v := y_in - y_out;
if (y_in = 0.0) then
err_rel_v := 0.0;
else
err_rel_v := abs (err_abs_v / y_in);
end if;
err_rel_dB_v := 20.0 * log10(err_rel_v);
if (y_out_valid = '1') then
err_abs <= err_abs_v;
err_relative <= err_rel_v;
err_relative_dB <= err_rel_dB_v;
err_abs_std <= real2std(err_abs_v, DW_ERR, DW_ERR_FRAC, true);
err_relative_std <= real2std(err_rel_v, DW_ERR, DW_ERR_FRAC, true);
if (err_rel_v = 0.0) then
err_relative_dB_std <= err_relative_dB_std;
else
err_relative_dB_std <= real2std(err_rel_dB_v, DW_ERR, DW_ERR_FRAC, true);
end if;
end if;
end if;
end process output_proc;
proc_check_end : process is
begin
wait until (x_std = ONES and en = '0');
clk_wait(clk, 200);
report "END SIM" severity note;
finish(0);
end process proc_check_end;
end architecture;