第2章 轮询仲裁(Round-Robin):原理与实现、公平性分析、在AHB/APB总线中的应用
大家好,欢迎来到第二章。上一章我们聊了总线仲裁的基本概念,今天咱们深入一个最常用的仲裁算法——轮询仲裁,也就是 Round-Robin。
为什么说它常用?因为它在“公平”和“效率”之间找到了一个不错的平衡点。我做了这么多年总线设计,可以说,80% 的多主设备场景,第一版原型我都会先用轮询。为什么?因为它简单、可靠,而且不容易饿死谁。
2.1 轮询仲裁的核心思想
说白了,轮询仲裁就像老师上课点名回答问题。老师手里有个名单,按顺序一个一个叫。叫完最后一个,再回到第一个重新开始。每个学生都有机会,谁也别想插队。
在总线里,这个“老师”就是仲裁器,“学生”就是各个主设备(Master)。仲裁器维护一个指针,叫“当前优先级指针”或者“轮询指针”。每次仲裁结束后,这个指针就移到下一个主设备。
你想想看,如果不用轮询,用固定优先级,那低优先级的设备可能永远抢不到总线。这在很多实时系统里是致命的。我有个项目,早期用了固定优先级,结果一个低速传感器死活拿不到总线,数据一直丢。换成轮询,问题立刻解决。
2.2 轮询仲裁的实现方式
实现轮询仲裁,主要有两种方式:硬件轮询和软件轮询。咱们做硬件架构的,当然主要关注硬件实现。
2.2.1 硬件轮询仲裁器结构
一个典型的硬件轮询仲裁器,核心是一个循环移位寄存器和一个优先级编码器。
我画个图,你一看就明白。
这个图里,流程很清楚:
- 请求输入:所有主设备把请求信号(req)发给仲裁器。
- 轮询指针:仲裁器内部有个指针,记录当前谁有最高优先级。
- 仲裁逻辑:从指针指向的设备开始,按顺序检查谁有请求。找到第一个有请求的,就授权给它。
- 指针更新:授权完成后,指针移到下一个设备。
2.2.2 代码实现示例
咱们用 Verilog 写一个简单的 4 主设备轮询仲裁器。代码不长,但核心逻辑都在里面。
module round_robin_arbiter (
input wire clk,
input wire rst_n,
input wire [3:0] req, // 4个主设备的请求
output reg [3:0] grant, // 授权输出,独热码
output reg grant_valid // 授权有效信号
);
reg [1:0] pointer; // 轮询指针,2位,指向当前最高优先级
// 仲裁逻辑:从指针位置开始,循环查找第一个请求
always @(*) begin
grant = 4'b0;
grant_valid = 1'b0;
case (pointer)
2'b00: begin
if (req[0]) begin grant = 4'b0001; grant_valid = 1'b1; end
else if (req[1]) begin grant = 4'b0010; grant_valid = 1'b1; end
else if (req[2]) begin grant = 4'b0100; grant_valid = 1'b1; end
else if (req[3]) begin grant = 4'b1000; grant_valid = 1'b1; end
end
2'b01: begin
if (req[1]) begin grant = 4'b0010; grant_valid = 1'b1; end
else if (req[2]) begin grant = 4'b0100; grant_valid = 1'b1; end
else if (req[3]) begin grant = 4'b1000; grant_valid = 1'b1; end
else if (req[0]) begin grant = 4'b0001; grant_valid = 1'b1; end
end
2'b10: begin
if (req[2]) begin grant = 4'b0100; grant_valid = 1'b1; end
else if (req[3]) begin grant = 4'b1000; grant_valid = 1'b1; end
else if (req[0]) begin grant = 4'b0001; grant_valid = 1'b1; end
else if (req[1]) begin grant = 4'b0010; grant_valid = 1'b1; end
end
2'b11: begin
if (req[3]) begin grant = 4'b1000; grant_valid = 1'b1; end
else if (req[0]) begin grant = 4'b0001; grant_valid = 1'b1; end
else if (req[1]) begin grant = 4'b0010; grant_valid = 1'b1; end
else if (req[2]) begin grant = 4'b0100; grant_valid = 1'b1; end
end
endcase
end
// 指针更新:每次仲裁有效后,指针指向下一个
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
pointer <= 2'b00;
end else if (grant_valid) begin
pointer <= pointer + 1'b1; // 循环加1
end
end
endmodule
这段代码里,我用了 case 语句实现循环优先级查找。你可能会问:为什么不用 for 循环?嗯,在硬件里,for 循环综合出来可能不是你想要的样子。这种显式的 case 写法,综合工具能更好地优化。
- 请求信号同步(跨时钟域处理)
- 总线占用超时处理(防止某个设备死锁)
- 低功耗优化(时钟门控)
2.3 公平性分析
轮询仲裁最大的优点就是公平。但公平也分很多种,咱们得仔细聊聊。
2.3.1 什么是真正的公平?
在总线仲裁里,公平通常指:每个主设备在长期运行中,获得总线访问权的概率相等。
轮询仲裁能做到这一点吗?理论上可以。但实际中,有几个因素会影响公平性:
| 影响因素 | 影响方式 | 严重程度 |
|---|---|---|
| 请求频率差异 | 高频请求的设备看起来“抢”到的次数多 | 低(轮询本身不偏向) |
| 传输长度差异 | 一次传输占用的时钟周期不同 | 中(需要加权轮询) |
| 指针更新时机 | 在传输开始还是结束时更新指针 | 高(设计细节决定) |
| 优先级重置 | 是否允许高优先级打断 | 高(破坏公平性) |
你看,公平性不是非黑即白的。我见过一个设计,指针在传输开始时就更新,结果一个设备刚拿到总线,下一个设备立刻变成最高优先级。如果这个设备一直有请求,它就能连续拿到总线。这其实破坏了公平性。
2.3.2 加权轮询(Weighted Round-Robin)
有时候,我们需要“不公平”。比如,一个视频处理单元需要更高的带宽,而一个配置寄存器访问频率很低。这时候,可以用加权轮询。
加权轮询的原理很简单:给每个主设备分配一个权重值,权重高的设备在一次轮询周期里可以多次获得总线。
举个例子:
- Master A:权重 3(每次轮询可以拿 3 次总线)
- Master B:权重 2
- Master C:权重 1
轮询顺序就是:A, A, A, B, B, C, 然后重复。这样 A 的带宽是 C 的 3 倍。
我在一个视频编解码芯片里用过加权轮询。编码器需要大量带宽,解码器次之,CPU 只需要偶尔配置一下。权重配成 4:2:1,效果很好。
2.4 在AHB/APB总线中的应用
好,咱们回到实际的总线协议。AHB 和 APB 是 ARM 公司 AMBA 总线体系里的两个重要成员。
2.4.1 AHB 总线中的轮询仲裁
AHB 总线支持多个主设备,仲裁器是必须的。AHB 协议本身没有规定用哪种仲裁算法,所以轮询仲裁是常见选择。
在 AHB 里,仲裁发生在以下几个时刻:
- 地址相位开始:仲裁器根据当前请求,决定下一个时钟周期谁可以驱动地址和控制信号。
- 传输结束:当 HREADY 为高时,表示当前传输完成,仲裁器可以切换主设备。
- 分裂传输(Split):如果从设备发出 SPLIT 响应,仲裁器可以临时把总线让给其他主设备。
我画个 AHB 轮询仲裁的时序图,帮你理解。
这个时序图展示了两个主设备轮流获得总线的情况。注意看,Master 0 在 T0 发起请求,T1 获得授权,T2 开始传输地址。Master 1 在 T2 发起请求,T3 获得授权,T4 开始传输。这就是轮询的典型表现。
2.4.2 APB 总线中的特殊情况
APB 总线比较特殊。它通常只有一个主设备(一般是 AHB 到 APB 的桥),所以不需要仲裁。但如果你在 APB 上挂了多个主设备(比如某些 SoC 设计),轮询仲裁也是可行的。
不过,APB 的时序比 AHB 简单,没有流水线。所以 APB 上的轮询仲裁实现起来更直接:
- 每个 APB 传输需要两个时钟周期(SETUP 和 ACCESS)
- 仲裁可以在 SETUP 相位进行
- 传输完成后立即切换
2.5 轮询仲裁的优缺点总结
咱们来做个总结,把轮询仲裁的优缺点摆出来。
| 优点 | 缺点 |
|---|---|
| 公平性好,不会饿死任何设备 | 延迟不确定,高优先级请求不能立即响应 |
| 实现简单,硬件开销小 | 带宽利用率不是最优(需要等待轮询) |
| 可预测性强,适合实时系统 | 不支持紧急请求(除非加优先级打断) |
| 容易扩展,增加主设备只需修改指针宽度 | 所有设备获得相同机会,无法差异化服务 |
你看,没有完美的仲裁算法。轮询仲裁适合那些对公平性要求高、对延迟不太敏感的场景。如果你的系统里有一个设备需要紧急响应,那就得考虑优先级仲裁或者混合仲裁了。
- 所有设备重要性相同 → 轮询仲裁
- 有明确优先级需求 → 固定优先级仲裁
- 需要兼顾公平和紧急 → 加权轮询 + 优先级打断
好了,这一章的内容就到这里。轮询仲裁虽然基础,但用好它并不容易。我建议你在做设计时,先画清楚时序图,再写代码。很多时候,时序图画清楚了,代码就是翻译工作。