Tutorial for executing simulations on PYNQ server

Developers: Shengru Yu, Yusheng Yao, Diederik Markus, Mojtaba Haghi

**It is highly recommended you read and implement the provided tutorial example.

In this tutorial, different steps to conduct a hardware-in-the-loop (HIL) simulation using the PYNQ board is introduced.

This tutorial is specifically developed for the course project of Embedded Control Systems in TU/e.

An example program is provided and explained for the development.

1. Connect to the PYNQ server

In order to connect to the PYNQ server, you need to use a SSH client. We recommend you to use Mobaxterm as your client if your operating system is windows. A small tutorial on how to use Mobaxterm is provided to you in CANVAS. Within a ssh client. Run this command:

ssh student@es-pynq???.ics.ele.tue.nl

In this command write your dedicated PYNQ board number instead of the question mark. Once the terminal asks you for the password, insert the provided password for your project group.

Hint: It is recommended to ssh to the PYNQ board in two separate terminals and use one of them for monitoring the output and one of them to commanding the FGPA part of the board.

A. Access the outputs of the embedded platform

In the main directory of the board, Type sudo ./readout.sh. This will continuously read the output of the platform. Press ‘Ctrl+C’ to halt the reading program. The figure below is what you will see by running the command where the platform is ready to be programmed.

B. Access the files directory and the SDK folder

The set of codes for all the applications to be executed on the platform is integrated into an SDK folder. There are two SDKs provided to you on you server boards. The “Default_project_SDK” is for you to implement your assinment one, and the “Tutorial_SDK” is for implementing the tutorial program. In the SDK folders you can see the composition of the SDK as below:

Folders in the SDK corresponds to the three deployed processor tiles as in its name ‘tile_x’ .In the sdk folder each tile has three application folders and one operating system folder. In this project example, you might use the three applications on the first tile “‘app_tile_0_x’” for your controller applications. The first application on the second tile ‘app_tile_1_1’ executes the DC motor plant code. The first application on the third tile ‘app_tile_2_1’ executes the Dual rotary plant code.

In each application folder, there are two files: ‘main.c’ and ‘Makefile’. In ‘main.c’, you can write down the code for each application.

Note: Please do not delete the contents of the main.c files. Only edit the necessary parts and put your code in the correct place in each of the files. More information is provided in each separate file.

C. Configure the TDM schedule

You can define the scheduling of each processor tile to order the execution of applications on the tile. This can be done through the provided file in the SDK called vep-config.txt. This file is responsible for defining the number of partition slots, size of each partition slots, and the application allocated to each partition slot.

It is advised to read and implement to provided esamples in the tutorial example to completely understand the TDM schedule. Keep in mind that the system clock frequency is 40MHz .

Note 1: The number of partition slots should be a fixed number of 4 partition slots.

Note 2: Any unsed partition slot (not indicated by the user) is assigned to operating system application.

Note 3: There is a context switching CoMik slot with a fixed size of 2000 clock cycles between each two partition slots.

The following figure illustrates the TDM schedule corresponding to the parameters defined in the figure above. In this example the TDM size is 28000 clock cycles:

After the applications code and the TDM configurations are programmed, following steps can be conducted.

HINT: To ensure that control applications run periodically, you can use an assembly command: asm(“wfi”);. This command will halt your program until the end of its partition slot and will start the program once more at the start of the next partition slot dedicated to the application.

D. Compile, run and stop a program

When you want to compile your code, first go to the SDK folder. Then:

  • Run the command make clean. This will erase all the executables from your previous build.

  • Run the command make. This will makes all the executables for your applications.

  • Run the command ./exec.sh a. This will upload all the executables on the platform and executes your program for a seconds on the platforms. for example, ./exec.sh 1 will execute your program on the platform for 1 second.

  • If the compile finishes without any error you will see the system output in the other terminal you opened for the readout.sh command.

You can now save the output of the system for further plotting.

E. Saved the output as txt

The output can be saved by selecting the printed output from the terminal, right click and select copy. Select everything then paste the output in the empty file and save it as .txt.

2. Example program

This part gives an example program for controlling the PATO system.

A. The PATO system

PATO is a dual rotary 4th-order motion system composed of a motor, two masses and a spring. The input to the system is the current which drives the motor. The control objective is position of the first mass (θ1) tracks the reference r.

B. Simulation file of the PATO control system

