ARTICLE DETAIL

资讯详情

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

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

FPGA开发必备:MicroBlaze通过JTAG输出调试信息实战 1. 调试信息输出这件事为什么值得单独拿出来讲做FPGA开发的人都有一个共识板子焊好、比特流下载进去之后真正的麻烦才刚开始。尤其是用MicroBlaze软核做嵌入式方案的时候程序跑飞了、卡死了、结果不对了你手里如果没有一条能实时看到CPU内部状态的通道那基本就是盲人摸象。Xilinx FPGA里利用JTAG实现MicroBlaze调试信息输出说白了就是解决这个问题的——不用额外接UART、不用占用宝贵的IO、不用改硬件板子直接用下载器那根JTAG线把printf的信息从芯片里掏出来。这个方案的核心价值在于三点。第一零硬件成本你不需要在PCB上预留串口座子也不需要额外的USB转串口芯片一根Platform Cable或者兼容下载器就够了。第二不占用用户IOMicroBlaze的UART通常要占两个引脚在小封装FPGA或者引脚紧张的项目里这两个脚可能就是生死线。第三调试信息实时性可控MDMMicroBlaze Debug Module通过JTAG与上位机通信配合Vitis的Terminal或者SDK的STDIO能做到接近实时的输出体验。适合谁来参考如果你正在用MicroBlaze做软核开发板子上没有预留串口或者串口被其他功能占用了又或者你只是想在调试阶段快速加一条打印通道那这套方案就是为你准备的。即便你是刚接触FPGA的新手只要跟着步骤走也能在半小时内把调试信息从芯片里引出来。2. 整体设计思路与方案选型拆解2.1 为什么选JTAG而不是UART或者AXI UARTliteMicroBlaze输出调试信息常规做法有三种一是用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 ManagerAuto 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跑10MHzMDM时钟至少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 ProductsCreate HDL Wrapper。第五步添加约束。至少要有时钟约束和JTAG引脚约束。JTAG引脚通常由Vivado自动分配但如果你用了自定义引脚要在XDC里写清楚。第六步生成比特流导出XSA。Export Hardware时勾选Include Bitstream。4.2 Vitis工程创建与BSP配置打开VitisCreate Platform Project导入XSA。然后Create Application Project选择MicroBlaze处理器。在BSP配置里找到standaloneModify。STDOUT选mdm_0STDIN选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跑100MHzJTAG频率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无输出怎么办这是最常见的问题。排查顺序如下确认BSP的STDOUT是mdm_0。有时候创建BSP时默认是none忘了改。确认程序下载成功。Vitis的Console里应该有Program FPGA成功的提示。确认程序跑到了main。可以在main开头加一个GPIO翻转用示波器看。确认Terminal选的是Vitis Serial Terminal而不是普通Serial Terminal。确认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的输入通道做一个简单的命令行界面在调试阶段动态改参数。这些我都试过确实能省不少时间。
返回列表