Skip to content

Latest commit

 

History

History
929 lines (681 loc) · 34.6 KB

pynq_microblaze_subsystem.rst

File metadata and controls

929 lines (681 loc) · 34.6 KB

PYNQ MicroBlaze Subsystem

The PYNQ MicroBlaze subsystem gives flexibility to support a wide range of hardware peripherals from Python. The PYNQ MicroBlaze is intended as an offload processor, and can deal with the low level communication protocols and data processing and provides data from a sensor that can be accessed from Python. The subsystem is deterministic, and is suitable for real-time control.

MicroBlaze applications will typically be developed in C or C++, and will run bare-metal.

The following sections show how to develop applications for the MicroBlaze soft processors running inside an overlay.

Memory Architecture

Each PYNQ MicroBlaze has local memory (implemented in Xilinx BRAMs) and a connection to the PS DDR memory.

The PYNQ MicroBlaze instruction and data memory is implemented in a dual port Block RAM, with one port connected to the PYNQ MicroBlaze, and the other to the ARM processor. This allows an executable binary file to be written from the ARM to the PYNQ MicroBlaze instruction memory. The PYNQ MicroBlaze can also be reset by the ARM, allowing the PYNQ MicroBlaze to start executing the new program.

The PYNQ MicroBlaze data memory, either in local memory, or in DDR memory, can be used as a mailbox for communication and data exchanges between the ARM processor and the PYNQ MicroBlaze.

DDR memory is managed by the Linux kernel running on the Cortex-A9s. Therefore, the PYNQ MicroBlaze must first be allocated memory regions to access DRAM. The PYNQ allocate function is used to allocate memory in Linux. It also provides the physical address of the memory. A PYNQ applications can send the physical address to a PYNQ MicroBlaze, and the PYNQ MicroBlaze can then access the buffer.

DDR Memory

The PYNQ MicroBlazes are connected to the DDR memory via the General Purpose AXI slave port. This is a direct connection, so it is only suitable for simple data transfers from the PYNQ MicroBlaze. The MicroBlaze can attempt to read or write the DDR as quickly as possible in a loop, but there is no support for bursts, or streaming data.

PYNQ MicroBlaze Memory Map

The local PYNQ MicroBlaze memory is 64KB of shared data and instruction memory. Instruction memory for the PYNQ MicroBlaze starts at address 0x0.

PYNQ and the application running on the PYNQ MicroBlaze can write to anywhere in the shared memory space. You should be careful not to write to the instruction memory unintentionally as this will corrupt the running application.

When building the MicroBlaze project, the compiler will only ensure that allocated stack and heap of the application fit into the BRAM and DDR if used. For communication between the ARM and the MicroBlaze, a part of the shared memory space must also be reserved within the MicroBlaze address space.

There is no memory management in the PYNQ MicroBlaze. You must ensure the application, including stack and heap, do not overflow into the defined data area. Remember that declaring a stack and heap size only allocates space to the stack and heap. No boundary is created, so if sufficient space was not allocated, the stack and heap may overflow and corrupt your application.

If you need to modify the stack and heap for an application, the linker script can be found in the <project_directory>/src/ directory.

It is recommended to follow the same convention for data communication between the two processors via a MAILBOX.

Instruction and data memory start 0x0
Instruction and data memory size 0xf000
Shared mailbox memory start 0xf000
Shared mailbox memory size 0x1000
Shared mailbox Command Address 0xfffc

These MAILBOX values for a PYNQ MicroBlaze application are defined here:

<PYNQ repository>/boards/sw_repo/pynqmb/src/circular_buffer.h

The corresponding Python constants are defined here:

<PYNQ repository>/pynq/lib/pmod/constants.py
<PYNQ repository>/pynq/lib/arduino/constants.py
<PYNQ repository>/pynq/lib/rpi/constants.py

The following example explains how Python could initiate a read from a peripheral connected to a PYNQ MicroBlaze.

  1. Python writes a read command (e.g. 0x3) to the mailbox command address (0xfffc).
  2. MicroBlaze application checks the command address, and reads and decodes the command.
  3. MicroBlaze performs a read from the peripheral and places the data at the mailbox base address (0xf000).
  4. MicroBlaze writes 0x0 to the mailbox command address (0xfffc) to confirm transaction is complete.
  5. Python checks the command address (0xfffc), and sees that the MicroBlaze has written 0x0, indicating the read is complete, and data is available.
  6. Python reads the data in the mailbox base address (0xf000), completing the read.

