4.04 - Всякий матан

Аппроксимация экспоненты

В записи 4.03 - EXP для ADSR я пробовал аппроксимировать результат с помощью линий. Но экспоненту нужно аппроксимировать не линиями, а полиномом, не ниже 3-го порядка.

Полинома 3-го порядка недостаточно для того, чтобы аппроксимировать кривую, которую нужно получить для ADSR. Но 4го порядка уже достаточно, можно поделить диапазон на две части, тогда нормально: файл ADSR_EXP.xlsx лист 1.

Рассчет коэффициентов полинома теперь делать легко - в excel с помощью функции ЛИНЕЙН()

Умножения

Умножение часто требуется в проекте. Но количество умножителе в ПЛИС ограничено (в Cyclone iv EP3CE10 их 46 штук, по 9 бит). А если умножение будет синтезироваться, то на одно умножение уйдет около 100 ячеек. Умножить число на константу очень просто: достаточно просуммировать комбинацию сдвигов. Калькулятор можно найти по адресу http://www.golovchenko.org/cgi-bin/constdivmul

/2 >>> 1

/4 >>> 2

/8 >>> 3

/16 >>> 4

/32 >>> 5

/64 >>> 6

/128 >>> 7

/256 >>> 8 (2^8)

/512 >>> 9

/1024 >>> 10

/2048 >>> 11

/4096 >>> 12

/8192 >>> 13 (2^13)

/16384 >>> 14

/32768 >>> 15

/65536 >>> 16

/131072 >>> 17

/262144 >>> 18

/524288 >>> 19

/1048576 >>> 20

/2097152 >>> 21

/4194304 >>> 22

/8388608 >>> 23

/16777216 >>> 24

/33554432 >>> 25

/67108864 >>> 26

/134217728 >>> 27

/268435456 >>> 28

/536870912 >>> 29

/1073741824 >>> 30

/2147483648 >>> 31

/4294967296 >>> 32

Умножение так же, только сдвиг в другую сторону. Если число без знака, то в Verilog Это сдвиги >> (две стрелки). Если число со знаком, то три >>>.

