易灵思Ti60F100 FPGA挂载HyperRAM的Native接口实现与调试
2026/9/19 2:16:20 网站建设 项目流程

1. 项目缘起与方案选型考量

1.1 为什么要在Ti60F100上挂HyperRAM

易灵思Ti60F100这颗FPGA在国产FPGA圈子里热度一直不低,100K LUT级别的规模,加上Trion系列一贯的低功耗特性,很适合做图像采集、工业控制、显示驱动这类中等复杂度的项目。但实际做项目的时候你会发现一个很现实的问题:片内BRAM根本不够用。Ti60F100的BRAM容量大概在几百Kb这个量级,你随便缓存一帧720p的图像,或者做一个稍大一点的FIFO缓冲,BRAM就见底了。

这时候外扩一颗HyperRAM就是很自然的选择。HyperRAM本质上是一颗自刷新DRAM,接口是HyperBus,相比传统的SDRAM或者DDR,它的引脚数少得可怜——一套8位数据总线加上读写控制信号,总共十几个引脚就能跑起来。对于Ti60F100这种引脚资源不算特别富裕的封装来说,HyperRAM的引脚友好性是非常大的加分项。而且HyperRAM的容量通常从64Mb起步,到256Mb都有,用来做帧缓存、数据缓冲、查找表存储都绰绰有余。

我这次选的是Winbond的64Mb HyperRAM,型号是W956D8MBYA,3.3V供电,8位数据宽度。选它的理由很简单:供货稳定、资料相对齐全、易灵思的参考设计里也有类似的搭配,踩坑的概率会低一些。

1.2 Native接口和Hardware IP的取舍

易灵思的Efinity开发环境里,HyperRAM控制器有两种实现方式:一种是Hardware IP,也就是厂家提供的硬核控制器;另一种是Native接口,需要你自己写时序逻辑去驱动HyperBus。

Hardware IP的好处是省事,配置一下参数就能用,时序由硬核保证,跑高频比较稳。但它的问题也很明显:灵活性差,你没法根据自己项目的特殊需求去调整读写策略,而且Hardware IP会占用固定的引脚位置,PCB布线的时候约束比较多。

Native接口就完全相反了。你需要自己实现HyperBus的协议时序,包括CA阶段(Command/Address)、数据阶段、延迟周期等等,工作量不小。但好处是你对时序有完全的掌控权,可以根据实际需求优化读写效率,引脚分配也更自由。

我这次选择Native接口,主要原因是项目里对HyperRAM的访问模式比较特殊,需要频繁地在不同地址之间跳转读写,Hardware IP的固定突发长度策略不太适合。另外也是想借这个机会把HyperBus的时序彻底吃透,后面换其他厂家的HyperRAM也能快速上手。

2. HyperBus协议核心细节拆解

2.1 HyperBus的信号组成与基本时序

HyperBus的引脚其实不多,但每一根都有明确的职责。数据总线是双向的8位DQ[7:0],加上一根DQ[8]作为数据掩码(DM),读写的时候用来屏蔽不需要的字节。时钟是差分对CK和CK#,HyperRAM在时钟的上升沿和下降沿都采样数据,所以实际数据率是时钟频率的两倍。片选信号CS#低有效,复位信号RESET#低有效。还有一根RWDS(Read-Write Data Strobe),读的时候它是随数据一起返回的选通信号,写的时候它用来指示数据掩码。

一次完整的HyperBus访问分为两个阶段:CA阶段和数据阶段。CA阶段是6个时钟周期,传输48位的信息,包括地址、读写方向、突发长度等。数据阶段就是实际的数据传输,读的时候HyperRAM会在CA阶段结束后插入若干个延迟周期(Latency),然后才开始输出数据。

这里有个很容易搞混的点:HyperBus的CA阶段是在时钟的上升沿和下降沿都传输的,也就是说6个时钟周期传输12位,但实际有效的是48位,因为每个时钟周期传输4位?不对,我重新理一下。HyperBus的CA阶段确实是6个时钟周期,每个时钟周期在CK的上升沿和下降沿各传输一次,所以总共12个边沿,每个边沿传输4位?也不对。

