首页 / 资讯中心 / 文章详情

安路PH1 ERAM六模式与硬件FIFO选型、深度计算及调试

安路PH1 ERAM六模式与硬件FIFO选型、深度计算及调试 ★ FEATURED ARTICLE
1. 先把ERAM的边界划清楚它到底是什么不是什么1.1 一块可编程砖而不是一颗内存颗粒刚接触安路PH1的人看到手册里ERAM那一章第一反应往往是哦片内RAM。这个理解不能说错但只对了一半而且是最没用的那一半。ERAMEmbedded RAM在PH1里是一组物理位置固定、布线资源专属、时序确定性极高的专用存储块它和外部DDR、和用LUT拼出来的分布式RAM是三件完全不同的事。外部存储的问题在于不确定性。你发一个读命令出去经过IO、走线、控制器、颗粒内部时序回来要多少个周期取决于板级走线长度、颗粒型号、控制器状态机的排队情况。你在做图像行缓存、跨时钟域缓冲、总线突发整流这些活的时候最怕的就是这一拍到底能不能拿到数据这种不确定。ERAM解决的正是这个问题它在芯片内部读写延迟是固定周期数综合工具在布局阶段就把它的位置钉死你只要不违反建立保持时间行为就是可预期的。用LUT拼分布式RAM也就是常说的Distributed RAM / SRL当然也能存数据代价是每个比特都要吃掉逻辑资源。存个32位×64的缓冲就是2048个触发器或者等效的LUT这些资源本来该拿去做运算和状态机的。更要命的是分布式RAM很难做真正的双端口读写冲突的处理非常别扭。所以我一般的判断标准很粗暴只要数据宽度超过8位、深度超过16就别犹豫往ERAM上放。ERAM在PH1里的定位是一块可配置的存储子系统而不是一块死的SRAM。这个差别很关键——同一块物理资源你可以把它配成单端口RAM、简单双端口RAM、真双端口RAM、ROM、移位寄存器以及本文重点要讲的硬件FIFO。配置模式决定了这块砖内部哪些电路被激活、哪些被旁路也决定了你能拿到什么状态信号、付出多少额外逻辑。1.2 PH1的ERAM资源账怎么算具体到资源规格不同型号的PH1器件ERAM块数量和单块容量并不一样精确数字必须去翻对应型号的数据手册我这里编一个数字出来对你没有任何帮助反而会误导。但有几条规律是通用的掌握了这几条你自己就能把账算明白。第一ERAM是按块block计数的不是按比特连续分配。工具在综合时会把你声明的一个大RAM拆成若干个物理块再通过级联信号把它们串起来。所以你在报告里看到的使用了3个ERAM指的是占用了3个物理块哪怕你实际只用了其中一小部分容量这个块也算被占了。这个特性直接导致一个实操结论零碎的、深度很小的RAM非常浪费资源。存个8×16的小表就用掉一整个块不如老老实实用分布式RAM或者干脆用寄存器数组。第二每块的位宽和深度是可换算的。一块ERAM的总容量是固定的PH1这一代基本是9Kb量级为基本块具体以手册为准你可以把它配成宽而浅比如512×18、256×36也可以配成窄而深比如1024×9、2048×4。位宽每减半深度理论上翻倍但受限于块内地址译码和级联结构不是所有组合都合法。工具在IP配置界面里会把合法组合列给你你只需要在合法集合里挑别自己硬凑。第三级联cascade是硬件通路。当你的深度需求超过单块容量时工具会启用块间级联信号让地址高位直接选择块、数据总线直通。这条路是芯片设计时就预留好的比你在逻辑层用多路选择器拼接要快得多。反过来说如果你的RAM深度需求刚好卡在单块容量的1.2倍那你就要付出两块的成本这时候更好的做法是重新审视数据组织方式看看能不能把宽度压一压把深度让出来。第四位置会影响时序。ERAM块在芯片上的物理分布不是均匀的靠近IO的块和靠近逻辑阵列中心的块走线延迟不一样。对于高速接口的缓冲比如把外部并口数据先收进FIFO再进逻辑我习惯在约束里把相关ERAM的位置往IO侧引导能省下不少布线余量。这一条工具不会主动提醒你但它在你时序余量只剩零点几纳秒的时候往往是救命的那一手。1.3 六种配置模式的整体地图先把全景摆出来后面几章再逐块拆。PH1的ERAM在IP配置时能选的工作模式归纳起来是下面这几类模式端口结构典型用途是否消耗额外逻辑单端口RAM一个时钟一套地址读写分时系数表、查找表、临时缓存极少简单双端口RAM一个写口、一个读口可各自时钟跨时钟域数据搬运、乒乓缓冲极少真双端口RAM两套完全独立的读写口双主共享存储、多核交互极少ROM只读上电加载初值正弦表、字符点阵、固化参数无移位寄存器固定深度串行移位延迟对齐、滑动窗口无硬件FIFO内置指针与状态控制突发整流、跨时钟域缓冲、速率匹配几乎为零这张表里最右边一列是重点。前五种模式省的是存储资源第六种模式省的是控制逻辑。很多人配FIFO的时候只盯着我要多少深度完全没意识到在PH1上选硬件FIFO模式和用软逻辑搭一个FIFO资源差距有多大。这正是标题里不止是存储的第一层含义也是下一章要展开的东西。2. 硬件FIFO控制器省下来的不只是LUT2.1 软核FIFO的隐性成本算过才知道疼在没有硬件FIFO控制器的平台上做一个FIFO是标准动作一块双端口RAM存数据一组写指针、一组读指针指针位宽是log2(深度)1多出来的那一位用来区分满和空再加比较逻辑判断full/empty异步FIFO还要把指针做格雷码编码、两级同步、再转回二进制比较。这套东西写起来不难抄一遍模板十分钟的事但它吃掉的东西很少有人认真核算。拿一个32位宽、深度256的异步FIFO举例。存储本体用掉一块ERAM这没跑。指针各9位两套就是18个触发器加上格雷码转换、两级同步寄存器每级9位两级就是36个触发器再加上满空比较、almost阈值比较、data_count的计算逻辑整体下来几百个LUT和上百个触发器是跑不掉的。这还只是资源账。更麻烦的是时序账。异步FIFO里最脆弱的一环是格雷码指针跨时钟域之后的那次比较。指针同步过来有两拍延迟这两拍里写侧可能又往前走了一段所以满判断必须留保守余量标准做法是同步后的读指针与写指针比较若高位不同而低位相同则判满。这个逻辑本身没错但它对综合和布局的敏感度很高——一旦同步寄存器被拆散到不同slice或者比较逻辑的路径被拉长你就会在时序报告里看到一条很难收敛的路径而且这条路径往往藏在跨时钟域里STA报出来的违例信息不够直白排查起来很耗时间。我见过一个项目图像前端做行缓冲四个通道各一个异步FIFO深度都不大64结果为了这四个FIFO的指针同步逻辑时序余量被啃掉一大块最后不得不降低前端采样率来迁就。后来把FIFO换成硬件控制器版本那一片区域的逻辑密度直接降下来时序自己就松了。这不是玄学是物理事实同样一件事专用电路做和通用逻辑做代价差一个数量级。2.2 控制器内部到底在干什么PH1的ERAM在FIFO模式下块内部会激活一套专用的控制电路。你从外面看到的是几个端口信号里面发生的事情大致是这么几层第一层是指针管理。写指针和读指针都由硬件维护你只需要给wr_en和rd_en指针自己走。对同步FIFO来说读写同源时钟指针比较在一个时钟域内完成逻辑最简对异步FIFO来说两套指针各自在自己的时钟域里递增跨域时由内部电路完成编码转换和同步具体用什么编码、同步几级这是芯片设计者的事你不用管也不该管。第二层是状态判定。full、empty、almost_full、almost_empty、overflow、underflow、wr_data_count、rd_data_count这些信号由内部比较器直接产生。almost阈值的可配置性是个很实用的细节它让你能在FIFO快满之前就提前给上游发反压信号而不是等到真的满了才急刹车。在流式数据通路里这一两个周期的提前量往往就是数据不丢和数据丢了再重传的区别。第三层是读写地址到物理块的映射。当FIFO深度超过单块容量时内部自动完成多块级联和地址分配你看到的仍然是一个连续的FIFO不需要在外部做任何拼接。这一点在软核实现里是做不到这么干净的软核级联必须自己在地址高位上做文章一不留神就在块边界处出现读写错位。第四层是冲突处理。FIFO模式下读写地址天然不会撞车空的时候不读满的时候不写所以内部不需要像真双端口RAM那样处理同地址冲突。这也是FIFO模式比双端口RAM软控制更省资源的原因之一——它把不需要的电路真的关掉了。端口命名各家工具会有差异你在实际工程里以IP生成器吐出来的头文件为准。下面这个例化模板用的是通用命名示意一下接口长什么样// 异步FIFO例化示意端口名以实际IP生成结果为准 fifo_async_32x256 u_rx_fifo ( .rst (fifo_rst ), // 复位只清指针不清存储内容 .wr_clk (rx_clk ), .rd_clk (sys_clk ), .din (rx_data ), .wr_en (rx_valid ), .rd_en (fifo_rd_en ), .dout (fifo_dout ), .full (fifo_full ), .almost_full (fifo_afull ), // 阈值提前反压 .empty (fifo_empty ), .almost_empty (fifo_aempty ), .overflow (fifo_ovf ), // 建议接ILA调试期必看 .underflow (fifo_unf ), .wr_data_count(fifo_wcnt ) );这段代码里我特意把overflow和underflow引出来了。很多人图省事把它们悬空结果上板出问题时只能靠猜。这两根线是硬件控制器白送你的诊断口接上逻辑分析仪一眼就能看出是写侧灌太猛还是读侧取太慢排查效率差好几倍。2.3 什么时候必须上硬件FIFO什么时候不必不是所有缓冲都值得用FIFO模式。我的经验判断是三条跨时钟域且速率不匹配优先硬件FIFO。这是它最擅长的场景。两个时钟域之间传流式数据你需要的不是一个存储器而是一个有背压能力的弹性缓冲full/empty就是握手信号almost_full就是提前量。软核也能做但硬件版本省下的逻辑和时序余量在你通道数一多的时候就非常可观。突发整流优先硬件FIFO。上游是突发式写入比如DMA一次性甩过来64个数据下游是匀速消费比如串行发送中间必须有缓冲吸收突发。这时候FIFO深度要按最坏突发长度算而不是按平均速率算这一点第四章会详细给计算方法。纯延迟对齐别用FIFO。如果只是要把一个信号延迟N拍用ERAM的移位寄存器模式更省或者干脆用SRL。上FIFO属于杀鸡用牛刀还白白占了一套控制逻辑的配置。反过来说如果你的场景是两个主设备共享一块数据区那根本不是FIFO的活老老实实用真双端口RAM。选型选错后面所有调试都是在给错误买单。3. 灵活配置模式的选型逻辑六种模式怎么挑3.1 单端口、简单双端口、真双端口别被名字骗了这三种模式的区别本质是端口独立程度的递进选错了会在两个地方让你难受读写冲突的优先级和时钟域的数量。单端口RAM只有一套地址线读和写分时复用。它的优点是省资源、时序最简单缺点是同一时刻只能干一件事。什么时候够用典型的场景是写进去、稍后再读出来读写之间天然有间隔。系数表、状态缓存、命令队列这种访问模式不密集的地方都合适。但如果你在一个时钟周期里既想写新数据又想读旧数据单端口直接不满足别硬凑。简单双端口Simple Dual Port是一个写口、一个读口写口和读口可以挂在不同的时钟上各自有独立的地址。这是做跨时钟域搬运最常用的结构也是软核FIFO的存储底座。它的问题是读口不能写写口不能读。如果你的算法需要读出来、改一改、写回去而且读写要在同一个端口上完成那简单双端口就不够用了。真双端口True Dual Port是两套完全独立的读写口每一套都能读能写各自有时钟、地址、使能。灵活性最高代价是块内电路最复杂同一地址被两个口同时访问时的行为需要你明确指定。这个同时访问的处理策略一般有三种可选read-first读出口旧值再写入新值、write-first读出口新值、no-change读口保持原值。这个选项看起来是个不起眼的下拉框但它是真双端口最容易出事的地方。我的习惯是永远显式指定绝不留给默认值——因为你换个工具版本、换个器件型号默认值可能就变了而这种变化在仿真里未必暴露。模式时钟域同拍读写典型场景需要注意单端口1不可查找表、临时缓存读写必须分时简单双端口1 或 2写口写、读口读跨时钟域搬运、乒乓读口不能写真双端口1 或 2两口均可双主共享、读改写冲突策略必须显式指定3.2 ROM与移位寄存器两个容易被忽略的顺手工具ROM模式是把ERAM当只读存储器用上电时从初始化文件加载内容。这里有个非常实际的坑初始化文件的格式和路径。综合工具吃的是特定格式.mif、.hex、.coe之类看工具链而且路径最好用相对路径放在工程目录里。我吃过一次亏用了绝对路径本机跑得好好的换一台机器编译ROM里全是零波形上看数据一直是0查了半天以为是地址算错了。后来发现是初始化文件没找到工具不报错静默给了全零。ROM的另一个用途是做非线性校正表。传感器输出经过ADC之后往往不是线性的与其在逻辑里做分段拟合不如直接查表。一块ERAM做1024×16的校正表一个周期出结果比任何算法都快。这种用法里ERAM的价值不在存储而在用一个周期换取任意复杂的映射关系。移位寄存器模式是把ERAM配置成固定深度的串行移位链。它的价值在于大深度延迟对齐。比如你要把一路数据延迟512拍去和另一路对齐用触发器实现要512个用SRL也要占不少资源而用移位寄存器模式的ERAM一块就搞定而且不占逻辑。做多通道同步采集的时候这一招特别好用各通道的走线延迟不同用移位寄存器做精细对齐比在PCB上做等长布线灵活得多。3.3 位宽与深度的换算先把账算在纸上在打开IP配置工具之前我建议先在纸上把三个数写清楚总容量、目标位宽、目标深度。三者之间是乘除关系但受块结构限制不能随意缩放。举个例子。假设单块ERAM容量是9Kb你要做宽度32位、深度128的缓冲。32×128 4096b小于9Kb理论上单块就够。但如果工具规定这一模式下32位宽的合法深度上限是256因为地址线宽度限制那128完全没问题。反过来如果你要宽度36位、深度51236×512 18432b超过单块必须级联两块而且级联之后时序路径会变长你得重新看时序报告。这里有个容易被忽视的细节位宽超过单块最大位宽时工具用并行拼接深度超过单块最大深度时工具用级联。两种扩展方式的代价不一样。并行拼接带来的是数据总线的扇出增加级联带来的是地址译码路径的延迟增加。前者在高速设计里更容易出问题。所以在位宽和深度可以互相折中的场合比如一个二维数组你可以按行存也可以按列存尽量选窄而深避开宽总线的扇出问题。还有一点不要为了凑整而浪费块。如果你实际只需要3Kb却配了一块9Kb剩下6Kb不能借给别的模块用——物理块被占用就是被占用了。这种浪费在资源紧张的项目里非常致命。我在一个多通道设计里见过六个通道各配了一块只用了1/4容量的FIFO六块ERAM白白浪费后来改成两个通道共用一个真双端口RAM做时分复用一下省出四块。省下来的块拿去做了别的事整个设计的资源占用从刚好卡住变成还有余量。3.4 FIFO的同步与异步两条路两个选型点FIFO模式往下再分就是同步和异步两条路。这个选择不是看个人喜好是看你的读写两侧到底是不是同一个时钟。同步FIFO的读写挂同一时钟。它的控制最简单状态更新在当拍就能反映几乎不会出现状态延迟导致的误判。如果你两侧时钟同源同频哪怕相位有点差只要能保证是同一个时钟网络就用同步FIFO别没事找事上异步。异步FIFO的读写各自独立时钟。它的内部必须处理跨时钟域状态信号full/empty相对于另一侧时钟是有延迟的这是物理规律不是实现缺陷。这一点在写控制逻辑时必须体现出来看到empty拉低不代表下一拍一定读得到数据看到full拉高不代表上一拍的数据一定没写进去。正确做法是用状态信号做握手而不是当成组合逻辑用。还有一个选项值得单独提FWFTFirst Word Fall Through模式。标准FIFO的行为是读使能有效后的下一拍数据才出现在输出端而FWFT模式是只要FIFO非空第一个数据就自动挂在输出端读使能相当于弹栈。前者是标准握手后者更像一个带缓冲的队列。FWFT的好处是省掉一拍延迟在流水线里能少插一级寄存器坏处是控制逻辑要相应调整如果你按标准FIFO的思路去写FWFT的读逻辑会莫名其妙丢数据。选哪个取决于你的下游能不能接受那一拍延迟我一般在高速流水的场合用FWFT在控制路径上用标准模式图个稳妥。4. 从参数计算到板上跑通完整实操路径4.1 先把FIFO深度算对这是一切的地基FIFO深度算小了会溢出丢数据算大了浪费ERAM块还可能拖累时序。这一步必须在打开工具之前完成算错了后面所有工作都是白费。基本模型是这样的写侧在某段时间内以平均速率Rw写入读侧以平均速率Rr读出Rw Rr那么差值会在FIFO里累积。如果写侧是突发式的那就要按最坏突发来算而不是按平均值。最常用的公式是所需深度 ≥ 突发长度 − 突发长度 × (Rr / Rw)前提是读写背靠背、无空闲周期。这个公式的物理含义很直白一次突发写进来N个在写这N个的过程中读侧已经消化掉了N×(Rr/Rw)个剩下的才需要缓冲。举个具体例子。写侧时钟100MHz、连续写入读侧时钟80MHz、读取不中断一次突发256个数据。写入耗时256 / 100MHz 2.56μs同期读出80MHz × 2.56μs 204.8取整204个需要缓冲256 − 204 52个52往上取2的幂就是64。所以深度配64就够配128是浪费。这里要注意这个算法假设读侧从第一个数据进FIFO开始就在读。如果读侧有启动延迟比如要等半满才开始读那就要把这段延迟期间写入的数据也算进深度里。再看一个跨时钟域的例子。写侧50MHz突发写128个读侧150MHz但读侧每读4个要停3个周期有效占空比4/7。写入耗时128 / 50MHz 2.56μs读侧有效速率150MHz × 4/7 ≈ 85.7MHz同期读出85.7M × 2.56μs ≈ 219个这里读出量大于写入量理论上不需要缓冲。但如果读侧是突发结束后才启动比如由一个中断触发那深度必须≥128。这个例子说明一件事深度计算的答案强烈依赖于读侧什么时候开始读这个假设。这个假设必须写进设计文档否则半年后你自己都不记得当初为什么配了128。almost_full的阈值怎么设我的经验是设为深度的75%左右留出足够时间让上游反压生效。反压生效的延迟 上游检测到almost_full的延迟 上游停止写入的延迟。如果上游是一个有流水线的模块这个延迟可能有3到5拍那阈值就要往下压保证在这几拍里FIFO不会被灌满。这个值不要拍脑袋实际算一下上游从看到反压到真正停写要几拍就留几拍再加两拍余量。4.2 IP配置的关键选项逐个说清楚打开IP生成器之后一堆下拉框和输入框哪些是真的会影响结果的按我的经验下面这几项必须逐个确认其余可以用默认。位宽与深度。这个前面算过了填进去就行。注意看工具给出的实际占用块数如果和你预期不符说明你的位宽深度组合触发了级联或者拼接要回头重新评估。读写时钟关系。同步还是异步选错了整个FIFO的行为都不对。有些工具会根据你填的时钟名自动判断别信自己确认。almost阈值。前面算过填进去。如果工具支持同时配almost_full和almost_empty两个都配上反压是双向的。复位行为。这一项是重灾区。多数硬件FIFO的复位只清指针和状态不清存储内容。这意味着复位之后你读出来的数据可能还是上一次的残留虽然empty标志会说FIFO是空的所以不会真的读出来但在调试时如果你绕过empty强行读就会看到旧数据很容易误判为复位没生效。这个特性必须记住。输出寄存器选项。有些FIFO可以选是否在输出端加一级寄存器。加了能改善时序把读路径的延迟从块内移到块外更容易收敛但会增加一拍读延迟。在高速设计里这个选项很值钱在控制路径上就没必要。初始化。FIFO模式一般不接受初值文件ROM模式才有。如果你的设计依赖上电时FIFO里有预设数据来实现某种状态机那这个思路本身就是错的换方案。4.3 例化与约束几个必须做的事例化本身没什么难度照着生成的头文件端口连就行。真正需要注意的是约束。第一跨时钟域路径要正确声明。异步FIFO内部已经做了同步处理但工具不知道它会在两个时钟域之间做时序分析然后报一堆违例。正确做法是用工具提供的跨时钟域约束语法set_clock_groups或者等效的false path声明把这些路径排除掉。这一步不做你的时序报告永远是红的而且红得没有意义。但反过来不能图省事把所有跨时钟域路径一刀切全false掉那样真出问题时你连报警都看不到。正确的粒度是按FIFO实例声明一个实例一组约束。第二ERAM的位置约束。前面提过靠近IO的块对接口缓冲更友好。如果你的FIFO接的是外部接口可以在约束里给个区域提示。这一条不是必须但在时序紧张的时候值得试。第三复位的同步释放。FIFO的复位一般要求异步置位、同步释放尤其是异步FIFO两个时钟域各需要一个复位同步器。如果直接拿一个异步复位信号去捅两个时钟域复位释放的时刻在不同域里差几拍指针状态可能进入一个非法的中间态。这个坑我在早期项目里踩过现象是上电后FIFO的empty一直是高怎么都不出数据重新烧录一次又好了——典型的复位时序问题靠运气。// 复位同步释放示意每个时钟域一份 reg [1:0] rst_sync_wr, rst_sync_rd; always (posedge wr_clk or negedge rst_n) if (!rst_n) rst_sync_wr 2b00; else rst_sync_wr {rst_sync_wr[0], 1b1}; always (posedge rd_clk or negedge rst_n) if (!rst_n) rst_sync_rd 2b00; else rst_sync_rd {rst_sync_rd[0], 1b1}; // 用 rst_sync_wr[1] 和 rst_sync_rd[1] 分别作为两个域的复位4.4 仿真阶段必须检查的四个点仿真跑通不等于设计正确。我一般会在testbench里专门构造几种边界情况这几种情况在正常激励下几乎撞不到但它们恰恰是硬件FIFO最容易出问题的地方。第一写满边界。连续写到full拉高然后再多写一拍看overflow有没有报。如果overflow没接出来就看不到。同时看full拉高的时刻最后一个数据到底写进去了没有——有些实现的full是写完后拉高有些是写之前就拉高差一拍就差一个数据。手册上会写但你最好在仿真里亲眼看到。第二读空边界。连续读到empty拉高再多读一拍看underflow。同时看empty拉高时输出端的数据是有效还是无效。标准FIFO在empty时输出端的数据是上一次的残留不是零也不是新数据。如果你的下游逻辑在empty时还在采样dout就会拿到垃圾。这个必须在代码层面就防住读使能 !empty 其他条件。第三跨时钟域的极限情况。让写侧时钟和读侧时钟的频率比接近1:1但不同源。这时候指针同步的两拍延迟影响最大满空判断最容易出现明明还有空间却报满或者明明还有数据却报空。这个场景用随机相位差跑几万拍如果不出问题基本可以放心。仿真的时钟周期不要用整数比用带小数的周期比如10ns和10.3ns能把相位漂移扫遍。第四复位时刻。在FIFO半满的时候拉一次复位看指针是不是真的清零、状态是不是真的一致。有些实现在复位期间如果读写使能还有效会出现状态紊乱。这个场景在真实系统里一定会遇到——上电时序、看门狗复位、软复位都可能在FIFO工作期间打断它。4.5 上板调试把内部状态引出来仿真过了上板还是可能出问题。这时候最有效的办法是把FIFO的状态信号引到嵌入式逻辑分析仪上。要引哪些full / empty最基本的两个almost_full / almost_empty看反压是否及时wr_data_count / rd_data_count看FIFO里实际有多少数据这个最直观overflow / underflow只要亮一次就说明有问题必须锁存住看wr_en / rd_en 的原始信号看使能是不是在非法时刻有效触发条件我一般设成overflow或underflow任一为高一旦触发就抓一段前后的波形。这个设置能抓到绝大部分数据流问题。还有一个实用技巧在FIFO的写侧和读侧各加一个自由运行的计数器把计数值也引到分析仪上。这样你能直接看到两侧各自处理了多少个数据一比就知道是丢在入口还是丢在出口。这个办法比猜快得多。5. 踩坑记录与排查实录5.1 症状到根因的对照表下面这张表是我这些年实际遇到过的、和ERAM/FIFO相关的典型问题按症状归类方便你对照排查。症状可能根因排查方法仿真正常上板FIFO一直不出数复位没有同步释放指针停在非法态检查两个域的复位同步器重新上电是否恢复偶发丢数据频率很低almost阈值设得太大反压来不及把阈值往小调观察丢包率变化empty拉低后读出的第一拍是旧数据误用了标准模式却按FWFT的思路写读逻辑确认IP的FWFT选项对齐读时序时序报告在跨时钟域路径上大量违例没做跨时钟域约束按FIFO实例加时钟组约束不要全局一刀切换台机器编译后ROM读出全零初始化文件用了绝对路径或格式不对改相对路径确认文件格式与工具要求一致FIFO深度够但依然溢出深度按平均速率算的没按最坏突发算重新按突发长度算或加almost反压复位后第一次读到残留数据硬件FIFO复位不清存储内容这是正常行为靠empty屏蔽不要绕过多个FIFO占用ERAM块数远超预期小深度FIFO各占一整块无法共享改用真双端口RAM做时分复用或合并通道真双端口RAM同地址读写结果不对冲突策略用了默认值与实际预期不符显式指定read-first/write-first/no-change级联后深度翻倍但时序变差级联路径延迟增加地址译码变长检查是否需要改数据组织方式避免跨块边界5.2 几条手册上不会写但很值钱的经验第一条先用小深度跑通再往上加。我见过太多人在第一次配FIFO的时候就把深度设成2048结果出了问题完全不知道是深度的事还是别的。正确顺序是先用深度16跑通全链路确认读写握手、复位、反压都正常再逐步加大到目标值。这样一旦出问题变量只有一个。第二条data_count 是你的朋友但别用它做控制。计数信号非常适合调试和监控但拿它做控制逻辑要小心——它相对某一侧时钟是有延迟的你看到count是5的时候实际可能已经是6或者4。用它做够不够读的判断边界上会出错。判断够不够永远用empty/almost_empty。第三条通道数超过三个就该考虑共享存储了。每个通道一个独立FIFO看起来干净但ERAM块是按块占用的小FIFO的利用率极低。三个以上通道的时候我会算一下总带宽如果总带宽没超过单块ERAM的吞吐能力就改成一块大RAM做时分复用用地址分区管理。代价是控制逻辑复杂一点收益是省下一大半存储资源。第四条跨时钟域的FIFO两个时钟的比值不要取整数。整数比的时钟在仿真里会掩盖相位问题因为它们的相对相位是固定的。真实板子上两个时钟源各自的抖动会让相位缓慢漂移某些相位组合下才会暴露的问题在整数比仿真里永远撞不到。仿真时故意用带小数的周期比能提前发现这类问题。第五条ERAM的初始化只在ROM模式或者支持初值的模式下有效FIFO模式不要指望它。如果你的设计依赖上电时存储区有一个已知的初值那就在逻辑里显式写一遍初始化流程用一个状态机在复位后把需要的地址先写一遍别指望工具帮你做。这个习惯能避免很多仿真对、上板错的诡异问题。第六条综合报告里的ERAM占用数要逐块核对。工具给的汇总数字是对的但它不会告诉你哪一块被谁占了。资源紧张的时候一定要看详细的资源分配报告找到那些只用了几个比特却占了一整块的浪费点。这种浪费在初期不明显等到设计后期加功能加不进去的时候回头一看全是这种小浪费堆出来的。第七条真双端口的冲突策略写进代码注释里。这个选项在IP配置界面里不在代码里半年后接手的人包括你自己看代码完全看不出这里有个隐含约定。把本RAM同地址读写采用write-first策略读出的为新值这行注释写在例化旁边能省下未来一次深夜debug。我个人在实际项目里对ERAM的态度经历过一个明显的变化。早期把它当纯粹的存储能不用就不用觉得逻辑资源够使后来被跨时钟域和突发整流的问题反复教育才开始认真对待它的可配置性。到现在我打开一个新工程的第一个动作往往不是画状态机而是先把数据通路上有哪些缓冲点标出来逐个判断该用哪种模式。这个顺序调整过来之后很多原本会在后期爆发的问题在架构阶段就被消解掉了。硬件FIFO控制器这类东西的价值不在于它多先进而在于它把一件容易做错的事变成了不容易做错的事。指针同步、满空判定、跨域握手这些地方的坑是固定的谁写都得踩一遍。能让专用电路替你踩就别自己上。至于那些剩下的、真正需要你动脑子的部分——深度怎么算、阈值怎么设、位宽深度怎么折中——那才是设计中真正有含量的地方也是工具永远替不了你的地方。
阅读完成 · 觉得有帮助?
咨询建站