wiki:Debugging/OpenOCD/Xilinx_Zynq

Version 5 (modified by Chris Johns, on Jan 14, 2016 at 9:24:44 PM) (diff)

Add L2 cache control and masking the ISR set up.

OpenOCD Support for XIlinx Zynq

OpenOCD supports the Xilinx Zynq-7000 parts. The support is not current in the OpenOCD source but you can create a suitable environment to the configurations here and access the part.

Xilinx define the JTAG access to the Zynq part with a 14-pin header while suitable adaptors such as the Flyswatter2 have the standard ARM 20pin header. You can buy from Avnet a ZedBoard Processor Debug Adapter.

JTAG access to the Zynq only operates if the board is in a suitable JTAG mode and it is not in a secure boot mode. Please refer to the Xilinx Zynq-7000 Technical Reference Manual and any user manual for your hardware for details on how to set the board's mode into JTAG.

Xilinx SDK PS7 Initialisation

The Xilinx design tools and SDK produce initialisation code. The System design tool lets you specific the type of memory, clocks and bus structures and the tools generate C code for use in the First Stage Boot Loader (FSBL) and TCL code for use with the Xilinx XDM JTAG tool. Save the following code as xilinx-tcl.cfg:

#
# TCL to allow the Xilinx PS7 Init TCL code to run in OpenOCD.
#

proc mrd { args } {
    if {[llength $args] == 0} {
        echo "mrd address \[count \[w|h|b\]\]"
        echo "    Read <count> memory locations starting at <address>.  Defaults to one word."
        return
    }
    set addr [lindex $args 0]
    set count  1
    set bits  32
    if {[llength $args] > 1} {
        set count [lindex $args 1]
        if {[llength $args] > 2} {
            switch [lindex $args 2] {
                w       { set bits 32 }
                h       { set bits 16 }
                b       { set bits  8 }
                default { set bits 32 }
            }
        }
    }
    mem2array x $bits $addr $count
    set nibbles [expr {$bits / 4}]
    set bytes   [expr {$bits / 8}]
    set result  {}
    foreach {idx elmt} $x {
        append result [format "%08x:   %0*x\n" [expr {$addr + $idx * $bytes}] $nibbles $elmt]
    }
    return $result
}

proc mask_write { addr mask value } {
    set curval "0x[string range [mrd $addr] end-8 end]"
    set maskedval [expr {$curval & ~$mask}]
    #echo "curval = [format 0x%08x $curval] maskedval = [format 0x%08x $maskedval]"
    set writeval(0) [expr {$maskedval | $value}]
    #echo " $addr <= [format 0x%08x $writeval(0)] ([format 0x%08x $curval]: [format 0x%08x $mask]/[format 0x%08x $value])"
    array2mem writeval 32 $addr 1
}

proc xilinx_ps7_init { } {
   poll off
   reset init
   reset halt
   targets zc706.cpu.0
   sleep 100
   halt
   ps7_debug
   ps7_init
   ps7_post_config
   poll on
}

This file will allow you to run the TCL script from the Xilinx SDK using OpenOCD. There are some instances around some parts of the initialisation that may not work. Uncomment the echo lines, locate the writes that fail and comment those out from your PS7 init TCL script.

The main purpose of this initialisation process is to get working clocks and DDR memory so you can load your code and run it.

First Stage Boot Loader Initialisation

A second approach to initialisation is to use the FSBL. The FSBL contains the C version of the PS7 initialisation produced by the SDK. The FSBL should detect the mode is JTAG and place the ARM code in a state where JTAG access is enabled. To do this you create an OpenOCD TCL script that loads the FSBL as an ELF file into the OCM and runs it, pauses for a small amount of time to let it complete and then halts the ARM code. At this point in time the Zynq will be initialised and you can download your application into DDR RAM.

Xilinx Zynq-7000 Configuration File

The following is a target configuration for the Xilinx Zynq-7000. Copy this to a file called zynq-7000.cfg:

#
# Xilinx Zynq 7000 SoC
#
#  Chris Johns <chrisj@rtems.org>
#
# Setup
# -----
#
# Create a user configuration following the "Configuration Basics" in the user
# documentation. In the file have:
#
#    source [find interface/ftdi/flyswatter2.cfg]
#    source [find board/zynq-zc706-eval.cfg]
#    adapter_khz 2000
#    init
#

