среда, 23 октября 2013 г.

Комплексное число

Набросаем на плюсах как выглядит комплексное число. Найдем его модуль и аргумент, используя стандартные библиотеки:
Exported from Notepad++
#include <iostream> #include <math.h> #include <limits.h> using namespace std; const double PI = M_PI; class complex { private: int re; int im; public: complex(){re = 0; im = 0;} complex(int r, int i){re = r; im = i;} int get_re(){return re;} int get_im(){return im;} void set_re(int r) {re = r;} void set_im(int i) {im = i;} int get_abs_val(){return sqrt(re*re+im*im);} float get_arg(); }; float complex::get_arg() { float f_re = float(re); float f_im = float(im); float arg = atan2f(f_im,f_re)*180/M_PI; return arg; } int main() { complex *a = new complex(3,4); cout << a->get_abs_val() << endl; cout << a->get_arg() << endl; return 0; }

































Вывод: 5, 53.1301
Как перевести на Verilog вычисление модуля уже разобрались тут и тут. Остается вопрос с фазой. Определение из wiki выглядит следующим образом:

Выглядит сложно. Поэтому придумали другой способ вычисления аргумента и, заодно, модуля комплексного числа, названный CORDIC. Это итерационный метод, хорошо описанный в wiki. Я попробую объяснить его немного с другой стороны.
Пусть у нас есть комплексное число Z = 3+4j. На комплексной плоскости оно выглядит так:
Аргумент этого числа рассчитали в программе выше и он равен 53.1301, или для простоты 53 градуса. Для этого вектора выполняются следующие равенства:
Re = |Z|*cos(53) = 3
Im = |Z|*sin(53) = 4, где |Z| - модуль комплексного числа.
Повернем этот вектор на L градусов, тогда:
Re_new = |Z|*cos(53+L) = |Z|*(cos(53)*cos(L) - sin(53)*sin(L))
Im_new = |Z|*sin(53+L) = |Z|*(sin(53)*cos(L) + cos(53)*sin(L)).
Тут надо вспомнить школу и тригонометрические формулы. Just google it. Если раскрыть скобки и воспользоваться подстановкой |Z|*cos(53) = Re и  |Z|*sin(53) = Im, то получится:
Re_new = Re*cos(L) - Im*sin(L)
Im_new = Im*cos(L) + Re*sin(L)
Получается логичная вещь: значения поворачиваемого вектора зависят от первоначального положения вектора и угла, на который вращают вектор. При этом нет разницы повернем мы вектор один раз на L градусов, или два раза на L/2 градусов или N раз на L/N градусов.Если мы вращаем на отрицательный угол, то поменяется лишь знак:
Re_new = Re*cos(-L) - Im*sin(-L) = Re*cos(L) + Im*sin(L)
Im_new = Im*cos(-L) + Re*sin(-L) = Im*cos(L) - Re*sin(L)

