APB 等待态:PREADY 拉低如何在 Access 相位插入多拍等待与 PSLVERR 时序
更新时间:2026-09-25。本文是「集成电路大类 · 芯片设计」接口主题簇里的一篇长尾文。APB 总线时序与从机设计 建立了 Setup→Access 两拍访问的模型,但写真实外设的 RTL 时一定会遇到:I2C/SPI 控制器这类慢外设两拍根本完不成一次访问。答案就是 APB 等待态——从机拉低
PREADY,把 Access 相位拉长到任意拍数。
本文要回答的问题
PREADY拉低后总线停在哪个相位,PSEL/PENABLE各自保持什么值?- 等待期间地址、写数据、
PWRITE为什么必须纹丝不动,提前变化会怎样? - I2C/SPI 控制器内部在什么情况下需要多拍,怎样把内部状态翻译成
PREADY? PSLVERR在带等待的访问里哪一拍有效,能不能在等待中途报错?- 验证等待态该定向覆盖哪些场景,记分板按什么条件判定一笔访问完成?
一、先回到两相位:等待态发生在哪里
APB 一次访问固定经过两个相位:Setup 相位 PSEL=1、PENABLE=0,地址与控制信号在此拍上线并稳定;Access 相位 PSEL=1、PENABLE=1,从机在该拍配合 PREADY 完成传输。关键在于:等待态不是第三个相位,而是 Access 相位的重复。从机在 Access 相位采样到 PENABLE=1 但 PREADY=0 时,主机(桥)不会让状态机前进,下一拍仍然驱动完全相同的 Access 相位——PSEL 保持高、PENABLE 保持高、PADDR/PWRITE/PWDATA/PSTRB 全部保持。
因此 APB 的完成条件可以写成一个明确的布尔式:
传输完成 = PSEL & PENABLE & PREADY 的 PCLK 上升沿从机内部所有真正改变状态的动作(写寄存器、锁存读数据)都只能挂在这个沿上。一个无等待访问在 Access 的第一拍就满足它;需要 N 拍等待的访问则让 PREADY 连续 N-1 拍为 0、第 N 拍为 1。APB 不规定等待拍数上限,理论上从机可以无限期拖住总线,所以「永久不就绪」等价于总线死锁,这也是后面验证必须覆盖超时场景的原因。
二、等待态时序:PREADY 拉低的三个拍
下面是一笔插入 2 拍等待的读访问:T1 为 Setup,T2 进入 Access 但 PREADY=0,T3 继续等待,T4 PREADY=1 完成,T5 总线回到空闲。

