Generate HDL Code for Programmable FIR Filter

This example demonstrates how to generate HDL code for a programmable FIR filter. You can program the filter to a desired response by loading the coefficients into internal registers using the host interface.

In this example, we will implement a bank of filters, each 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 host interface to load the coefficients for each response to a register file when needed.

This design adds 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.


You must have an HDL Coder™ license to run this example.

Model Programmable FIR Filter

Enter the following commands to open the example model:

modelname = 'hdlcoder_dspprogfir';

Consider two FIR filters, one with a lowpass response and the other with a highpass response. The coefficients can be specified with the Model Properties InitFcn Callback function.

The Programmable FIR via Registers block loads the lowpass coefficients from the Host Behavioral Model, and processes the input chirp samples first. Then the block loads the highpass coefficients and processes the same chirp samples again.

Enter the following commands to open the Programmable FIR via Registers block:

systemname = [modelname '/Programmable FIR via Registers'];

The coeffs_registers block loads the coefficients into internal registers when the 'write_enable' signal is high. The following shadow registers are updated from the coefficients registers when the 'write_done' signal is high. This enables simultaneous loading and processing of data by the filter entity. In this example, we apply fully parallel architecture implementation to the Discrete FIR Filter block. You can also choose serial architectures from the HDL Block Properties menu.

Notice that the blocks load the second set of coefficients and process the last few input samples simultaneously.

Simulink® Simulation Results

Enter the following command to run the example model:


Enter the following command to open the scope:

open_system([modelname '/Scope']);

Compare the DUT (Design under Test) output with the reference output.

Enter the following command to close the scope:

close_system([modelname '/Scope']);

Using the Logic Analyzer

You can also view the signals in the Logic Analyzer. The Logic Analyzer enables you to view multiple signals in one window. It also makes it easy to spot the transitions in the signals.

% The signals of interest - input coefficient, write address, write enable,
% write done, filter in, filter out, reference out and error have been
% logged into a Dataset called hdlcoder_dspprogfir_logsout.

% The helper function hdlcoder_analyzeLogicFromSimulink creates a wave for every
% input provided in the Dataset. In order to make the default order of
% signals displayed more meaningful, we reorder the Dataset from Simulink
% to make the Coefficient signals appear first and the data signals next.
% The Error values are converted to logical values.
logsout_r = hdlcoder_dspprogfirReorderDataset(hdlcoder_dspprogfir_logsout);

% The Dataset can now be used to display data in the Logic Analyzer.
% We use the helper function hdlcoder_analyzeLogicFromSimulink, which
% creates a Logic Analyzer System object, adds in a wave for every input
% passed in, labels the wave based on the name of the logged signal and
% displays the data in the Dataset. The function returns a handle to
% the System object that can be used to modify the display.
H = hdlcoder_analyzeLogicFromSimulink(logsout_r);

Modifying the display in the Logic Analyzer

The Logic Analyzer System object has several properties to control its display. You can modify the height of all the display channels, the spacing between the display channels and the time span of the display.

H.DisplayChannelSpacing = 8;
H.DisplayChannelHeight  = 24;
H.TimeSpan = 12;

The Logic Analyzer display can also be controlled on a per-wave or per-divider basis. To modify an individual wave or divider, use the tag associated with it. You can obtain the tag associated with a wave or divider as the return value of addWave and addDivider, or you can get all the tags for the waves and dividers using the getDisplayChannelTags method.

% Get all the tags for the displayed waves
HDisplayTags = H.getDisplayChannelTags;

% View the write address (second wave in the display) in Decimal mode
modifyDisplayChannel(H, 'DisplayChannelTag', HDisplayTags{2}, ...
    'Radix', 'Signed decimal');

Another useful mode of visualization in the Logic Analyzer is the Analog format.

% View the Filter In, Filter Out and Ref Out signals in Analog format
modifyDisplayChannel(H, 'DisplayChannelTag', HDisplayTags{5}, ...
    'Format', 'Analog');
modifyDisplayChannel(H, 'DisplayChannelTag', HDisplayTags{6}, ...
    'Format', 'Analog');
modifyDisplayChannel(H, 'DisplayChannelTag', HDisplayTags{7}, ...
    'Format', 'Analog');
H.TimeSpan = 500;

The Logic Analyzer allows you to add in dividers and waves and move them around in the display. By default, the wave or divider is added to the bottom of the display. If desired, the display channel can be passed in while adding in the wave or divider. The wave or divider can also be moved after it has been added to the display.

% Add dividers for the coefficient and data signals. The divider for the
% coefficient signals is set to be shown in Display Channel 1. The divider
% for the data signals is added to the bottom of the display.
addDivider(H, 'DisplayChannel', 1, 'Name', 'Coeff');
divTagData = addDivider(H, 'Name', 'Data');

% Increase the size of the window so that all the waves and dividers are
% visible.
pos = H.Position;
H.Position = [pos(1) pos(2) pos(3) pos(4)+100];

