在RTL编码中,经常遇到多个队列(或者多通道/FIFO)轮询(round-robin)调度。
若队列(或者通道/FIFO)数较少,可以通过case语句实现轮询调度。
方法1:使用case语句实现round-robin调度器。
module rr_sch (
input clk ,
input rst_n ,
input sch_en , // 调度使能,单拍有效
input [4-1:0] que_st , // 队列的状态
output reg [2-1:0] que_id // 当前选中的队列
);
always @ (posedge clk or negedge rst_n) begin
if (rst_n == 1'b0) begin
que_id <= 3'd0;
end
else if ((sch_en == 1'b1) && (|que_st == 1'b1)) begin
case (que_id)
2'd0: que_id <= (que_st[1]) ? 2'd1 :
(que_st[2]) ? 2'd2 :
(que_st[3]) ? 2'd3 : 2'd0;
2'd1: que_id <= (que_st[2]) ? 2'd2 :
(que_st[3]) ? 2'd3 :
(que_st[0]) ? 2'd0 : 2'd1;
2'd2: que_id <= (que_st[3]) ? 2'd3 :
(que_st[0]) ? 2'd0 :
(que_st[1]) ? 2'd1 : 2'd2;
default: que_id <= (que_st[0]) ? 2'd0 :
(que_st[1]) ? 2'd1 :
(que_st[2]) ? 2'd2 : 2'd3;
endcase
end
else;
end
endmodule
该调度器根据输入的队列状态和上一次队列索引,选择下一次调度的队列索引。
缺点:不易扩展,当队列数量增加时,case语句会变得很长。
方法2:使用for循环实现round-robin调度器。
module rr_sch #(
parameter NUM = 4 ,
parameter WIDTH = $clog2(NUM)
)(
input clk ,
input rst_n ,
input sch_en ,
input [NUM-1:0] que_st ,
output reg [WIDTH-1:0] que_id
);
reg [WIDTH-1:0] rr_id;
always @ (posedge clk or negedge rst_n) begin : RR_SCH
integer i;
if (rst_n == 1'b0) begin
que_id <= {WIDTH{1'b1}};
end
else if ((sch_en == 1'b1) && (|que_st == 1'b1)) begin
for (i = NUM; i >= 1; i = i - 1) begin // 遍历所有队列的状态
rr_id = que_id + i;
if (que_st[rr_id] == 1'b1) begin
que_id <= rr_id[WIDTH-1:0];
end
end
end
else;
end
endmodule
该调度器根据输入的队列状态和上一次队列索引,从索引+1的位置开始遍历队列,直到当前索引。等同于case语句实现的方法。
举例说明,假如上一次队列索引是4,队列状态que_st = 8'b1111_1111,循环过程如下图。
循环遍历顺序,由4->3->2->1->0->7->6->5结束,只要对应状态位是1,始终由后面索引覆盖。也就是最后遍历的在本次优先级最高,或者说上一次队列索引+1在本次优先级最高,完全等同于case语句实现的方法。
缺点:NUM大的情况下,时序可能不满足。
优点:相比较case语句实现的方法,可参数化扩展。
方法3:使用for循环实现round-robin调度器。
module rr_sch #(
parameter NUM = 4 ,
parameter WIDTH = $clog2(NUM)
)(
input clk ,
input rst_n ,
input sch_en , // 调度使能,单拍有效
input [NUM-1:0] que_st , // 队列的状态
output reg [WIDTH-1:0] que_id // 当前选中的队列
);
reg que_lsel_vld;
reg [WIDTH-1:0] lsel_que;
reg [WIDTH-1:0] hsel_que;
wire [WIDTH-1:0] que_sel;
always @ ( * ) begin : RR_SCH
integer i;
que_lsel_vld = 1'b0;
lsel_que = {WIDTH{1'b0}};
hsel_que = {WIDTH{1'b0}};
if ((sch_en == 1'b1) && (|que_st == 1'b1)) begin
for (i = 0; i < NUM; i = i + 1) begin
if (que_st[i] == 1'b1) begin
if (i < que_id) begin
que_lsel_vld = 1'b1; // 前半组选择有效标记
lsel_que = i[WIDTH-1:0]; // 前半组选择端口
end
else begin
hsel_que = i[WIDTH-1:0]; // 后半组选择有效标记
end
end
end
end
end
assign que_sel = (que_lsel_vld == 1'b1) ? lsel_que : hsel_que;
always @ (posedge clk or negedge rst_n) begin
if (rst_n == 1'b0) begin
que_id <= {WIDTH{1'b0}};
end
else if ((sch_en == 1'b1) && (|que_st == 1'b1)) begin
que_id <= que_sel;
end
else;
end
endmodule
前半组组内SP调度,高位优先级高;后半组组内SP调度,高位优先级高;组间根据lsel_vld信号选择队列索引。注意:前后组由索引决定,因此前后组会跟随索引而发生变化。
举例说明,如下图。
步骤1:假如上一次队列索引是3,队列状态时8‘b1111_0111,由于前半组状态有效,前半部分选择索引2,后半部分选择索引7,因此这次轮询选择索引2;
步骤2:假如上一次队列索引是2,队列状态时8‘b1111_0011,由于前半组状态有效,前半部分选择索引1,后半部分选择索引7,因此这次轮询选择索引1;
步骤3:假如上一次队列索引是1,队列状态时8‘b1111_0001,由于前半组状态有效,前半部分选择索引0,后半部分选择索引7,因此这次轮询选择索引0;
步骤4:假如上一次队列索引是1,队列状态时8‘b1111_0000,由于前半组状态无效,前半部分选择索引6,后半部分选择索引0,因此这次轮询选择索引6;
步骤5:假如上一次队列索引是1,队列状态时8‘b0111_0000,由于前半组状态有效,前半部分选择索引6,后半部分选择索引0,因此这次轮询选择索引6;
步骤5:假如上一次队列索引是1,队列状态时8‘b0011_0000,由于前半组状态有效,前半部分选择索引5,后半部分选择索引0,因此这次轮询选择索引5;
后面步骤省略......
若有不正确的地方,欢迎大家指正。