1. 为什么选高云GW2A-LV18PG256C8做DDR3+LVDS实战
国产FPGA这几年是真起来了,高云GW2A系列算是其中出货量很大的一条产品线。我选择GW2A-LV18PG256C8这块芯片做DDR3和LVDS的实战项目,核心原因有三个。
第一个原因是性价比。LV18这颗芯片的逻辑资源大概是18K LUT级别,内置了硬核DDR3控制器,同时还支持丰富的LVDS IO。这个配置放在同价位的进口芯片上基本找不到,之前用某国际大厂的芯片做类似项目,光DDR3软核授权就得额外掏一笔钱。GW2A把DDR3控制器直接做成硬核,省掉的授权成本和时间成本非常可观。
第二个原因是高云的工具链这几年进步明显。云源软件(Gowin YunYuan)从早期的各种不顺手,到现在已经比较稳定了。我这次项目从建工程到跑通DDR3读写和LVDS通信,整个开发流程走下来,遇到问题基本能在官方文档和社区里找到对应方案,这是一个生态逐步成熟的信号。
第三个原因是我踩过坑之后的经验积累。早些年用FPGA做项目,DDR3布线是个让人头大的事,阻抗匹配、等长控制、分组走线,每一处都需要仔细处理。而高云GW2A官方提供了DDR3参考设计和配套的PCB封装,严格按照官方设计来做,一次成功的概率会高很多。这次项目我把DDR3控制器从配置到读写验证完整走了一遍,再把LVDS通信接口打通,最终发现两者结合能应用到很多实际场景里,比如图像采集与显示、高速数据采集系统。
这篇文章我尽量把整个流程写清楚,适合手里有高云GW2A开发板、想从零跑通DDR3和LVDS的工程师参考。软件版本是云源软件,芯片型号就是标题里的GW2A-LV18PG256C8,开发板用的是第三方厂商做的核心板。
2. DDR3控制器配置与读写验证实操
2.1 硬核控制器的基本特性
GW2A系列内置的DDR3硬核控制器,官方名称叫DDR3 Memory Controller,支持DDR3/DDR3L颗粒,数据位宽可以配置为16位、32位,我这次用的是16位数据总线搭配一颗常见的DDR3L颗粒,容量512MB。控制器IP通过云源软件的IP Core Generator生成,配置界面比较直观,和Xilinx的MIG、Intel的EMIF工具流程类似,但参数设置上更精简一些。
配置控制器之前,先要明确几个关键参数:
- 内存颗粒型号、行列地址位宽和Bank数量
- 数据位宽与ECC是否启用
- 内存时钟频率(我配的是400MHz,即DDR3-800)
- 控制器接口模式(高云提供AXI4接口和Native接口两种模式)
高频内存接口设计里,时钟是个关键变量。DDR3控制器的输入时钟和用户逻辑时钟是两回事,控制器内部有PLL负责把输入时钟倍频到内存时钟。配置的时候需要输入一个参考时钟频率,我用的开发板上有50MHz有源晶振,通过PLL内部倍频到400MHz给DDR3物理层使用,用户逻辑侧时钟则是100MHz。
注意:DDR3控制器对参考时钟的占空比和抖动有要求,如果开发板上的时钟源质量一般,建议先通过示波器确认时钟波形再开始配置。
2.2 配置界面关键选项解析
打开IP Core Generator,选择DDR3 Memory Controller,配置界面分几个标签页。
Memory Type选择DDR3L,因为我板上用的颗粒是低电压版本,工作电压1.35V。如果选错成DDR3,电压1.5V,可能导致初始化失败甚至损坏颗粒,这一点要仔细核对颗粒丝印。Data Width选16,ECC选项默认关闭,这个项目数据量不大,不需要ECC纠错能力。
Address Configuration部分需要对照颗粒的datasheet填写。我用的这个颗粒是8Bank、行地址15位、列地址10位,这些参数如果填错,控制器初始化阶段就会卡住,读不到正确的模式寄存器配置结果。
Frequency Configuration是重要部分。High-Speed Mode选DDR3-800,Input Clock Frequency填50,PLL Reference Clock Frequency也填50。控制器会自动计算PLL的倍频分频系数。还有一项是CAS Latency,这个参数需要根据颗粒的时序参数表来设置,一般DDR3-800频率下CL=6比较常见,但最终要颗粒手册为准。
接口模式选择界面,我建议新手直接选Native接口。之前很多教程推荐AXI4接口,但高云这个硬核控制器的AXI4从机接口封装得不算特别顺手,突发长度限制和地址对齐规则有点绕。Native接口简单直接,用户逻辑自己写状态机控制读写,对理解DDR3时序本质很有帮助。等Native接口跑通了,再切到AXI4做大数据量传输也不迟。
2.3 初始化时序和用户逻辑设计
DDR3控制器上电后会有一段初始化过程,包括复位、时钟稳定等待、DQS gate训练、ZQ校准等一系列内部操作。这个过程由硬核自动完成,用户逻辑只需要在初始化完成信号拉高之后,再向控制器发起读写请求。
初始化完成信号在Native接口里叫init_done,设计状态机前要把它作为总使能信号。没有init_done置位之前,不要向控制器发送任何读写命令,否则命令会被直接忽略,还会扰乱内部状态。
我设计的读写验证状态机分四步:
- 等待
init_done拉高 - 向特定地址顺序写入一串固定数据(0xAA55AA55、0x55AA55AA交替,或递增计数)
- 写完成之后等待一段时间,再向相同地址发起读请求
- 比较读回数据和写入数据是否一致,通过LED或串口打印结果
Native接口用户逻辑的核心信号包括:
cmd、cmd_en:命令通道,支持写、读、刷新等命令addr:32位地址输入wr_data、wr_data_en、wr_data_mask:写数据通道rd_data、rd_data_valid:读数据通道
这里有个容易踩坑的地方:高云DDR3控制器的地址映射逻辑。Native接口地址是经过编码的地址,不是直接用行列地址映射,比如功能地址中的某些位对应Bank地址,某些位对应行地址。如果写读操作使用的地址不一致,数据校验会失败,误以为是控制器配置错误。实际排查方法是用一个简单的递增地址模式,从0开始连续写读,对比哪个bit位对不上,结合官方地址映射表(文档里叫Address Mapping)反查原因。
写突发长度固定为8(即一次写命令连续传输8个数据),所以写128个数据,实际上只需要发起16次写命令,每次突发跨越两个256MB存储空间的概念需要心里有数。读操作同理,读128个数据需要16次读命令。
2.4 仿真验证和板级实测
配置完控制器、写完用户逻辑,不要急着上板。官方IP生成目录下自带了DDR3的仿真模型(通常是一个带时序参数的Verilog模型文件),搭建一个简单的testbench,把控制器实例化和仿真模型连起来,先做仿真验证。
仿真时需要注意几个点:
- 初始化过程仿真时间比较长,建议把仿真时间设置到至少200us,等待
init_done信号拉高 - 读写数据比较逻辑在testbench里做,不要让用户逻辑直接输出比较结果,这样便于仿真波形查看
- 如果仿真中出现初始化失败,优先检查时钟频率配置和地址配置,然后再查IP版本是否和软件版本匹配
仿真通过后烧录到开发板。我这次实测时遇到一个比较隐蔽的问题:开发板上的DDR3颗粒没有正确连接复位引脚,导致控制器初始化一直失败。检查原理图发现,DDR3复位信号被拉到了FPGA的一个普通IO上,而高云硬核控制器的初始化流程里,复位信号需要特殊时序配合,后来按官方参考设计调整后问题解决。
板级实测阶段,我习惯的做法是先在内部生成一个递增计数器作为写数据源,写进去再读出来,读出的数据经过一个比较模块,结果输出到LED灯。全对则LED常亮绿色,有错误则LED闪烁。这样调试效率很高,不需要每次都用逻辑分析仪看。
3. LVDS接口通信实现细节
3.1 GW2A的LVDS资源与引脚约束
DDR3读写跑通之后,我做的第二部分是LVDS通信。LVDS(Low Voltage Differential Signaling)是FPGA板间通信中特别常用的一种电平标准。相比单端信号,LVDS用两根差分线传输,共模抑制能力强,速率可以跑到几百Mbps甚至Gbps级别。
GW2A-LV18PG256C8的引脚支持LVDS25、LVDS33等不同电平标准,但并不是所有IO都能做LVDS,只有特定的差分对引脚才支持。这一点在引脚约束时要格外小心,如果随便选两个普通IO配置成LVDS,编译会直接报错,或者更隐蔽的是编译能过,但实际信号完全不对。
查阅芯片引脚手册(Package Pinout),支持LVDS的引脚通常以P/N成对出现,比如IO_L1P、IO_L1N这种命名方式。我在工程里用的是Bank 2的一对差分引脚,约束文件里的写法是:
IO_PORT "lvds_tx_p" LOCATION: A2, IO_STANDARD: LVDS25; IO_PORT "lvds_tx_n" LOCATION: A1, IO_STANDARD: LVDS25;这里有个细节:高云软件里LVDS差分引脚必须同时指定P和N,不能只约束P端,否则编译时软件无法自动推断N端位置。同时,接收端和发送端的IO_STANDARD必须一致,都是LVDS25,否则信号无法正确解析。
提示:如果开发板原理图上标注了差分对,但引脚命名和芯片手册不一致,要优先以芯片手册为准。开发板厂商有时会把差分对引到排针上,但排针走线阻抗不连续,超过200Mbps速率就很容易产生误码。
3.2 LVDS发送与接收的两种实现路径
高云FPGA实现LVDS接口有两条路径,我这次都验证了。
第一条路径是用原语(Primitive),高云提供了ELVDS_IO这类原语,可以配置成发送或接收模式,发送时把并行数据串行化输出到差分引脚,接收时把差分输入解串成并行数据。优势是可控性强,缺点是时序要自己约束好。
第二条路径是用IP核。云源软件里有专门的高速率LVDS接口IP,支持1:7、1:8等串化比,适合把并行数据转成高速串行流,常用于摄像头(MIPI CSI-2需要额外协议层,但底层物理层就是LVDS)和显示接口。IP的优势是底层DDR寄存器和延迟链已经自动约束好,用户只需要关注并行侧逻辑。
我的测试工程比较简单,发送端用一个计数器产生并行数据,经过LVDS IP发送出去;接收端用另一块开发板(或者回环测试)接收,解串后对比数据。单板回环测试时,发送差分对直接通过短跳线连接到接收差分对。
实际测试时,短距离回环和长距离传输的体验差异很大。用5厘米短跳线连接时,1.2Gbps速率稳定无误差。换成20厘米的杜邦线双绞连接后,误码率明显提升,这是因为杜邦线阻抗完全不受控,差分信号完整性被破坏。板间高速LVDS通信建议使用专用的差分走线,PCB上的阻抗控制在100欧姆差分阻抗。
3.3 多通道LVDS的位对齐处理
如果只是单通道LVDS回环,调试比较简单。但实际项目中往往用到多通道LVDS,比如3路RGB接口转LVDS显示,或者4通道图像数据传输,这时会遇到一个棘手问题:通道间偏移(Skew)。
多通道LVDS信号经过PCB走线和连接器,各通道的传播延迟天然存在差异,接收端解串后各通道并行数据的bit位置可能错位。比如通道0解串后第1个bit是bit0,通道1解串后第1个bit可能成了bit6,直接拼接数据必然出错。
解决方法是做通道对齐,常用方案是发送端在数据里嵌入训练序列,接收端检测到训练序列后调整各通道的解串bit偏移,最终让所有通道对齐。高云的LVDS IP里有一个硬件特性叫Bit Slip,专门用来调整解串器的bit偏移。
我在测试工程里实现了一个简化的对齐逻辑:
- 复位后发送端周期性发送固定训练字0xF0F0
- 接收端监听并行数据,当捕获到训练字后,对该通道设置一个标志
- 如果多个通道捕获训练字的时刻不同,就通过调整对应通道的Bit Slip值直到对齐
这个过程如果完全用手写逻辑做,工作量不小。好在高云LVDS IP提供了可选的对齐状态输出,可以省去一部分设计工作。如果项目里用的是普通IO口自己搭的LVDS收发器,Bit Slip就需要自己实现了,核心思路都是在接收端串行数据流里做延迟选择,调整解串边界。
3.4 时序约束与时钟域处理
LVDS接口调试中,时序约束是最容易被忽略但又至关重要的环节。高云软件里对LVDS差分信号的约束,和普通单端信号约束差异很大。
首先是输入延迟约束。接收端LVDS信号从引脚进入,经过输入缓冲和延迟链,从系统同步时钟角度看,需要设定input delay约束。这个约束值根据PCB走线长度和串化比估算,如果约束值设置不当,接收端采样点大概率落在数据翻转区域内,出现随机误码。
我习惯的做法是先跑一遍静态时序分析,看软件给出的setup/hold裕量,再根据裕量调整约束值。如果某个通道的时序裕量为负,首先检查该通道PCB走线是否有额外过孔或者长度补偿不当。
第二个是跨时钟域处理。LVDS接收解串后的并行数据是在接收时钟域(Rx Clock)下的,而用户逻辑和DDR3控制器工作在另一个时钟域,两者之间需要做异步FIFO或者握手。我直接调用了高云的异步FIFO IP(Gowin_FIFO),读写时钟分别接接收时钟和用户时钟。FIFO深度按最大数据突发量设置,避免溢出。
第三个是发送端的位对齐。发送侧并行数据进入串化器之前,需要保证数据在正确的时钟沿采样,通常用发送时钟的上升沿打一拍再进串化器。高云LVDS IP内部已经处理了这部分,如果自己搭逻辑,就要格外注意这一点。
4. 工程架构设计与整体联调
4.1 系统层次划分
这个项目整体上分成三个模块:DDR3存储模块、LVDS通信模块和顶层控制模块。顶层控制负责接收外部命令(我用板上按键和拨码开关来做),把数据从LVDS接口接收并写入DDR3,或者从DDR3读出数据通过LVDS发送出去。
顶层模块的端口定义大致是:
- 输入:系统时钟50MHz、复位按键、拨码开关(控制读写方向)、LVDS接收差分对
- 输出:LED状态指示、LVDS发送差分对、预留的调试串口
逻辑内部实例化DDR3控制器IP、LVDS IP、异步FIFO和数据通路控制状态机。DDR3控制器IP通过Native接口连接到用户逻辑,LVDS IP的并行侧数据经过异步FIFO与DDR3数据总线对接。
这样的设计结构便于单独调试,DDR3部分写一个测试状态机,LVDS部分写一个回环测试逻辑,各自验证通过后再将两者连起来。
4.2 数据通路设计与流控机制
数据通路分为两个方向。接收链路是LVDS接收并行数据先进入异步FIFO,再通过写状态机写入DDR3。发送链路是DDR3读数据进入另一个异步FIFO,再送入LVDS发送端串化输出。
流控机制在联调阶段非常重要。LVDS接收速率可能和DDR3写速率不匹配,如果接收端数据速率高于DDR3写入带宽,FIFO会溢出。我的做法是:
- LVDS接收侧FIFO接近满时,暂停接收后面的数据(回压信号给发送端)
- DDR3写操作通过突发方式批量进行,每次写满一个Page之后仲裁切换
- 发送链路的FIFO空时,读状态机自动暂停,避免读空数据
这套流控机制在数据回环测试中表现稳定,连续传输数分钟没有出现FIFO溢出或读空的问题。
注意:DDR3控制器不支持任意长度的单笔读写,突发长度是8的倍数,所以FIFO深度和数据传输块大小尽量设计为8的整数倍。实际项目中我把每次DDR3读写的块大小定为64字节,对应8次突发,逻辑上更简单。
4.3 多Die约束与布局考量
GW2A-LV18这颗芯片虽然逻辑资源不大,但高云更早的GW2A系列部分型号有多Die结构(多逻辑单元阵列拼接),在布局布线上有一些特殊性。我这次用的LV18不是多Die结构,但一些用户可能在更大容量型号上做项目,简单说明一下多Die约束的注意事项。
高云的多Die FPGA在约束里的体现主要是逻辑锁定(Logic Lock)区域和引脚规划的物理位置约束。多Die架构的Die间互联延迟比Die内部大,如果设计的关键路径横跨了两个Die,时序收敛会比较吃力。布局阶段就要把相关逻辑尽量放在同一个Die内,尤其是高速接口的收发逻辑和对应IO所在的Die要绑定在一起。
云源软件里有物理约束编辑器,可以查看引脚对应的Die归属,通过设置逻辑区域约束把关键模块限制在指定的Die范围内。如果不做约束,布局器可能把用户的逻辑分散到多个Die,导致时序问题很难查。
4.4 整体联合调试过程
联合调试第一步,先把DDR3控制器自测跑通,此时LVDS部分不参与,直接用板上拨码开关控制写读操作,确认DDR3读写数据正确。第二步是单独测试LVDS回环,把发送差分对用短跳线连到接收差分对,确认串行数据能正确收发。第三步再把两套逻辑通过数据通路连接起来,采用回环模式测试:LVDS发出去的数据进来后写DDR3,再从DDR3读出通过LVDS发送到另一端,另一端接收后比对。
这个回环测试能同时验证DDR3的读写正确性、LVDS的收发一致性、异步FIFO的跨时钟域处理。实测在室温25度环境下,连续运行2小时数据比对无错误,整个链路是稳定的。
调试过程中我用的工具主要是逻辑分析仪(抓DDR3控制器接口信号)和示波器(看LVDS差分波形)。如果手头没有逻辑分析仪,也可以把关键信号引到空闲的IO上,通过板载LED观察状态。不过DDR3控制器接口信号太多,用LED观察效率太低,建议至少准备一台便宜的逻辑分析仪,带宽要求并不高,主要是抓控制信号状态。
5. 遇到过的坑与排查思路
5.1 DDR3初始化失败的几种典型原因
DDR3控制器初始化失败是很多人第一个遇到的拦路虎,表现是init_done信号长时间不拉高。我调试过程中总结了几种典型原因。
第一种是颗粒参数配置错误。特别是行地址和列地址的位宽,需要对照颗粒手册仔细填写。有个朋友把行地址15位填成了14位,初始化过程中校准逻辑一直在试图访问不存在的地址,导致初始化超时。
第二种是时钟频率配置错误。控制器对DDR3颗粒的时钟频率有严格要求,超过颗粒额定速率会导致时序不稳定。比如标称DDR3L-1600的颗粒,配置成DDR3-1600跑没有问题,但如果颗粒实际是DDR3L-1333,还硬要跑1600,就会出现偶发初始化失败。
第三种是参考时钟质量差。高云硬核控制器对输入参考时钟的抖动比较敏感,如果开发板上的时钟走线过长或者和高速信号交叉,抖动会叠加到DDR3时钟上。排查方式是用示波器看波形,有条件的用频谱分析仪看相位噪声。
第四种是复位时序问题。DDR3颗粒的复位信号和时钟之间有时序要求,如果复位信号释放太晚或太早,颗粒无法进入正确的初始化状态。高云参考设计里对复位信号做了专门处理,照着参考设计接就对了。
5.2 LVDS误码与信号完整性排查
LVDS通信如果出现误码,排查思路和DDR3完全不一样,重点放在信号完整性上。
先用示波器查看差分信号眼图。如果眼图闭合,说明信号质量有问题。常见原因包括:链路上的阻抗不连续、发送端驱动能力设置不当、接收端端接电阻不匹配。LVDS标准要求接收端100欧姆差分端接,有些开发板上的端接电阻已经做在板内,外部再接一个会导致阻抗偏低,幅度减小。
通道间偏移就通过软件配置的Bit Slip来修正。
如果回环测试正常,但两个板卡之间传输误码,大概率是链路质量出了问题。我遇到过的情况是连接器引脚氧化导致的偶发误码,更换连接器后就好了,当时排查了很久。
5.3 工具链相关的坑
云源软件在DDR3和LVDS的IP生成上,不同版本有小幅差异。我在用某个新版本软件打开旧IP工程时,偶尔会出现IP核被重新生成的提示,重新生成后参数全部恢复默认,之前的配置就丢了。所以在工程开始前,建议固定软件版本,避免中途升级。
编译报错的排查路径也有讲究。高云软件的报错信息相对友好,但偶尔会给出误导性的提示。遇到编译错误,优先查看综合日志,定位到具体模块再解决。有一次报错提示引脚约束冲突,实际原因是另一个模块占用了同一对引脚,在综合日志里才能看清楚真正的资源占用关系。
约束文件的管理也要注意。高云工程支持.cst文件,类似Xilinx的.xdc。如果一个工程里存在多个约束文件,要注意优先级和冲突处理规则,避免出现部分引脚约束被覆盖的情况。我在工程里只用了一个top.cst,把时钟、LED、按键、DDR3接口引脚、LVDS引脚全部集中管理,方便排查也方便复用。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 处理办法 |
|---|---|---|
init_done一直为低 | 颗粒参数配置错误 | 重新核对行列地址位宽和Bank数 |
init_done为低 | 参考时钟频率不对 | 检查PLL配置和实际输入时钟频率 |
init_done拉高但读写数据错误 | 用户逻辑地址映射错误 | 对照官方地址映射表反查 |
init_done拉高但写读不一致 | Native接口命令时序不对 | 在仿真中检查命令通道的时序 |
| 读写数据偶发错误 | 电源纹波大或温度过高 | 检查DDR3供电电压纹波,加强散热 |
| LVDS回环正常但远距离误码 | 走线阻抗不连续 | 换成专用的100欧姆差分走线或双绞线 |
| LVDS误码且单通道数据错位 | 通道间偏移过大 | 调整Bit Slip做通道对齐 |
| LVDS信号无法解析 | 引脚约束了普通IO而非差分对 | 查阅芯片引脚手册确认差分对位置 |
| 仿真通过但上板不工作 | 复位逻辑跨时钟域 | 对复位信号做异步复位同步释放处理 |
| 上板偶发死机 | FIFO溢出或读空 | 检查流控逻辑,增大FIFO深度 |
6. 总结一下我踩过的坑和心得
这个项目从开始规划到最终跑通,我花在DDR3上的时间比预想的多不少。最大的感受是:FPGA里跑DDR3,真正的难点不在逻辑代码,而在对颗粒时序和控制器行为模型的理解。
用户逻辑的读写状态机其实很简单,谁都能写出来。但一旦遇到写进去读出来不一致、初始化不成功这类问题,如果不懂控制器内部的初始化流程和时序参数,就只能瞎猜。所以我建议新手在接触高云DDR3 IP时,花点时间看一下官方用户手册里的时序图,理解init_done之前控制器到底做了哪些事情。这样排查问题时有方向,而不是靠运气。
还有一个经验是在调试LVDS时一定要有一根高质量的双绞线或者同轴电缆做回环测试。我第一次调试时直接用了几根普通杜邦线绕在一起当差分线用,结果怎么调都有误码,一度以为代码或者IP配置有问题。后来换用一根短的屏蔽双绞线,误码率瞬间降为零。这个教训印象非常深刻,信号完整性在高速调试里永远是第一位的。
最后分享一个小技巧:工程里保存一份完整的IP配置截图,放在工程目录下的docs文件夹里。每次用新版软件打开工程,如果IP被重新生成,可以快速对照截图恢复参数。这个习惯帮我省了很多重复配置的时间。
后续如果大家有需要,我还可以写一篇关于高云GW2A系列驱动DDR3做图像缓存的进阶文章,结合帧缓存和行列切换的应用场景来谈控制器的优化配置。这个内容我这次项目也做了一部分预研,后面有时间整理出来。