Либо вместо сдвига можно отрезать часть и добавлять биты Y = X << 2 все равно, что Y = {X, 2'b0}. Какой из них лучше, пока не могу сказать с точки зрения экономии ресурсов.

Все действия производить над дробными числами с фиксированной точкой.

Умножение на Х

Обычное умножение, которое синтезатор сделает сам, потратив 1 или более аппаратных умножителей. Если ПЛИС не может аппаратно умножать, то на одно умножение уйдет около 100 ячеек.

http://www.fpga-faq.narod.ru/ еще FAQ

Возведение в степень

Потратит не меньше одного аппаратного умножителя

Verilog:

Y = X**2

Квадраторы

http://kanyevsky.kpi.ua/kvadrat.html позволяют умножать помедленнее, но потратив меньше ячеек. Есть варианты последовательного рассчета.

Вычисление значения полинома - Схема Горнера

https://ru.wikipedia.org/wiki/%D0%A1%D1%85%D0%B5%D0%BC%D0%B0_%D0%93%D0%BE%D1%80%D0%BD%D0%B5%D1%80%D0%B0

сырки на паскале:

http://www.cyberforum.ru/turbo-pascal/thread243323.html

http://www.cyberforum.ru/turbo-pascal/thread88602.html

Результат вычисляется последовательно.

Нахождение корня n-й степени

Рассчет частоты ноты изначально считается через формулу 440Гц * 2^((нота-69)/12)

нота - это номер MIDI ноты

То есть 2 ^ (1/12)

12корень из двух

https://ru.wikipedia.org/wiki/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%BD%D0%B0%D1%85%D0%BE%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D1%8F_%D0%BA%D0%BE%D1%80%D0%BD%D1%8F_n-%D0%BD%D0%BE%D0%B9_%D1%81%D1%82%D0%B5%D0%BF%D0%B5%D0%BD%D0%B8

Но без умножений все равно никак не получится, даже если считать корень. Более того, появится еще и деление. Поэтому, вывод: нужно рассчитать полином и считать его последовательно Схемой Горнера.

Последовательное вычисление и почему я на него согласен

Алгоритм последовательного вычисления естественно работает медленнее, чем прямая реализация формулы в железе. Хотя, за счет "развесистости" схемы и она будет работать не мгновенно. Но так ли нужно мгновенное вычисление результата? Тем более на частоте проекта в 50МГц? Но даже если и так, то выдача результата все равно задержана на один такт, потому что все схемы в проекте реализованы синхронно, относительно CLK. Что будет, если результат будет рассчитываться в N шагов? Данные на выходе появятся через N тактов после поступления на вход. Но даже такая скорость реально не нужна, с учетом того, что скорость передачи данных по MIDI равна 31250 бод в формате 8n1. Получается, что данные поступают со скоростью 31250/9 и еще поделить на 3, потому что MIDI команда минимум 3 байта. В результате получается 1157 раз в секунду могут поменяться какие-то параметры. Максимальная частота смены значения 1157 Гц, период 0,000864304235090751с. Если считать, что для вычисления потребуется даже 10 тактов, то будет 50000000 / 10 = 5 МГц. Период, за который сменится значение 1/5000000 = 0,0000002с. Делим периоды 4321. То есть скорость вычисления нового значения будет занимать 1/4321 -ю часть от периода после получения нового значения. Возможно, с такой скоростью будет работать даже частотная модуляция. Ну и главная цель - экономия ресурсов ПЛИС, которых хоть и много, но они все равно ограничены. Приятным моментом будет то, что количество тактов будет ясно исходя их схемы на Verilog (на МК пришлось бы считать, сколько тактов будут выполняться команды).

Вычисление значения полинома по Схеме Горнера на Verilog

Для начала рассмотрю, как сделано на любимом Паскале

uses crt; var i,n:byte; f,s,x:real; a:array[0..10] of real; begin clrscr; write('Введите n=');readln(n); write('Введите x=');readln(x); writeln('Введите ',n+1,' коэфф. многочлена степени ',n); for i:=0 to n doreadln(a[i]); f:=1; s:=a[n]; for i:=1 to n do begin f:=f*x; s:=s+a[n-i]*f; end; write('S=',s:0:5); readlnend.

Проверю на своих данных, за одно и пойму, как оно работает

Полином 6, 7го порядка уже одним куском очень близок к искомой функции (только в начале происходит отклонение на 10%, что несущественно), НО, коэффициенты имеют на столько малые значения, что для fixed point потребуется очень много бит. Первое уможение, которое вычисляет f*x предполагаю сделать аппаратным умножителем, а второе умножение константное, поэтому можно заменить сумматорами и сдвигами. Вычисление на паскале показали работоспособность алгоритма, а так же подтвердилось опасение на счет малых значений и проблем с округлением.

Программа

program gorner;

{$APPTYPE CONSOLE}

uses

SysUtils;

var i,n, x:byte;

f,s{,x}:real;

a:array[0..10] of real;

readstr:string;

begin

{ TODO -oUser -cConsole Main : Insert code here }

WriteLn('Gogner');

n:=7;

a[0]:=-0.0000000000247;

a[1]:=0.0000000181367;

a[2]:=-0.0000058021696;

a[3]:=0.0010564414100;

a[4]:=-0.1193188300525;

a[5]:=8.4515592560791;

a[6]:=-351.8828415101810;

a[7]:=6724.5973910791600;

for x:=0 to 127 do begin

f:=1;

s:=a[n];

for i:=1 to n do begin

f:=f*x;

s:=s+a[n-i]*f;

end;

WriteLn('f(',x,')=', s:0:5);

end;

ReadLn(readstr)

end.

Результат почти удовлетворил, потому что значения Х после 110 плавно приближаются к 10, а мне нужно, чтобы приближались к 4. Возможно, подгонкой коэффициентов можно улучшить результат, однако, это пока не важно, потому что нужно реализовывать на Verilog.

Следущуй шаг - реализация алгоритма на Verilog с не синтезируемым float.

Считаю значение DDS от номера ноты. Попробовал и решил, что полинома 2го порядка достаточно, если поделить диапазон на куски в одну октаву. Отклонение 0.1 %.

module gorner(clk, in_val, out_val);input wire clk;input wire [6:0] in_val;output reg [31:0] out_val;reg [6:0] local_in_val;reg [3:0] state; real aa [0:8][0:4];reg [2:0] diapazon; real s, f, result; initial begin state <= 1'd0; local_in_val <= 18952.88; //!!! result <= 18952.88; //!!! diapazon <= 0; aa[0][0]<=45.02379; aa[1][0]<=1025.013; aa[2][0]<=18952.88; aa[0][1]<=45.02379; aa[1][1]<=1025.013; aa[2][1]<=118952.88; endalways @ (posedge clk) begin //вычисления //смена состояния if (state==4'd0) begin if (in_val<3) begin diapazon = 0; end else begin diapazon = 1; end s <= aa[`PR][diapazon]; local_in_val <= in_val; f <= in_val; end else if ((state>4'd0)&&(state<(`PR+1'b1))) begin if (state==`PR) begin result <= s + ((aa[`PR-state][diapazon])*f); end else begin s <= s + ((aa[`PR-state][diapazon])*f); end f <= f*local_in_val; end //смена состояний if (state==4'd0) begin state <= (in_val == local_in_val) ? 4'd0 : 4'd1; end else if ((state>4'd0)&&(state<(`PR+1'b1))) begin //state <= state + 1'b1; state <= (state==(`PR)) ? 4'd0 : state + 1'b1; endendendmodule