Running Code on Different MicroBlazes

The MicroBlaze local BRAM memory is mapped into the MicroBlaze address space, and also to the ARM address space. These address spaces are independent, so the local memory will be located at different addresses in each memory space. Some example mappings are shown below to highlight the address translation between MicroBlaze and ARM's memory spaces.

MicroBlaze Base Address MicroBlaze Address Space ARM Equivalent Address Space
0x4000_0000 0x0000_0000 - 0x0000_ffff 0x4000_0000 - 0x4000_ffff
0x4200_0000 0x0000_0000 - 0x0000_ffff 0x4200_0000 - 0x4200_ffff
0x4400_0000 0x0000_0000 - 0x0000_ffff 0x4400_0000 - 0x4400_ffff

Note that each MicroBlaze has the same range for its address space. However, the location of the address space in the ARM memory map is different for each PYNQ MicroBlaze. As the MicroBlaze address space is the same for each PYNQ MicroBlaze, any binary compiled for one PYNQ MicroBlaze will work on another PYNQ MicroBlaze.

For example, suppose a PYNQ MicroBlaze exists at 0x4000_0000, and a second PYNQ MicroBlaze exists at 0x4200_0000. The same binary can run on the first PYNQ MicroBlaze by writing the binary from python to the address space 0x4000_0000, and on the second PYNQ MicroBlaze by writing to 0x4200_0000.

Building Applications

There are a number of steps required before you can start writing your own software for a PYNQ MicroBlaze. This document will describe the PYNQ MicroBlaze architecture, and how to set up and build the required software projects to allow you to write your own application for the MicroBlaze inside an PYNQ MicroBlaze.

Xilinx® SDK projects can be created manually using the SDK GUI, or software can be built using a Makefile flow. Starting from image v2.1, users can also directly use the Jupyter notebook to program the PYNQ MicroBlaze; more examples can be found in

<PYNQ dashboard>/base/microblaze

MicroBlaze Processors

As described in the previous section, a PYNQ MicroBlaze can be used as a flexible controller for different types of external peripherals. The ARM® Cortex®-A9 is an application processor, which runs PYNQ and Jupyter notebook on a Linux OS. This scenario is not well suited to real-time applications, which is a common requirement for an embedded systems. In the base overlay there are three PYNQ MicroBlazes. As well as acting as a flexible controller, a PYNQ MicroBlaze can be used as dedicated real-time controller.

PYNQ MicroBlazes can also be used standalone to offload some processing from the main processor. However, note that the MicroBlaze processor inside a PYNQ MicroBlaze in the base overlay is running at 100 MHz, compared to the Dual-Core ARM Cortex-A9 running at 650 MHz. The clock speed, and different processor architectures and features should be taken into account when offloading pure application code. e.g. Vector processing on the ARM Cortex-A9 Neon processing unit will be much more efficient than running on the MicroBlaze. The MicroBlaze is most appropriate for low-level, background, or real-time applications.

Software Requirements

Xilinx SDK (Software Development Kit) contains the MicroBlaze cross-compiler which can be used to build software for the MicroBlaze inside a PYNQ MicroBlaze. SDK is available for free as part of the Xilinx Vivado WebPack.

The full source code for all supported PYNQ MicroBlaze peripherals is available from the project GitHub. PYNQ ships with precompiled PYNQ MicroBlaze executables to support various peripherals (see pynq-libraries), so Xilinx software is only needed if you intend to modify existing code, or build your own PYNQ MicroBlaze applications/peripheral drivers.

PYNQ releases are built using:

Release version Vivado and SDK
v1.4

2015.4

v2.0

2016.1

v2.1

2017.4

It is recommended to use the same version to rebuild existing Vivado and SDK projects. If you only intend to build software, you will only need to install SDK. The full Vivado and SDK installation is only required to modify or design new overlays. You can use the Vivado HLx Web Install Client and select SDK and/or Vivado during the installation.

Compiling Projects

Software executables run on the MicroBlaze inside a PYNQ MicroBlaze. Code for the MicroBlaze can be written in C or C++ and compiled using Xilinx SDK .

