FPGA Artix-7 Bootloader Python 更新

目录

  • 软硬环境

  • 复现官方 srec_spi_bootloader

    • 例子简介

    • Vivado硬件部分

    • 存储划分

    • Vitis 嵌入式 Boot

    • Vitis 嵌入式 App

    • elf转换srec

    • 合并boot和app得到mcs文件

    • 下载测试

    • 过程分析

  • 基础知识

    • BIT MCS HEX BIN

    • Bit Swapping

    • SREC 文件格式

    • Vivado约束

  • 串口Boot

    • 地址划分

    • 链接脚本修改

    • Github Link

    • App

    • Boot

    • 一条命令升级

  • 参考链接


本文在Artix7上复现了Xilinx官方的srec_spi_bootloader例子, 有详细的过程分析和图文说明, 然后动手实现了FPGA串口Boot的完整过程, 通过Python脚本一条命令升级, 自动把app的elf文件转bin文件,从app跳转boot,擦写flash, 写入app, 进行crc32校验后跳转到新的app.

本篇有详细的图文说明和源码链接, 欢迎大家评论, 点赞和收藏.

软硬环境

软件:

  • Vivado v2023.2.2
  • Vitis Unified IDE v2023.2.0

硬件:

  • FPGA: xc7a35tfgg484-2
  • 晶振: 3.3V, 50MHz 有源 单端, Y18
  • 复位: 低电平复位, F20
  • LED: F19, 低电平点亮
  • UART: RX G15, TX G16
  • SPI Flash, 3.3V, N25Q128:
    • CLK: CCLK0
    • CS: T19
    • IO 0~3: {P22 R22 P21 R21}

对于SPI Flash需要注意的地方:

  • 7 系列 FPGA 的 AXI Quad SPI 的 IP 配置里面需要勾选 Enable STARTUP Primitive 选项进行实例化, 让CLK能正常工作, CLK不体现在引脚配置里面
  • flash 型号选择 mt25ql128-spi-x1_x2_x4, 别名 n25q128-3.3v-spi-x1_x2_x4, NOR Flash, 128Mbit, 16MB, 后面的代码里扇区Sector统一按64KB擦除, 页Page大小256写入
  • 厂商选 Micron(Numonyx)
  • 未使用DDR3, 所幸实现功能的代码并不复杂, Boot和App全用片内RAM测试

复现官方 srec_spi_bootloader

例子简介

这个例子的默认路径是 C:XilinxVitis2023.2dataembeddedswlibsw_appssrec_spi_bootloader

简介:

简单的 SREC 引导加载程序

给定内存中镜像的位置,它能够启动 SREC 格式的镜像文件(Mototorola S-record 格式)。 特别是,该引导加载程序专为存储在可从处理器寻址的非易失性闪存中的镜像而设计。

请修改 blconfig.h 头文件中的定义“FLASH_IMAGE_BASEADDR”,以指向引导加载程序获取闪存镜像的位置。

您可以将这些源包含在您的软件应用程序项目中,并为您希望进行引导加载的处理器构建项目。 您还可以随后修改这些源,以使引导加载程序适应您可能需要的任何特定场景。

一般是Boot(Golden Image)运行在BRAM, 把 App(Multiboot Image) 搬到DDR3, 这里简化一下, 不使用DDR3, 只用FPGA自带的RAM来测试这个例子以简化流程.

Vivado硬件部分

Clocking Wizard:

  • 输入频率 50MHz, Single ended clock
  • 输出频率默认 100MHz
  • 复位类型: Active Low
  • 引出 clk_in1 引脚

Uartlite:

  • 波特率 115200
  • 引出 UART

Timer 不做更改, 用于验证Boot跳转后, App的中断是否能正常运行.

GPIO:

  • GPIO Width 设为 1
  • 引出 GPIO, 用于点灯.

Quad SPI:

  • Mode 选 Quad
  • Slave Device 选 Micron(Numonyx)

MicroBlaze:

  • 勾选 Enable Peripheral AXI Data Interface
  • Run Block Automation, Local Memory 128KB, 勾选 Interrupt Controller
  • 连接好 SPI, Timer 和 Uart 的中断

附上AXI Quad SPI的配置

