This project will explore the capabilities of ZYNQ SOCs and Digilent's EclypseZ7 with ZMODs in signal processing at the edge.
Project use PL of the zynq for implement a signal processing system with a 32 order fir filter, a clock enable generator for reduce the frequency, and a signal generator. The PS also is used for run PetaLinux and a C application for compute the coefficients, a Hanning window, quantification and send them to the custom IP through AXI Bus.
First of all we need to download the board file from Digilent's Github and if you do not have Git installed on your system, you can download in.zip format. Otherwise, just clone the repository and copy folder eclypse-z7 on <vivado dir>/data/boards/board_files
If you use Linux, and your system language is different than your local language, Vivado sometimes has problems with the decimal separator. For avoid that, change the environment variable LC_NUMERIC to your default language. In my case, I added this line to.bashrc.
export LC_NUMERIC="en_US.UTF-8"
Now, open Vivado from terminal.
Creating the project for Eclypse-Z7Once we have the doard file in Vivado's directory, we can create the project, and select Eclypse-Z7 as our part.
Filter we will use in this design is packaged on the IP axi_fir32_v1_0. This IP implements a 32 order FIR filter. Coefficients of the filter will be configured through AXI Bus from the PS.
Next you can see how FIR filter adders and multipliers are implemented, using a MAC structure. This makes easy to synthesizer optimize the DSP48E1 Slices.
/* Multiplier-Accumulator (MAC) structure */
assign ws64_pipe_coeff[0] = rs32_pipe[0]*is32_coeff_0;
assign ws64_pipe_coeff[1] = rs32_pipe[1]*is32_coeff_1+ws64_pipe_coeff[0];
assign ws64_pipe_coeff[2] = rs32_pipe[2]*is32_coeff_2+ws64_pipe_coeff[1];
assign ws64_pipe_coeff[3] = rs32_pipe[3]*is32_coeff_3+ws64_pipe_coeff[2];
assign ws64_pipe_coeff[4] = rs32_pipe[4]*is32_coeff_4+ws64_pipe_coeff[3];
assign ws64_pipe_coeff[5] = rs32_pipe[5]*is32_coeff_5+ws64_pipe_coeff[4];
assign ws64_pipe_coeff[6] = rs32_pipe[6]*is32_coeff_6+ws64_pipe_coeff[5];
assign ws64_pipe_coeff[7] = rs32_pipe[7]*is32_coeff_7+ws64_pipe_coeff[6];
assign ws64_pipe_coeff[8] = rs32_pipe[8]*is32_coeff_8+ws64_pipe_coeff[7];
assign ws64_pipe_coeff[9] = rs32_pipe[9]*is32_coeff_9+ws64_pipe_coeff[8];
assign ws64_pipe_coeff[10] = rs32_pipe[10]*is32_coeff_10+ws64_pipe_coeff[9];
assign ws64_pipe_coeff[11] = rs32_pipe[11]*is32_coeff_11+ws64_pipe_coeff[10];
assign ws64_pipe_coeff[12] = rs32_pipe[12]*is32_coeff_12+ws64_pipe_coeff[11];
assign ws64_pipe_coeff[13] = rs32_pipe[13]*is32_coeff_13+ws64_pipe_coeff[12];
assign ws64_pipe_coeff[14] = rs32_pipe[14]*is32_coeff_14+ws64_pipe_coeff[13];
assign ws64_pipe_coeff[15] = rs32_pipe[15]*is32_coeff_15+ws64_pipe_coeff[14];
assign ws64_pipe_coeff[16] = rs32_pipe[16]*is32_coeff_16+ws64_pipe_coeff[15];
assign ws64_pipe_coeff[17] = rs32_pipe[17]*is32_coeff_17+ws64_pipe_coeff[16];
assign ws64_pipe_coeff[18] = rs32_pipe[18]*is32_coeff_18+ws64_pipe_coeff[17];
assign ws64_pipe_coeff[19] = rs32_pipe[19]*is32_coeff_19+ws64_pipe_coeff[18];
assign ws64_pipe_coeff[20] = rs32_pipe[20]*is32_coeff_20+ws64_pipe_coeff[19];
assign ws64_pipe_coeff[21] = rs32_pipe[21]*is32_coeff_21+ws64_pipe_coeff[20];
assign ws64_pipe_coeff[22] = rs32_pipe[22]*is32_coeff_22+ws64_pipe_coeff[21];
assign ws64_pipe_coeff[23] = rs32_pipe[23]*is32_coeff_23+ws64_pipe_coeff[22];
assign ws64_pipe_coeff[24] = rs32_pipe[24]*is32_coeff_24+ws64_pipe_coeff[23];
assign ws64_pipe_coeff[25] = rs32_pipe[25]*is32_coeff_25+ws64_pipe_coeff[24];
assign ws64_pipe_coeff[26] = rs32_pipe[26]*is32_coeff_26+ws64_pipe_coeff[25];
assign ws64_pipe_coeff[27] = rs32_pipe[27]*is32_coeff_27+ws64_pipe_coeff[26];
assign ws64_pipe_coeff[28] = rs32_pipe[28]*is32_coeff_28+ws64_pipe_coeff[27];
assign ws64_pipe_coeff[29] = rs32_pipe[29]*is32_coeff_29+ws64_pipe_coeff[28];
assign ws64_pipe_coeff[30] = rs32_pipe[30]*is32_coeff_30+ws64_pipe_coeff[29];
assign ws64_pipe_coeff[31] = rs32_pipe[31]*is32_coeff_31+ws64_pipe_coeff[30];
assign ws64_pipe_coeff[32] = rs32_pipe[32]*is32_coeff_32+ws64_pipe_coeff[31];
Creating the block design.Before create the block design we need to add some sources to the project.
cen_generator_v1_0
This file generate a clock enable signal for the rest of the modules. The configuration of this module will set the output frequency of the signal and the cut frequency of the filter.
signal_bram_reader_v1_0
Since out design does not use the ADC, signal has been stored in a Block RAM. This module read this Block RAM and generate one sample each clock enable cycle, emulating an ADC.
obuf_ds_inst
This module instantiate a differential output buffer. This kind of buffers can not be added though IP catalog, so we have to create a.v file and instantiate it inside.
Once this modules are in our project, we can create a block design. For use the FIR32 ip, we need to add the project IP repository. Next we can add all the components of the block design. The result must be similar to this.
When block design is complete, we have to generate the signal we will filter. The width of the signal must be 14 bits and 1024 words of length. In my case, the signals has been generated with the next equation:
signal = 0.4*cos(t)+0.1*sin(5*t)+0.1*cos(10*t)+0.3*cos(30*t)
Signal has 2 low frequency harmonics, one mid frequency harmonic and the last one that emulates noise.
This signal will be stored in signal2.mem file, and readed by signal_bram_reader_v1_0 block.
Once the block design is complete, and the signal is generated, the next is implement the project and generate the bitstream.
When the design has been implemented. Export hardware including bitstream.
File > Export > Export Hardware
Create a new PetaLinux project with this command.
petalinux-create --type project --template zynq --name eclypsez7_plnx
Now navigate to PetaLinux project and Open the hardware description.
petalinux-config --get-hw-description ../../../xilinx/eclypse_plnx/eclypse_plnx.sdk/
The next that I always do is configured an static IP for connect the board directly to the computer.
Subsystem AUTO Hardware Settings -->
Ethernet Settings -->
[ ] Obtain IP address automatically = N
(192.168.1.10) Static IP address
(255.255.255.0) Static IP netmask
(192.168.0.1) Static IP gateway
In this case, we will not build the application together with Linux, instead we build the application inside Linux. Doing that we can split two builds and if application has to be modified, we can do it without any modification in Linux.
For allo compile applications in Linux, we have to add to our distribution the gcc compiler. We can do that configuring the rootfs.
petalinux-config -c rootfs
Filesystem packages --> misc --> packagegroup-core-buildessential
Once the package is added we can build our PetaLinux distribution.
petalinux-build
Once PetaLinux is built, we need to create the image file and boot file.
cd <petalinux_proj>/images/linux
petalinux-package --boot --fsbl zynq_fsbl.elf --fpga system.bit –uboot
Now copy files on SD card, insert the SD in EclypseZ7 board, and turn on the board.
fir32 C application.Once the board has started, we will connect to the EclypseZ7 via SSH.
ssh root@192.168.1.10
and when it ask for the pass, we have to type root.
Then we have to copy the application from our computer to the Eclypse Z7. If you use Linux, this can be though scp command. In the case that you use Windows, you can download the application winscp and use it to copy the.c file.
Once we have the application on Eclypse, we can build it with gcc.
root@eclypsez7_fir:~# gcc -lm fir32driver.c
A file named a.out will be create in the same folder.
The application that I develop have 3 arguments. The first one is the cut frequency. This value must be greater than 0 and lower than 0.5, and the argument is multiplied by 1000, so the value of the argument can admit a value between 0 and 500. The next argument is the gain of the filter, being 100 for a gain of 1, and the last one is a boolean argument, and indicate to the application if we want to use a Hanning window to compute FIR arguments.
For the signal I created, we want discard the most as possible the noise, and preserve the low harmonics. For get that, the value of the cut frequency has to be 0.04, a 40 in the argument, and the gain has to be 0.95, a 95 in the argument. Testing this applivation, I get better results without window, so the last value will be 0, and this is what application returns.
root@eclypsez7_fir:~# ./a.out 40 95 0
wc = 0.080
Computing fir coefficients
-0.013922 -0.011329 -0.007602 -0.002787 0.003020 0.009675 0.016993 0.024751 0.032698 0.040569 0.048088 0.054990 0.061024 0.065968 0.069638 0.071897 0.072659 0.071897 0.069638 0.065968 0.061024 0.054990 0.048088 0.040569 0.032698 0.024751 0.016993 0.009675 0.003020 -0.002787 -0.007602 -0.011329 -0.013922
Compute Hamming window
0.080000 0.088839 0.115015 0.157524 0.214731 0.284438 0.363966 0.450258 0.540000 0.629742 0.716034 0.795562 0.865269 0.922476 0.964985 0.991161 1.000000 0.991161 0.964985 0.922476 0.865269 0.795562 0.716034 0.629742 0.540000 0.450258 0.363966 0.284438 0.214731 0.157524 0.115015 0.088839 0.080000
Quantifying coefficients
-59795728 -48656120 -32649514 -11971062 12968666 41553940 72984184 106303528 140438336 174241072 206538480 236181760 262096496 283329728 299092224 308793504 312068480 308793504 299092224 283329728 262096496 236181760 206538480 174241072 140438336 106303528 72984184 41553940 12968666 -11971062 -32649514 -48656120 -59795728
Writting coefficients on PL
The application return the values of all coefficients, in all the steps.
The result is as expected.
Low harmonics are present in the filtered signal, and the high harmonics are rejected. The harmonic near to 20khz is in the very close to the cut frequency, and its value is arround 0.5 of the original, as expected in a FIR filter.
Comments