Jump to content

    
Halfback

Фильтр скользящее среднее

Recommended Posts

Доброго времени суток!

Нужна помощь в оптимизации кода на Verilog, среда разработки Quartus II 13.0.1 , плисина Циклон 3.

Вход/выход 16-битный, клок 120МГц

Размер окна фильтра на 16 точек

 

module MovAverageFilter(clk,datain,dataout);

input  clk;
input  [15:0] datain;
output reg [15:0] dataout;


reg [15:0] FIFO [0:15];
integer i;

initial begin
	for(i = 15; i >= 0; i=i-1) FIFO[i] = 16'd0;
	end


always@(posedge clk) begin

	dataout <= (FIFO[0]+FIFO[1]+FIFO[2]+FIFO[3]+FIFO[4]+FIFO[5]+FIFO[6]+FIFO[7]+FIFO[8]+FIFO[9]+ 		FIFO[10]+FIFO[11]+FIFO[12]+FIFO[13]+FIFO[14]+FIFO[15])>>4;	

	end

	
always@(negedge clk) begin
      
	for(i = 15; i > 0; i=i-1) FIFO[i] <= FIFO[i-1]; 	
	FIFO[0] <= datain;	
	end

endmodule

 

Есть желание увеличить размер окна с 16 до 32 или даже до 64 но тогда нужно будет больше сумматоров и я беспокоюсь за обеспечение синхронности схемы. Может есть более изящный вариант брать сумму точек в окне и делить на их кол-во?

 

Edited by Halfback

Share this post


Link to post
Share on other sites

Приветствую!

2 hours ago, Halfback said:

Есть желание увеличить размер окна с 16 до 32 или даже до 64 но тогда нужно будет больше сумматоров и я беспокоюсь за обеспечение синхронности схемы. Может есть более изящный вариант брать сумму точек в окне и делить на их кол-во?

Для  скользящего  среднего при котором отсчеты приходят по одному  достаточно  одного сложения и одного вычитания  для окна любой длинны. :yes3:

Удачи! Rob.

Share this post


Link to post
Share on other sites

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

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

Со скользящей медианой чуть сложней, но там тоже на кольцевом буфере все завязано (а для построения qsort используется массив указателей).

Share this post


Link to post
Share on other sites
12 часов назад, RobFPGA сказал:

Для  скользящего  среднего при котором отсчеты приходят по одному  достаточно  одного сложения и одного вычитания  для окна любой длинны. :yes3:

Наверное я правильно понял что вот это

dataout <= (FIFO[0]+FIFO[1]+FIFO[2]+FIFO[3]+FIFO[4]+FIFO[5]+FIFO[6]+FIFO[7]+FIFO[8]+FIFO[9]+FIFO[10]+FIFO[11]+FIFO[12]+FIFO[13]+FIFO[14]+FIFO[15])>>4;

надо заменить на вот это

dataout <= (dataout + FIFO[0]-FIFO[15])>>4;

Другой вопрос что в Си-подобном языке dataout справа в скобках будет расценено как предыдущее значение а слева - как текущее значение. Так ли это будет в верилоге? (Я всё равно в железе проверю)

Share this post


Link to post
Share on other sites

по заданию требуется возможность генерации модуля с разным размером окна и разрешены размеры окна по степеням двойки, поэтому в модуле можно в качестве параметра задавать степень (FIFO_LEN_LOG) и на основе ее рассчитывать размер окна (FIFO_LEN)

    parameter FIFO_LEN_LOG = 4;
    localparam FIFO_LEN = (1 << FIFO_LEN_LOG);

обьявление, инициализация и работа внутренней памяти (FIFO) останется прежней, но с учетом новых параметров

    reg [15:0] FIFO [0:FIFO_LEN-1];
    integer i;

    initial
    begin
        for(i = FIFO_LEN-1; i >= 0; i=i-1)
        begin
            FIFO[i] = 16'd0;
        end
    end
    
    always @(posedge clk)
    begin

        for(i = FIFO_LEN-1; i > 0; i=i-1)
        begin
            FIFO[i] <= FIFO[i-1];
        end
        
        FIFO[0] <= datain;
        
    end

как было замечено выше в обсуждении темы, для вычисления результата суммирования (fifo_sum) с использованием предыдущего результата рассчета количество сложений может быть уменьшено

    reg [FIFO_LEN_LOG+15:0] fifo_sum = 0;
    
    always @(posedge clk)
    begin
        fifo_sum <= fifo_sum + datain - FIFO[FIFO_LEN-1];    
    end