The figure below depicts the control system where a full-state feedback gain and a constant feedforward gain are involved. Both gains are designed using MATLAB (the simulation file is provide to you in CANVAS). A sensor to actuator delay block is used to mimic the delay incurred by embedded control system.

C. Composition of the program

In the provided SDK for your project, you can construct the control system program for conducting the hardware-in-the-loop simulation. For simulation, we replace the physical plant with a mathematical representation. To implement to control loop we need the following applications.

  1. Plant: the PATO system model, actuated with the computed input and calculates system states. This application is already provided.
  2. Sensing: sense the plant states at the beginning of the sampling period.
  3. Computing: compute the input to the plant according to the sensed states.
  4. Actuating: actuate the plant with the computed input.

These four different parts could be found in the provided sdk in the following applications:

  • Sensing, computation and actuation is expected to be implemented in App_tile_0_1
  • Plant in App_tile_2_1

3. Useful tips to program the platform

A. Xil_printf to print the output

This function has a similar functionality as printf function:

xil_printf("%d\n", state0);  //print the int type variable ‘state0’.

Since xil_printf is a blocking function, it might halt your program for an amount of time. It might cause your program to end its allocated partition slot causing your program to lose periodicity. There are some tricks introduces in the tutorial example to help you use xil_printf in a more secure way.

NOTE: Unlike printf, the xil_printf is not able to print float variables. To print a floating point, cast it as an integer and print it. To preserver the decimal values, multiply your value to a power of 10 equal to the number of decimal values you might to preserve. The code below is an example which you can refer to.

NOTE: You can use the code provided below to print the states of the plant. Please use this part in your controller application. This loop is printing zeros in the initial SDK. Replace the zero with the corresponding variables of the plant to print the state of the plant. Do not change other parts of the print function.

int k = 0;
			float state[N-1][1];
                        for(int i=0; i<N-1; i++)
                        	//USER EDITS BELOW THIS POINT
                        	// edit statement below to print the data of the plant to the console.
                            state[i][0] = 0;
                        //DO NOT EdIT BELOW THIS POINT
                            xil_printf("%d ", (int)(state[i][0]*100000));


Each processor tile has two timers, their address can be found at ‘memmap.h’ in the share folder in the SDK. Taking tile0 as the example:

Timer name Description
TILE0_TIMER_0_S_AXI global timer

To get the current time:

volatile const uint64_t * const timer = (uint64_t*)TILE0_TIMER_0_S_AXI; // declare the timer.
uint64_t t_iter = *timer; //get the current time and assign it as the ‘uint64_t’ variable ‘t_iter’

NOTE:Since timers are shared between applications on a processor, their parameters should be defined as volatile.

C. Compute the execution time of the program

Combined with the xil_printf function, we can simply measure the execution time of each control task/function. Following code gives an example for measuring the execution time.

uint64_t t_start = *timer; //get the begin time
*Function which the execution time is to be measured*
uint64_t t_end = *timer; //get the end time of the function above
xil_printf("execution time: %d\n", (uint32_t) t_end- t_start); //print the time difference.

NOTE:The execution time of the control tasks should be derived as a requirement.

D. Shared memory

The Hardware layer of the CompSOC platform consists of three processor tiles. These tiles can communicate through 1-v-1 shared memories. The following figure demonstrates the hardware architecture:

To be able to use the shared memory you need to find the correct memory addresses to access them. Address of the shared memory between tiles are as follow: (you can find more details by reading the system memory map in the SDK/share folder)

From/To tile0 tile1 tile2
tile0 - (1). tile0_comm1 (2). tile0_comm2
tile1 (3). tile1_comm0 - (4). tile1_comm2
tile2 (5). tile2_comm0 (6). tile2_comm1 -

Example of exchanging data between tile0 and tile2 through shared memory:

On tile 0:

// declare the address of the shared memory between 0 and 2.
    volatile uint32_t *share_to_2 = tile0_comm2; 
// write a value at the address on shared memory.
    *share_to_2 = 0x2;

On tile 1:

// declare the address of the shared memory between 2 and 0.
    volatile uint32_t *share_to_0 = tile2_comm0;
//After tile0 writes, the value written can be directly used by applications run on tile 2 as ‘*share_to_0’.

NOTE:Since the shared mamory is shared between applications on different processor, their parameters should be defined as volatile to stop the compiler to treat them as local variables.