尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

易灵思Ti60F100 FPGA挂载HyperRAM的Native接口实现与调试

易灵思Ti60F100 FPGA挂载HyperRAM的Native接口实现与调试 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型号是W956D8MBYA3.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#低有效。还有一根RWDSRead-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.3VVCCQ是给IO用的可以是1.8V或3.3V。我这次VCCQ也是3.3V和FPGA的IO电平匹配。如果你用1.8V的VCCQFPGA那边的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) 3d0: dq_out ca_data[47:40]; 3d1: dq_out ca_data[39:32]; 3d2: dq_out ca_data[31:24]; 3d3: dq_out ca_data[23:16]; 3d4: dq_out ca_data[15:8]; 3d5: 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 3d5) begin state DATA_WRITE; rwds_out 1b0; end else if (state DATA_WRITE) begin dq_out write_data[7:0]; write_data write_data 8; rwds_out 1b1; end endRWDS在写操作的时候是FPGA输出的用来指示数据掩码。如果不需要掩码就一直拉高就行。5. 调试过程中踩过的坑与排查方法5.1 地址对不上CA阶段位序搞反了这是我遇到的第一个大坑。代码写完之后往HyperRAM里写数据再读出来发现读出来的数据完全不对。用逻辑分析仪抓了一下波形发现CA阶段的48位数据和我预期的不一样。排查了半天发现是我把CA阶段的位序搞反了。HyperBus的CA阶段是从最高位开始发送的第47位先发第0位最后发。我一开始以为是从第0位开始发结果地址完全错位。这个问题的排查方法很简单用逻辑分析仪抓CA阶段的波形把48位数据手动拼出来然后和预期值对比。如果对不上就检查位序。注意不同厂家的HyperRAMCA阶段的位定义可能略有不同。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一下不要等到板子回来才发现问题。
返回列表