деление результата суммирования также можно записать с учетом параметров

    assign dataout = fifo_sum >> FIFO_LEN_LOG;

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

Spoiler



module MovAverageFilter
#(
    parameter FIFO_LEN_LOG = 4//,
)
(
    input  clk,
    input  [15:0] datain,
    output [15:0] dataout//,
);
    localparam FIFO_LEN = (1 << FIFO_LEN_LOG);
    
    reg [15:0] FIFO [0:FIFO_LEN-1];
    integer i;

    initial
    begin
        for(i = FIFO_LEN-1; i >= 0; i=i-1)
        begin
            FIFO[i] = 16'd0;
        end
    end
    
    always @(posedge clk)
    begin

        for(i = FIFO_LEN-1; i > 0; i=i-1)
        begin
            FIFO[i] <= FIFO[i-1];
        end
        
        FIFO[0] <= datain;
        
    end
    
    reg [FIFO_LEN_LOG+15:0] fifo_sum = 0;
    
    always @(posedge clk)
    begin
        fifo_sum <= fifo_sum + datain - FIFO[FIFO_LEN-1];	
    end
    
    assign dataout = fifo_sum >> FIFO_LEN_LOG;
    
endmodule


 

модуль проверялся вот таким тестбенчем

Spoiler

`timescale 1 ns / 1 ps

module MovAverageFilter_tb();

reg clk = 0;

always #(10.0/2)
begin
    clk = ~clk;
end

reg [15:0] datain;


MovAverageFilter_16 MovAverageFilter_16_i
(
	.clk(clk),
	.datain(datain)//,
);

MovAverageFilter 
#(
    .FIFO_LEN_LOG(4)//,
)
MovAverageFilter_i_4
(
	.clk(clk),
	.datain(datain)//,
);

MovAverageFilter 
#(
    .FIFO_LEN_LOG(6)//,
)
MovAverageFilter_i_6
(
	.clk(clk),
	.datain(datain)//,
);

real signal;
real signal_noise;


initial
begin
    datain = 0;
    signal = 0;
    signal_noise = 0;
end

always @(posedge clk)
begin
    
    //
    signal = (1-1e-5)*signal + (5e-8)*$random();
    signal_noise = signal + (1e-6)*$random() + 2.0**15;
    signal_noise = signal_noise + (1e-6)*$random();
    
    //
    datain = signal_noise;
    
    if (signal_noise>=(2.0**16-1))
    begin
        datain = (2.0**16-1);
    end
    if (signal_noise<=(0))
    begin
        datain = (0);
    end
    
end

endmodule

тут

(signal) - низкочастотный сигнал
(signal_noise) - зашумленный низкочастотный сигнал

(MovAverageFilter_16_i.dataout) - выход оригинального блока
(MovAverageFilter_i_4.dataout) - выход параметризованного варианта с размером окна как у оригинального
(MovAverageFilter_i_6.dataout) - выход параметризованного варианта с большим размером окна

(MovAverageFilter_16) - оригинальный блок для сравнения результатов

Spoiler


module MovAverageFilter_16
(
    input  clk,
    input  [15:0] datain,
    output [15:0] dataout//,
);
    
    reg [15:0] FIFO [0:15];
    integer i;

    initial
    begin
        for(i = 15; i >= 0; i=i-1)
        begin
            FIFO[i] = 16'd0;
        end
    end
    
    always @(posedge clk)
    begin

        for(i = 15; i > 0; i=i-1)
        begin
            FIFO[i] <= FIFO[i-1];
        end
        
        FIFO[0] <= datain;
        
    end
    
    reg [19:0] fifo_sum = 0;
    
    always @(posedge clk)
    begin
        fifo_sum <=
        (
            +FIFO[ 0]+FIFO[ 1]+FIFO[ 2]+FIFO[ 3]
            +FIFO[ 4]+FIFO[ 5]+FIFO[ 6]+FIFO[ 7]
            +FIFO[ 8]+FIFO[ 9]+FIFO[10]+FIFO[11]
            +FIFO[12]+FIFO[13]+FIFO[14]+FIFO[15]
        );	
    end
    
    assign dataout = fifo_sum >> 4;
    
endmodule

 

 

результаты моделирования

res_00.thumb.png.17e00d3146d97c30fb58654536ff16d6.png

 

сигнал с выхода параметризованного варианта с размером окна как у оригинального повторяет сигнал с выхода оригинального блока, сигнал с выхода параметризованного варианта с большим размером окна отсеивает больше шума

 

Share this post


Link to post
Share on other sites

stealthisname

спасибо большое!!!

есть еще идея оптимизировать в части 

for(i = FIFO_LEN-1; i > 0; i=i-1)
        begin
            FIFO[i] <= FIFO[i-1];
        end

Идея в том чтобы этого не делать сдвиг массива а организовать кольцевой буфер как посоветовал Eddy_Em

Share this post


Link to post
Share on other sites

Оптимизировал. Проверил, работает вроде также.

Скрытый текст

module MovAverageFilter
#(
    parameter FIFO_LEN_LOG = 4//,
)
(
    input  clk,
    input  [15:0] datain,
    output [15:0] dataout//,
);
    localparam FIFO_LEN = (1 << FIFO_LEN_LOG);
    
    reg [15:0] FIFO [0:FIFO_LEN-1];
     reg [FIFO_LEN_LOG+15:0] fifo_sum = 0;
    integer i;
     
     reg [FIFO_LEN_LOG-1:0] k = 0;

    initial
    begin
        for(i = FIFO_LEN-1; i >= 0; i=i-1)  FIFO = 16'd0;
    end

    
     always @(posedge clk)
        begin        
        fifo_sum <= fifo_sum + datain - FIFO[k];        
        end
        
    always @(negedge clk)
        begin        
        FIFO[k] <= datain;
        k<=k+1;        
        end
    
    assign dataout = fifo_sum >> FIFO_LEN_LOG;
    
endmodule

 

P.S. Бегающий указатель "к" специально не обнуляю, в железе это само собой происходит, т.е. переполнения нет.

Edited by Halfback

Share this post


Link to post
Share on other sites
1 час назад, Halfback сказал:

Оптимизировал. Проверил, работает вроде также.

  Скрыть содержимое

    always @(negedge clk)

 

        begin        
        FIFO[k] <= datain;
        k<=k+1;        
        end
 

 

 

Не надо использовать задний фронт.

Share this post


Link to post
Share on other sites
22 hours ago, Halfback said:

dataout <= (FIFO[0]+FIFO[1]+FIFO[2]+FIFO[3]...

Неужели таким способом частота 120 МГц была достижима? При 16 битных то семплах, на какой интересно элементной базе и как оно структурно вообще могло такой сумматор построить. Никогда не задумывался я, а что, там N каскадов сумматоров организуется синтезатором? У меня была бы изначально идея с кольцевым буфером, не знаю даже как иначе это эффективно могло быть сделано.

Share this post


Link to post
Share on other sites
3 минуты назад, AVR сказал:

Неужели таким способом частота 120 МГц была достижима?

окно на 16 при таком способе худо-бедно но работает, на 32 окно пробовал - не работает.

3 часа назад, andrew_b сказал:

Не надо использовать задний фронт.

почему?

Edited by Halfback

Share this post


Link to post
Share on other sites
16 minutes ago, Halfback said:

окно на 16 при таком способе худо-бедно но работает, на 32 окно пробовал - не работает.

юзайте

https://drive.google.com/drive/folders/1EQpuRnWBgQJ1AEHo5MNp98cgfFQFPJDS?usp=sharing

 

 

 

Share this post


Link to post
Share on other sites
4 часа назад, Halfback сказал:

почему?

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

Share this post


Link to post
Share on other sites
21 hours ago, Halfback said:

почему?

Потому что Вы:

а) уменьшаете допустимое время сетап от rising_edge флопов до falling_edge (для этих путей требования по времянке х2)

б) то же самое для FF от falling_edge до следующих rising_edge

в) проект становится сложным в понимании

г) в этом нет острой необходимости

д) скважность клока может быть не 50% (по техническим причинам PLL) тогда у Вас пункт а) и б) будут гулять + джиттер

е) это не красиво тем более что г)

ж) тут можно подставить ещё с 10-ток пунктов...

Если хочется быстродействия - поднимите частоту и не занимайтесь ерундой.

Share this post


Link to post
Share on other sites

Nick_K

трудно поспорить с перечисленными Вами пунктами. Пока мне не очень понятно насколько корректно будет работать описание внутри posegde и negedge если их засунуть в один posegde. Надо проверять, т.к. вычисление должно происходить за один такт. Я с плисиной и verilog еще сравнительно мало работаю.

Edited by Halfback

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.