Software API
Measuring Time
The ARM core contains a monotonically increasing counter, which can be used to measure time in the system without controlling a full countdown timer manually (detailed below). The timer increases at half the ARM clock frequency (i.e. every two clock cycles).
Time can be accessed using the XTime
functions, as follows:
Countdown Timer
The ARM system has an internal timer which can be used to measure execution times. An example of doing this is shown below:
Note that the timer is a countdown timer. xparameters.h
includes a #define
called XPAR_CPU_CORTEXA9_0_CPU_CLK_FREQ_HZ
which is the current clock rate. This can be used to convert clock cycles to time.
A common use of the timer is to trigger a periodic interrupt. The code sample below shows how to set this up.
Important
The ethernet framework below also makes use of this timer!
Ethernet
The ARM cores can use the Zybo's Ethernet connection to send and receive messages over the network. To use the Ethernet, do the following:
In Vitis, double click your project's
.prj
file and select Navigate to BSP Settings, then Modify BSP Settings.- Tick the
lwip211
(Lightweight IP) library. (Note: this may be a higher number if a more recent version has been released.) In the list on the left, under standalone, click
lwip211
. This shows the settings for the library.- Expand
dhcp_options
and setlwip_dhcp
totrue
.
This will bring in the Lightweight IP library, and set it to obtain an IP address by DHCP when your system boots.
Add the following two platform files to your project (or replace them if they already exist). They set up various parts of the system and initialise the hardware.
Create a
main.c
and follow the code structure as in the examples below.
If you are working in C++ then rename platform.c
to platform.cpp
and the tools should automatically use the correct compilation. You will need to wrap the LWIP includes at the top of platform.cpp in an extern C declaration to tell the C++ compiler to expect a C library. Like this:
Using the Ethernet
The following code structure shows examples of how to use the ethernet:
Important things to note:
- The above code is just to show sample usage, and will not compile as it is.
- You must use a unique MAC address. In EMBS these are listed on the EMBS Student Network page.
- Sending and receiving requires a packet buffer (
pbuf
). You must remember to free these after using them. - Sending and receiving also requires Protocol Control Blocks (PCBs). While you can remove these when you've finished using them, we recommend re-using them if you're going to send or receive more than once.
After setting up any handlers you must call
handle_ethernet()
.
If you don't have DHCP
The default ethernet code uses DHCP to automatically obtain an IP address from the network, based on your MAC address. If DHCP requests aren't working, it often means you're not connected to the network correctly, or you have a problem with your code. There could also be network issues, so ask a demonstrator if unsure.
If you're sure that you shouldn't be using DHCP (e.g. if you're not using the EMBS network), you can use a manual IP address as follows:
- Set up the application and BSP as above.
- Right click your BSP and click Board Support Package Settings. In the left-hand column, under
standalone
, clicklwip202
. - Expand
dhcp_options
and setdhcp_does_arp_check
andlwip_dhcp
both to false.
Now you must provide an IP address and subnet mask manually, as below:
Sharing Memory Between HLS and the ARM
To share a large amount of data between the ARM cores and an HLS component you will use main system memory. The Zybo Z7 has 1GB of main DDR memory which can be accessed from an HLS component by using an AXI Master interface on the HLS core.
Look at this diagram. It helps to understand how the system is laid out.
The ARM cores read and write data from main memory. Your HLS core is controlled by the ARM over its slave interface, but it can also access main memory via its master interface. For this reason, you should see why it doesn't make sense to ask "how do I pass data from the ARM core to HLS?". The data is always in memory, instead the ARM core simply needs to tell the FPGA where to look for it.
We can see therefore that the HLS core and the ARM cores are reading and writing from the same memory. Therefore we will declare a segment of that memory that we can use for sharing. The easiest way to do this is to declare a global array, then pass the address of the shared memory into the HLS component using XToplevel_Set_ram
:
In HLS we can read and write from RAM address 0 and it will be offset by the value we passed in with XToplevel_Set_ram
to access the shared memory:
In the example above we declared 4000 bytes to use as shared memory between HLS and the ARM cores. This is not only "input" data, it is shared data. If your algorithm needs to read in some input data and produces a chunk of output data, you can arrange it all in the array accordingly. For example, imagine a problem which takes in 400 bytes and produces 400 bytes:
Bulk reads and writes with memcpy
(include string.h
) are faster than reading individual words. For example:
Both the loop and the call to memcpy
do the same thing, but memcpy
is much faster because HLS will use what is called a burst transfer to copy in data at a faster rate. You can also memcpy
data out to RAM.
Remember that the system contains caches! If you simply write data and do nothing else the ARM will write and read from its caches, which are not visible to the HLS component. Also any memory changed by HLS will not invalidate the ARM's cache lines so you may not see the updates. You must flush the caches when you want to force the ARM to write to or read from system memory. For example:
This code uses Xil_DCacheFlush()
and Xil_DCacheInvalidate()
to flush changes from the cache to main memory and re-read from main memory into cache. Xil_DCacheFlushRange()
and Xil_DCacheInvalidateRange()
can also be used to specify regions of memory that have changed.
If you are having issues which you suspect are cache-related you can completely disable caches by calling Xil_DCacheDisable()
, but this will make your code a lot slower.
Using C Maths Functions
Functions such as sin
and floor
are defined in the standard C header math.h
. If you use this you may find that the compiler does not include the maths library by default, resulting in errors like:
undefined reference to `sin'
To fix this:
- In SDK, right click your application project and select
Properties
- Go to
C/C++ Build | Settings
- In the
Tool Settings
tab, underARM v7 gcc linker
clickLibraries
- Click the Add button and enter
m