You can pull or clone the PYNQ repository, and all the driver source and project files can be found in <PYNQ repository>\pynq\lib\<driver_group_name>\<project_directory>.

SDK Application, Board Support Package, Hardware Platform

Each SDK application project requires a BSP project (Board Support Package), and a hardware platform project. The application project will include the user code (C/C++). The Application project is linked to a BSP. The BSP (Board Support Package) contains software libraries and drivers to support the underlying peripherals in the system.

Internally, the BSP is linked to a Hardware Platform. A Hardware Platform defines the peripherals in the PYNQ MicroBlaze subsystem, and the memory map of the system. It is used by the BSP to build software libraries to support the underlying hardware.

All Application projects can be compiled from the command line using makefiles, or imported into the SDK GUI.

You can also use existing projects as a starting point to create your own project.

Board Support Package

A Board Support Package (BSP) includes software libraries for peripherals in the system. For example, the SDK projects for Pmod and Arduino peripherals require the following 2 BSPs:

BSP for the Arduino PYNQ MicroBlaze:

<PYNQ repository>/pynq/lib/arduino/bsp_iop_arduino/

BSP for the Pmod PYNQ MicroBlaze:

<PYNQ repository>/pynq/lib/pmod/bsp_iop_pmod

A BSP is specific to a processor subsystem. There can be many BSPs associated with an overlay, depending on the types of processors available in the system.

An application for the Pmod PYNQ MicroBlaze will be linked to the Pmod PYNQ MicroBlaze BSP. As the two Pmod PYNQ MicroBlazes are identical, an application written for one Pmod PYNQ MicroBlaze can run on the other Pmod PYNQ MicroBlaze.

An Arduino application will be linked to the Arduino PYNQ MicroBlaze BSP.

Building the Projects

To build all the software projects, for example, you can run the corresponding makefile:

<PYNQ repository>/pynq/lib/arduino/makefile

<PYNQ repository>/pynq/lib/pmod/makefile

Application projects for peripherals that ship with PYNQ (e.g. Pmod and Arduino peripherals) can also be found in the same location. Each project is contained in a separate folder.

The makefile compiles the application projects based on the BSP provided in the correct location.

The makefile requires SDK to be installed, and can be run from Windows, or Linux.

To run make from Windows, open SDK, and choose a temporary workspace (make sure this path is external to the downloaded PYNQ repository). From the Xilinx Tools menu, select Launch Shell.

image

In Linux, open a terminal, and source the SDK tools.

From either the Windows Shell, or the Linux terminal, navigate to the sdk folder in your local copy of the PYNQ repository:

The following example shows how to run make in <PYNQ repository>/pynq/lib/pmod/:

image

This will clean all the existing compiled binaries (bin files), and rebuild all the application projects.

image

If you examine the makefile, you can the BIN_PMOD variable at the top of the makefile includes all the bin files required by Pmod peripherals. If you want to add your own custom project to the build process, you need to add the project name to the BIN_PMOD variable, and save the project in the same location as the other application projects.

Similarly, you have to following the same steps to build Arduino application projects.

In addition, individual projects can be built by navigating to the <project_directory>/Debug and running make.

Binary Files

Compiling code produces an executable file (.elf) along with its binary format (.bin) to be downloaded to a PYNQ MicroBlaze.

A .bin file can be generated from a .elf by running the following command from the SDK shell:

mb-objcopy -O binary <input_file>.elf <output_file>.bin

This is done automatically by the makefile for the existing application projects. The makefile will also copy all .bin files into the <PYNQ repository>/pynq/lib/<driver_group_name>/ folder.

Creating Your Own

Using the makefile flow, you can use an existing project as a starting point for your own project.

Copy and rename the project, and modify or replace the .c file in the src/ with your C code. The generated .bin file will have the same base name as your C file.

For example, if your C code is my_peripheral.c, the generated .elf and .bin will be my_peripheral.elf and my_peripheral.bin.

The naming convention recommended for peripheral applications is <pmod|arduino>_<peripheral>.

You will need to update references from the old project name to your new project name in <project_directory>/Debug/makefile and <project_directory>/Debug/src/subdir.mk.

If you want your project to build in the main makefile, you should also append the .bin name of your project to the BIN_PMOD (or BIN_ARDUINO) variable at the top of the makefile.

