• 0
Sign in to follow this  
Hendrik

4 tap FIR filter

Question

Hi there,

I am trying to implement a 4 tap FIR filter on the Basys 3 board.Basically,I feed the ADC with an analog sine wave signal that will get digitized (12 bits) that get fed into the FIR filter( low pass filter 400Hz cut off freq)the output of the FIR filter will drive a 8 bit PMOD DAC.

My question is as follows:

1.When I use the on board 12-bit ADC ,do you have some VHDL sample code to show how the A/D conversion would be implemented for this case?

2.During filtering I assume I would multiply my 8 bit coefficient with the 12 bit word from the ADC ending up with a 20 bit word.Now I need to scale this down to 8 bits to drive my DAC.How would I do this in VHDL?

Any info would be appreciated.I was not able to find any ADC VHDL sample code for this board if you can provide a link that would be great.

Thanks allot

H

Share this post


Link to post
Share on other sites

9 answers to this question

Recommended Posts

  • 0

@Hendrik,

By the "onboard ADC", are you referencing the XADC capability on the FPGA?  I've thought about building my own capability to access that hardware component, but ... to be honest, I've never used the XADC before.  Have you looked at the XADC demo at all?

As for your second question ... are you sure you are only going from 20-bits down to 8?  :D  If you are running a four tap filter, you should be up at 22-bits and needing to come down to 8, else you might be dropping bits in your 4-tap filter.  Sure, the 12-bit sample times an 8-bit tap will get you to 20 bits, but adding four of these 20-bit numbers together will increase your bit width to 22 bits unless you throw things out.

As for going from 22 bits down to 8 bits can be as easy or hard as you would like.  The easy way is to just drop the bottom 12 bits and keep the top 8.  It's simple, it's easy, and it requires almost no logic.

It also introduces a bias into your data which will then manifest itself into an unwanted DC term.  (At least, ... I never wanted such DC terms)

You could also try adding rounding down to 8-bits.  To do this, you'd add a one to the first bit your are going to drop (i.e. add a 22'h02000) and then truncate as above.  This is a much better solution, but it has a problem associated with what you do with the exact half number.

In my own personal study, I had to do a bit of work with the various forms of rounding until I found something that worked well enough for me.  What you are looking for is something called "convergent rounding."  Sometimes it's called bankers rounding, sometimes called round half to even, etc.  This takes the times when your number is exactly between two numbers (ex: 22'h002000, 22'h006000, 22'h00a000, etc) and it rounds the number towards the nearest even value (Ex from before: 8'h0, 8'h2, 8'h2, etc.)

As for code to do this, sorry--I don't have any VHDL code.  (That's why I wasn't responding to your inquiry--I do all my work in Verilog.  :P)  You can find my own rounding work as part of the FFT core I built.  It's in the fftgen.cpp file between lines 345 and 450, or you may find it easier to read if you actually build an FFT using the core and then look for the convround.v file it will create.

Dan

Share this post


Link to post
Share on other sites
  • 0

1) See http://hamsterworks.co.nz/mediawiki/index.php/Minimal_XADC_design for a very minimal XADC using primatives (if that is your thing!)

2) Here is a really simple framework for your filter. Although it looks straightforward you will have to look at everything very closely. Issues and subtleties  lurk in almost every line - type conversions, overflow errors, timing errors. very poor use of resources, implicit assumptions about intermediate type, rounding, precision errors - it has the general form of the answer you are looking for, but will not work without you working hard to see what is going on.

I recommend you set up a project in Vivado, and then have a close look at the RTL schematic to see what it actually describes, then try and design and pass pathological data through it in the simulator to show up the errors (such as overflows and underflows).

However, the line you are looking for in the 20-bit to 12-bit conversion is the one after the "begin" statement.

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
entity filter is
    Port ( clk             : in  STD_LOGIC;
           in_sample       : in  STD_LOGIC_VECTOR(11 downto 0);
           in_data_enable  : in  STD_LOGIC;
           out_sample      : out STD_LOGIC_VECTOR(11 downto 0);
           out_data_enable : out STD_LOGIC);
end filter;

architecture Behavioral of filter is
    type t_data is array(0 to 4) of signed(11 downto 0);
    type t_kernel is array(0 to 4) of signed(7 downto 0);

    signal data         : t_data := (others => (others => '0'));
    signal kernel       : t_kernel := (to_signed(  10,8),
                                       to_signed( -10,8),
                                       to_signed( 255,8),
                                       to_signed( -10,8),
                                       to_signed(  10,8));

    signal updated_data : std_logic := '0';
    signal total        : signed(19 downto 0) := (others => '0');
begin

    --------------------------------------------------
    -- Type conversion and scaling
    --------------------------------------------------
    out_sample <= std_logic_vector(total(19 downto 8));

process(clk)
    begin
        if rising_edge(clk) then
            ---------------------------------------
            -- Process any data received last cycle
            ---------------------------------------
            if updated_data = '1' then
                total <= (data(0) * kernel(0))
                       + (data(1) * kernel(1))
                       + (data(2) * kernel(2))
                       + (data(3) * kernel(3))
                       + (data(4) * kernel(4));
                out_data_enable <= '1';
            end if;

            --------------------------------------------
            -- Receive any new data presented this cycle
            --------------------------------------------
            if in_data_enable = '1' then
                data(1 to 4) <= data(0 to 3);
                data(0)      <= signed(in_sample); 
                updated_data <= '1';
            end if; 
        end if;
    end process;

