Chapter 32

Implementing Digital Filters in VHDL

Peter Wilson

32.1 Introduction

An important part of systems that interface to the “real world” is the ability to process sampled data in the digital domain. This is often called sampled-data systems (SDS) or operating in the Z-domain. Most engineers are familiar with the operation of filters in the Laplace or S-domain where a continuous function defines the characteristics of the filter and this is the digital domain equivalent to that.

For example, consider a simple RC circuit in the analog domain as shown in Figure 32.1. This is a low-pass filter function and can be represented using the Laplace notation shown in Figure 32.1

image

Figure 32.1 RC filter in the analog domain

This has the equivalent S-domain (or Laplace) function as follows:

image

This function is a low-pass filter because the Laplace operator s is equivalent to jω, where w = 2πf (with f being the frequency). If f is zero (the DC condition), then the gain will be 1, but if the value of sCR is equal to 1, then the gain will be 0.5. This in dB is −3 dB and is the classical low-pass filter cut-off frequency.

In the digital domain, the s operation is replaced by Z. Z−1 is practically equivalent to a delay operator, and similar functions to the Laplace filter equations can be constructed for the digital, or Z-domain equivalent.

There are a number of design techniques, many beyond the scope of this book (if the reader requires a more detailed introduction to the realm of digital filters, Cunningham’s Digital filtering: an introduction is a useful starting point); however, it is useful to introduce some of the basic techniques used in practice and illustrate them with examples.

The remainder of this chapter will cover the introduction to the basic techniques and then demonstrate how these can be implemented using VHDL on field-programmable gate arrays (FPGAs).

32.2 Converting S-Domain to Z-Domain

The method of converting an S-domain equation for a filter to its equivalent Z-domain expression is done using the ‘bilinear transform’. This is simply a method of expressing the equation in the S-domain in terms of Z. The basic approach is to replace each instance of s with its equivalent Z-domain notation and then rearrange into the most convenient form. The transform is called bilinear, as both the numerator and denominator of the expression are linear in terms of z.

image

If we take a simple example of a basic second-order filter we can show how this is translated into the equivalent Z-domain form:

image

replace s with (z − 1)/(z + 1):

image

Now, the term H(Z) is really the output Y(Z) over the input X(Z) and we can use this to express the Z-domain equation in terms of the input and output:

image

This can then be turned into a sequence expression using delays (z is one delay, z2 is two delays and so on) with the following result:

image

This is useful because we are now expressing the Z-domain equation in terms of delay terms, and the final step is to express the value of y(n) (the current output) in terms of past elements by reducing the delays accordingly (by 2 in this case):

image

The final design note at this point is to make sure that the design frequency is correct, for example the low-pass cut-off frequency. The frequencies are different between the S- and Z-domain models, even after the bilinear transformation, and in fact the desired digital domain frequency must be translated into the equivalent S-domain frequency using a technique called pre-warping. This simple step translates the frequency from one domain to the other using the expression below:

image

where Ωc is the digital domain frequency, T is the sampling period of the Z-domain system and ωc is the resulting frequency for the analog domain calculations.

Once we have obtained our Z-domain expressions, how do we turn this into practical designs? The next section will explain how this can be achieved.

32.3 Implementing Z-Domain Functions in VHDL

Z-domain functions are essentially digital in the time domain as they are discrete and sampled. The functions are also discrete in the amplitude axis, as the variables or signals are defined using a fixed number of bits in a real hardware system, whether this is integer, signed, fixed-number or floating-point, there is always a finite resolution to the signals. For the remainder of this chapter, signed arithmetic is assumed for simplicity and ease of understanding. This also essentially defines the number of bits to be used in the system. If we have 8 bits, the resolution is 1 bit and the range is −128–+127.

32.3.1 Gain Block

The first main Z-domain block is a simple gain block. This requires a single signed input, a single signed output and a parameter for the gain. This could be an integer or also a signed value. The VHDL model for a simple Z-domain gain block is given below:

Library ieee;
Use ieee.numeric_std.all;
   Entity zgain is
       Generic ( n : integer := 8;
               gain : signed
       );
       Port (
             Zin : in signed (n-1 downto 0);
             Zout : out signed (n-1 downto 0)
       );
End entity zgain;
Architecture zdomain of zgain is
Begin
     p1 : process (zin)
         variable product : signed (2*n-1 downto 0);
     begin
         product := zin * gain;
         zout <= product (n-1 downto 0);
    end process p1;
End architecture zdomain;

We can test this with a simple testbench that ramps up the input and we can observe the output being changed in turn:

library ieee;

use ieee.std_logic_1164.all;

use ieee.numeric_std.all;

entity tb is

end entity tb;

architecture testbench of tb is

signal clk : std_logic := ‘0’;

signal dir : std_logic := ‘0’;

signal zin : signed (7 downto 0):= X“00”;

signal zout : signed (7 downto 0):= X“00”;

component zgain

generic (

n : integer := 8;

gain :signed := X“02”

);

port (

signal zin : in signed(n-1 downto 0);

signal zout : out signed(n-1 downto 0)

);

end component;

for all : zgain use entity work.zgain;

begin

clk <= not clk after 1 us;
DUT : zgain generic map ( 8, X“02” ) port map (zin, zout);

p1 : process (clk)

begin

zin <= zin + 1;

end process p1;

end architecture testbench;

Clearly, this model has no error checking or range checking and the obvious problem with this type of approach is that of overflow. For example, if we multiply the input (64) by a gain of 2, we will get 128, but that is the sign bit, and so the result will show −128! This is an obvious problem with this simplistic model and care must be taken to ensure that adequate checking takes place in the model.

32.3.2 Sum and Difference

Using this same basic approach, we can create sum and difference models which are also essential building blocks for a Z-domain system. The sum model VHDL is shown below:

Library ieee;

Use ieee.numeric_std.all;

Entity zsum is

Generic ( n : integer := 8

);

Port (

Zin1 : in signed (n-1 downto 0);

Zin2 : in signed (n-1 downto 0);

Zout : out signed (n-1 downto 0)

);

End entity zsum;

Architecture zdomain of zsum is

Begin

p1 : process(zin)

variable zsum : signed (2*n-1 downto 0);

begin

zsum := zin1 + zin2;

zout <= zsum (n-1 downto 0);

end process p1;

End architecture zdomain;

Despite the potential for problems with overflow, both of the models shown have the internal variable that is twice the number of bits required, and so this can take care of any possible overflow internal to the model, and in fact checking could take place prior to the final assignment of the output to ensure the data is correct. The difference model is almost identical to the sum model except that the difference of zin1 and zin2 is computed.

32.3.3 Division Model

A useful model for scaling numbers simply in the Z-domain is the division by 2 model. This model simply shifts the current value in the input to the right by 1 bit—hence, giving a division by 2. The model could easily be extended to shift right by any number of bits, but this simple version is very useful by itself. The VHDL for the model relies on the logical shift right operator (SRL) which not only shifts the bits right (losing the least significant bit) but adding a zero at the most significant bit. The resulting VHDL is shown below for this specific function:

zout <= zin srl 1;

The unit shift can be replaced by any integer number to give a shift of a specific number of bits. For example, to shift right by 3 bits (effectively a divide-by-8) would have the following VHDL:

zout <= zin srl 3;

The complete division by 2 model is given below:

Library ieee;

Use ieee.numeric_std.all;

Entity zdiv2 is

Generic ( n : integer := 8

);

Port (

Zin : in signed (n-1 downto 0);

Zout : out signed (n-1 downto 0)

);

End entity zdiv2;

Architecture zdomain of zdiv2 is

Begin

zout <= zin srl 1;

End architecture zdomain;

In order to test the model a simple test circuit that ramps up the input is used and this is given below:

library ieee;

use ieee.std_logic_1164.all;

use ieee.numeric_std.all;

