Home / Resources / Interfacing software running on an ARM Cortex-A9 with FPGA Artix-7 using GPIO

Introduction

The Zynq-7000 family is based on the Xilinx All Programmable SoC architecture. These products integrate a feature-rich dual or single-core ARM Cortex-A9 MPCore based processing system (PS) and Xilinx programmable logic (PL) in a single device.

The Cortex-A9 processor implements the ARM v7-A architecture with full virtual memory support and can execute 32-bit ARM instructions, 16-bit and 32-bit Thumb instructions, and 8-bit Java byte codes in the Jazelle state.

The PL is nearly identical to a Xilinx 7-series Artix FPGA, except that it contains several dedicated ports and buses that tightly couple it to the PS. The PL must be configured either directly by the processor or via the JTAG port.

OpenOCD is an on-chip debugging in-system programming and boundary-scan testing tool for various ARM and MIPS systems. The debugger uses an IEEE 1149-1 compliant JTAG TAP bus master to access on-chip debug functionality available on ARM based microcontrollers or system-on-chip solutions.

Here is shown how to interface software running on an ARM Cortex-A9 with hardware implemented in a FPGA Artix-7.

Implementation has been conducted on a Digilent Cora Z7 development board. The Digilent Cora Z7 is a ready-to-use, low-cost, and easily embeddable development platform designed around the Zynq-7000 All-Programmable System-on-Chip from Xilinx.

Hardware

We connect the EMIO ports from the Zynq-7000 to a button and a LED on the board. Each GPIO has a 2-bits vector port and we need to select the appropriate input/output.

module connexions(
  input  [1:0] gpio_o,  output led_0,
  output [1:0] gpio_i,  input  btn_0
);
  assign led_0     = gpio_o[0];
  assign gpio_i[1] = btn_0;
endmodule

In parallel, we connect a second button to a second LED via a flip-flop. This works independently from the Zynq-7000. The only restriction is to have an active clock.

module flipflop(
  input  clk,
  input  btn,
  output led
);
  reg r_led;
  always @( posedge(clk) ) begin
    r_led = btn;
  end
  assign led = r_led;
endmodule

These modules and the Zynq-7000 are connected together as described by the following schematic:

Hardware schematic

On the Cora-Z7, green LEDs are connected to package pins L14 and G17, buttons are connected to package pins D19 and D20.

set_property -dict { PACKAGE_PIN G17   IOSTANDARD LVCMOS33 } [get_ports { led_0 }];
set_property -dict { PACKAGE_PIN L14   IOSTANDARD LVCMOS33 } [get_ports { led_1 }];
set_property -dict { PACKAGE_PIN D20   IOSTANDARD LVCMOS33 } [get_ports { btn_0 }];
set_property -dict { PACKAGE_PIN D19   IOSTANDARD LVCMOS33 } [get_ports { btn_1 }];

Software

We can run some software on the CPU to switch the registers of the GPIO. Assuming that we have set their direction, enabled the output and that function gpio_getBankAndBitIndex() updates two variables referring to the Bank and Bit indexes, here is a function to read from an input:

uint32_t gpio_read(
  uint8_t theGpioIndex  //!< integer that refers to the GPIO index
) {
  uint8_t theBank;        // bank index for the GPIO
  uint8_t theBitIndex;    // bit index in the bank for the GPIO
  uint32_t * theAddress;  // register address to read the GPIO value from

  gpio_getBankAndBitIndex(theGpioIndex, &theBank, &theBitIndex);
  theAddress = (uint32_t *)(XGPIOPS_BASE_ADDR + XGPIOPS_DATA_OFFSET) + theBank;
  return ((*theAddress & (1 << theBitIndex)) >> theBitIndex);
}

And here is a function to write an output:

void gpio_write(
  uint8_t theGpioIndex, //!< integer that refers to the GPIO index
  uint8_t theValue      //!< sets the GPIO if non-zero
) {
  uint8_t theBank;        // bank index for the GPIO
  uint8_t theBitIndex;    // bit index in the bank for the GPIO
  uint32_t * theAddress;  // register address to read the GPIO value from

  gpio_getBankAndBitIndex(theGpioIndex, &theBank, &theBitIndex);
  theAddress = (uint32_t *)(XGPIOPS_BASE_ADDR + XGPIOPS_DATA_OFFSET) + theBank;
  if ( theValue != 0 ) {
    *theAddress = (*theAddress & ~(1 << theBitIndex)) | (1 << theBitIndex);
  } else {
    *theAddress = (*theAddress & ~(1 << theBitIndex)) | (0 << theBitIndex);
  }
  return;
}

Now, the main program runs an infinite loop where it reads from one input and writes the opposite value to an output.

int main (
  void
) {
  uint32_t theInputValue;   // what is read on GPIO input

  gpio_setDirection(  GPIO_OUTPUT_INDEX, 1);
  gpio_setOuputEnable(GPIO_OUTPUT_INDEX, 1);
  //
  gpio_setDirection(GPIO_INPUT_INDEX, 0);

  while ( 1 ) {
    theInputValue = gpio_read(GPIO_INPUT_INDEX);
    gpio_write(GPIO_OUTPUT_INDEX, !theInputValue);  // opposite value
  }

  return 0;
}

Run

There are several possibilities to run this co-design on the board. Here is described how to program the board through JTAG with openOCD and gdb.

The following files must be available:

The following gdb script monitors openOCD and programs the board:

set architecture armv7

monitor reset halt
monitor pld load 0 ./hw/design_0_wrapper.bit
monitor gdb_sync

file  sw/fsbl/main.elf
load  sw/fsbl/main.elf
break _boot
jump  _boot

break Loop
break FsblHookFallback
continue
continue

# load the application:
file sw/app/main.elf
load sw/app/main.elf
break _start
jump  _start

break main
continue
continue

Makefile

Building the hardware requires a synthesis using Xilinx Vivado. A script is provided in the archive from the associated resources.

Building the software requires a compilation. Here we use gcc and ld. A script is provided in the archive from the associated resources.

Running requires to open two terminals: the first one to run openOCD and to communicate with the hardware ; the second one to run gdb and monitor openOCD.

Here we use a custom openOCD configuration file for zynq_7000: there is only one CPU core in case of cora Z7 and we must not create 2 targets.

Here is a Makefile that provides the recipe (make sure to indent with tabulations):

OCD = ../openocd_zynq.tcl

all: openocd

openocd: ${OCD} hw/design_0_wrapper.bit gdbinit.gdb
    make -C sw
    # TO RUN IN AN OTHER TERMINAL:
    # gdb-multiarch -ex "set architecture armv7" -ex "target extended-remote localhost:3333" --command="gdbinit.gdb"
    #
    /usr/bin/openocd -f $<

hw/design_0_wrapper.bit:
    make -C hw

Associated resources

Here is an archive which contains the discussed example and its Makefiles.



no cookie, no javascript, no external resource, KISS!