If you are using the SDK GUI, you can import the Hardware Platform, BSP, and any application projects into your SDK workspace.

image

The SDK GUI can be used to build and debug your code.

Writing Applications

The previous section described the software architecture and the software build process. This section will cover how to write the PYNQ MicroBlaze application and also the corresponding Python interface.

The section assumes that the hardware platform and the BSPs have already been generated as detailed in the previous section.

Header Files and Libraries

A library is provided for the PYNQ MicroBlaze which includes an API for local peripherals (IIC, SPI, Timer, Uart, GPIO), the configurable switch, links to the peripheral addresses, and mappings for the mailbox used in the existing PYNQ MicroBlaze peripheral applications provided with PYNQ. This library can be used to write custom PYNQ MicroBlaze applications.

The PYNQ MicroBlaze can deploy a configurable IO switch. It allows the IO pins to be connected to various types of controllers. The header files associated with the corresponding configurable switch can be found:

<PYNQ repository>/boards/ip/io_switch_1.1/drivers/io_switch_v1_0/src

The PYNQ MicroBlaze has a dedicated library pynqmb. It wraps up low-level functions for ease of use. The header files can be found

<PYNQ repository>/boards/sw_repo/pynqmb/src

To use these files in a PYNQ MicroBlaze application, include these header file(s) in the C program.

For example:

#include "xio_switch.h"
#include "circular_buffer.h"
#include "gpio.h"

Controlling the IO Switch

The IO switch needs to be configured by the PYNQ MicroBlaze application before any peripherals can be used. This can be done statically from within the application, or the application can allow Python to write a switch configuration to shared memory, which can be used to configure the switch.

For Pmod, there are 8 data pins that can be connected to GPIO, SPI, IIC, or Timer. For Arduino, there are 20 shared data pins that can be connected to GPIO, UART, SPI, or Timer. For RPi, there are 28 shared data pins that can be connected to GPIO, UART, SPI, or Timer.

The following function, part of the provided IO switch driver (xio_switch.h), can be used to configure the switch from a PYNQ MicroBlaze application.

void init_io_switch(void);
void set_pin(int pin_number, u8 pin_type);

The function init_io_switch() will just set all the pins to GPIO by default. Then users can call set_pin() to configure each individual pin. The valid values for the parameter pin_type are defined as:

Pin Value

GPIO

0x00

UART0_TX

0x02

UART0_RX

0x03

SPICLK0

0x04

MISO0

0x05

MOSI0

0x06

SS0

0x07

SPICLK1

0x08

MISO1

0x09

MOSI1

0x0A

SS1

0x0B

SDA0

0x0C

SCL0

0x0D

SDA1

0x0E

SCL1

0x0F

PWM0

0x10

PWM1

0x11

PWM2

0x12

PWM3

0x13

PWM4

0x14

PWM5

0x15

TIMER_G0

0x18

TIMER_G1

0x19

TIMER_G2

0x1A

TIMER_G3

0x1B

TIMER_G4

0x1C

TIMER_G5

0x1D

TIMER_G6

0x1E

TIMER_G7

0x1F

UART1_TX

0x22

UART1_RX

0x23

TIMER_IC0

0x38

TIMER_IC1

0x39

TIMER_IC2

0x3A

TIMER_IC3

0x3B

TIMER_IC4

0x3C

TIMER_IC5

0x3D

TIMER_IC6

0x3E

TIMER_IC7

0x3F

For example:

init_io_switch();
set_pin(0, SS0);
set_pin(1, MOSI0);
set_pin(3, SPICLK0);

This would connect a SPI interface:

  • Pin 0: SS0
  • Pin 1: MOSI0
  • Pin 2: GPIO
  • Pin 3: SPICLK0
  • Pin 4: GPIO
  • Pin 5: GPIO
  • Pin 6: GPIO
  • Pin 7: GPIO

IO Switch Modes and Pin Mapping

Note that the IO switch IP is a customizable IP can be configured by users inside a Vivado project (by double clicking the IP icon of the IO switch). There are 4 pre-defined modes (pmod, dual pmod, arduino, raspberrypi) and 1 fully-customizable mode (custom) for users to choose. In the base overlay, we have only used pmod and arduino as the IO switch modes.

Switch mappings used for Pmod:

Pin

GPIO

UART

PWM

Timer

SPI

IIC

Input-Capture

