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:
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:
hw/design_0_wrapper.bit
, the bitstream to program the FPGAsw/fsbl/main.elf
, the build of the First Stage Boot Loadersw/app/main.elf
, the build of the application
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.