FPGA Artix-7 Bootloader Python 更新
在这里插入图片描述

自动连接后:

FPGA Artix-7 Bootloader Python 更新
在这里插入图片描述

Generate Output Products

Create HDL Warapper

xdc 约束文件:

set_property CFGBVS VCCO [current_design]
set_property CONFIG_VOLTAGE 3.3 [current_design]
set_property BITSTREAM.CONFIG.CONFIGRATE 50 [current_design]
set_property BITSTREAM.GENERAL.COMPRESS TRUE [current_design]
# 注释掉暂时规避SPI驱动异常
# set_property BITSTREAM.CONFIG.SPI_BUSWIDTH 4 [current_design]
# set_property CONFIG_MODE SPIx4 [current_design]

set_property IOSTANDARD LVCMOS33 [get_ports clk_in1_0]
set_property IOSTANDARD LVCMOS33 [get_ports reset_rtl_0]
set_property IOSTANDARD LVCMOS33 [get_ports {GPIO_0_tri_io[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports UART_0_rxd]
set_property IOSTANDARD LVCMOS33 [get_ports UART_0_txd]
set_property IOSTANDARD LVCMOS33 [get_ports spi_rtl_0_io0_io]
set_property IOSTANDARD LVCMOS33 [get_ports spi_rtl_0_io1_io]
set_property IOSTANDARD LVCMOS33 [get_ports spi_rtl_0_io2_io]
set_property IOSTANDARD LVCMOS33 [get_ports spi_rtl_0_io3_io]
set_property IOSTANDARD LVCMOS33 [get_ports {spi_rtl_0_ss_io[0]}]

set_property PACKAGE_PIN Y18 [get_ports clk_in1_0]
set_property PACKAGE_PIN F20 [get_ports reset_rtl_0]
set_property PACKAGE_PIN F19 [get_ports {GPIO_0_tri_io[0]}]
set_property PACKAGE_PIN G15 [get_ports UART_0_rxd]
set_property PACKAGE_PIN G16 [get_ports UART_0_txd]
set_property PACKAGE_PIN T19 [get_ports {spi_rtl_0_ss_io[0]}]
set_property PACKAGE_PIN P22 [get_ports spi_rtl_0_io0_io]
set_property PACKAGE_PIN R22 [get_ports spi_rtl_0_io1_io]
set_property PACKAGE_PIN P21 [get_ports spi_rtl_0_io2_io]
set_property PACKAGE_PIN R21 [get_ports spi_rtl_0_io3_io]

Generate Bitstream, 得到:

  • FPGA 位流文件 design_1_wrapper.bit, 大小约 2.09MB
  • FPGA 内存配置文件 design_1_wrapper.mmi

导出硬件 XSA 文件, Include bitstream

资源消耗

FPGA Artix-7 Bootloader Python 更新
在这里插入图片描述

存储划分

RAM: MicroBlaze 的 Local Memory 分配了 128KB, 把前32KB给Boot, 后96KB给App

Flash: FPGA位流文件大小 2.09MB, 和boot合并后也这么大, 压缩后874KB, 把16MB SPI Flash的前4MB预留给Boot

Vitis 嵌入式 Boot

Open Workspace, 选择一个文件夹作为工作空间.

Create Platform Component, 导入XSA文件, 创建硬件平台, 编译成库.

File -> New Component -> From Examples, 从 Srec_spi_bootloader 例子创建 Boot 程序

修改 App 的偏移地址为4MB处

FPGA Artix-7 Bootloader Python 更新
在这里插入图片描述

修改链接文件中的SIZE为(32KB-0x50 = 0x7FB0), 下面的Section也都是用的BRAM, 这里没有用DDR3

FPGA Artix-7 Bootloader Python 更新
在这里插入图片描述

FlashReadID这里读两遍以防止第一次读出错

FPGA Artix-7 Bootloader Python 更新
在这里插入图片描述

编译得到 srec_spi_bootloader.elf

 mb-size --format=berkeley srec_spi_bootloader.elf
    text    data     bss     dec     hex filename
   15004     360    2092   17456    4430 srec_spi_bootloader.elf

菜单栏 Vitis -> Program Device, 点Generate从 FPGA位流文件, 内存映射文件, elf文件 生成 download.bit 文件

FPGA Artix-7 Bootloader Python 更新
在这里插入图片描述

对应的 tcl 命令为

update_mem -meminfo C:/z/ws_vivado/fpga_boot_app/ba_vitis/srec_spi_bootloader/_ide/bitstream/design_1_wrapper.mmi -data {C:zws_vivadofpga_boot_appba_vitissrec_spi_bootloaderbuildsrec_spi_bootloader.elf} -proc design_1_i/microblaze_0 -bit C:/z/ws_vivado/fpga_boot_app/ba_vitis/srec_spi_bootloader/_ide/bitstream/design_1_wrapper.bit -out C:/z/ws_vivado/fpga_boot_app/ba_vitis/srec_spi_bootloader/_ide/bitstream/download.bit -force
 Loading bitfile C:/z/ws_vivado/fpga_boot_app/ba_vitis/srec_spi_bootloader/_ide/bitstream/design_1_wrapper.bit

因为Vivado中约束文件没有位流压缩, 这里出来的download.bit大小也是2.09MB, 但其实和先前的 design_1_wrapper.bit 不一样了.

Vitis 嵌入式 App

新建 app_component

修改链接文件中的基地址为32KB(0x8000), SIZE 96KB(0x18000)

FPGA Artix-7 Bootloader Python 更新
在这里插入图片描述

加上 gpio 和 timer 的代码

#include "xgpio.h"
#include "xil_exception.h"
#include "xil_printf.h"
#include "xinterrupt_wrap.h"
#include "xparameters.h"
#include "xtmrctr.h"
#include <stdbool.h>

volatile bool timer_isr_flag = false;

void TimerCounterHandler(void *CallBackRef, u8 TmrCtrNumber) {
  XTmrCtr *InstancePtr = (XTmrCtr *)CallBackRef;
  if (XTmrCtr_IsExpired(InstancePtr, TmrCtrNumber)) {
    timer_isr_flag = true;
  }
}

int timer_init(XTmrCtr *TmrCtrInstancePtr, UINTPTR BaseAddr, u32 ReloadValue) {
  int Status = XTmrCtr_Initialize(TmrCtrInstancePtr, BaseAddr);
  if (Status != XST_SUCCESS) {
    return -1;
  }
  Status = XSetupInterruptSystem(
      TmrCtrInstancePtr, (XInterruptHandler)XTmrCtr_InterruptHandler,
      TmrCtrInstancePtr->Config.IntrId, TmrCtrInstancePtr->Config.IntrParent,
      XINTERRUPT_DEFAULT_PRIORITY);
  if (Status != XST_SUCCESS) {
    return -2;
  }
  XTmrCtr_SetHandler(TmrCtrInstancePtr, TimerCounterHandler, TmrCtrInstancePtr);
  u8 TmrCtrNumber = 0;
  XTmrCtr_SetOptions(TmrCtrInstancePtr, TmrCtrNumber,
                     XTC_INT_MODE_OPTION | XTC_AUTO_RELOAD_OPTION |
                         XTC_DOWN_COUNT_OPTION);
  XTmrCtr_SetResetValue(TmrCtrInstancePtr, TmrCtrNumber, ReloadValue);
  XTmrCtr_Start(TmrCtrInstancePtr, TmrCtrNumber);

  return 0;
}

int gpio_init(XGpio *Gpio, UINTPTR BaseAddr) {
  int Status = XGpio_Initialize(Gpio, BaseAddr);
  if (Status != XST_SUCCESS) {
    return -1;
  }
  XGpio_SetDataDirection(Gpio, 1, ~0x01);
  XGpio_DiscreteWrite(Gpio, 10x01);
  return 0;
}

int main(void) {
  xil_printf("rn###################################rn");

  XGpio led0;
  int ret = gpio_init(&led0, XPAR_XGPIO_0_BASEADDR);
  if (ret != 0) {
    xil_printf("gpio_init failed: %drn", ret);
    return -1;
  }

  XTmrCtr timer0;
  ret = timer_init(&timer0, XPAR_XTMRCTR_0_BASEADDR, 100000000);
  if (ret != 0) {
    xil_printf("timer_init failed: %drn", ret);
    return -2;
  }
  bool led_status = true;
  while (1) {
    if (timer_isr_flag) {
      timer_isr_flag = false;
      if (led_status) {
        led_status = false;
        xil_printf("led0 onrn");
        XGpio_DiscreteClear(&led0, 10x01);
      } else {
        led_status = true;
        xil_printf("led0 offrn");
        XGpio_DiscreteWrite(&led0, 10x01);
      }
    }
  }

  return 0;
}

编译得到 app_component.elf 文件

elf转换srec

# mb-objcopy 位置
# C:XilinxVitis2023.2gnumicroblazentbinmb-objcopy.exe
# C:XilinxVivado2023.2gnumicroblazentbinmb-objcopy.exe
mb-objcopy -O srec app_component.elf app_component.srec

合并boot和app得到mcs文件

需要的文件:

  • boot文件: download.bit(这里面也包含了FPGA位流文件, 内存描述文件), 放到地址 0
  • app文件: app_component.srec, 放到地址 0x00400000

写tcl脚本文件

# make_mcs.tcl
write_cfgmem -force -format MCS -size 16 -interface SPIx1 -loadbit " up 0 ./download.bit" -loaddata " up 0x00400000 app_component.srec " merge.mcs

运行 tcl 脚本, 生成 merge.mcs

vivado -mode batch -source  make_mcs.tcl

如果是 SPIx4 会得到如下错误, 改约束文件没啥用, 无奈 -interface SPIx1

ERROR: [Writecfgmem 68-20] SPI_BUSWIDTH property is set to "1" on bitfile ./download.bit. This property has to be set to "4" to generate a configuration memory file for the SPIX4 interface. Please ensure that a valid value has been set for the property BITSTREAM.Config.SPI_buswidth and rerun this command.

下载测试

菜单栏 Vitis -> Program Flash, 镜像文件选

FPGA Artix-7 Bootloader Python 更新
在这里插入图片描述

编程完后, 断电重启, 因为是 SPIx1, 启动会比较慢, 6s后, 串口日志:

SREC SPI Bootloader
FlashID=0xFF 0xFF 0xFF

FlashID=0x20 0xBA 0x18

Loading SREC image from flash @ address: 00400000

Bootloader: Processed (0x)00000001 S-records
Bootloader: Processed (0x)00000002 S-records
Bootloader: Processed (0x)00000003 S-records
Bootloader: Processed (0x)00000004 S-records
...
Bootloader: Processed (0x)000004be S-records
Bootloader: Processed (0x)000004bf S-records
Bootloader: Processed (0x)000004c0 S-records
Bootloader: Processed (0x)000004c1 S-records
Executing program starting at address: 00000000

###################################
led0 on
led0 off
led0 on
led0 off

此时如果不是断电重启, 而是按下复位按键, 会从App处重新开始执行.

过程分析

先来看APP的SREC文件是怎么合并到MCS文件(可改后缀为HEX格式查看)的:

FPGA Artix-7 Bootloader Python 更新
在这里插入图片描述

相当于整个SREC文件被存入了Flash的0x00400000里面

bootloader的分析如下:

  • 先是初始化 SPI, 用 FlashReadID() 读ID, 这里连读两次后读出来 0x20 0xBA 0x18, FlashID[3]字节含义为
    • 0, Man.ID, 如 0x20 Micron(Numonyx) 或 ST?
    • 1, ID Code, 如 0xBA 和 0x18连起来 0xBA18 指代 N25Q128
    • 2, 容量, 0x15 2MB; 0x16 4MB, 0x17, 8MB; 0x18 16MB; 0x19 32MB; 0x20 64MB; 0x21 128MB; 0x22 256MB
  • 判断容量大于16MB(2^24), 从24bit地址进入32bit地址, if (FlashID[FLASH_SIZE] > FLASH_16_MB) { Status = FlashEnterExit4BAddMode(&Spi, ENTER_4B); ...}, 当然此处板子没有超过16MB, 就没进入4字节地址模式
  • flash_get_srec_line() 函数, 先从APP SREC文件存放的Flash地址(FLASH_IMAGE_BASEADDR 0x00400000)处读4个字节, 如S017, 得到长度len如 0x17 = 23, 然后再读len * 2字节(因为一个字节转成HEX文本是两个), 这样就读出了一行SREC记录存入sr_buf, 地址再加2个字符rn就是下一个记录的开始地址.
  • decode_srec_line() 函数, 把sr_buf按照srec的记录格式, 转成对应的 类型 地址 数据 数据长度, 同时也做了校验, 因为checksum=0xFF-(sum&0xFF), 所以checksum+(sum&0xFF)=0xFF, 也就是cksum为0xFF表示校验正确
  • 判断这一行的记录类型, 如果是S1 S2 S3表示数据, 就拷贝到内存中去 memcpy ((void *)srinfo.addr, (void *)srinfo.sr_data, srinfo.dlen);, 如果是S7 S8 S9表示起始地址和文件结束 laddr = (void (*)())srinfo.addr;
  • 最后跳转到起始地址运行app: (*laddr)();, 初始定义是 void (*laddr)();

基础知识

BIT MCS HEX BIN

AMD配置文件格式:

文件扩展 位交换 AMD Vivado TCL 命令 说明
BIT NO write_bitstream 含标头信息的二进制文件
RBT NO write_bitstream -raw_bitfile 含文本标头以及ASCII 1 0的BIT文件等效
BIN NO write_bitstream -bin_file 二进制数据文件, 无标头信息
MCS YES(除SPI) write_cfgmem -format MCS ASCII PROM 文件, 含数据 地址 校验和
HEX 用户定义 write_cfgmem -format HEX ASCII PROM 文件, 仅数据

注: RBT 是 Rawbits 的简写

7 系列 FPGA 位流由三部分组成:

  • 总线宽度自动检测, 仅用于并行模式, SPI模式会忽略这些字节
  • 同步字 0xAA995566
  • FPGA配置

PROM 文件用于重新格式化位流文件以进行 PROM 编程(write_cfgmem -loadbit), PROM文件通常是位交换的, 除 SPI 配置模式外.

GUI方式生成BIT RBT BIN文件, 工程设置里面勾选, 下次Generate Bitstream就有了

FPGA Artix-7 Bootloader Python 更新
在这里插入图片描述

生成的文件对比, Header不会被下到Flash里面, 总线宽度检测会被SPI模式忽略, 但仍会放到0x00000000的位置, SPI模式会直接找到同步字 0xAA995566

FPGA Artix-7 Bootloader Python 更新
在这里插入图片描述

GUI方式生成MCS BIN HEX文件, Vivado菜单栏Tools -> Generate Memory Configuration File

FPGA Artix-7 Bootloader Python 更新
在这里插入图片描述

对应的Command为

write_cfgmem  -format mcs -size 16 -interface SPIx4 -loadbit {up 0x00000000 "C:/z/ws_vivado/fpga_boot_app/ba_vitis/srec_spi_bootloader/_ide/bitstream/download.bit" } -loaddata {up 0x00400000 "C:/z/ws_vivado/fpga_boot_app/ba_vitis/app_component/build/app_component.srec" } -force -file "C:/z/ws_vivado/fpga_boot_app/merge/merge.mcs"

Bit Swapping

位交换适用于串行、SelectMAP 或 BPI PROM 文件以及 ICAPE2 接口。SPI接口不考虑这个. 图略.

位交换是字节内位的交换。 各种文件格式:

  • MCS PROM 文件格式始终是位交换的,除非使用 SPI 配置模式的 write_cfgmem -interface spi1|spi2|spi4 选项。
  • HEX 文件格式可以进行位交换或不进行位交换,具体取决于用户选项。
  • 位流文件(BIT、RBT、BIN)永远不会进行位交换。

HEX 文件格式仅包含配置数据。其他 PROM 文件格式包括不应发送到 FPGA 的地址和校验和信息。地址和校验和信息由某些第三方器件编程器使用,但不会编程到 PROM 中。

SREC 文件格式

Motorola S-record 以 ASCII 文本形式将二进制信息作为十六进制值传达, 此文件格式也可以称为 SRECORD、SREC、S19、S28、S37。

FPGA Artix-7 Bootloader Python 更新
在这里插入图片描述

说明:

  • S0 是 Header, 可以十六进制翻译成ASCII字符串, 一般是文件名, 文件地址, 编译日期, 版本等
  • S1,S2,S3表示数据, 分别有16bit,24bit,32bit地址
  • S7,S8,S9表示结束, 对应有32bit,24bit,16bit起始地址或零地址. 一般S1 S9搭配, S2 S8搭配, S3 S7搭配
  • S5, S6表示计数, 分别对应16bit, 24bit计数, 前者用于少于65536(0xFFFF), 后者少于1677215(0xFFFFFF), 可选的, 一般srec文件里面找不到这两个记录
  • Count 表示记录其余部分(地址 + 数据 + 校验和)后面的字节数(十六进制数字对), 一般至少2字节地址, 加上1字节校验, 所以Count至少为0x03, 最大为0xFF
  • Checksum 校验和是字节计数、地址和数据字段的两个十六进制数字对所表示的值之和的最小有效字节。在 C 编程语言中,总和通过以下方式转换为校验和: 0xFF - (sum & 0xFF), 每行有效的记录都要有校验和.

具体到文件

FPGA Artix-7 Bootloader Python 更新
在这里插入图片描述

Vivado约束

添加上位压缩, 让bitstream文件更小一些

set_property BITSTREAM.CONFIG.CONFIGRATE 50 [current_design]
set_property BITSTREAM.GENERAL.COMPRESS TRUE [current_design]

编译后从原来的 2141KB 缩为 874KB, 可减少一部分的Flash占用

串口Boot

常见的Boot的方式:

  • Golden + Multiboot, 依靠 ICAPE2 原语, 无需嵌入式编程, 当然 MicroBlaze 也有对应的 HWICAP
  • A B SWAP, 这在汽车中比较常见, 相互翻转, 可硬件支持或软件模拟
  • Boot A B, Boot 中可以加入救砖或安全启动
  • Boot App, 一般嵌入式中比较常见, 这里也给出一个这样的例子, 仅仅是例子而已

写好一个相对完善的Boot并不容易, 涉及 救砖 安全 是否需要相互升级 行业协议等等, 总有考虑不到的地方.

本节的Flash读写来自 C:XilinxVitis2023.2dataembeddedswXilinxProcessorIPLibdriversspi_v4_11examplesxspi_numonyx_flash_quad_example.c

串口或SPI的读写尽量都改成了非阻塞或异步的.

地址划分

本篇的区域划分如下, 因为没有使用Flash XIP, 需要把固件全部拷贝到RAM中, 性能更高, 但需要固件不要超过RAM的大小, 外部有DDR3时, RAM是足够的, 本节使用的全部片内RAM, 这里划给APP编译出来不超过64KB是能跑的:

FPGA Artix-7 Bootloader Python 更新
在这里插入图片描述

链接脚本修改

boot 的链接脚本文件修改

FPGA Artix-7 Bootloader Python 更新
在这里插入图片描述

app 的链接脚本文件修改

FPGA Artix-7 Bootloader Python 更新
在这里插入图片描述

Github Link

源码上传到了: https://github.com/weifengdq/domain_controller_orin_x2_tc397/tree/main/fpga_artix7_boot_app

代码是实验性质的, 功能上跑通, 未充分测试或整理优化, 仅供参考.

文件说明:

  • ba_vivado.tcl
    • 重建 Vivado 工程: vivado -mode batch -source .ba_vivado.tcl -nolog -nojournal
    • 打开 Vivado 工程: vivado .ba_vivadoba_vivado.xpr
  • design_1_wrapper.xsa, Vivado 导出的硬件(含位流文件), Vitis 可以从这里创建 Platform
  • boot 该文件夹是Vitis Embedded工程对应的 boot 源码, flash 驱动 bsp_spi_flash.c 不同的Flash型号不一样, 注意修改
  • app 该文件夹是Vitis Embedded工程对应的 app 源码
  • uptool.py, 升级工具Python3

App

elf转bin

# 注意修改升级工具 `uptool.py` 中的路径
# objcopy = r'C:XilinxVitis2023.2gnumicroblazentbinmb-objcopy.exe'
# elf = r'C:zws_vivadofpga_boot_appbs_vitis_embeddedappbuildapp.elf'

mb-objcopy -o binary app.elf app.bin

因为 app 的基地址上面设置的 0x10000, 而中断区在前 0x50 里面, 所以BIN文件的 0x50~0xFFFF 是无用的零值, BIN文件是地址连续的, 但app只要掐头去尾, 这也是官方用srec的原因, 这里依然用BIN是因为PC上BIN文件哪怕几MB也不算太大, 减少些HEX或SREC文件格式的解析, 脚本里面把这一段无用的零值忽略不传送.

其它说明:

  • info 指令判断当前是否是 app 文件, 这里只是随便用的方法 xil_printf("current: %sn", (*(uint32_t *)0x00000000) == 0xB0000001 ? "app" : "unknown");, 不同的工程可能不同, 具体查看ELF转出的BIN文件
  • reset 指令可以出发cpu复位, 原理是跳到0地址全局中断区 (*((void (*)())(0x00000000)))(); 当然这个不能在硬件中断中用, 在主循环可以用, 有一个 microblaze_disable_interrupts(); 关中断再复位, 但这样只能复位一次
  • jump 指令可以从 app 跳转到 boot, 原理也不复杂, 因为每次重新上电先走的boot, 跳转app的时候交换了上图中的全局中断区和中断暂存区, app里面只需要把这两个区再交换一下, 跳到0地址执行就可以从app进入boot了, boot的[0x50, 0xFFAF]的ram区还是boot的, 没有人动它. 跳到boot如果没有救砖操作, 超时后就又自动跳转回boot

app测试截图:

FPGA Artix-7 Bootloader Python 更新
在这里插入图片描述

Boot

说明:

  • 主要实现在bsp_uart_boot.c
  • 判断当前在 boot: (*(uint32_t *)0x00000000) == 0xB0000000 根据实际情况而定
  • 串口定长接收 16 字节, 只有收APP数据的时候是定长 272( = 16 + 256) 字节
  • 1s的救砖时间, 收到救砖命令就停留在boot
  • 救砖超时开始把APP从Flash拷贝到RAM, 拷贝完后进行CRC32校验, 失败仍留在Boot
  • CRC32校验成功跳转APP

boot 测试截图

FPGA Artix-7 Bootloader Python 更新
在这里插入图片描述

一条命令升级

app改动编译后可通过 update 命令直接把 elf 文件刷进FPGA Flash里面

update 综合命令说明:

  • 每条指令前16字节是 CRC32 CMD ADDR SIZE, 收到的ACK前16字节是 CRC32 0xFFFFFFFF-CMD X X
  • 脚本直接调 mb-objcopy 把elf转成bin文件
  • 发 info 指令 可以判断当前是 app 还是 boot
  • 发 jump 跳转命令从app调到boot
  • 发 save brick 救砖命令禁止boot跳转app
  • 发 erase 命令 擦除FLASH扇区(从bin文件大小自动计算出扇区数)
  • 发 write 命令 256字节按页对齐写入FLASH(从bin文件摘出前0x50的APP_ISR和0x10000后的APP, 计算CRC32, 把地址长度和算出的CRC也存入Flash, 用于下次的安全启动)
  • 发 check 命令 从Flash拷回到中断暂存区和APP的RAM区, 进行CRC32校验, 和开机的安全启动是一样的
  • 发 jump 指令 从boot跳转到app

执行:

> python .uptool.py -s COM32 -c update
convert C:zws_vivadofpga_boot_appbs_vitis_embeddedappbuildapp.elf to app.bin
7E B2 F5 FA F3 FF FF FF 00 00 00 00 00 00 00 00   ~...............
63 75 72 72 65 6E 74 3A 20 61 70 70 0A            current:.app.
■ 17:20:49.013958
✌️: current: app
current    : app
63 75 72 72 65 6E 74 3A 20 62 6F 6F 74 0A         current:.boot.
■ 17:20:49.028623

0F 2D 61 88 FC FF FF FF 00 00 00 00 00 00 00 00   .-a.............
73 61 76 65 20 62 72 69 63 6B 20 67 65 74 0A      save.brick.get.
■ 17:20:49.211294
...
...
✌️: current: boot
save brick ok
app size 42254, isr size 80, erase size 131072, sectors 2
38 0D C4 15 F7 FF FF FF 00 00 3F 00 00 00 01 00   8.........?.....
65 72 61 73 65 20 64 6F 6E 65 3A 20 30 78 30 30   erase.done:.0x00
33 46 30 30 30 30 2C 20 6C 65 6E 3A 20 36 35 35   3F0000,.len:.655
33 36 0A                                          36.
■ 17:20:51.221235
2C B2 B0 EF F7 FF FF FF 00 00 40 00 00 00 01 00   ,.........@.....
65 72 61 73 65 20 64 6F 6E 65 3A 20 30 78 30 30   erase.done:.0x00
34 30 30 30 30 30 2C 20 6C 65 6E 3A 20 36 35 35   400000,.len:.655
33 36 0A                                          36.
■ 17:20:51.739222
D8 2B BC DA F8 FF FF FF 00 00 00 00 10 01 00 00   .+..............
6E 65 78 74 5F 6C 65 6E 3A 20 32 37 32 0A         next_len:.272.
■ 17:20:52.233010
write 1/166
AA F2 D5 3C F6 FF FF FF 00 00 40 00 10 01 00 00   ...<......@.....
77 72 69 74 65 20 64 6F 6E 65 3A 20 30 78 30 30   write.done:.0x00
34 30 30 30 30 30 2C 20 6C 65 6E 3A 20 32 37 32   400000,.len:.272
0A                                                .
■ 17:20:52.279141
write 2/166
1E F9 A2 9A F6 FF FF FF 00 01 40 00 10 01 00 00   ..........@.....
77 72 69 74 65 20 64 6F 6E 65 3A 20 30 78 30 30   write.done:.0x00
34 30 30 31 30 30 2C 20 6C 65 6E 3A 20 32 37 32   400100,.len:.272
0A 
...
...
■ 17:20:59.682305
write app_info, app_isr_info and isr, addr 4194304
BE 4D A1 C6 F6 FF FF FF 00 00 3F 00 10 01 00 00   .M........?.....
77 72 69 74 65 20 64 6F 6E 65 3A 20 30 78 30 30   write.done:.0x00
33 46 30 30 30 30 2C 20 6C 65 6E 3A 20 32 37 32   3F0000,.len:.272
0A                                                .
■ 17:20:59.728140
write app_info, app_isr_info and isr end
EF 41 7E DB F8 FF FF FF 00 00 00 00 10 00 00 00   .A~.............
6E 65 78 74 5F 6C 65 6E 3A 20 31 36 0A            next_len:.16.
■ 17:20:59.772271
90 B3 5A 64 FE FF FF FF 00 00 00 00 00 00 00 00   ..Zd............
■ 17:20:59.787298
61 70 70 5F 63 72 63 20 63 68 65 63 6B 20 6F 6B   app_crc.check.ok
2C 20 65 6E 74 65 72 20 61 70 70 0A               ,.enter.app.
■ 17:20:59.857745
read data size: 28
app_crc check ok, enter app

check ok, jump to app
F1 5B 6D 8E F4 FF FF FF 00 00 00 00 00 00 00 00   .[m.............
6A 75 6D 70 20 74 6F 20 61 70 70 0A 63 75 72 72   jump.to.app.curr
65 6E 74 3A 20 61 70 70 0A                        ent:.app.
■ 17:21:01.383697
jump to app
current: app

参考链接

  • Vivado Design Suite 用户指南: 编程和调试 (UG908)
  • 7 Series FPGAs Configuration User Guide(UG470)
  • MultiBoot with 7 Series FPGAs and SPI
  • SPI SREC Bootloader Example Design for the Arty Evaluation Board (avnet.com)
  • FPGA Bootloader Part 1 – MicroBlaze SREC SPI Bootloader Hardware Step-by-step | Shadowcode
  • FPGA Bootloader Part 2 – Vitis SREC SPI Bootloader Software Step-by-Step | Shadowcode
  • SPI (Serial Peripheral Interface) – 杰哥的知识库 (jia.je)
  • SREC (file format) – Wikipedia
  • Intel HEX – Wikipedia
  • 16进制到文本字符串的转换,16进制-BeJSON.com


原文始发于微信公众号(weifengdq):FPGA Artix-7 Bootloader Python 更新

版权声明:admin 发表于 2024年3月29日 下午6:46。
转载请注明:FPGA Artix-7 Bootloader Python 更新 | CTF导航

相关文章