Red Pitaya FPGA Project 3 – Stopwatch
In this Red Pitaya FPGA project we will learn about communication between the Linux processing system and the programmable logic. We will demonstrate the basic functionality with a simple FPGA project – Stopwatch.
The FPGA algorithm will be based on a 32-bit binary counter with Clock Enable (CE) and Synchronous Clear (SCLR) inputs which we will control from the Linux side. The counter’s output value we will be read with a Linux program and partially displayed with the on-board LEDs. In order to do this we will use standard AXI communication protocol that connects different parts on the Zynq device such as processing system, programmable logic, DDR memory, external peripherals and more.
To build Stopwatch project in Vivado you can either download the project’s tcl script from the github as described in the first post or simply go through the following steps. This guide assumes that you are familiar with the concepts introduced in the previous two posts.
Open one of the previous Vivado projects to get a basic block diagram. Next, insert the binary counter, if it is not already present, and add CE and SCLR ports. This can be done by double-clicking on the binary counter IP core in our block design and select CE and SCLR under the Control tab. Then add xlslice IP block with Din Width: 32, Din From: 31, Din Down To: 24. Connect CLK pin of the binary counter to PS’s FCLK_CLK0, output of the binary counter to the input of the xlslice and output of the xlslice to led_o external port. This last part will display 8 MSBs of the 32-bit counter on Red Pitaya’s LED bar. If you started from Project 1 change the LEFT property of led_o port from 0 to 7.
We are ready to insert AXI General Purpose IO IP core (AXI GPIO) to our block design. When the core is added, double-click on the block, check Enable Dual Channel and set All Inputs for the GPIO 2. To connect the AXI GPIO to the processing system click on Run Connection Automation on top of the block design. Select S_AXI and click OK. This will automatically create AXI Interconnect and Processor System Reset blocks. Next, add two xlslice IP cores with 32-bit Din. xls_CE should have Din From and Din Down To both set to 0 and xls_SCLR should have them both set to 1. Connect all the blocks as shown in the figure below.
Next, we need to set the AXI GPIO core’s memory address and range. We will use this address later to access the IP core from the Linux side. On top of the window choose Address Editor tab and set all the values as shown below. Remember the address of our GPIO block is 0x4200_0000.
The FPGA program is ready. Proceed with synthesis, implementation and generate the bitstream file. When the file is generated and copied to a folder on Red Pitaya’s Linux write the bitstream file to programmable logic with the following command
cat system_wrapper.bit > /dev/xdevcfg
To write or read from our FPGA program we will use Red Pitaya’s monitor tool available in the Red Pitaya’s Linux. Try the following commands.
monitor 0x42000000 1 # write: start, SCLR = 0, CE = 1 monitor 0x42000000 0 # write: stop, SCLR = 0, CE = 0 monitor 0x42000000 2 # write: clear, SCLR = 1, CE = 0 monitor 0x42000000 # read: cfg on GPIO1 monitor 0x42000008 # read: data on GPIO2
Great, we have created a stopwatch with a resolution of 8 ns! Using AXI communication protocol we can easily access our GPIO IP core. More details about the GPIO core can be found in the references part of this post.
If you would like to know how much time has passed between start and stop in seconds and not in the number of clock cycles, you can use the following program on Linux to write, read and convert data. This program, based on Pavel Demin’s code, can also be a useful template for more advanced applications where you need to set several parameters and read large amount of data generated on FPGA.
stopwatch.c:
#include <stdio.h> #include <stdint.h> #include <unistd.h> #include <sys/mman.h> #include <fcntl.h> #include <stdlib.h> int main(int argc, char **argv) { int fd; float wait_time; uint32_t count; void *cfg; char *name = "/dev/mem"; const int freq = 124998750; // Hz if (argc == 2) wait_time = atof(argv[1]); else wait_time = 1.0; if((fd = open(name, O_RDWR)) < 0) { perror("open"); return 1; } cfg = mmap(NULL, sysconf(_SC_PAGESIZE), /* map the memory */ PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0x42000000); *((uint32_t *)(cfg + 0)) = 2; // clear timer *((uint32_t *)(cfg + 0)) = 1; // start timer sleep(wait_time); // wait for [wait_time] seconds *((uint32_t *)(cfg + 0)) = 0; // stop timer count = *((uint32_t *)(cfg + 8)); // get binary counter output printf("Clock count: %5d, calculated time: %5f s\n", count, (double)count/freq); munmap(cfg, sysconf(_SC_PAGESIZE)); return 0; }
stopwatch.c program maps the memory at a given address to cfg pointer. By writing an appropriate 32-bit value to this pointer the code first clears the counter by setting SCLR (2nd bit), then starts the count by setting CE (1st bit). After wait_time in seconds the program stops the counter by clearing the CE bit. To read counter’s output value we need to access the second port of the GPIO IP core. According to GPIO documentation the address of the second port is shifted by 8 (0x4200_0008). At the end the counter output value is scaled by the FCLK_CLK0 frequency and printed on the screen.
Compile and execute the program as shown here.
gcc -o stopwatch stopwatch.c ./stopwatch 5 # wait for 5 s
Interestingly, FCLK_CLK0 has a frequency of 124.99875 MHz (= 3.75*33.333 MHz). This is a default Red Pitaya frequency generated by IO PLL using 33.333 MHz external clock (PS_CLK). To increase the frequency to, for example, 143 MHz use the bash script mentioned by Jean in the comments.
In conclusion, we have created another simple project where we learned how to communicate between our FPGA program and Linux running on Red Pitaya’s Zynq7 ARM processor. Armed with this knowledge we can now move to more serious projects such as high-speed ADC averager for heterodyne detection or lock-in amplification. Coming soon.
<< Red Pitaya Project 2 – Knight Rider | Red Pitaya Project 4 – Frequency Counter >> |
20 Comments
Hello,
Thank you for your posts.
It looks like your Linux image has been build with fclk0 at 125 MHz by default. You can set fclk0 to 143 MHz with the following bash script:
devcfg=/sys/devices/soc0/amba/f8007000.devcfg
test -d $devcfg/fclk/fclk0 || echo fclk0 > $devcfg/fclk_export
echo 1 > $devcfg/fclk/fclk0/enable
echo 143000000 > $devcfg/fclk/fclk0/set_rate
Cheers,
Jean
Hi Jean,
Great! Thanks for this comment.
Hi Jean (and Anton),
I need to change fclk0 to a lower value (e.g., 12.5MHz). The script looks promising but I haven’t had any luck when I try it. My RP image is version 0.97 (build 336). Do I need to install anything?
FWIW, I need to generate low frequency sine/square waves (0.5 – 100Hz) with enough accuracy and precision that I can select exact multiples between the two channels (e.g., 0.5 and 2Hz). Unfortunately because the default fclk0 is so high, the phase increments used for the waveform generators at the required frequencies are very small. Hence, the actual frequencies for 0.5 and 2Hz are 0.466 and 1.979Hz. If I can reduce fclk0 by just a factor of 10 I will be able to get actual frequencies of 0.499 and 2.000Hz. Reducing by another order of magnitude would be even better for my needs.
Thank you,
John.
Hi John,
I don’t know why Jean’s method does not work in your case. One could investigate how much fclk could be modified. Another trick you can try is to use the Clocking Wizard IP, which you probably already have in your Block Design connected to DAC block. This will allow you to derive new clocks with more than an order of magnitude lower frequencies compared to your existing clock.
Let me know if this works out.
Best
Hi Apotocnik,
Thanks in advance for all instructions! You are certainly helping many RedPitaya users!
In this tutorial, I did not understand how to implement the C code mentioned.
Where and how is the ‘stopwatch.c’ implemented? Do I have to include it in Vivado’s project?
Thanks
Hi Charles,
Thanks for the comment. “stopwatch.c” program is used for writing and reading data from your FPGA, similar to the monitor tool.
This c code needs to be compiled and executed in Linux running on Red Pitaya. To connect to Red Pitaya’s Linux simply use Putty or ssh as described in the first post.
Hope this helps.
Anton
Hi Anton!
I really appreciate your attention!
Another question… In order to work with the extension connector (E1), what I have to change in Vivado’s project? I would like to work with all digital IO pins located in E1 connector. For instance, I would like to make a simple application using a digital pin to read a digital level by pressing an external pushbutton and light an external LED using another pin.
Sorry for any inconvenience and thank you again!
Best regards!
Hi Charles,
No problem. For that you can use exp_p_tri_io port. Have a look at the cfg/port.xdc constraint file and port construction file cfg/port.tcl.
Since IO port cannot be simply connected in IP integrator use tcl command: “connect_bd_net [get_bd_pins exp_p_tri_io] [get_bd_pins xlconcat_0/In1]” or delete the port
and define it as the input only with tcl command: “create_bd_port -dir I -from 7 -to 0 exp_p_tri_io”.
You probably know about Red Pitaya schematics: https://dl.dropboxusercontent.com/s/jkdy0p05a2vfcba/Red_Pitaya_Schematics_v1.0.1.pdf
And Sensor Extension Documentation page: http://redpitaya.readthedocs.io/en/latest/doc/appsFeatures/visualProgramming/visualProgramming.html#connectors
Good luck.
Hi Anton!
Thank you so much for helping me!
I’m starting to understand FPGA programming under Red Pitaya’s board.
Please, don’t stop to make more tutorials!
They are quite helpful!
Cheers!
Hi Anton!
It’s me again!
I’ve been studying about AXI interfaces in order to achieve the communication between PL and PS.
Could I ask some advice to you?
I’m trying to implement a basic application with my Red Pitaya (a Zynq-based board) using its on-board fast analog input.
I would like to start an acquisition after a trigger signal coming from the linux side, through a c-code for instance.
After that, the acquired data must return to the linux side for processing.
Finally, based on the results obtained, I would like to send some digital signals to the GPIO (located in the on-board expansion connector E1.)
I already know that this communication – between Linux and FPGA (in other words, between PS and PL) – is performed by AXI interfaces. For instance: CPU AXI bus GPIO module GPIO pins.
Also, I know that to read and send data or commands, it is necessary to map the memory.
My questions are…
1) Which is the minimum project on Vivado? I mean, which modules/IPs are necessary for this purpose? I already have all basic configurations (ports, etc.). I know it seems a dummy question, but I have some difficulties concerning AXI Interfaces. I keep reading, but I’m still in doubt.
2) How can I implement this communication in the PS side, through a simple C-code?
In other words, how can I send the trigger signal to start acquistion, read the acquired data and send some digital values back to GPIOs?
If my questions take so much time, could you recommend any tutorial/document (e.g., video, book, etc.) to read?
Thanks in advance and sorry for any inconvenience!
Dear Charles,
I am currently working on a new project/post exactly on these topics called averager. I have put a beta version of the project on the github with a very short description on the github wiki.
As for the literature all I could recommend are the Pavel Demin’s projects and a some youtube videos like
Dear Anton!
I really appreciate your attention in advance!
I have just finished the entire Zynq Traininq you suggested.
It helped me a lot but I still have some issues concerning the communication between PL and PS. I’m not able to implement my application yet.
Now, I’m taking a look at the beta version of your new project on github!
I will let you know if there is any further update!
Thank you again for the help!
I look forward to seeing your new post here!
Regards!
Hello Anton!
It`s me again!
The code you have mentioned before called `averager`.
I have downloaded it from your git-hub and I`m trying to understand it.
I was not able to implement/run it on my FPGA. Is the project already executable?
Could you give me same explanations about the project. I`ve already seen what you have written on the the github wiki, but I could not understand so much.
Thank you again for your attention!
Regards!
Hi Anton! Happy New Year!! I hope you’re doing well!
After reading more about some functions (such as mmap(), munmap(), etc.) and learning more about the communication using stream sockets, I’m starting to understand the ‘averager’. I still have to get familiar with some concepts, but I’m excited after some progress. This project is quite complex!
Congratulations for the job! I’ll keep working on it until understand!
I look forward to seeing your new post here!
Best regards!
Hi Anton,
first of all: I appreciate the work you put into these tutorials, they are really good.
Nevertheless I have a question: I used the stopwatch design as a starting point and tried to utilize the GPIO pins of the expansion header (exp_p/n_tri_io) for digital signals. Unfortunately, this is already as far as I could get, because I expected that I could simply add another Slice to the output of the AXI GPIO module (axi_gpio_0) and connect that to the IO-Port. But in the block design I am not able to connect the slice and the IO port. I tried changing IO port to be a output port, but that didnt help.
Maybe I’m trying something obviously wrong, in that case it would be nice if you could point that out to me.
Thanks!
Hi Christof,
In my comment on November 27, 2016 at 12:39 am above I describe two methods how to connect output from your slice block to the exp_p_tri_io port. Let me know if that work for you.
Hi Anton,
thanks for the help, I must have missed that comment… Actually I used another approach and wrote a piece of code which basically contains only the IOBUF IP from Xilinx (don’t know why this isnt available in Block Design). See https://www.xilinx.com/support/documentation/sw_manuals/xilinx13_4/spartan6_hdl.pdf, page 126f
After integrating that module into the Block Design, I was able to connect the inout port of my module to the expansion IO-port. The tristate-select input is constant for now, because I’m just playing around with the expansion as output.
Hi Anton,
i would like to know how to program certain bits train, i.e simple data sequence from a certain output. one of expansion of the E1 connector. could please help
thanks in advnaced
Hi Anton,
I’m new to Red Pitaya. I wonder if there is any way to use Python instead of C? Something like overlay. I saw RedPitaya has Jupyter NoteBook but don’t know how to use it with a custom .bit file.
Hi Sukho,
I’m new to using the FPGA programmable logic as well. I was interested in making the PL communicate with a Jupyter NoteBook as well. With some searching and combining other’s code I managed to get the Python code below to work in a notebook (after running cat /root/stopwatch.bit > /dev/xdevcfg)
import mmap
import os
import time
import numpy as np
axi_gpio_regset = np.dtype([
('gpio1_data' , 'uint32'),
('gpio1_control', 'uint32'),
('gpio2_data' , 'uint32'),
('gpio2_control', 'uint32')
])
memory_file_handle = os.open('/dev/mem', os.O_RDWR)
axi_mmap = mmap.mmap(fileno=memory_file_handle, length=mmap.PAGESIZE, offset=0x40000000)
axi_numpy_array = np.recarray(1, axi_gpio_regset, buf=axi_mmap)
axi_array_contents = axi_numpy_array[0]
freq = 124998750 #FPGA Clock Frequency Hz
axi_array_contents.gpio1_data = 0x02 #clear timer
axi_array_contents.gpio1_data = 0x01 #start timer
time.sleep(34.2) # Count to the maximim LED (8 MSB value)
axi_array_contents.gpio1_data = 0x00 #stop timer
print("Clock count: ", axi_array_contents.gpio2_data, " calculated time: ", axi_array_contents.gpio2_data/freq, " Seconds")
Thank you Anton your project examples are great.