3. 握手信号基础:同步握手与异步握手、valid-ready握手协议、握手时序要求
握手信号,说白了就是两个模块之间互相打个招呼:「我准备好了,你呢?」。在PLL时钟接口的设计里,这步要是没做好,整个系统可能就乱套了。我这些年调试过的PLL问题,有一小半都出在握手逻辑上。
3.1 同步握手 vs 异步握手
先说说同步握手。两个模块在同一个时钟域里,用同一个时钟沿来采样握手信号。这种场景下,信号只要满足建立时间和保持时间,基本不会出问题。我个人习惯在同步握手里用两级寄存器打拍,防止亚稳态。
同步握手的典型场景:
- PLL输出时钟直接驱动下游模块
- 同频同相的时钟域之间
- 数据速率不高,时序裕量充足
异步握手就麻烦多了。两个模块的时钟可能不同频、不同相,甚至一个快一个慢。你想想看,发送端在clk_a下发出的信号,接收端用clk_b去采,万一采到跳变沿上,亚稳态就来了。
我在项目中遇到过最典型的问题:一个PLL输出的250MHz时钟,和另一个PLL输出的200MHz时钟,两个模块要交换数据。直接用同步握手的方式,结果仿真没问题,上板就随机出错。后来查了三天,发现是异步握手没做同步处理。
避坑指南:我曾经在异步握手设计里只用了单级同步器,结果亚稳态传播导致整个状态机跑飞。从那以后,我坚持用两级同步器,关键信号甚至用三级。
3.2 valid-ready握手协议
valid-ready协议是目前最主流的握手方式。它只有两根控制线:valid和ready。发送方拉高valid表示数据有效,接收方拉高ready表示可以接收。当valid和ready同时为高时,数据完成一次传输。
嗯,这里要注意:valid不能依赖ready。也就是说,发送方不能等到ready为高才拉valid,必须自己主动拉。ready则可以依赖valid,也可以不依赖。这是协议的核心规则。
// 典型的valid-ready握手逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
data_out <= '0;
valid_out <= 1'b0;
end else begin
if (valid_in && ready_out) begin
// 发送端准备好,接收端也准备好,传输完成
data_out <= data_in;
valid_out <= 1'b1;
end else if (ready_out) begin
// 接收端准备好,但发送端还没准备好
valid_out <= 1'b0;
end
end
end
assign ready_out = ~valid_out | ready_in; // 背压逻辑
个人经验:我建议在valid-ready握手里加上超时保护。曾经有个项目,接收端因为某个异常状态一直不拉ready,结果发送端的数据被堵死了,整个流水线卡住。加个计数器,如果ready长时间不拉高,就强制复位或者报错。
3.3 握手时序要求
握手时序说白了就是信号什么时候变、什么时候采。我总结了几条铁律:
| 时序要求 | 说明 | 常见问题 |
|---|---|---|
| valid先于数据稳定 | valid拉高前,数据必须已经稳定 | 数据还在跳变,valid就拉高了 |
| ready不能有毛刺 | ready信号必须干净,不能有glitch | 组合逻辑直接输出ready |
| 握手完成信号 | valid&ready同时为高后,下一拍必须变化 | valid一直拉高,导致重复传输 |
为什么会这样?因为PLL时钟接口对时序要求特别苛刻。PLL输出的时钟本身就有抖动,再加上握手信号的延迟,如果时序裕量不够,很容易出现setup或hold违例。
我记得有一次做PLL分频输出接口,分频后的时钟频率只有几十MHz,但PLL内部是GHz级别的。握手信号从快时钟域传到慢时钟域,如果不做展宽处理,慢时钟根本采不到。后来我用了脉冲同步器加展宽逻辑,才把问题解决。
核心要点:
- 同步握手:同频同相,两级打拍防亚稳态
- 异步握手:跨时钟域,必须做同步处理
- valid-ready:valid不能依赖ready,ready可以依赖valid
- 时序要求:valid先于数据,ready无毛刺,握手完成即变化
最后说一句,握手信号的设计没有银弹。每个项目都有自己的时钟架构和性能要求,最好的办法是先把基础协议吃透,再根据实际情况做调整。我这些年踩过的坑,大多是因为想当然地套用别人的方案,没有仔细分析自己的时钟域关系。
可可精品资料库