МАРСОХОД

Open Source Hardware Project

Цифровой КИХ фильтр на Verilog для цветомузыки

color leds

В предыдущей статье я писал об изготовлении самодельного микрофонного шилда к плате Марсоход3bis.
Для чего мне понадобилась такая плата? Мне захотелось сделать "новогодний проект" - "Цветомузыка". Я хочу сделать автономное устройство, которое будет слушать микрофоном окружающий звук, оцифровывать его с помощью АЦП в ПЛИС платы MAX10, далее фильтровать на низкие, средние и высокие частоты и светить соответственно тремя цветами: красным, зеленым и синим в такт музыки.

Конечно, я понимаю, что оцифровывать звук таким маленьким электретным микрофоном типа CZN-15E - это не очень здоровое занятие.. но хотелось получить именно автономное устройство, которое бы не зависило от проигрывателя, компьютера или еще чего..

Пожалуй самый сложный вопрос в этом проекте будет цифровой фильтр. Далее я расскажу, как собираюсь делать фильтр в FPGA MAX10 платы Марсоход3bis.

Есть два способа реализации цифрового фильтра в ПЛИС Intel/Altera. Первый способ - использовать встроенные мегафунции САПР Intel Quartus Prime. Для этого можно через меню квартуса Tools => IP Catalog поискать в библиотеках Library => DSP => Filters => FIR II и запустить визард, который поможет создать нужные экземпляры устройств фильтров. Фильтры, предоставляемые САПР Quartus Prime, имеют множество настроек:

FIR II filter Quartus IP catalog wizard

Действительно мощный инструмент, однако, реальное использование многих мегафункций из IP Catalog Quartus Prime может потребовать дополнительной платной лицензии. Это не наш путь. Попробую пойти другим путем, написать на Verilog свой собственный цифровой фильтр. Если вы следите за нашим блогом, то увидите, что у меня уже была статья про КИХ фильтр, написанный на верилоге. Этот фильтр в общем замечательно работает в симуляции, однако, честно говоря я сомневаюсь, что его получится использовать в этом проекте. Причина простая - тот фильтр представляет в точности схему, как ее рисуют в книжках: линия задержки на регистрах, умножители по числу регистров задержки, итоговый сумматор. Вот так:

fir filter schema

В реальном проекте может оказаться, что число регистров в линии задержки велико, значит потребуется много регистров и много умножителей. Думаю в обычную ПЛИС такое может и не поместится.

Попробуем рассчитать коэффициенты КИХ фильтра средних частот в онлайн-калькуляторе t-filter:

 fir filter middle pass, Green color

Например, при частоте оцифровки звука 50кГц, для полосового КИХ фильтра средних частот от 700Гц до 2700Гц потребуется линия задержки в 327 регистров. Столько же нужно умножителей. это слишком много. Но что же делать в этом случае? Выход прост. Частота оцифровки - всего 50кГц. Можно записывать отсчеты в память ПЛИС в циклический буфер, а за интервал времени между записями можно перечитывать все ранее накопленные отсчеты, перемножать на нужные коэффициенты из ПЗУ и складывать в регистр накопитель результата.

Попробую проиллюстрировать идею следующим рисунком:

cyclic memory buffer for FIR filter

Понятно, что на такой фильтр нужно подавать рабочую частоту в 512 раз выше частота оцифровки полезного сигнала. То есть, если оцифровывать звук на частоте 50кГц, то рабочая частота такого модуля фильтра будет 50000*512 = 25600000 Гц. Это всего 25,6 МГц и это совсем не много для FPGA.

Каждые 512 тактов рабочей частоты будет производиться всего одна запись очередной выборки сигнала в циклическую память. Однако, за эти же 512 тактов нужно перечитать каждый элемент из блока памяти и параллельно каждый элемент из постоянной памяти, где хранятся коэффициенты фильтра.

Накапливающий регистр обнуляется при каждой записи выборки сигнала, и потом туда суммируются результаты умножения отсчетов фильтра на соответствующие коэффициенты фильтра.

