Будет рассказано, как сделать сдвиговый регистр на RAM, уменьшив при этом затраты 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. Во-первых, это сделано для простоты получения частоты с делителя или PLL. Во-вторых у меня была возможность повышать частоту в 512 раз, потому что sample rate был 16 кГц. Но об этом дальше. Конечно не очень читабельно из-за параметризации, но разобраться можно.
Коэффициенты фильтра
Прочли топик из песочницы по ссылке, что была наверху? Там был шаблон RAM? Вот этот шаблон больше нас не устраивает. Не получилось у меня заставить ту 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? Спектр мне больше нравится. Но это не принципиально. Поменять коэффициенты — занятие не долгое. К тому же можно прикрепить файл инициализации памяти скриптом $readmemb или $readmemh после initial begin.
Сдвиговый регистр
Вот собственно основная причина, почему я это пишу. Может кто-то подумает про себя, что это и так знал. Может еще что подумает об авторе хорошего, что-то там про колесо.
Тут будет написано, как на RAM сделать сдвиговый регистр при помощи обертки. Наверно каждый читал в handbook на свою FPGA о том, что RAM может работать, как сдвиговый регистр. Как? У меня получилось, в этом нет ничего сложного. Только зачем? Семейство Cyclone позиционируется, как устройства с уклоном на память «devices feature embedded memory structures to address the on-chip memory needs of FPGA designs.» И нужно уметь этой памятью пользоваться. Задача решается в два эта: RAM и обертка. RAM аналогична случаю с хранением коэффициентов фильтра:
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
Единственное, что непроинициализировав RAM она автоматически заполняется нулями. Кстати, этим приемом можно пользоваться при записи коэффициентов фильтра, если их меньше, чем 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
Один и тот же адрес подается на RAM с коэффициентами и сдвиговым регистром. По обратной связи через RAM со сдвигового регистра подается на модуль предыдущее значение, которое записывается по текущему адресу. Таким образом сдвиг осуществляется не за один такт, а за каждый по одному значению. На каждый нулевой адрес записывается входное слово.
Зачем я упорно пользуюсь конечным автоматом, хоть некоторые состояния не задействованы? Вспоминаем, что было написано по ссылке в самом начале. Теперь этот модуль работает в два раза быстрее, а значит при прочих равных еще и простаивает половину времени. Теоретически, эту половину можно чем-нибудь занять. Это может быть пересчет коэффициентов фильтра для адаптивной фильтрации, или работа второго фильтра (что-то вроде тайм слота). Тут этого ничего нету и FSM тут не нужен, но я все равно оставил этот атавизм. Убрать 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
Сразу видно, что можно поправить, чтобы стало красивее.
Теперь еще раз о том, что получилось. Главный минус — данный фильтр full serial. То есть частоту работы фильтра нужно поднимать в 2^(ADDR_WIDTH) раз относительно скорости входных данных. Эту проблему можно решить, если импульсный отклик фильтра симметричный, но при этом RAM сдвигового регистра придется разбивать на два модуля, в которые будут посылаться 2 адреса, значения будут из RAM будут складываться и умножаться в модуле mult, которому придется дописывать еще один вход. Тогда частоту нужно будет поднимать в 2^(ADDR_WIDTH-1) раз.
Исходники и проект в Quartus 9.0
ifolder.ru/27556340