======= D0 D1

====== GPIO GPIO

============ UART0_RX/TX UART0_RX/TX

====== PWM0 PWM0

============

TIMER_G0 TIMER_G0

========

SS0 MOSI0


=============

TIMER_IC0 TIMER_IC0

D2 GPIO UART0_RX/TX PWM0

TIMER_G0

MISO0

SCL0

TIMER_IC0

D3 D4 D5

GPIO GPIO GPIO

UART0_RX/TX UART0_RX/TX UART0_RX/TX

PWM0 PWM0 PWM0

TIMER_G0 TIMER_G0 TIMER_G0

SPICLK0 SS0 MOSI0

SDA0

TIMER_IC0 TIMER_IC0 TIMER_IC0

D6 GPIO UART0_RX/TX PWM0

TIMER_G0

MISO0

SCL0

TIMER_IC0

D7 GPIO UART0_RX/TX PWM0

TIMER_G0

SPICLK0

SDA0

TIMER_IC0

Note:

  • PWM0, TIMER_G0, TIMER_IC0 can only be used once on any pin.
  • UART0_TX/RX is supported by Pmod, but not implemented in the base overlay.
  • SS0, MOSI0, MISO0, SPICLK0 can either be used on top-row (pins D0 - D3) or bottom-row (D4 - D7) but not both.
  • SCL0, SDA0 can either be used on to-row (pins D2 - D3) or bottom-row (D6 - D7) but not both.

Switch mappings used for Arduino:

Pin

GPIO

UART

PWM

Timer

SPI

IIC

Input-Capture

======= D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 D10 D11 D12 D13 D14/A0 D15/A1 D16/A2 D17/A3 D18/A4 D19/A5

====== GPIO GPIO GPIO GPIO GPIO GPIO GPIO GPIO GPIO GPIO GPIO GPIO GPIO GPIO GPIO GPIO GPIO GPIO GPIO GPIO

============ UART0_RX UART0_TX


PWM0

PWM1 PWM2

PWM3 PWM4 PWM5


TIMER_G0 TIMER_G6 TIMER_G1 TIMER_G2

TIMER_G7 TIMER_G3 TIMER_G4 TIMER_G5


SS0 MOSI0 MISO0 SPICLK0



TIMER_IC0 TIMER_IC6 TIMER_IC1 TIMER_IC2

TIMER_IC7 TIMER_IC3 TIMER_IC4 TIMER_IC5

Note:

  • On Arduino, a dedicated pair of pins are connected to IIC (not going through the IO switch).

Switch mappings used for dual Pmod:

Pin

GPIO

UART

PWM

Timer

SPI

IIC

Input-Capture

======= D0 D1

====== GPIO GPIO

============ UART0_RX/TX UART0_RX/TX

====== PWM0 PWM0

============ TIMER_G0 TIMER_G0

======== SS0 MOSI0


=============

TIMER_IC0 TIMER_IC0

D2 GPIO UART0_RX/TX PWM0 TIMER_G0 MISO0 SCL0

TIMER_IC0

D3 D4 D5

GPIO GPIO GPIO

UART0_RX/TX UART0_RX/TX UART0_RX/TX

PWM0 PWM0 PWM0

TIMER_G0 TIMER_G0 TIMER_G0

SPLCLK0 SS0 MOSI0

SDA0

TIMER_IC0 TIMER_IC0 TIMER_IC0

D6 GPIO UART0_RX/TX PWM0 TIMER_G0 MISO0 SCL0

TIMER_IC0

D7 GPIO UART0_RX/TX PWM0 TIMER_G0 SPICLK0 SDA0

TIMER_IC0

Pin

GPIO

UART

PWM

Timer

SPI

IIC

Input-Capture

======= D0 D1

====== GPIO GPIO

============ UART0_RX/TX UART0_RX/TX

====== PWM0 PWM0

============ TIMER_G1 TIMER_G1

======== SS1 MOSI1


=============

TIMER_IC1 TIMER_IC1

D2 GPIO UART0_RX/TX PWM0 TIMER_G1 MISO1 SCL1

TIMER_IC1

D3 D4 D5

GPIO GPIO GPIO

UART0_RX/TX UART0_RX/TX UART0_RX/TX

PWM0 PWM0 PWM0

TIMER_G1 TIMER_G1 TIMER_G1

