В общем, довольно наигравшись при переводе float в int я для себя сделал такие выводы:
1. Опасайся отрицательных чисел в int, ибо сколько не сдвигай отрицательное число вправо, все равно получишь -1. Например -2 >> 4 = -1. Пруф в любом калькуляторе.
2. Из "1" вытекает, что с тригонометрическими величинами удобнее работать в первом квадранте, где синус и косинус положительные.
3. Нормируй фазу в диапазоне [-360, 360) градусов. Тогда будет удобнее определять квадрант, в котором лежит вектор. Тогда будет удобно вращать "возвращать" фазу в первый квадрант, вычитая из текущей фазы 270,180,90 градусов.
4. Если вспомнить вывод формулы для CORDIC, то посчитать как изменятся значения квадратур при повороте на 270,180,90 градусов не составит труда.
Фактически выполняется такая последовательность над фазой. На входе фаза в диапазон [0,360), вычитаем из нее 90,180,270 градусов, чтобы получить диапазон [0,90) - первый квадрант. Считаем квадратуры для этого угла. Дальше в зависимости от квадранта первоначального угла переставляем квадратуры. Так, например, если фаза на входе L из 2 квадранта и ее квадратуры равны Re0 и Im0, а посчитанные квадратуры в первом квадранте для (L-90) градусов равны Re` и Im`, то
Re0 = -Im`
Im0 = Re`
Если L из третьего квадранта, то
Re0 = -Re`
Im0 = -Im`
Из четвертого:
Re0 = Im`
Im0 = -Re`
Мой переделанный конструктор выглядит следующим образом:
Exported from Notepad++
Тут сделано все, о чем говорилось выше о фазе и квадратурах. Забыл добавить константы, которые использую для расчета:
#define N 12 //number steps
#define ARG_N 16384 //how much 360 degrees in int
Exported from Notepad++
Мой маленький testbench для модуля выглядит так:
module cordic_tb;
reg clk;
reg [ 14 : 0 ] arg;
wire [ 15 : 0 ] Re_out;
wire [ 15 : 0 ] Im_out;
cordic_phase u0
(
.clk(clk),
.arg(arg),
.Re_out(Re_out),
.Im_out(Im_out)
);
initial begin
clk = 0;
arg = 0;
end
always
#10 clk = !clk;
always@(posedge clk)
begin
arg <= arg + 1;
if(arg == 16383)
arg <= 0;
end
endmodule
1. Опасайся отрицательных чисел в int, ибо сколько не сдвигай отрицательное число вправо, все равно получишь -1. Например -2 >> 4 = -1. Пруф в любом калькуляторе.
2. Из "1" вытекает, что с тригонометрическими величинами удобнее работать в первом квадранте, где синус и косинус положительные.
3. Нормируй фазу в диапазоне [-360, 360) градусов. Тогда будет удобнее определять квадрант, в котором лежит вектор. Тогда будет удобно вращать "возвращать" фазу в первый квадрант, вычитая из текущей фазы 270,180,90 градусов.
4. Если вспомнить вывод формулы для CORDIC, то посчитать как изменятся значения квадратур при повороте на 270,180,90 градусов не составит труда.
Фактически выполняется такая последовательность над фазой. На входе фаза в диапазон [0,360), вычитаем из нее 90,180,270 градусов, чтобы получить диапазон [0,90) - первый квадрант. Считаем квадратуры для этого угла. Дальше в зависимости от квадранта первоначального угла переставляем квадратуры. Так, например, если фаза на входе L из 2 квадранта и ее квадратуры равны Re0 и Im0, а посчитанные квадратуры в первом квадранте для (L-90) градусов равны Re` и Im`, то
Re0 = -Im`
Im0 = Re`
Если L из третьего квадранта, то
Re0 = -Re`
Im0 = -Im`
Из четвертого:
Re0 = Im`
Im0 = -Re`
Мой переделанный конструктор выглядит следующим образом:
void complex::calc_cordic(int& r, int& i) {
ofstream creating_test("debug_constructor.txt", ios::out);
cout << get_mod() << endl;
cout << get_arg() << endl;
float d_angle[N];
float sum_angles = .0f;
//float argument
for(int k = 0; k < N; k++) {
d_angle[k] = atan(1.0/pow(2,k)) * (180.0/PI);
sum_angles += d_angle[k];
}
//int argument
int int_output_angle = 0;
int int_angles[N];
for(int k = 0; k < N; k++) {
int_angles[k] = d_angle[k]*ARG_N/360.0f;
cout << int_angles[k] << endl;
}
float cordic_gain = .0f;
for(int k =0; k < N; k++) {
if(k == 0) {
cordic_gain = cos(d_angle[k]*PI/180.0);
}
else {
cordic_gain *= cos(d_angle[k]*PI/180.0);
}
}
short Re[N+1];
short Im[N+1];
float tmp = (get_mod()* cordic_gain); //int cordic gain
float my_arg = get_arg();
creating_test << "my arg: "<< my_arg << endl;
if(my_arg >= 90.0f && my_arg < 180) {
my_arg -= 90.0f;
}
if(my_arg >= 180.0f && my_arg < 270) {
my_arg -= 180.0f;
}
if(my_arg >= 270.0f && my_arg < 360) {
my_arg -= 270.0f;
}
Re[0] = short(tmp);
Im[0] = 0;
int int_input_angle = my_arg*ARG_N/360.0f;
creating_test << int_input_angle << endl;
for(int k = 0; k < N; k++) {
if(int_output_angle > int_input_angle) {
Re[k+1] = Re[k] + (Im[k] >> k);
Im[k+1] = Im[k] - (Re[k] >> k);
int_output_angle -= int_angles[k];
}
else {
Re[k+1] = Re[k] - (Im[k] >> k);
Im[k+1] = Im[k] + (Re[k] >> k);
int_output_angle += int_angles[k];
}
cout << "\t" << k << "\t\t" << Re[k+1] << "\t\t" << Im[k+1] << "\t\t" << int_output_angle << endl;
}
if(get_arg() >= 0.0f && get_arg() < 90) {
r = Re[N];
i = Im[N];
}
if(get_arg() >= 90.0f && get_arg() < 180) {
r = -Im[N];
i = Re[N];
}
if(get_arg() >= 180.0f && get_arg() < 270) {
r = -Re[N];
i = -Im[N];
}
if(get_arg() >= 270.0f && get_arg() < 360) {
r = Im[N];
i = -Re[N];
}
creating_test.close();
}
Тут сделано все, о чем говорилось выше о фазе и квадратурах. Забыл добавить константы, которые использую для расчета:
#define N 12 //number steps
#define ARG_N 16384 //how much 360 degrees in int
N я взял 12 для большей точности вычислений. ARG_N это то число, которому равна фаза 360 градусов и оно не случайно равно степени двойки. Поскольку включение в диапазон [-360,360) справа не замкнутое, то фактически эта величина недостижима. Значит фаза [0,360) будет лежать в диапазоне 0..16383. Для этого хватает 15 бит, где старший - знаковый. Отрицательные целочисленные значения представляют фазу в диапазоне [-360,0).
При порте на Verilog я пользовался конвейером. Углы для CORDIC я взял прямо из конструктора, оттуда же и усиление CORDIC в целочисленной форме. Здесь уже не получится свободно менять N и ARG_N, ибо углы и усиление константные. Получилось схоже по стилистике с конструктором:
`timescale 1ns / 1ps
module cordic_phase(
input wire clk,
input wire [ 14 : 0 ] arg,
output wire [ 15 : 0 ] Re_out,
output wire [ 15 : 0 ] Im_out
);
localparam N = 12;
localparam DAT_WIDTH = 16;
localparam ARG_WIDTH = 15;
reg signed [ DAT_WIDTH-1 : 0 ] CORDIC_GAIN = 16'd19897;
integer k;
reg [ ARG_WIDTH-1 : 0 ] angle[0:N-1];
initial begin
angle[ 0] = 15'd2048;
angle[ 1] = 15'd1209;
angle[ 2] = 15'd638;
angle[ 3] = 15'd324;
angle[ 4] = 15'd162;
angle[ 5] = 15'd81;
angle[ 6] = 15'd40;
angle[ 7] = 15'd20;
angle[ 8] = 15'd10;
angle[ 9] = 15'd5;
angle[10] = 15'd2;
angle[11] = 15'd1;
end
reg signed [ DAT_WIDTH-1 : 0 ] Re[0:N];
reg signed [ DAT_WIDTH-1 : 0 ] Im[0:N];
reg signed [ ARG_WIDTH-1 : 0 ] r_input_arg[0:N];
reg signed [ ARG_WIDTH-1 : 0 ] r_output_arg[0:N];
reg [ 2 : 0 ] r_quad[0:N];
reg signed [ DAT_WIDTH-1 : 0 ] r_Re_out = 16'b0;
reg signed [ DAT_WIDTH-1 : 0 ] r_Im_out = 16'b0;
always@(posedge clk)
begin
Re[0] <= CORDIC_GAIN;
Im[0] <= 16'b0;
r_input_arg[0] <= {3'b0, arg[11:0]};
r_output_arg[0] <= 15'b0;
r_quad[0] <= arg[14-:3];
for(k = 0; k < N; k = k + 1)
begin
if(r_output_arg[k] > r_input_arg[k])
begin
Re[k+1] <= Re[k] + (Im[k] >>> k);
Im[k+1] <= Im[k] - (Re[k] >>> k);
r_output_arg[k+1] <= r_output_arg[k] - angle[k];
r_input_arg[k+1] <= r_input_arg[k];
r_quad[k+1] <= r_quad[k];
end
else
begin
Re[k+1] <= Re[k] - (Im[k] >>> k);
Im[k+1] <= Im[k] + (Re[k] >>> k);
r_output_arg[k+1] <= r_output_arg[k] + angle[k];
r_input_arg[k+1] <= r_input_arg[k];
r_quad[k+1] <= r_quad[k];
end
end
r_Re_out <= r_quad[N] == 3'b000 ? Re[N] :
r_quad[N] == 3'b001 ? -Im[N] :
r_quad[N] == 3'b010 ? -Re[N] :
Im[N];
r_Im_out <= r_quad[N] == 3'b000 ? Im[N] :
r_quad[N] == 3'b001 ? Re[N] :
r_quad[N] == 3'b010 ? -Im[N] :
-Re[N];
end
assign Re_out = r_Re_out;
assign Im_out = r_Im_out;
endmodule
Мой маленький testbench для модуля выглядит так:
module cordic_tb;
reg clk;
reg [ 14 : 0 ] arg;
wire [ 15 : 0 ] Re_out;
wire [ 15 : 0 ] Im_out;
cordic_phase u0
(
.clk(clk),
.arg(arg),
.Re_out(Re_out),
.Im_out(Im_out)
);
initial begin
clk = 0;
arg = 0;
end
always
#10 clk = !clk;
always@(posedge clk)
begin
arg <= arg + 1;
if(arg == 16383)
arg <= 0;
end
endmodule
В результате чего имеем такую красочную картинку:
Пока собирается мой проект, что-то искал, наткнулся на статью и, не вдаваясь в подробности, решил попробовать простым копи-пейстом приведённого кода.
ОтветитьУдалитьВидимо, с фазой какой-то косяк всё-таки.
https://1.downloader.disk.yandex.ru/preview/00c105a166df96467b47140417ba0f8b/mpfs/JSVfit-XKdWcIKt9CUNU14or6IZnyeaikJUQi1XcLdZpQBz7joeTUWa94W7LOoR6MSeeagsOo5yo44bsPCcJrA%3D%3D?uid=0&filename=sinPNG&disposition=inline&hash=&limit=0&content_type=image%2Fpng&size=XXL&crop=0
Ссылка по-красивше
Удалитьhttps://yadi.sk/d/HjEF2Ywtbu6sX
Вот я балбесина, там же signed.
УдалитьИзменил radix на signed, теперь красота.
Бывает
УдалитьНе подскажите, как вычислить CORDIC_GAIN, если я использую 18 бит?
УдалитьCORDIC_GAIN для случая 18 битных выходных данных считается как произведение косинусов во float умноженное на (2^17)-1
УдалитьЭтот комментарий был удален автором.
ОтветитьУдалитьДобрый день!
ОтветитьУдалитьПодскажите, как переделать на 32х-битный аргумент?
нужно пересчитать CORDIC_GAIN
УдалитьЭтот комментарий был удален автором.
Удалитьпочему же тогда у Вас для 15 и 20 битного входного аргумента одно и то же значение CORDIC_GAIN а для 32бит его уже надо пересчитывать?
ОтветитьУдалитьЭтот комментарий был удален автором.
ОтветитьУдалитьХорошо что у меня есть мозг! Я его включил и все решил. Спасибо автору за материал.
ОтветитьУдалить