Zynq-7000 SoC – Xilinx. Z-turn Lite. Bare metal. Часть 3. Cortex M1.

 

  1. Cortex M1 и Cortex M3 в открытом доступе.
  2. Zynq-7000 SoC — Xilinx. Z-turn Lite. Bare metal. Часть 1.
  3. Zynq-7000 SoC — Xilinx. Z-turn Lite. Bare metal. Часть 2.

Cortex-M1

Некоторое время назад, мной был сделан обзор о доступности Cortex M1 и M3, по первой ссылке Вы сможете возобновить знания об этих ядрах. В этой статье я хотел бы поделиться знанием о том, как запустить ядро Cortex M1 на отличной платформе, например на zynq.  Вторая и третья ссылка позволит быстро войти в курс проектирования под zynq и даст базовое понимание о работе в Vivado.

Ядро Cortex-M1 –  оптимизировано для использования на FPGA. Cortex-M1 ориентирован на OEM-производителей, которые смогут унифицировать и специализировать архитектуру своих решений. ARM Cortex-M1 представляет собой 32-разрядный RISC-процессор с трехступенчатым конвейером, использующим несколько модифицированный набор инструкций Thumb-2. Процессорное ядро, как утверждается, способно исполнять код Thumb, таким образом, программно совместимо с ARM7TDMI и старше. Cortex-M1 может работать на частотах более 170 МГц, обеспечивая производительность 0,8 DMIPS (млн. инструкций в секунду)/МГц. Основными приложениями для Cortex-M1 являются встроенные средства управления, коммуникации, промышленное оборудование,  сетевое оборудование и устройства авиации.

Xilinx Vivado

1. Создаём новый проект в Xilinx Vivado, называем проект “Cortex M1”.

2. Выбираем необходимый камень.


3. Добавляем директорию с IP ARM Cortex M1.

4. Видим, что в добавленной директории находится необходимое нам IP.

5. Добавляем ip Zynq и ip Cortex M1.

6. Добавленное ядро Cortex M1 в блок дизайне.

7. Конфигурируем Cortex M1, в ядре убираем отладку, ставим режим без отладчика, чтобы сэкономить пространство pl части Zynq.

8. Добавляем IP axi uartlite, данное ядро было использовано в предыдущей статье.

9. Устанавливаем скорость в 115200 для IP axi uartlite.

10. Добавляем IP AXI GPIO, для AXI GPIO делаем настройку, как на скриншоте ниже.

11. Добавляем IP констант для Cortex M1. Константа значение 0 для порта прерываний, константа значение 0 для порта NVIC, константа значение 3 для порта CFGITCMEM, далее делаем автоматическое соединение линий и шин в Vivado для нашего проекта, итоговый проект должен иметь вид как на скриншоте.

12. Проверяем адресное пространство, если адресное пространство не назначено для IP, необходимо сделать назначение.

13. Запускаем синтез проекта, после синтеза проекта настраиваем порты io для axi uartlite, в меню портов, красным цветом подсвечены порты которые не определены.

14. Порты axi uartlite назначим на io_b35_ln3 и io_b35_ln2.

15. Данный банк запитан от 3.3В, напряжение для банка выбирается с помощью джампера на основной плате с zynq.

16. Порты io_b35_ln3 и io_b35_ln2 на разъеме J4.

17. Порты io_b35_ln3 и io_b35_ln2 на камне. Далее создаем файл ограничения целостности сигнала, и в него либо руками вводим содержание под скриншотом, либо назначаем порты через меню.

Содержание файла XDC.

set_property IOSTANDARD LVCMOS33 [get_ports uart_rtl_0_rxd]
set_property IOSTANDARD LVCMOS33 [get_ports uart_rtl_0_txd]
set_property PACKAGE_PIN D18 [get_ports uart_rtl_0_rxd]
set_property PACKAGE_PIN A20 [get_ports uart_rtl_0_txd]

18. Выполняем имплементацию проекта. Результат имплементации проекта.

После имплементации проекта необходимо выполнить файл make_mmi_file.tcl, source make_mmi_file.tcl, содержание файла ниже.

