вторник, 29 октября 2013 г.

CORDIC Verilog

В общем, довольно наигравшись при переводе 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++
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, ибо углы и усиление константные. Получилось схоже по стилистике с конструктором:

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

В результате чего имеем такую красочную картинку:


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

  1. Пока собирается мой проект, что-то искал, наткнулся на статью и, не вдаваясь в подробности, решил попробовать простым копи-пейстом приведённого кода.
    Видимо, с фазой какой-то косяк всё-таки.
    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

    ОтветитьУдалить
    Ответы
    1. Ссылка по-красивше
      https://yadi.sk/d/HjEF2Ywtbu6sX

      Удалить
    2. Вот я балбесина, там же signed.
      Изменил radix на signed, теперь красота.

      Удалить
    3. Не подскажите, как вычислить CORDIC_GAIN, если я использую 18 бит?

      Удалить
    4. CORDIC_GAIN для случая 18 битных выходных данных считается как произведение косинусов во float умноженное на (2^17)-1

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

    ОтветитьУдалить
  3. Добрый день!

    Подскажите, как переделать на 32х-битный аргумент?

    ОтветитьУдалить
  4. почему же тогда у Вас для 15 и 20 битного входного аргумента одно и то же значение CORDIC_GAIN а для 32бит его уже надо пересчитывать?

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

    ОтветитьУдалить
  6. Хорошо что у меня есть мозг! Я его включил и все решил. Спасибо автору за материал.

    ОтветитьУдалить