这次工作起于一个问题:能不能分析 jtag_hw_usb-blaster.dll,看看它到底支持哪些设备?随后又追到了 USB Blaster III 的实现位置,并将其中适用于普通 FTDI 四线 JTAG 的行为重构进现有 mbftdi 插件,最后整理成 xtftdi 项目。
工作主要完成于 2026 年 11 月 1 日。这篇文章记录排查依据、实现改动和验证结果。当前交付的是 Windows x64 四线 JTAG 预发布版,不是官方 USB Blaster III 全功能等价实现。
先分清下载器支持和 FPGA 器件支持
分析的 jtag_hw_usb-blaster.dll 为 AMD64 文件,版本资源标注为 Quartus Prime Standard 25.1.0.1129。
最初使用 Python、pefile 和 Capstone 检查 PE 结构、导出函数、字符串及代码引用,没有执行原始 DLL。它唯一的导出是 get_supported_hardware,实际只返回三类接口描述:
| 索引 | 接口类型 |
|---|---|
| 0 | USB-Blaster |
| 1 | Cubic Cyclonium |
| 2 | Nios II Evaluation Board |
内部还存在 12 条设备记录,包括重复名称、带 Altera 前缀的名称,以及 Cyclone III、Stratix III 开发板标签。识别路径先比较三个基本设备名称,未命中时再通过 FT_* 接口查询 USB ID,并与表中常量匹配。
这里容易误读:表中的 EP3C25、EP3SL150 等名称是开发板板载 USB-JTAG 接口的标签,不能据此得到“这个 DLL 支持哪些 FPGA 芯片”的完整清单。目标器件支持还涉及上层软件、器件数据库和配置算法。
表中还有一处值得保留原样的异常:Altera Cubic Cyclonium 对应常量是 0x009FB602,并非另一条 Cubic Cyclonium 记录的 0x09FB6002。疑似遗留笔误,但仅凭二进制无法确认原因,因此分析记录没有擅自更正。
usb-blaster-iii.zip 只提供了哪一层?
随后检查了 usb-blaster-iii.zip。这个包包含 INF、CAT 签名目录,以及 x32/x64 的 WDF、WinUSB 安装辅助 DLL,没有 jtag_hw_*.dll 插件。
INF 的关键作用是将指定 USB 设备绑定到 Windows 的 WinUSB.sys,并注册软件可枚举的设备接口 GUID:
{09FB6020-0000-0000-0000-00B0D03D3169}
设备匹配项使用 VID 09FB 和若干 602x PID,多数还限定了复合设备的 MI_00 接口。包中的辅助 DLL 导出的是安装相关函数。
所以,这个 ZIP 解决的是设备绑定与 USB 访问入口。Quartus 如何打开接口、组织 JTAG 命令、处理返回数据,还需要用户态实现。只安装这个驱动包,不能单凭驱动目录的存在就认定某套 Quartus 已具备完整的 Blaster III 支持。
找不到 blaster3 DLL,不代表没有实现
最初把独立文件名 jtag_hw_usb-blaster3.dll 当成了寻找实现的线索。实际检查提供的 Standard 安装目录后,发现 quartus/bin64 中确实没有这个文件。
继续分析 jtagserver.exe,才找到真正的实现位置:
- RTTI 中存在
JTAG_SERVER_FTDI_BLASTER和JTAG_SERVER_FTDI_FUNCTIONS_WIN。 - 程序包含 WinUSB 的初始化、控制传输、读写等调用逻辑。
- 代码实际枚举的接口 GUID 与上述 INF 注册的 GUID 一致。
- 枚举出的设备路径直接进入服务程序内部的 FTDI-Blaster 对象创建和初始化路径。
这套安装文件的 version.txt 标注为 25.1std.0.1129。因此,最终结论应限定为这套实际检查过的文件:Blaster III 相关逻辑编在 JTAG Server 内,独立 blaster3 DLL 的缺失不能证明它不支持。
为了核对调用关系,后续使用 Ghidra 导出相关函数,并结合 RTTI、虚函数表和反汇编交叉检查。这里有一个实用经验:对包含嵌入数据的代码节做一次线性反汇编,可能中途停止。按 PE 异常表中的函数范围分别解码,更适合做后续函数覆盖检查。
与本次移植直接相关的参考位置包括:
| 参考 RVA | 作用 |
|---|---|
0x30096 | FTDI 接口枚举路径 |
0x26C60 | MPSSE TMS/数据命令选择与合并 |
0x34490 | JTAG 时钟分频设置 |
0x347C0 | 数值参数设置 |
0x1B000 | 数值参数读取 |
这些地址仅用于定位参考版本中的实现。新插件不加载参考 EXE,也不依赖这些绝对地址。
移植到现有 mbftdi 插件
现有项目源自 Marsohod MBFTDI JTAG programmer,已经能够通过 D2XX 访问普通 FT2232/FT4232 设备。
这次指定的目标是 FT4232H,只接 TCK、TDI、TDO、TMS;后续硬件测试改用本机连接的 Digilent FT2232H。因此选择保留现有 D2XX 传输层,对照内置实现重构适用的四线 JTAG 行为,而没有将 WinUSB 机器码直接搬进 DLL。
两条路径可以分别理解为:
所分析的内置路径:
Quartus → JTAG Server 内置 FTDI-Blaster → WinUSB → 下载器
xtftdi 路径:
Quartus → JTAG Server 插件接口 → xtftdi DLL → D2XX → FTDI MPSSE
原项目保留的设备标识为 0403:6010、0403:6011。这次没有把它改成官方 09FB:602x WinUSB 设备,也没有修改下载器 EEPROM。
MPSSE 编码与传输
新增的 blaster3_protocol.h 实现了参考行为中的 TMS/数据命令选择:在保持 TDI 不变的 TMS 命令与保持 TMS 的数据命令之间选择,并合并连续整字节传输。
移植过程中保留了之前已经修复的几项关键行为:
- 不为建立 TMS 电平而插入额外时钟。
- 按 Quartus 属性边界处理需要采样和忽略 TDO 的区间。
- 保持
report与pass_data的回调顺序。 - 处理部分读写、零进展、有限超时及失败后的关闭重开。
- 在需要时并行处理 USB IN/OUT,避免大批量双向传输互相阻塞。
尤其是属性边界问题:一次 write_masked 回调可以同时包含忽略 TDO 的头部和需要采样的数据。不能只看回调起点的属性,就将整段都当成不需要返回数据。
接入 Quartus 参数接口
时钟功能还必须接入宿主真正调用的插件槽位。通过分析 JTAG_SERVER_EXTERNAL 的转发函数,确认了 x64 描述结构中的接口位置:
| 描述结构偏移 | 用途 |
|---|---|
0x58 | 数值参数设置 |
0x60 | 数值参数读取 |
0xA8 | 数据/字符串参数设置 |
0xB0 | 数据/字符串参数读取 |
插件接入了 JtagClock、JtagClockAutoAdjust、固定频率控制,以及按长度返回的 SerialNumber。
实际集成测试发现了一个软件模型最初没有覆盖的问题:jtagconfig --setparam 返回成功,但下一条命令重新打开接口后,时钟恢复默认值。内置对象和插件会话的生命周期不同,参数不能只存在一次打开产生的对象里。
修复后,参数按插件端口保留在 DLL 的内存状态中,跨接口关闭、重开仍然有效;服务退出后则重新读取配置文件。这项行为又单独加入了回归测试。USB 重排与热插拔时的端口身份行为尚未完成验证。
自动校准
新增的 blaster3_calibration.h 采用低速基准、链延迟与图样比较的方式进行时钟资格检查:
- 在低速下重复检查 IR 长度与捕获结果。
- 检查 BYPASS 延迟。
- 通过 1024 位图样验证移位返回结果。
- 检查目标频率,失败时沿普通频率档位向下降低。
校准期间产生的采样数据只供内部判断使用,不交给 Quartus 的业务 TDO 回调。
这是参考行为的可移植重构,不是原始训练算法所有分支的逐项复制;原实现的另一组激进频率搜索分支没有移植。校准会改变 TAP 状态,显式校准应在操作之间执行,并随后重新扫描链路。
JTAG 时钟怎么配置
在实际运行的 jtagserver.exe 同目录放置 mbftdi.cfg:
channel=0
frequency=15000000
auto_adjust=1
frequency 单位是 Hz。新版默认请求 15 MHz,auto_adjust=1 允许检查后降频;需要固定时钟时设为 0。修改文件后重启对应的 JTAG Server。
运行期间可以设置第 1 个下载器:
jtagconfig --setparam 1 JtagClockAutoAdjust 0
jtagconfig --setparam 1 JtagClock 10M
jtagconfig --getparam 1 JtagClock
返回的是整数分频后的实际频率。例如使用 60 MHz 源、按 30 MHz / (divisor + 1) 分频时,请求 12 MHz 会得到 10 MHz,而不是任意连续可调的 12 MHz。
多版本 Quartus 共存时,要先确定当前运行的是哪个 JTAG Server。把 DLL 或配置文件放进另一套安装目录,不会自动改变当前服务的行为。
验证结果:软件模型与实板分开记录
软件回归使用独立 MPSSE 解释器和 TAP 模型,包括 120 组随机混合任务、百万时钟传输、TDO 属性边界、回调顺序、部分读写和故障处理。
新增测试还覆盖两个模拟器件的 IR/BYPASS 链,以及注入与频率有关的错误后,选择 15 MHz、10 MHz 或 100 kHz 的情况。软件测试通过只说明对应模型中的行为正确。
实板使用 Digilent FT2232H 通道 A,得到以下结果:
| 请求 TCK | 连续读链次数 | IDCODE |
|---|---|---|
| 1 MHz | 3 | 02B220DD,一致 |
| 5 MHz | 3 | 02B220DD,一致 |
| 10 MHz | 3 | 02B220DD,一致 |
| 15 MHz | 3 | 02B220DD,一致 |
自动校准测得 IR=10、BYPASS=1,并通过了 15 MHz 的移位检查。
随后分别运行隔离的 Quartus 18.1 和 Standard 25.1.0.1129 JTAG Server,加载同一个新版 DLL。两者的 jtagconfig --debug 均读到:
Captured DR after reset = (02B220DD) [32]
Captured IR after reset = (155) [10]
Captured Bypass after reset = (0) [1]
Captured Bypass chain = (0) [1]
将 JtagClock 改为 10M、关闭自动校准后,后续独立命令仍读回 10M,链扫描也报告 10 MHz。
这些测试使用临时服务目录、私有配置文件和独立端口,测试后退出服务,没有替换已安装的 Quartus 插件,也没有下载 FPGA 配置或写入 Flash、EEPROM。
本次发布 DLL 的 SHA256 为:
889f4d95b87046f12a5a3684d78274c9e91cd0d5edb761cdc3cb05d58823dd31
测试记录按这个哈希关联。文件名相同,不代表可以套用另一个版本的验证结果。
交付与仍未完成的部分
项目已整理到 mediskyseek/xtftdi,首个版本为 v3.0.0-preview.1。仓库目前是私有的,查看源码或下载 Release 需要相应权限。
仓库上传了 150 个源码和文档文件,排除了历史预编译库、目标文件及本地构建缓存。DLL 放在 Release 中,同时提供 ZIP 和 SHA256 校验值。上游许可证及来源说明均保留;干净源码快照已重新构建并通过软件回归测试。
Windows x64 构建入口是:
python build_x64.py
它使用 VS2022 C++ Build Tools v143 和 Windows SDK,输出到 dist/blaster3_x64/。构建脚本本身不安装插件、不访问硬件。
当前仍有明确边界:
- FT4232H 的物理验证尚未完成,不能把 FT2232H 实测直接当成 FT4232H 实测。
- 新 DLL 的 FPGA 配置下载、System Console 内存事务、AS、耐久和热插拔恢复尚未验证。
- 官方专用 WinUSB/TCP 传输、内部 EEPROM 协议、RGB/辅助控制脚及其他采样模式未完整移植。
Identify、InternalEepromContents等未实现能力返回“不支持”,没有映射成任意 GPIO 操作或 FTDI 配置 EEPROM 写入。
这次得到的是一个已经通过软件回归和实板读链测试、可以由两代 Quartus JTAG Server 调用的四线 FTDI JTAG 插件预发布版。后续验证应继续围绕具体操作和具体 DLL 哈希展开,不能将读链成功扩大解释为全部功能或长期稳定性已经成立。