Верилог. Цифровой Фильтр В Оперативной Памяти

Что делать, если вам нужно разместить большой цифровой фильтр на FPGA? Что делать, если плата уже подключена? Аппаратное обеспечение старое? В проекте осталось мало места? В этой теме будет обсуждаться одна из возможных реализаций цифрового КИХ-фильтра на ПЛИС Altera Cyclone II EP2C15. По сути это продолжение Эта тема из песочницы.

Будет показано, как сделать сдвиговый регистр в оперативной памяти, при этом снизив затраты на LE, и как получить из него цифровой фильтр.

Как работает фильтр? Основная операция – умножение и накопление.

Коэффициенты фильтра умножаются на значения в сдвиговом регистре и суммируются.

Всё, если не вдаваться в подробности.

Необходимые ингредиенты объявлены, теперь приступим к делу.



Умножение и накопление
Мы считаем, что уже определились с нужным типом АЧХ фильтра, порядком фильтра, получили его коэффициенты и знаем скорость входных данных.

Еще лучше, если эти параметры каким-то образом параметризуются.

Именно это мы и постараемся сделать.

Вот такая получилась моя реализация умножения и накопления:

  
  
  
  
   

module mult #(parameter COEF_WIDTH = 24, parameter DATA_WIDTH = 16, parameter ADDR_WIDTH = 9, parameter MULT_WIDTH = COEF_WIDTH + DATA_WIDTH) ( input wire clk, input wire en, input wire [ (ADDR_WIDTH-1) : 0 ] ad, input wire signed [ (COEF_WIDTH-1) : 0 ] coe, input wire signed [ (DATA_WIDTH-1) : 0 ] pip, output wire signed [ (DATA_WIDTH-1) : 0 ] dout ); wire signed [(MULT_WIDTH-1) : 0 ] mu = coe * pip; reg signed [ (MULT_WIDTH-1) : 0 ] rac = {(MULT_WIDTH){1'b0}}; reg signed [ (DATA_WIDTH-1) : 0 ] ro = {DATA_WIDTH{1'b0}}; assign dout = ro; always @(posedge clk) if(en) if(ad == {ADDR_WIDTH{1'b0}}) begin rac <= mu; ro <= rac[ (MULT_WIDTH-2) -: (DATA_WIDTH) ]; end else rac <= rac + mu; endmodule

Почему ADDR_WIDTH = 9? Потому что порядок фильтра выбран 2^9 = 512. Во-первых, это сделано для удобства получения частоты с делителя или ФАПЧ.

Во-вторых, у меня была возможность увеличить частоту в 512 раз, ведь частота дискретизации составляла 16 кГц.

Но об этом позже.

Конечно, не очень читабельно из-за параметризации, но разобраться можно.



Коэффициенты фильтра
Вы читали тему песочницы по ссылке вверху? Был ли шаблон RAM? Этот шаблон нам больше не подходит. Я не мог заставить эту оперативную память читать/записывать за один такт. Может это все по незнанию, но коэффициенты фильтра теперь хранятся в этом модуле:

module coef #(parameter DATA_WIDTH=24, parameter ADDR_WIDTH=9) ( input wire [(DATA_WIDTH-1):0] data, input wire [(ADDR_WIDTH-1):0] addr, input wire we, input wire clk, output wire [(DATA_WIDTH-1):0] coef_rom ); reg [DATA_WIDTH-1:0] rom[2**ADDR_WIDTH-1:0]; reg [(DATA_WIDTH-1):0] data_out; assign coef_rom = data_out; initial begin rom[0 ] = 24'b000000000000000000000000; rom[1 ] = 24'b000000000000000000000001; //new year tree rom[510] = 24'b000000000000000000000001; rom[511] = 24'b000000000000000000000000; end always @ (posedge clk) begin data_out <= rom[addr]; if (we) rom[addr] <= data; end endmodule

Приблизительно 508 коэффициентов были опущены, чтобы не вызывать беспокойства.

Почему 24 бита, а не 16? Спектрум мне больше нравится.

Но это не важно.

Изменение коэффициентов – не долгая задача.

Кроме того, вы можете прикрепить файл инициализации памяти с помощью сценария $readmem или $readmemh после первоначального запуска.



Регистр сдвига
Собственно, это основная причина, почему я пишу это.

Может быть, кто-то подумает про себя, что уже знал это.

Может быть, он подумает еще что-нибудь об авторе чего-нибудь хорошего, что-нибудь о колесе.

Здесь будет написано, как сделать сдвиговый регистр в оперативной памяти с помощью обертки.

Наверное, каждый читал в справочнике по ПЛИС, что ОЗУ может работать как сдвиговый регистр.

Как? Я так и сделал, ничего сложного в этом нет. Но почему? Семейство Cyclone позиционируется как устройства, ориентированные на память: «устройства имеют встроенные структуры памяти для удовлетворения потребностей встроенной памяти в конструкциях FPGA».

И нужно уметь этой памятью пользоваться.

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

Оперативная память аналогична случаю хранения коэффициентов фильтра:

module pip #(parameter DATA_WIDTH=16, parameter ADDR_WIDTH=9) ( input wire [(DATA_WIDTH-1):0] data, input wire [(ADDR_WIDTH-1):0] read_addr, write_addr, input wire we, input wire clk, output wire [(DATA_WIDTH-1):0] pip_ram ); reg [DATA_WIDTH-1:0] ram[2**ADDR_WIDTH-1:0]; reg [(DATA_WIDTH-1):0] data_out; assign pip_ram = data_out; always @ (posedge clk) begin data_out <= ram[read_addr]; if (we) ram[write_addr] <= data; end endmodule

Единственное, если оперативная память не инициализирована, она автоматически заполняется нулями.