entity tb is

end entity tb;

architecture testbench of tb is

signal clk : std_logic := ‘0’;

signal dir : std_logic := ‘0’;

signal zin : signed (7 downto 0) := X“00”;

signal zout : signed (7 downto 0) := X“00”;

component zdiv2

generic (

n : integer := 8

);

port (

signal zin : in signed (n-1 downto 0);

signal zout : out signed (n-1 downto 0)

);

end component;

for all : zdiv2 use entity work.zdiv2;

begin

clk <= not clk after 1 us;

DUT : zdiv2 generic map (8) port map (zin, zout);

p1 : process (clk)

begin

zin <= zin + 1;

end process p1;

end architecture testbench;

The behavior of the model is useful to review, if the input is X“03” (Decimal 3), binary ‘00000011’ and the number is right shifted by 1, then the resulting binary number will be ‘00000001’ (X“01” or decimal 1), in other words this operation always rounds down. This has obvious implications for potential loss of accuracy and the operation is skewed downward, which has again, implications for how numbers will be treated using this operator in a more complex circuit.

32.3.4 Unit Delay Model

The final basic model is the unit delay model (zdelay). This has a clock input (clk) using a std_logic signal to make it simple to interface to standard digital controls. The output is simply a one clock cycle delayed version of the input.

Library ieee;

use ieee.std_logic_1164.all;

Use ieee.numeric_std.all;

Entity zdelay is

Generic ( n : integer := 8 );

Port (

clk : in std_logic;

Zin : in signed (n-1 downto 0);

Zout : out signed (n-1 downto 0) := (others

=> ‘0’)

);

End entity zdelay;

Architecture zdomain of zdelay is signal lastzin : signed (n-1 downto 0) := (others => ‘0’);

Begin

p1 : process(clk)

begin

if rising_edge(clk) then

zout <= lastzin;

lastzin <= zin;

end if;

end process p1;

End architecture zdomain;

Notice that the output zout is initialized to all zeros for the initial state, otherwise “don’t care” conditions can result that propagate across the complete model.

32.4 Basic Low-Pass Filter Model

We can put these elements together in simple models that implement basic filter blocks in any configuration we require, as always taking care to ensure that overflow errors are checked for in practice.

To demonstrate this, we can implement a simple low-pass filter using the basic block diagram shown in Figure 32.2.

image

Figure 32.2 Simple Z-domain low-pass filter

We can create a simple test circuit that uses the individual models we have already shown for the sum and delay blocks and apply a step change and observe the response of the filter to this stimulus. (Clearly, in this case, with unity-gain the filter exhibits positive feedback and so to ensure the correct behavior we use the divide by 2 model zdiv2 in both the inputs to the sum block to ensure gain of 0.5 on both. These are not shown in the Figure 32.2.) The resulting VHDL model is shown below (note the use of the zdiv2 model):

library ieee;

use ieee.std_logic_1164.all;

use ieee.numeric_std.all;

entity tb is

end entity tb;

architecture testbench of tb is

signal clk : std_logic := ‘0’;

signal x : signed (7 downto 0):= X“00”;

signal y : signed (7 downto 0):= X“00”;

signal y1 : signed (7 downto 0):= X“00”;

signal yd : signed (7 downto 0):= X“00”;

signal yd2 : signed (7 downto 0):= X“00”;

signal x2 : signed (7 downto 0):= X“00”;

component zsum

generic (

n : integer : = 8

);

port (

signal zin1 : in signed(n-1 downto 0);

signal zin2 : in signed(n-1 downto 0);

signal zout : out signed(n-1 downto 0)

);

end component;

for all : zsum use entity work.zsum;

component zdiff

generic (

n : integer := 8

);

port (

signal zin1 : in signed(n-1 downto 0);

signal zin2 : in signed(n-1 downto 0);

signal zout : out signed(n-1 downto 0)

);

end component;

for all : zdiff use entity work.zdiff;

component zdiv2

generic (

n : integer := 8

);