读图时抓住三条线的相位关系:PSEL 从 Setup 起一直高到完成拍之后;PENABLE 只在 Access 相位(含所有等待拍)为高;PREADY 是唯一允许在 Access 期间跳变的从机输出。读访问时 PRDATA 必须从第一个 Access 拍起就驱动并在整个等待期间保持稳定,主机只在完成沿采样一次——不能因为 PREADY 还低就提前撤数据或换值。写访问同理,PWDATA 在等待期间保持,从机若提前在 Setup 或第一个 Access 沿就写入,会在后续每个等待沿重复写同一笔数据,带副作用的寄存器(如写 1 清零、自启动位)就会被误伤。
三、低速外设为什么需要等待:I2C/SPI 控制器的内部状态
配置寄存器本身一拍就能写完,等待态几乎总是来自寄存器读写背后的「慢路径」。I2C/SPI 控制器里典型有三种:
| 慢路径来源 | 具体场景 | 典型拍数 |
|---|---|---|
| 跨时钟域 | APB 总线在高频 PCLK,波特率发生器/移位器在低频 SCL/SCK 域,状态要经 CDC 同步回读 | 2–4 拍同步 |
| 内部忙状态 | 传输进行中读 FIFO 数据、访问只有移位间隙才能更新的影子寄存器 | 数个到数十个 PCLK |
| 多拍内部操作 | 访问触发内部状态机动作(如清空 TXFIFO、重载分频器),需要握手完成 | 由内部计数器决定 |
设计上不要让 PREADY 组合地跟着内部状态毛刺跳变,规范做法是把「本拍是否完成」收敛成一个从机内部的就绪条件。下面的状态流描述了 I2C 控制器读 STATUS 类寄存器的决策过程:进入 Access 后先判断访问目标,普通配置寄存器立即就绪;需要跨时钟取状态的寄存器启动同步/等待计数器,计数期间持续拉低 PREADY;计数结束(或发现非法状态)后拉高 PREADY,必要时同时给出 PSLVERR。
四、RTL 写法:用计数器而不是门控时钟
常见的可综合写法是在 Access 相位启动一个小等待计数器,PREADY 由计数完成产生;未选中或 Setup 相位静态输出 1,避免把桥拖死。示意如下:
logic [2:0] wait_cnt;
logic wait_in_progress;
// 只在 Access 起始沿启动,等待期间 PENABLE 保持高
always_ff @(posedge PCLK or negedge PRESETn) begin
if (!PRESETn) begin
wait_in_progress <= 1'b0;
wait_cnt <= '0;
end else if (psel && penable && !pready) begin
wait_in_progress <= 1'b1;
wait_cnt <= wait_cnt + 3'd1;
end else if (psel && penable && pready) begin
wait_in_progress <= 1'b0;
wait_cnt <= '0;
end
end
assign pready = !psel ? 1'b1 :
!penable ? 1'b1 :
slow_path ? (wait_cnt >= WAIT_N) : 1'b1;三个要点:第一,绝不能把 PREADY 当时钟或门控时钟用,寄存器照常由 PCLK 触发,PREADY 只参与使能;第二,动作条件写成 psel && penable && pready,多拍等待只会完成一次;第三,读数据路径用寄存输出或稳定的组合输出,保证等待期间 PRDATA 不变。桥侧对接时还有一层细节:PREADY/PSLVERR 通常经多路选择回到桥,未选中从机的输出不应参与,桥内一般要求静态高的默认值。
五、PSLVERR:与完成拍严格对齐
PSLVERR(APB3 起)只在完成拍——即 PSEL=1、PENABLE=1、PREADY=1 的那一拍——有效,和 PRDATA/PWDATA 同相位。等待拍上即使错误条件已经成立,也不能提前把 PSLVERR 拉高,因为主机只在完成沿采样它;提前有效要么被忽略,要么被某些桥实现误判。典型报错来源是访问保留地址、对只读寄存器写、或慢路径最终返回失败(如内部状态机进入异常态)。
不支持错误响应的从机把 PSLVERR 静态绑 0。还要注意错误是「一拍事件」:完成拍一过,PSLVERR 必须回到 0,下一笔访问重新决定。带等待的错误访问时序就是「若干拍 PREADY=0、PSLVERR=0 → 最后一拍 PREADY=1、PSLVERR=1」,这与 AHB 错误响应需要两拍、AXI 靠 RRESP/BRESP 通道的模型都不同,跨总线桥接时错误如何映射要逐桥确认。
六、验证要点与常见坑
定向用例至少覆盖:零等待基准、固定多拍等待、等待拍数随机化(约束 PREADY 每拍独立 Bernoulli)、等待后紧接下一笔访问的背靠背场景、等待中 PSLVERR=1、以及长时间不就绪后的超时恢复。检查项包括:等待期间 PADDR/PWDATA/PSTRB/PWRITE 稳定性、PRDATA 稳定性、寄存器只被写一次、PSLVERR 仅在完成拍有效。
随机化策略上有一个容易忽略的点:PREADY 虽然只在 Access 相位被采样,但监视器仍应检查它在 Setup 相位和总线空闲时为高,防止从机把内部未就绪状态直接透传到总线、无谓拖慢每一笔访问。背靠背场景还要确认完成拍的下一拍就能进入新的 Setup,桥没有被从机一侧的延迟更新多卡一拍。
记分板不要按周期数判定完成,而应以 psel && penable && pready 沿作为唯一弹出条件,入队内容是采样自 Setup 相位的地址与控制;期望队列的顺序必须严格等于 Setup 出现的顺序,因为 APB 不支持乱序,前一笔未完成时后一笔根本不会开始。常见 bug 的「表现 → 处理」:写 1 清零位在等待态被重复清零 → 把动作条件加上 pready;仿真开局桥一直停在 Access → 查未选中从机的 PREADY 是否默认高;偶发读到旧数据 → 查 PRDATA 是否在等待中途变化;PSLVERR 随机失效 → 核对它是否与完成拍对齐。总线功能模型可参考 APB 总线时序与从机设计 中的信号集,寄存器级写法对照 AXI4-Lite 从机。
这一篇在知识体系里的位置
APB 两拍协议全貌见 APB 总线时序与从机设计;APB 如何挂到高速骨干见 AHB/APB 与桥接 与 APB/AHB 桥接设计;寄存器表与地址解码见 寄存器表与地址解码;对比另一类低速配置口见 AXI4-Lite 从机;总线选型见 AMBA 总线选型。
总结
APB 等待态不是独立相位,而是从机在 Access 相位拉低 PREADY、让 PSEL/PENABLE 与地址控制保持不变地重复若干拍,直到 PREADY=1 的上升沿才完成唯一一次动作;I2C/SPI 等低速外设按 CDC 同步、内部忙状态和等待计数器驱动 PREADY,PSLVERR 必须与完成拍严格对齐且只有效一拍;验证以完成沿作为唯一判据,定向覆盖随机等待、背靠背访问、报错与超时恢复。