1.4.2.7. Moving average on Red Pitaya

On the Red Pitaya development board, we will build a circuit for signal scaling and filtering with a small averaging sieve that calculates the average of four consecutive samples.

1.4.2.7.1. Generation of an example from the repository

To launch the example from the repository, you need to execute the following command:

cd C:/Users/RedPitaya-FPGA/prj/Examples/Simple_moving_average/
vivado -source make_project.tcl

1.4.2.7.2. Creation of a new project

  1. Make a folder called “Simple Moving Average” in /RedPitaya-FPGA/prj/Examples.

  2. Copy all files from /RedPitaya-FPGA/prj/v0.94 into the newly created folder.

  3. Create a new file named red_pitaya_proc.vhd in the “Simple Moving Average/rtl” directory.

  4. Copy the framework of filter development into the file.

library IEEE;
use IEEE.STD_LOGIC_1164.all;
use IEEE.NUMERIC_STD.all;

entity red_pitaya_proc is
port (
    clk_i   : in  std_logic;                      -- bus clock
    rstn_i  : in  std_logic;                      -- bus reset - active low
    addr_i  : in  std_logic_vector(31 downto 0);  -- bus address
    wdata_i : in  std_logic_vector(31 downto 0);  -- bus write data
    wen_i   : in  std_logic;                      -- bus write enable
    ren_i   : in  std_logic;                      -- bus read enable
    rdata_o : out std_logic_vector(31 downto 0);  -- bus read data
    err_o   : out std_logic;                      -- bus error indicator
    ack_o   : out std_logic;                      -- bus acknowledge signal

    adc_i : in  std_logic_vector(13 downto 0);
    adc_o : out std_logic_vector(13 downto 0)
    );
end red_pitaya_proc;

architecture Behavioral of red_pitaya_proc is
    component moving_average
        port (
            data_i   : in std_logic_vector (13 downto 0);
            clk_i    : in std_logic;
            rstn_i   : in std_logic;
            tag_i    : in unsigned (1 downto 0);
            data_o   : out std_logic_vector (13 downto 0));
    end component;

begin


pbusr: process(clk_i)
begin
    if(rising_edge(clk_i)) then
    if (wen_i or ren_i)='1' then
        ack_o <= '1';
    end if;

    if (rstn_i = '0') then

    else
        case addr_i(19 downto 0) is
            when X"00000" => rdata_o <= X"00000001";

            when others => rdata_o <= X"00000000";
        end case;
    end if;
    end if;
end process;

end Behavioral;

Also, copy red_pitaya_scope.v from /RedPitaya-FPGA/rtl/classic to Simple Moving Average/rtl and rename it to loop_scope.v. Also, change the name of the module inside the file from red_pitaya_scope to loop_scope.

Now create red_pitaya_proc_tb.vhd in /Simple Moving Average/tbn and copy the code there:

library IEEE;
use IEEE.STD_LOGIC_1164.all;
use IEEE.numeric_std.all;

entity red_pitaya_proc_tb is
end red_pitaya_proc_tb;

architecture Behavioral of red_pitaya_proc_tb is

    component red_pitaya_proc
        port (
            clk_i   : in  std_logic;
            rstn_i  : in  std_logic;
            addr_i  : in  std_logic_vector(31 downto 0);
            wdata_i : in  std_logic_vector(31 downto 0);
            wen_i   : in  std_logic;
            ren_i   : in  std_logic;
            rdata_o : out std_logic_vector(31 downto 0);
            err_o   : out std_logic;
            ack_o   : out std_logic;
            adc_i   : in  std_logic_vector(13 downto 0);
            adc_o   : out std_logic_vector(13 downto 0)
        );
    end component;

    signal clk_i   : std_logic := '0';
    signal rstn_i  : std_logic;
    signal addr_i  : std_logic_vector(31 downto 0);
    signal wdata_i : std_logic_vector(31 downto 0);
    signal wen_i   : std_logic;
    signal ren_i   : std_logic;
    signal rdata_o : std_logic_vector(31 downto 0);
    signal err_o   : std_logic;
    signal ack_o   : std_logic;
    signal adc_i   : std_logic_vector(13 downto 0);
    signal adc_o   : std_logic_vector(13 downto 0);

    signal i : integer range 0 to 30 := 0;
    type memory_type is array (0 to 29) of integer range -128 to 127;
    signal sine : memory_type := (0, 16, 31, 45, 58, 67, 74, 77, 77, 74, 67, 58, 45, 31, 16, 0,
                                    -16, -31, -45, -58, -67, -74, -77, -77, -74, -67, -58, -45, -31, -16);

    -- Simulation control
    signal sim : std_logic := '0';

    constant T  : time := 50 ns;

