简介:JESD204B协议的串行接收模块代码包,面向FPGA开发者和高速接口调试人员,基于Vivado环境提供完整可仿真的工程方案。JESD204B是面向高速数据转换器与FPGA的串行接口标准,多通道对齐与确定性时延特性在接收链路中尤为关键。模块覆盖8b/10b解码、多lane同步、错误检测与时钟恢复等核心逻辑,适用于ADC/DAC与FPGA互连场景下的协议验证与二次开发。压缩包共593个文件,约26.68MB,其中txt说明文档272个,涵盖协议要点与工程说明;v源码51个,实现接收链路核心逻辑;xdc约束、do仿真脚本与tcl脚本则分别负责引脚时序约束、自动化仿真与流程控制,另有xci配置、dcp综合结果等,构成从编译、约束到仿真的完整工程链。已有2338人学习下载。通过该工程可掌握JESD204B接收端设计方法,灵活调整lane数与速率适配不同系统;配套仿真激励和波形配置便于快速定位同步偏差、校验失败等问题,适合负责高速串行接口落地的中高级工程师参考。
1. JESD204B 串行接收模块:为什么它比发送端更容易让人翻车
做高速数据采集的工程师,第一次接触 JESD204B 接收链路时,多半会经历三个阶段:先是觉得协议分层清楚、文档齐全,应该不难;接着面对 GT 收发器、SYSREF、ILAS 这几座大山,开始怀疑人生;最后是在仿真里一切都对,上板之后误码率却居高不下,只能拿着 ILA 逐个信号去对。这个标题里说的「串行接收模块」,恰恰是整个 JESD204B 链路中最容易出问题的半边:发送端的数据是自己组织的,时序上可控;接收端要恢复时钟、找对齐、解扰、拼字节,任何一个环节没站稳,后面全是乱码。这篇文章想把接收模块从协议骨架到 Vivado 实现、再到仿真验证和上板排障的做法一次讲透,适合已经在用 GT 收发器、但被 JESD204B 链路层初始化折腾过的工程师,也适合准备从 LVDS 接口切换到 JESD204B、想先弄清楚接收侧要具备哪些条件再动手的团队。
2. 接收链路协议基础:从 SYSREF 到 ILAS,初始化过程拆解
2.1 三层的职责划分:物理层、数据链路层、传输层各管什么
JESD204B 协议把链路分成物理层、数据链路层和传输层,接收模块的代码虽然通常只写在 FPGA 逻辑里,但你必须清楚每一层的边界在哪里,否则排障时永远找不对方向。
物理层负责串行数据的收发,具体到 FPGA 里就是 GT 收发器的工作:CDR 从串行比特流里恢复出采样时钟和字节边界,完成串并转换,然后交给上层。Lane 速率由 ADC/DAC 和 FPGA 之间的线速率决定,这一层的参数主要在 GT 的 IP 里配置,JESD204B IP 本身不直接管它。
数据链路层是接收模块的核心战场,包含 8B10B 解码、码组同步(Code Group Sync)、帧同步、多帧同步、通道对齐和加解扰。协议规定链路初始化必须先完成码组同步,再建立帧同步,然后通过 ILAS(Initial Lane Alignment Sequence)完成多帧同步和参数交换,最后才进入数据阶段。8B10B 解码在 GT 里通常已经做好,但码组同步的状态机一般由 JESD204B IP 或者你自己写的逻辑来承担,这一层最麻烦的是各种同步丢失后的重同步条件。
传输层关心的是字节怎么拼接成样本,以及样本怎么分配给各个转换器。接收模块拿到解扰后的字节流,需要按参数 L、F、S、N 把这些字节重新组织成语样本,再输出给后端的 FIFO 或 DMA。新手上板遇到数据错位,十有八九不是传输层的组装逻辑写错,而是上层链路层的对齐就没建立起来。
物理层问题通常在 GT 层面解决,传输层问题通常在数据通路里解决,中间的链路层才是 JESD204B 接收模块要花大力气写状态机的部分。调试时如果看到链路状态停在一个阶段不动,先用这个分层判断问题大概在哪一层,再决定去翻 GT 的 status 寄存器还是去看 ILAS 的 K 字符。
2.2 RX 初始化状态机:码组同步、帧同步、ILAS 三个阶段的推进条件
接收端的初始化状态机是按协议 4.10 节的流程推进的,Vivado 的 JESD204B IP 已经把整套状态机封装好了,但用 IP 不等于你可以不懂流程。因为你迟早会遇到需要自己写一个简化接收控制器、或者要手工触发复位重同步的场景。
整个流程从 RX 拉低 SYNC 信号开始。发送端看到 SYNC 拉低后,会连续发送 /K/ 字符(K28.5),接收端在数据流里持续检测到 /K/ 字符,就进入码组同步状态。这个阶段要盯住两个条件:一是 /K/ 字符连续出现的数量,二是每次 /K/ 字符出现的位置,也就是字节边界是否已经对齐。
帧同步建立在码组同步之后。协议规定一个帧里第一个字节必须是 /K/ 字符,后续字节是配置数据。接收端通过检测多个帧的起始位置来确认帧边界。帧同步建立后,就开始检测 ILAS。ILAS 包含 4 个多帧,每个多帧以 /R/ 字符开始(K28.0),中间是 14 个配置字和一个 /A/ 字符(K28.3),多帧之间以 /F/ 字符(K28.7)分隔。
ILAS 里的配置字包含了 L、F、S、N、N'、HD、JESDV 等参数,接收端就是靠这些字来确认收发双方的配置是否一致。如果你在调试时发现链路一直停在 ILAS 阶段回不到数据态,优先检查这些配置字是不是和发送端对得上,尤其是 L 和 F 两个参数,这个我后文在踩坑章节里会展开。
完成 ILAS 检测后,接收端拉高 SYNC,进入数据阶段。此时接收到的数据按帧格式解析,如果出现误码或者同步丢失,接收端需要重新拉低 SYNC 进行一次完整的重同步流程。这个回落条件在 IP 里常见的是连续的误码计数超过门限,自己写状态机时千万不要省掉这个计数逻辑,否则链路在噪声环境下会长时间输出脏数据而不自知。
2.3 确定性延迟和 LMFC:接收端最容易忽略的主干
JESD204B 相对早期版本最大的进步就是确定性延迟,核心机制是 LMFC(Local Multi-Frame Clock)。简单理解:LMFC 是一个固定的多帧周期时钟,所有数据帧的边界都对齐到 LMFC 上,RBD(Receive Buffer Delay)规定接收端在收到 ILAS 对齐信号后,要等到特定的 LMFC 边界才释放数据,这样无论上电多少次,数据从 ADC 到 FPGA 的延迟都是确定的。
在接收模块里,LMFC 的生成通常由 SYSREF 触发,SYSREF 是一个比 LMFC 低得多的周期脉冲,接收端的 JESD204B IP 用它来完成 LMFC 的对齐。如果你的系统不要求确定性延迟,理论上可以不用 SYSREF,但我不建议这么做。原因很简单:不用 SYSREF 时,LMFC 的相位在上电后是未知的,链路虽然能正常工作,但你永远不知道数据到底延迟了多少周期,后续做多通道同步或者系统校准的时候会非常被动。
实现层面,接收模块要处理三类延迟:固定延迟(线缆和收发器带来的)、可变延迟(CDR 和时钟相位带来的)、链路延迟(SYSREF 对齐后定义的)。接收端的 buffer 就是为了消化可变延迟而存在的。仿真时你可以把 SYSREF 做得非常理想,但上板之后 SYSREF 的时序、skew、毛刺都会真实存在,所以我在实际项目里一定会把 SYSREF 的源、PCB 走线长度、FPGA 的 buffer 位置一并检查清楚。SYSREF 没接好,接收模块代码写得再对也无济于事。
3. 在 Vivado 里搭接收模块:IP 配置、代码骨架与仿真
3.1 JESD204B RX IP 的关键参数和选型依据
Vivado 提供的 JESD204B IP(Xilinx JESD204B LogiCORE IP)是接收模块最常见的落地点,它的 RX 部分支持 1~8 条 lane,线速率上限取决于所选 FPGA 的 GT 能力,通常在 12.5Gbps 左右(具体按你手里器件的 GT 规格来定)。配置 IP 时,Lanes、F、K、S 这几个参数必须与发送端严格一致,只是接收端相对宽容一点,因为传输层的样本条数 S 只影响你输出数据的组织形式,不影响链路能否建立。
典型的配置组合,数据采集场景常用 L=4、F=1、K=32,也就是 4 条 lane、每帧 1 个字节、每个多帧 32 帧;而一些高速 ADC 会用 L=8、F=2。选择这些参数的依据是线速率和时钟频率的平衡:F 越大,帧时钟越低,但每条 lane 承载的有效字节数也越多,帧内结构越复杂。作为接收模块的设计者,你通常没有选择权,因为 ADC 侧的参数已经定死了,你要做的是准确理解这些参数怎么影响你的接收数据格式。
我自己做接收模块的习惯是:先到 ADC 的数据手册里查清楚它的 JESD204B 输出参数表,把 L、F、K、S、N、N'、CS、CF 这些值全部抄出来,再逐一对照到 IP 配置界面里。这一步千万别凭印象填,曾经遇到过某人把 F 值填错导致 ILAS 里配置字对不上,链路初始化永远成功的概率极低,问题是无法直接看到的,只能靠仿真波形去推测。
其余的几个关键配置项:外部 SYSREF 模式选择需要确定 SYSREF 是单次还是周期性触发、逻辑电平、是否在 GT 的 buffer 里打一拍;时钟方案里 RX 侧的 core clock 由 GT 恢复时钟提供还是由外部时钟提供,这直接关系到你在约束文件里怎么写时钟关系。
3.2 最小接收模块代码骨架:接口、复位、数据输出
用 IP 之后,你的接收模块代码主要是在 IP 外面包一层接口逻辑。下面给出一个最小可用的例化骨架,包含异步复位同步释放、IP 例化和简单的数据打拍输出,这是我在多个 JESD204B 接收项目里反复使用的结构。
module jesd204b_rx_top ( input wire rx_refclk_p, // GT 参考时钟差分对 input wire rx_refclk_n, input wire [3:0] rx_serial_p, // 4 lane 串行输入 input wire [3:0] rx_serial_n, input wire sysref_i, // SYSREF 脉冲输入 output wire sync_o, // 请求发送端重同步 output wire [31:0] rx_data_out, // 4 lane 解扰后的采样数据 output wire rx_valid_out, // 数据有效指示 input wire aresetn // 全局异步复位 ); wire rx_core_clk; wire rx_reset_done; wire [3:0] rx_axis_tdata; wire rx_axis_tvalid; wire rx_axis_tready; // 异步复位同步释放,保证 IP 的复位时序满足要求 reg [3:0] sync_reset_n = 4'h0; always @(posedge rx_core_clk or negedge aresetn) begin if (!aresetn) sync_reset_n <= 4'h0; else sync_reset_n <= {sync_reset_n[2:0], 1'b1}; end wire ip_reset_n = sync_reset_n[3]; // JESD204B RX IP 例化 jesd204b_rx_ip u_rx_ip ( .aresetn (ip_reset_n), .sysref (sysref_i), .rx_serial_p (rx_serial_p), .rx_serial_n (rx_serial_n), .rx_gt_refclk_p (rx_refclk_p), .rx_gt_refclk_n (rx_refclk_n), .sync (sync_o), .rx_core_clk (rx_core_clk), .rx_tdata (rx_axis_tdata), .rx_tvalid (rx_axis_tvalid), .rx_tready (rx_axis_tready), .rx_reset_done (rx_reset_done) ); // 数据打拍,避免跨时钟域采样问题 reg [31:0] data_r; reg valid_r; always @(posedge rx_core_clk or negedge ip_reset_n) begin if (!ip_reset_n) begin data_r <= 32'd0; valid_r <= 1'b0; end else begin if (rx_axis_tready && rx_axis_tvalid) begin data_r <= rx_axis_tdata; valid_r <= 1'b1; end else begin valid_r <= 1'b0; end end end assign rx_data_out = data_r; assign rx_valid_out = valid_r; endmodule代码里最关键的是复位处理。JESD204B IP 对复位的释放时机有要求,特别是 GT 部分的复位必须在参考时钟稳定之后释放,逻辑部分的复位必须在 GT 完成复位之后释放。这里用了简单的移位寄存器做异步复位同步释放,实际项目里更严谨的做法是把它和 GT 的 tx/rx reset done 信号做逻辑与。如果你的复位时序不合适,最常见的现象是:仿真里链路正常,上板后 SYNC 一直拉低、初始化永远不完成。
另一个需要注意的地方是 rx_axis_tready 的处理。这里直接绑成常高,在数据持续输出的场景下没问题;如果后端接入的是 FIFO 或 DMA,需要根据背压信号动态控制 tready,否则可能出现数据覆盖。tready 拉低的时序如果处理不好,会让 IP 内部的状态机产生不确定的数据停顿,具体表现为偶发的单字节错位。
3.3 带仿真跑通:testbench 激励与断言检查
Vivado 自带的仿真器其实够用,但你需要一个能模拟发送端的 testbench,否则接收模块只能反复进行复位-重同步的过程,链路永远建立不起来。常见的做法是例化一个 JESD204B TX IP 来和 RX 对打,但 TX IP 同样需要配置、复位、SYSREF,整套跑起来比较重。如果你只是验证 RX IP 的初始化流程和数据输出,我建议先写一个行为级的发送激励模型,用 generate 循环生成 K 字符和配置字。
下面给出一个简化的激励模型核心部分,它在收到 SYNC 拉低后发送 K28.5,完成码组同步后按 ILAS 格式发送 4 个多帧,最后进入数据模式发送递增字节序列。
// 简化发送激励:模拟 JESD204B 发送端行为 reg [7:0] tx_data [3:0]; reg tx_charisk [3:0]; localparam K28_5 = 8'hBC; // /K/ 码组同步字符 localparam K28_0 = 8'h1C; // /R/ 多帧起始 localparam K28_3 = 8'h7C; // /A/ 对齐字符 localparam K28_7 = 8'hFC; // /F/ 多帧结束 task send_kchars(input integer count); repeat(count) begin for (int i = 0; i < 4; i++) begin tx_data[i] = K28_5; tx_charisk[i] = 1'b1; end end endtask task send_ilas(); // 4 个多帧,每个多帧 32 个字节,首字节 /R/,尾字节 /F/ for (int m = 0; m < 4; m++) begin tx_data[0] = K28_0; tx_charisk[0] = 1'b1; // 中间填充配置字,这里用递增字节代替 for (int b = 1; b < 31; b++) begin tx_data[b%4] = b[7:0]; tx_charisk[b%4] = 1'b0; end tx_data[3] = K28_7; tx_charisk[3] = 1'b1; end endtasktask 里 send_kchars 用来模拟码组同步阶段发送端连续发送的 /K/ 字符,send_ilas 是简化版的 ILAS 序列。真实场景里 ILAS 中间填充的是协议规定的配置参数,这里写成递增字节只是为了让仿真能跑起来。需要说明的是,这个行为模型没有模拟 GT 的串行化过程,数据是并行送到 RX IP 的数字侧的,因此它只能验证链路初始化逻辑,不能替代上板测试。
testbench 里最值得花时间写的是断言检查。断言的目标并不是保证功能正确,而是在功能出错时能第一时间告诉你哪里错。常用的检查点有三个:一是链路初始化完成后 sync 信号应该拉高;二是 ILAS 之后第一个数据帧的起始位置是否符合预期;三是连续数据输出时有没有缺字节。下面给出一个简单的$error断言写法。
// 等待 sync 拉高,超时则报错 initial begin wait (sync_o === 1'b1); $info("SYNC asserted at time %t", $time); end // 检查数据输出连续性 reg [31:0] expected; always @(posedge rx_core_clk) begin if (rx_axis_tvalid && rx_axis_tready) begin if (expected != 32'h0 && rx_axis_tdata != expected) $error("Data mismatch at %t: exp %h got %h", $time, expected, rx_axis_tdata); expected <= expected + 32'h1; end end断言代码的核心价值是缩短排障时间。没有断言的时候,你要在波形窗口里手动找第一个错误点,而错误点往往发生在几千个周期之前,等你翻到那里,很多中间状态已经丢失了。自动断言能在错误发生的第一时间停下来,配合 Vivado 仿真器的 break on error 功能,效率会明显提升。
仿真通过不等于上板能跑,这个结论在 JESD204B 上尤其明显。原因在于仿真模型对 GT 的 CDR、接收检测(RxDetect)和通道间 skew 都做了理想化处理。所以仿真阶段要重点验证的是逻辑时序和状态机流转,而不是物理层行为。
4. 仿真验证要做到什么程度:激励设计、ILAS 模拟、误码检查
4.1 怎么构造一个可用的发送激励端
很多工程师用 JESD204B IP 自带的 example design 做仿真,跑通了就以为万事大吉。其实 example design 里自带的发送激励是为了让 IP 能演示完整的链路建立过程,它验证的是「IP 自己的发送端能把数据送到自己的接收端」,并不能代表你的 ADC 或另一颗 FPGA 的行为。真正要验证接收模块的边界条件,需要自己构造接近真实场景的激励。
构造发送端激励时,核心要模拟的是三个行为:SYNC 拉低后的 /K/ 字符连续发送、ILAS 配置字的可配置性、数据阶段的字节流内容。其中 ILAS 配置字是最容易写错也最值得模拟的部分,因为接收端 IP 在 ILAS 阶段会校验 L、F、K、S 等参数,一旦校验失败,链路就会重新回到码组同步阶段,看起来像是卡住了,实际是参数没对上。
我习惯把 ILAS 的配置字做成 task 的参数,在 testbench 里允许通过宏参数覆盖。这样当你要验证接收端在参数不匹配时的行为时,不需要改源码,直接改宏定义就行。这个习惯帮我在某个采集项目里快速定位过一次问题,当时是某数据采集卡换了 ADC 批次,参数从 L=4 变成了 L=2,但接收模块配置忘了更新,仿真里第一时间就暴露出来了。
`define ILAS_L 4'd4 `define ILAS_F 8'd1 `define ILAS_K 8'd32 task send_ilas_full(); // 通过宏参数生成 ILAS 配置字 tx_data[0] = K28_0; tx_charisk[0] = 1'b1; tx_data[1] = `ILAS_L; tx_charisk[1] = 1'b0; // L 参数 tx_data[2] = `ILAS_F; tx_charisk[2] = 1'b0; // F 参数 tx_data[3] = `ILAS_K; tx_charisk[3] = 1'b0; // K 参数 // 其余配置字按协议顺序填充 endtask这段代码用宏配置 L、F、K,好处是当你并行验证多种配置组合时,只需要在 testbench 里切换宏定义。真实的 ILAS 配置字长度和位置在协议里是按字节序号固定的,我这里只展示 L、F、K 的写法,具体工程里需要把 JESDV、S、N、N'、CS、CF、HD 全部填完整,否则接收端 IP 仍然会判定配置不一致。
有的工程师会问:只在 example design 上跑仿真,不构造配置字,链路不也能建立吗?能,但那只证明 IP 在配套参数下能工作,不能证明你的接收数据通路在后端不同配置下仍然正确。比如你改了 F 值,数据拼接方式就变了,如果不重新仿真并检查输出数据的字节序,上板之后就很容易出现看似正确但实际字节错位的现象。
4.2 断言与自动检查:不靠肉眼看波形
前面提到断言能缩短排障时间,这一节展开讲 JESD204B RX 验证里最值得写的几个断言类型。
第一类是状态机流转断言。链路初始化的状态变化(码组同步 → 帧同步 → ILAS → 数据态)可以用 property 检查:ILAS 结束之后不能直接跳回码组同步,除非有复位或者误码超门限触发。这类断言能抓住配置字错误导致的隐性重同步问题。
第二类是数据通路断言。接收端在数据阶段输出的字节顺序必须严格按 L/F 参数排列,比如 L=4、F=1 时每帧从 lane 0 到 lane 3 依次输出。如果你修改了 N 值,样本的位宽会变化,但字节顺序仍然要遵守协议,这时用断言把输出字节和原始数据序列做逐字节比较,错误会在几个周期内被发现。
property p_data_order; @(posedge rx_core_clk) disable iff (!ip_reset_n) rx_axis_tvalid |=> rx_axis_tdata == $past(rx_axis_tdata) + 8'h1; endproperty assert property (p_data_order) else $error("Data sequence broken at time %t", $time);上面这段 property 检查相邻两个有效数据的差值是否为 1,适用于发送端是连续递增序列的场景。如果你的发送端不是递增序列,就需要一个参考模型或者 shadow memory,写一个与发送端相同的生成逻辑,在接收端做逐拍比较。
第三类是复位行为断言。复位释放后,一段合理时间(比如 20000 周期)内链路必须完成初始化并进入数据态。这个断言在回归测试里极有用,每次改完代码跑一遍,能快速确认没有引入新的状态机死锁。
我用这套断言体系跑回归的经验是:仿真时长的分配上,链路初始化和复位行为占 30%,数据通路正确性占 50%,边界和异常占 20%。JESD204B 接收模块最怕的不是常态数据错,而是某个配置组合下偶发的一帧错位,这往往要到长时间仿真打完误码统计才能暴露。
4.3 参数化仿真:换 lane 数、换 L/F 值时改哪些地方
项目后期你大概率会面临参数调整:ADC 换了、通道数变了、线速率的约束变了。每次调整都要重新配置 IP,这是不可省的,但仿真环境应该做到尽量少的改动就能覆盖新配置。
参数化仿真的关键是让 testbench 里的发送激励、参考模型和断言全部跟随宏定义变化。比如 lane 数从 4 变成 8,那么发送激励里的并行数据位宽、ILAS 的 lane 映射、断言里的数据顺序都要相应调整。只改 IP 配置而不改 testbench,会导致仿真结果与实际配置不一致,而这个不一致往往以「仿真通过、上板失败」的方式暴露。
`define NUM_LANES 4 `define FRAME_BYTES (`NUM_LANES * 8) // 发送激励按 lane 数参数化 for (int i = 0; i < `NUM_LANES; i++) begin tx_data[i] = data_byte_base + i; tx_charisk[i] = (i == 0) ? 1'b1 : 1'b0; end参数化做得比较好的测试平台,更换 L/F 参数之后只需要重跑仿真,断言会告诉你哪些地方需要同步修改,而不是让你自己去找。我自己在维护多个 JESD204B 接收项目时,专门抽时间把 testbench 整理成了模板,每次新开项目先复制模板再改参数,省下大量时间。
另一个值得注意的点是 SYSREF 的行为也要参数化。单次 SYSREF 和周期性 SYSREF 会直接影响 LMFC 的对齐行为,仿真里建议至少覆盖两种模式:一种是理想 SYSREF(固定相位、无抖动),用于验证正常流程;另一种是加入一个可变的 phase 偏移,用于验证接收端的 buffer 是否能吸收 SYSREF 的相位变化。第二种模式对确定接收端 buffer 深度是否足够很有帮助,如果 buffer 深度设计过浅,SYSREF phase 偏移到某个值时就会出现数据断裂,这正是你上板前最想发现的问题。
仿真验证做到这个程度,基本可以保证接收模块的逻辑功能是正确的。但必须再次提醒:仿真永远验证不了物理层的眼图、CDR 抖动和通道间 skew,这些只能靠板级测试来兜底。
5. JESD204B 接收模块的五个典型踩坑与排查方法
5.1 现象:SYSREF 没接,输出数据每次都不同
曾经处理过某图像处理 Demo 的接收链路,ADC 输出的 JESD204B 数据在 FPGA 端每次上电后第一个样本的位置都不同,导致整个采集序列对不齐。刚开始怀疑是复位时序问题,反复调整复位电路都没用,最后排查到原理图,发现板子上根本没有 SYSREF 布线,ADC 的 SYSREF 引脚悬空。
原因分析:没有 SYSREF 时 LMFC 的相位完全随机,而接收端的确定性延迟机制依赖 SYSREF 来对齐 LMFC,相位随机意味着数据进入 buffer 的时机每次都不同,输出的初始位置自然也不确定。这不是逻辑 bug,而是系统设计缺陷。
解决方式有两个层面。方案层面,如果硬件已经定了,可以尝试用 FPGA 内部逻辑生成一个周期性的 SYSREF 替代外部输入,Vivado 的 JESD204B IP 支持这种模式,但不一定完全等价,因为内部生成的 SYSREF 无法和 ADC 的本地帧时钟做真正的相位对齐。长远的方案是修改硬件,把 SYSREF 走线补上,并保证 SYSREF 与参考时钟的时序关系满足器件手册要求。这个案例提醒我,接收模块调试的第一步永远是先确认系统时钟和 SYSREF 方案,而不是急着看波形。
5.2 现象:复位后 GT 一直没锁定,link 状态卡死
另一个项目里,接收模块上板后 SYNC 信号一直为低,发送端持续发送 /K/ 字符,但接收端的链路状态一直停留在物理层未就绪。从 IP 的 status 寄存器看,rx_reset_done 一直没有拉高,这说明 GT 的复位过程没有正常完成。
原因分析:GT 的复位必须满足严格的时序要求,参考时钟必须先稳定,然后释放 GT 的 reset,等待 tx_reset_done/rx_reset_done 拉高后,才能释放逻辑部分的复位。当时的问题是逻辑复位和 GT 复位同时释放,导致 GT 还处于复位状态时逻辑就开始等待 rx_reset_done,时序完全错乱。
解决方式是按顺序做分级复位:第一阶段等参考时钟锁定(refclk locked),第二阶段释放 GT reset,等 rx_reset_done;第三阶段释放核心逻辑复位。代码骨架里我写的移位寄存器同步释放只能保证异步信号同步化,真正的分级时序要额外设计。排查这一类问题时,建议直接看 IP 自带的 example design 复位顺序,那是一个可靠的参照。
5.3 现象:仿真 ILAS 阶段卡住,进不了 DATA 态
在某个实验室内部项目里,A 同学跑仿真时发现接收链路执行完码组同步和帧同步后,ILAS 状态一直不退出。波形上看,ILAS 的 /R/ 和 /F/ 字符都出现了,但状态机始终在 ILAS_CHECK 里循环。
原因分析:都是 ILAS 配置字内容与接收端 IP 配置不一致导致的。ILAS 里携带的 L、F、K、S 参数,只要有一个值和 IP 配置界面不一样,接收端就会判定链路参数不匹配,然后回到码组同步阶段重新来过。仿真里表现为 ILAS 状态循环,实际上链路根本没有完成初始化。
解决方式是把 IP 配置界面里的参数逐一与 ILAS 配置字进行对照。推荐直接在 testbench 里用$display打印 ILAS 的完整配置字,和 IP 的参数表做自动比对。这个坑在真实板卡上同样会发生,只是仿真里看起来更明显。如果你是用 FPGA 自己实现接收端而不是用 IP,ILAS 的解析逻辑要特别注意配置字是 16 位还是 12 位有效,协议定义的高位保留位不要参与比较。
5.4 现象:多 lane 数据错位,单 lane 却正常
某项目里接收端在单 lane 模式下数据正确,扩展到 4 lane 后,数据出现周期性错位,每隔几个样本就有一次字节交换。从 ILA 抓取的数据看,每个 lane 的数据单独看都是对的,但 lane 之间拼接时顺序乱了。
原因分析:JESD204B 规范要求多 lane 在进入数据阶段之前,所有 lane 的数据必须在 SYSREF 对齐的 LMFC 边界完成对齐。如果接收端只做了帧同步,没有做通道间对齐,或者对齐逻辑里少了 deskew 的延时补偿,就会出现这种每个 lane 独立正确、合并后错位的现象。另外还有一个容易被忽视的因素:GT 的通道极性配置错误会导致 lane 数据取反,某些误码测试中会被错当成错位处理。
解决方式是检查 IP 配置里 deskew 相关参数,确认接收 buffer 深度足够,并检查每条 lane 的接收极性。排查手段是在仿真和实测中分别对每一条 lane 注入 K 字符,确认所有 lane 能在同一时刻检测到 K 字符,如果检测时刻不一致,就说明通道间对齐没有完全生效。L-F 参数的变化会影响对齐的粒度,F 越大,可对齐的边界越少,对 SYSREF 的质量要求越高,这一点在链路预算分析时要提前考虑进去。
5.5 现象:仿真通过,上板之后误码率降不下来
最棘手的踩坑案例往往不是逻辑错误,而是物理层劣化。有一个做高速数据采集的测试场景,接收模块仿真完全正常,上板后误码率在 10^-6 量级波动,IBERT 测试眼图正常,但 JESD204B 链路就是有零星误码。
原因分析:这类问题通常是多个弱因素叠加。GT 参考时钟的抖动超标、电源纹波偏大、SYSREF 走线引入的噪声、板上串扰,单独每一项都可能在误码率测试的临界点上造成偶发错误。JESD204B 的高线速率对信号完整性极度敏感,而仿真完全覆盖不了这条链路。
解决方式是把问题拆开做二分定位。先用 GT 自带的误码测试功能测物理层误码率,如果物理层没问题,再测链路层;如果物理层误码高,就要查电源、时钟和 PCB 走线。另一个有效手段是降低 lane rate 测试,确认是否是速率相关的信号完整性问题。某次定位到最后是参考时钟扇出 buffer 的电源滤波电容布局不合理,换了个容值组合后误码率恢复到了 10^-12 以下。这类问题需要系统地做链路预算,不要指望靠修改逻辑代码来解决。
6. 进阶验证手段:用复位一致性测试确认确定性延迟
6.1 多次复位下的相位记录方法
确定 L、F、SYSREF 都正确之后,你还需要验证一个关键指标:确定性延迟是否真的「确定」。验证方法并不复杂:设计一个测试流程,反复进行复位和链路重新初始化,记录每次初始化完成后输出数据的第一个有效样本的相位,累计若干次后检查这些相位是否一致。
记录相位的方法是在数据输出端放一个计数器和一个采样寄存器。每次链路进入数据态时,将计数器的值捕获下来,连续复位 100 次,如果捕获值每次都相同,说明确定性延迟成立。如果捕获值在少数几个值之间跳变,说明某个延迟环节没有完全锁定,最常见的元凶是 SYSREF 和参考时钟的相位关系不稳定,或者接收 buffer 深度不够。
// 数据态相位捕获逻辑 reg [15:0] phase_cnt; reg [15:0] phase_captured; reg link_in_data; always @(posedge rx_core_clk or negedge ip_reset_n) begin if (!ip_reset_n) begin phase_cnt <= 16'd0; phase_captured <= 16'd0; end else begin if (link_in_data && !phase_captured_valid) begin phase_captured <= phase_cnt; phase_captured_valid <= 1'b1; end phase_cnt <= phase_cnt + 1'b1; end end这段代码在每次链路进入数据态后的第一个有效数据周期捕获相位计数器的值,你可以通过读取这个寄存器的值来判断每次初始化的相位一致性。注意需要在复位流程中自动记录并比较,而不是手动记录到纸上,否则 100 次复位的工作量会让人崩溃。
6.2 把误码计数器和状态监视做成一个子系统
上板调试时我不喜欢频繁改动代码,而是在接收模块里预先放一个调试子系统:一个误码计数器、一个链路状态寄存器、一个触发捕获逻辑。误码计数器维护在数据阶段,对断言逻辑已经识别出的错位、缺字节、CRC 失败进行计数,通过 AXI-Lite 接口读回。链路状态寄存器把当前处于哪个初始化阶段映射成二进制码,让软件层随时能读到。
误码计数器的设计要点是区分「首次误码」和「持续误码」。首次误码触发后,把当前的帧计数器和字计数器快照保存,这样你可以精确知道错误发生在哪个帧的哪个字节位置。持续误码则记录累计值用于统计。就这个习惯而言,让我在多个项目里最快地定位了问题,有时软件工程师反馈的问题其实在硬件层几百微秒前就已经发生了。
6.3 我的习惯
我做 JESD204B 接收模块这几年最深刻的体会是:这个协议不怕代码难写,最怕验证体系不完整。代码可能只有几百行,但排障的时间往往是以天为单位的,主要时间消耗在物理层、时钟、复位这类「看起来不是代码问题」的地方。现在我每接一个新项目,第一步永远是先把 SYSREF、参考时钟、复位时序这三件事确认完,再开始写代码;第二步是仿真环境里强制加断言,宁可多写几十行断言也不愿在波形里人肉找错;第三步上板前先跑一遍复位一致性测试,确认确定性延迟成立后再做正常功能调试。这套流程帮我避开了大部分 JESD204B 接收端的暗坑。如果你正卡在某个链路初始化问题上,希望这篇文章能让你少走一些弯路,也希望你能把这些验证手段带进自己的项目里,让 JESD204B 接收模块真正变成一个可靠的部分,希望帮到你。
本文还有配套的精品资源,点击获取