ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

DSP与FPGA通过EMIF实现高速地址映射通信

DSP与FPGA通过EMIF实现高速地址映射通信 三年前接手过一块电机控制板DSP 跑控制环FPGA 负责采样时序和多路编码器解码两边原本只靠一根串口交换状态字控制周期一缩到 50 微秒串口那点带宽立刻成了瓶颈每帧刚发完参数就得准备下一帧CPU 光在等中断。后来我把 FPGA 整个挂到 DSP 的 EMIF 上让 DSP 直接拿读写内存的方式去读写 FPGA 里的寄存器和缓冲区通信这件事从要花心思变成了几乎不存在。EMIF、FPGA、DSP 三者凑在一起做高速连接说白了就是这么个路子DSP 这边不动驱动、不写协议栈FPGA 这边扮演一颗听话的 SRAM双方在中低速映射访问和高速块搬运之间找一个平衡点。这篇东西写给正在选通信方案的人、第一次接触 EMIF 的 FPGA 工程师以及被 DSP 和 FPGA 之间的数据搬运折磨过的嵌入式开发者从硬件连线、寄存器配置、时序计算一直讲到我踩过的那些坑。1. 先把 EMIF 这件事捋清楚DSP 眼里的 FPGA 到底是个什么角色1.1 地址映射式通信比协议栈式通信省太多事EMIF 的全称是 External Memory Interface外部存储器接口。DSP 设计它出来本意是接 NOR Flash、NAND、SRAM 这类外存CPU 访问这些外存的方式就是最朴素的 load/store 指令给一个地址读回来一个数没有任何中间层。FPGA 能挂上去核心原因就在于 EMIF 控制器并不关心对面那颗芯片到底是什么型号它只关心对面是不是按约定的时序在正确的时间把数据放到总线上。所以 FPGA 只要把自己的接口做成一颗 SRAM 的样子——接受片选、接受地址、接受读写选通、在规定窗口内驱动数据线——DSP 就完全把它当内存看。这件事带来的直接好处是软件侧的开发量几乎归零。你不需要写驱动、不需要定义描述符、不需要处理协议握手*(volatile unsigned int *)0x60000000 value;这样一行代码就能把数据丢进 FPGA反过来一句读取就能把 FPGA 采集的结果拿回来。控制器、加速器、图像流水线这些实时性要求高的场景最怕的就是通信引入不确定延迟地址映射式访问的延迟基本是可以算出来的固定值这对闭环控制来说非常关键。我在实际项目里做过对比同一批数据走 SPI 主从加 DMA 的时候光是中断和描述符管理的代码就有四百多行调试期间最头疼的是偶发的时序竞争换成 EMIF 之后FPGA 侧的状态机不到两百行 RTLDSP 侧零驱动出现问题基本都能定位到某根线或者某个时序参数上。1.2 选型的时候要想清楚为什么不用其他几种接口很多人第一反应是EMIF 引脚这么多为什么不用 SPI 或者 uPP。这个问题的答案取决于你的数据量和延迟要求我按实际用过的几种方案做了一张对照表方便你按需取舍。接口方式典型带宽量级引脚数量软件复杂度适合场景SPI几 Mbps 到几十 Mbps4 根低配置寄存器、慢速遥测UART几百 kbps 到几 Mbps2 根低调试打印、命令下发uPP几十到几百 MB/s十几根中视频、ADC 数据流EMIF 异步十几到几十 MB/s40 根以上低寄存器映射、中速数据块EMIF 同步一百到几百 MB/s40 根以上低到中大批量数据搬运高速串行类接口上 GB/s4 到 8 根差分高板间大数据、图像从表里能看出来EMIF 的位置很微妙引脚数量最多但软件复杂度极低带宽处于中档偏上的区间。如果你的数据量在几十 MB/s 这个量级又希望软件侧尽量简单EMIF 是非常划算的选择。真正需要上 GB/s 的场景EMIF 并行总线在信号完整性上会先撑不住这时候就该考虑高速串行方案了。这里有一个很多人会忽略的判断点EMIF 的低软件复杂度是长期收益而引脚多是一次性成本。板子面积紧张的时候可能没得选但板子面积够用的情况下我更倾向于用 EMIF因为后期维护和调试的时间成本远高于多占几平方厘米的 PCB。2. 硬件层的连接方案信号清单、位宽与引脚规划2.1 一根都不能漏的信号清单和方向约定以 C66x 的 EMIF16 异步接口为例常见信号大致分四类。第一类是地址和片选包括 EMIF_A 地址总线、EMIF_BA 高位地址、EMIF_CE 片选全部是 DSP 输出、FPGA 输入第二类是读写的选通信号EMIF_WE 写使能、EMIF_OE 读使能也都是 DSP 输出第三类是数据总线 EMIF_D双向方向由当前是读周期还是写周期决定第四类是握手和时钟EMIF_WAIT 是 FPGA 输出给 DSP 的等待请求EMIF_CLK 是 DSP 输出的接口时钟。这些信号里最容易出问题的是方向没搞对的那几根。数据总线必须做成三态FPGA 只有在片选有效且读使能有效的窗口内才驱动它其余时间必须处于高阻否则会和 DSP 在写周期驱动的数据撞车轻则读回来数据全乱重则长期对打把 IO 打坏。我在第一次做这块的时候因为寄存器里漏写了输出使能条件导致 FPGA 在写周期也在驱动数据线现象是写进去 0x1234读回来 0xAAAA查了两天才反应过来是总线争用。片选信号的数量决定了你能给 FPGA 划多少个独立地址空间。C66x 的 EMIF16 一般有若干个片选每个片选可以独立配置时序参数实际使用时常见做法是把一段连续区域给寄存器组另一段给数据缓冲区用两个片选分开这样读写的时序要求可以分别调优。2.2 位宽怎么定16 位是主流但要算清代价C66x 的 EMIF16 数据宽度固定 16 位这是硬约束不存在配置成 32 位的选项。有些老一代 C64x 系列的 EMIF 支持 8/16/32/64 位可配如果你用的是那一类器件选择空间会大一些。位宽直接决定单次访问能搬多少数据在频率和周期数相同的前提下32 位对比 16 位就是两倍的吞吐量。那 16 位位宽不够用怎么办我试过两种思路。第一种是用两个片选做交错访问把低 16 位放一个片选、高 16 位放另一个片选软件侧写成一个结构体两次访问拼出一个 32 位数。这种做法软件上稍微麻烦一点但硬件上省事引脚也不增加。第二种是干脆上同步模式同步模式下接口时钟连续输出配合 FPGA 内部的突发支持单位时间的有效数据量比异步模式高出一大截代价是 FPGA 侧的状态机要复杂一些。关于地址线通常只需要接足够的位数覆盖你的地址空间。FPGA 里用不到那么多地址的话多余的高位地址线可以不接但要注意 DSP 侧的 EMIF 配置里地址空间大小要配置成和实际连线一致配大了访问到未连接的地址会读到随机值。这个坑我踩过一次当时配置成 16 位地址空间实际只接了 12 位结果读某几个特定偏移的时候数据总是不对最后发现是高位地址悬空导致的。注意数据总线一定要做三态控制且必须在片选无效时立刻释放。释放的时序余量要留足宁可晚一点驱动也不能早一点释放这两种错误的后果不对称。2.3 板级设计里那些容易被忽略的细节EMIF 的并行总线在几十兆的等效频率下板级设计的重要性会突然上升。我总结下来要盯住三个点走线参考平面要完整、地址和数据总线之间要做等长约束、接口电压要匹配。参考平面不完整是很多偶发读错问题的根源。地址线或者控制线跨过平面分割的时候回流路径被切断边沿会变缓甚至出现过冲在示波器上看可能只是边沿变圆了一点但对采样窗口来说就是实实在在的压缩。我遇到过一块板子异步模式跑得完全正常一改成同步模式提高频率就出错最后发现是数据线跨了电源分割区。等长的要求没有 DDR 那么严苛但在时序余量已经被压到极限的时候地址线和数据线之间几十皮秒的偏差也会体现在最终的采样正确率上。我的习惯是让地址线、控制线相对数据线的长度差控制在几百 mil 以内这个量级的布线约束在大部分 EDA 工具里用规则就能自动满足。接口电压要对着 DSP 的 IO 电压域确认。有的 DSP 的 EMIF IO 电压可以选择有的固定FPGA 侧对应的 IO bank 电压必须匹配顺便确认一下上下拉的需求。片选、读写使能这类控制信号我一般会在 FPGA 侧或者 DSP 侧加弱上拉保证上电复位期间总线处于确定状态避免上电瞬间的误访问。3. DSP 侧的寄存器配置与时序计算3.1 CEnCFG 里每个字段到底在管什么EMIF 的异步接口配置集中在一个叫 CEnCFG 的寄存器里每个片选一个。字段名各代器件略有差异但含义是相通的我这里按功能讲具体位域以你手上那版手册为准。ASIZEAddress Size地址空间大小决定这个片选能映射多大范围。SSSelect Strobe选择用片选信号还是用地址有效来界定访问边界。EWExtended Wait是否使能 WAIT 信号扩展周期做流控必须打开。W_SETUP / W_STROBE / W_HOLD写周期的建立、选通、保持三段时钟数。R_SETUP / R_STROBE / R_HOLD读周期的建立、选通、保持三段时钟数。TATurn Around读写方向切换之间的空闲周期数防止总线争用。这几个字段里TA 是最容易被漏掉的。当一次读紧跟着一次写的时候如果 TA 是 0写周期开始驱动数据的那一刻FPGA 可能还没完全释放数据线两边就会打架。我现在的习惯是异步接口一律给 TA 留至少 1 个周期多花的这点时间换来的稳定性非常值得。另外还有一组独立的手握配置寄存器用来管理 WAIT 的行为包括在等待开始前额外插入多少周期、等待采样前插入多少周期、以及最大等待周期数。最大等待周期数这个字段是保护 CPU 不被永久卡死的关键如果 FPGA 异常导致 WAIT 一直拉低没有超时保护的话 DSP 就彻底挂在那里了。3.2 时序参数怎么算从时钟周期反推纳秒时序参数的计算逻辑很直白先把 EMIF 的接口时钟频率确认下来算出每个周期多少纳秒再根据 FPGA 侧组合逻辑的延迟和板级飞行的延迟反推需要多少个周期。举个例子EMIF 时钟假设跑 100 MHz一个周期就是 10 ns。FPGA 侧从片选有效到内部数据准备好纯组合逻辑大概 5 到 8 ns加上板级飞行和 IO 缓冲的几纳秒整体在 10 ns 上下。保守起见读周期的建立段给 2 个周期也就是 20 ns选通段给 4 个周期 40 ns保持段给 1 个周期 10 ns那么一次读访问的总时间就是 204010 加方向切换的开销大约 70 到 80 ns。按 16 位也就是 2 字节算单次读访问的等效带宽是 2 字节除以 75 ns约等于 26 MB/s。这个数字就是异步模式的现实天花板附近。想让带宽更高要么缩短选通周期要么上同步模式要么把位宽做大。我把不同配置下的理论带宽整理成下面这张表方便你快速估算。接口模式时钟/周期建立选通保持单次访问时间16 位等效带宽异步保守配置100 MHz / 10 ns261约 90 ns约 22 MB/s异步优化配置100 MHz / 10 ns231约 60 ns约 33 MB/s异步极限配置100 MHz / 10 ns121约 40 ns约 50 MB/s同步模式100 MHz 连续突发 4 拍约 40 ns / 4 字约 200 MB/s表里的极限配置在实际板子上未必能稳定跑因为留给 FPGA 逻辑的余量已经很薄了温度、电压、批次差异都可能让它偶发失效。我一般不会把参数压到极限留出 30% 左右的余量换来的是长期运行的稳定性。3.3 用 EDMA 把 CPU 从搬数据里解放出来寄存器映射访问虽然简单但如果每一拍数据都靠 CPU 去读CPU 就被绑死在搬运上了。这时候要上 EDMA把 FPGA 映射的地址配成源或者目的触发方式选成手动触发或者由 FPGA 通过某个中断信号触发让 DMA 引擎去做批量搬运CPU 只在搬完之后处理一次中断。配置 EDMA 搬运的时候有几个地方要注意。第一个是传输的地址维度EMIF 是 16 位宽度但 EDMA 的传输粒度可以是 16 位、32 位或者更大如果配成 32 位传输硬件会自动拆成两次 16 位访问前提是地址空间连续且对齐。第二个是每次搬运的字节数一次搬太多会让中断响应变慢搬太少则中断开销占比上升我一般会按实际数据帧的长度来定让一次搬运刚好覆盖一帧。实测下来EDMA 配上 EMIF 异步接口16 位宽度的情况下能稳定跑到接近 20 MB/s 的有效吞吐而且 CPU 占用几乎为零这对跑控制环的 DSP 来说非常关键。如果换成同步模式同样的 EDMA 配置能跑到一百多 MB/s这已经是另一个量级了。4. FPGA 侧从异步从机模型到跨时钟域缓冲4.1 异步接口读写状态机的实现要点FPGA 侧要做的第一件事是写一个能听懂 EMIF 读写周期的从机。核心思路是用一个比接口速率快得多的内部时钟去采样 EMIF 的控制信号检测边沿然后走状态机。采样时钟的倍数我一般取 4 倍以上比如接口等效频率 20 MHz内部就用 100 MHz 去采样这样能分辨出信号的先后关系。下面是一个简化到骨架的模型实际项目里需要根据你的地址译码和数据通路补充module emif_slave #( parameter AW 16 // 地址位宽 )( input wire clk, // 内部采样时钟建议 4x 接口速率 input wire rst_n, input wire ce_n, // 片选低有效 input wire oe_n, // 读使能低有效 input wire we_n, // 写使能低有效 input wire [AW-1:0] addr, inout wire [15:0] dq, // 双向数据总线 output reg wait_n, // 等待请求低有效 // 用户侧接口 output reg [15:0] reg_data, output reg reg_we, input wire [15:0] rd_data ); // 三级同步 边沿检测 reg [2:0] ce_s, oe_s, we_s; always (posedge clk or negedge rst_n) begin if (!rst_n) begin ce_s 3b111; oe_s 3b111; we_s 3b111; end else begin ce_s {ce_s[1:0], ce_n}; oe_s {oe_s[1:0], oe_n}; we_s {we_s[1:0], we_n}; end end wire ce_active ~ce_s[1]; wire wr_active ce_active ~we_s[1]; wire rd_active ce_active ~oe_s[1]; // 写通路在写使能有效的稳定期内锁存 always (posedge clk or negedge rst_n) begin if (!rst_n) begin reg_we 1b0; reg_data 16h0; end else begin reg_we 1b0; if (wr_active ~we_s[2] we_s[1]) begin reg_data dq; reg_we 1b1; end end end // 读通路只在读窗口内驱动总线其余时间高阻 assign dq (rd_active !wr_active) ? rd_data : 16hz; // 需要拖周期的时候拉低 wait_n always (posedge clk or negedge rst_n) begin if (!rst_n) wait_n 1b1; else wait_n 1b1; // 默认不等待按需改成条件拉低 end endmodule这段代码里最关键的两处一是数据总线的三态控制条件必须同时判断读有效和写无效二是写数据的锁存要在写使能的稳定区而不是边沿因为总线上数据可能还在变化。我早期版本里在边沿锁存结果读到的值偶尔会差一个查了很久才发现是采样点选早了。4.2 跨时钟域缓冲把接口时钟和内部时钟彻底隔开FPGA 内部逻辑通常跑在自己的时钟域和 EMIF 接口的节奏完全没关系。如果 DSP 写进来的数据直接被内部逻辑使用就会出现跨时钟域的问题表现为偶发的数据错乱或者状态机跑飞。标准做法是在接口和内部逻辑之间放一层异步 FIFO 或者双口 RAM。异步 FIFO 的读写指针跨时钟域传递时要用格雷码这样每次只有一个比特变化采样的时候最多错一位而不会出现完全错误的指针值。这个细节很多新手会忽略直接用二进制指针跨域平时看起来没事一上温度或者一改布局就出错很难复现也很难查。缓冲的深度要根据数据产生和消费的速率差来定。如果 DSP 是按帧写、内部逻辑是连续消费缓冲至少要能存下两帧这样即使 DSP 因为其他任务晚了一小会儿内部逻辑也不会断流。我一般会按最坏情况下的延迟差乘以数据速率再留一倍的余量来定深度宁大勿小FPGA 里多一块 BRAM 的成本远低于返工。提示给异步 FIFO 加上水位标志一半满和四分之一满这两个阈值分别接到中断或者状态寄存器上DSP 侧就能靠查询状态来决定什么时候该搬数据比盲目定时轮询可靠得多。4.3 时序约束和引脚约束FPGA 工程里对 EMIF 接口的引脚必须做输入输出延迟约束否则综合工具不知道外部信号的到达时间时序分析结果没有意义。输入延迟要按 DSP 的输出有效窗口来估输出延迟要按 FPGA 的驱动延迟加板级飞行时间来估。这两个值算不准最后靠上板试出来的参数就是碰运气。引脚约束里还有一个电压标准的选择。FPGA 侧对应 bank 的 IO 标准必须和 DSP 的接口电压一致常见的是 3.3V 或者 1.8V 的 LVCMOS。如果两边电压不一致又必须直连就得考虑电平转换这会额外引入延迟时序参数要重新算。我在一个项目里因为 FPGA 用的 bank 是 1.8V、DSP 是 3.3V中间加了转换芯片结果建立时间不够最后是把选通周期从 3 个加到 4 个才稳下来。5. 带宽测算与提速手段5.1 先把天花板算清楚再谈优化很多人一上来就问怎么让 EMIF 跑得更快但如果不清楚当前配置的理论上限优化就没有方向。带宽的三个乘数是位宽、频率和有效占空比任何一项提升都能直接反映到带宽上。位宽方面C66x 的 EMIF16 固定 16 位没法改如果器件支持多位宽配置优先选宽的那档。频率方面EMIF 接口时钟通常由系统时钟分频得到提高分频系数能直接缩短每个周期的时间但要注意时序余量会成比例压缩。有效占空比是最容易优化的地方指的是总时间里真正在传数据的比例异步模式下每次访问都有建立、保持和方向切换的开销占空比能到 60% 就不错了同步模式下可以做到 90% 以上。我做过一次实测同样的硬件异步保守配置的有效带宽是 21 MB/s把选通和保持各压缩一个周期之后升到 30 MB/s改成同步模式之后直接到 180 MB/s。这三个数字说明了一件事接口模式的选择比参数微调的影响大得多。如果你的数据量真的需要上百 MB/s就应该在设计初期就选同步模式而不是在异步上调来调去。5.2 提带宽的四个实操手段第一个手段是改用同步模式。同步模式下 DSP 连续输出接口时钟FPGA 按节拍响应省掉了每次访问的建立和保持开销。代价是 FPGA 侧的状态机要跟着时钟节拍走地址和数据要在正确的时钟沿上稳定设计复杂度高一些但收益非常明显。第二个手段是用 WAIT 做流控而不是无脑加大选通周期。当 FPGA 内部缓冲区快满或者正在处理别的任务时主动拉低 WAIT 让 DSP 多等几个周期平时则让 WAIT 保持无效访问以最快速度通过。这样一来平均延迟比固定的大周期配置低得多因为大部分访问其实并不需要那么长时间。第三个手段是做乒乓缓冲。把数据区分成两块DSP 写 A 块的时候内部逻辑读 B 块写满后交换。这样两侧永远不会同时访问同一块内存也不需要软件层面加锁。乒乓的块大小要大于一帧数据交换的时机由状态标志控制。第四个手段是减少小包访问。每次访问都有固定的开销访问次数越少摊销下来的开销越低。能用 EDMA 一次搬 1 KB 就不要分成十次搬 100 字节。我在项目里把离散的寄存器读写改成一整块结构化的映射区一次读回全部状态代码简单了带宽也上去了。6. 常见问题排查实录6.1 读回来的数据全错或者固定值现象是读任何地址都返回同一个值或者返回 0xFFFF、0x0000。这类问题九成出在硬件连线和方向控制上。先确认片选有没有接对有的板子把片选接到了错误的引脚上DSP 访问那个地址区间的时候 FPGA 根本不知道。再确认读使能的极性EMIF 上的读使能是低有效如果 FPGA 代码里当成高有效来处理那就永远不会驱动数据总线。如果是返回固定值还要检查 FPGA 有没有真的在驱动总线。最简单的验证办法是把数据总线接一个逻辑分析仪或者示波器在 DSP 发起读的时候看线上有没有电平变化。如果一直处于中间电平或者高阻态那就是 FPGA 侧的输出使能条件写错了。还有一个容易被忽略的点是地址译码。如果 FPGA 只接了低位地址而 DSP 配置的地址空间包含了未连接的高位那么访问高位地址区域的时候FPGA 会把所有高位地址都当成同一个位置读回来自然是重复的。这时候要么把高位地址也接上要么把地址空间配置成实际连接的大小。6.2 WAIT 信号不生效或者 DSP 直接卡死WAIT 相关的配置分散在两个地方一个在异步配置寄存器里决定是否使能扩展等待另一个在专门的等待周期配置寄存器里决定等待的采样时机和最大长度。如果使能位没打开FPGA 把 WAIT 拉得再低也没用DSP 会按固定周期走完访问。反过来如果最大等待周期数配置得太大或者干脆没配而 FPGA 因为逻辑异常一直没有释放 WAITDSP 就会永远停在那条读指令上看门狗如果没开整个系统就挂了。我现在的做法是最大等待周期数设置成正常需求的三倍左右既能容纳正常的流控延迟又能在异常时及时超时。超时之后 DSP 会通过总线错误的方式报出来方便定位。排查这类问题我一般会同时抓三根线片选、WAIT、数据总线上的某一位。看片选有效的时刻 WAIT 是什么状态看 WAIT 释放的时候数据有没有稳定。这三根线的相对时间关系一看就清楚了。6.3 低速正常、提速就出错这个现象几乎可以断定是时序余量不足。可能的来源有三个板级飞行延迟随温度变化、FPGA 内部逻辑延迟随电压变化、时钟抖动导致采样点漂移。异步模式下对时钟抖动不敏感所以问题一般出在采样窗口本身就不够。处理办法是逐项压缩不必要的延迟。先看 FPGA 侧从地址有效到数据有效的路径是不是太长能不能拆成流水线再看板级走线有没有绕远路能不能重布最后才考虑回调时序参数。顺序很重要因为调参数是最省事但也最容易掩盖真实问题的做法如果根因在板级调参数只是在赌温度范围。如果确实是同步模式下的高频问题还要检查跨时钟域的信号有没有做同步处理。我在一次调试中发现内部逻辑直接用了片选信号做使能没有打两拍平时运行正常一旦环境温度升高就出现偶发丢帧加上两级同步器之后就彻底干净了。6.4 常见问题速查现象最可能的原因排查方向读回固定值总线未驱动或极性错误测数据线电平查输出使能条件写入读回不一致总线争用或锁存时刻错误查三态释放时序调整锁存点高速时偶发错误时序余量不足或跨时钟域加同步器回调时序参数DSP 卡死WAIT 未释放且无超时配置最大等待周期检查流控逻辑数据高低字节颠倒字节序或字节使能接反核对字节选择信号与地址线特定地址段读错高位地址未连接补齐地址线或缩小配置空间注意排查顺序建议从硬件到软件先确认线的物理连接和电平再看寄存器配置最后看时序参数。反过来查容易在软件里绕圈子最后发现是根线没接。7. 几个踩过坑之后才想明白的经验EMIF 看起来是一个很老派的接口并行总线、地址映射、时序参数都是上世纪就有的思路但它解决实际问题的效率非常高。我用它做过图像数据搬运、做过运动控制参数交互、也做过 FPGA 侧 DMA 的触发源每一次的感受都是同一句话简单的东西只要用对了地方就不会过时。真正需要花心思的地方不是接口本身而是接口两端的缓冲区设计。接口只是条管道管道再宽如果两端的水位控制没做好照样会溢出或者断流。所以我现在做这类项目先花时间画清楚两侧的数据流图算清楚产生速率和消费速率把缓冲深度和状态标志设计好再去写状态机和配置寄存器效率比上来就写 RTL 高得多。最后分享一个我自己一直在用的小习惯在 FPGA 侧留一个固定的状态寄存器区把接口的运行计数、错误计数、FIFO 水位、最近一次访问的地址和数据都挂上去。调试的时候 DSP 几句读取就能把现场还原出来比接示波器抓线快得多。这个寄存器区本身占不了几个地址但在排查偶发问题的时候价值极高。
返回列表