基于Verilog实现通过JTAG接口对zynqPS和PL程序重构 ------代码实现部分
概述
本文档描述了基于JTAG协议的Zynq SoC重构加载控制逻辑。该控制器使用Verilog语言实现,可部署于CPLD或FPGA平台,负责加载Zynq器件的PL端和PS端程序。当前实现中,PL端支持全局配置比特流文件的加载;PS端支持加载并运行Zynq FSBL(First Stage Bootloader)和u-boot启动程序,并且在FSBL加载完成后,能够加载并执行任意的ELF格式可执行文件映像。
完整工程文件下载: (点击蓝色字体获取)
一、介绍
Zynq SoC的常规启动模式包括QSPI、SD卡等,其中JTAG启动模式通常用于与调试主机的连接,配合Vivado和SDK软件加载需要调试的程序。本文介绍的控制器能够实现JTAG启动模式下的无外部干预加载运行,适用于Zynq硬件设备的备份启动、量产测试等场景。
理解本文内容要求读者具备对JTAG协议时序及其状态机的基本了解,并熟悉Zynq SoC的功能架构及PL/PS之间的关系。此外,读者还应了解Zynq BootROM、FSBL和u-boot的功能及其启动流程。这些内容在我之前的文章也都进行过讲解,不懂的可以去翻阅一下之前的文章。
用户需自行准备适用于PL配置的bitstream文件,以及FSBL和u-boot的ELF文件。不同硬件电路的映像文件不可互换。
二、代码实现
上一章,我们主要讲述了,JTAG重构的理论部分,本章继续开始讲解代码实现部分
硬件资源
主要采用A3P250作为加载设备,MRAM作为重构文件存储介质,zynq7020作为被重构加载设备,进行设计。
1. A3P250
A3P250 是一款低功耗、非易失性FPGA,适合作为加载设备。
- 非易失性:A3P250 基于闪存技术,配置数据在上电后无需外部存储器加载即可保留,简化了系统设计,降低了启动时间和外部依赖。
- 低功耗:它的静态功耗非常低,非常适合需要长时间运行或功耗敏感的应用场景,例如嵌入式系统或便携式设备。
- 可靠性强:闪存FPGA具有抗辐射和抗单粒子翻转(SEU)的特性,适合在恶劣环境下(如航天或工业领域)使用。
2. MRAM(磁阻随机存取存储器)
MRAM 作为重构文件存储介质
- 非易失性与高速读写:MRAM 结合了 SRAM 的快速读写和闪存的非易失性特性,能够快速存储和读取Zynq 7020的重构文件,同时在断电后数据不会丢失,适合动态重构场景。
- 耐久性:与传统闪存相比,MRAM 的写入寿命几乎无限(>10^14次),非常适合频繁更新重构文件的设计需求。
- 低功耗:MRAM 的读写操作功耗较低,且待机功耗极低,适合嵌入式系统中的长时间运行。
- 抗环境干扰:MRAM 对温度变化、辐射和磁场干扰有较强的抵抗力,适合在苛刻环境中存储关键配置数据。
- 数据完整性:相比传统的EEPROM或NAND闪存,MRAM 的数据保存时间更长(通常超过20年),确保重构文件长期可靠。
2.1 逻辑设计
控制器顶层代码为bootloader.v,其中实现了JTAG控制器、MRAM控制器、加载控制器、串口通讯控制器等。其中MRAM为存放重构文件的介质、

jtag_ctrl实现通用JTAG协议。
mram_32实现了一个32位数据接口的MRAM读写控制器,用于写入和读取数据。
dataloader按特定协议播放ROM数据中的读写命令,控制jtag_ctrl执行相应功能。
serial_mm实现了一个串口通讯接口,通过串口执行内部寄存器或MRAM的读写。
2.2 接口信号
顶层模块bootloader.v的接口如下:
|
信号 |
位宽 |
方向 |
功能 |
|
rst |
1 |
In |
高电平复位 |
|
clk |
1 |
In |
工作时钟 |
|
boot_start |
1 |
In |
高脉冲触发加载流程 |
|
boot_done |
1 |
Out |
高有效表示加载完成 |
|
serial_rx_data |
8 |
In |
串口输入数据字节 |
|
serial_rx_vld |
1 |
In |
串口输入数据有效标识 |
|
serial_tx_data |
8 |
Out |
串口发送数据字节 |
|
serial_tx_vld |
1 |
Out |
串口发送使能信号 |
|
serial_tx_rdy |
1 |
In |
串口发送就绪,1表示可以发送数据,0表示正忙 |
|
mram_we |
1 |
Out |
mram写有效 |
|
mram_oe |
1 |
Out |
mram输出使能 |
|
mram_ce |
1 |
Out |
mram片选 |
|
mram_ube |
1 |
Out |
Mram高字节选通 |
|
mram_lbe |
1 |
Out |
mram低字节选通 |
|
mram_dq |
16 |
Inout |
mram数据总线 |
|
mram_addr |
20 |
Out |
mram地址总线 |
|
tck |
1 |
Out |
JTAG TCK输出 |
|
tms |
1 |
Out |
JTAG TMS输出 |
|
tdi |
1 |
Out |
JTAG TDI输出 |
|
tdo |
1 |
In |
JTAG TDO输入 |
|
boot_mode |
3 |
Out |
Zynq的BOOTMODE配置 |
|
ps_por_b |
1 |
Out |
Zynq的复位信号 |
2.2.1 JTAG控制器
JTAG控制器代码为jtag_ctrl.v
JTAG控制器是一个通用的JTAG协议接口模块,其主要功能除了实现了IEEE 1149.1定义的TAP主控端协议外,还拓展实现了对多TAP串联的优化支持,从而满足对Zynq中PL端TAP和ARM端DAP串联JTAG链的高速访问。
控制器包括一套内存映射的用户访问接口,在这个接口上实现了一系列寄存器用于JTAG控制。
|
偏移量 |
寄存器 |
|
|
0x00 |
CTRL |
功能控制 |
|
0x04 |
STAT |
状态读取 |
|
0x08 |
LEN |
字段长度配置 |
|
0x0C |
CMD |
JTAG命令配置 |
|
0x10 |
DOUT0 |
字段0输出数据 |
|
0x14 |
DOUT1 |
字段1输出数据 |
|
0x18 |
DOUT2 |
字段2输出数据 |
|
0x1C |
DOUT3 |
字段3输出数据 |
|
0x20 |
DIN0 |
字段0输入数据 |
|
0x24 |
DIN1 |
字段1输入数据 |
|
0x28 |
DIN2 |
字段2输入数据 |
|
0x2C |
DIN3 |
字段3输入数据 |
jtag_ctrl.v还提供了几个GPIO信号,可用于通过软件控制zynq的PS_POR_B以及BOOTMODE引脚等功能。默认PS_POR_B连接到GPIO[0],BOOTMODE[2:0]连接到GPIO[3:1]。GPIO引脚仅在软件控制下使能,默认状态以及完成加载后可以设置为高阻,以避免和外部JTAG电路的冲突。
对jtag_ctrl.v的控制代码参见测试用例test_jtag_ctrl.sv和上位机程序jtagloader.py.
2.2.2 加载控制器
加载控制器代码为dataloader.v
加载控制器支持从ROM类器件中读取数据映像并执行相关操作。ROM中的数据格式必须符合以下定义:
ROM映像文件格式,以数据包为单位,每个数据包中包括该数据包的读写类型和数据数量。每个数据包代表对内存映射地址的32位读或写访问。每个数据包的第一个32位是包头,其中包括操作类型和数据量,数据量以32位为单位。例如:TYPE==0x3, COUNT==4表示数据包包括4个32位数据,写入以ADDR起始的连续地址空间。
数据包的第二个32位是字节地址,代表读写操作的目的地址。TYPE为固定地址读写时,每次读写操作地址保持初始值不变。TYPE为自增地址读写时,每完成一次读写地址自增0x4。
|
字节偏移量 |
字段定义 |
说明 |
||||
|
0x00 |
[31:24] |
[23:22] |
[21:20] |
[19:18] |
[17:0] |
0xFFFFFFFF表示映像结束,否则必须包含以MAGIC开头的指令; MAGIC固定为0x1A; TYPE: 0x0: 固定地址读 0x1: 固定地址写 0x2: 自增地址读 0x3: 自增地址写 COUNT: 操作数据量,表示32位读写的个数 |
|
MAGIC |
RSV |
TYPE |
RSV |
COUNT |
||
|
0x04 |
ADDR |
目标地址 |
||||
|
0x08 |
DATA0 |
Write指令: COUNT个数据 Read指令: 空 |
||||
|
0x0C |
DATA1 |
|||||
|
…… |
…… |
|||||
|
0x08+(COUNT-1)*4 |
DATAn |
|||||
加载控制器的start信号输入检测到高电平时,启动加载过程:从ROM中逐个读取数据包并执行数据包定义的读写操作,直到读到0xFFFFFFFF,表示数据映像结束。
利用加载控制器,可以在ROM中预存对JTAG控制器的读写操作序列,从而实现各种复杂的JTAG数据时序。
2.2.3 串口控制器
串口控制器实现了一个从串行字节流到内存映射数据总线的访问接口,支持读写操作。其控制协议参见测试用例test_serial_mm.sv和上位机程序jtagloader.py里的SerialAccess类。
串口控制器不包括UART串行部分实现,需要配合一套uart_tx/uart_rx程序工作。示例工程中已包含该代码。
2.2.4 上位机程序
FPGA程序功能是JTAG操作指令的播放,而JTAG指令的生成才是主要内容。基于bitstream文件和ELF文件,生成用于写入MRAM的JTAG指令序列,这部分程序在jtagloader.py中实现,并且在gui.py中提供了一个操作界面。其主要执行流程是:
- 读取bitstream文件,生成PL加载的JTAG指令序列,包括JPROGRAM,CFGIN和JSTART指令;
- 读取fsbl的elf文件,生成加载并运行fsbl的JTAG指令序列,包括初始化DAP,halt,写入内存,写入跳转指令,restart;
- 读取u-boot的elf文件,生成加载并运行u-boot的JTAG指令序列。由于u-boot默认需要在DDR中运行,而DDR的初始化在fsbl中,因此必须先保证fsbl已经运行完毕,才能开始加载u-boot并执行;
- 将以上生成的JTAG操作序列写入MRAM,dataloader将会读取并执行该指令序列。
三、使用说明
3.1 FPGA程序清单
|
文件 |
功能 |
|
Bootloader.v |
控制器顶层 |
|
Dataloader.v |
读取并执行JTAG操作 |
|
Jtag_ctrl.v |
JTAG控制器实现 |
|
Mram_32.v |
32位MARM访问接口 |
|
Mram_controller.v |
MRAM器件访问接口 |
|
Serial_mm.v |
串口访问地址映射寄存器的实现 |
|
My_uart_rx.v |
串口接收程序 |
|
My_uart_tx.v |
串口发送程序 |
|
A3p250_top.v |
用于A3P250开发板的FPGA顶层代码 |
|
Zx005a_top.v |
用于一体化组件的A3P400的顶层代码 |
|
test_xxx.v |
相关功能的单元测试 |
3.2 上位机python程序清单
|
文件 |
功能 |
|
Jtagloader.py |
JTAG控制器的上位机逻辑实现 |
|
Gui.py |
一个简单的操作界面 |
|
Test_xxx.py |
相关功能的单元测试和示例代码 |
Python源代码的运行和编译需要以下Python依赖:
- Python3.8以上
- Pyelftools
- Pyserial
- Gooey
- pyinstaller
3.3 上位机软件
预编译的上位机控制界面是gui.exe,其运行界面如下:

Command命令可选项有:
|
命令 |
功能 |
|
Load |
直接通过串口加载运行 |
|
Flash |
加载序列写入MRAM |
|
Reset |
执行复位操作,可选mode有: QSPI - 从QSPI启动 JTAG - JTAG模式启动 |
|
Boot |
从MRAM里加载运行 |
点击start即可开始运行选定的命令,窗口会显示运行进度和运行结果。

四、下板验证
4.1 硬件连接
通过杜邦线把ZYQN的JTAG接口,连接到A3P的IO
通过杜邦线连接A3P串口,接到PC
通过串口线,引出zynq的串口输出,接到PC,用于打印信息
具体如下所示:

4.2 验证
通过串口加载JTAG,启动zynq u-boot界面:
我们现在给A3P下载我们的程序,然后打开上位机软件,选择好串口号,command选择load,并选择fsbl.elf、u-boot.elf文件到上位机(也可以选择bit,都进行过测试),然后点击start

u-boot成功被加载起来

通过MRAM里面的文件加载JTAG,启动zynq u-boot界面:
我们现在给A3P下载我们的程序,然后打开上位机软件,选择好串口号,command选择flash,并选择fsbl.elf、u-boot.elf文件到上位机(也可以选择bit,都进行过测试),然后点击start,即可对MRAM进行写入被加载的文件
先MRAM写入完文件之后,需要修改command为boot,并点击start,A3P就会读取MRAM里面的文件,然后通过JTAG接口加载到ZYNQ,实现PS和PL端的重构


然后通过zynq串口,显示成功加载起来了u-boot,测试完成!!
我还对ZYNQ的JTAG接口接到了逻辑分析仪上,上面可以显示JTAG加载的全部过程,如下图所示

跟使用Xilinx下载器,抓到的现象一样,完美验证!!
制作不易,记得三连哦,给我动力,持续更新!!!

浙公网安备 33010602011771号