if { [info exists CHIPNAME] } {
    global _CHIPNAME
    set _CHIPNAME $CHIPNAME
} else {
    global _CHIPNAME
    set _CHIPNAME zynq
}

if { [info exists ENDIAN] } {
    set _ENDIAN $ENDIAN
} else {
    # this defaults to a bigendian
    set _ENDIAN little
}

if { [info exists SMP] } {
    global _SMP
    set _SMP 1
} else {
    global _SMP
    set _SMP 0
}

#
# PL Tap.
#
# See ug585 ZYNQ-7000 TRM PSS_IDCODE for how this number is constructed.
#   0x03731093 - ZC706 Eval board 1.1
#   0x23731093 - ??
#   0x23727093 - Zedboard Rev. C and D
#
# Set in your configuration file or board specific file.
#
if { [info exists PL_TAPID] } {
    set _PL_TAPID $PL_TAPID
} else {
    set _PL_TAPID 0x03731093
}

jtag newtap $_CHIPNAME tap -irlen 6 -ircapture 0x001 -irmask 0x003 \
    -expected-id $_PL_TAPID

#
# CoreSight Debug Access Port
#
if { [info exists DAP_TAPID] } {
    set _DAP_TAPID $DAP_TAPID
} else {
    set _DAP_TAPID 0x4ba00477
}

jtag newtap $_CHIPNAME dap -irlen 4 -ircapture 0x01 -irmask 0x03 \
    -expected-id $_DAP_TAPID

#
# GDB target: Cortex-A9, using DAP, configuring only one core
# Base addresses of cores:
# core 0  -  0xF8890000
# core 1  -  0xF8892000
#
# Read from the ROM table with the patch to read the nested table.
#

set _TARGETNAME_0 $_CHIPNAME.cpu.0
set _TARGETNAME_1 $_CHIPNAME.cpu.1

target create $_TARGETNAME_0 cortex_a -coreid 0 \
    -endian $_ENDIAN \
    -chain-position $_CHIPNAME.dap \
    -dbgbase 0x80090000
if { $_SMP } {
    echo "Zynq CPU1."
    target create $_TARGETNAME_1 cortex_a -coreid 1 \
        -endian $_ENDIAN \
        -chain-position $_CHIPNAME.dap \
        -dbgbase 0x80092000
    target smp $_TARGETNAME_0 $_TARGETNAME_1
}

#
# Hack to get the registers into a stable state when first booting a zynq in
# JTAG mode. If r11 is pointing to an invalid address and you use gdb to set a
# register the write will fail because gdb attempts to scan or unwind the
# current frame and the bad address seems to lock the bus up. This code puts
# the registers into the OCM and hopefull safe.
#
proc zynq_clear_registers { target } {
    echo "Zynq-7000 Series setup: $target"
    set _OCM_END 0x0003FFF0
    mww phys 0xF8007000 0x4E00E07F
    reg r0 0
    reg r1 0
    reg r2 0
    reg r3 0
    reg r4 0
    reg r5 0
    reg r6 0
    reg r7 0
    reg r8 0
    reg r9 0
    reg r10 0
    reg r11 $_OCM_END
    reg sp_svc $_OCM_END
    reg lr_svc $_OCM_END
    reg sp_abt $_OCM_END
    reg lr_abt $_OCM_END
    reg sp_und $_OCM_END
    reg lr_und $_OCM_END
}

proc zynq_disable_mmu_and_caches { target } {
    # arm mcr pX op1 CRn CRm op2 value
    echo "Disable MMU and caches"
    # Invalidate caches
    catch {
        $target arm mcr 15 0 7 5 0 0
        $target arm mcr 15 0 7 7 0 0
        # Invalidate all TLBs
        $target arm mcr 15 0 8 5 0 0
        $target arm mcr 15 0 8 6 0 0
        $target arm mcr 15 0 8 7 0 0
        $target arm mcr 15 4 8 3 0 0
        $target arm mcr 15 4 8 7 0 0
        set cp [$target arm mrc 15 0 1 0 0]
        echo "SCTRL => [format 0x%x $cp]"
        set mask [expr 1 << 29 | 1 << 12 | 1 << 11 | 1 << 2 | 1 << 1 | 1 << 0]
        set cp [expr ($cp & ~$mask)]
        $target arm mcr 15 0 1 0 0 $cp
        echo "SCTRL <= [format 0x%x $cp]"
    }
}