SPICLK1 SS1 MOSI1

SDA1

TIMER_IC1 TIMER_IC1 TIMER_IC1

D6 GPIO UART0_RX/TX PWM0 TIMER_G1 MISO1 SCL1

TIMER_IC1

D7 GPIO UART0_RX/TX PWM0 TIMER_G1 SPICLK1 SDA1

TIMER_IC1

Note:

  • PWM0, TIMER_G0, TIMER_IC0 can only be used once on any pin of D0 - D7.
  • PWM0, TIMER_G1, TIMER_IC1 can only be used once on any pin of D8 - D15.
  • SS0, MOSI0, MISO0, SPICLK0 can either be used on top-row (pins D0 - D3) or bottom-row (D4 - D7) but not both.
  • SS1, MOSI1, MISO1, SPICLK1 can either be used on top-row (pins D8 - D11) or bottom-row (D12 - D15) but not both.
  • SCL0, SDA0 can either be used on to-row (pins D2 - D3) or bottom-row (D6 - D7) but not both.
  • SCL1, SDA1 can either be used on to-row (pins D10 - D11) or bottom-row (D14-D15) but not both.

Switch mappings used for Raspberry Pi:

Pin

GPIO

UART

PWM

Timer

SPI

IIC

Input-Capture

======= GPIO0 GPIO1 GPIO2 GPIO3 GPIO4 GPIO5 GPIO6 GPIO7 GPIO8 GPIO9 GPIO10 GPIO11 GPIO12 GPIO13 GPIO14 GPIO15 GPIO16 GPIO17 GPIO18 GPIO19 GPIO20 GPIO21 GPIO22 GPIO23 GPIO24 GPIO25

======

GPIO GPIO GPIO GPIO GPIO GPIO GPIO GPIO GPIO GPIO GPIO GPIO GPIO GPIO GPIO GPIO GPIO GPIO GPIO GPIO GPIO GPIO GPIO GPIO GPIO GPIO


UART0_TX UART0_RX


PWM0 PWM1



SS0 SS0 MISO0 MOSI0 SPICLK0

SS1

MISO1 MOSI1 SPICLK1

==== SDA0 SCL0 SDA1 SCL1


