How to implement moving average in VHDL

Moving Average

In some application, we need to perform moving average on input data in our design.

In the moving average for each incoming sample, we need to perform an equation like

Starting from this equation we should perform the average computation for each input sample.

Moving average equation

If N=4, I mean

y(0) = 1/4 (x0+x1+x2+x3)
y(1) = 1/4 (x1+x2+x3+x4)

At the moment, leave the 1/4 apart and focus on the add section.

y(2) = (x2+x3+x4+x5)

in other words

y(2) = y(1) + x5 - x1

This consideration can simplify the architecture of the moving average block using a simple accumulator adding the incoming sample and subtracting the outcoming as in Figure2:

Figure 2 – Moving average architecture for N=4

The division by N became very simple in case of N is a power of two. In fact, in this case, the division is performed simply as right shift.

In case of N is not a power of two you can refer to this post where you can learn how to implement a division by constant.

 

VHDL code for moving average

Here below is presented the VHDL code for the moving average architecture of Figure 2. In order to work fine, all the registers shall be in the reset state as an initial condition. This condition is implemented using a synchronous reset “i_sync_reset”.

This VHDL implementation of moving average algorithm configures the moving average length as a power of two. The generic value for moving average length is passed as log2 value so it will be simple to perform the output right shift.

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity moving_average is
generic (
  G_NBIT                     : integer := 8;
  G_AVG_LEN_LOG              : integer := 2 );
port (
  i_clk                      : in  std_logic;
  i_rstb                     : in  std_logic;
  i_sync_reset               : in  std_logic;
  -- input
  i_data_ena                 : in  std_logic;
  i_data                     : in  std_logic_vector(G_NBIT-1 downto 0);
  -- output
  o_data_valid               : out std_logic;
  o_data                     : out std_logic_vector(G_NBIT-1 downto 0));
end moving_average;

architecture rtl of moving_average is

type t_moving_average is array (0 to 2**G_AVG_LEN_LOG-1) of signed(G_NBIT-1 downto 0);

signal p_moving_average                 : t_moving_average;
signal r_acc                            : signed(G_NBIT+G_AVG_LEN_LOG-1 downto 0);  -- average accumulator
signal r_data_valid                     : std_logic;

begin

