Я улучшил работу модуля на Verilog, причем без особых заморочек. Если смотреть программу на С++, то там наблюдается такая же аномалия. При том она вылезает при повороте на угол 0,03 градуса, при повороте на 0,1 градусов все чисто. Ну что же, первое что приходить на ум - увеличить разрядность фазы, второе что приходит на ум - увеличить количество итераций CORDIC. Сделаем то и другое.
Изначально под фазу было отдано 15 бит, но этого не хватает. Чтобы не было искажений, поставим фазу 20 бит. Количество итераций увеличим с 12 до 14. Тогда график наложения идеального модуля косинуса и рассчитанный CORDIC станет таким:
Текстовые файлы я генерю из С++, т.к. это быстрее. Графики строю в FreeMat. График в окрестностях 0, где был пик относительной погрешности. Поскольку фаза стала 20 битная, то на один период требуется 524287 точек, что очень много. Я ограничился 135000, поэтому будет чуть больше четверти периода. Относительную погрешность буду строить в окрестностях 0, картинка получилась такая:
Exported from Notepad++
Изначально под фазу было отдано 15 бит, но этого не хватает. Чтобы не было искажений, поставим фазу 20 бит. Количество итераций увеличим с 12 до 14. Тогда график наложения идеального модуля косинуса и рассчитанный CORDIC станет таким:
Текстовые файлы я генерю из С++, т.к. это быстрее. Графики строю в FreeMat. График в окрестностях 0, где был пик относительной погрешности. Поскольку фаза стала 20 битная, то на один период требуется 524287 точек, что очень много. Я ограничился 135000, поэтому будет чуть больше четверти периода. Относительную погрешность буду строить в окрестностях 0, картинка получилась такая:
Пик уменьшился в 5 раз. Считать такую погрешность приемлемой для 16 битного числа или нет решает каждый сам. Если увеличить N до 16, то пик станет равен 4. Вообще в этих точках вот что происходит: вместо правильного числа 0, CORDIC считает -4. Так как на 0 делить мы не можем: вместо 0 ставим 1 и считаем относительную погрешность. Если нужно убрать этот пик - делаем хак. Мы знаем точки, в которых этот выброс, значит при какой-то входном аргументе на выходе ставим 0. По хорошему это уже объединение двух методов: CORDIC и LUT (lookup table). В LUT мы храним значения каких-то углов, а если аргумент попадает между углов LUT, считаем его CORDIC. Я считаю, что -4 вместо 0 не криминал, поэтому оставлю так. В подтверждение своих слов приведу картинку абсолютной погрешности в области около 0:
Отлично, абсолютная погрешность не превышает 6 по модулю, что гораздо лучше 70.
И в заключение еще один хак. Каждую первую итерацию, независимо от угла, мы делаем одинаковое действие: вращаем вектор на 45 градусов. Поэтому мы можем первую итерацию вынести из цикла for, сделав так:
Re[0] = short(tmp);
Im[0] = short(tmp);
int_output_angle = int_angles[0];
int int_input_angle = my_arg*ARG_N/360.0f;
for(int k = 0; k < (N-1); k++) {
if(int_output_angle > int_input_angle) {
Re[k+1] = Re[k] + (Im[k] >> k+1);
Im[k+1] = Im[k] - (Re[k] >> k+1);
int_output_angle -= int_angles[k+1];
}
else {
Re[k+1] = Re[k] - (Im[k] >> k+1);
Im[k+1] = Im[k] + (Re[k] >> k+1);
int_output_angle += int_angles[k+1];
}
}
Если мы раньше объявляли Re[N+1], то теперь хватит Re[N]. Тогда на Verilog код будем выглядеть так:
`timescale 1ns / 1ps
module cordic_phase(
input wire clk,
input wire [ 19 : 0 ] arg,
output wire [ 15 : 0 ] Re_out,
output wire [ 15 : 0 ] Im_out
);
localparam N = 14;
localparam DAT_WIDTH = 16;
localparam ARG_WIDTH = 20;
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] = 20'd65536;
angle[ 1] = 20'd38688;
angle[ 2] = 20'd20441;
angle[ 3] = 20'd10376;
angle[ 4] = 20'd5208;
angle[ 5] = 20'd2606;
angle[ 6] = 20'd1303;
angle[ 7] = 20'd651;
angle[ 8] = 20'd325;
angle[ 9] = 20'd162;
angle[10] = 20'd81;
angle[11] = 20'd40;
angle[12] = 20'd20;
angle[13] = 20'd10;
end
reg signed [ DAT_WIDTH-1 : 0 ] Re[0:N-1];
reg signed [ DAT_WIDTH-1 : 0 ] Im[0:N-1];
reg signed [ ARG_WIDTH-1 : 0 ] r_input_arg[0:N-1];
reg signed [ ARG_WIDTH-1 : 0 ] r_output_arg[0:N-1];
reg [ 2 : 0 ] r_quad[0:N-1];
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] <= CORDIC_GAIN;
r_input_arg[0] <= {3'b0, arg[(ARG_WIDTH-4):0]};
r_output_arg[0] <= angle[0];
r_quad[0] <= arg[(ARG_WIDTH-1)-:3];
for(k = 0; k < N-1; k = k + 1)
begin
if(r_output_arg[k] > r_input_arg[k])
begin
Re[k+1] <= Re[k] + (Im[k] >>> k+1);
Im[k+1] <= Im[k] - (Re[k] >>> k+1);
r_output_arg[k+1] <= r_output_arg[k] - angle[k+1];
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+1);
Im[k+1] <= Im[k] + (Re[k] >>> k+1);
r_output_arg[k+1] <= r_output_arg[k] + angle[k+1];
r_input_arg[k+1] <= r_input_arg[k];
r_quad[k+1] <= r_quad[k];
end
end
r_Re_out <= r_quad[N-1] == 3'b000 ? Re[N-1] :
r_quad[N-1] == 3'b001 ? -Im[N-1] :
r_quad[N-1] == 3'b010 ? -Re[N-1] :
Im[N-1];
r_Im_out <= r_quad[N-1] == 3'b000 ? Im[N-1] :
r_quad[N-1] == 3'b001 ? Re[N-1] :
r_quad[N-1] == 3'b010 ? -Im[N-1] :
-Re[N-1];
end
assign Re_out = r_Re_out;
assign Im_out = r_Im_out;
endmodule
Добрый вечер!
ОтветитьУдалитьМеняю ARG_WIDTH = 20 на 32 и синусоида не хило так искажается... в чем может быть дело?
Здравствуйте! Почему значение Im[0] стало равно CORDIC_GAIN?
ОтветитьУдалитьЗдесь допущение, что вычисления происходят в 1 квадранте и вынесена первая итерация Кордика из цикла. Первый поворот всегда будет на 45 градусов, то будет Ре0 и Им0 = Кордик_Гейн/2. Если мы с учётом этого пересчитаем Кордик_Гейн, то можно сохранить одну ячейку памяти
УдалитьПонял, спасибо! Еще одно уточнение. У меня на графиках, cos и sin начинаются не с 0 фазы, а с предыдущего значения (например если взять 8 точек на период, то графики начинаются с -45 град.). С чем это связано?
УдалитьСложно вот так сказать, могу предложить посмотреть более новую тему с исходниками http://fpgach.blogspot.com/2014/07/cordic.html?m=1
УдалитьВсе также... Можно Вам куда-нибудь скрин скинуть, может так понятней будет мой вопрос?
УдалитьОх, лучше код на github или pastebin
УдалитьЭтот комментарий был удален автором.
УдалитьДля 16битного числа поворот на 45 градусов равен 4096 в деках, в хексах 0x10_00. Не помню почему я оставил 3 бита на квадрант, видимо для маневра с поворотом больше чем 360 градусов. Так вот первый квадрант это 3 старших нуля, потом 13 бит фазы
УдалитьЯ разобрался с графиками, спасибо
Удалить