Итак,  я написал вот такой модуль на языке Verilog HDL:


`timescale 1ns/1ps

module fir_filter(
    input wire nreset,
    input wire clk, //idata12 sampling frequency
    input wire [11:0]idata12, //samples unsigned
    output reg signed [47:0]out_val,
    output reg out_ready
);
parameter MIF = "coeffs/mid/coeffs-450-5800.mif";

reg [8:0]rd_addr;
reg [8:0]wr_addr;

//make signed input sample from unsigned
wire signed [15:0]idata; assign idata = { idata12, 4'h0 }-16'h8000;

//read samples from cyclic buffer
always @(posedge clk or negedge nreset)
  if( ~nreset )
    rd_addr <= 0;
  else
    rd_addr <= rd_addr + 1;

//"wr" signal -> writes new sample to cyclic buffer
reg wr;
always @(posedge clk or negedge nreset)
  if( ~nreset )
    wr <= 1'b0;
  else
    wr <= (rd_addr==9'h1ff);

always @(posedge clk or negedge nreset)
  if( ~nreset )
    wr_addr <= 0;
  else
  if( rd_addr==9'h1ff )
    wr_addr <= wr_addr + 1;

wire signed [15:0]odata;

//cyclic buffer for samples
`ifdef ICARUS
dp_mem_1clk_p #( .DATA_WIDTH(16), .ADDR_WIDTH(9), .RAM_DEPTH(1 << 9) )mem_samples
(
    .Clk( clk ),
    .Reset_N( nreset ),
    .we( wr ),
    .rd( nreset ),
    .wr_addr( wr_addr ),
    .rd_addr( rd_addr ),
    .data_in( idata ),
    .data_out( odata )
);
`else
fir_ram fir_ram_inst (
    .clock ( clk ),
    .data ( idata ),
    .rdaddress ( rd_addr ),
    .wraddress ( wr_addr ),
    .wren ( wr ),
    .q ( odata )
);
`endif

//fir coefficients contiguously extracted from filter embeddef memory
wire [8:0]rd_addr_coeff;
assign rd_addr_coeff = 512 - rd_addr + wr_addr;
wire signed [15:0]fir_coeff;

