МАРСОХОД

Open Source Hardware Project

Язык описания аппаратуры Verilog HDL

Реализация SIN и COS на Verilog

Синусоида на осцилографе

Язык описания аппаратуры 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:

Просмотр аналогового сигнала в GtkWave

Теперь видна савершенно замечательная картина:

синусоида на Verilog, GtkWave

Видно, что частота сигнала меняется как только меняется значение переменной freq.

Можно измерить период колебаний с помощью маркеров в GtkWave:

Использование GtkWave

Видно, что период самой медленной частоты составляет около 2мс, что соответсвует желаемым 500Гц. Значит наш тестбенч синтезирует частоту правильно.

Ну и наконец, можно заметить, что вычисление косинуса можно производить из вычисления синуса:


function real cos;
input x;
real x;
begin
  cos = sin(x + 3.14159265/2.0);
end
endfunction


Теперь о синтезе функции синуса в ПЛИС. Понятно, что для вычисления синуса по приведенной выше формуле нужно произвести много арифметических вычислений. Пожалуй самое емкое вычисление – это возведение как минимум в седьмую степень. Возведение в степень – это умножение числа самого на себя семь раз. Потом, используются операции умножения на коэффициенты и сложение. Теперь видно, насколько трудна реализация тригонометрических функций в ПЛИС. Тем не менее, это, конечно, вполне решаемая задача.

 

Комментарии  

+2 #4 Кирилл 08.05.2013 05:29
Синус можно генерировать при помощи алгоритма Герцеля. Реализация получается достаточно простая. На Cyclone III получал синус до 20 МГц, при тактовой частоте 150 МГц.
+2 #3 Владимир Ходукин 17.02.2013 13:13
Цитирую Василий:
Отличная статья!
Тока вот код синуса для синтеза так и не нашел. Просто бы увидеть, что из себя представляет такой код
http://bookre.org/reader?file=446792&pg=43
Наслаждайтесь :-)
0 #2 lesha birukov 09.02.2013 11:12
Ну так ведь это тестбенч, он на синтез не расчитан, в начале написано. В принципе, заменив real на фиксированную точку, этот алгоритм можно переложить на синтезируемую логику, но это будет весьма не эффективно, каскад умножений даст ужасную задержку.
Я сам синтезируемый синус делал, но не тейлором а кордиком.
+1 #1 Василий 06.02.2013 05:30
Отличная статья!
Тока вот код синуса для синтеза так и не нашел. Просто бы увидеть, что из себя представляет такой код

Добавить комментарий


Защитный код
Обновить


GitHub YouTube Twitter
Вы здесь: Начало Verilog Реализация SIN и COS на Verilog