Кстати, этот прием можно использовать при записи коэффициентов фильтра, если они меньше 2^N. Теперь сама обертка:

module upr #(parameter COEF_WIDTH = 24, parameter DATA_WIDTH = 16, parameter ADDR_WIDTH = 9) ( input wire clk, input wire en, input wire [ (DATA_WIDTH-1) : 0 ] ram_upr, input wire [ (DATA_WIDTH-1) : 0 ] data_in, output wire [ (DATA_WIDTH-1) : 0 ] upr_ram, output wire we_ram, output wire [ (ADDR_WIDTH-1) : 0 ] adr_out ); assign upr_ram = (r_adr == {ADDR_WIDTH{1'b0}}) ? data_in : ram_upr; assign we_ram = (r_state == state1) ? 1'b1 : 1'b0; assign adr_out = r_adr; reg [ 2 : 0 ] r_state = state0; localparam state0 = 3'b001, state1 = 3'b010, state2 = 3'b100; reg [ (ADDR_WIDTH-1) : 0 ] r_adr = {ADDR_WIDTH{1'b0}}; always @(posedge clk) if(en) begin case(r_state) state0: r_state <= state1; state1: r_state <= state1; state2: begin end endcase end always @(posedge clk) case(r_state) state0: r_adr <= {ADDR_WIDTH{1'b0}}; state1: r_adr <= r_adr + 1'b1; state2: begin end endcase endmodule

Этот же адрес подается в ОЗУ с коэффициентами и сдвиговым регистром.

Обратная связь через ОЗУ из сдвигового регистра отправляет в модуль предыдущее значение, которое записывается по текущему адресу.

Таким образом, сдвиг осуществляется не за один такт, а на каждое одно значение.

В каждый нулевой адрес записывается входное слово.

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

Теперь этот модуль работает в два раза быстрее, а значит, при прочих равных условиях, он еще и половину времени простаивает. Теоретически эту половину можно чем-то занять.

Это может быть перерасчет коэффициентов фильтра для адаптивной фильтрации или работа второго фильтра (что-то вроде временного интервала).

Здесь ничего этого нет и ФСМ здесь не нужен, но я все равно оставил этот атавизм.

Удалить FSM всегда проще, чем добавить.



Общий
Здесь приведу топовый файл, пришедший от шимантика:

module filtr_ram( CLK, D_IN, MULT ); input CLK; input [15:0] D_IN; output [15:0] MULT; wire SYNTHESIZED_WIRE_13; wire [15:0] SYNTHESIZED_WIRE_1; wire [8:0] SYNTHESIZED_WIRE_14; wire SYNTHESIZED_WIRE_4; wire [15:0] SYNTHESIZED_WIRE_15; wire SYNTHESIZED_WIRE_6; wire [0:23] SYNTHESIZED_WIRE_8; wire [23:0] SYNTHESIZED_WIRE_11; assign SYNTHESIZED_WIRE_4 = 1; assign SYNTHESIZED_WIRE_6 = 0; assign SYNTHESIZED_WIRE_8 = 0; pip b2v_inst( .

we(SYNTHESIZED_WIRE_13), .

clk(CLK), .

data(SYNTHESIZED_WIRE_1), .

read_addr(SYNTHESIZED_WIRE_14), .

write_addr(SYNTHESIZED_WIRE_14), .

pip_ram(SYNTHESIZED_WIRE_15)); defparam b2v_inst.ADDR_WIDTH = 9; defparam b2v_inst.DATA_WIDTH = 16; upr b2v_inst1( .

clk(CLK), .

en(SYNTHESIZED_WIRE_4), .

data_in(D_IN), .

ram_upr(SYNTHESIZED_WIRE_15), .

we_ram(SYNTHESIZED_WIRE_13), .

adr_out(SYNTHESIZED_WIRE_14), .

upr_ram(SYNTHESIZED_WIRE_1)); defparam b2v_inst1.ADDR_WIDTH = 9; defparam b2v_inst1.COEF_WIDTH = 24; defparam b2v_inst1.DATA_WIDTH = 16; coef b2v_inst3( .

we(SYNTHESIZED_WIRE_6), .

clk(CLK), .

addr(SYNTHESIZED_WIRE_14), .

data(SYNTHESIZED_WIRE_8), .

coef_rom(SYNTHESIZED_WIRE_11)); defparam b2v_inst3.ADDR_WIDTH = 9; defparam b2v_inst3.DATA_WIDTH = 24; mult b2v_inst5( .

clk(CLK), .

en(SYNTHESIZED_WIRE_13), .

ad(SYNTHESIZED_WIRE_14), .

coe(SYNTHESIZED_WIRE_11), .

pip(SYNTHESIZED_WIRE_15), .

dout(MULT)); defparam b2v_inst5.ADDR_WIDTH = 9; defparam b2v_inst5.COEF_WIDTH = 24; defparam b2v_inst5.DATA_WIDTH = 16; endmodule

Сразу видно, что можно улучшить, чтобы стало красивее.

Теперь давайте еще раз поговорим о том, что произошло.

Главный недостаток – этот фильтр полностью серийный.

То есть рабочую частоту фильтра необходимо увеличить в 2^(ADDR_WIDTH) раза относительно скорости входных данных.

Эту проблему можно решить, если импульсная характеристика фильтра будет симметричной, но в этом случае ОЗУ сдвигового регистра придется разделить на два модуля, на которые будут отправляться 2 адреса, значения из ОЗУ.

будут сложены и умножены в модуле mult, которому нужно будет добавить еще один вход. Тогда частоту нужно будет увеличить в 2^(ADDR_WIDTH-1) раз.

Исходники и проект в Quartus 9.0 ifolder.ru/27556340 Теги: #FPGA #verilog #Программирование микроконтроллеров

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.