从零构建公平仲裁器:Verilog实现Round Robin轮询算法实战
在数字系统设计中,当多个主设备(如处理器、DMA控制器、外设等)需要共享同一总线或资源时,仲裁器的作用就变得至关重要。传统固定优先级仲裁方案虽然实现简单,但容易导致低优先级设备长期无法获得访问权限,这种现象被称为"饿死"(starvation)。本文将带您深入理解Round Robin仲裁机制,并通过两种典型的Verilog实现方案,手把手教您构建一个参数化、可复用的公平仲裁模块。
1. Round Robin仲裁机制核心原理
Round Robin(轮询)算法通过动态调整优先级来保证每个请求方都能公平地获得资源访问权。其核心思想是:当一个请求方获得授权后,它的优先级会在下一轮仲裁中降至最低,其他请求方则按既定顺序提升优先级。这种机制确保了所有活跃的请求方都能轮流获得服务机会。
1.1 基础概念解析
请求向量(Request Vector):一组二进制信号,每位代表一个主设备的请求状态。例如,4-bit请求向量
req[3:0]中,req[2]=1表示设备2正在请求总线访问权。授权向量(Grant Vector):仲裁器输出的one-hot编码信号,指示当前哪个设备获得访问权。如
grant=4'b0100表示设备2获得授权。优先级轮转:每次授权后,被授权设备的优先级降至最低,其余设备优先级相应调整。例如,初始优先级为0>1>2>3,若设备1获得授权,则新优先级变为2>3>0>1。
1.2 工作流程示例
考虑四个设备(A/B/C/D)的仲裁场景,初始优先级为A>B>C>D:
| 周期 | 请求 | 原始优先级 | 授权 | 新优先级 |
|---|---|---|---|---|
| 1 | A,C | A>B>C>D | A | B>C>D>A |
| 2 | A,C | B>C>D>A | C | D>A>B>C |
| 3 | A,B | D>A>B>C | A | B>C>D>A |
| 4 | B,D | B>C>D>A | B | C>D>A>B |
注意:仅当设备实际发出请求时才会被纳入优先级排序,未请求的设备不会占用授权机会。
2. 优先级动态调整方案实现
第一种实现方案通过实时调整优先级顺序来实现Round Robin机制,其核心是维护一个表示当前最高优先级设备的寄存器。
2.1 基础优先级仲裁模块
首先实现一个可配置优先级的固定仲裁器模块:
module priority_arbiter #( parameter WIDTH = 4 )( input [WIDTH-1:0] req, input [WIDTH-1:0] base_priority, // One-hot指示当前最高优先级位 output [WIDTH-1:0] grant ); // 将请求向量复制两份以处理优先级环绕 wire [2*WIDTH-1:0] double_req = {req, req}; // 计算授权向量:找到base_priority及以上第一个置位的请求 wire [2*WIDTH-1:0] double_gnt = double_req & ~(double_req - base_priority); // 合并两个区间的授权结果 assign grant = double_gnt[WIDTH-1:0] | double_gnt[2*WIDTH-1:WIDTH]; endmodule这个模块的关键在于base_priority输入,它指定了当前优先级最高的设备。例如,当base_priority=4'b0100时,优先级顺序为2>3>0>1。
2.2 动态优先级控制逻辑
接下来实现优先级调整的状态机:
module round_robin_arbiter #( parameter WIDTH = 4 )( input clk, input rst_n, input [WIDTH-1:0] req, output [WIDTH-1:0] grant ); reg [WIDTH-1:0] priority_reg; // 实例化基础仲裁器 priority_arbiter #(.WIDTH(WIDTH)) arb ( .req(req), .base_priority(priority_reg), .grant(grant) ); // 优先级更新逻辑 always @(posedge clk or negedge rst_n) begin if (!rst_n) begin priority_reg <= {{(WIDTH-1){1'b0}}, 1'b1}; // 默认最低位优先级最高 end else if (|req) begin // 有请求时才更新 priority_reg <= {grant[WIDTH-2:0], grant[WIDTH-1]}; // 左移实现优先级轮转 end end endmodule关键点说明:
- 复位时设置设备0为最高优先级
- 每次授权后,将授权向量左移一位作为新的优先级基准
- 仅当存在有效请求时才更新优先级寄存器
2.3 方案特点分析
优势:
- 结构简单,代码直观
- 优先级调整逻辑清晰可见
局限性:
- 基础仲裁器使用了较大的减法运算,在请求位数较多时(如64位)可能导致时序紧张
- 需要维护全宽度的优先级寄存器
3. 请求屏蔽方案实现
第二种实现方案采用固定优先级但动态屏蔽已服务请求的策略,通过两个并行仲裁器实现高效轮询。
3.1 核心设计思想
- 主仲裁器:处理未被屏蔽的请求(即尚未获得服务的设备)
- 辅助仲裁器:当所有请求都被屏蔽时(即完成一轮轮询),处理原始请求
- 屏蔽逻辑:记录已被服务的设备,防止重复授权
3.2 完整实现代码
module rr_arbiter #( parameter N = 4 )( input logic clk, input logic rst_n, input logic [N-1:0] req, output logic [N-1:0] grant ); logic [N-1:0] mask; logic [N-1:0] masked_req; logic [N-1:0] higher_pri_mask; logic [N-1:0] grant_masked; logic [N-1:0] grant_unmasked; logic no_masked_reqs; // 生成屏蔽后的请求 assign masked_req = req & mask; // 主仲裁器(处理屏蔽后的请求) assign higher_pri_mask[N-1:1] = higher_pri_mask[N-2:0] | masked_req[N-2:0]; assign higher_pri_mask[0] = 1'b0; assign grant_masked = masked_req & ~higher_pri_mask; // 辅助仲裁器(处理原始请求) logic [N-1:0] higher_pri_unmask; assign higher_pri_unmask[N-1:1] = higher_pri_unmask[N-2:0] | req[N-2:0]; assign higher_pri_unmask[0] = 1'b0; assign grant_unmasked = req & ~higher_pri_unmask; // 仲裁器选择逻辑 assign no_masked_reqs = ~(|masked_req); assign grant = no_masked_reqs ? grant_unmasked : grant_masked; // 屏蔽寄存器更新 always_ff @(posedge clk or negedge rst_n) begin if (!rst_n) begin mask <= {N{1'b1}}; // 初始不屏蔽任何设备 end else begin if (|masked_req) begin // 更新屏蔽:屏蔽已授权设备及其更高优先级设备 mask <= higher_pri_mask; end else if (|req) begin // 开始新一轮轮询 mask <= higher_pri_unmask; end end end endmodule3.3 关键信号说明
| 信号名称 | 描述 |
|---|---|
mask | 屏蔽寄存器,1表示对应设备可参与仲裁 |
masked_req | 被屏蔽后的请求向量 |
higher_pri_mask | 主仲裁器生成的优先级掩码 |
grant_masked | 基于屏蔽后请求的授权结果 |
grant_unmasked | 基于原始请求的授权结果(当所有请求被屏蔽时使用) |
no_masked_reqs | 标志位,指示是否所有活跃请求都已被屏蔽 |
3.4 性能对比
两种实现方案在资源消耗和时序表现上的对比:
| 指标 | 优先级调整方案 | 请求屏蔽方案 |
|---|---|---|
| 寄存器数量 | N bits | N bits |
| 组合逻辑复杂度 | O(2N)减法 | O(N)优先级编码 |
| 关键路径延迟 | 减法器+多路选择 | 两级优先级编码 |
| 适用位宽 | 中小规模(≤16) | 大规模(≥32) |
实际选择时需权衡设计复杂度与性能需求,对于FPGA实现通常16位以下两种方案差异不大
4. 测试平台构建与验证
完整的仲裁器设计必须包含充分的验证。下面给出SystemVerilog测试平台示例:
4.1 测试平台框架
module rr_arbiter_tb #(parameter N = 4); logic clk = 0; logic rst_n; logic [N-1:0] req; logic [N-1:0] grant; // 时钟生成 always #5 clk = ~clk; // 实例化被测设计 rr_arbiter #(.N(N)) dut (.*); // 测试用例 initial begin // 初始化 rst_n = 0; req = '0; #20 rst_n = 1; // 测试用例1:单设备持续请求 $display("=== Test 1: Single requester ==="); req = 4'b0001; repeat(8) @(posedge clk); // 测试用例2:多设备交替请求 $display("=== Test 2: Multiple requesters ==="); req = 4'b0101; repeat(16) @(posedge clk); // 测试用例3:随机请求 $display("=== Test 3: Random requests ==="); repeat(32) begin req = $urandom_range(0, 2**N-1); @(posedge clk); end $finish; end // 监控与断言 always @(posedge clk) begin $display("[%0t] req=%b grant=%b", $time, req, grant); // 授权应为one-hot或全零 assert ($onehot0(grant)) else $error("Invalid grant pattern"); // 授权设备必须正在请求 if (|grant) begin assert (req & grant) else $error("Granted device not requesting"); end end endmodule4.2 典型测试场景
公平性验证:
- 让所有设备持续发出请求
- 统计各设备获得授权的次数
- 预期结果:各设备授权次数应基本相等
饿死现象检测:
- 设置高优先级设备持续请求
- 低优先级设备间歇性请求
- 验证低优先级设备最终能获得授权
边界条件测试:
- 无请求时授权应为全零
- 单设备请求时应立即获得授权
- 全设备请求时应轮转授权
4.3 波形分析要点
使用仿真工具(如ModelSim、VCS等)查看波形时,应重点关注:
- 授权信号与请求信号的时序关系
- 优先级/屏蔽寄存器的更新时机
- 从复位开始的初始状态
- 请求突变的处理情况
5. 实际应用优化技巧
在真实项目中使用Round Robin仲裁器时,还需要考虑以下优化点:
5.1 参数化设计增强
module flexible_rr_arbiter #( parameter WIDTH = 8, parameter TYPE = "MASK" // "PRIORITY" or "MASK" )( input clk, input rst_n, input [WIDTH-1:0] req, output [WIDTH-1:0] grant ); generate if (TYPE == "PRIORITY") begin // 实例化优先级调整方案 priority_adjust_impl #(.WIDTH(WIDTH)) impl (.*); end else begin // 实例化请求屏蔽方案 mask_based_impl #(.WIDTH(WIDTH)) impl (.*); end endgenerate endmodule5.2 权重扩展实现
基础Round Robin算法中每个设备的权重相同。可通过以下修改实现加权轮询:
- 为每个设备添加权重计数器
- 每次授权后减少对应设备的权重计数
- 当计数器归零时暂时屏蔽该设备
- 所有计数器归零后重新加载初始权重
5.3 跨时钟域处理
当请求来自不同时钟域时,需要添加同步逻辑:
// 请求同步化处理 sync_cell #(.STAGES(2)) sync [WIDTH-1:0] ( .clk(clk), .din(async_req), .dout(sync_req) );5.4 性能监控接口
添加统计接口用于调试和性能分析:
logic [WIDTH-1:0][31:0] grant_count; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin grant_count <= '0; end else begin for (int i = 0; i < WIDTH; i++) begin if (grant[i]) grant_count[i] <= grant_count[i] + 1; end end end在完成基本功能验证后,建议在实际系统环境中进行集成测试,观察仲裁器在真实流量模式下的表现。特别是在高负载情况下,公平性算法对系统整体性能的影响会变得更加明显。