`ifdef ICARUS
dp_mem_1clk_p #( .DATA_WIDTH(16), .ADDR_WIDTH(9), .RAM_DEPTH(1 << 9) )mem_coeff
(
    .Clk( clk ),
    .Reset_N( nreset ),
    .we( 1'b0 ),
    .rd( nreset ),
    .wr_addr( 9'd0 ),
    .rd_addr( rd_addr_coeff ),
    .data_in( 16'd0 ),
    .data_out( fir_coeff )
);
`else
fir_coef_rom_param #(.MIF(MIF)) fir_coef_rom_inst (
    .address ( rd_addr_coeff ),
    .clock ( clk ),
    .q ( fir_coeff )
);
`endif

reg signed [47:0]filter_val_acc;
always @(posedge clk or negedge nreset)
  if( ~nreset )
  begin
    out_val <= 0;
    filter_val_acc <=0;
  end
  else
  if( wr )
  begin
    out_val <= filter_val_acc;
    filter_val_acc <= fir_coeff * odata;
  end
  else
  begin
    filter_val_acc <= filter_val_acc + fir_coeff * odata;
  end

always @( posedge clk or negedge nreset )
  if( ~nreset )
    out_ready <= 0;
  else
    out_ready <= wr;

endmodule


Здесь в модуле имеются девятиразрядный регистр-счетчик указатель чтения из памяти rd_addr, и девятиразрядный регистр-счетчик  указатель записи в память wr_addr. При этом, регистр rd_addr увеличивается постоянно с каждым тактом, а адрес записи wr_addr увеличивается лишь каждые 512 тактов. Одновременно с увеличением регистра адреса wr_addr вырабатывается импульс записи wr, который записывает в память очередной отсчет входных оцифрованных данных. В модуле установлена память для хранения входных отсчетов. Я для симуляции использую Icarus Verilog, мне так удобнее, поэтому для икаруса с помошью условной компиляции я вставляю модуль памяти типа dp_mem_1clk_p (экземпляр mem_samples) - это компонент взятый мной когда-то с opencores. Количество элементов в памяти - 512 шестнадцатибитных слов. Получается если все время инкрементировать адреса, то после адреса 0x1FF будет адрес 0x000 - циклический буфер. Для компиляции квартуса я сделаю визардом Quartus модуль Dual Port RAM (DPRAM). И я знаю квартусовский DPRAM работает так же как и простой модуль dp_mem_1clk_p. Но различие конечно есть - квартусовский библиотечный DPRAM будет использовать встроенные блоки памяти из FPGA, а не регистры.

Так же здесь установлена постоянная память, в которой хранятся коэффициенты КИХ фильтра. Для икаруса, этот тот же dp_mem_1clk_p (экземпляр mem_coeff). Здесь так же 512 слов по 16 бит. Когда буду компилировать квартусом, то тут будет установлен компонент созданный визардом квартуса ROM и туда будет загружен Memory Initialization File (*.MIF).

Отмечу, что адреса на ROM растут в противоположном направлении, не так как хранятся отсчеты в памяти данных, так как более старые выборки оказываются позади головы - указателя записи. И к более старым отсчетам нужно прилагать коэффициенты фильтра, которые расположены в более высоких адресах.

Для накопления разультата фильтра используется 48-ми разрядный регистр filter_val_acc. Думаю этой разрядности должно хватить.

Ниже приведен тестбенч на Verilog. С его помощью я хочу проверить, как мой фильтр подавляет не нужные частоты. Я проверяю Middle-Pass-Filter. Фильтр, который пропускается средние частоты где-то с 700Гц до 2700Гц на зеленую лампу цветомузыки. Коффициенты фильтра рассчитаны в онлайн-калькуляторе t-filter. Частотная характеристика фильтра должна получиться такая, как на рисунке выше.


`timescale 1ns / 1ps

module tb();

reg reset;

//assume basic clock is 10Mhz
reg clk;
initial clk=0;
always
  #50 clk = ~clk;

//fir clk
reg fir_clk;
initial fir_clk=0;
always
  #19.531 fir_clk = ~fir_clk;

//function calculating sinus 

function real sin;
input x;
real x;
real x1,y,y2,y3,y5,y7,sum,sign;
begin
  sign = 1.0;
  x1 = x;
  if (x1<0)
  begin
    x1 = -x1;
    sign = -1.0;
  end
  while (x1 > 3.14159265/2.0)
  begin
    x1 = x1 - 3.14159265;
    sign = -1.0*sign;
  end
  y = x1*2/3.14159265;
  y2 = y*y;
  y3 = y*y2;
  y5 = y3*y2;
  y7 = y5*y2;
  sum = 1.570794*y - 0.645962*y3 +
  0.079692*y5 - 0.004681712*y7;
  sin = sign*sum;
end
endfunction

//generate requested "freq" digital
integer freq;
reg [31:0]cnt;
reg cnt_edge;
always @(posedge clk or posedge reset)
begin
  if(reset)
  begin
    cnt <=0;
    cnt_edge <= 1'b0;
  end
  else
  if( cnt>=(10000000/(freq*64)-1) )
  begin
    cnt<=0;
    cnt_edge <= 1'b1;
  end
  else
  begin
    cnt<=cnt+1;
    cnt_edge <= 1'b0;
  end
end

real my_time;
real sin_real;
reg signed [11:0]sin_val;

//generate requested "freq" sinus
always @(posedge cnt_edge)
begin
  sin_real <= sin(my_time);
  sin_val <= sin_real*2047+2047;
  my_time <= my_time+3.14159265*2/64;
end

wire signed [47:0]out;
wire out_rdy;
fir_filter fir_(
  .nreset( ~reset ),
  .clk( fir_clk ),
  .idata12( sin_val ),
  .out_val( out ),
  .out_ready( out_rdy )
  );