proc zynq_boot_ocm_setup { } {
    #
    # Enable the OCM
    #
    echo "Zynq Boot OCM setup"
    catch {
      mww phys 0xF8000008 0xDF0D
      mww phys 0xF8000238 0
      mww phys 0xF8000910 0xC
    }
}

proc zynq_rtems_setup { } {
    cache_config l2x 0xF8F02000 8
    cortex_a maskisr on
}

proc zynq_restart { wait } {
    global _SMP
    global _TARGETNAME_0
    global _TARGETNAME_1
    set target0 $_TARGETNAME_0
    set target1 $_TARGETNAME_1
    echo "Zynq reset, resetting the board ... "
    poll off
    #
    # Issue the reset via the SLCR
    #
    catch {
        mww phys 0xF8000008 0xDF0D
        mww phys 0xF8000200 1
    }
    echo "Zynq reset waiting for $wait msecs ... "
    sleep $wait
    #
    # Reconnect the DAP etc due to the reset.
    #
    $target0 cortex_a dbginit
    $target0 arm core_state arm
    if { $_SMP } {
        $target1 arm core_state arm
        $target1 cortex_a dbginit
        cortex_a smp_off
    }
    poll on
    #
    # We can now halt the core.
    #
    if { $_SMP } {
        targets $target1
        halt
    }
    targets $target0
    halt
    zynq_rtems_setup
}

proc zynq_gdb_attach { target } {
    catch {
      halt
    }
}

Custom Board Configuration

Create a directory somewhere on your host and create a target directory and place the zynq-7000.cfg file in it. Create a board configuration file. In this example we will create zynq-zc706-eval.cfg with the contents of:

#
# Xilinx Zynq ZC706 Evaluation Board
#
#  Chris Johns <chrisj@rtems.org>
#

set PL_TAPID 0x23731093

source [find target/zynq-7000.cfg]
source [find xilinx-tcl.cfg]

#
# Configure the reset.
#
reset_config srst_only
adapter_nsrst_assert_width 250
adapter_nsrst_delay 400

Finally I have more than one Flyswatter2 connected so I create a configuration file for each pod. The zynq-zc706-1.cfg is:

source [find interface/ftdi/flyswatter2.cfg]
source [find zynq-zc706-eval.cfg]

ftdi_serial FS01

# Set the speed
adapter_khz 10000

init

Running OpenOCD

Start OpenOCD using:

$ sudo openocd -f ps7_init.tcl -f zynq-zc706-1.cfg -c reset

The PS7 TCL file is passed on the command line so you can vary the initialisation for specific boards as you need by changing how to start OpenOCD.

GDB Configuration

This configuration allows you to commit into your repository a standard configuration placing the host specific configuration in your home directory.

Create a $HOME/.gdbinit file and place in it:

def zynq-connect
  target remote :3333
end

def zynq-fsbl-restart
 mon xilinx_ps7_init
end

def zynq-restart
 mon xilinx_ps7_init
 mon load_image /my/path/fsbl.elf 0x00000000 elf
 mon resume 0
 mon sleep 2000
 mon halt
end

To debug an FSBL create a file called zynq-gdbinit and have your build system copy it to a suitable place in your build tree calling it .gdbinit. It should contain:

#
# Zynq FSBL Support.
#

zynq-connect
zynq-fsbl-restart

load

b _exit

For an RTEMS application create another zynq-gdbinit file and also have your build system copy it to your build tree calling it .gdbinit and place in it:

#
# Zynq debug start up.
#

zynq-connect
zynq-restart

load

b _exit
b bsp_reset

tb Init
c

Issues

  1. The latest OpenOCD may have problems when the L2 cache is enabled. This is being looked into as time permits.
  2. SMP support is not well tested.