Теперь рассмотрим такую ситуацию: у нас на входе комплексное число с произвольными Re и Im. Как определить аргумент такого числа? Мы просто будем поворачивать этот вектор до тех пор, пока Re или Im не станет равной нулю. При повороте вектора его модуль не меняется и в случае, когда одна из составляющих комплексного числа равно нулю, то другая равна его модулю. Вот и весь смысл метода CORDIC. Теперь рассмотрим более детально реализацию.
Видно, что для расчета Re_new и Im_new в ПЛИС нужно хранить две таблицы: синусов и косинусов. Их количество можно уменьшить вдвое, если поделить все на cos(L):
Re_new/cos(L) = Re - Im*tan(L)
Im_new/cos(L) = Im + Re*tan(L)
Видно, что теперь нужно хранить таблицу только из тангенсов углов. Перепишем выражение выше таким образом:
Re_new_сordic = Re - Im*tan(L)
Im_new_cordic = Im + Re*tan(L)
Проблему с делением на cos(L) на каждом ходу решается довольно просто: если количество итераций(поворотов вектора) сделать константой, то в результате N итераций соотношение между Re_new и Re_new_cordic будет таким:
Re_new = Re_new_cordic * cordic_const, где cordic_const равна произведению косинусов углов поворота и является константой. То есть мы спокойно считаем N итераций результат Re_new_cordic, а потом конечный результат просто умножаем на константу. Стоит отметить, что cordic_const принято называть cordic gain.
Ну и последнее, что важно отметить: на какие углы вращать вектор? Представим, что аргумент равновероятно лежит в диапазоне 0..90 градусов. Логично воспользоваться бинарным поиском и делить отрезки все время пополам, тогда углы будут 45, 22, 11, 5, 2, 1. В этом случае cordic_const = cos(45)*cos(22)*cos(11)*cos(5)*cos(2)*cos(1) и таблица тангенсов состоит из шести значений. Но есть и другой вариант, который подходит больше. Нативным для Verilog являются целочисленные переменные. А для целочисленных переменных деление на степень двойки равносильно операции сдвига. Поэтому нужно подобрать такие углы, тангенс которых был бы равен 1/(2^k), k = 0..N-1, где N - количество итераций. Если все рассуждения перенести в код, то получится такая программа:
Exported from Notepad++
#include <iostream> #include <fstream> #include <math.h> #include <limits.h> using namespace std; const double PI = M_PI; #define N 8 //number steps class complex { private: int re; int im; public: complex(){re = 0; im = 0;} complex(int r, int i){re = r; im = i;} int get_re(){return re;} int get_im(){return im;} void set_re(int r) {re = r;} void set_im(int i) {im = i;} int get_abs_val(){return sqrt(re*re+im*im);} float get_arg(); }; float complex::get_arg() { float f_re = float(re); float f_im = float(im); float arg = atan2f(f_im,f_re)*180/M_PI; return arg; } int main() { ofstream console("console.txt", ios::out); float d_angle[N]; float sum_angles = .0f; console << "tangens angles:\n"; for(int i = 0; i < N; i++) { d_angle[i] = atan(1.0/pow(2,i)) * (180/PI); sum_angles += d_angle[i]; console << d_angle[i] << endl; } float cordic_gain = .0; for(int i =0; i < N; i++) { if(i == 0) { cordic_gain = cos(d_angle[i]*PI/180.0); } else { cordic_gain *= cos(d_angle[i]*PI/180.0); } } console << "CORDIC gain:\n" << cordic_gain << endl; short Re[N+1]; short Im[N+1]; float arg = .0f; complex *a = new complex(3,4); Re[0] = a->get_re(); console << "Re of Z: " << Re[0] << endl; Im[0] = a->get_im(); console << "Im of Z: " << Im[0] << endl; for(int i = 0; i < N; i++) { if(Im[i] >= 0) { Re[i+1] = Re[i] + (Im[i] >> i); Im[i+1] = Im[i] - (Re[i] >> i); arg -= d_angle[i]; } else { Re[i+1] = Re[i] - (Im[i] >> i); Im[i+1] = Im[i] + (Re[i] >> i); arg += d_angle[i]; } cout << i << "\t" << Re[i+1] << "\t" << Im[i+1] << "\t" << arg << endl; } cout << "\nCORDIC:\t" << -arg << "\t real: " << a->get_arg() << endl; console << "\nCORDIC arg:\t" << -arg << "\t real: " << a->get_arg() << endl; int my_abs = Re[N] * cordic_gain; cout << "\nCORDIC:\t" << my_abs << "\t real: " << a->get_abs_val() << endl; console << "\nCORDIC module:\t" << my_abs << "\t real: " << a->get_abs_val() << endl; console.close(); return 0; }

Вывод в файл console.txt:
tangens angles:
45
26.5651
14.0362
7.12502
3.57633
1.78991
0.895174
0.447614
CORDIC gain:
0.607259
Re of Z: 3
Im of Z: 4

CORDIC arg: 57.1128 real: 53.1301

CORDIC module: 5 real: 5

Далее будем разбираться с результатами и переносить код на Verilog

Комментариев нет:

Отправить комментарий