begin
    uut : red_pitaya_proc port map (
                clk_i   => clk_i,
                rstn_i  => rstn_i,
                addr_i  => addr_i,
                wdata_i => wdata_i,
                wen_i   => wen_i,
                ren_i   => ren_i,
                rdata_o => rdata_o,
                err_o   => err_o,
                ack_o   => ack_o,
                adc_i   => adc_i,
                adc_o   => adc_o
            );

    -- Define the clock
    clk_process : process
    begin
        if sim = '0' then
        clk_i <= '0';
        wait for T/2;
        clk_i <= '1';
        wait for T/2;
        else
        wait;
        end if;
    end process;

    -- Generate a sine signal from the table
    singen : process(clk_i)
    begin
        if (rising_edge(clk_i)) then
        --  adc_i <= std_logic_vector(to_signed(20*sine(i), 14));
            if (sine(i) > 0) then
                adc_i <= std_logic_vector(to_signed(2000, 14));
            else
                adc_i <= std_logic_vector(to_signed(-2000, 14));
            end if;
            i <= i + 1;
            if (i = 29) then
                i <= 0;
            end if;
        end if;
    end process;

    -- Sets the simplified AXI bus signals
    stim_proc : process
    begin
        rstn_i  <= '0';                     -- active reset
        addr_i  <= X"00000008";
        wdata_i <= X"00000000";
        wen_i   <= '0'; ren_i <= '0';

        wait for T;
        rstn_i  <= '1';  -- deactivate reset, write to register
        addr_i  <= X"00000008";
        wdata_i <= X"00000002";
        wen_i   <= '1';

        wait for T;
        wen_i <= '0';

        wait for 100*T;                      -- entry of a new value in the register
        wdata_i <= x"00000003";
        wen_i   <= '1';

        wait for T;
        addr_i  <= X"00000000";
        wen_i <= '0';

        wait for 100*T;
        sim <= '1';                         -- stop the simulation
        wait;
    end process;

end;

Now we need to create a project generation script. Make a copy of the red_pitaya_vivado_project_Z10.tcl and name it Average_project.tcl, for example.

We need to change some strings in the file:

cd prj/$prj_name                     cd prj/Examples/$prj_name
set path_brd ./../brd                set path_brd ./../../brd
set path_sdc ../../sdc               set path_sdc ../../../sdc
add_files  ../../$path_rtl   add_files  ../../../$path_rtl

Add a variable:

set path_tbn tbn

Also, we need to add the following strings after the string in the second code-block below:

add_files -fileset sim_1 -norecurse $path_tbn/red_pitaya_proc_tb.vhd
add_files $path_bd

Now we can generate a project (the -tclargs parameter should be the same as the main project folder name):

vivado -source Average_project.tcl -tclargs "Simple Moving Average"

If everything is done correctly, in the generated project we can generate a bitstream without any errors.

Edit the file red_pitaya_top.sv. Declare two new signals (adc_i and adc_o), connect them to the oscilloscope module, and replace red_pitaya_scope with our new loop_scope:

////////////////////////////////////////////////////////////////////////////////
// oscilloscope
////////////////////////////////////////////////////////////////////////////////

logic trig_asg_out;
logic  [14-1: 0] adc_i;
logic  [14-1: 0] adc_o;