# -----------------------------------------------------------------------------
#  The confidential and proprietary information contained in this file may
#  only be used by a person authorised under and to the extent permitted
#  by a subsisting licensing agreement from ARM limited.
#
#             (C) COPYRIGHT 2018 ARM limited.
#                 ALL RIGHTS RESERVED
#
#  This entire notice must be reproduced on all copies of this file
#  and copies of this file may only be made by a person if such person is
#  permitted to do so under the terms of a subsisting license agreement
#  from ARM limited.
#
#       SVN Information
#
#       Checked In          : $Date$
#
#       Revision            : $Revision$
#
#       Release Information : Cortex-M1 DesignStart-r0p1-00rel0
#
# -----------------------------------------------------------------------------
#  Project : Cortex-M1 Arty A7 Example design with V2C-DAPLink adaptor board
#
#  Purpose : Script to get ITCM BRAM locations
#            MMI format from following two articles
#
# https://www.xilinx.com/support/answers/63041.html
# https://forums.xilinx.com/t5/Vivado-TCL-Community/export-BRAM-locations-into-MMI-file/td-p/771221
# -----------------------------------------------------------------------------

# Set MMI output file name
set mmi_file "m1.mmi"
set part     "xc7z010clg400-1"

# Function to swap bits
proc swap_bits { bit } {

    if { $bit > 23 } {return [expr {24 + (31 - $bit)}]}
    if { $bit > 15 } {return [expr {16 + (23 - $bit)}]}
    if { $bit > 7  } {return [expr {8  + (15 - $bit)}]}
    return [expr {7 - $bit}]
}

# Find all the ITCM RAMs, place in a list
set itcm_ram [get_cells -hier -regexp {.*itcm.*ram_block_reg.*} -filter {REF_NAME =~ RAMB36E1}]

# Vivado appears to read the memories in their actual bit order
# However update_mem amongst its very many failings doesn't support endianness, even though you specify it in the file!
# It also pays no attention to the bit_lane definition, it does the conversion based on the order memories are defined
# in the file!  Not clear what the MMI file does achieve!

# So go through and reverse each block of 4 memories
if { [expr {[llength $itcm_ram] % 4}] != 0 } {
    puts "Error - Number of memories not divisible by 4"
    return -1
}

# Number of RAMs details memory size.  Each RAM is 32kb, so 4kB.
set itcm_size_bytes [expr {4096*[llength $itcm_ram]}]
puts "Instruction memory size $itcm_size_bytes"

# Currently only support memory sizes between 16kB, (one byte per mem), and 128kB, (one bit per mem)
if { ($itcm_size_bytes <= (4*4096)) || ($itcm_size_bytes > (32*4096)) } {
    puts "Error - Memory size of $itcm_size_bytes out of range"
    puts "        Script only supports memory sizes between 16kB and 128kB"
    return -1
}

# Create and open target mmi file
set fp [open $mmi_file {WRONLY CREAT TRUNC}]
if { $fp == 0 } {
    puts "Error - Unable to open $mmi_file for writing"
    return -1
}

# Write the file header
puts $fp "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
puts $fp "<MemInfo Version=\"1\" Minor=\"15\">"
puts $fp "    <Processor Endianness=\"ignored\" InstPath=\"dummy\">"
puts $fp "        <AddressSpace Name=\"design_1_i.CORTEXM1_AXI_0.inst.u_x_itcm\" Begin=\"0\" End=\"[expr {$itcm_size_bytes-1}]\">"
puts $fp "            <BusBlock>"

# Create an array to put the location and top memory index into
array set mem_array {}

# Calculate the expected number of bits per memory
set mem_bits [expr {32/[llength $itcm_ram]}]

# set itcm_ram_reordered [list]