end Behavioral;
Edited by hamster

Share this post


Link to post
Share on other sites
  • 0

Hi Hamster

Thanks for responding to my question.Can you explain this block?How did you calculate the coefficients?

 

signal kernel       : t_kernel := (to_signed(  10,8),
                                       to_signed( -10,8),
                                       to_signed( 255,8),
                                       to_signed( -10,8),
                                       to_signed(  10,8));

The way I did it was using Matlab,

and come up with these coefficients for example:

0.144,0.22,0.25,0.22,0.144 how do I scale them?

 

Thanks,

Hendrik

Share this post


Link to post
Share on other sites
  • 0

@Hendrik,

I'd start by scaling a filter to 8-bit coefficients by multiplying the coefficients by (2^(8-1)-1) and dividing by the maximum coefficient.  In this case, your strongest coefficient should be 127.  A 255 in signed 8-bit math is a -1, so @hamster's work above would've had some problems.  But, like he said, "Although it looks straightforward you will have to look at everything very closely. Issues and subtleties  lurk in almost every line ..."  This would be one of them.

Another approach to scaling a filter is look for the diabolical case that would cause your filtter to overflow.  In this case, you'd want to pretend your inputs to the filter are the maximum integer values for the data type you are dealing with (127 for 8-bit data, 2047 for 12-bit data, etc), save only that the data values have the sign of your filter values at their points.  Hence, for @hamster's [ 10, -10, 255, -10, 10 ] filter above, you might wish to pretend your data set was [ 255, -255, 255, -255, 255 ] (adjusted for the proper scale, of course).  This data set will give the maximum filter output.  To keep it from overflowing, you'll want to make certain that you can represent 255 * [ 10 + 10 + 255 + 10 + 10 ] = 75225 without overflow.  In this case, 9-bits in (because 255 is a 9-bit signed integer), together with a 9-bit filter,  requires 18 bits out (because 75225 <= 2^(18-1)-1) is a 18-bit signed integer--even though it is a 17-bit unsigned integer).  You could lower your scale a touch if you wanted the result to fit within a 17-bit signed integer output.

In your case, coefficients 0.144, 0.22, 0.25, ... could easily be turned into 37 57 65 57 37.  If you multiply this by your maximum data sequence, [ 1 1 1 1 1 ]*2047, you'll get a filter response of 517891, which fits within 20-bits.  You could rescale the maximum value for 127 instead of 65, but then your result would fit in 21 bits, not 20. 

But what about rounding?  How did I handle rounding?  Haphazardly and with neither true rhyme nor reason.  There's a whole field dedicated to filter design and how to properly turn coefficients from infinite precision to truncated integers for the best design, but I don't have time to get into that here.  Wander down to your local library, or start searching IEEE, if you need some help there.

Dan

Share this post


Link to post
Share on other sites
  • 0

@Dan - You will give away all the secrets! - Yes the to_signed(255,8) was one of a few talking points.

I find modeling it in a spreadsheet is the best way to play with filter values to ensure you use the best precision and avoid overflows. and to play with different scaling values - have the "ideal" filter in floating point, and then try different scaling and roundings.

To handle rounding I sometimes to subtract 2^(n-1) from negative total values before truncating to n bits in length...

Edited by hamster

Share this post


Link to post
Share on other sites
  • 0

Thanks for the info very helpful understanding scaling the bits.

So I plan to use 2 files XADC.vhl(adc config) and FIR4_tap.vhl (top level) for this design and try to understand how to tie them together.

Now,looking at the XADC in continuous sampling mode it takes 22 cycles to perform an A/D  conversion.(BUSY go high in the beginning of the conversion phase and low when done with the conversion)

So I assume reading through this specsheet I will need to wait 22 ADCCLK cycles (scaled version form clk) then read the VAUXP[0]/VAUXN[0] (Register #0)register.Is this the right train of though?

If the "in_data_enable" signal is equal to the "busy" signal in the XADC then I can use this to read the samples  stored in the register.

 

I am a little confused how to access the conversion data in the register,can you give me direction on this portion?

Thanks,

Hendrik

 

Share this post


Link to post
Share on other sites
  • 0

Hi Dan

Yes I did do be honest trying to make sense from the Verilog code pretty new to VHDL myself no experience in Verilog syntax.I have more experience with MCU's,C,Python etc.

This is quite a learning curve as you pointed out but I am working it.

Thanks for the great support.

Hendrik

 

 

Share this post


Link to post
Share on other sites
  • 0

I have completed my FIR filter code on this FIR project (low pass cutoff frequency is 8 KHz) and created a top level design to glue all the building blocks together.

1.The FIR module worked perfect in my simulation.

2.I created a ADC module using the IP core.(I kept it very simple )

3.I then programmed the Basys3 board.

In my test phase I feed the board with a tone 300mV p-p that was generated with a Agilent 33220A waveform generator at 8 KHz.I can see the signal on the DAC output but when I adjust the frequency of the tone to 10 KHz (this frequency is supposed to be attenuated by the LPF) I see the same signal level no attenuation at all.This led me to believe there is something wrong with the signal flow between the ADC module and the filter module.

Any Ideas??

I have attached the the top level design and the ADC modules,maybe you can help point out the obvious.By the way the output of the FIR feed the PMOD R2R(DAC 8 bit).

Thanks,

H.

FIR.thumb.png.f8bfb6ef4d2ddfd8e8bc1b1847dadca9.png

adc.vhd

fir_top.vhd

xadc_wiz_0.vhd

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this