p_average : process(i_clk,i_rstb)
begin
  if(i_rstb='0') then
    r_acc              <= (others=>'0');
  p_moving_average   <= (others=>(others=>'0'));
  r_data_valid       <= '0';
  o_data_valid       <= '0';
  o_data             <= (others=>'0');
  elsif(rising_edge(i_clk)) then
  r_data_valid       <= i_data_ena;
  o_data_valid       <= r_data_valid;
  if(i_sync_reset='1') then
    r_acc              <= (others=>'0');
    p_moving_average   <= (others=>(others=>'0'));
  elsif(i_data_ena='1') then
    p_moving_average   <= signed(i_data)&p_moving_average(0 to p_moving_average'length-2);
    r_acc              <= r_acc + signed(i_data)-p_moving_average(p_moving_average'length-1);
  end if;
  o_data             <= std_logic_vector(r_acc(G_NBIT+G_AVG_LEN_LOG-1 downto G_AVG_LEN_LOG));  -- divide by 2^G_AVG_LEN_LOG
  
  end if;
end process p_average;

end rtl;

 

VHDL Moving average ModelSim simulation

 

In the next figures is reported a ModelSim simulation of the VHDL code implementing moving average algorithm. Figure3 is relative to input sample with continuous data enable.

 

Figure 3 – simulation of moving average on 4 input samples continuous enable
Figure 4 – simulation of moving average on 4 input samples not continuous enable

 

In both simulations is highlighted the moving average compute the following

1+2+3+4 = 10;  => 10/4 = 2

7+8+9+10 = 34; => 34/4 = 8

 


Conclusion

In this post, we did address the implementation of a moving average algorithm in VHDL using a very efficient hardware structure that allows the user to implement the moving average using only a data pipe of the dimension of the moving average length and an accumulator.

The control logic is implemented as a synchronous reset that guarantees a correct initial status of the data pipe and accumulator.

In case of very long moving average length, you can adopt the FIFO implementation of the data pipe. The data pipe implemented as a FIFO is treated in this post.

In the case of FIFO implementation, an additional control logic shall be introduced to handle the initial condition of the FIFO and of the accumulator.

 

 


References

[1] https://www.altera.com/

[2] https://www.mathworks.com/

[3] http://www.scilab.org

[4] https://www.gnu.org/software/octave/

 

22 thoughts to “How to implement moving average in VHDL”

  1. library IEEE;
    use ieee.std_logic_1164.all;
    use ieee.std_logic_arith.all;
    use IEEE.NUMERIC_STD.ALL;
    use ieee.std_logic_unsigned.all;

    entity counter is

    port(Clock: in std_logic;
    reset: in std_logic;
    count: out std_logic_vector(0 to 15);
    dout:out std_logic);
    end counter;

    architecture behavioral of counter is
    signal tmp: std_logic_vector(0 to 15);

    begin

    process (Clock, reset)

    begin
    if (reset=’1′) then
    dout<='0';
    tmp<="0000000000000000";
    else if (Clock'event and Clock='1') then
    tmp<=tmp + 1;
    count=”XC8″&”X1068″)then
    dout=”X1068″&”X11F8″)
    dout<='0';
    end if;
    end if;
    end process;
    end behavioral;
    ——is this correct?—–plss…post the correct programm…….

    1. It isn’t correct, but you should clarify what you want to do.
      When you want to treat number it is better to use std_logic_vector(15 downto 0); instead of “to”.
      I don’t understand what the code should do, for example, “dout” is std_logic so you cannot write something like
      dout=”X1068″&”X11F8″

  2. I want the vhdl code for moving average using the component based approach e.g calling multipliers and adders in main code as components…plzzz

  3. moving average algorithm gives floating point values sometimes depending upon the input .How should we handle that floating numeric values and display the correct output,because in your code the outputs are in integer format?

    thank you

    1. this example is on integer values.
      Basically FPGAs work on integer values, unless you use floating point, but it not the case.
      Only in the last years, the FPGAs are implementing floating point block computation, but it is a particular case.

  4. Quick Question, do I still need to divide by N on the output, N being the G_AVG_LEN_LOG or is this done on line 49?
    Cheers

    1. just implement the memory depth 10.
      In this case you cannot divide the result simply shifting to the right since 10 is not power of 2.
      You can divide by 8 or 16

  5. Hi, How about making the “G_AVG_LEN_LOG” as a variable (e.g input register)?
    Many thanks for the post.
    I have a warning as “signal G_AVG_LEN_LOG is used in subtype-indication /type-definition” in the line of “signal r_acc : signed(G_NBIT+G_AVG_LEN_LOG-1 downto 0); — average accumulator”

    library ieee;
    use ieee.std_logic_1164.all;
    use ieee.numeric_std.all;

    entity moving_average is
    generic (
    G_NBIT : integer := 16);
    port (
    i_clk : in std_logic;
    i_rstb : in std_logic;
    i_sync_reset : in std_logic;
    — input
    i_data_ena : in std_logic;
    i_data : in std_logic_vector(G_NBIT-1 downto 0);
    coeff : in std_logic_vector(G_NBIT-1 downto 0);
    — output
    o_data_valid : out std_logic;
    o_data : out std_logic_vector(G_NBIT-1 downto 0));
    end moving_average;

    architecture rtl of moving_average is

    signal G_AVG_LEN_LOG : integer;
    type t_moving_average is array (0 to 2**G_AVG_LEN_LOG-1) of signed(G_NBIT-1 downto 0);
    signal p_moving_average : t_moving_average;
    signal r_acc : signed(G_NBIT+G_AVG_LEN_LOG-1 downto 0); — average accumulator
    signal r_data_valid : std_logic;

    begin
    o_data <= i_data when i_data_ena='0';
    G_AVG_LEN_LOG <= to_integer(signed(coeff));
    p_average : process(i_clk,i_rstb)
    begin
    if(i_rstb='0') then
    r_acc ‘0’);
    p_moving_average (others=>’0′));
    r_data_valid <= '0';
    o_data_valid <= '0';
    o_data ‘0’);
    elsif(rising_edge(i_clk)) then
    r_data_valid <= i_data_ena;
    o_data_valid <= r_data_valid;
    if(i_sync_reset='1') then
    r_acc ‘0’);
    p_moving_average (others=>’0′));
    elsif(i_data_ena=’1′) then
    p_moving_average <= signed(i_data)&p_moving_average(0 to p_moving_average'length-2);
    r_acc <= r_acc + signed(i_data)-p_moving_average(p_moving_average'length-1);
    end if;
    o_data <= std_logic_vector(r_acc(G_NBIT+G_AVG_LEN_LOG-1 downto G_AVG_LEN_LOG)); — divide by 2^G_AVG_LEN_LOG

    end if;
    end process p_average;
    end rtl;

  6. Hi, could you please help me with making the moving average length an input register or a variable, instead of a generic value.
    Many thanks.

    1. just replace the generic value with a number. The generic is a static value known at compile time. It is used to write a parametric code.

  7. How can we design a model where n numbers are stores in n registers and arithmetic mean of the numbers in registers is calculated?

  8. hi, i found this code about moving average filter.

    library ieee;
    use ieee.std_logic_1164.all;
    use ieee.numeric_std.all;
    use std.textio.all;
    use ieee.std_logic_textio.all;

    entity AvgFilter is
    generic (
    G_DATA_W :integer := 16;
    G_FIL_L :integer := 4
    );
    port (
    clk :in std_logic;
    rst :in std_logic;
    en :in std_logic;
    iv_data :in std_logic_vector(G_DATA_W-1 downto 0);
    ov_avg :out std_logic_vector(G_DATA_W-1 downto 0)
    );
    end entity;

    architecture AvgFilter_rtl of AvgFilter is

    — calculate number of bits needed to extend sum vector
    function sumlog2(m :positive) return natural is
    begin
    for index in 1 to 30 loop
    if (m <= 2**index) then
    return(index);
    end if;
    end loop;
    return(31);
    end function;

    signal en_reg :std_logic;

    — array for storing samples
    type t_arr_FilL_x_data is array (G_FIL_L-1 downto 0) of unsigned(G_DATA_W-1 downto 0);
    signal a_samples :t_arr_FilL_x_data;

    begin
    reg: process(clk)

    — to add G_FIL_L values is needed sumlog2(G_FIL_L) more bits for result
    variable v_sum :unsigned(G_DATA_W+sumlog2(G_FIL_L)-1 downto 0);

    begin
    if rising_edge(clk) then
    if rst = '1' then
    en_reg <= '0';
    a_samples (others => ‘0’));
    v_sum := (others => ‘0’);
    ov_avg ‘0’);
    else
    en_reg <= en;

    a_samples(0) <= unsigned(iv_data);
    for i in 1 to G_FIL_L-1 loop
    a_samples(i) ‘0’);
    if en_reg = ‘1’ then
    for i in 0 to G_FIL_L-1 loop
    v_sum := v_sum + resize(a_samples(i), v_sum’length);
    end loop;
    end if;

    ov_avg <= std_logic_vector(v_sum(G_DATA_W+sumlog2(G_FIL_L)-1 downto sumlog2(G_FIL_L))); — divide by sumlog2(G_FIL_L)
    end if;
    end if;
    end process;
    end architecture;

    and file dataIn.dat : 890B
    0D00
    0044
    0048
    B10E
    8F1A
    00A0
    0A0A

    But when I simulation it just only takes the top 6 values, and the average filter result is only divided by 4. can you help me explain
    this one and how can i handle more than 6 values. Thanks you so much

        1. the concept is the same.
          you can start from this architecture. If you need different architecture, please, post a possible architecture the community can help you

  9. As an alternative, you can implement an exponential moving average to remove the need to store the previous values. Limiting the smoothing factor to (negative) powers of two will make it possible to use bit shifting instead of multipliers/dividers.
    Mean(n) = Mean(n – 1) * (2^N – 1) / 2^N + Data(n) / 2^N = Mean(n – 1) – Mean(n – 1) >> N + Data(n) >> N

    1. Hi thanks for your feedback.
      The implementation you are suggesting the quantization error is increasing since you shift the data before adding its value Data(n) >> N

Leave a Reply

Your email address will not be published. Required fields are marked *