Vivado 中实现 LVDS 串行通信的设计流程
LVDS(Low-Voltage Differential Signaling)因其抗干扰能力强、功耗低、传输距离远等优势,成为机器视觉、雷达信号处理等领域的高速接口首选。Xilinx 7 系列及以后的 FPGA 原生支持 LVDS 电平标准,无需外挂 PHY 芯片。
为什么 LVDS 成了高速接口的首选?
在机器视觉、雷达信号处理、工业相机这些领域,动辄上百 Mbps 甚至 Gbps 的数据量,传统单端信号早就不堪重负。而 LVDS 之所以能成为主流选择,靠的是它与生俱来的三项硬实力:
- 抗干扰能力强:差分结构天然抑制共模噪声,哪怕在电机旁边也能稳定工作。
- 功耗低:恒流源驱动,3.5mA 电流就能跑出 655 Mbps 以上的速率。
- 传输距离远:双绞线上传输几米不成问题,EMI 还小得惊人。
举个例子,一台 1080p@60fps 的 CMOS 相机,原始数据率轻松突破 1 Gbps。如果用并行 TTL 传输,不仅布线复杂、易受干扰,PCB 根本没法做。但换成 8 通道 LVDS,每路跑 125–150 Mbps,系统瞬间变得简洁可靠。
FPGA 里是怎么'看懂'LVDS 信号的?
FPGA 内部有一套完整的差分信号处理链路,理解这套机制,是避免后续踩坑的前提。
差分输入:IBUFDS 是第一道门
外部 LVDS 信号进入 FPGA 的第一站,就是 IBUFDS —— 差分输入缓冲器。
IBUFDS u_ibuf (
.I(clk_p), // 正端
.IB(clk_n), // 负端
.O(clk_250m) // 单端输出
);
这个模块干了三件事:
- 接收一对差分电压(典型摆幅 350mV)
- 判定逻辑高低(>100mV 为 1,<-100mV 为 0)
- 输出标准 CMOS 电平供内部逻辑使用
关键点来了:你必须确保这对引脚位于支持 LVDS 的 Bank 内。比如 Artix-7 的 HR Bank 可以支持 LVDS_25 或 LVDS_33,但某些低电压 Bank 就不行。翻错手册,硬件就废了。
而且,要不要启用片内终端?可以通过参数控制:
IBUFDS #( .DIFF_TERM("TRUE") // 开启 100Ω片内端接 ) u_ibufds (...);
开启后,PCB 上就可以省掉外接的 100Ω电阻,适合短距离板内连接;但如果走线很长或需要驱动多个负载,建议还是外置终端更稳妥。
差分输出:OBUFDS 把数据送出去
反过来,你想把 FPGA 里的数据以 LVDS 格式发出去,就得用 OBUFDS。
OBUFDS #( .IOSTANDARD("LVDS_25") ) u_obuf (
.I(data_from_fpga),
.O(data_p),
.OB(data_n)
);
注意 .I 是单端输入,.O/.OB 自动生成差分对。这里 .IOSTANDARD 必须和 Bank 电压匹配——2.5V Bank 写 LVDS_25,3.3V Bank 写 LVDS_33,否则可能烧毁 IO。
Vivado 工程怎么搭才不翻车?
别小看工程结构,一个清晰的目录组织,能让你后期维护省下大把时间。
推荐这样布局:
lvds_camera_project/
├── src/
│ ├── top.v
│ ├── lvds_rx.v // LVDS 接收控制器
│ └── frame_buffer_ctrl.v
├── constraint/
│ └── pin.xdc // 引脚与时序约束
├── ip/
│ └── clk_wiz_0.xci // 时钟 IP 核
└── sim/
└── tb_lvds_rx.v // 功能仿真测试平台
新建工程时选"RTL Project",不要勾选"Do not specify sources",方便后续管理文件。
器件型号一定要准确填写,比如 xc7a200tfbg676-2,因为不同速度等级对应的时序特性不一样,综合工具会据此优化路径延迟。
引脚约束不是贴标签,而是定规则
很多初学者以为,只要把管脚连上就行。结果一进 Implementation 阶段,全是红色警告:"Unconstrained Pin"。
记住一句话:所有 LVDS 端口都必须有明确的电气标准和时序定义。
来看一段真实的 XDC 约束:
# === LVDS 输入时钟 ===
set_property PACKAGE_PIN AB12 [get_ports rx_clk_p]
set_property PACKAGE_PIN AB11 [get_ports rx_clk_n]
set_property IOSTANDARD LVDS_25 [get_ports rx_clk_p]
create_clock -name sensor_clk -period 8.000 [get_ports rx_clk_p]
# === LVDS 数据通道(8 位)===
set_property PACKAGE_PIN Y14 [get_ports rx_data_p[0]]
set_property PACKAGE_PIN Y13 [get_ports rx_data_n[0]]
set_property PACKAGE_PIN W14 [get_ports rx_data_p[1]]
set_property PACKAGE_PIN W13 [get_ports rx_data_n[1]]
# ... 其余通道类似
foreach p [get_ports rx_data_p[*]] {
set_property IOSTANDARD LVDS_25 $p
}
# === 输入延迟约束 ===
set_input_delay -clock sensor_clk -max 1.8 [get_ports rx_data_p[*]]
set_input_delay -clock sensor_clk -min 0.4 [get_ports rx_data_p[*]]
重点解析:
create_clock告诉工具:这是我系统的主频来源。set_input_delay描述了数据相对于时钟的到达窗口。这个值不能乱填!得查传感器手册里的"Output Data Valid Window"参数。
如果你偷懒没加这些约束,Vivado 默认按最宽松的情况处理,最后生成的比特流很可能在实际硬件上跑飞。
高速采样靠什么?MMCM + IDELAY2 黄金组合
LVDS 常用于源同步传输,即随路携带时钟。这时候最大的挑战是:如何让 FPGA 内部时钟精准对齐外部数据的有效窗口?
答案是相位调整。
用 MMCM 生成 90°相移时钟
假设输入数据速率是 125 Mbps,对应时钟周期 8 ns。如果我们用同相时钟去采样,刚好落在数据跳变沿附近,极易出错。
解决办法:用 MMCM 生成一个滞后 90°的时钟,在数据最稳定的中间点采样。
操作步骤:
- 打开 IP Catalog → 搜索 Clocking Wizard
- 输入时钟:125 MHz(来自 IBUFDS)
- 输出两个时钟:
clk_out1: 125 MHz, 0° 相移(给主逻辑用)clk_out2: 125 MHz, 90° 相移(专门用于 DDR 采样)
生成 IP 后例化:
clk_wiz_0 u_clk_wiz (
.clk_in1(rx_clk_250m),
.reset(rst),
.clk_out1(sys_clk),
.clk_out2(sample_clk_90)
);
现在你有了一个'黄金采样时钟',大大提升建立保持余量。
还不够准?上 IDELAY2 微调!
即使有了 90°相移,由于 PCB 走线差异、器件温漂等因素,最佳采样点仍可能偏移几个百皮秒。
这时候就需要动态延迟单元登场——IDELAY2。
它能在 0~1.2ns 范围内以 78ps 步长调节输入信号的延迟,相当于给你一把显微镜级别的调焦旋钮。
典型用法:
IDELAY2 #(
.DELAY_SRC("IDATAIN"),
.SIGNAL_PATTERN("DATA"),
.CINVCTRL_SEL("FALSE"),
.HIGH_PERFORMANCE_MODE("TRUE")
) u_idelay (
.IDATAIN(data_raw),
.DATAOUT(data_delayed),
.CE(ce_inc),
.INC(inc),
.LD(ld),
.CLK(clk_200m),
.IOCLK0(ioclk)
);
配合状态机实现自动训练流程:
- 上电复位后加载初始延时值
- 启动数据接收,观察误码情况
- 若错误多,则递增/递减延时,扫描整个窗口
- 找到误码最少的那个点,锁定为最优设置
你可以把这个过程想象成'自动对焦':不断微调,直到眼图完全睁开。
真正的杀手锏:ISERDES 实现高效串并转换
当你面对的是更高带宽需求,比如 Camera Link 或千兆视频流,单纯的 DDR 采样已经不够用了。这时就要祭出 Xilinx 的王牌模块——ISERDESE2。
它可以将高速串行数据(如 7:1 压缩)转换为本地时钟域下的并行信号,极大减轻逻辑负担。
来看一个实用配置:
ISERDESE2 #(
.DATA_RATE("DDR"),
.DATA_WIDTH(4),
.INTERFACE_TYPE("NETWORKING"),
.DYN_CLKDIV_INV_EN("FALSE"),
.DYN_CLK_INV_EN("FALSE"),
.NUM_CE(1),
.SERDES_MODE("MASTER")
) u_iser (
.D(rx_data_p), // 来自 IBUFDS 的信号
.CLK(clk_250m), // 高速采样时钟(250MHz)
.CLKB(~clk_250m), // 反向时钟
.CLKDIV(sample_clk_90),// 分频时钟(125MHz)
.RST(rst),
.SHIFTIN1(shiftin1),
.SHIFTIN2(shiftin2),
.Q4(Q) // 4 位并行输出
);
解释几个关键点:
.CLK是高速时钟(250MHz),负责每个边沿采样.CLKDIV是慢速时钟(125MHz),用来同步输出数据- 当
DATA_WIDTH=4,意味着每 4 个高速周期输出一次 4 位数据
配合 IDELAY2 使用,形成完整接收链:
rx_data_p → IDELAY2 → IBUFDS → ISERDESE2 → 并行数据
这一整套流水线下来,哪怕面对复杂的嵌入式时钟协议,也能稳稳拿下。
实战案例:工业相机图像采集系统
设想这样一个系统:
- CMOS 传感器输出 8 位 LVDS 数据 + 1 位随路时钟
- FPGA 接收后缓存至 DDR3
- MicroBlaze 读取并通过 UDP 上传 PC 显示
整个数据通路如下:
[Sensor] ↓ (LVDS @ 125Mbps × 8bits)
[FPGA (Artix-7)] ↓ (AXI4 Stream)
[FIFO → DDR3 Controller] ↓ (DMA)
[MicroBlaze + LWIP] ↓ (Ethernet)
[Host PC]
其中最关键的一环,就是 LVDS 接收是否稳定。
我们曾在一个项目中遭遇严重误码,排查发现竟是因为忽略了 Bank 供电去耦。虽然功能正常,但在高频切换时产生地弹,导致采样失败。后来在每个 Bank 电源引脚旁补上 0.1μF 陶瓷电容,问题迎刃而解。
另一个常见问题是跨时钟域处理不当。LVDS 数据进来是源同步时钟域,而 DDR 写入是系统时钟域,两者频率略有偏差。如果不加异步 FIFO 缓冲,迟早溢出。
解决方案很简单:用 XPM_FIFO_ASYNC 打一层胶水逻辑,实现安全跨时钟传递。
调试秘籍:ILA 才是你的终极武器
说得再多,不如亲眼看到波形实在。
强烈建议你在关键节点插入 ILA(Integrated Logic Analyzer)探针:
ila_0 u_ila (
.clk(clk_125m),
.probe0(rx_data_p),
.probe1(idelay_tap_value),
.probe2(iserdes_output),
.probe3(frame_valid)
);
然后跑起来,打开 Hardware Manager,实时查看:
- 原始 LVDS 信号质量
- IDELAY 调节过程
- ISERDES 输出是否连续
- 帧同步是否捕获成功
你会发现,很多你以为正确的设计,其实在信号层面早已扭曲变形。而 ILA 就像内窥镜,让你直视系统的'血液循环'。
最后划重点:那些年我们都踩过的坑
| 问题现象 | 根本原因 | 解决方案 |
|---|---|---|
| 数据乱码、误码率高 | 采样点未对齐 | 使用 MMCM+IDELAY2 联合调相 |
| Implementation 报 Timing Fail | 缺少 input delay 约束 | 补全 XDC 中的 set_input_delay |
| 引脚无反应 | IO 标准与 Bank 不匹配 | 查 UG475 确认电压兼容性 |
| 眼图闭合 | 差分走线不对等 | PCB 调整等长±10mil 以内 |
| 功耗异常 | 多个 LVDS Bank 同时切换 | 加强电源去耦,分散布局 |
还有三点最佳实践请牢牢记住:
- 差分走线坚持 3W 原则:线间距≥3 倍线宽,减少串扰
- 全程 100Ω阻抗控制:无论是走表层微带线还是内层带状线
- 避免跨分割平面布线:参考平面断裂会导致回流路径中断,引发振铃
掌握这些技能,你不只是会用 Vivado,而是真正掌握了高速接口的设计思维。而这,正是迈向高级 FPGA 工程师的关键一步。

