Что делать, если вам нужно разместить большой цифровой фильтр на FPGA? Что делать, если плата уже подключена? Аппаратное обеспечение старое? В проекте осталось мало места? В этой теме будет обсуждаться одна из возможных реализаций цифрового КИХ-фильтра на ПЛИС Altera Cyclone II EP2C15. По сути это продолжение Эта тема из песочницы.
Будет показано, как сделать сдвиговый регистр в оперативной памяти, при этом снизив затраты на LE, и как получить из него цифровой фильтр.
Как работает фильтр? Основная операция – умножение и накопление.
Коэффициенты фильтра умножаются на значения в сдвиговом регистре и суммируются.
Всё, если не вдаваться в подробности.
Необходимые ингредиенты объявлены, теперь приступим к делу.
Умножение и накопление
Мы считаем, что уже определились с нужным типом АЧХ фильтра, порядком фильтра, получили его коэффициенты и знаем скорость входных данных.Еще лучше, если эти параметры каким-то образом параметризуются.
Именно это мы и постараемся сделать.
Вот такая получилась моя реализация умножения и накопления:
Почему ADDR_WIDTH = 9? Потому что порядок фильтра выбран 2^9 = 512. Во-первых, это сделано для удобства получения частоты с делителя или ФАПЧ.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
Во-вторых, у меня была возможность увеличить частоту в 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 #Программирование микроконтроллеров
-
Стоит Ли Мне Регистрировать Доменное Имя?
19 Oct, 24 -
Amazon Покупает Audible.com
19 Oct, 24 -
Почему Я Ненавижу Фреймворки
19 Oct, 24 -
Первый Коворкинг-Офис В Киеве: Фото
19 Oct, 24 -
Телефон - (Не)Модем
19 Oct, 24