initial
begin
$dumpfile("out.vcd");

$dumpvars(1,
  tb.freq,
  tb.fir_.idata12,
  tb.fir_.out_val
  );

reset = 1;
#1000;
reset = 0;

read_bp_coeff();

my_time=0;

for ( freq=300; freq<4000; freq=freq+200 )
begin
  #20000000;
  if( freq>1000 )
  freq=freq+200;
end

$finish;
end

integer file_filter;
integer i;
integer scan_result;
reg signed [15:0]coeff;

task read_bp_coeff;
begin
  file_filter = $fopen("coeffs/mid/fresp_700_2700.txt", "r");
  if (file_filter == 0) begin
    $display("file bp filter handle was NULL");
    $finish;
  end
  for( i=0; i<512; i=i+1 )
  begin
    scan_result = $fscanf(file_filter, "%d\n", coeff);
    if ( scan_result!=1 )
      coeff = 0;
    //$display("coeff %d = %d",i,coeff);
    fir_.mem_coeff.mem[i] = coeff;
  end
  $fclose(file_filter);
end
endtask

endmodule


В этом тестбенче есть синтезатор синусоиды. Про него подробнее можно почитать здесь. Тестбенч управляет этим генератором и он выдает частоты по очереди в интервале от 300Гц до 4000Гц. Эта частота подается на экземпляр моего фильтра fir_filter fir_.  Внутренняя память фильтра для хранения коэффициентов КИХ фильтра заполняется тестбенчем из задачи read_bp_coeff(). Там буквально открывается текстовый файл coeffs/mid/fresp_700_2700.txt и из него построчно считываются значения коэффициентов и вписываются в память fir_.mem_coeff.mem[i].

Тестбенч записывает изменения сигналов модулей в выходной файл out.vcd. Это определяется системной функцией $dumpfile. Этот файл потом можно посмотреть в программе GtkWave. Сигналы, которые мне интересны для просмотра я задаю системной функцией $dumpvars. Можно конечно записать в VCD файл вообще все сигналы, но тогда файл получается просто огромный, гигабайты. Это и медленно и не удобно.

Теперь покажу, что получилось в результате симуляции. Как обычно, я запускаю компилятор Icarus Verilog, затем симулятор икаруса и GtkWave:

>iverilog -DICARUS=1 -o qqq tb.v fir_filter.v dp_mem_1clk_p.v

>vvp qqq

>gtkwave out.vcd

После запуска GtkWave вижу вот такие сигналы:

gtkwave signals

На временной диаграмме видно, что на выходе фильтра частоты до 700Гц и после 3000Гц не проходят. Так и должно быть, это соответствует расчетным значениям и желаемой Амплитудно-Частотной Характеристике (АЧХ).

Для проекта цветомузыки потребуется как минимум три фильтра: один для низких частот (красный цвет), этот фильтр для средних частот (зеленый цвет) и высоких частот (синий цвет).

В следующих статьях надеюсь будет уже полностью опубликован проект цветомузыки.

Комментарии  

0 #1 JamesZer 17.05.2019 11:28
Всем,кто знаком с продвижением сайтов и закупкой ссылок,а также
с покупкой рекламы в Яндекс и Google этот СОФТ станет незаменимым
помощником.
У программы есть полная документация по настройки и запуску,а также
отзывчивая техническая поддержка,уроки на Youtube,закрыты й форум с поддержкой
Русского,Англий ского,Немецкого языков.
При покупке по данным ссылкам предоставляется скидка!!!
Вам доступно (бесплатно) регистрация и в личном кабинете у Вас будет
возможность ознакомиться с документацией перед возможной покупкой.

coffey projects christchurch
aapc project xtern
project joiner mount stewart
the clio project managing heterogeneity
project veterinary clinic worden mt

Добавить комментарий



ВКонтакте  facebook  GitHub  YouTube  Twitter
Вы здесь: Начало Статьи о разном Цифровой КИХ фильтр на Verilog для цветомузыки