正确的理解是:HyperBus的CA阶段,每个时钟周期传输8位,6个时钟周期就是48位。具体来说,在CK的上升沿传输低4位,下降沿传输高4位,这样每个时钟周期就是8位。DQ[7:0]在上升沿和下降沿分别输出不同的半字节。这个细节在写Native接口的时候非常关键,搞错了地址就完全对不上。

2.2 CA阶段的48位到底怎么排布

CA阶段的48位不是随便排的,它有固定的格式。对于HyperRAM来说,CA阶段的内容包括:

  • 第47位到第45位:保留位,通常填0
  • 第44位:读写方向,1表示读,0表示写
  • 第43位到第16位:行地址和列地址,具体怎么分要看HyperRAM的容量和组织方式
  • 第15位到第3位:保留位
  • 第2位到第0位:突发长度指示

对于64Mb的HyperRAM,地址是22位,分成行地址和列地址。行地址是12位,列地址是9位,还有一个Bank地址。具体到W956D8MBYA这颗片子,它的存储阵列是8个Bank,每个Bank有8Mb。行地址是13位,列地址是9位,加起来22位。

在CA阶段里,地址的排布是从高位到低位依次排列的。第43位到第31位是行地址的高13位,第30位到第22位是Bank地址,第21位到第13位是列地址的高9位,第12位到第3位是列地址的低位?这里我可能记混了,实际写代码的时候我是对着数据手册一位一位对出来的。

提示:CA阶段的位排布是Native接口调试中最容易出错的地方,建议在写代码之前先把数据手册里的CA位定义表打印出来,用荧光笔标出每一位的含义,写代码的时候逐位对照。

2.3 延迟周期与RWDS的关系

HyperRAM的读操作有一个固定的延迟周期,这个延迟周期是可配置的。W956D8MBYA支持两种延迟模式:固定延迟和可变延迟。固定延迟就是每次读操作都插入相同数量的延迟周期,通常是6个时钟周期。可变延迟则是HyperRAM通过RWDS信号来指示数据什么时候有效。

我这次用的是固定延迟模式,因为实现起来简单,时序确定。在CA阶段结束之后,HyperRAM会等待6个时钟周期,然后在第7个时钟周期开始输出数据。这个6个周期的延迟是HyperRAM内部的工作机制决定的,你没法改变它,只能去适应它。

写操作没有延迟周期,CA阶段结束后直接进入数据阶段。但写操作有一个额外的要求:在CA阶段和数据阶段之间,RWDS需要拉低一个周期,用来指示写数据的开始。

3. 硬件连接与PCB设计要点

3.1 引脚分配与约束文件编写

Ti60F100的引脚分配需要在Efinity的Interface Designer里完成,或者直接写约束文件。我这次用的是直接写约束文件的方式,因为引脚比较多,用图形界面点来点去反而慢。

HyperRAM的引脚连接有几个原则:

  • DQ[7:0]尽量分配到同一个Bank的连续引脚上,这样时序一致性最好
  • CK和CK#必须分配到支持差分输出的引脚对上
  • CS#、RESET#、RWDS这些控制信号可以分配到普通IO上
  • 所有HyperRAM相关的引脚都要设置正确的IO标准,通常是3.3V LVCMOS

约束文件的写法大概是这样的:

set_pin_assignment { DQ[0] } { LOCATION = R14; IOSTANDARD = LVCMOS33; } set_pin_assignment { DQ[1] } { LOCATION = R15; IOSTANDARD = LVCMOS33; } ... set_pin_assignment { CK } { LOCATION = T12; IOSTANDARD = LVCMOS33; } set_pin_assignment { CK_N } { LOCATION = T13; IOSTANDARD = LVCMOS33; }