После этого fixed point.

Линейная интерполяция

Полиномы это, конечно, все интересно, однако, линейная интерполяция те же умножения, но их меньше, разрядность перемножаемых данных тоже меньше, ну и плюс непрерывность значений (при переходе от одного куска полинома к другому куску точно будет разрыв). Да, линейна интерполяция даст небольшую ошибку, но полиномы 2го порядка тоже врут до 0.2%, тут будет явно меньше. Уже недели 3 прошло без особого прогресса, поэтому ближе к делу.

Самая большая потребность в наличие экспоненциатора - это плавная смена частоты ноты- Pitch Wheel. Плюс LFO на частоту генератора. Если бы этого всего было не нужно, то можно было бы и дальше обходиться преобразованием нота->частота, вернее нота -> приращение к DDS для получения нужной частоты по таблице.

Поэтому ближе к делу, надо уже пробовать что-то, пиликать новые звуки. Итак

Pitch wheel

LSB MSB

224 0 0 - минимум (-1 октава)

224 0 64 - ноль

224 127 127 - максимум (+1 октава)

14 битное значение 8192 (0x2000) середина

1) Берем значение Pitch wheel

2) поделить на 682,66666666667 все равно, что умножить на 0.00146484375

чудеса! хороший знак!

; ALGORITHM:

; Clear accumulator

; Add input / 1024 to accumulator

; Add input / 2048 to accumulator

; Move accumulator to result

;

; Approximated constant: 0.00146484, Error: 0 %

Перед целочисленным делением сдвинем число на 7 бит влево. В результате, старшие 7 бит будут содержать число со знаком от -12 до +11 это значение ноты, которое надо прибавить к той, что взяли (реально бит будет не 7, а меньше). А в младших 7 битах будет дробная часть, которую мы будем применять для интерполяции между двумя значениями из таблицы.

Гугл че то перестал грузить файлы на страницы :(

....

Разработка модуля note_pitch2dds.v в первоначальном виде закончена, протестирована на бенче IcarusVerilog, скомпилирована в железо, получилось 144 ячейки и что странно, 0 аппаратных умножителей. Вместо них less than. Таймквест 92 МГц - укладываемся в проектные 50 МГц.

Модуль вставлен в текущий одноголосый проект, получилось 1157 ячеек, 7 умножителей. Тестирование завтра.