首页 / 资讯中心 / 文章详情

FPGA SelectIO IP核配置与LVDS源同步接收时序避坑

FPGA SelectIO IP核配置与LVDS源同步接收时序避坑 ★ FEATURED ARTICLE
简介围绕Xilinx FPGA SelectIO接口技术展开的PDF文档面向需要掌握可编程I/O配置的FPGA设计者与接口调试人员。内容从SelectIO IP核概述切入梳理LVCMOS、LVTTL、LVDS、HSTL、SSTL等电气标准以及数据速率、功耗、信号完整性和LUT、触发器、I/O缓冲器等资源占用情况并说明端口方向、电平标准与速度参数的配置方式。文档随后按自定义生成IP核、添加约束、仿真验证、综合实现的设计流程展开并补充时钟缓冲与相位调整、数据锁存与位宽匹配等数据路径要点可作为Vivado环境下SelectIO Interface Wizard配置的查阅材料。压缩包仅含1个PDF文件大小约1.11MB轻量便于随查随用。已有1856人学习适合希望系统理解SelectIO结构并落实到IP核参数设置的读者。1. SelectIO Interface Wizard 省掉的到底是哪几百行从一组 LVDS 差分线说起手头这块板子要接四路 LVDS 差分数据、一路随路时钟外加一组按网络协议时序走的 SGMII 形态接口引脚预算被压到最紧。这种时候没人愿意手写 ISERDESE2 的级联、IDELAYE2 的 tap 控制和 BUFIO/BUFR 的分配更不想半夜对着 IDELAYCTRL 不 lock 发愁。SelectIO 结构本来就是为“少引脚、高并行”的活儿准备的硬核资源而 SelectIO IP 核配置Interface Wizard把它收敛成一个向导选方向、选 I/O 标准、选串并比吐出可综合的 HDL、示例设计和 XDC。适合已经能把 LED 点亮的 FPGA 从业者也适合第一次做源同步接收、被眼图折腾过一轮的人。2. 拆开 IP 的两条主干时钟缓冲与逐引脚数据通路向导生成的东西看着文件不少结构其实只有两块一块负责时钟的缓冲、分频与分配一块按引脚逐个实例化数据通路。PG070 里那张框图把这件事说得很直白——左边是 IOB 与时钟生成右边是 ISERDES/OSERDES 与延迟单元中间隔着一个“fabric 时钟域到 I/O 时钟域”的跨越。搞不清这条分界线后面的参数怎么填都是碰运气。2.1 时钟这半边BUFIO、BUFR、MMCM、BUFG 各管一段I/O 侧的时钟不能像内部逻辑那样随手接一根线。BUFIO 只能驱动 IOB 里的时钟网络速度快、抖动低但它进不了 fabricBUFR 是带分频的区域时钟能进 fabric代价是布线范围受限MMCM 灵活能出多个相位和分频但路径长、延迟大。向导把这三者的取舍做成了选项选完之后通过clk_out、clk_div_in、clk_div_out三个端口暴露给使用者这三个端口并不是同时有效的。具体规则要看清楚不启用串行化、且时钟原语选 MMCM 时clk_out才是有效输出它把缓冲或延迟后的时钟送回 fabric启用串行化、时钟原语选 MMCM 时改用clk_div_in从 fabric 送分频时钟进来启用串行化、时钟原语选 BUFIO 时clk_div_out才是那个该接进 fabric 的分频时钟。很多人在 8:1 串行化下把 fabric 侧接到了clk_out编译不报错跑起来数据整段错位就是因为这条规则没对上。还有两个容易被忽略的输入。clk_enable接的是时钟原语上的使能等效于 BUFGCE 的 CE 脚做低功耗门控或者上电时序控制时要用ref_clock是给 IDELAYCTRL 的参考时钟必须走 BUFG不能从普通布线过去频率按 200 MHz 这一档配偏差控制在允许范围内。ref_clock一旦失锁delay_locked会一直为低所有 tap 调整都变成无效动作。单端时钟用clk_in差分时钟用clk_in_p/clk_in_n二选一不需要同时接。这一点在向导里是互斥的改参数的时候如果没清掉另一侧的连接综合会报多驱动。2.2 数据这半边ISERDES/OSERDES、IDDR/ODDR 与 IDELAYE2 的串接一个输入引脚的完整链条是pad → IBUF 或 IBUFDS → 可选 IDELAYE2 → ISERDESE2 → fabric。输出方向反过来fabric → OSERDESE2 → 可选 ODELAYE2 → OBUF 或 OBUFDS → pad。双向的情况中间换成 IOBUF并且多出一个tristate_output端口这个信号跟输入数据是同步的写代码时别再自己打一拍否则总线换向时会撞上。串行化那一段最需要理解的是模式。ISERDESE2 有两种接口类型MEMORY 模式和 NETWORKING 模式。只有 NETWORKING 模式下 bitslip 功能才开放也只有这种模式下bitslip[n-1:0]这个端口才会出现在生成核的端口列表里。做源同步接收、需要靠移位找字边界的时候模式必须选对否则你在顶层例化时找不到这个端口只能回头重生成一遍。数据宽度上限是 16 位总线。串并比做得比 8 更大时比如 1:10、1:14向导会自动用主从级联的方式把两个 ISERDESE2 串起来级联的连线是内部生成的不用手工接。但级联会带来一个额外约束两个 SERDES 必须共用同一组时钟时钟分配上不能用两个不同的 BUFIO。DDR 与 SDR 的选择直接决定中间那一级是 IDDR/ODDR 还是直通。选 DDR 且复位类型设为 SYNC 时sync_reset端口才会生效它只接到 IDDR 上不影响别的逻辑。这一点在调试时容易误判——以为按了复位整个通路都清了其实 OSERDES 那一侧没动。延迟单元是可选插入的。选了输入延迟端口列表里就多出delay_data_ce、delay_data_inc、delay_tap_in[4:0]、delay_tap_out[4:0]、delay_reset这一组选了输出延迟则多出对应的输出侧一组。tap 宽度固定 5 位取值 0 到 31每个 tap 的绝对延时随器件速度等级变化别看文档给的典型值就当成实测值用。2.3 端口按域分类读哪些接 pad哪些接 fabric拿到生成核之后第一步不是急着例化而是把这几十个端口按域分一遍。分错了域后面所有时序分析都是白做。端口组典型端口归属侧说明时钟输入clk_in、clk_in_p、clk_in_npad 侧单端与差分二选一时钟输出clk_out、clk_div_in、clk_div_out跨域三者有效性由串行化与时钟原语决定参考时钟ref_clockfabric 侧必须经 BUFG供 IDELAYCTRL 使用复位clk_reset、io_reset、sync_reset、delay_resetfabric 侧释放顺序有硬性要求pad 侧数据data_in_from_pins、data_out_to_pins、data_to_and_from_pinspad 侧差分时带_p/_n后缀fabric 侧数据data_in_to_device、data_out_from_devicefabric 侧宽度等于总线宽度乘以串并比控制bitslip[n-1:0]、tristate_outputfabric 侧bitslip 仅 NETWORKING 模式可用延迟delay_*系列fabric 侧每个 IODELAYE2 一份注意 fabric 侧数据宽度和 pad 侧不是一回事。8 位总线、1:8 串行化data_in_to_device的宽度是 64 位中间还夹着一个并行时钟域。写接收逻辑时如果按引脚数去算 FIFO 深度会差出整整一个串并比倍数。3. 在 Vivado 里把核配出来参数、模板与批量生成向导界面本身不复杂复杂的是在点开它之前脑子里要有的那几个数。参数是连锁的改一个数据宽度会牵动串并比、牵动时钟分频、牵动 fabric 侧位宽最后牵动约束写法。所以顺序上先定死几个不变量再让向导去推其余部分。3.1 动手之前先定三件事方向、速率、串并比方向决定例化哪一套原语单向输入、单向输出、双向三条路径生成的代码结构差别很大中途改方向等于重做不如一开始就定死。速率这件事要分成两个数看pad 侧的单位间隔 UI和 fabric 侧要跑的逻辑时钟频率。这两者被串并比绑在一起UI 是 1 ns 的时候1:8 串行化意味着 fabric 侧每 8 ns 收一个 8 位字逻辑时钟 125 MHz。串并比的选择有个实用原则能在 ISERDESE2 单级内解决就不要级联。单级最多到 1:8DDR 下超过这个数就进级联级联会额外吃掉一个 SERDES 和一组布线资源还会让时钟树变复杂。所以我一般先把串并比压到 8 以内如果压不住说明前端速率确实高那就老老实实上级联。还有一个容易被忽略的选型点向导内置了几套预定义模板涵盖芯片间互联、摄像头收发、DVI 收发和 SGMII。这些模板给的是参数组合的起点不是最终答案。拿 SGMII 模板举例它把方向、串并比、时钟原语都配好了但如果你的板上参考时钟不是模板假设的那个频率直接套用会撞上ref_clock分频对不上的问题。模板的正确用法是先套、再逐项核对时钟页。3.2 逐页填参数宽度、标准、延迟、复位类型第一页是总线定义。数据宽度填 1 到 16方向三选一串行化开或关SDR 或 DDR。这里有个细节宽度填的是 pad 侧引脚的并行数量不是 fabric 侧位宽别把 64 位的数据位宽填进去。第二页是 I/O 标准。单端可选 HSTL 系列、LVCMOS 的 3.3/2.5/1.8/1.5/1.2 各档、SSTL15 与 SSTL18 的 I 类和 II 类差分可选 DIFF_HSTL、DIFF_SSTL、LVDS25、TMDS_33、MINI_LVDS_25、PPDS_25、BLVDS_25、RSDS_25 这一串。选型不是看哪个名字眼熟而是看对端器件给的直流参数。LVDS25 和 MINI_LVDS_25 看着像驱动能力和共模范围不一样接错的表现是链路能通但误码率随温度飘。第三页是延迟。输入延迟和输出延迟是两个独立开关开了才出 tap 端口。如果打算做动态对齐这里必须打开如果板级走线等长做得很好、余量足够关掉能省一组控制逻辑。开了之后记得把delay_reset接出来上电必须复位一次 IODELAYE2否则 tap 值是不确定的。第四页是复位和时钟。复位类型在异步与同步之间选选 SYNC 才有sync_reset。时钟页要填输入时钟是单端还是差分、时钟原语用 MMCM 还是 BUFIO。这一步选错前面 2.1 讲的三个时钟输出端口就对不上了。3.3 用 Tcl 把生成过程固化成脚本界面点出来的核换台机器重做一遍容易漏参数。我习惯把配置写成 Tcl参数名不靠记忆先用工具自己查出来。# 先建一个默认配置的核用来枚举它到底有哪些可配参数 create_ip -name selectio_wiz -version 5.1 -module_name selio_probe report_property [get_ips selio_probe] # 看清参数名和取值范围后删掉探针按实际需求重建 # 注意参数名前缀随版本可能变化必须以 report_property 的输出为准 remove_ip [get_ips selio_probe]report_property的输出是这类 IP 最可靠的黑匣子说明书比翻文档快。拿到参数名之后再写正式脚本# 正式生成接收方向LVDS 差分1:8 串行化开输入延迟 create_ip -name selectio_wiz -version 5.1 -module_name selio_rx_8to1 set_property -dict [list \ CONFIG.BUS_DIR {1} \ CONFIG.BUS_SIG_TYPE {1} \ CONFIG.BUS_WIDTH {1} \ CONFIG.SERIALIZATION_STAGES {8} \ CONFIG.USE_SERIALIZATION {true} \ CONFIG.EN_DELAY {true} \ ] [get_ips selio_rx_8to1] # 生成所有目标综合源、仿真源、约束 generate_target {instantiation_template synthesis simulation} \ [get_ips selio_rx_8to1]第一段的意义在于“别信记忆”。这套 IP 的参数名在不同版本之间动过硬背下来的字段名换个工具版本就可能不认。第二段里BUS_DIR控制方向、BUS_SIG_TYPE控制单端与差分、BUS_WIDTH是 pad 侧引脚数、USE_SERIALIZATION与SERIALIZATION_STAGES成对出现EN_DELAY决定是否实例化 IODELAYE2。参数之间是有依赖的比如关掉USE_SERIALIZATION再设SERIALIZATION_STAGES不会报错但生成结果里那一段逻辑会被静默丢掉。脚本化还有个额外好处多个通道要例化多份核的时候改一个变量名就能批量套用不用在界面上来回点。3.4 生成之后先看文件再看层次生成完别急着写顶层先把输出目录翻一遍。典型产物包括一份例化模板给顶层复制粘贴用、一份 IP 的顶层 wrapper、内部的实现文件、一份 XDC 约束、一份可综合的示例设计以及一份演示用 testbench。文件名随module_name变化但结构是固定的。产物类型用途使用建议例化模板顶层粘代码直接复制别手写端口IP wrapper实际被综合的模块只读不改重生成会覆盖内部实现文件原语例化细节调试时看它确认原语参数XDC 约束时钟与引脚相关约束与自己的约束合并注意去重示例设计可综合的完整通路拿来对照自己顶层的接法testbench演示激励改激励比从零写快得多我一般会打开内部实现文件扫一眼原语参数确认DATA_RATE、DATA_WIDTH、INTERFACE_TYPE这三项和预期一致。界面上的选项最终都落到这几个属性上这里是唯一的真相。示例设计也值得花十分钟读一遍它的复位释放顺序、时钟连接方式基本就是标准答案。4. 约束与时序XDC 怎么写才不和生成的打架IP 生成的 XDC 已经覆盖了一部分时钟约束这既是好事也是麻烦事。好事是不用从零写麻烦是重复约束会以警告甚至错误的形式冒出来而且这类冲突往往在实现阶段才暴露。4.1 引脚与 I/O 标准谁说了算生成的 XDC 里通常带了 I/O 标准的设定顶层再写一遍set_property IOSTANDARD就会冲突。正确的做法是先决定一处为准另一处注释掉而不是两边都留。我的习惯是I/O 标准以顶层约束为准把生成 XDC 里对应的行删掉这样引脚分配表改一次就够。引脚位置约束必须自己写向导不可能知道你的板子怎么连。差分对要注意两点位置只能在 P 端写一次N 端由工具推导DIFF_TERM是否打开取决于板上有无外部端接电阻板上已经有 100 欧端接还打开内部端接信号幅度会被压掉一截。# 差分输入对只在 P 端指定位置N 端由工具自动配对 set_property PACKAGE_PIN AK12 [get_ports data_in_from_pins_p[0]] set_property IOSTANDARD LVDS25 [get_ports data_in_from_pins_p[0]] set_property DIFF_TERM TRUE [get_ports data_in_from_pins_p[0]] # 单端输出每个引脚都要单独指定 set_property PACKAGE_PIN AL15 [get_ports data_out_to_pins[0]] set_property IOSTANDARD LVCMOS18 [get_ports data_out_to_pins[0]] set_property SLEW SLOW [get_ports data_out_to_pins[0]]第二段里SLEW SLOW是为了压边沿速率、减小串扰在总线宽、走线密的板上值得加上代价是时序余量变紧。这类取舍没有标准答案得看板级实测。4.2 差分输入时钟的约束写法差分时钟只需要在 P 端约束一次。写成两端各一条create_clock是常见错误工具会认为有两个时钟源后面的时序分析全乱。# 差分输入时钟周期 4.000 ns即 250 MHz仅在 P 端约束 create_clock -name clk_in_p -period 4.000 [get_ports clk_in_p] # 如果时钟是从 I/O 侧转发出去的用生成时钟描述它的来源与倍频关系 create_generated_clock -name clk_fwd \ -source [get_ports clk_in_p] \ -divide_by 1 \ [get_ports clk_out_to_pins_p]第二段处理的是时钟转发场景数据随路时钟一起送出去接收端拿这根时钟采样。这类接口的时序分析不能只看发送端还要在输出端口上加set_output_delay把对端建立保持窗口折算成约束值。折算参数从对端器件手册拿别自己猜。4.3 ref_clock 与 IDELAYCTRL一个 200 MHz 的硬条件ref_clock的作用是给 IDELAYCTRL 提供参考进而校准整个 bank 里所有 IODELAYE2 的 tap 绝对值。它的约束有三条硬要求频率落在 200 MHz 这一档、必须来自 BUFG、每个用到延迟的 bank 至少有一个 IDELAYCTRL 实例。# 参考时钟200 MHz周期 5.000 ns create_clock -name ref_clk -period 5.000 [get_ports ref_clock] # 确认它确实走了全局时钟缓冲而不是普通布线 set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets ref_clock_ibufg]第二行是个诊断手段不是常规写法。正常情况下工具会自动把ref_clock放到 BUFG 上如果实现阶段报“时钟未走专用布线”说明前面某一级被优化掉了这时候用它强制放行只是临时手段根治办法是检查ref_clock的驱动源是不是被综合工具当成了普通逻辑。delay_locked这个输出要接到你的复位逻辑里去。它的含义是 IDELAYCTRL 已经锁定可以开始调 tap。上电后它拉高需要时间如果逻辑在它拉高之前就开始扫 tap扫出来的结果没有意义。我在顶层习惯把它和外部复位做一次与运算作为延迟控制状态机的启动条件。4.4 仿真直接改生成的 testbench 比从零写快生成的 testbench 已经包含时钟生成、复位时序和基本激励。改它比自己搭一个快得多尤其是复位释放顺序那一部分手写很容易漏掉io_reset相对clk_reset的延迟。仿真时重点看三件事delay_locked有没有在预期时间内拉高、data_in_to_device上的数据在并行时钟域里是否稳定、bitslip操作后字边界有没有移动。第三件事在没有对齐逻辑的时候看不出来得自己加一个比对模块把收到的字和激励里发的已知图案做对比。综合阶段如果用第三方综合工具注意向导文档里标注的支持列表不同工具的流程对 IP 生成文件的处理方式有差异。用 Vivado 自带综合的话基本不用额外配置。5. 避坑与排查五条从板子上换来的记录这一节全是现象、原因、解决三段的实录。有些是编译期就能看出来的有些要到板子上跑起来才现形。5.1 复位一起释放SERDES 死活锁不住现象仿真里数据正常上板后data_in_to_device一直是固定图案或全零改时钟频率也没用。原因clk_reset和io_reset同时释放。文档明确要求io_reset必须在clk_reset释放之后、且时钟已经稳定若干个周期才能释放。8:1 串行化的情况下建议两者之间隔 16 个 I/O 时钟周期。同时释放时SERDES 内部的移位寄存器在时钟还没稳的时候就完成了初始化锁到一个错误状态再也不动。解决写一个两级计数器clk_reset释放后开始计数计满再放io_reset。// 复位释放序列clk_reset 先释放io_reset 延后 16 个 I/O 时钟周期 // io_clk 为 I/O 侧时钟位宽 5 位足够计到 16 reg [4:0] rst_cnt 5d0; reg io_reset_r 1b1; always (posedge io_clk) begin if (clk_reset) begin rst_cnt 5d0; io_reset_r 1b1; end else if (rst_cnt ! 5d16) begin rst_cnt rst_cnt 1b1; io_reset_r 1b1; end else begin io_reset_r 1b0; // 计满后释放 end endrst_cnt的位宽按最大延迟周期数取别卡在边界值上。io_reset_r保持高有效还是低有效取决于向导里的极性设置实际接的时候对一下端口声明。5.2 ref_clock 没走 BUFGdelay_locked 一直是低现象延迟调整完全不生效delay_tap_out读回来永远是 0delay_locked全程为低。原因ref_clock从普通引脚直接接到 IDELAYCTRL中间没经过全局时钟缓冲。IDELAYCTRL 对参考时钟的路径有硬性要求走普通布线时它不工作但综合和实现都可能不报错。解决在顶层把ref_clock先过一个 BUFG 再送进 IP同时检查约束里没有把它约束成普通信号。加完之后重新看delay_locked的上电时序正常情况下会在几十微秒内拉高。5.3 差分对只约束了 P 端 IOSTANDARD现象实现阶段报差分对的两端 I/O 标准不一致或者更隐蔽——不报错但 N 端的标准被工具推成了一个默认值板子上跑起来共模电平不对。原因只对_p端口写了set_property IOSTANDARD_n端口漏了。有些版本的流程会自动推导有些不推行为不一致。解决差分端口成对约束。如果嫌麻烦就在生成 XDC 里保留 IP 自带的那一份顶层只补PACKAGE_PIN两边分工明确别都写。5.4 想做字对齐结果 bitslip 端口根本不存在现象顶层例化时找不到bitslip翻遍生成文件也没有。原因bitslip只在输入数据通路、且 ISERDESE2 处于 NETWORKING 模式时才存在。选成 MEMORY 模式或者方向选成纯输出这个端口就不会生成。解决回向导改INTERFACE_TYPE。改完之后必须重新生成光改顶层例化没用。顺带说一句bitslip[n-1:0]是每位一根位宽等于总线宽度不是串并比。5.5 8:1 串行化下 fabric 侧接错时钟现象数据不是全错而是有规律地错位几个字换个采样点又能对一段像是时钟相位问题但怎么调都不彻底。原因fabric 侧接了clk_out而不是clk_div_out。这两个端口的有效条件不同8:1 串行化加 BUFIO 的组合下clk_out根本不是给 fabric 用的那个分频时钟。解决回到 2.1 那张规则表对一遍按串行化开关和时钟原语类型选定唯一正确的输出端口。改完之后重新跑时序分析确认 fabric 侧的时钟域和预期频率一致。6. 从能跑到跑准bitslip 对齐与延迟扫描的实测手法链路能通只是第一步。源同步接收真正难的是让每个通道都落在眼图中心而不是靠运气落在边缘。我一般走两步先用 bitslip 把字边界对齐再用 tap 扫描把采样点推到中间。字边界对齐靠已知图案。发送端周期性插入一个固定同步字接收端在每个通道上独立地尝试 0 到 N-1 次 bitslip每试一次比对一次对上了就锁住该通道的移位值。这里的坑是各通道的移位值不能共用走线长度差一点需要的 bitslip 次数就差一位。// 单通道 bitslip 搜索最多尝试 8 次命中同步字即锁定 // sync_word 为发送端插入的已知图案ch_data 为该通道并行数据 localparam [7:0] SYNC_WORD 8h5A; reg [2:0] slip_cnt 3d0; reg slip_done 1b0; reg bitslip_r 1b0; always (posedge clk_div) begin bitslip_r 1b0; // 默认拉低只发单周期脉冲 if (!slip_done) begin if (ch_data SYNC_WORD) begin slip_done 1b1; // 对齐完成停止搜索 end else if (slip_cnt ! 3d7) begin bitslip_r 1b1; // 未命中则再移一位 slip_cnt slip_cnt 1b1; end else begin slip_done 1b1; // 达到上限报错由上层处理 end end endslip_cnt的上限取串并比减一就够了超过这个数说明不是移位问题而是别的地方错了。bitslip_r必须是单周期脉冲拉高两个周期会移两位这是最常见的翻车点。延迟扫描比 bitslip 麻烦因为它要遍历 32 个 tap 并记录误码。我的做法是写一个小状态机从 tap 0 扫到 31每个 tap 停留足够长时间统计误码把结果通过 ILA 或者串口吐出来画一条“误码率-延迟值”曲线取曲线的平台中心作为最终值。这条曲线在温度变化大的场合要重扫别一次扫完就当成永久参数。这里补一句血泪经验扫描期间一定要关掉 bitslip 的自动搜索否则两次调整叠在一起曲线会变得毫无规律看着像玄学其实是两个环路在互相打架。从那以后我每次上板调延迟都强制先锁 bitslip、再冻住它、最后才动 tap顺序反了就等着加班。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站