loop_scope i_scope (
    // Simple Moving Average
    .adc_in        (adc_o       ),
    .adc_out       (adc_i       ),
    // ADC
    .adc_a_i       (adc_dat[0]  ),  // CH 1
    .adc_b_i       (adc_dat[1]  ),  // CH 2
    ...

Add two ADC data ports to the loop_scope.v file (found under Design Sources by expanding the red_pitaya_top module and double clicking i_scope: loop_scope).

module loop_scope #(parameter RSZ = 14  // RAM size 2^RSZ
)(
    // Simple Moving Average
    input      [ 14-1: 0] adc_in          ,
    output     [ 14-1: 0] adc_out         ,
    // ADC
    input                 adc_clk_i       ,  // ADC clock
    input                 adc_rstn_i      ,  // ADC reset - active low

Furthermore, replace this process:

always @(posedge adc_clk_i) begin
    if (adc_we && adc_dv) begin
        adc_a_buf[adc_wp] <= adc_a_dat ;
        adc_b_buf[adc_wp] <= adc_b_dat ;
    end
end

With this one:

// Simple Moving Average
always @(posedge adc_clk_i) begin
    if (adc_we && adc_dv) begin
        adc_a_buf[adc_wp] <= adc_in ;
        adc_b_buf[adc_wp] <= adc_b_dat ;
    end
end

assign adc_out = adc_b_dat;

Then, in the file red_pitaya_top.sv, we must connect the signals to red_pitaya_proc. Add the following code somewhere in the oscilloscope section, after the logic declarations:

// Simple Moving Average
red_pitaya_proc i_proc (
    .clk_i    (  adc_clk     ),  // clock
    .rstn_i   (  adc_rstn    ),  // reset - active low
    .addr_i   (  sys[6].addr ),  // address
    .wdata_i  (  sys[6].wdata),  // write data
    .wen_i    (  sys[6].wen  ),  // write enable
    .ren_i    (  sys[6].ren  ),  // read enable
    .rdata_o  (  sys[6].rdata),  // read data
    .err_o    (  sys[6].err  ),  // error indicator
    .ack_o    (  sys[6].ack  ),  // acknowledge signal
    .adc_i    (  adc_i       ),
    .adc_o    (  adc_o       )
);

We need to remove the stub for the current bus (near line 290 - change the i=6 to i=7):

generate
    for (genvar i=7; i<8; i++) begin: for_sys
        sys_bus_stub sys_bus_stub_5_7 (sys[i]);
    end: for_sys
endgenerate

After these manipulations, we have redirected the data from the red_pitaya_proc.vhd module to the first ADC channel. And the data from the second channel was connected to the red_pitaya_proc.vhd input. Within this module, you can already start processing data.

1.4.2.7.3. Development of the moving average

Create a scheme that calculates the current average of the last three inputs. Basic outline of the moving average:

../../_images/diag1.png

Connections:

  • clk, reset (active at logical 0)

  • data_i, 8-bit input

  • tag_i, 2-bit control input

  • data_o, 8-bit output

  • tag_o, 2-bit control output

The data comes into the circuit one after the other, and the control input indicates the cycles in which the data is valid. At tag_i = 01, the first data row is at the input; at 10, the second data row is at the output; and at 11, the last data row is at the output:

cycle

1

2

3

4

5

6

7

8

9

tag_i

00

01

10

10

10

10

11

00

00

data_i

xx

100

50

200

200

200

120

xx

xx

The task of the circuit is to calculate the current average of the last three values. For the first valid data, assume that the previous two values are equal to 0.

The circuit contains three series-connected registers and a combinational circuit for calculating the average value after equations: p = (a + b + c) * 1/3 ≈ ((a + b + c) * 85) >> 8

Instead of dividing by 3, we will use an approximation: ⅓ ≈ 85/256. Use a 7-bit constant of 85 to multiply. Division by 256 represents the value shifted by 8 places to the right. The shift is made by selection subvector, where the lower 8 bits of the product are removed.

In order to implement this, we will create a new component with VHDL:

Create a new file moving_average.vhd in Simple Moving Average/rtl (Add Sources => Add or create design sources => Create File (VHDL)).

1.4.2.7.3.1. Code Explanation

Define inputs and outputs:

entity moving_average is
Port ( data_i   : in std_logic_vector (13 downto 0);    -- adc input data
       clk_i    : in std_logic;                         -- bus clock
       rstn_i   : in std_logic;                         -- bus reset - active low
       tag_i    : in unsigned (1 downto 0);             -- filter window size
       data_o   : out std_logic_vector (13 downto 0));  -- filtered data
end moving_average;

We will need some memory to store previous values. Describe the memory type and create it. Also, we will need some kind of register to store the sum:

architecture Behavioral of moving_average is
    type mem_t is array (0 to 2) of signed (13 downto 0);

    signal regs: mem_t; -- buffer for moving average algorithm
    signal sum: signed(13 downto 0); -- register for storing the sum of register values
begin

The data is updated for each clk, thus, the process runs at each clock change:

process (clk_i)
begin
    if(rising_edge(clk_i)) then

We need to reset the registers:

if (rstn_i = '0') then
    sum <= "00000000000000";

Connect the first register with the ADC directly.

regs(0) <= signed(data_i);

The summer will always constructively add 3 registers:

sum <= regs(0) + regs(1) + regs(2);

Then we should describe connections among registers. We should keep in mind that the summer constructively adds 3 registers. Thus, we need to reset register values to 0 so that the moving average is calculated correctly each time.

if (tag_i(1) = '1') then
    regs(1) <= regs(0);
else
    regs(1) <= "00000000000000";
end if;

if (tag_i(0) = '1') then
    regs(2) <= regs(1);
else
    regs(2) <= "00000000000000";
end if;

The last thing we need is the multiplexer to calculate an average value for a buffer with different lengths. Since division is a pretty complex procedure, we need to simplify it. One of the approaches is a real number with a fixed point. We can represent a division as 1/3 ≈ 85/256. Division by 256 is executed by a simple operation of right logical shift.

case tag_i is
    -- regs
    when "01" => data_o <= std_logic_vector(sum);

    -- regs / 2
    when "10" => data_o <= std_logic_vector(shift_right(sum, 1));

    -- (regs * 85) / 256
    when "11" => data_o <= std_logic_vector(resize(shift_right(sum * 85, 8), 14));

    -- (regs * 85) / 256
    when others => data_o <= std_logic_vector(resize(shift_right(sum * 85, 8), 14));
end case;

1.4.2.7.3.2. The complete code

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.all;

entity moving_average is
    Port ( data_i   : in std_logic_vector (13 downto 0);    --
           clk_i    : in std_logic;                         -- bus clock
           rstn_i   : in std_logic;                         -- bus reset - active low
           tag_i    : in unsigned (1 downto 0);             --
           data_o   : out std_logic_vector (13 downto 0));  --
end moving_average;

architecture Behavioral of moving_average is
    type mem_t is array (0 to 2) of signed (13 downto 0);

    signal regs: mem_t; -- buffer for moving average algorithm
    signal sum: signed(13 downto 0);
begin

regs(0) <= signed(data_i);

process (clk_i)
begin
    if(rising_edge(clk_i)) then
        if (rstn_i = '0') then
            sum <= "00000000000000";
        else
            case tag_i is
                -- regs
                when "01" => data_o <= std_logic_vector(sum);

                -- regs / 2
                when "10" => data_o <= std_logic_vector(shift_right(sum, 1));

                -- (regs * 85) / 256
                when "11" => data_o <= std_logic_vector(resize(shift_right(sum * 85, 8), 14));

                -- (regs * 85) / 256
                when others => data_o <= std_logic_vector(resize(shift_right(sum * 85, 8), 14));
            end case;

            if (tag_i(1) = '1') then
                regs(1) <= regs(0);
            else
                regs(1) <= "00000000000000";
            end if;

            if (tag_i(0) = '1') then
                regs(2) <= regs(1);
            else
                regs(2) <= "00000000000000";
            end if;

            sum <= regs(0) + regs(1) + regs(2);
        end if;
    end if;
end process;

end Behavioral;

By clicking the + sign under sources, you can add the red_pitaya_proc.vhd file to the project. The previously created module/component must be added to red_pitaya_proc. The component moving_average is already added to the file (component … end component), so we just add the component connection to the architecture (anywhere between the begin and end architecture lines):

rp_average:
    moving_average
        port map (
            data_i => adc_i,
            clk_i => clk_i,
            rstn_i => rstn_i,
            tag_i => tag_i,
            data_o => adc_o
        );

Create a register/signal in the architecture to store the moving average of a chosen length- Between the end component and the begin lines, insert the following code:

signal tag_i: unsigned(1 downto 0) := "01";

Define the value after the reset in the process:

if (rstn_i = '0') then
    tag_i <= "01";
else
...

1.4.2.7.4. Work with registers

In order to change the buffer dimension, we need to have the “writing” rights for this register at the address. The module red_pitaya_proc is already connected to the system bus and has the following address: 0x406xxxxx. Upon receiving data by address, we must write in the tag_i register (further modification to the process in the red_pitaya_proc.vhd file):

case addr_i(19 downto 0) is
    when X"00000" => rdata_o <= X"00000001";
    when X"00008" => tag_i <= unsigned(wdata_i(1 downto 0));
    when others => rdata_o <= X"00000000";
end case;

You can find more details about the Red Pitaya register map here.

Device enquiry and their configuration are made by 0x40600000, thus, we’re using 0x40600008.

1.4.2.7.5. Simulation

red_pitaya_proc_tb.vhd should be defined as the upper module in the Simulation Sources-> sim_1:

../../_images/diag2.png

Launch the simulation and configure the signals adc_i and adc_o as analog:

../../_images/diag3.png

Setup the data type of signal:

../../_images/diag4.png

Setup the display of these signals:

../../_images/diag5.png
../../_images/diag6.png

Set the simulation time to 10 us and restart the simulation:

../../_images/diag11.png

After the simulation is done, you should see the following oscillogram:

../../_images/diag7.png

We can notice that the signal gets corrupted when we change the size of tag_i (about 5us on the oscillogram). This is caused by the fact that when we increase the size of tag_i, one or two registers become empty and the signal amplitude falls down.

To see how this filter handles a sinewave, comment the rectangle generation and uncomment the sine generation in the red_pitaya_proc_tb.vhd file (located in Simulation Sources):

-- Generate a sine signal from the table
singen : process(clk_i)
begin
    if(rising_edge(clk_i)) then
        adc_i <= std_logic_vector(to_signed(20*sine(i), 14));
--        if (sine(i) > 0) then
--          adc_i <= std_logic_vector(to_signed(2000, 14));
--        else
--          adc_i <= std_logic_vector(to_signed(-2000, 14));
--        end if;
        i <= i+ 1;
        if(i = 29) then
            i <= 0;
        end if;
    end if;
end process;

1.4.2.7.6. Upload bitstream to Red Pitaya

Insert an SD card with the uploaded ecosystem.

Please note that you need to change the forward slashes to backward slashes on Windows.

  1. Open Terminal or CMD and go to the .bit file location.

cd <Path/to/RedPitaya/repository>/prj/Examples/Simple_moving_average/tmp/Simple_moving_average/Simple_moving_average.runs/impl_1
  1. Send the .bit file to the Red Pitaya with the scp command or use WinSCP or a similar tool to perform the operation.

scp system_wrapper.bit root@rp-xxxxxx.local:/root/Simple_moving_average.bit
  1. Now establish an SSH communication with your Red Pitaya and check if you have the copy Simple_moving_average.bit in the root directory.

redpitaya> ls
  1. Load the Simple_moving_average.bit to xdevcfg with

redpitaya> cat Simple_moving_average.bit > /dev/xdevcfg

1.4.2.7.7. Testing

Connect to the Red Pitaya and start the oscilloscope and connect OUT1 to IN2. Start the generator on the first channel at a frequency of 1 MHz or more. You should see a signal on IN1 even though nothing is connected to it. This is just the filtered moving average data. In order to setup the filter, we need to connect via SSH and enter the following command:

monitor 0x40600008 3

where 0x40600008 is the address of our register and 3 is the value that should be written in the register.

The result of our filter’s work when the register value equals 3:

../../_images/diag8.png

The result of our filter’s work when the register value equals 2:

../../_images/diag9.png

The result of our filter’s work when the register value equals 1:

../../_images/diag10.png

1.4.2.7.8. Author & Source

  • Orignal author: Laboratory for Integrated Circuit Design

Original lesson: link

Please note that the original site is in Slovene.