четверг, 31 октября 2013 г.

Улучшаем CORDIC

Я улучшил работу модуля на Verilog, причем без особых заморочек. Если смотреть программу на С++, то там наблюдается такая же аномалия. При том она вылезает при повороте на угол 0,03 градуса, при повороте на 0,1 градусов все чисто. Ну что же, первое что приходить на ум - увеличить разрядность фазы, второе что приходит на ум - увеличить количество итераций CORDIC. Сделаем то и другое.

Изначально под фазу было отдано 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 код будем выглядеть так: 

Exported from Notepad++
`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

10 комментариев:

  1. Добрый вечер!

    Меняю ARG_WIDTH = 20 на 32 и синусоида не хило так искажается... в чем может быть дело?

    ОтветитьУдалить
  2. Здравствуйте! Почему значение Im[0] стало равно CORDIC_GAIN?

    ОтветитьУдалить
    Ответы
    1. Здесь допущение, что вычисления происходят в 1 квадранте и вынесена первая итерация Кордика из цикла. Первый поворот всегда будет на 45 градусов, то будет Ре0 и Им0 = Кордик_Гейн/2. Если мы с учётом этого пересчитаем Кордик_Гейн, то можно сохранить одну ячейку памяти

      Удалить
    2. Понял, спасибо! Еще одно уточнение. У меня на графиках, cos и sin начинаются не с 0 фазы, а с предыдущего значения (например если взять 8 точек на период, то графики начинаются с -45 град.). С чем это связано?

      Удалить
    3. Сложно вот так сказать, могу предложить посмотреть более новую тему с исходниками http://fpgach.blogspot.com/2014/07/cordic.html?m=1

      Удалить
    4. Все также... Можно Вам куда-нибудь скрин скинуть, может так понятней будет мой вопрос?

      Удалить
    5. Ох, лучше код на github или pastebin

      Удалить
    6. Этот комментарий был удален автором.

      Удалить
    7. Для 16битного числа поворот на 45 градусов равен 4096 в деках, в хексах 0x10_00. Не помню почему я оставил 3 бита на квадрант, видимо для маневра с поворотом больше чем 360 градусов. Так вот первый квадрант это 3 старших нуля, потом 13 бит фазы

      Удалить
    8. Я разобрался с графиками, спасибо

      Удалить