Язык описания аппаратуры Verilog HDL очень часто используется для проектирования аппаратуры с интенсивными математическими вычислениями. Алгоритмы БПФ (Быстрое Преобразование Фурье), ДКП (Дискретное Косинусное Преобразование), обработка и фильтрация сигналов, нейронные сети, геометрические 3D построения – все это зачастую требует реализации тригонометрических функций (синус, косинус, тангенс) в «кремнии».
Реализация тригонометрии в аппаратуре – очень непростое занятие. Стандарт Verilog поддерживает тип real для симуляции, но не поддерживает его для синтеза. Это значит, что не то, что синус или косинус, но даже базовые математические операции вроде сложения, вычитания или умножения для реальных чисел придется каким-то образом реализовывать с помощью двоичной логики и регистров reg.
Написать «универсальную библиотеку» тригонометрических функций на Verilog HDL врядли целесообразно, так как реализация может сильно зависить от поставленной технической задачи и архитектуры конвейера вычислителя. Очень многое зависит от требуемой точности вычислений, разрешенного количества тактов на вычисление, способа представления чисел в аппаратуре (плавающая точка или фиксированная точка), применяемой системы счисления и так далее.
По этой причине в этой статье я не буду собственно реализовывать синус на Verilog для синтеза в FPGA / CPLD. Вместо этого, я постараюсь предложить некий Verilog testbench для проверки правильности реализации тригонометрической функции «Синус» и для генерации синусоидального сигнала.
Обычно сложные функции апроксимируются на заданном интервале степенными многочленами. В википедии есть статья «Ряд Тейлора». Там же можно посмотреть и формулу многочлена для синуса или косинуса:
Собственно вот эта формула и служит отправной точкой для реализации функции синуса на Verilog HDL:
function real sin;
input x;
real x;
real x1,y,y2,y3,y5,y7,sum,sign;
begin
sign = 1.0;
x1 = x;
if (x1<0)
begin
x1 = -x1;
sign = -1.0;
end
while (x1 > 3.14159265/2.0)
begin
x1 = x1 - 3.14159265;
sign = -1.0*sign;
end
y = x1*2/3.14159265;
y2 = y*y;
y3 = y*y2;
y5 = y3*y2;
y7 = y5*y2;
sum = 1.570794*y - 0.645962*y3 +
0.079692*y5 - 0.004681712*y7;
sin = sign*sum;
end
endfunction
Эта функция работает в диапазоне входных значений -Pi/2 < x < Pi/2, где Pi=3,14159265..
Для диапазонов входных значений функции вне этого интервала используется свойство периодичности функции синус – по формуле приведения в цикле while() входное значение x возвращается в базовый интервал. Вообще-то при этом может потеряться точность вычислений, это нужно учитывать при симуляции проектов.
Попробуем генерировать синусоидальный сигнал этой функцией в тестбенче.
Предположим, что нужно синтезировать синусоиду заданной частоты freq. В тестбенче объявим переменную с таким названием: integer freq. Присваивая этой переменной разные значения можно будет менять частоту нашей результирующей синусоиды.
Имеется базовая частота clk равная 10Мгц. Базовая частота clk должна быть много больше, чем желаемая частота синтезируемой синусоиды freq.
reg clk;
initial clk=0;
always
#0.05 clk = ~clk;
Используя эту частоту clk синтезируем цифровой сигнал cnt_edge с частотой, примерно равной freq*64 (полагаем, что на период синусоиды будет приходиться 64 ее выборки):
reg [31:0]cnt;
reg cnt_edge;
always @(posedge clk or posedge reset)
begin
if(reset)
begin
cnt <=0;
cnt_edge <= 1'b0;
end
else
if( cnt>=(10000000/(freq*64)-1) )
begin
cnt<=0;
cnt_edge <= 1'b1;
end
else
begin
cnt<=cnt+1;
cnt_edge <= 1'b0;
end
end
Теперь, когда есть сигнал cnt_edge, по его фронту вычисляем синус и наращиваем время:
real my_time;
real sin_real;
always @(posedge cnt_edge)
begin
sin_real <= sin(my_time);
my_time <= my_time+3.14159265*2/64;
end
Наблюдать за синусоидой будем так:
initial
begin
$dumpfile("out.vcd");
$dumpvars(0,testbench);
my_time=0;
freq=500;
#10000;
freq=1000;
#10000;
freq=1500;
#10000;
$finish;
end
Устанавливаем желаемое значение частоты синусоиды в 500Гц, ждем немного. Потом ставим 1000Гц, опять ждем. В конце симуляции ставим freq=1500Гц ждем немного и завершаем симуляцию.
Дамп сигналов симуляции будет записан в файл out.vcd, который можно просмотреть в графическом виде с помощью программы GtkWave.
Полностью программа нашего Verilog HDL testbench выглядит вот так:
`timescale 1us / 1ns
module testbench();
//assume basic clock is 10Mhz
reg clk;
initial clk=0;
always
#0.05 clk = ~clk;
//make reset signal at begin of simulation
reg reset;
initial
begin
reset = 1;
#0.1;
reset = 0;
end
//function calculating sinus
function real sin;
input x;
real x;
real x1,y,y2,y3,y5,y7,sum,sign;
begin
sign = 1.0;
x1 = x;
if (x1<0)
begin
x1 = -x1;
sign = -1.0;
end
while (x1 > 3.14159265/2.0)
begin
x1 = x1 - 3.14159265;
sign = -1.0*sign;
end
y = x1*2/3.14159265;
y2 = y*y;
y3 = y*y2;
y5 = y3*y2;
y7 = y5*y2;
sum = 1.570794*y - 0.645962*y3 +
0.079692*y5 - 0.004681712*y7;
sin = sign*sum;
end
endfunction
//generate requested "freq" digital
integer freq;
reg [31:0]cnt;
reg cnt_edge;
always @(posedge clk or posedge reset)
begin
if(reset)
begin
cnt <=0;
cnt_edge <= 1'b0;
end
else
if( cnt>=(10000000/(freq*64)-1) )
begin
cnt<=0;
cnt_edge <= 1'b1;
end
else
begin
cnt<=cnt+1;
cnt_edge <= 1'b0;
end
end
real my_time;
real sin_real;
reg signed [15:0]sin_val;
//generate requested "freq" sinus
always @(posedge cnt_edge)
begin
sin_real <= sin(my_time);
sin_val <= sin_real*32000;
my_time <= my_time+3.14159265*2/64;
end
initial
begin
$dumpfile("out.vcd");
$dumpvars(0,testbench);
my_time=0;
freq=500;
#10000;
freq=1000;
#10000;
freq=1500;
#10000;
$finish;
end
endmodule
Для симуляции я использую Icarus Verilog.
Запускаю в командной строке компилятор Verilog:
>iverilog –o qqq testbemch.v
Затем запускаю симулятор:
>vvp qqq
Теперь можно посмотреть получившиеся сигналы:
>gtkwave out.vcd
После последней команды запускается графическая среда GtkWave.
Чтобы посмотреть наш выходной сигнал sin_real в аналоговом виде выбирайте его в списке сигналов, кликайте на нем правую кнопку мыши. Затем найдите пункт меню Data Format / Analog / Step:
Теперь видна савершенно замечательная картина:
Видно, что частота сигнала меняется как только меняется значение переменной freq.
Можно измерить период колебаний с помощью маркеров в GtkWave:
Видно, что период самой медленной частоты составляет около 2мс, что соответсвует желаемым 500Гц. Значит наш тестбенч синтезирует частоту правильно.
Ну и наконец, можно заметить, что вычисление косинуса можно производить из вычисления синуса:
function real cos;
input x;
real x;
begin
cos = sin(x + 3.14159265/2.0);
end
endfunction
Теперь о синтезе функции синуса в ПЛИС. Понятно, что для вычисления синуса по приведенной выше формуле нужно произвести много арифметических вычислений. Пожалуй самое емкое вычисление – это возведение как минимум в седьмую степень. Возведение в степень – это умножение числа самого на себя семь раз. Потом, используются операции умножения на коэффициенты и сложение. Теперь видно, насколько трудна реализация тригонометрических функций в ПЛИС. Тем не менее, это, конечно, вполне решаемая задача.
Подробнее...