这里有个坑:易灵思的差分输出引脚对是固定的,你不能随便找两个引脚就当差分对用。必须查Ti60F100的引脚手册,找到标注为差分对的引脚。我一开始没注意,把CK和CK#分配到了两个普通的IO上,结果综合的时候直接报错。

3.2 PCB布线的经验之谈

HyperRAM的时钟频率我这次跑的是100MHz,也就是200MB/s的数据率。这个速度不算特别高,但也不是随便画画就能稳的。PCB布线的时候有几个点要注意:

第一,CK和CK#的走线要等长,误差控制在5mil以内。差分对的阻抗要控制在100欧姆。如果板子空间允许,尽量让差分对走内层,两边包地。

第二,DQ[7:0]这8根线要尽量等长,误差控制在50mil以内。虽然HyperRAM对数据线的 skew 容忍度比DDR高一些,但能控制还是控制一下。

第三,RWDS这根线要特别注意。读的时候它是HyperRAM输出的,写的时候是FPGA输出的,方向会变。布线的时候要当成双向信号来处理,不要加任何单向的缓冲器。

第四,去耦电容要放够。HyperRAM的电源引脚旁边至少要放一个0.1uF的陶瓷电容,距离越近越好。我这次在HyperRAM的VCC和VCCQ引脚旁边各放了一个0.1uF和一个1uF的电容,实测下来电源纹波控制得不错。

注意:HyperRAM的VCC和VCCQ是分开供电的,VCC是给存储阵列用的,通常是3.3V,VCCQ是给IO用的,可以是1.8V或3.3V。我这次VCCQ也是3.3V,和FPGA的IO电平匹配。如果你用1.8V的VCCQ,FPGA那边的IO标准也要相应改成1.8V。

4. Native接口时序逻辑实现

4.1 状态机设计思路

Native接口的核心是一个状态机,我把它分成了几个状态:IDLE、CA_PHASE、LATENCY、DATA_READ、DATA_WRITE、DONE。

IDLE状态就是等待读写请求。当有读写请求进来的时候,状态机跳到CA_PHASE,开始发送48位的CA信息。CA_PHASE持续6个时钟周期,每个时钟周期发送8位。发送完之后,如果是读操作,跳到LATENCY状态,等待6个时钟周期;如果是写操作,直接跳到DATA_WRITE状态。

LATENCY状态结束后,进入DATA_READ状态,这时候HyperRAM开始输出数据,FPGA这边要在RWDS的边沿采样数据。DATA_READ状态持续到所有数据读完,然后回到IDLE。

DATA_WRITE状态就是FPGA输出数据,HyperRAM在时钟的边沿采样。写完之后回到IDLE。

这个状态机的设计看起来简单,但实际写的时候有几个细节要处理。比如CA阶段的48位数据怎么组织,读写方向怎么控制,RWDS的方向怎么切换,这些都是容易出问题的地方。

4.2 CA阶段的数据组织

CA阶段的48位数据我是在一个always块里用移位寄存器的方式发送的。具体做法是:先把48位的数据准备好,放在一个48位的寄存器里,然后在CA_PHASE状态下,每个时钟周期输出8位,同时把寄存器右移8位。

reg [47:0] ca_data; reg [2:0] ca_cnt; always @(posedge clk) begin if (state == CA_PHASE) begin case (ca_cnt) 3'd0: dq_out <= ca_data[47:40]; 3'd1: dq_out <= ca_data[39:32]; 3'd2: dq_out <= ca_data[31:24]; 3'd3: dq_out <= ca_data[23:16]; 3'd4: dq_out <= ca_data[15:8]; 3'd5: dq_out <= ca_data[7:0]; endcase ca_cnt <= ca_cnt + 1; end end

这里要注意的是,dq_out的输出是在时钟的上升沿更新的,但HyperBus要求数据在上升沿和下降沿都有效。所以实际上dq_out需要在时钟的上升沿和下降沿都保持稳定。我的做法是用一个2倍频的时钟来驱动dq_out,或者用DDIO的方式输出。

