尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

异步FIFO设计全解析:从格雷码到Verilog实现与调试

异步FIFO设计全解析:从格雷码到Verilog实现与调试 1. 项目概述异步FIFO的核心价值与挑战在数字电路设计尤其是FPGA和ASIC开发中异步FIFOFirst In First Out是一个绕不开的经典话题。它本质上是一个数据缓冲队列但其核心挑战在于数据的写入和读取操作分别由两个不同频率、不同相位的时钟域控制。想象一下你有一个高速的ADC模数转换器在源源不断地产生数据而处理这些数据的DSP数字信号处理器却运行在另一个较慢的时钟下。如何安全、高效地将数据从“生产者”传递到“消费者”而不发生数据丢失、重复或错乱这就是异步FIFO要解决的终极问题。我接触过的很多项目从高速数据采集到多核处理器间的通信异步FIFO都是保证系统稳定性的基石。然而实现一个真正可靠、无漏洞的异步FIFO远不是简单例化一个IP核那么简单其背后涉及跨时钟域同步、指针比较、空满判断等一系列精妙且容易踩坑的设计。2. 异步FIFO的整体架构与设计思路一个健壮的异步FIFO其设计思路必须围绕“安全跨时钟域”这一核心展开。我们不能让写指针和读指针直接在不同时钟域间比较那会产生亚稳态导致系统崩溃。因此通用的架构采用“双端口RAM 同步化指针”的方案。2.1 核心组件拆解一个典型的异步FIFO包含以下几个关键部分双端口RAM这是数据的物理存储单元。一个端口服务于写时钟域负责存入数据另一个端口服务于读时钟域负责取出数据。两个端口完全独立互不干扰。写指针与写地址生成逻辑位于写时钟域。写指针指向下一个将要写入的RAM地址。每次有效的写操作后写指针递增。读指针与读地址生成逻辑位于读时钟域。读指针指向下一个将要读出的RAM地址。每次有效的读操作后读指针递增。指针同步链这是设计的灵魂。为了在写时钟域判断“FIFO是否已满”我们需要知道读指针走到了哪里。但读指针属于读时钟域不能直接使用。因此我们需要将读指针同步到写时钟域。同样为了在读时钟域判断“FIFO是否为空”我们需要将写指针同步到读时钟域。这个同步过程通常通过两级或多级触发器链实现以降低亚稳态传播的风险。空满标志生成逻辑基于本地指针和同步过来的对方指针通过特定的比较逻辑产生fifo_empty和fifo_full信号。这两个信号的生成算法是设计的核心难点。2.2 为什么选择格雷码在搜索热词中“格雷码”被频繁提及这绝非偶然。在指针同步环节如果我们使用普通的二进制码作为指针会引入灾难性的问题。假设写指针从01117变为10008在二进制下这需要4个比特位同时翻转。在跨时钟域同步的瞬间如果采样的时钟沿正好卡在变化过程中同步链可能采样到0000到1111之间的任何一个非法值导致空满判断完全错误。格雷码的魔力在于相邻的两个数值之间仅有1个比特位发生变化。例如十进制7和8的格雷码分别是0100和1100只有最高位不同。这样即使在变化瞬间被采样也只会产生一个比特的亚稳态最终同步得到的结果要么是前一个值7要么是后一个值8而绝不会是一个毫不相干的中间值。这极大地提高了指针同步的可靠性。因此在FIFO内部我们使用格雷码来传递指针仅在生成RAM地址时将格雷码指针转换回二进制地址。注意使用格雷码的前提是FIFO的深度必须是2的幂次方如16 32 64 128等。只有这样指针在递增时才能构成一个闭环的格雷码序列。如果深度不是2的幂次方格雷码的相邻特性在序列头尾衔接处会被破坏。3. 关键细节解析与设计要点理解了整体架构我们深入到几个最容易出问题的细节。这些地方往往是仿真能过但上板就死机的根源。3.1 指针的位宽与深度表示这是一个初学者极易混淆的概念。对于一个深度为DEPTH的FIFO我们需要多少位宽的指针答案是指针位宽需要比地址位宽多1位。假设FIFO深度DEPTH 8那么寻址RAM需要log2(8) 3位地址addr[2:0]范围0-7。但我们的写指针wptr和读指针rptr需要log2(8) 1 4位ptr[3:0]。多出来的那一位最高位MSB有什么妙用它用于区分指针“绕了一圈”的情况。我们可以这样理解当写指针和读指针指向同一个地址时FIFO可能是空也可能是满。如何区分就看指针是否已经“套圈”了。空条件当wptr和rptr的所有位包括MSB都完全相等时FIFO为空。这意味着写指针追上了读指针且没有发生套圈。满条件当wptr和rptr的除MSB外的低位部分相等但MSB不同时FIFO为满。这意味着写指针比读指针多跑了一圈套了一圈虽然它们指向的RAM地址相同但写指针的MSB已经翻转而读指针的MSB没有。这种比较方式完美地解决了空满状态的判断问题。3.2 同步链的设计与亚稳态指针同步链通常采用两级触发器这是权衡了可靠性和延迟后的经典选择。// 将读指针的格雷码同步到写时钟域 reg [PTR_WIDTH-1:0] rptr_gray_sync1, rptr_gray_sync2; always (posedge wclk or negedge wrst_n) begin if (!wrst_n) begin rptr_gray_sync1 0; rptr_gray_sync2 0; end else begin rptr_gray_sync1 rptr_gray; // 第一级同步亚稳态可能发生在这里 rptr_gray_sync2 rptr_gray_sync1; // 第二级同步输出趋于稳定 end end // 使用 rptr_gray_sync2 进行满标志判断第一级触发器sync1是亚稳态的高发区。如果rptr_gray在wclk的上升沿附近变化sync1的输出可能在一段时间内处于非0非1的亚稳态。第二级触发器sync2的作用就是“隔离”它给sync1的输出更多的时间一个wclk周期来稳定到一个确定值。虽然sync2的输出仍然可能不是当前rptr_gray的真实值可能是上一个周期的值但对于FIFO的空满判断来说指针慢一拍是安全的。它只会让full或empty信号提前一点或延后一点产生而不会产生错误的判断这属于性能上的保守设计但保证了功能的正确性。3.3 空满标志的生成逻辑基于同步后的指针和本地指针生成空满标志。这里以满标志full为例写时钟域逻辑将同步过来的读指针格雷码rptr_gray_sync2转换回二进制码rptr_bin_sync仅用于比较不用于RAM地址。计算写指针和同步读指针的“差值”。由于指针是多一位的我们可以直接比较。满条件{~wptr[MSB], wptr[MSB-1:0]} rptr_bin_sync[MSB-1:0]。翻译过来就是写指针反转其最高位后的剩余部分等于同步读指针的剩余部分。这等价于我们之前说的“除MSB外相等MSB不同”。空标志empty的生成在读时钟域进行逻辑类似但更简单直接比较同步过来的写指针格雷码和本地读指针格雷码若全部位相等则为空。实操心得在仿真时一定要密切关注空满标志的跳变时机。一个常见的错误是full信号在FIFO真满之前就拉高导致最后一个数据写不进去保守或者full信号拉晚了一个周期导致溢出危险。前者可接受后者是致命错误。通过仔细分析同步延迟可以调整比较逻辑的“提前量”在安全和性能之间取得平衡。4. 异步FIFO的完整Verilog实现与关键代码解析下面我将结合一个深度为8的异步FIFO实例拆解关键模块的Verilog代码。我们将采用典型的模块划分async_fifo顶层模块、dual_port_ram双端口RAM、sync_r2w同步器、sync_w2r同步器、wptr_full写控制与rptr_empty读控制。4.1 顶层模块与参数定义module async_fifo #( parameter DATA_WIDTH 8, // FIFO数据位宽 parameter ADDR_WIDTH 3 // FIFO地址位宽深度2**ADDR_WIDTH8 )( // 写端口 input wire wclk, input wire wrst_n, // 低电平有效写时钟域复位 input wire winc, // 写使能 input wire [DATA_WIDTH-1:0] wdata, output wire wfull, // 读端口 input wire rclk, input wire rrst_n, // 低电平有效读时钟域复位 input wire rinc, // 读使能 output wire [DATA_WIDTH-1:0] rdata, output wire rempty ); // 内部信号声明 wire [ADDR_WIDTH:0] wptr, rptr; // 多一位的二进制指针 wire [ADDR_WIDTH:0] wptr_gray, rptr_gray; // 格雷码指针 wire [ADDR_WIDTH:0] wptr_gray_sync2r, rptr_gray_sync2w; // 同步后的格雷码指针 // 实例化双端口RAM dual_port_ram #( .DATA_WIDTH(DATA_WIDTH), .ADDR_WIDTH(ADDR_WIDTH) ) u_ram ( .wclk (wclk), .waddr (wptr[ADDR_WIDTH-1:0]), // 写地址取指针低ADDR_WIDTH位 .wdata (wdata), .we (~wfull winc), // 非满且写使能有效时才写入 .rclk (rclk), .raddr (rptr[ADDR_WIDTH-1:0]), // 读地址取指针低ADDR_WIDTH位 .rdata (rdata) ); // 写指针与满标志生成模块 wptr_full #( .ADDR_WIDTH(ADDR_WIDTH) ) u_wptr_full ( .wclk (wclk), .wrst_n (wrst_n), .winc (winc ~wfull), // 确保不满时才递增 .rptr_gray_sync2w (rptr_gray_sync2w), .wptr (wptr), .wptr_gray (wptr_gray), .wfull (wfull) ); // 读指针与空标志生成模块 rptr_empty #( .ADDR_WIDTH(ADDR_WIDTH) ) u_rptr_empty ( .rclk (rclk), .rrst_n (rrst_n), .rinc (rinc ~rempty), // 确保不空时才递增 .wptr_gray_sync2r (wptr_gray_sync2r), .rptr (rptr), .rptr_gray (rptr_gray), .rempty (rempty) ); // 同步器将读指针格雷码同步到写时钟域 sync_r2w #( .ADDR_WIDTH(ADDR_WIDTH) ) u_sync_r2w ( .wclk (wclk), .wrst_n (wrst_n), .rptr_gray (rptr_gray), .rptr_gray_sync2w (rptr_gray_sync2w) ); // 同步器将写指针格雷码同步到读时钟域 sync_w2r #( .ADDR_WIDTH(ADDR_WIDTH) ) u_sync_w2r ( .rclk (rclk), .rrst_n (rrst_n), .wptr_gray (wptr_gray), .wptr_gray_sync2r (wptr_gray_sync2r) ); endmodule顶层模块清晰地勾勒了数据流和控制流。数据通过dual_port_ram流动而控制信号指针通过同步器在时钟域间传递。winc ~wfull和rinc ~rempty这样的组合逻辑确保了在FIFO满时忽略写请求在FIFO空时忽略读请求这是防止操作错误的基本防护。4.2 双端口RAM的实现双端口RAM是存储核心可以用FPGA内部的Block RAM资源实现也可以用寄存器阵列Register File实现后者更灵活但资源消耗大。这里给出一个通用的寄存器阵列实现便于理解和仿真。module dual_port_ram #( parameter DATA_WIDTH 8, parameter ADDR_WIDTH 3 )( input wire wclk, input wire [ADDR_WIDTH-1:0] waddr, input wire [DATA_WIDTH-1:0] wdata, input wire we, input wire rclk, input wire [ADDR_WIDTH-1:0] raddr, output reg [DATA_WIDTH-1:0] rdata ); // 定义存储器 reg [DATA_WIDTH-1:0] mem [(2**ADDR_WIDTH)-1:0]; // 写操作写时钟域 always (posedge wclk) begin if (we) begin mem[waddr] wdata; end end // 读操作读时钟域 always (posedge rclk) begin rdata mem[raddr]; // 同步读延迟一个时钟周期输出 end endmodule这是一个简单的同步读写的RAM模型。在实际的FPGA设计中我们通常会使用厂商提供的IP核如Xilinx的Block Memory Generator或Intel的RAM:2-PORT来替代这个模块以获得更好的面积和时序性能。IP核可以配置为真双端口True Dual Port、简单双端口Simple Dual Port并选择不同的读写模式。4.3 指针控制与空满判断模块这是设计的精髓。我们来看写控制模块wptr_full。module wptr_full #( parameter ADDR_WIDTH 3 )( input wire wclk, input wire wrst_n, input wire winc, input wire [ADDR_WIDTH:0] rptr_gray_sync2w, // 同步过来的读指针格雷码 output reg [ADDR_WIDTH:0] wptr, // 二进制写指针 output wire [ADDR_WIDTH:0] wptr_gray, // 格雷码写指针 output reg wfull ); reg [ADDR_WIDTH:0] wptr_next; wire [ADDR_WIDTH:0] rptr_bin_sync; // 同步读指针转换回的二进制 // 二进制指针递增逻辑 always (*) begin if (!wfull winc) begin wptr_next wptr 1b1; end else begin wptr_next wptr; end end // 写指针寄存器更新 always (posedge wclk or negedge wrst_n) begin if (!wrst_n) begin wptr 0; end else begin wptr wptr_next; end end // 二进制转格雷码 assign wptr_gray (wptr_next 1) ^ wptr_next; // 格雷码转二进制用于比较的同步读指针 gray2bin #(.SIZE(ADDR_WIDTH1)) u_g2b_sync ( .gray (rptr_gray_sync2w), .bin (rptr_bin_sync) ); // 满标志生成逻辑 always (posedge wclk or negedge wrst_n) begin if (!wrst_n) begin wfull 1b0; end else begin // 关键比较判断写指针的下一个位置是否等于同步读指针 // 即{~wptr_next[ADDR_WIDTH], wptr_next[ADDR_WIDTH-1:0]} rptr_bin_sync[ADDR_WIDTH-1:0] // 等价于判断 wptr_next 的高位取反后其低ADDR_WIDTH位是否等于 rptr_bin_sync 的低ADDR_WIDTH位 // 并且最高位不同。 // 一种更直观的实现方式 if ( (!wfull) ( (wptr_next[ADDR_WIDTH] ! rptr_bin_sync[ADDR_WIDTH]) (wptr_next[ADDR_WIDTH-1:0] rptr_bin_sync[ADDR_WIDTH-1:0]) ) ) begin wfull 1b1; end else if (wfull (wptr[ADDR_WIDTH-1:0] ! rptr_bin_sync[ADDR_WIDTH-1:0])) begin // 当FIFO满时一旦发生读操作导致读指针变化同步过来后低ADDR_WIDTH位不等满标志撤销 wfull 1b0; end end end endmodulegray2bin是一个简单的组合逻辑模块实现格雷码到二进制码的转换。读控制模块rptr_empty与之对称但空标志的判断更简单只需比较同步过来的写指针格雷码wptr_gray_sync2r和本地的读指针格雷码rptr_gray是否完全相等。4.4 同步器模块同步器模块非常简单就是经典的两级触发器链。module sync_r2w #( parameter ADDR_WIDTH 3 )( input wire wclk, input wire wrst_n, input wire [ADDR_WIDTH:0] rptr_gray, output reg [ADDR_WIDTH:0] rptr_gray_sync2w ); reg [ADDR_WIDTH:0] rptr_gray_sync1; always (posedge wclk or negedge wrst_n) begin if (!wrst_n) begin rptr_gray_sync1 0; rptr_gray_sync2w 0; end else begin rptr_gray_sync1 rptr_gray; rptr_gray_sync2w rptr_gray_sync1; end end endmodule4.5 二进制与格雷码转换函数这两个函数是组合逻辑可以放在单独的模块或使用function定义。// 二进制转格雷码 function [SIZE-1:0] bin2gray; input [SIZE-1:0] bin; begin bin2gray (bin 1) ^ bin; end endfunction // 格雷码转二进制串行转换逻辑清晰但延迟随位宽增加 function [SIZE-1:0] gray2bin; input [SIZE-1:0] gray; reg [SIZE-1:0] bin; integer i; begin bin[SIZE-1] gray[SIZE-1]; // 最高位相同 for (i SIZE-2; i 0; i i - 1) begin bin[i] bin[i1] ^ gray[i]; // 次高位 高位 ^ 当前格雷码位 end gray2bin bin; end endfunction格雷码转二进制也可以使用并行逻辑实现以降低关键路径延迟bin[i] ^(gray i)即从最高位到第i位的所有位进行异或。这可以用一个生成循环来实现。5. 仿真验证、常见问题与深度调试技巧设计完成后的仿真验证至关重要。一个完善的测试平台Testbench应该覆盖所有边界情况。5.1 测试场景设计基本功能测试连续写入数据直到满再连续读出直到空检查数据是否正确。随机读写测试使用$random或更高级的约束随机化方法随机生成winc和rinc信号长时间运行检查是否发生数据错误或状态标志错误。时钟频率与相位差测试写快读慢验证full标志能否有效阻止溢出。写慢读快验证empty标志能否有效阻止读空。时钟频率比非整数例如写时钟100MHz读时钟77MHz。这是最考验同步器设计的场景。时钟相位突变在仿真中动态改变时钟相位模拟真实的时钟抖动和偏移。复位测试在读写过程中随机复位复位释放后FIFO状态应恢复正确指针归零。边界条件测试在FIFO即将满/空时进行单次读写操作观察空满标志的跳变是否准确是否存在“漏判”或“误判”。5.2 常见问题与排查实录在实际项目中我遇到过不少异步FIFO相关的问题这里分享几个典型案例问题一仿真通过上板后偶尔数据错乱。现象在特定数据吞吐率下读出的数据顺序或内容发生错误。排查首先检查同步链。增加同步级数到三级sync1,sync2,sync3看问题是否缓解。如果缓解说明亚稳态是根源。检查格雷码转换逻辑。确认深度是否为2的幂次方。我曾遇到一个项目需求是深度12工程师图省事用了4位指针深度16但空满判断逻辑没按16深度设计导致指针在12处回环时格雷码序列断裂引发灾难。使用嵌入式逻辑分析仪如Xilinx的ILA抓取wptr_gray,rptr_gray_sync2w,wfull等关键信号。发现wfull信号在FIFO物理上未满时提前一个写时钟周期拉高导致最后一个数据写不进去。但下游模块以为写入了造成了数据缺失。解决问题出在满标志判断逻辑过于“保守”。我们比较的是wptr_next和同步读指针。由于同步读指针rptr_bin_sync可能比真实的读指针慢1-2个wclk周期导致wfull提前产生。对于某些对吞吐率要求极高的场景可以尝试调整逻辑例如仅当wptr而非wptr_next与同步读指针满足“满”条件时才拉高wfull。但这会引入“满”状态判断延迟一个周期的风险需要在安全性和性能间权衡。最终我们选择了保守但安全的方案并通知系统架构师此处存在一个气泡Bubble需要在系统级流量控制中考虑。问题二空满标志抖动Glitch。现象在Modelsim波形中看到empty或full信号在稳定前后有极窄的毛刺。排查空满标志是由组合逻辑比较器产生的。当参与比较的指针特别是经过同步链和格雷码转换的指针发生变化时由于各路径延迟Skew不同比较器的输入可能短暂处于非法状态导致输出产生毛刺。解决必须对空满标志信号进行寄存器输出打拍这是一个黄金法则。将比较逻辑的结果用触发器寄存一个周期后再输出。虽然这会引入一个周期的状态报告延迟但彻底消除了毛刺避免了将亚稳态或冒险传播到其他逻辑。always (posedge wclk or negedge wrst_n) begin if (!wrst_n) begin wfull 1b0; end else begin wfull wfull_nxt; // wfull_nxt是组合逻辑比较的结果 end end问题三读写指针不同步导致的“虚空”或“虚满”。现象FIFO报告为空但实际RAM中还有数据未读出虚空或报告为满但实际还有空间虚满。排查这通常是同步延迟导致的固有特性而非错误。由于读指针同步到写时钟域有延迟写逻辑“看到”的读指针比实际位置靠后因此会提前认为FIFO要满了保守。同理读逻辑“看到”的写指针也比实际靠后会提前认为FIFO要空了保守。这会导致FIFO的有效容量略小于理论深度例如8深度的FIFO可能只能稳定存储6-7个数据。解决理解并接受这是异步FIFO的正常行为。在设计系统时FIFO的深度要有一定余量例如所需缓冲深度为N则设计FIFO深度为N4或更多以抵消同步延迟带来的容量损失。不要试图去“补偿”这个延迟那会引入复杂的时序和风险。5.3 深度调试技巧波形标记法在仿真波形中将wptr和rptr的二进制值和格雷码值都以十进制和二进制形式显示出来同时显示它们同步后的值。将wfull和rempty信号高亮。这样任何时刻的FIFO状态都一目了然。断言Assertion在Testbench中插入SystemVerilog断言自动检查设计属性。例如// 检查满标志拉高时写指针不能再递增 property no_write_when_full; (posedge wclk) disable iff (!wrst_n) (wfull) |- ( !(winc !wfull) ); endproperty assert property (no_write_when_full) else $error(Write occurred while FIFO was full!);断言能在第一时间捕捉到设计违规比肉眼查看波形高效得多。功能覆盖率收集定义覆盖点Coverpoint如“FIFO从空到满再到空”、“在满状态下进行读操作”、“在空状态下进行写操作”、“读写指针交叉”等场景。确保测试用例覆盖了所有这些有趣的边界情况。门级仿真Gate-level Simulation在布局布线后用包含实际延迟信息的网表进行仿真。这是检查保持时间Hold Time违例、以及同步链在真实延迟下是否仍能工作的最终关卡。虽然耗时但对高可靠性设计是必要的。6. 异步FIFO的高级变体与优化方向基础的异步FIFO满足了大多数需求但在一些极端或特殊场景下我们可以考虑以下优化或变体6.1 基于计数器的异步FIFO有些设计会直接使用一个跨时钟域的计数器来表示FIFO中的数据个数。写侧递增计数器读侧递减计数器或使用一个计数器通过同步进行加减。这种方法概念简单但将多比特计数器同步本身就是一个挑战通常也需要借助格雷码。其空满判断变得极其简单count 0为空count DEPTH为满但同步计数器的延迟和复杂度可能并不比同步指针更有优势。6.2 几乎满Almost Full/几乎空Almost Empty标志在实际流控中等到full信号拉高再停止发送数据可能为时已晚因为上游模块可能需要一个反应时间。因此可以额外产生almost_full和almost_empty信号。例如当FIFO中的数据量达到DEPTH - 4时就拉高almost_full给上游模块预留几个时钟周期来停止发送。这只需要在现有的指针比较逻辑上增加一个偏移量即可实现。6.3 支持突发Burst传输的FIFO如果写入和读出通常是固定长度的数据包可以设计一个支持突发传输的FIFO接口。例如写侧提供一个burst_size输入当winc有效时一次性写入burst_size个数据。内部逻辑需要能处理指针的跳跃式前进。这可以显著提升总线利用率和传输效率。6.4 使用异步复位同步释放Asynchronous Reset Synchronous Release我们的设计中写复位wrst_n和读复位rrst_n是异步的。更严谨的做法是采用“异步复位同步释放”策略确保复位撤消时与各自的时钟边沿对齐避免复位撤消时刻不同步带来的潜在问题。这需要在本时钟域内对复位信号进行同步处理。7. 项目集成与FPGA实战要点当你把异步FIFO模块集成到一个更大的FPGA项目中时还有一些工程细节需要注意。7.1 资源与性能评估资源异步FIFO消耗的主要是触发器用于同步链、指针、状态寄存器和Block RAM用于存储。一个深度512、宽度32位的FIFO在典型FPGA上可能消耗1个Block RAM和几百个触发器。使用厂商的FIFO IP核通常是最优选择因为它们针对特定器件架构做了高度优化。性能关键时序路径通常在空满标志的产生逻辑上尤其是格雷码转二进制和之后的比较器。确保该路径满足时钟频率要求。如果频率很高如300MHz可能需要将比较逻辑流水线化Pipeline但这会进一步增加状态报告的延迟。7.2 与外部接口的配合AXI-Stream接口这是最常用的高速数据流接口。可以将异步FIFO的wdata/winc/wfull映射到AXI-S的TDATA/TVALID/TREADY。TREADY由~wfull驱动。读侧同理。这样你的异步FIFO就能无缝接入基于AXI-Stream的生态系统。自定义握手接口如果你使用简单的valid/ready握手确保遵循协议数据发送方在valid1且ready1时完成传输。FIFO的full信号应反相后作为对上游的ready信号。7.3 静态时序分析STA约束对于异步FIFO传统的STA工具如Vivado的Timing无法分析跨时钟域路径从wclk到rclk的路径。我们需要使用约束来告诉工具忽略这些路径或者使用特定的跨时钟域CDC分析工具。 在Xilinx Vivado中通常使用set_clock_groups -asynchronous来声明两个时钟域是异步的set_clock_groups -name async_clks -asynchronous \ -group [get_clocks wclk] \ -group [get_clocks rclk]但是同步器路径rptr_gray到rptr_gray_sync2w需要被约束。我们应该对同步器的第一级触发器设置set_false_path因为其输入是异步的建立/保持时间无法保证。而第二级及之后的触发器其时钟是同步的需要被正常约束。# 假设同步器模块实例名为 u_sync_r2w set_false_path -to [get_pins u_sync_r2w/rptr_gray_sync1_reg*/D]正确的CDC约束是保证设计在高速下稳定运行的最后一道保险务必仔细设置。从理解格雷码消除亚稳态的原理到亲手实现指针同步与空满判断再到处理上板后的各种疑难杂症异步FIFO的设计是一个将理论深刻应用于实践的完美范例。它没有炫酷的算法却处处体现着数字电路设计者对时序、时钟域和可靠性的极致考量。每一次调试都是对“同步”二字的更深一层理解。当你设计的FIFO在数百兆时钟频率下稳定运行无误地搬运海量数据时那种成就感是实实在在的。记住保守的设计往往更可靠在指针同步和状态产生上留有余地好过为了追求极致性能而埋下崩溃的种子。
返回列表