1. 为什么ISERDES/OSERDES是FPGA高速接口的“心脏”,而不是可有可无的IP?
你手上那块Xilinx Artix-7或者Intel Cyclone V开发板,跑着200MHz的系统时钟,但你要接的LVDS摄像头输出是1.2Gbps,MIPI CSI-2数据流是1.5Gbps,甚至PCIe Gen2链路标称5Gbps——这些速率远超FPGA内部逻辑能直接采样的极限。这时候,你不能靠写个状态机拼命“打拍子”来硬扛,就像不能让一个百米跑12秒的运动员去参加F1方程式赛车一样。ISERDES(Input Serializer/Deserializer)和OSERDES(Output Serializer/Deserializer)就是FPGA芯片里内置的、专为这种“速度 mismatch”问题而生的硬件加速器。它们不是软件模块,不是Verilog代码写的逻辑,而是硅片上真实存在的、经过严格时序验证的专用电路单元,直接集成在IO Bank的物理层旁边。
我第一次在项目里用ISERDES时,目标是接收一个800Mbps的并行LVDS数据流(8位宽,100MHz时钟域)。如果不用ISERDES,我得用8个IO引脚各自采样,再用复杂的相位对齐逻辑把8路数据在内部时钟下拼成一个字节——结果仿真波形乱成一团,上板后根本锁不住相位,眼图张开度不到30%。换成ISERDES后,只用1对差分LVDS线(即2个物理引脚),配置成DDR模式、8:1串并转换,内部自动完成采样点动态调整、数据对齐、时钟域同步,最终实测误码率低于1e-12。这不是“功能实现”,而是“工程落地”的分水岭:前者是实验室里的Demo,后者是能放进产品外壳、连续运行三个月不出错的工业级设计。
核心关键词“FPGA高速接口设计”里的“高速”,在这里有明确的量化定义:当单线数据速率超过200Mbps,且需要稳定、低误码、低抖动传输时,就必须绕过通用IO逻辑,直连SERDES硬核或其简化版ISERDES/OSERDES。而“工程实践”四个字,恰恰意味着你不能只看手册里“例化一个IP核就完事”,必须理解它内部的采样机制、时钟约束关系、复位时序要求,以及最关键的——它和你板级PCB走线、电源完整性、参考时钟抖动之间的耦合效应。比如,一个常见的坑是:ISERDES的CLKDIV时钟频率算出来是125MHz,你直接用PLL生成这个时钟,却忽略了该时钟必须与输入数据边沿保持严格的相位关系,否则采样点会漂移。这已经不是代码问题,而是整个信号链路的协同设计问题。
2. ISERDES与OSERDES的本质:不是“串并转换”,而是“时间域重构引擎”
很多人把ISERDES简单理解为“串行转并行”,OSERDES理解为“并行转串行”,这就像说汽车发动机只是“把汽油变成轮子转动”一样,完全忽略了其内在的精密时序控制机制。它们真正的本质,是FPGA IO结构中用于重构数据在时间域上的采样与重构窗口的专用硬件。我们拆开来看:
2.1 ISERDES:如何在亚皮秒级精度上“切片”高速数据流
以Xilinx 7系列FPGA的ISERDES为例,其核心是一个由多个并联采样器组成的阵列。假设你配置为DDR模式、DATA_WIDTH=8(即8:1转换),那么它内部实际启用了8个独立的采样触发器,每个触发器由同一个源时钟(CLK)的不同相位驱动。关键点在于:这8个相位不是随意生成的,而是通过FPGA内部的专用延迟链(Delay Chain)精确控制,彼此间隔恰好为数据周期的1/8。例如,若输入数据速率为800Mbps(周期1.25ns),则8个采样点将均匀分布在1.25ns的时间窗口内,间隔156.25ps。
提示:这个“均匀分布”不是理想化的数学概念,而是受工艺偏差、温度漂移影响的物理现实。因此ISERDES必须配合IDELAYE2原语使用——IDELAYE2不是可选配件,而是ISERDES的“校准探针”。它实时测量输入信号到达IO引脚的实际延迟,并动态调整采样点位置,确保总有一个采样器落在数据眼图的中心区域。没有IDELAYE2的ISERDES,就像没有瞄准镜的狙击枪,理论精度再高,实战必然脱靶。
2.2 OSERDES:如何把并行数据“编织”成精准的串行波形
OSERDES的工作原理是反向的“时间编织”。当你向OSERDES写入一个8位宽的数据字(D[7:0]),它不会简单地按顺序把D[0]到D[7]逐个输出。相反,它内部有一个8级移位寄存器,由高速时钟(OSERDES_CLK)驱动。每来一个OSERDES_CLK上升沿,寄存器就左移一位,将最高位输出到IO引脚。因此,要输出D[7:0],你需要在8个OSERDES_CLK周期内,依次写入D[7], D[6], ..., D[0]。但这里的关键陷阱是:OSERDES_CLK必须与FPGA内部逻辑时钟(如100MHz系统时钟)严格同步,否则写入时序错乱,输出波形就会出现毛刺或重复位。
注意:OSERDES的输出并非直接连接到IO引脚,而是先经过OBUFDS(差分输出缓冲器)或IOBUF(双向缓冲器)。这意味着你必须在约束文件(XDC)中,为OSERDES_CLK、数据写入时钟、以及最终的IO引脚,同时添加时序约束。漏掉任何一环,综合工具就会把这段路径当作普通逻辑处理,导致布线延迟不可控,高速输出失真。
2.3 为什么不能用普通逻辑替代?——从“门级延迟”到“物理层抖动”的鸿沟
有人尝试用纯Verilog写一个8:1串并转换器,认为“不就是8个D触发器加一个多路选择器吗”。理论上可行,但工程上灾难性失败。原因在于:普通逻辑单元的建立/保持时间(Setup/Hold Time)是纳秒级的,而800Mbps数据的位宽只有1.25ns。一个典型的LUT输出延迟约0.5ns,布线延迟在长距离下可达1ns以上,再加上时钟偏斜(Clock Skew),最终导致某些位采样在数据有效窗口之外。更致命的是,普通逻辑无法对抗电源噪声引起的抖动(Jitter)——当VCCINT电压波动10mV时,LUT延迟可能变化50ps,这对156.25ps的采样间隔是毁灭性的。而ISERDES/OSERDES的采样电路是专门设计的模拟前端,具有极高的电源抑制比(PSRR)和内置的相位检测反馈环路,这是任何RTL代码都无法模拟的物理特性。
3. 工程实践全流程:从需求分析到上板调试的七步法
我把一个典型的ISERDES/OSERDES项目拆解为七个不可跳过的步骤,每一步都对应一个真实的工程决策点。下面以“设计一个1.2Gbps LVDS接收接口,对接工业相机输出”为例,全程实操记录。
3.1 第一步:需求反推——确定SERDES工作模式与参数
拿到相机手册,关键参数是:LVDS差分对,数据速率1.2Gbps,8位并行数据打包为10位(含2位控制码),采用DDR模式(双沿采样)。这意味着:
- 单线速率 = 1.2Gbps
- 实际数据有效位宽 = 8 bits per cycle
- 由于DDR,每个时钟周期采样2次,因此所需ISERDES采样时钟(CLK)频率 = 1.2Gbps / 2 = 600MHz
- 为得到8位并行输出,需配置ISERDES为8:1转换,故CLKDIV时钟(即内部并行数据输出时钟) = 600MHz / 8 = 75MHz
实操心得:这里有个常见误区——认为CLKDIV必须等于系统时钟。实际上,CLKDIV可以是任意频率,只要满足:CLKDIV频率 ≤ FPGA内部逻辑能安全处理的最高频率(通常≤200MHz),且其相位必须与CLK严格同步。我曾在一个项目中,为匹配下游AXI总线,故意将CLKDIV设为100MHz,通过在ISERDES后加一级异步FIFO做跨时钟域桥接,效果比强行拉高CLKDIV更稳定。
3.2 第二步:IO资源规划——在物理层面锁定引脚与Bank
打开Xilinx Vivado的Pin Planner,查看目标器件(如XC7A35T)的IO Bank分布。LVDS信号必须成对使用(P/N),且同一组LVDS对必须位于同一个IO Bank内,因为Bank内的VCCO电压和参考电压(VREF)是共享的。我选择Bank 34,其VCCO=1.8V,支持LVDS_25标准。将相机的8对LVDS数据线(D0P/D0N ~ D7P/D7N)和1对时钟线(CLKP/CLKN)全部分配到Bank 34内。特别注意:CLKP/CLKN必须使用专用的全局时钟引脚(如H15/G15),否则无法驱动ISERDES的CLK输入。
提示:不要忽略电源引脚!Bank 34需要独立的VCCO_34供电,且必须添加足够数量的去耦电容(建议0.1uF + 10uF组合,紧贴FPGA焊盘)。我在早期版本中,因VCCO_34滤波不足,导致ISERDES在高温下误码率飙升,更换为4颗0.1uF陶瓷电容后问题消失。
3.3 第三步:IDELAYE2校准——不是“设置一个值”,而是构建闭环反馈
IDELAYE2的配置不是静态的。它需要在FPGA上电后,执行一个“校准序列”:先用内部参考时钟触发IDELAYE2的计数器,测量输入信号从引脚到采样点的延迟;然后根据测量结果,动态写入DELAY_VALUE寄存器。这个过程必须在ISERDES开始接收有效数据前完成。我的做法是:
- 在顶层模块中,例化一个IDELAYCTRL原语,为其提供稳定的200MHz参考时钟;
- 将IDELAYE2的C端口连接到IDELAYCTRL的REFCLK;
- 编写一个简单的状态机,在系统复位后,等待10us,然后启动IDELAYE2的CAL信号,持续256个周期;
- 校准完成后,将IDELAYE2的输出(DATAIN)连接到ISERDES的D端口。
实操心得:IDELAYE2的DELAY_SRC必须设为"IDATAIN",即延迟对象是输入数据本身,而不是时钟。我曾错误地将其设为"CLK",导致校准的是时钟路径而非数据路径,结果眼图完全闭合。另外,IDELAYE2的MODE应设为"VAR_LOAD",允许在运行时动态更新延迟值,这对应对温度漂移至关重要。
3.4 第四步:ISERDES例化与参数配置——避开手册里的“默认陷阱”
Xilinx官方IP Catalog里的ISERDES IP核,虽然方便,但隐藏了太多细节。我坚持手写原语例化,以完全掌控每一个参数。关键配置如下:
ISERDESE2 #( .DATA_WIDTH(8), // 必须与实际数据位宽一致 .INTERFACE_TYPE("MEMORY"),// 选择"MEMORY"模式,获得最佳时序性能 .IOBDELAY("IFD"), // 使用IDELAYE2,而非内部固定延迟 .SERDES_MODE("MASTER"), // 主模式,用于单通道接收 .NUM_OF_BITS(2) // DDR模式,每次采样2位 ) uut_iserdese2 ( .Q0(q0), .Q1(q1), .Q2(q2), .Q3(q3), .Q4(q4), .Q5(q5), .Q6(q6), .Q7(q7), .CLK(clk_600m), // 高速采样时钟 .CLKB(~clk_600m), // DDR互补时钟 .CLKDIV(clkdiv_75m), // 并行输出时钟 .RST(rst_sync), // 同步复位,必须持续至少4个CLKDIV周期 .D(data_in_delayed) // 经IDELAYE2延迟后的数据 );注意:
.INTERFACE_TYPE("MEMORY")是关键。很多教程用"NETWORKING"模式,但它会引入额外的时序裕量(Timing Margin),牺牲了最大工作频率。在高速场景下,必须选"MEMORY",并手动确保所有时序约束到位。
3.5 第五步:时序约束——不是“写几行代码”,而是定义物理世界的规则
XDC约束文件是ISERDES工程的生命线。以下是我实际使用的约束片段:
# 定义输入时钟约束(来自外部晶振) create_clock -name sys_clk -period 10.000 [get_ports clk_in] # 为ISERDES的CLK生成衍生时钟 create_generated_clock -name clk_600m -source [get_pins uut/uut_iserdese2/CLK] -divide_by 1 [get_pins uut/uut_iserdese2/CLK] # 约束输入数据相对于CLK的建立/保持时间 set_input_delay -clock clk_600m -max 0.4 [get_ports {data_in_p[*]}] set_input_delay -clock clk_600m -min -0.2 [get_ports {data_in_p[*]}] # 约束CLKDIV时钟的输出 create_generated_clock -name clkdiv_75m -source [get_pins uut/uut_iserdese2/CLKDIV] -divide_by 1 [get_pins uut/uut_iserdese2/CLKDIV] # 关键:约束IDELAYE2的延迟范围 set_property IDELAY_VALUE 15 [get_cells uut/uut_idelaye2]实操心得:“set_input_delay”的数值不是凭空写的。我用示波器实测了相机输出的时钟与数据之间的skew(偏斜),发现最大为+400ps,最小为-200ps,因此设置了上述-max/min值。如果只写一个固定值,综合工具会按最坏情况优化,导致布线过于保守,无法达到目标频率。
3.6 第六步:复位与同步——跨越三个时钟域的“握手协议”
ISERDES涉及三个时钟域:外部输入时钟(600MHz)、内部并行时钟(75MHz)、以及系统主时钟(100MHz)。复位信号必须在这三个域间安全传递。我的方案是:
- 首先,用100MHz时钟对异步复位(rst_n)进行两级同步,生成rst_sync_100m;
- 然后,用clkdiv_75m对rst_sync_100m再做两级同步,生成rst_sync_75m;
- 最后,用clk_600m对rst_sync_75m做两级同步,生成rst_sync_600m,作为ISERDES的RST输入。
提示:ISERDES的RST必须是高电平有效,且必须在CLKDIV时钟下保持至少4个周期。因此,rst_sync_600m的释放时刻,必须与clkdiv_75m的上升沿对齐。我在代码中加入了
always @(posedge clkdiv_75m) rst_sync_600m_dly <= rst_sync_600m;这样的延迟寄存器,确保RST信号在clkdiv_75m域内被干净释放。
3.7 第七步:上板调试——用“眼图”代替“波形”,用“误码率”代替“功能”
上板后,第一件事不是看ILA抓到的数据是否正确,而是用示波器看眼图。将ISERDES输出的并行数据(q0~q7)通过一个高速DAC转换为模拟信号,接入示波器。一个健康的800Mbps LVDS接收眼图,应该满足:
- 眼高 > 600mV(LVDS标准差分摆幅)
- 眼宽 > 0.7UI(Unit Interval,即1.25ns * 0.7 ≈ 875ps)
- 交叉点抖动(Crossing Point Jitter) < 15ps
如果眼图闭合,立即检查:
- PCB走线是否等长(误差<5mil)?
- 电源纹波是否<20mVpp(用示波器AC耦合测量VCCINT)?
- IDELAYE2的CAL是否成功(用ILA监测CAL_DONE信号)?
实操心得:我曾遇到一个案例,眼图正常,但数据校验失败。用逻辑分析仪抓取q0~q7,发现偶数位总是错。最后定位到是IDELAYE2的DELAY_VALUE被错误地写成了奇数,导致采样点落在了数据过渡沿上。解决方案:强制将DELAY_VALUE设为偶数,并在代码中加入
delay_value <= delay_value & (~2'b11);进行对齐。
4. 常见问题与排查技巧实录:那些手册里不会写的“血泪教训”
在数十个FPGA高速接口项目中,我总结出一套高效的问题排查流程。以下是最常遇到的五个问题,附带真实现场记录和独家解决技巧。
4.1 问题一:ISERDES输出数据“随机翻转”,时好时坏
现象描述:ILA抓取的q0~q7数据,在连续运行2小时后,突然出现某一位(如q3)持续为0,重启FPGA后恢复,但几小时后又复现。
排查过程:
- 第一步:排除代码逻辑。用固定测试向量注入ISERDES,问题依旧,确认非RTL错误。
- 第二步:检查电源。用示波器测量VCCINT,发现存在200kHz的周期性纹波,峰峰值达80mV。
- 第三步:溯源纹波。追踪到是板载DCDC的反馈环路不稳定,其开关噪声通过电源平面耦合到IO Bank。
根本原因:ISERDES的采样电路对电源噪声极其敏感。80mV的纹波足以使内部比较器的阈值发生偏移,导致采样判决错误。
解决方案:
- 在VCCINT电源入口处,增加一个LC滤波器(1uH电感 + 100uF钽电容);
- 为IO Bank的VCCO单独敷铜,并在其电源引脚旁放置4颗0.1uF X7R陶瓷电容;
- 在XDC中,为ISERDES相关路径添加
set_false_path -from [get_clocks clk_600m] -to [get_clocks clkdiv_75m],避免综合工具过度优化导致的时序冲突。
独家技巧:在Vivado中,启用“Power Analysis”功能,导入PCB的电源网络模型,可以提前预判电源完整性风险。这比上板后抓波形快十倍。
4.2 问题二:OSERDES输出波形“多出半个周期”,下游设备无法识别
现象描述:用示波器观测OSERDES输出的LVDS信号,发现第一个数据位的起始沿比预期晚了约0.625ns(即半个1.2Gbps周期)。
排查过程:
- 第一步:检查OSERDES例化代码,确认DATA_WIDTH=8,NUM_OF_BITS=2(DDR)。
- 第二步:用ILA抓取OSERDES的输入数据(D[7:0])和写入时钟(OSERDES_CLK),发现数据写入时刻与OSERDES_CLK上升沿对齐良好。
- 第三步:深入阅读UG471手册,发现OSERDES有一个隐藏的“INIT”参数,默认为0,表示“初始状态为0”。但我们的协议要求第一个输出位必须是帧头标志(0x01),因此需要将INIT设为8'h01。
根本原因:OSERDES在复位释放后,其内部8级移位寄存器的初始值为全0。当第一个数据字(0x01)写入时,寄存器左移,输出的是0,而不是0x01的最高位。这导致整个数据流向右偏移了一位。
解决方案:
- 在OSERDES例化中,显式设置
.INIT(8'h01); - 在复位释放后,增加一个“预填充”阶段:连续写入8个0xFF,清空移位寄存器,然后再写入有效数据。
独家技巧:用Vivado的“Waveform Viewer”功能,对OSERDES原语进行门级仿真,可以直观看到移位寄存器的每一位状态变化,比纯RTL仿真更能暴露此类问题。
4.3 问题三:IDELAYE2校准失败,CAL_DONE信号永不拉高
现象描述:FPGA上电后,CAL_DONE信号保持低电平,IDELAYE2的CNTVALUEOUT始终为0。
排查过程:
- 第一步:确认IDELAYCTRL已正确例化并供时钟。
- 第二步:用ILA监测IDELAYCTRL的RDY信号,发现其为低电平。
- 第三步:查阅UG471,发现RDY为低,表示IDELAYCTRL未完成内部校准,原因通常是参考时钟频率不在允许范围内(Xilinx要求200MHz±10%)。
根本原因:我使用的参考时钟是195MHz,虽接近200MHz,但低于下限。IDELAYCTRL的内部RC振荡器无法在此频率下稳定锁定。
解决方案:
- 更换参考时钟为200MHz晶振;
- 或者,在IDELAYCTRL例化中,将
REFCLK_FREQUENCY属性显式设为195.0,强制其接受该频率(需查证器件手册是否支持)。
独家技巧:在Vivado中,右键点击IDELAYCTRL原语,选择“Customize IP”,在GUI界面中可以直接修改REFCLK_FREQUENCY,比手写属性更直观。
4.4 问题四:跨时钟域数据丢失,FIFO深度永远不为0
现象描述:ISERDES输出的75MHz并行数据,经异步FIFO送入100MHz系统,但下游模块始终收不到完整帧。
排查过程:
- 第一步:用ILA抓取FIFO的wr_data、wr_en、rd_data、rd_en,发现wr_data有数据,wr_en有效,但rd_data始终为0。
- 第二步:检查FIFO状态信号,发现full信号为高,说明写入端堵死。
- 第三步:计算FIFO深度。ISERDES每75MHz周期写入1字节,100MHz系统每100MHz周期读取1字节,理论深度只需1。但实际需要考虑burst写入和读取的间隙。
根本原因:异步FIFO的格雷码指针在跨时钟域同步时,因亚稳态未被充分过滤,导致指针值错误,FIFO误判为满。
解决方案:
- 将FIFO的写入端口(wr_ptr)和读取端口(rd_ptr)的格雷码宽度,从默认的4位扩展到6位;
- 在同步链路上,将两级触发器改为三级触发器(Triple-Metastability Hardening);
- 在XDC中,为FIFO的跨时钟域路径添加
set_false_path -through [get_pins fifo_inst/rd_ptr_reg[*]/C],避免工具对亚稳态路径做时序优化。
独家技巧:用Vivado的“Report CDC”功能,自动生成跨时钟域报告,它会标出所有未约束的CDC路径,比人工检查快得多。
4.5 问题五:时序报告“Failed Paths”,但功能测试全绿
现象描述:Vivado的时序分析报告中,有数百条路径显示“Setup Violation”,最大负裕量达-120ps,但上板后所有功能测试均通过。
排查过程:
- 第一步:确认这些违规路径是否属于ISERDES内部路径。用
report_timing -from [get_pins uut/uut_iserdese2/CLK] -to [get_pins uut/uut_iserdese2/Q0],发现违规路径正是ISERDES的采样路径。 - 第二步:查阅UG471,发现ISERDES的内部路径是“时序例外路径”(Timing Exception Path),其时序由厂商保证,无需用户约束。
根本原因:Vivado默认对所有路径进行时序分析,但ISERDES的内部采样路径(从CLK到Qx)是黑盒,其延迟已在器件数据库中固化,综合工具无法也不应该对其进行优化。
解决方案:
- 在XDC中,添加时序例外:
set_false_path -from [get_pins uut/uut_iserdese2/CLK] -to [get_pins uut/uut_iserdese2/Q*] set_false_path -from [get_pins uut/uut_iserdese2/CLKB] -to [get_pins uut/uut_iserdese2/Q*]- 同时,为IDELAYE2的输出路径也添加例外,因为其延迟是可编程的,但已由IDELAYCTRL校准保证。
独家技巧:在Vivado中,“Timing Constraints”窗口下的“Exceptions”标签页,可以图形化创建和管理这些false path,比手写TCL更不易出错。
5. 进阶实战:从单通道到多通道,构建可扩展的高速接口架构
单个ISERDES/OSERDES只是起点。在真实项目中,你往往需要处理多路并行数据流,比如一个4K@60Hz的MIPI CSI-2摄像头,需要4个数据通道(Lane0~Lane3)加1个时钟通道。这时,架构设计就决定了项目的成败。
5.1 多通道时钟域管理:避免“时钟风暴”
四个LVDS通道,每个都需要600MHz采样时钟。如果为每个通道单独生成一个600MHz PLL,FPGA的PLL资源会迅速耗尽,且各时钟间的相位关系无法保证。我的方案是:
- 只用一个MMCM生成一个600MHz主时钟;
- 用BUFGCE(时钟使能缓冲器)为每个通道分配独立的时钟使能信号;
- 通过IDELAYE2的动态延迟调整,补偿各通道PCB走线长度差异(最大可达500ps)。
这样,所有通道共享同一个时钟源,相位一致性极高,且资源占用减少75%。
5.2 数据对齐:从“通道对齐”到“帧对齐”
四个通道的数据,在ISERDES输出后,必须在同一个时钟域下对齐成一个32位字。难点在于:各通道的IDELAYE2校准时间不同,导致数据到达时间有微小差异。我的做法是:
- 在每个通道后,添加一个“Alignment FIFO”,深度为4;
- 用一个全局帧同步信号(如MIPI的SYNC_PULSE),同时复位所有FIFO;
- 当所有FIFO的rd_data_valid信号同时为高时,认为数据已对齐。
实操心得:Alignment FIFO的rd_en信号,必须由一个“与门”生成,即
rd_en = fifo0_rd_valid && fifo1_rd_valid && fifo2_rd_valid && fifo3_rd_valid。这确保了只有当所有通道都有有效数据时,才读取,彻底避免了数据错位。
5.3 可扩展性设计:为未来升级预留“物理层接口”
在PCB设计阶段,我就为每个LVDS通道预留了两套引脚:一套用于当前的1.2Gbps速率,另一套用于未来升级到1.6Gbps。具体做法是:
- 在Bank 34内,为每个通道分配2对LVDS引脚(如D0P/D0N 和 D0P_ALT/D0N_ALT);
- 在原理图中,用0欧姆电阻选择其中一对;
- 在FPGA代码中,通过顶层参数
SPEED_GRADE控制ISERDES的DATA_WIDTH和CLKDIV频率。
这样,当需要升级速率时,只需更换电阻、更新bitstream,无需改板。
我个人在实际操作中的体会是:FPGA高速接口设计,70%的功夫在板级,30%在代码。一个完美的ISERDES例化,如果放在一条阻抗失控的PCB走线上,结果就是零。所以,永远把示波器和网络分析仪当成你的第一开发工具,而不是Vivado。