for {set i 0} {$i < [llength $itcm_ram]/4} {incr i} {
    set start     [expr {$i*4}]
    set end       [expr {($i*4)+3}]
    set new_list  [lreverse [lrange $itcm_ram [expr {$i*4}] [expr {($i*4)+3}]]]
#    puts "$start $end\n$new_list"
    # lreplace $itcm_ram [expr {$i*4}] [expr {($i*4)+3}] [lreverse [lrange $itcm_ram [expr {$i*4}] [expr {($i*4)+3}]]]


# puts $itcm_ram_reordered

    # For each entry display the location
    foreach ram $new_list {
        # Get the RAM location
        set loc_val [get_property LOC [get_cells $ram]]
        regexp -- {(RAMB36_)([0-9XY]+)} $loc_val full ram_name loc_xy

        # Get the nets driven by the D0 pins
        set data_bus [get_nets -of_objects [get_pins -filter {REF_PIN_NAME =~ DOADO*} -of [get_cells $ram]]]
        
        # Check number of bits is the same as that expected
        if { [llength $data_bus] != $mem_bits } {
            puts "Error - Number of data pins read, [llength $data_bus], does not equal expected memory bits, $mem_bits"
            return -1
        }

        # Number of pins connected to the memory sets the memory depth.
        set memory_depth [expr {(32768/[llength $data_bus])-1}]
        
        set idx_list [list]
        foreach entry $data_bus {
            # Filter the data_bus down to just the two index numbers
            set index [regexp -inline -- {[0-9]+} [regexp -inline -- {\[.*} [lindex $entry 0]]]
            lappend idx_list $index
        }
        
        # Sort the index list from highest to lowest
        set idx_list [lsort -decreasing -integer $idx_list]
        
        # Assign the highest and lowest bits for the range variables
        set index_low  [lindex $idx_list end]
        set index_high [lindex $idx_list 0]
        
        # Debug
        # puts $data_bus
        # puts $idx_list
        # puts "$index_high downto $index_low pos $loc_val"

        array set mem_array [list $index_high $loc_xy]
    }; # foreach
}; # for

    # Sort array into index order
    array set mem_array_sorted {}

    foreach entry [lsort [array names mem_array]] {
        array set mem_array_sorted [list $entry $mem_array($entry)]
    }
    
    foreach entry [array names mem_array_sorted] {
        # puts "$entry : $mem_array_sorted($entry)"
    }
    
    # MMI file needs to be in little endian format because update_mem doesn't actually use the endianness field
    # So first index is 7, next is 15, 23, 31.
    # Number of entries to write is 8/mem_bits.  Lower index is index_high - (mem_bits - 1)
    for {set top_idx 7} {$top_idx < 32} {incr top_idx 8} {
        for {set idx_high $top_idx} {$idx_high > ($top_idx-8)} {incr idx_high -$mem_bits} {
            # puts $idx_high
            set loc $mem_array_sorted($idx_high)
            set idx_low [expr {$idx_high - $mem_bits + 1}]
            
            if { $loc == "" } {
                puts "Error - No location entry for index $idx_high"
                return -1
            }
    
            # Write relevant XML
            puts $fp "                <BitLane MemType=\"RAMB36\" Placement=\"$loc\">"
            puts $fp "                    <DataWidth MSB=\"$idx_high\" LSB=\"$idx_low\"/>"
            puts $fp "                    <AddressRange Begin=\"0\" End=\"$memory_depth\"/>"
            puts $fp "                    <Parity ON=\"false\" NumBits=\"0\"/>"
            puts $fp "                </BitLane>"
        }
    }



# Write the file tail
puts $fp "            </BusBlock>"
puts $fp "        </AddressSpace>"
puts $fp "    </Processor>"
puts $fp "    <Config>"
puts $fp "        <Option Name=\"Part\" Val=\"$part\"/>"
puts $fp "    </Config>"
puts $fp "    <DRC>"
puts $fp "      <Rule Name=\"RDADDRCHANGE\" Val=\"false\"/>"
puts $fp "    </DRC>"
puts $fp "</MemInfo>"

# Close the output file
close $fp


# Useful facilities
# Get the bus as a list
# get_nets -hierarchical -regexp  {.*itcm.*doutA.*}

Данный файл создаст “m1.mmi” файл, который содержит описание адресного пространства Cortex-M1.

Файл m1.mmi

<?xml version="1.0" encoding="UTF-8"?>
<MemInfo Version="1" Minor="15">
    <Processor Endianness="ignored" InstPath="dummy">
        <AddressSpace Name="design_1_i.Cortex_M1_0.inst.u_x_itcm" Begin="0" End="32767">
            <BusBlock>
                <BitLane MemType="RAMB36" Placement="X1Y1">
                    <DataWidth MSB="7" LSB="4"/>
                    <AddressRange Begin="0" End="8191"/>
                    <Parity ON="false" NumBits="0"/>
                </BitLane>
                <BitLane MemType="RAMB36" Placement="X0Y1">
                    <DataWidth MSB="3" LSB="0"/>
                    <AddressRange Begin="0" End="8191"/>
                    <Parity ON="false" NumBits="0"/>
                </BitLane>
                <BitLane MemType="RAMB36" Placement="X2Y2">
                    <DataWidth MSB="15" LSB="12"/>
                    <AddressRange Begin="0" End="8191"/>
                    <Parity ON="false" NumBits="0"/>
                </BitLane>
                <BitLane MemType="RAMB36" Placement="X1Y0">
                    <DataWidth MSB="11" LSB="8"/>
                    <AddressRange Begin="0" End="8191"/>
                    <Parity ON="false" NumBits="0"/>
                </BitLane>
                <BitLane MemType="RAMB36" Placement="X0Y6">
                    <DataWidth MSB="23" LSB="20"/>
                    <AddressRange Begin="0" End="8191"/>
                    <Parity ON="false" NumBits="0"/>
                </BitLane>
                <BitLane MemType="RAMB36" Placement="X1Y3">
                    <DataWidth MSB="19" LSB="16"/>
                    <AddressRange Begin="0" End="8191"/>
                    <Parity ON="false" NumBits="0"/>
                </BitLane>
                <BitLane MemType="RAMB36" Placement="X1Y4">
                    <DataWidth MSB="31" LSB="28"/>
                    <AddressRange Begin="0" End="8191"/>
                    <Parity ON="false" NumBits="0"/>
                </BitLane>
                <BitLane MemType="RAMB36" Placement="X0Y5">
                    <DataWidth MSB="27" LSB="24"/>
                    <AddressRange Begin="0" End="8191"/>
                    <Parity ON="false" NumBits="0"/>
                </BitLane>
            </BusBlock>
        </AddressSpace>
    </Processor>
    <Config>
        <Option Name="Part" Val="xc7z010clg400-1"/>
    </Config>
    <DRC>
      <Rule Name="RDADDRCHANGE" Val="false"/>
    </DRC>

19. Результат выполнения команды source make_mmi_file.tcl.

20. Генерируем bitstream.

21. Итоговый результат утилизации ресурсов pl части Zynq, информация об утилизации ресурсов в виде графика.

22. Информация об утилизации ресурсов в виде таблицы.

Xilinx SDK

23. Запускаем SDK (предварительно сделав экспорт проекта), в настройках добавляем глобальную директорию, как на скриншоте ниже.

24. Адресное пространство проекта в SDK.

25. Создаем BSP для Cortex M1.

26. Настраиваем bsp, самое главное – это проверить чтобы стандартный поток шел через axi uartlite.

27. Запускаем проект в Keil для генерации elf файла. Путь до проекта software/m1_for_arty_s7/Build_Keil. В sdk_workspace добавляем сгенерируемый bsp, а также файлы Xpsuedo_asm_rvct.c и Xpseudo_asm_rvct.h, путь до файлов vivado/Arm_sw_repository/CortexM/bsp/standalone_v6_7/src/arm/cortexm1/armcc. Все исходники на которые будет ругаться Keil будем удалять.

Исходный код проекта для Keil.

/*
 * Copyright:
 * ----------------------------------------------------------------
 * This confidential and proprietary software may be used only as
 * authorised by a licensing agreement from ARM Limited
 *   (C) COPYRIGHT 2014, 2016 ARM Limited
 *       ALL RIGHTS RESERVED
 * The entire notice above must be reproduced on all authorised
 * copies and copies may only be made to the extent permitted
 * by a licensing agreement from ARM Limited.
 * ----------------------------------------------------------------
 * File:     main.c
 * Release Information : Cortex-M1 DesignStart-r0p1-00rel0
 * ----------------------------------------------------------------
 *
 */

/*
 * --------Included Headers--------
 */

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>

// Xilinx specific headers
#include "xparameters.h"
//#include "xgpio.h"

#include "m1_for_arty.h"        // Project specific header
#include "gpio.h"
#include "uart.h"
#include "spi.h"

/*******************************************************************/

int main (void)
{

    // Define local variables
    int     status;
    int     DAPLinkFittedn;
    int     i;
    int     readbackError;
    char    debugStr[256];
    
    // Illegal location
    volatile u32 emptyLoc;
        
    // CPU ID register
    volatile u32 *pCPUId = (u32 *)0xE000ED00;
    volatile u32 CPUId;
    int          CPU_part;
    int          CPU_rev;
    int          CPU_var;
    char         CPU_name[20];
        
    // init_platform is defined in platform.c
    // It calls enable_caches which is uBlaze specific and then init_uart
    InitialiseUART();

    // Clear all interrupts
    NVIC_ClearAllPendingIRQ();
    
    // Enable the UART interrupt
    NVIC_EnableIRQ(UART0_IRQn);

    // Enable UART Interrupts
    EnableUARTInterrupts();

    // Read the DAPLinkFitted input, (assigned to IRQ[31]).
    // Note the IRQ is never enabled, so polling the pending register will indicate the status
    NVIC_DisableIRQ(DAPLinkFittedn_IRQn);
    DAPLinkFittedn = NVIC_GetPendingIRQ( DAPLinkFittedn_IRQn );

    // Set DAPLink QSPI to the normal read-write controller
    // Do NOT do this for code running from the DAPLink QSPI.  This will switch from the XIP QSPI
    // controller to the standard controller, so the processor will not be able to access it's code image
    // This should only be done if the XIP QSPI is used to copy code to internal TCM, then boot-load from that TCM
    // SetDAPLinkQSPIMode( QSPI_QSPIMODE );
        
    // Read the CPU ID register to auto-detect the CPU and revision
    // Note however that code is compiled for a specific processor, so even though
    // the processor can be auto-detected, if the compiled code has extended commands not
    // supported by the processor, then runtime issues can occur
    CPUId    = *pCPUId;
    CPU_var  = ((CPUId & 0x00F00000) >> 20);
    CPU_part = ((CPUId & 0x0000FFF0) >> 4);
    CPU_rev  = CPUId & (0x0000000F);
    
    switch (CPU_part)
    {
        case 0xC21 : strcpy(  CPU_name, "Cortex-M1" ); break;
        case 0xC23 : strcpy(  CPU_name, "Cortex-M3" ); break;
        default    : sprintf( CPU_name, "Unknown %x", CPU_part );
    }
    
    sprintf (debugStr, "Arm %s Revision %i Variant %i\r\n\n", CPU_name, CPU_rev, CPU_var );

#ifndef SIM_BUILD    
    // Use Xilinx version print command
    print ("************************************\r\n");
    print ( debugStr );
    print ("Example design for Digilent S7 board\r\n");
    if ( DAPLinkFittedn )
        print ("\nV2C-DAPLink board not detected\r\n");
    else
        print ("\nV2C-DAPLink board detected\r\n");
    print ("Use DIP switches and push buttons to\r\ncontrol LEDS\r\n");
    print ("Version 1.2\r\n");
    print ("************************************\r\n");
#else
    print ( debugStr );
#endif


    // Main loop.  Handle LEDs and switches via interrupt
    while ( 1 )
    {
        /* Main loop. Wait for interrupts to occur */
        /*
        if ( CheckUARTRxBytes() != 0 )
            print ("x");
        */
				print("mcu.by");
    }
}

/* Interrupt handler for DAPLink Fitted */
// This routine should never be called as the signal is used as IO
// Routine created to prevent exceptions in the case the IRQ is enabled
void DAPLinkFittedn ( void )
{
    // Clear the IRQ and disable any future IRQs
    NVIC_ClearPendingIRQ(DAPLinkFittedn_IRQn);
    NVIC_DisableIRQ(DAPLinkFittedn_IRQn);
}

Исходный код bat файла для генерации elf, тут самое главное правильный путь до fromelf, с помощью fromelf генерируется elf файл.

@REM -----------------------------------------------------------------------------
@REM  The confidential and proprietary information contained in this file may
@REM  only be used by a person authorised under and to the extent permitted
@REM  by a subsisting licensing agreement from ARM limited.
@REM
@REM             (C) COPYRIGHT 2018 ARM limited.
@REM                 ALL RIGHTS RESERVED
@REM
@REM  This entire notice must be reproduced on all copies of this file
@REM  and copies of this file may only be made by a person if such person is
@REM  permitted to do so under the terms of a subsisting license agreement
@REM  from ARM limited.
@REM
@REM       SVN Information
@REM
@REM       Checked In          : $Date$
@REM
@REM       Revision            : $Revision$
@REM
@REM       Release Information : Cortex-M1 DesignStart-r0p1-00rel0
@REM
@REM -----------------------------------------------------------------------------
@REM  Project : Cortex-M1 Arty A7 Example design with V2C-DAPLink adaptor board
@REM
@REM  Purpose : Convert axf file to hex for both BRAM and QSPI simulation models
@REM            Also convert axf file to elf for bitstream generation
@REM -----------------------------------------------------------------------------

@REM - Create the output files.
@REM - File to load into FPGA RAM
call C:\Keil_v5\ARM\ARMCC\bin\fromelf --vhx --32x1 --output bram_s7.hex objects\m1_for_arty_s7.axf
@REM - File to merge SW into bitstream
call C:\Keil_v5\ARM\ARMCC\bin\fromelf --elf        --output bram_s7.elf objects\m1_for_arty_s7.axf
@REM - File to load into DAPLink QSPI simulation
call C:\Keil_v5\ARM\ARMCC\bin\fromelf --vhx --8x1  --output qspi_s7.hex objects\m1_for_arty_s7.axf
@REM - Files to load onto DAPLink QSPI board
call C:\Keil_v5\ARM\ARMCC\bin\fromelf --bin        --output qspi_s7.bin objects\m1_for_arty_s7.axf

@REM - Copy the files to the relevant directories of the hardware project
copy bram_s7.* ..\..\..\hardware\m1_for_arty_s7\m1_for_arty_s7
copy qspi_s7.hex ..\..\..\hardware\m1_for_arty_s7\testbench

Все итоговые части проекта готовы для запуска на железе, переносим файл design_1_wrapper.bit, bram_s7.elf и m1.mmi в одну директорию и выполняем source make_prog_files.tcl. И получаем файл zynq_m1.bit, который содержит уже все необходимое.

Содержание файла make_prog_files.tcl.

# -----------------------------------------------------------------------------
#  The confidential and proprietary information contained in this file may
#  only be used by a person authorised under and to the extent permitted
#  by a subsisting licensing agreement from ARM limited.
#
#             (C) COPYRIGHT 2018 ARM limited.
#                 ALL RIGHTS RESERVED
#
#  This entire notice must be reproduced on all copies of this file
#  and copies of this file may only be made by a person if such person is
#  permitted to do so under the terms of a subsisting license agreement
#  from ARM limited.
#
#       SVN Information
#
#       Checked In          : $Date$
#
#       Revision            : $Revision$
#
#       Release Information : Cortex-M1 DesignStart-r0p1-00rel0
#                             
# -----------------------------------------------------------------------------
#  Project : Cortex-M1 Arty A7 Example design with V2C-DAPLink adaptor board
#
#  Purpose : Script to create bit and mcs files for Arty A7 board
#
#            Combines the original bit file, mmi file, and software elf to create
#            the full bitstream
#            Then converts full bitstream to mcs for download to the onboard flash
#
#            Can be run either in Vivado GUI TCL console, or else in batch mode
#            from command line
#            If run in Vivado TCL console, pwd needs to be set to root of project, 
#            in the same location as the bit file
# -----------------------------------------------------------------------------

# Input files
set mmi_file            "./m1.mmi"
set elf_file            "./bram_s7.elf"
set source_bit_file     "./design_1_wrapper.bit"
#set reference_bit_file  "./m1_for_arty_s7_reference.bit"

# Output files
set output_bit_file "zynq_m1.bit"
set output_mcs_file "zynq_m1.mcs"

# Enable to turn on debug
set updatemem_debug 0

# Assemble bit file that can be downloaded to device directly
# Combine the original bit file, mmi file, and software elf to create the full bitstream

# Delete target file
file delete -force $output_bit_file
file delete -force $output_mcs_file

# Determine if the user has built the project and has the target source file
# If not, then use the reference bit file shipped with the project
if { ![file exists $source_bit_file] } {
    puts "\n********************************************"
    puts "INFO - File $source_bit_file doesn't exist as project has not been built"
    puts "       Using $reference_bit_file instead\n"
    puts "********************************************/n"
    set source_bit_file $reference_bit_file
}

# Banner message to console as there is no output for a few seconds
puts "  Running updatemem ..."

if { $updatemem_debug } {
    set error [catch {exec updatemem --debug --force --meminfo $mmi_file --data $elf_file --bit $source_bit_file --proc dummy --out $output_bit_file} result]
} else {
    set error [catch {exec updatemem --force --meminfo $mmi_file --data $elf_file --bit $source_bit_file --proc dummy --out $output_bit_file} result]
}

# Print the stdout from updatemem
puts $result

# Updatemem returns 0 even when there is an error, so cannot trap on error.  Having deleted output file to start, then
# detect if it now exists, else exit.
if { ![file exists $output_bit_file] } {
    puts "ERROR - $output_bit_file not made"
    return -1
} else {
    puts "\n********************************************"
    puts "  $output_bit_file correctly generated"
    puts "********************************************\n"
}

# Create MCS file for base board QSPI flash memory
write_cfgmem -force -format MCS -size 16 -interface SPIx1 -loadbit " up 0 $output_bit_file" $output_mcs_file

# Check MCS was correctly made
if { ![file exists $output_mcs_file] } {
    puts "ERROR - $output_bit_file not made"
    return -1
} else {
    puts "\n********************************************"
    puts "  $output_mcs_file correctly generated"
    puts "********************************************\n"
}

После загрузки bitstream, если все шаги сделаны правильно в консоли мы должны увидеть следующий результат.