Note:

  • SPI0 can have up to two Slave Selects (SS's). SS0 can be used to program the functionality for the IO switch.

PYNQ MicroBlaze Example

MicroBlaze C Code

Taking Pmod ALS as an example PYNQ MicroBlaze driver (used to control the Pmod light sensor):

<PYNQ repository>/pynq/lib/pmod/pmod_als/src/pmod_als.c

First note that the pynqmb header files are included.

#include "spi.h"
#include "timer.h"
#include "circular_buffer.h"

Next, some constants for commands are defined. These values can be chosen properly. The corresponding Python code will send the appropriate command values to control the PYNQ MicroBlaze application.

By convention, 0x0 is reserved for no command (idle, or acknowledged); then PYNQ MicroBlaze commands can be any non-zero value.

// MAILBOX_WRITE_CMD
#define READ_SINGLE_VALUE 0x3
#define READ_AND_LOG      0x7
// Log constants
#define LOG_BASE_ADDRESS (MAILBOX_DATA_PTR(4))
#define LOG_ITEM_SIZE sizeof(u32)
#define LOG_CAPACITY  (4000/LOG_ITEM_SIZE)

The ALS peripheral has as SPI interface. An SPI variable is defined and accessible to the remaining part of the program.

spi device;

The user defined function get_sample() calls spi_transfer() to read data from the device.

u32 get_sample(){
   /* 
   ALS data is 8-bit in the middle of 16-bit stream. 
   Two bytes need to be read, and data extracted.
   */
   u8 raw_data[2];
   spi_transfer(device, NULL, (char*)raw_data, 2);
   return ( ((raw_data[1] & 0xf0) >> 4) + ((raw_data[0] & 0x0f) << 4) );
}

In main() notice that no IO switch related functions are called; this is because those functions are performed under the hood automatically by spi_open(). Also notice this application does not allow the switch configuration to be modified from Python. This means that if you want to use this code with a different pin configuration, the C code must be modified and recompiled.

int main(void)
{
   int cmd;
   u16 als_data;
   u32 delay;

   device = spi_open(3, 2, 1, 0);

   // to initialize the device
   get_sample();

Next, the while(1) loop continually checks the MAILBOX_CMD_ADDR for a non-zero command. Once a command is received from Python, the command is decoded, and executed.

// Run application
while(1){

   // wait and store valid command
   while((MAILBOX_CMD_ADDR & 0x01)==0);
   cmd = MAILBOX_CMD_ADDR;

Taking the first case, reading a single value; get_sample() is called and a value returned to the first position (0) of the MAILBOX_DATA.

MAILBOX_CMD_ADDR is reset to zero to acknowledge to the ARM processor that the operation is complete and data is available in the mailbox.

Remaining code:

switch(cmd){

  case READ_SINGLE_VALUE:
// write out reading, reset mailbox
MAILBOX_DATA(0) = get_sample();
MAILBOX_CMD_ADDR = 0x0;

break;

   case READ_AND_LOG:
 // initialize logging variables, reset cmd
 cb_init(&circular_log, LOG_BASE_ADDRESS, LOG_CAPACITY, LOG_ITEM_SIZE);
 delay = MAILBOX_DATA(1);
 MAILBOX_CMD_ADDR = 0x0; 

      do{
         als_data = get_sample();
     cb_push_back(&circular_log, &als_data);
     delay_ms(delay);

      } while((MAILBOX_CMD_ADDR & 0x1)== 0);

      break;

   default:
      // reset command
      MAILBOX_CMD_ADDR = 0x0;
      break;
}

} return(0);

}

Python Code

With the PYNQ MicroBlaze Driver written, the Python class can be built to communicate with that PYNQ MicroBlaze.

<PYNQ repository>/pynq/lib/pmod/pmod_als.py

First the Pmod package is imported:

from . import Pmod

Then some other constants are defined:

PMOD_ALS_PROGRAM = "pmod_als.bin"
PMOD_ALS_LOG_START = MAILBOX_OFFSET+16
PMOD_ALS_LOG_END = PMOD_ALS_LOG_START+(1000*4)
RESET = 0x1
READ_SINGLE_VALUE = 0x3
READ_AND_LOG = 0x7

The MicroBlaze binary file for the PYNQ MicroBlaze is defined. This is the application executable, and will be loaded into the PYNQ MicroBlaze instruction memory.

The ALS class and an initialization method are defined:

class Pmod_ALS(object):

   def __init__(self, mb_info):

The initialization function for the module requires the MicroBlaze information. The __init__ is called when a module is initialized. For example, from Python:

from pynq.lib.pmod import Pmod_ALS
from pynq.lib.pmod import PMODA
als = Pmod_ALS(PMODA)

This will create a Pmod_ALS instance, and load the MicroBlaze executable (PMOD_ALS_PROGRAM) into the instruction memory of the specified PYNQ MicroBlaze.

Since the MicroBlaze information, imported as Pmod constants, can also be extracted as an attribute of the overlay, the following code also works:

from pynq.overlays.base import BaseOverlay
base = BaseOverlay("base.bit")
als = Pmod_ALS(base.PMODA)

In the initialization method, an instance of the Pmod class is created. This Pmod class controls the basic functionalities of the MicroBlaze processor, including reading commands/data, and writing commands/data.

Internally, when the Pmod class is initialized, the run() call pulls the PYNQ MicroBlaze out of reset. After this, the PYNQ MicroBlaze will be running the pmod_als.bin executable.

The read() method in the Pmod_ALS class will read an ALS sample and return that value to the caller. The following steps demonstrate a Python to MicroBlaze read transaction specific to the Pmod_ALS class.

def read(self):

First, the command is written to the MicroBlaze shared memory. In this case the value READ_SINGLE_VALUE represents a command value. This value is user defined in the Python code, and must match the value the C program expects for the same function.

self.microblaze.write_blocking_command(READ_SINGLE_VALUE)

The command is blocking so that Python code will not proceed unless an acknowledgement has been received from the MicroBlaze. Internally, after the PYNQ MicroBlaze has finished its task, it will write 0x0 to clear the command area. The Python code checks this command area (in this case, the Python code constantly checks whether the 0x3 value is still present at the CMD_OFFSET).

Once the command is no longer 0x3 (the acknowledge has been received), the result is read from the data area of the shared memory MAILBOX_OFFSET.

data = self.microblaze.read_mailbox(0)
return data