HDL Programmable FIR Filter
This example illustrates how to generate HDL code for an FIR filter with a processor interface for loading coefficients. The filter can be programmed to any desired response by loading the coefficients into an internal coefficient memory using the processor interface.
Let us assume that we need to implement a bank of filters, having different responses, on a chip. If all of the filters have a direct-form FIR structure, and the same length, then we can use a processor interface to load the coefficients for each response from a RAM or register file when needed.
This design will add latency of a few cycles before the input samples can be processed with the loaded coefficients. However, it has the advantage that the same filter hardware can be programmed with new coefficients to obtain a different filter response. This saves chip area, as otherwise each filter would be implemented separately on the chip.
In this example, we will consider two FIR filters, one with a highpass response and the other with a lowpass response. We will show how the same filter hardware can be programmed for each response by loading the corresponding set of coefficients. We will generate VHDL code for the filter and show the two responses using the generated VHDL test bench.
Design the Filters
Create the lowpass filter design object, then create the FIR Filter System object (Hlp). Then, transform it to create a FIR Filter System object with a highpass response (Hhp).
Fpass = 0.45; % Passband Frequency Fstop = 0.55; % Stopband Frequency Apass = 1; % Passband Attenuation (dB) Astop = 60; % Stopband Attenuation (dB) f = fdesign.lowpass('Fp,Fst,Ap,Ast',Fpass,Fstop,Apass,Astop); lpFilter = design(f, 'equiripple','FilterStructure', 'dfsymfir','SystemObject',true); % Lowpass hpcoeffs = firlp2hp(lpFilter.Numerator); hpFilter = dsp.FIRFilter('Numerator', hpcoeffs); % Highpass
Quantize the Filters
Assume the coefficients need be stored in a memory of bit width 14. Using this information, apply fixed point settings to the System object filter.
lpFilter.FullPrecisionOverride=false; lpFilter.CoefficientsDataType='Custom'; lpFilter.CustomCoefficientsDataType=numerictype(1,14,13); lpFilter.OutputDataType='Same as Accumulator'; lpFilter.ProductDataType='Full precision'; lpFilter.AccumulatorDataType='Full precision'; hpFilter.FullPrecisionOverride=false; hpFilter.CoefficientsDataType='Custom'; hpFilter.CustomCoefficientsDataType=numerictype(1,14,13); hpFilter.OutputDataType='Same as Accumulator'; hpFilter.ProductDataType='Full precision'; hpFilter.AccumulatorDataType='Full precision';
After applying fixed point settings, it is important to verify that the System object filter still meets the specifications. We will use the function 'measure' to check if this is true.
ans = Sample Rate : N/A (normalized frequency) Passband Edge : 0.45 3-dB Point : 0.46957 6-dB Point : 0.48314 Stopband Edge : 0.55 Passband Ripple : 0.89243 dB Stopband Atten. : 55.3452 dB Transition Width : 0.1
Verify the Filter Output
Generate a linear swept-frequency stimulus signal using chirp. Use this input stimulus for filtering through the lowpass FIR filter first. Then change the coefficients of the filter to obtain a highpass response and use the same input sample to filter again.
For the above two-stage filtering operation our goal is to compare the filter output from MATLAB® with that from the generated HDL code.
Plotting the input samples and the filtered output shows the lowpass and highpass behavior.
x = chirp(0:199,0,199,0.4); lpcoeffs = lpFilter.Numerator; % store original lowpass coefficients y1 = lpFilter(fi(x,1,14,13).'); % filter the signal lpFilter.Numerator = hpFilter.Numerator; % load the highpass filter coefficients y2 = lpFilter(fi(x,1,14,13).'); % filter the signal y = [y1; y2]; % concatenate output signals lpFilter.Numerator = lpcoeffs; % restore original lowpass coefficients subplot(2,1,1);plot([x,x]); xlabel('Time [samples]');ylabel('Amplitude'); title('Input Stimulus'); subplot(2,1,2);plot(y); xlabel('Time [samples]');ylabel('Amplitude'); title('Filtered Output');
Generate VHDL Code with Processor Interface and Test Bench
For the quantized lowpass filter, we will generate the VHDL code with a processor interface by setting the property 'CoefficientSource' to 'ProcessorInterface'. This will result in the generated code having additional ports for write_address, write_enable, coeffs_in, and write_done signals. This interface can be used to load the coefficients from a host processor into an internal register file. The HDL has an additional shadow register that is updated from the register file when the 'write_done' signal is high. This enables simultaneous loading and processing of data by the filter entity.
To verify that the filter entity can be successively loaded with two different sets of filter coefficients, we will generate a VHDL test bench. First, the test bench loads the lowpass coefficients and processes the input samples. Then the test bench loads the coefficients corresponding to the highpass filter response, and processes the input samples again.
The generated VHDL code and VHDL test bench can be compiled and simulated using an HDL simulator such as ModelSim®. Notice that the loading of the second set of coefficients and the processing of the last few input samples are performed simultaneously.
In order to generate the required test bench, we set the property 'GenerateHDLTestbench' to 'on' and pass 'TestbenchCoeffStimulus' in the call to the generatehdl command. The value passed in for 'TestbenchCoeffStimulus' is a vector of coefficients that are to be used for subsequent processing of input samples. This example passes in a vector of coefficients corresponding to a highpass filter.
Assume 14-bit signed fixed-point input with 13 bits of fraction precision is needed due to fixed data path requirements of input ADC.
%As the symmetric structure is selected, the field 'TestbenchCoeffStimulus' % has to be the half of length of filter. workingdir = tempname; generatehdl(lpFilter,'Name','FilterProgrammable', ... 'InputDataType',numerictype(1,14,13), ... 'TargetLanguage','VHDL', ... 'TargetDirectory',workingdir, ... 'CoefficientSource','ProcessorInterface', ... 'GenerateHDLTestbench','on', ... 'TestBenchUserStimulus',x, ... 'TestbenchCoeffStimulus',hpFilter.Numerator(1:(length(hpFilter.Numerator)+1)/2));
### Starting VHDL code generation process for filter: FilterProgrammable ### Generating: /tmp/Bdoc23b_2361005_717342/tp03e817a0_d984_4e0e_a35f_aebfed939133/FilterProgrammable.vhd ### Starting generation of FilterProgrammable VHDL entity ### Starting generation of FilterProgrammable VHDL architecture ### Successful completion of VHDL code generation process for filter: FilterProgrammable ### HDL latency is 2 samples ### Starting generation of VHDL Test Bench. ### Generating input stimulus ### Done generating input stimulus; length 200 samples. ### Generating Test bench: /tmp/Bdoc23b_2361005_717342/tp03e817a0_d984_4e0e_a35f_aebfed939133/FilterProgrammable_tb.vhd ### Creating stimulus vectors ... ### Done generating VHDL Test Bench.
ModelSim® Simulation Results
The following display shows the ModelSim HDL simulator after running the generated .do file scripts for the test bench. Compare the ModelSim result with the MATLAB result as plotted before.
We designed highpass and lowpass FIR filters to meet the given specifications. We then quantized the filter and generated VHDL code for the filter, with an interface to load the coefficients from a processor. We then generated a VHDL test bench that showed the processing of input samples after loading lowpass coefficients, repeating the operation with the highpass coefficients. We showed how to generate the VHDL code that implements filter hardware that is reusable for different responses when different sets of coefficients are loaded via the port interface from a host processor.