Code example for FIR/IIR filters in VHDL?

I'm trying to get started with DSP in my Spartan-3 board. I made a AC97 board with a chip from an old motherboard, and so far I got it to do ADC, multiply the samples for a number <1 (decrease volume) and then DAC.

Now I'd like to do some basic DSP stuff, like a low-pass filter, high-pass etc. But I'm really confused about numeric representation (integers? fixed point? Q0.15? Overflow or saturation?).

I just want some example code of an actual simple filter to get me started. No high-efficiency, fast, or anything like that. Just the theoretical filter implemented in VHDL.

I've been searching but I just find theoretical formulas - I get that, what I don't understand is how to process the signed 16-bit, 48KHz audio samples I'm getting from the ADC. I've been using these libraries: http://www.vhdl.org/fphdl/. If I multiply my samples by 0.5, 0.25, etc, I can hear the difference. But a larger filter gives me just noise.

Thanks.

• While I'm all for using whatever you have on hand for learning stuff, I'd like to point out that doing audio filters in an FPGA is not a very efficient or cost effective way to do it. So, if you do a real project then I'd recommend using a low cost DSP instead. Exceptions: When you're doing an ungodly number of audio channels at the same time, or you are doing FIR's with an absurd number of taps. – user3624 Jun 24 '11 at 17:43

It sounds like you need to figure out the DSP aspects first, then make an implementation in FPGA.

• Sort out the DSP in C, Matlab, Excel, or anywhere else
• Try and think how you'll transfer what you've learned from that into FPGA-land
• Discover you've made some assumption about the implementation that doesn't work well (like the use of floating point for example)
• Go back and update your offline DSP stuff to take account of this.
• Iterate n times :)

Regarding data types, you can use integers just fine.

here's some sample code to get you going. Note that it's missing a lot of real-world issues (for example reset, overflow management) - but hopefully it's instructive:

library ieee;
use ieee.std_logic_1164.all;
entity simple_fir is
generic (taps : integer_vector);
port (
clk      : in  std_logic;
sample   : in  integer;
filtered : out integer := 0);
end entity simple_fir;
----------------------------------------------------------------------------------------------------------------------------------
architecture a1 of simple_fir is
begin  -- architecture a1
process (clk) is
variable delay_line : integer_vector(0 to taps'length-1) := (others => 0);
variable sum : integer;
begin  -- process
if rising_edge(clk) then  -- rising clock edge
delay_line := sample & delay_line(0 to taps'length-2);
sum := 0;
for i in 0 to taps'length-1 loop
sum := sum + delay_line(i)*taps(taps'high-i);
end loop;
filtered <= sum;
end if;
end process;
end architecture a1;
----------------------------------------------------------------------------------------------------------------------------------
-- testbench
----------------------------------------------------------------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
entity tb_simple_fir is
end entity tb_simple_fir;
architecture test of tb_simple_fir is
-- component generics
constant lp_taps : integer_vector := ( 1, 1, 1, 1, 1);
constant hp_taps : integer_vector := (-1, 0, 1);

constant samples : integer_vector := (0,0,0,0,1,1,1,1,1);

signal sample   : integer;
signal filtered : integer;
signal Clk : std_logic := '1';
signal finished : std_logic;
begin  -- architecture test
DUT: entity work.simple_fir
generic map (taps => lp_taps)  -- try other taps in here
port map (
clk      => clk,
sample   => sample,
filtered => filtered);

-- waveform generation
WaveGen_Proc: process
begin
finished <= '0';
for i in samples'range loop
sample <= samples(i);
wait until rising_edge(clk);
end loop;
-- allow pipeline to empty - input will stay constant
for i in 0 to 5 loop
wait until rising_edge(clk);
end loop;
finished <= '1';
report (time'image(now) & " Finished");
wait;
end process WaveGen_Proc;

-- clock generation
Clk <= not Clk after 10 ns when finished /= '1' else '0';
end architecture test;

• Thanks for your answer. That's more or less what I did but I'm having some issues with number representation. My ADC gives me values in the -32k to +32k (signed 16-bit). I also have the problem of the filter constant - how do I represent that? And the result of multiply between the sample and the constant? That's what's confusing me the most. – hjf Jun 24 '11 at 15:55
• @hjf - it's all just integers. So long as everything stays within 32 bits you're OK. IF you need more width than that you can use UNSIGNED or SIGNED vectors as wide as you like. Or use the fixed_point types from VHDL2008 (see here: vhdl.org/fphdl) – Martin Thompson Jun 26 '11 at 21:33

The simplest low pass FIR filter you can try is y(n) = x(n) + x(n-1). You can implement this quite easily in VHDL. Below is a very simple block diagram of the hardware you want to implement.

According to the formula, you need the current and previous ADC samples in order to get the appropriate output. What you should do is to latch the incoming ADC samples on the falling edge of the clock, and perform the appropriate calculations on the rising edge in order to get the appropriate output. Since you are adding two 16-bit values together, it's possible you'll end up with a 17-bit answer. You should store the input into 17-bit registers and use a 17-bit adder. Your output, however, will be the lower 16 bits of the answer. Code might look something like this but I can't guarantee that it will work completely since I haven't tested it, let alone synthesized it.

IEEE.numeric_std.all;
...
signal x_prev, x_curr, y_n: signed(16 downto 0);
signal filter_out: std_logic_vector(15 downto 0);
...
process (clk) is
begin
if falling_edge(clk) then
--Latch Data
x_prev <= x_curr;
x_curr <= signed('0' & ADC_output); --since ADC is 16 bits
end if;
end process;

process (clk) is
begin
if rising_edge(clk) then
--Calculate y(n)
y_n <= x_curr + x_prev;
end if;
end process;

filter_out <= std_logic_vector(y_n(15 downto 0));  --only use the lower 16 bits of answer


As you can see, you can use this general idea to add in more complicated formulas, such as ones with coefficients. More complicated formulas, like IIR filters, may require the use of variables to get algorithm logic correct. Finally, an easy way to get around filters that have real numbers as coefficients is to find a scale factor so that all the numbers end up being as close to whole numbers as possible. Your final result will have to be scaled back down by the same factor to get the correct result.

I hope this can be of use to you and help you get the ball rolling.

*This has been edited so that the data latching and output latching are in separate processes. Also using signed types instead of std_logic_vector. I'm assuming your ADC input is going to be a std_logic_vector signal.

• Processes which trigger off both edges (as you have described) are very unlikely to synthesise – Martin Thompson Jun 24 '11 at 11:49
• @Martin I'm assuming you know a lot more about FPGA's than I do, but I have latched incoming data on the falling edge and latched output on the rising edge for a class assignment so I thought this would have worked. Can you explain why such processes don't work? – dhsieh2 Jun 24 '11 at 12:31
• It'll work fine in a simulator. Synthesisers will choke on it though (in my experience) as the flipflops in the device can only clock on one edge. – Martin Thompson Jun 24 '11 at 14:44
• @dhsieh2 Thanks, this is the kind of answer I was looking for. Another question, how would I do it if I was using Signed numbers (my ADC gives me values in the -32k to +32k). – hjf Jun 24 '11 at 15:52
• @Martin I clock things off of both clock edges all the time in Xilinx FPGA's, no problem. You just can't clock the same FF off of both edges. When you look at the timing analyzer output it actually makes it very clear that you're doing opposite edges and adjusts the timing budget accordingly. – user3624 Jun 24 '11 at 17:38

Another simple code snippet (just the guts). Note I didn't write the VHDL directly, I used MyHDL to generate the VHDL.

-- VHDL code snip
architecture MyHDL of sflt is

type t_array_taps is array(0 to 6-1) of signed (15 downto 0);
signal taps: t_array_taps;

begin

SFLT_RTL_FILTER: process (clk) is
variable sum: integer;
begin
if rising_edge(clk) then
sum := to_integer(x * 5580);
sum := to_integer(sum + (taps(0) * 5750));
sum := to_integer(sum + (taps(1) * 6936));
sum := to_integer(sum + (taps(2) * 6936));
sum := to_integer(sum + (taps(3) * 5750));
sum := to_integer(sum + (taps(4) * 5580));
taps(0) <= x;
for ii in 1 to 5-1 loop
taps(ii) <= taps((ii - 1));
end loop;
y <= to_signed(sum, 16);
end if;
end process SFLT_RTL_FILTER;

end architecture MyHDL;


This is a direct implementation. It will require multipliers. The synthesis of this circuit, targetted for an Altera Cyclone III, didn't use any explicit multipliers but required 350 logic elements.

This is a small FIR filter and will have the following response (not so great) but should be useful as an example.

In addition I have a couple examples, here and here, that might be useful.

Also, your question appears to ask: "what is appropriate fixed-point representation?" Frequently when implementing DSP functions, fixed-point representation is used, because it simplifies analyzing the filters. As mentioned, fixed-point is just integer arthimetic. The actual implementation is simply working with integers but our preceived representation is fractional.
Issues usually arise when converting from implementation integer (fixed-point) to/fro design floating-point.

I don't know how well the VHDL fixed-point and floating-point types are supported. They will work fine in simulation but I don't know if they will synthesize with most synthesis tools. I created a separate question for this.

OpenCores has a number of DSP examples, IIR and FIR, including BiQuad. You'll have to register to download the files.

edit
I understand Kortuk's comment on dead links, and indeed, if the link to OpenCores dies the answer will become useless. I'm quite confident that this won't happen; my link is a generic one, and it will only die if the complete OpenCores domain would disappear.
I tried to look for some examples I could use for this answer but they're all too long to be represented here. So I'll stick to my advice to register for the site yourself (I had to move to New York, because my home town wasn't accepted) and have a look at the code presented there.

• As with all things, links break. We have discussed before that a link on its own does not make an answer. Can you bring over some of what is there and make a meaty answer that has that link as a reference to learn more? – Kortuk Sep 20 '11 at 22:20
• @Kortuk - I wanted to do this yesterday. I registered yesterday with opencores to get some details, but they need a few days to think if they'll have me – stevenvh Sep 21 '11 at 5:38
• glad to hear it, I was honestly wondering if something had gotten in your way. Look forward to hearing more about it. – Kortuk Sep 21 '11 at 5:39

I have tried to implement scripts for authomatic implementation of IIR filters, where you can defined whether the design should be as fast as possible (so each multiplication is performed with dedicated multiplier) or as small as possible (so each multiplier is reused).

The sources have been published on alt.sources as "Behavioral but synthesizable implementation of IIR filters in VHDL" (you can also find it in google archive: https://groups.google.com/group/alt.sources/msg/c8cf038b9b8ceeec?dmode=source )

Posts to alt.sources are in "shar" format, so you need to save the message as text, and unshar it (with "unshar" utility) to get sources.