Using the tag for a given wave or divider, the moveDisplayChannel method can be used to move it to the Display Channel of your choice.

% Move the Data signal divider to Display Channel 6, right before the
% Filter In wave display
moveDisplayChannel(H, 'DisplayChannelTag', divTagData, 'DisplayChannel', 6);

The above operations on individual channels are also accessible through the GUI by right-clicking on the channel name.

For further information on the Logic Analyzer System object, refer to the documentation.

Generate HDL Code and Test Bench

Get a unique temporary directory name for the generated files

workingdir = tempname;

To check whether there are any issues with the model for HDL code generation, without generating HDL code, you can run the following command:


Enter the following command to generate HDL code:

### Generating HDL for 'hdlcoder_dspprogfir/Programmable FIR via Registers'.
### Using the config set for model <a href="matlab:configset.showParameterGroup('hdlcoder_dspprogfir', { 'HDL Code Generation' } )">hdlcoder_dspprogfir</a> for HDL code generation parameters.
### Starting HDL check.
### Begin VHDL Code Generation for 'hdlcoder_dspprogfir'.
### Working on hdlcoder_dspprogfir/Programmable FIR via Registers/coeffs_registers as /tmp/Bdoc19b_1305395_44507/tp5e33a715_9de8_43fb_bf33_2328ea2fe3fa/hdlcoder_dspprogfir/coeffs_registers.vhd.
### Working on hdlcoder_dspprogfir/Programmable FIR via Registers/Discrete FIR Filter as /tmp/Bdoc19b_1305395_44507/tp5e33a715_9de8_43fb_bf33_2328ea2fe3fa/hdlcoder_dspprogfir/Discrete_FIR_Filter.vhd.
### Working on hdlcoder_dspprogfir/Programmable FIR via Registers as /tmp/Bdoc19b_1305395_44507/tp5e33a715_9de8_43fb_bf33_2328ea2fe3fa/hdlcoder_dspprogfir/Programmable_FIR_via_Registers.vhd.
### Generating package file /tmp/Bdoc19b_1305395_44507/tp5e33a715_9de8_43fb_bf33_2328ea2fe3fa/hdlcoder_dspprogfir/Programmable_FIR_via_Registers_pkg.vhd.
### Creating HDL Code Generation Check Report file:///tmp/Bdoc19b_1305395_44507/tp5e33a715_9de8_43fb_bf33_2328ea2fe3fa/hdlcoder_dspprogfir/Programmable_FIR_via_Registers_report.html
### HDL check for 'hdlcoder_dspprogfir' complete with 0 errors, 0 warnings, and 0 messages.
### HDL code generation complete.

Enter the following command to generate the test bench:

### Begin TestBench generation.
### Generating HDL TestBench for 'hdlcoder_dspprogfir/Programmable FIR via Registers'.
### Begin simulation of the model 'gm_hdlcoder_dspprogfir'...

### Collecting data...
### Generating test bench data file: /tmp/Bdoc19b_1305395_44507/tp5e33a715_9de8_43fb_bf33_2328ea2fe3fa/hdlcoder_dspprogfir/coeffs_in.dat.
### Generating test bench data file: /tmp/Bdoc19b_1305395_44507/tp5e33a715_9de8_43fb_bf33_2328ea2fe3fa/hdlcoder_dspprogfir/write_address.dat.
### Generating test bench data file: /tmp/Bdoc19b_1305395_44507/tp5e33a715_9de8_43fb_bf33_2328ea2fe3fa/hdlcoder_dspprogfir/write_enable.dat.
### Generating test bench data file: /tmp/Bdoc19b_1305395_44507/tp5e33a715_9de8_43fb_bf33_2328ea2fe3fa/hdlcoder_dspprogfir/write_done.dat.
### Generating test bench data file: /tmp/Bdoc19b_1305395_44507/tp5e33a715_9de8_43fb_bf33_2328ea2fe3fa/hdlcoder_dspprogfir/filter_in.dat.
### Generating test bench data file: /tmp/Bdoc19b_1305395_44507/tp5e33a715_9de8_43fb_bf33_2328ea2fe3fa/hdlcoder_dspprogfir/filter_out_expected.dat.
### Working on Programmable_FIR_via_Registers_tb as /tmp/Bdoc19b_1305395_44507/tp5e33a715_9de8_43fb_bf33_2328ea2fe3fa/hdlcoder_dspprogfir/Programmable_FIR_via_Registers_tb.vhd.
### Generating package file /tmp/Bdoc19b_1305395_44507/tp5e33a715_9de8_43fb_bf33_2328ea2fe3fa/hdlcoder_dspprogfir/Programmable_FIR_via_Registers_tb_pkg.vhd.
### HDL TestBench generation complete.

ModelSim™ Simulation Results

The following figure shows the ModelSim HDL simulator after running the generated .do file scripts for the test bench. Compare the ModelSim result with the Simulink result as plotted before.

This ends the Generate HDL Code for Programmable FIR Filter example.