易灵思的FPGA里有没有DDIO原语我记不太清了,我这次是用了一个简单的办法:把系统时钟倍频到200MHz,然后用这个200MHz的时钟来驱动dq_out。这样每个100MHz的时钟周期里,dq_out会更新两次,正好对应上升沿和下降沿。

4.3 读操作的延迟与采样

读操作是Native接口里最麻烦的部分。CA阶段结束后,HyperRAM会等待6个时钟周期,然后开始输出数据。这6个周期的延迟是固定的,但问题是:你怎么知道HyperRAM什么时候开始输出数据?

我的做法是用一个计数器,在CA_PHASE结束后开始计数,数到6的时候进入DATA_READ状态。在DATA_READ状态下,用RWDS的边沿来采样DQ上的数据。

RWDS在读操作的时候是HyperRAM输出的,它和DQ是同步的。RWDS的上升沿和下降沿对应DQ上数据的有效窗口。所以采样的时候要用RWDS的边沿来触发。

always @(posedge rwds or negedge rwds) begin if (state == DATA_READ) begin data_in <= {data_in[6:0], dq_in}; end end

这个写法看起来简单,但实际综合的时候可能会出问题。因为rwds是异步信号,直接用它做时钟会引入亚稳态。更稳妥的做法是用一个高速时钟(比如200MHz)来过采样rwds和dq,然后在数字域里恢复数据。

我一开始就是用rwds直接做时钟的,跑100MHz的时候没问题,但后来想试试跑133MHz,就出现了误码。后来改成200MHz过采样,就稳了。

提示:如果你的HyperRAM时钟频率超过100MHz,强烈建议用高速时钟过采样的方式来采样读数据,不要直接用RWDS做时钟。亚稳态在高速下是致命的。

4.4 写操作的时序控制

写操作比读操作简单一些,因为没有延迟周期。CA阶段结束后,直接进入DATA_WRITE状态,FPGA输出数据,HyperRAM在时钟边沿采样。

但写操作有一个细节:在CA阶段和数据阶段之间,RWDS需要拉低一个周期。这个拉低的动作是告诉HyperRAM:写数据要开始了。如果忘了这个动作,HyperRAM可能会把CA阶段的数据当成写数据,那就全乱了。

写操作的代码大概是这样的:

always @(posedge clk) begin if (state == CA_PHASE && ca_cnt == 3'd5) begin state <= DATA_WRITE; rwds_out <= 1'b0; end else if (state == DATA_WRITE) begin dq_out <= write_data[7:0]; write_data <= write_data >> 8; rwds_out <= 1'b1; end end

RWDS在写操作的时候是FPGA输出的,用来指示数据掩码。如果不需要掩码,就一直拉高就行。

5. 调试过程中踩过的坑与排查方法

5.1 地址对不上:CA阶段位序搞反了

这是我遇到的第一个大坑。代码写完之后,往HyperRAM里写数据再读出来,发现读出来的数据完全不对。用逻辑分析仪抓了一下波形,发现CA阶段的48位数据和我预期的不一样。

排查了半天,发现是我把CA阶段的位序搞反了。HyperBus的CA阶段是从最高位开始发送的,第47位先发,第0位最后发。我一开始以为是从第0位开始发,结果地址完全错位。

这个问题的排查方法很简单:用逻辑分析仪抓CA阶段的波形,把48位数据手动拼出来,然后和预期值对比。如果对不上,就检查位序。

注意:不同厂家的HyperRAM,CA阶段的位定义可能略有不同。W956D8MBYA的CA位定义在数据手册的第23页,建议打印出来放在手边。

5.2 读数据不稳定:采样时机不对

地址问题解决之后,读数据基本能读出来了,但偶尔会有几个bit出错。用逻辑分析仪看波形,发现RWDS的边沿和DQ的数据窗口没有对齐。

这个问题有两个原因:一是PCB布线的时候DQ和RWDS的走线长度差异太大,导致信号到达FPGA的时间不一致;二是采样时钟的相位不对。

第一个问题的解决办法是重新布线,把DQ和RWDS的走线长度控制在50mil以内。第二个问题的解决办法是调整采样时钟的相位,或者用IDELAY原语来微调。

易灵思的FPGA里有没有IDELAY原语我不太确定,我这次是用PLL来调整采样时钟的相位的。把采样时钟的相位往后调了90度,数据就稳了。

5.3 写操作失败:RWDS忘了拉低

写操作的问题更隐蔽。读操作正常之后,我以为写操作也没问题,结果写进去的数据读出来全是0。排查了半天,发现是CA阶段结束后忘了把RWDS拉低一个周期。

这个问题的排查方法是:用逻辑分析仪抓写操作的波形,看CA阶段和数据阶段之间RWDS有没有拉低。如果没有,那就是代码里漏了这个动作。

5.4 常见问题速查表

问题现象可能原因排查方法解决办法
读出的数据全错CA阶段位序错误逻辑分析仪抓CA波形,手动拼位对照数据手册修正位序
读数据偶尔出错采样时机不对抓RWDS和DQ的波形,看边沿对齐调整采样时钟相位或重新布线
写数据读出来全是0RWDS没拉低抓写操作波形,看CA和数据之间RWDS在CA结束后拉低RWDS一个周期
高速下误码率高亚稳态检查采样方式改用高速时钟过采样
综合报错引脚分配错误检查约束文件查引脚手册,用正确的差分对

6. 性能优化与后续扩展

6.1 提高读写效率的几个手段

Native接口跑通之后,我做了几轮优化,把HyperRAM的读写效率从最初的不到50%提升到了80%左右。主要做了这几件事:

第一,把突发长度从固定的32字节改成可变的。HyperRAM支持16、32、64、128字节的突发长度,根据实际数据量选择合适的突发长度,可以减少CA阶段的开销。

第二,把读写操作合并。如果连续有多个读请求,可以把它们合并成一次长突发读,而不是每次读都发一次CA。这样能省下不少时钟周期。

第三,把时钟频率从100MHz提到133MHz。Ti60F100的HyperRAM接口跑133MHz是没问题的,再高就要看PCB的布线质量了。

6.2 后续可以扩展的方向

这个项目做完之后,我觉得还有几个方向可以继续挖:

一是把HyperRAM做成一个通用的存储控制器,封装成FIFO或者RAM的接口,这样上层逻辑就不用关心HyperBus的时序细节了。

二是试试可变延迟模式。固定延迟模式虽然简单,但在高频下余量比较小。可变延迟模式用RWDS来指示数据有效,理论上可以跑更高的频率。

三是把HyperRAM和DMA结合起来,做大数据块的自动搬运。这样CPU或者软核就不用频繁地读写HyperRAM了,效率会更高。

提示:如果你也在用易灵思的FPGA挂HyperRAM,建议先把Native接口跑通,再考虑用Hardware IP。Native接口虽然麻烦,但跑通之后你对整个时序的理解会深很多,后面遇到问题也知道从哪里下手。

6.3 一些实测数据

最后分享一些实测数据,供参考:

  • 时钟频率100MHz时,连续读的吞吐率大约是160MB/s,连续写大约是180MB/s
  • 时钟频率133MHz时,连续读的吞吐率大约是210MB/s,连续写大约是230MB/s
  • 随机读写的吞吐率会低很多,大概只有连续读写的一半左右
  • 功耗方面,HyperRAM在连续读写的时候大概消耗30mA左右的电流,空闲的时候不到1mA

这些数据是在室温下测的,没有做高低温测试。如果你的项目对温度范围有要求,建议在高温和低温下都测一下,HyperRAM的时序参数在极端温度下会有变化。

我在实际调试中发现,HyperRAM的时序余量比想象中要小。尤其是在高频下,PCB布线的质量直接决定了能不能跑稳。如果你打算跑133MHz以上,建议在PCB设计阶段就找有经验的工程师review一下,不要等到板子回来才发现问题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询