FPGA SelectIO IP核配置、XDC约束与tap扫描避坑指南
简介:面向 Xilinx FPGA 接口设计者的一份 SelectIO IP 核配置参考文档,围绕 SelectIO Interface Wizard v5.1(PG070)展开,适合已具备基本 Vivado 使用经验、需要为网络协议类接口定制 I/O 电气标准与数据通路的工程师查阅。压缩包内仅 1 个 PDF 文件,约 1.11MB,篇幅紧凑,可直接作为配置时的对照手册。内容依次覆盖 IP 核概述、产品规格(LVCMOS、LVTTL、LVDS、HSTL、SSTL 等标准的适用范围,以及性能、资源利用率与端口描述)、设计流程步骤(自定义生成、约束、仿真、综合与实现),并进一步讲解时钟缓冲与相位操纵、数据路径的锁存与位宽匹配、时钟与复位处理,另附顶层示例设计、测试平台及验证与互操作性说明。读者可据此理清从参数设置到约束落地的完整链路,减少标准选型与端口配置中的反复试错。目前已有 1856 人学习。
1. 一块多板互联采集卡上的第一次失败经历
板子回来第一次上电,8 对差分线跑在 800 Mbps,ILA 抓回来的并行数据永远是 0x5A 和 0xA5 交替。查了两天才明白,不是收发器坏了,是接收端串并转换压根没对齐。SelectIO IP 核配置里最容易被跳过的那一页,决定的正是这件事能不能成——再往深一层,它和你最终要对接的网络协议、电气标准、时钟方案是绑死在一起的,改一个参数,另外两个就得跟着动。
这份材料对应的是 SelectIO Interface Wizard v5.1 的产品手册(文档编号 PG070,2016 年 10 月版),覆盖 7 系列及其 SoC 衍生型号。它做的事情很具体:按你填的几个参数,把 IOB 里的 ISERDES、OSERDES、IDDR、ODDR、IODELAYE2 以及配套的时钟缓冲原语连成一条能综合的 I/O 电路,同时吐出约束模板和可仿真的 test bench。适合已经能跑通 Vivado 基础流程、但面对 ISERDES 那一长串属性不知道从哪下手的工程师;也适合手上有个多板互联或者 Camera、DVI、SGMII 接口要落地、想少走弯路的人。
2. IP 核骨架拆解:时钟通路与数据通路的分工
拿到生成的代码先别急着例化,把它拆成两块看会轻松很多:一块是全局共享的时钟缓冲与操作,一块是每根引脚各自一份的数据通路。前者决定你的时序余量天花板,后者决定功能对不对。手册里的框图也是这么切的,左边时钟、右边数据,中间用 IOB 隔开。
2.1 BUFIO、BUFR、BUFG 与 MMCM 各管哪一段
IOB 里的 ISERDES 和 OSERDES 有两个时钟脚:CLK 是位时钟,频率等于线速率的一半(DDR),CLKDIV 是并行侧时钟。这两个脚只能由专用时钟网络驱动,走普通布线会直接给你上一课。BUFIO 专门干这个事,它在同一个 clock region 内驱动 IOB 的 CLK,抖动最小、skew 最小,代价是它出不了这个区域,也进不了 fabric。BUFR 是区域级的,能分频(1 到 8),既能驱动 IOB 也能驱动区域内的 fabric 逻辑,做 CLKDIV 很合适。BUFG 是全局的,能到任何地方,但抖动和 skew 都略大,一般只用来喂 fabric 侧的大网。
向导里的时钟原语选项基本就是这几个的组合。选 MMCM 方案时,你把参考时钟从clk_in(单端)或clk_in_p/n(差分)灌进去,MMCM 负责生成位时钟和分频时钟,位时钟经 BUFIO 送到 IOB,分频时钟经 BUFR 或 BUFG 送到 fabric。选外部直供方案时,位时钟和分频时钟你自己从外面给,IP 只负责把它们接到该接的地方。这里有个容易看漏的端口clk_div_in:手册明确写了它只在"选了串行化且时钟原语是 MMCM"的组合下出现,用途是从外部把已经分好的时钟灌进 I/O 逻辑。实际项目中我一般让 IP 内部 MMCM 一把出完,clk_div_in悬空,少一个时钟域就少一类问题。
串行化比 N 和时钟的关系要算清楚,这是后面所有约束的基准。DDR 模式下,并行侧时钟频率 = 位时钟频率 ÷ (N/2),线速率 = 位时钟频率 × 2。举个实数:N=8、位时钟 400 MHz,那么 CLKDIV 是 100 MHz,线速率 800 Mbps。同一颗器件上如果换成 N=10 做 SGMII,位时钟 625 MHz,CLKDIV 就是 125 MHz,线速率 1.25 Gbps。
提示:单级 ISERDESE2/OSERDESE2 在 DDR 下最多做到 8:1,10:1 和 14:1 必须级联两级。向导会根据你选的 N 自动决定要不要 cascade,但资源占用和延迟会跟着变,别以为改个数字无代价。
2.2 数据通路的三种形态:直通、双沿寄存器、串并转换
向导给你的数据通路其实有三种形态,选错了后面全是补丁。第一种是直通(pass-through),引脚侧和器件侧就隔一个 IOBUF,没有任何串并转换。这种最省资源、延迟最小,适合引脚不紧张、速率在 200 Mbps 以下的场合,比如一堆低速控制线和状态线。第二种是 IDDR/ODDR 双沿寄存器,SDR 或 DDR 都能配,把引脚上的双沿数据拆成 fabric 能处理的单沿数据。源同步并行总线大多落在这一档,几百 Mbps 量级,靠 IDELAY 调采样窗口。第三种才是 ISERDES/OSERDES 串行化,把 N 根线压成一根,是省引脚的唯一办法。
选型判断其实很简单:先算引脚预算。一个 484 脚的器件,真正能用的用户 I/O 可能只有两百多,如果你的接口有 64 根数据线要跨板,不串行化根本接不下。再算时序预算:线速率超过 800 Mbps 之后,源同步并行总线的走线等长和 skew 控制会变得非常难做,这时候串行化反而是省事的方案。手册里给的六个预定义模板——Chip-to-Chip、Camera 收发、DVI 收发、SGMII——就是三种形态的典型组合,Camera 和 DVI 是 7:1 或 10:1 带随路时钟,SGMII 是 10:1 且必须要 bitslip 对齐。
双向总线这里有个必须提前想清楚的点:tristate_output这个信号是同步到输入数据路径上的,不是随便一根组合逻辑就能拉。如果做双向数据总线(比如某种并行存储接口),三态切换的时机要和时钟域对齐,否则会出现两边同时驱动的瞬间。我的习惯是在 fabric 侧加一级寄存器,把三态使能锁在 CLKDIV 域上,宁可多一个周期延迟,也不要让它在时钟沿附近抖。
2.3 端口清单:哪些必须接,哪些可以悬空
手册表 2-1 把所有端口列了一遍,实际上按你选的配置,能出来三四十个信号。新手最容易犯的错是全接上,结果一半信号不知道该驱动什么。按可用条件分类记更实用。
| 端口 | 方向 | 出现条件 | 实际接法 |
|---|---|---|---|
clk_in/clk_in_p,n | 输入 | 单端 / 差分时钟二选一 | 接板上晶振或上级时钟 |
clk_out | 输出 | 无串行化且时钟原语为 MMCM | 接 fabric,或直接不用 |
clk_div_in | 输入 | 串行化 + MMCM 组合 | 内部 MMCM 方案下可悬空 |
clk_div_out | 输出 | 串行化 + BUFIO 方案 | 接 fabric 并行侧逻辑 |
ref_clock | 输入 | 用了 IDELAY 就必须有 | 200 MHz,必须来自 BUFG |
clk_reset/io_reset | 输入 | 始终存在 | 见 4.3,两者释放有先后 |
delay_reset | 输入 | 用了输入延时 | 高有效同步复位,与io_reset分开控 |
data_in_from_pins(_p/_n) | 输入 | 输入或双向 | 接顶层引脚 |
data_in_to_device | 输出 | 输入或双向 | 接 fabric,位宽 = 数据宽度 |
bitslip | 输入 | 输入通路且 ISERDES 在 NETWORKING 模式 | 单周期脉冲,同步 CLKDIV |
delay_locked | 输出 | 用了 IDELAY | 必须抓,判断 IDELAYCTRL 状态 |
delay_tap_in/out | 输入/输出 | 用了可变延时 | 5 位,in写CNTVALUEIN,out读CNTVALUEOUT |
表里最值得盯的是ref_clock和delay_locked这一对。很多人把ref_clock随手接到一个分频出来的用户时钟上,综合能过、实现能过、仿真也对,一上板 tap 值死活写不进去,最后查到是 IDELAYCTRL 没锁。7 系列对这颗参考时钟的要求是标称 200 MHz,偏差窗口很窄,我一般直接拿板上那颗给系统用的 200 MHz 差分晶振,经 BUFG 送进来,绝不从 MMCM 输出里二次分频凑。
3. 在 Vivado 里把向导跑完:逐页配置与生成产物
前面讲的是骨架,这一章是手的动作。向导本身的交互不复杂,难的是每一页的选项背后对应什么硬件行为,选完能不能反悔,改一个参数会不会连锁改掉别的。
3.1 建工程时就要定死的三件事
第一件是器件型号,必须精确到速度等级和温度等级。SelectIO 的很多能力边界是跟速度等级绑的,同一个 bank 在 -1 和 -2 上能跑的位时钟不一样,向导会根据器件过滤掉不可选的组合,选错了后面全白搭。第二件是设计语言,IP 核本身提供 Verilog 的 design file、example design 和 test bench,你的顶层是 VHDL 也能例化,但生成的例化模板是 Verilog 的,混用要注意端口名大小写。第三件是工程组织方式:如果你打算用 IP Integrator 做 block design,SelectIO 这个核一般不进 BD,直接在 RTL 里例化更省事,因为它的引脚侧信号最终都要连到顶层端口上,过一层 BD 的接口封装反而添乱。
新建 IP 的入口在 Flow Navigator 的 IP Catalog 里搜 selectio,选中 SelectIO Interface Wizard,版本选 5.1。生成位置建议单独放一个ip/目录,并勾上"生成时同时产生 example design",那份 example 里包含顶层、test bench 和一份 XDC,是后面排查问题最好的参照物。
3.2 向导页逐项配置:把参数表填对
向导的页面顺序大致是:接口类型与数据宽度 → 时钟方案 → 数据通路与串行化 → 延时与 bitslip → I/O 标准与模板。每一页的选择会动态影响后面页面的可见项,所以别跳着点。
| 配置项 | 常见取值 | 影响 | 我的常用值 |
|---|---|---|---|
| 总线方向 | 输入 / 输出 / 双向 | 决定例化 ISERDES 还是 OSERDES 还是两套 | 按接口定,双向尽量拆成两个单向 IP |
| 数据宽度 | 1 到 16 | 并行侧位宽,引脚数 = 位宽 ×(差分则为 2) | 8 |
| 数据速率 | SDR / DDR | DDR 时 CLKDIV = 位时钟/(N/2) | DDR |
| 串行化比 N | 1(直通)到 8 单级 | 决定要不要 cascade、时钟分频比 | 8 |
| 时钟原语 | MMCM / BUFIO / BUFR 组合 | 决定内部例化哪些时钟件 | MMCM + BUFIO |
| 输入延时 | 无 / 固定 / 可变 | 可变才出delay_tap_*和delay_locked | 可变 |
| bitslip | 使能 / 不使能 | 仅输入通路、NETWORKING 模式可用 | SGMII 类接口才开 |
| I/O 标准 | 见手册单端/差分列表 | 直接写进生成的 XDC 模板 | LVDS_25 或 DIFF_SSTL15 |
关于 I/O 标准这一页,手册列得很全:单端有 HSTL 系列的 I/II/III 及各电压版本、LVCMOS 从 3.3V 一路到 1.2V、SSTL15/18,差分有 DIFF HSTL、DIFF SSTL、LVDS25、TMDS_33、MINI_LVDS_25、PPDS_25、BLVDS_25、RSDS_25。选的时候别看名字像就点,先确认所在 bank 的 VCCO 电压,同一个 bank 里塞进两种不兼容的标准,实现阶段会直接给你一条 DRC 错误。差分对还有DIFF_TERM这个属性要不要开的问题:板上有 100 欧端接电阻就别开内部端接,两者叠加会把共模拉偏。
3.3 生成产物与 Tcl 批量重建
生成完之后目录里会有一组东西:IP 的 xci、综合用的网表或 RTL、example design、test bench、以及一份 XDC 模板。项目里如果有多个同样的接口,逐个点向导是折磨,改用 Tcl 改参数重生成会快很多。下面这段是我常用的批量套路,先生成一个基准 IP,再用脚本克隆改参数。
# 以已存在的 selio_bus0 为模板,克隆出 4 个同构实例并改数据宽度 set base_ip selio_bus0 foreach idx {1 2 3 4} { set new_ip "selio_bus${idx}" # 复制 IP,保持配置一致 create_ip -name selectio_interface -library ip -version 5.1 \ -module_name $new_ip -dir ./ip # 先看一眼可写属性,字段名随版本会变,别硬背 # report_property [get_ips $new_ip] set_property -dict [list \ CONFIG.C_DATA_WIDTH {8} \ CONFIG.C_DATA_RATE {DDR} \ CONFIG.C_SERIALIZATION {8} \ ] [get_ips $new_ip] generate_target all [get_ips $new_ip] }这段脚本的关键在注释里那句report_property。CONFIG.C_*这类字段名是 IP 打包时定的,不同版本之间偶有变动,硬抄别人的脚本经常在set_property那一行报未知属性。稳妥做法是先跑一次report_property [get_ips 名字],把输出存成文本,对照着改。generate_target all会一次性把综合、仿真、例化模板都产出来,比在 GUI 里点"Generate Output Products"省事,也方便放进 CI 里跑回归。
注意:克隆 IP 时如果原 IP 已经跑了综合,先
reset_target all再改参数,否则旧的综合结果会被缓存复用,你改了宽度但网表还是老的,这种问题查起来非常费时间。
3.4 顶层例化:连线顺序和命名习惯
生成的例化模板端口很多,直接抄进顶层容易漏。我的习惯是按功能分组注释,把时钟、引脚侧、器件侧、控制与状态四段分开,这样对照原理图检查时一眼能看出哪段没接。下面是一个输入方向、8 位差分、开了可变延时的例化骨架。
selio_bus0 u_selio_bus0 ( // ---- 时钟与复位 ---- .clk_in_p (sys_clk_p), // 板级差分参考时钟 .clk_in_n (sys_clk_n), .ref_clock (idelay_ref_clk), // 200 MHz,必须由 BUFG 驱动 .clk_reset (rst_clk), // 先释放 .io_reset (rst_io), // 后释放,见 4.3 .delay_reset (rst_delay), // 与 io_reset 分开控 // ---- 引脚侧 ---- .data_in_from_pins_p (rx_p), // 位宽 = 数据宽度 .data_in_from_pins_n (rx_n), // ---- 器件侧 ---- .data_in_to_device (rx_parallel), // 进 fabric 的并行数据 // ---- 状态与延时 ---- .bitslip (rx_bitslip), // 仅 NETWORKING 模式需要 .delay_locked (delay_locked), // 必须抓,判断 IDELAYCTRL 状态 .delay_data_ce (delay_ce), .delay_data_inc (delay_inc), .delay_tap_in (delay_tap_in), // 5 位,写 CNTVALUEIN .delay_tap_out (delay_tap_out) // 5 位,读 CNTVALUEOUT );例化时有三个细节值得单独说。delay_tap_in和delay_tap_out都是 5 位,对应 IODELAYE2 的 32 个 tap,别把它当成 8 位计数器来接。delay_locked是输出,一定要引到顶层或者至少引到 ILA 上,上板第一件事就是看它是不是高。bitslip在没用 NETWORKING 模式时不会出现在端口列表里,如果你的代码里写了这个连接但 IP 没生成这个端口,综合会报端口不存在,这时候回去把 ISERDES 模式改对,而不是在代码里注释掉。
4. 约束与时序:XDC 写不对,功能对也跑不起来
生成的 XDC 模板只是骨架,引脚位置、时钟周期、输入延时这些必须你自己补。这一章按约束的执行顺序讲:先定引脚和电平,再定时钟,最后处理复位和自查。
4.1 引脚与 I/O 标准:差分对要成对写
差分管脚在顶层是两个独立端口(_p和_n),但 IOSTANDARD 只需要写在_p上,工具会自动把_n归到同一对。PACKAGE_PIN 两个都要写,写错一个位置,实现阶段会报"差分对不匹配"。
# 差分参考时钟:两个引脚都定位,电平只写在 _p set_property PACKAGE_PIN AH12 [get_ports sys_clk_p] set_property PACKAGE_PIN AH11 [get_ports sys_clk_n] set_property IOSTANDARD LVDS_25 [get_ports sys_clk_p] # 8 位差分数据:_p 和 _n 分别按位定位 set_property PACKAGE_PIN AJ14 [get_ports {rx_p[0]}] set_property PACKAGE_PIN AJ13 [get_ports {rx_n[0]}] set_property IOSTANDARD LVDS_25 [get_ports {rx_p[0]}] # 板上有外部 100 欧端接时,关掉内部端接 set_property DIFF_TERM FALSE [get_ports {rx_p[*]}]这里最容易出问题的是 bank 电压。LVDS_25 要求所在 bank 的 VCCO 是 2.5V,如果你把同一个 IP 的另一半信号分到 1.8V 的 bank 上,就得换成 DIFF_SSTL18 之类的标准,而不是硬把 LVDS_25 写进去。我的习惯是在引脚规划阶段就把整个 bank 的电压定下来,再决定这批差分线放哪,而不是先画原理图再回来迁就约束。
4.2 时钟约束:位时钟、分频时钟与 200 MHz 参考时钟
时钟约束的核心是把位时钟的周期写准,其余都是它的派生。下面的例子对应位时钟 400 MHz、N=8、DDR 的组合,此时并行侧是 100 MHz。
# 位时钟:400 MHz,从差分对的正端创建 create_clock -name clk_bit -period 2.500 [get_ports sys_clk_p] # IDELAYCTRL 参考时钟:200 MHz,必须是独立时钟,不能由 clk_bit 分频得到 create_clock -name clk_ref -period 5.000 [get_ports idelay_ref_clk_p] # 如果对端随路时钟一起过来(源同步),用 generated clock 跟随 # create_generated_clock -name clk_rx_fwd -source [get_ports rx_clk_in_p] \ # -divide_by 1 [get_ports rx_clk_in_p] # 输入延时:告诉工具数据相对位时钟的到达窗口 set_input_delay -clock clk_bit -max 0.600 [get_ports {rx_p[*]}] set_input_delay -clock clk_bit -min 0.200 [get_ports {rx_p[*]}]create_clock写在_p上就够了,工具对差分对的另一侧自动识别。clk_ref那条千万别省,也千万别写成create_generated_clock从位时钟分频出来——这两个时钟在物理上必须是独立源,否则 IDELAYCTRL 的校准基准就跟着数据速率漂,tap 值和实际延时对不上。手册里对ref_clock的措辞是"必须来自 BUFG",意思就是它得走一条干净的全局时钟路径,不能是随手拉的普通信号。
提示:如果你的设计里有多个 SelectIO IP 实例都开了输入延时,注意它们共享同一个 IDELAYCTRL。别在每个 IP 里各例化一个,重复例化会在实现阶段报冲突,或者被工具悄悄优化掉一个导致某个实例的延时失效。
4.3 复位释放顺序:写成显式逻辑,别靠运气
手册里对io_reset有一句非常关键的描述:它必须在 SERDES 的时钟稳定之后才能撤销,而且要在clk_reset撤销之后再等若干周期;8:1 串行化时,两者之间建议隔 16 个 I/O 时钟周期。这句话如果不落实成逻辑,你得到的是"仿真全对、上板偶发错"的典型症状。
// 复位释放:clk_reset 先撤,等 16 个位时钟周期后再撤 io_reset reg [4:0] rst_cnt = 5'd0; reg rst_clk_r = 1'b1; reg rst_io_r = 1'b1; always @(posedge clk_bit) begin if (ext_rst) begin rst_cnt <= 5'd0; rst_clk_r <= 1'b1; rst_io_r <= 1'b1; end else if (!rst_clk_r) begin // clk_reset 已释放,开始计 16 拍 if (rst_cnt != 5'd16) rst_cnt <= rst_cnt + 1'b1; else rst_io_r <= 1'b0; end end // clk_reset 比 io_reset 早一拍释放 always @(posedge clk_bit) begin if (ext_rst) rst_clk_r <= 1'b1; else rst_clk_r <= 1'b0; end assign rst_clk = rst_clk_r; assign rst_io = rst_io_r;这段代码有两个点值得说明。计数器用的是位时钟clk_bit,不是并行侧时钟clk_div,因为手册说的"16 个 I/O 时钟周期"指的是位时钟;用并行时钟计 16 拍会多等 4 倍时间,虽然功能上不会错,但上电到出数的延迟会变长。另外delay_reset我单独引了一根,它在io_reset释放之后再拉低一拍,确保 IDELAY 的 tap 寄存器不会在时钟切换的瞬间被清掉。
4.4 三条必看的自查报告
约束写完先别急着跑实现,跑这三条命令能挡掉八成低级错误。第一条看时钟网络有没有被正确识别,重点是位时钟是不是走了 BUFIO 而不是普通布线。第二条看 I/O 的 bank 电压和标准有没有冲突。第三条看时序,重点看输入路径的 setup/hold 余量。
# 1. 时钟网络与拓扑 report_clocks report_clock_networks -name selio_clk # 2. I/O 电平与 bank 一致性 report_io -file io_report.rpt # 3. 时序余量 report_timing_summary -delay_type min_max -max_paths 20 -file timing.rptreport_clock_networks的输出里,如果看到位时钟被标成走 BUFG 进 IOB,基本可以确定 BUFIO 没用上,这时候回头查引脚是不是接在了非时钟 capable 的管脚上。report_io里如果出现 bank 电压冲突的 warning,不要当警告忽略,它一定会变成实现阶段的 DRC 错误。
5. 避坑排查:五条现场记录与解决办法
这一章的五条都是我或同事在真实板子上踩过的,现象描述尽量还原到你一眼能认出是自己的场景。
5.1 综合与实现阶段的两条
第一条:tap 值写进去读回来永远是 0,delay_locked一直低。
现象是代码里给delay_data_ce打脉冲、delay_data_inc拉高,用 ILA 抓delay_tap_out却纹丝不动,同时delay_locked恒为 0。原因九成是ref_clock没接或频率不对,IDELAYCTRL 没完成校准,IODELAYE2 就进不了工作状态。另一小半可能是delay_reset一直被拉着,或者多个 IP 实例各例化了一个 IDELAYCTRL 互相打架。解决顺序是这样的:先用 ILA 抓delay_locked,不为高就别往下查了,直接去看参考时钟;用report_clocks确认 200 MHz 那条时钟真的存在且周期是 5.000 ns;再检查工程里 IDELAYCTRL 是不是只有一个实例。我现在的习惯是在顶层就例化一个 IDELAYCTRL,所有 IP 的ref_clock都接到同一根 BUFG 输出上,从源头避免这件事。
第二条:实现阶段报时钟布线冲突,或者时序报告里 clock skew 大得离谱。
现象是 place 阶段报 BUFIO/BUFR 相关的 routing 冲突,或者时序报告里位时钟的 skew 到了纳秒级。原因是时钟输入引脚没落在 MRCC/SRCC 上,工具没法把专用时钟网络引进去,只能绕普通布线。7 系列每个 bank 有一对 MRCC 和若干 SRCC,差分时钟必须接在 MRCC 的_P端。解决办法是在引脚规划阶段就把时钟脚钉死,不要等布线报错再回头改。我一般会把时钟引脚写在约束文件最上面并加注释,任何人改这个文件都能看到。另外还有一种情况:位时钟和分频时钟在同一个区域里被两个 BUFIO 抢同一个时钟网络,这时候把分频那一路换成 BUFR,冲突就没了。
5.2 上板与调试阶段的三条
第三条:仿真通过、上板正常跑几分钟后开始偶发误码。
现象很迷惑人:常温下跑一会儿就错一批,重新上电又好,换一块板可能几个小时都不错。原因是io_reset和clk_reset同时撤销,SERDES 的时钟还没稳就放开了复位,初始相位落在一个随机的位置。手册里那句"8:1 串行化时两者之间隔 16 个 I/O 时钟周期"就是给这个场景写的。解决办法是按 4.3 那段代码改成显式计数释放,并且把sync_reset也接到同步复位域里。改完之后我又遇到一次类似现象,查下来是io_reset撤销的时机跟着外部按键抖动走,按键按下的那一下带来几十毫秒的毛刺,后来给按键加了消抖才彻底干净。
第四条:bitslip 打了脉冲,数据错位不收敛。
现象是你按对齐算法一次次打bitslip,数据要么不动,要么跳到一个更差的位置。原因有三个层面:一是 ISERDES 没配成 NETWORKING 模式,这个模式下bitslip端口根本不会出现在生成的 IP 里,你连的那根线其实是悬空的;二是bitslip是同步到 CLKDIV 域的脉冲,在别的时钟域打过去会漏采;三是脉冲拉得太宽,一个高电平持续多个周期等于连打了多次。解决办法是把bitslip的驱动逻辑锁在 CLKDIV 域,用单周期脉冲,并且每次打完之后等固定的若干周期再判断对齐是否成功。判断依据别用数据本身,用一个固定的训练图案(比如 0xBC 这类不会在正常数据里出现的字节)去比对,收敛会快很多。
第五条:I/O 标准选对,但 DRC 还是报 bank 电压冲突。
现象是每个管脚单独的 IOSTANDARD 都合法,但实现阶段报同一个 bank 里电压不兼容。原因是同一个 bank 的 VCCO 只能有一个值,你把 2.5V 的 LVDS_25 和 1.8V 的 LVCMOS18 混在了一个 bank 里。工具不会告诉你哪个引脚错了,只会告诉你这个 bank 冲突。解决办法是拿report_io的输出按 bank 排序,把冲突的引脚挪到别的 bank,或者统一把这一批信号换成同电压的标准。我现在的做法是在引脚规划表里加一列 bank 电压,分配之前先排序,避免事后返工。
6. 验证与进阶:用 tap 扫描把采样窗口的中心找出来
仿真跑通只是及格线,真正的功夫在把采样点摆到眼图正中间。7 系列的 IODELAYE2 给你 32 个 tap,每个 tap 的步进量级在几十皮秒(具体值看器件速度等级和工艺),这 32 个位置里通常只有中间那几个是干净的。与其凭感觉猜一个值写死,不如写个扫描逻辑,上板自动跑一遍。
6.1 先仿真,再上板扫
仿真阶段用 IP 自带的 test bench 就够了,重点看两件事:delay_locked在复位释放后多久拉高,以及并行数据在clk_div域上是不是稳定的。仿真里把ref_clock的周期设成 5 ns,把clk_reset和io_reset的释放顺序照 4.3 的方式写,能提前发现一大半复位时序问题。
上板扫描的思路很朴素:把 tap 从 0 走到 31,每档停留一段时间,统计这个窗口里的误码数或 CRC 错误数,最后选错误最少的区间取中间值。下面这段是精简后的状态机,wr_tap是你要写入的目标 tap 值,err_cnt是外部误码统计。
// 逐 tap 扫描:每档保持 TAP_HOLD 拍,记录该窗口的误码数 localparam [19:0] TAP_HOLD = 20'd200_000; reg [4:0] tap_idx = 5'd0; reg [19:0] hold_cnt = 20'd0; reg sweep_en = 1'b0; always @(posedge clk_div) begin if (!sweep_en) begin hold_cnt <= 20'd0; tap_idx <= 5'd0; delay_ce <= 1'b0; delay_inc <= 1'b0; end else if (hold_cnt == TAP_HOLD) begin hold_cnt <= 20'd0; delay_ce <= 1'b1; // 产生一次延时改变事件 delay_inc <= 1'b1; // 递增方向,扫完 31 后回到 0 重新开始 tap_idx <= (tap_idx == 5'd31) ? 5'd0 : tap_idx + 1'b1; end else begin hold_cnt <= hold_cnt + 1'b1; delay_ce <= 1'b0; // ce 只打一拍,保持高会连续改变 end end三个参数值得单独说。TAP_HOLD决定了每档停留多久,太短统计不到有效样本,太长整个扫一遍要几十秒;我一般按误码率量级取,误码在 1e-9 量级时给到 20 万拍(100 MHz 下约 2 ms)足够。delay_inc固定为 1 是单调递增扫描,扫到 31 再回卷,这样你能看到一条完整的 U 形曲线,而不是来回跳。delay_ce一定要只打一拍,这个信号是"延时改变事件"的使能,不是电平使能,拉高不放会连续改动 tap 值。
6.2 结果怎么读
扫完之后你会得到一张表,形态大概是这样,中间宽、两头窄。判断标准不是"误码为 0 的区间有多大",而是"误码开始上升的两个拐点在哪",取两个拐点的中点。因为拐点位置反映的是采样点撞上数据跳变沿的位置,中点才是眼图中心。
| tap 值 | 累计误码 | 判断 |
|---|---|---|
| 0 到 5 | 大量 | 撞上跳变沿,不可用 |
| 6 到 9 | 少量 | 边缘区,温度漂移会掉出去 |
| 10 到 21 | 0 | 可用窗口 |
| 22 到 25 | 少量 | 另一侧边缘区 |
| 26 到 31 | 大量 | 撞上另一侧跳变沿 |
取中间值意味着最终写死的是 15 或 16,而不是窗口边缘的 10。这样做的意义在于留出温度漂移和电压波动的余量——板上跑几个小时之后器件温度上来,延时链的特性会漂,采样点贴着边缘的设计往往就是这时候开始出错的。扫描逻辑不要在上板验证完就删掉,留在代码里做成一个可触发的调试模式,后面换批次板子或者改走线长度时还能用。
从那以后我每次上板前都强制走一遍 tap 扫描,哪怕时序报告显示余量充足、哪怕这批板子和上一批是同一家厂出的。因为延时链这种事,报告给不了你眼图的真实形状,只有扫出来的那条 U 形曲线会告诉你窗口还剩多宽。希望帮到你。
本文还有配套的精品资源,点击获取