port (

signal zin : in signed(n-1 downto 0);

signal zout : out signed(n-1 downto 0)

);

end component;

for all : zdiv2 use entity work.zdiv2;

component zdelay

generic (

n : integer := 8

);

port (

signal clk : in std_logic;

signal zin : in signed(n-1 downto 0);

signal zout : out signed(n-1 downto 0)

);

end component;

for all : zdelay use entity work.zdelay;

begin

clk <= not clk after 1 us;

GAIN1 : zdiv2 generic map (8) port map (x, x2);

GAIN2 : zdiv2 generic map (8) port map (yd, yd2);

SUM1 : zsum generic map (8) port map (x2, yd2, y);

D1 : zdelay generic map (8) port map (clk, y, yd);

x <= X“00”, X“0F” after 10 us;

end architecture testbench;

The test circuit applies a step change of X“00” to X“0F” after 10 μs, and this results in the filter response. We can show this graphically in Figure 32.3 with the output in both hexadecimal and “analog” form for illustration.

image

Figure 32.3 Basic low-pass filter simulation waveforms

It is interesting to note the effect of using the zdiv2 function on the results. With the input of 0F (binary 00001111) we lose the LSB when we divide by 2, giving the resulting input to the sum block of 00000111 (7), which added together with the division of the output gives a total of 14 as the maximum possible output from the filter. In fact, the filter gives an output of X“0D” or binary 00001101, which is two down from the theoretical maximum of X“0F” and this highlights the practical difficulties when using a “coarse” approximation technique for numerical work rather than a fixed- or floating-point method. On the other hand, it is clearly a simple and effective method of implementing a basic filter in VHDL.

Elsewhere in this book, the use of fixed- and floating-point numbers are discussed, as is the use of multiplication for more exact calculations and for practical filter design. Where higher accuracy is required, then it is likely that both these methods would be used. There may be situations, however, where it is simply not possible to use these advanced techniques, particularly a problem when space is at a premium on the FPGA and in these cases, the simple approach described in this chapter will be required.

There are numerous texts on more advanced topics in digital filter design, and these are beyond the scope of this book, but it is useful to introduce some key concepts at this stage of the two main types of digital filter in common usage today. These are the recursive (or Infinite Impulse Response – IIR) filters and nonrecursive (or Finite Impulse Response – FIR) filters.

32.5 FIR Filters

FIR filters are characterized by the fact that they use only delayed versions of the input signal to filter the input to the output. For example, if we take the expression for a general FIR filter below, we can see that the output is a function of a series of delayed, scaled versions of the input:

image

where Ai is the scale factor for the ith delayed version of the input. We can represent this graphically in the diagram shown in Figure 32.4. We can implement this model using the basic building blocks described in this chapter of gain, division, sums and delays to develop block based models for such filters. As noted in the previous section, it is important to ensure that for higher accuracy filters, fixed- or floating-point arithmetic is required and also the use of multipliers for added accuracy is preferable in most cases to that of simple gain and division blocks as described previously in this chapter.

image

Figure 32.4 FIR Filter Schematic

32.6 IIR Filters

IIR filters are characterized by the fact that they use delayed versions of the input signal and fed back and delayed version of the output signal to filter the input to the output. For example, if we take the expression for a general IIR filter below, we can see that the output is a function of a series of delayed, scaled versions of the input and output:

image

where Ai is the scale factor for the ith delayed version of the input and Bi is the scale factor for the ith delayed version of the output. This is obviously very similar to the FIR example previously and can be built up using the same basic elements. If we consider the simple example earlier in this chapter, it can be seen that this is in fact a simple first-order (single delay) IIR filter, with no delayed versions of the input and a single delayed version of the output.

32.7 Summary

This chapter has introduced the concepts of implementing basic digital filters in VHDL and has given examples of both the building blocks and constructed filters for implementation on an FPGA platform. The general concepts of FIR and IIR filters have been introduced so that the reader can implement the topology and type of filter appropriate for their own application.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset