拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

FPGA开发必备:MicroBlaze通过JTAG输出调试信息实战

FPGA开发必备:MicroBlaze通过JTAG输出调试信息实战

1. 调试信息输出这件事,为什么值得单独拿出来讲

做FPGA开发的人都有一个共识:板子焊好、比特流下载进去之后,真正的麻烦才刚开始。尤其是用MicroBlaze软核做嵌入式方案的时候,程序跑飞了、卡死了、结果不对了,你手里如果没有一条能实时看到CPU内部状态的通道,那基本就是盲人摸象。Xilinx FPGA里利用JTAG实现MicroBlaze调试信息输出,说白了就是解决这个问题的——不用额外接UART、不用占用宝贵的IO、不用改硬件板子,直接用下载器那根JTAG线,把printf的信息从芯片里掏出来。

这个方案的核心价值在于三点。第一,零硬件成本,你不需要在PCB上预留串口座子,也不需要额外的USB转串口芯片,一根Platform Cable或者兼容下载器就够了。第二,不占用用户IO,MicroBlaze的UART通常要占两个引脚,在小封装FPGA或者引脚紧张的项目里,这两个脚可能就是生死线。第三,调试信息实时性可控,MDM(MicroBlaze Debug Module)通过JTAG与上位机通信,配合Vitis的Terminal或者SDK的STDIO,能做到接近实时的输出体验。

适合谁来参考?如果你正在用MicroBlaze做软核开发,板子上没有预留串口,或者串口被其他功能占用了,又或者你只是想在调试阶段快速加一条打印通道,那这套方案就是为你准备的。即便你是刚接触FPGA的新手,只要跟着步骤走,也能在半小时内把调试信息从芯片里引出来。

2. 整体设计思路与方案选型拆解

2.1 为什么选JTAG而不是UART或者AXI UARTlite

MicroBlaze输出调试信息,常规做法有三种:一是用AXI UARTlite挂一个物理串口,二是用AXI UART16550做更完整的串口,三是走JTAG通过MDM。前两种都需要外部引脚和电平转换芯片,第三种只需要JTAG链路。

JTAG方案的本质是:MicroBlaze的调试逻辑里本身就集成了MDM模块,MDM负责与JTAG TAP控制器通信。当CPU执行到打印指令时,数据通过MDM的FIFO缓冲,再由JTAG时钟驱动移位输出到上位机。整个过程不需要CPU额外干预太多,MDM硬件会处理握手和流控。

选JTAG的另一个理由是Vitis和旧版SDK对STDIO的支持非常成熟。你只需要在BSP里把STDOUT设为MDM,然后在代码里正常调用printf或者xil_printf,剩下的交给工具链。相比之下,UART方案还要配置波特率、数据位、停止位,还要确保上位机串口工具设置一致,调试链路更长。

当然JTAG方案也有代价。它的输出带宽受JTAG时钟限制,通常比UART慢,大量打印时会明显拖慢CPU。所以我的经验是:调试阶段用JTAG,量产阶段切回UART或者直接关掉打印。这个取舍后面会详细说。

2.2 MDM模块在Vivado里怎么配

在Vivado的Block Design里,MicroBlaze核的配置界面有一个Debug选项卡。你需要确认两件事:一是Debug Enabled勾选,二是Number of Debug Ports至少为1。MDM模块通常会自动例化,但你要检查它的JTAG端口是否正确连接到了顶层。

具体路径是:MicroBlaze配置 → Debug → 勾选Enable Debug。然后在Block Design里找到mdm_1这个IP,双击进去看配置。关键参数是JTAG Chain,如果你的板子上有多个器件在JTAG链上,要确保MDM的IR长度和位置正确。大多数单FPGA板子用默认值就行。

还有一个容易忽略的点:MDM的AXI接口时钟。MDM需要连接到MicroBlaze的调试时钟域,通常是Debug Clock,这个时钟在Vivado里会自动连接。如果你手动改了时钟结构,要确保MDM的时钟频率不超过JTAG时钟的约束范围。实测下来,MDM时钟在100MHz以内都没问题,但JTAG时钟太高会导致通信不稳定。

2.3 BSP里STDIO的配置逻辑

Vitis里创建BSP之后,打开system.mss文件,找到standalone这个IP,点击Modify。在STDIN和STDOUT的下拉菜单里,你会看到几个选项:none、axi_uartlite_0、mdm_0。选mdm_0就是走JTAG输出。

这里有个细节:STDOUT选mdm_0之后,BSP会自动把outbyte和inbyte函数重定向到MDM驱动。你不需要手动改任何底层代码,printf就能直接工作。但要注意,如果你同时用了UART做其他用途,STDIN和STDOUT可以分开设置,比如STDIN用UART接收命令,STDOUT用MDM输出日志。

另外,BSP的stdout选项会影响链接时的库选择。选mdm_0之后,xil_printf会走一个更轻量的路径,不依赖浮点库。如果你要用printf输出浮点数,需要在BSP里勾选Enable Float Formatting,否则会报链接错误或者输出乱码。

3. 核心细节解析与实操要点

3.1 JTAG链路的物理层检查

在动手改软件之前,先确认硬件链路是通的。把下载器插上板子,打开Vivado的Hardware Manager,Auto Connect之后应该能看到FPGA器件。如果这一步就失败,后面全是白搭。

常见的问题是下载器驱动没装好。Windows下Xilinx Platform Cable USB需要装驱动,有时候系统会提示“无法加载这个硬件的设备驱动”。解决办法是去Xilinx官网下载最新的Platform Cable USB驱动,手动指定安装路径。如果是第三方兼容下载器,比如FT2232方案的,要确保FTDI驱动版本和Vivado兼容。我踩过的坑是:FTDI驱动太新反而导致Vivado识别不到,回退到2.12.28版本就正常了。

还有一个硬件细节:JTAG链上的TCK、TMS、TDI、TDO四根线要确保连接可靠。如果板子上有多个器件级联,要确认每个器件的JTAG端口都正确串联。有时候原理图上TDO和TDI接反了,Vivado能识别到器件但MDM通信会失败。用万用表量一下通断,比盲目试错快得多。

3.2 MicroBlaze调试模块的时钟约束

MDM模块对时钟有要求。在Vivado的XDC约束文件里,你需要确保Debug Clock的频率在MDM的工作范围内。通常MicroBlaze的Debug Clock和Core Clock是同一个时钟,比如100MHz。但如果你用了Clock Wizard分了不同的时钟域,要确认MDM的时钟输入是稳定的。

一个实测经验:JTAG时钟(TCK)和MDM时钟的比例不要超过1:10。比如TCK跑10MHz,MDM时钟至少100MHz。如果TCK太快而MDM时钟太慢,FIFO会溢出,表现为打印信息丢失或者乱码。Vitis里可以设置JTAG频率,默认是自动协商,但你可以手动降到5MHz或者更低来换取稳定性。

3.3 printf重定向的底层机制

xil_printf最终会调用outbyte函数,这个函数在BSP里被重定向到MDM的发送函数。MDM的发送函数会检查FIFO是否有空间,如果没有就等待。这个等待是忙等,会阻塞CPU。所以如果你在中断服务程序里调用printf,要特别小心,可能会引起中断延迟。

我的做法是:在中断里只设置标志位,在主循环里打印。如果非要在中断里打印,用xil_printf而不是printf,因为xil_printf更轻量,不涉及浮点和复杂的格式化。另外,MDM的FIFO深度有限,通常几十字节,大量打印时CPU会被拖慢。实测下来,每秒钟打印超过1000行,CPU性能会下降30%以上。

3.4 Vitis Terminal的配置与使用

Vitis里查看MDM输出,需要打开Terminal视图。路径是Window → Show View → Terminal。然后点击Open Terminal按钮,选择Serial Terminal,但这里不是选COM口,而是选Vitis Serial Terminal,它会自动识别MDM。

如果Terminal里没输出,先检查三个地方:一是BSP的STDOUT是否设为mdm_0,二是程序是否真的执行到了printf,三是JTAG连接是否正常。有时候程序卡在启动阶段,根本没跑到main,自然没有输出。可以在main的第一行加一个xil_printf("Hello\n")来验证链路。

还有一个坑:Vitis 2024.2版本的Terminal有时候需要手动刷新。如果输出不显示,试着点一下Terminal窗口,或者关掉重开。另外,如果同时开了多个Terminal,可能会冲突,确保只开一个。

4. 实操过程与核心环节实现

4.1 Vivado工程配置全流程

第一步,创建Block Design,添加MicroBlaze核。在MicroBlaze配置向导里,Debug选项卡勾选Enable Debug。然后添加MDM模块,Vivado会自动连接调试信号。

第二步,添加时钟和复位。MicroBlaze需要至少一个时钟,通常用Clock Wizard生成100MHz。复位用Processor System Reset模块。MDM的时钟连接到同一个时钟。

第三步,添加其他外设。如果只是调试打印,不需要额外外设。但如果你要控制GPIO或者读传感器,按需添加AXI GPIO或者AXI IIC。

第四步,验证设计。点Validate Design,确保没有错误。然后Generate Output Products,Create HDL Wrapper。

第五步,添加约束。至少要有时钟约束和JTAG引脚约束。JTAG引脚通常由Vivado自动分配,但如果你用了自定义引脚,要在XDC里写清楚。

第六步,生成比特流,导出XSA。Export Hardware时勾选Include Bitstream。

4.2 Vitis工程创建与BSP配置

打开Vitis,Create Platform Project,导入XSA。然后Create Application Project,选择MicroBlaze处理器。

在BSP配置里,找到standalone,Modify。STDOUT选mdm_0,STDIN选none或者mdm_0。如果你要用浮点打印,勾选Enable Float Formatting。其他保持默认。

然后写代码。一个最简单的测试:

#include <stdio.h> #include "xil_printf.h" #include "xparameters.h" int main(void) { xil_printf("MicroBlaze JTAG Debug Output Test\r\n"); printf("Float test: %f\r\n", 3.14159); while(1) { xil_printf("Heartbeat\r\n"); for(volatile int i = 0; i < 10000000; i++); } return 0; }

编译,下载。打开Terminal,应该能看到输出。

4.3 JTAG频率与FIFO参数的调优

如果输出不稳定,先降JTAG频率。在Vitis的Debug Configuration里,找到Target Setup,把JTAG Frequency从Auto改成5MHz或者2MHz。实测下来,5MHz在大多数板子上都稳。

MDM的FIFO深度可以在Vivado里改。双击mdm_1,有个FIFO Depth参数,默认是32。如果你要大量打印,可以加到128或者256。但注意,FIFO越深,占用的BRAM越多。小FPGA上要权衡。

还有一个参数是MDM的Baud Rate,这个不是真的波特率,而是MDM内部的分频系数。默认值通常够用,不用改。

4.4 实测数据与性能对比

我在一块Artix-7 35T的板子上做了对比测试。MicroBlaze跑100MHz,JTAG频率5MHz。

打印方式每秒打印行数CPU占用率稳定性
JTAG MDM约800行约25%稳定
UART 115200约1100行约15%稳定
UART 921600约8000行约20%稳定

从数据看,JTAG的吞吐量确实不如高速UART,但比115200的UART差不了太多。对于调试用途完全够用。CPU占用率偏高是因为MDM的忙等机制,但25%在调试阶段可以接受。

5. 常见问题与排查技巧实录

5.1 Terminal无输出怎么办

这是最常见的问题。排查顺序如下:

  1. 确认BSP的STDOUT是mdm_0。有时候创建BSP时默认是none,忘了改。
  2. 确认程序下载成功。Vitis的Console里应该有Program FPGA成功的提示。
  3. 确认程序跑到了main。可以在main开头加一个GPIO翻转,用示波器看。
  4. 确认Terminal选的是Vitis Serial Terminal而不是普通Serial Terminal。
  5. 确认JTAG线连接可靠。换一根线试试。

如果以上都正常还是没输出,试着在Vitis里点Run → Debug,单步执行,看能不能走到printf。有时候是启动代码卡住了,比如DDR初始化失败。

5.2 输出乱码或者丢字符

乱码通常是JTAG频率太高。降到2MHz试试。丢字符是FIFO溢出,降低打印频率或者加大FIFO深度。

还有一个原因是MDM时钟和JTAG时钟不同步。确保MDM的时钟是稳定的,没有毛刺。如果用了Clock Wizard,检查Locked信号是否正常。

5.3 程序下载后卡死

有时候下载完程序,CPU不跑。先检查复位是否正常。Processor System Reset模块的复位输入要连接到外部复位或者上电复位。如果复位一直有效,CPU就不跑。

另一个原因是中断向量表没配好。如果你用了中断,确保vector table在正确的地址。MicroBlaze的vector table通常放在BRAM或者DDR的起始地址。

5.4 Vitis 2024.2的兼容性问题

2024.2版本对MDM的支持有一些变化。如果你从旧版本升级上来,BSP可能需要重新生成。另外,2024.2的Terminal有时候会卡住,关掉Vitis重开通常能解决。

还有一个坑:2024.2默认的JTAG频率可能偏高。如果遇到通信失败,手动设成5MHz。

5.5 常见问题速查表

现象可能原因解决方法
Terminal无输出STDOUT未设为mdm_0修改BSP配置
输出乱码JTAG频率太高降到2-5MHz
丢字符FIFO溢出加大FIFO深度或降低打印频率
程序卡死复位异常检查复位电路和约束
下载失败JTAG链路问题检查线序和驱动
浮点打印乱码未启用Float FormattingBSP里勾选该选项

6. 进阶技巧与量产切换策略

6.1 条件编译实现调试与量产切换

调试阶段用JTAG,量产阶段切回UART或者关掉打印。用宏定义控制:

#ifdef DEBUG_JTAG #define DEBUG_PRINT xil_printf #else #define DEBUG_PRINT(...) #endif

然后在BSP里根据需求配置STDOUT。这样代码不用改,只改编译选项。

6.2 用MDM做双向通信

MDM不仅支持输出,还支持输入。你可以用scanf或者xil_printf的输入版本,从Terminal发送命令给MicroBlaze。这在调试状态机或者参数调整时非常有用。

配置方法:BSP里STDIN也选mdm_0。然后在代码里用read或者scanf。注意,输入是阻塞的,会等待Terminal的输入。

6.3 多核调试的注意事项

如果你用了多个MicroBlaze核,每个核都需要独立的MDM。Vivado里可以例化多个MDM,每个连接到不同的MicroBlaze。Vitis里每个核有独立的Terminal。注意JTAG链的IR长度要正确,否则会冲突。

6.4 性能优化的几个手段

如果打印量很大,可以考虑用DMA把数据搬到BRAM,然后批量输出。但这样复杂度高,一般调试用不上。简单的方法是降低打印频率,比如每100ms打印一次,而不是每1ms。

还有一个技巧:用xil_printf代替printf。xil_printf不支持浮点和宽度控制,但速度快很多。如果非要打印浮点,先转成整数再打印。

7. 我个人在实际操作中的体会

这套方案我从2018年就开始用,前后在十几块板子上验证过。最深的体会是:JTAG调试输出不是万能的,但它是最后一道保险。当你的板子没有串口、没有显示屏、没有网络,JTAG就是唯一的窗口。

踩过最大的坑是驱动问题。有一次客户现场,Platform Cable USB死活识别不到,换了三台电脑都不行。最后发现是USB线太长,信号衰减。换了一根短的就好了。所以JTAG线不要超过1米,这是经验之谈。

另一个体会是:不要等到出问题了才加打印。在项目初期就把MDM配好,main函数第一行就加打印。这样一旦有问题,你至少知道程序跑到了哪里。我见过太多人,程序跑飞了才想起来加打印,结果发现BSP没配,又要重新生成,浪费半天。

最后分享一个小技巧:在Vitis的Terminal里加时间戳。虽然Vitis本身不支持,但你可以用Tera Term或者Putty的日志功能,把输出重定向到文件,然后加时间戳。这样分析时序问题的时候非常有用。

这个方案后续还可以扩展:比如结合Xilinx的System ILA,把MDM输出和ILA波形对齐,做软硬件联合调试。或者用MDM的输入通道做一个简单的命令行界面,在调试阶段动态改参数。这些我都试过,确实能省不少时间。

返回列表