Systemverilog实现参数化的Round-Robin Arbiter Tree
本篇内容涉及的rtl代码为开源组织PLUP的common cell仓库中的源代码,本文只是对其进行些许解读。源码链接如下:[https://github.com/pulp-platform/common_cells/blob/dc555643226419b7a602f0aa39d449545ea4c1f2/src/rr_arb_tree.sv]
“想要快速提升编程能力的一个捷径是阅读优秀的源码” —— 忘记谁说的了
简介
- 首先Round Robin是考虑到公平性的一种仲裁算法。
- 基本思路:当一个req得到了grant许可之后,它的优先级在下一次仲裁时就会调整为最低
- 目的:每个req的优先级不固定,在被grant之后降至最低,保证所有req都能轮流被grant。
- 范例代码:PLUP开源仓库的common cell中的rr_arb_tree IP
功能
- 支持外部输入优先级
- 支持req锁存
- 内部优先级支持两种模式,一种是自动调整轮询顺序, 另一种是固定轮询顺序
代码分析
-
- 外部输入优先级:
if (ExtPrio) begin : gen_ext_rr
assign rr_q = rr_i;
assign req_d = req_i;
end else begin : gen_int_rr
...
end
-
如果使能了ExtPrio参数,则最高优先级序号rr_q将由输入的rr_i指定。
-
这里的rr_q是最高优先级的index, 例如输入的req序列有8bit, 我们希望初始时第3个req源具有最高优先级,则只需要把ExtPrio宏打开,并给rr_i输入3’d2即可。
-
选择外部优先级时不支持输入锁存和调整轮询顺序,也就是说此时该模块需要依赖外部来调整优先级,模块只会根据输入的优先级输出仲裁结果。
-
- req锁存:
if (LockIn) begin : gen_lock
logic lock_d, lock_q;
logic [NumIn-1:0] req_q;
assign lock_d = req_o & ~gnt_i;
assign req_d = (lock_q) ? req_q : req_i;
always_ff @(posedge clk_i or negedge rst_ni) begin : p_lock_reg
if (!rst_ni) begin
lock_q <= '0;
end else begin
if (flush_i) begin
lock_q <= '0;
end else begin
lock_q <= lock_d;
end
end
end
- req_i中存在1,也就是有待仲裁的request时,如果仲裁开始信号gnt_i还没拉高,那么会锁存此时的request序列,所以仲裁结果也不会改变。
- 直到仲裁开始信号gnt_i拉高后,才开始输出当前的request输入序列的仲裁结果
- 简单来说就是gnt_i信号为低, 但是有req请求时锁存上一次的仲裁结果。
3. 自动调整轮询顺序:
if (FairArb) begin : gen_fair_arb
logic [NumIn-1:0] upper_mask, lower_mask;
idx_t upper_idx, lower_idx, next_idx;
logic upper_empty, lower_empty;
for (genvar i = 0; i < NumIn; i++) begin : gen_mask
assign upper_mask[i] = (i > rr_q) ? req_d[i] : 1'b0;
assign lower_mask[i] = (i <= rr_q) ? req_d[i] : 1'b0;
end
lzc #(
.WIDTH ( NumIn ),
.MODE ( 1'b0 )
) i_lzc_upper (
.in_i ( upper_mask ),
.cnt_o ( upper_idx ),
.empty_o ( upper_empty )
);
lzc #(
.WIDTH ( NumIn ),
.MODE ( 1'b0 )
) i_lzc_lower (
.in_i ( lower_mask ),
.cnt_o ( lower_idx ),
.empty_o ( /*unused*/ )
);
assign next_idx = upper_empty ? lower_idx : upper_idx;
assign rr_d = (gnt_i && req_o) ? next_idx : rr_q;
- 通过FairArb参数使能自动调整轮询顺序。
- 如下图所示,假设此时rr=1, 也就是第2个req为最高优先级。构造两个mask,upper_mask过滤出第3-8个req, low_mask过滤出第1-2个req. 两组mask分别求第一个1所在的位置。如果upper mask中存在1,则下一个最高优先级就是upper mask中第一个1的index;如果upper mask中没有1, 则下一个最高优先级就是low mask组中的第一个1的index。
- 当仲裁开始信号gnt_i拉高并且req给出了结果后,本次的仲裁结束,就会将rr_d赋值为新的next_rr
- Round-Robin Arbiter Tree-autorr
- 仲裁器的树型实现:
localparam int unsigned NumLevels = unsigned'($clog2(NumIn));
idx_t [2**NumLevels-2:0] index_nodes; // used to propagate the indices
DataType [2**NumLevels-2:0] data_nodes; // used to propagate the data
logic [2**NumLevels-2:0] gnt_nodes; // used to propagate the grant to masters
logic[2**Nu mLevels-2:0] req_nodes; // used to propagate the requests to slave
// the final arbitration decision can be taken from the root of the tree
assign req_o = req_nodes[0];
assign data_o = data_nodes[0];
assign idx_o = index_nodes[0];
assign gnt_nodes[0] = gnt_i;
// arbiter tree
for (genvar level = 0; unsigned'(level) < NumLevels; level++) begin : gen_levels
for (genvar l = 0; l < 2**level; l++) begin : gen_level
// local select signal
logic sel;
// index calcs
localparam int unsigned Idx0 = 2**level-1+l;// current node
localparam int unsigned Idx1 = 2**(level+1)-1+l*2;
//////////////////////////////////////////////////////////////
// uppermost level where data is fed in from the inputs
if (unsigned'(level) == NumLevels-1) begin : gen_first_level
// if two successive indices are still in the vector...
if (unsigned'(l) * 2 < NumIn-1) begin : gen_reduce
assign req_nodes[Idx0] = req_d[l*2] | req_d[l*2+1];
// arbitration: round robin
assign sel = ~req_d[l*2] | req_d[l*2+1] & rr_q[NumLevels-1-level];
assign index_nodes[Idx0] = idx_t'(sel);
assign data_nodes[Idx0] = (sel) ? data_i[l*2+1] : data_i[l*2];
assign gnt_o[l*2] = gnt_nodes[Idx0] & (AxiVldRdy | req_d[l*2]) & ~sel;
assign gnt_o[l*2+1] = gnt_nodes[Idx0] & (AxiVldRdy | req_d[l*2+1]) & sel;
end
// if only the first index is still in the vector...
if (unsigned'(l) * 2 == NumIn-1) begin : gen_first
assign req_nodes[Idx0] = req_d[l*2];
assign index_nodes[Idx0] = '0;// always zero in this case
assign data_nodes[Idx0] = data_i[l*2];
assign gnt_o[l*2] = gnt_nodes[Idx0] & (AxiVldRdy | req_d[l*2]);
end
// if index is out of range, fill up with zeros (will get pruned)
if (unsigned'(l) * 2 > NumIn-1) begin : gen_out_of_range
assign req_nodes[Idx0] = 1'b0;
assign index_nodes[Idx0] = idx_t'('0);
assign data_nodes[Idx0] = DataType'('0);
end
//////////////////////////////////////////////////////////////
// general case for other levels within the tree
end else begin : gen_other_levels
assign req_nodes[Idx0] = req_nodes[Idx1] | req_nodes[Idx1+1];
// arbitration: round robin
assign sel = ~req_nodes[Idx1] | req_nodes[Idx1+1] & rr_q[NumLevels-1-level];
assign index_nodes[Idx0] = (sel) ?
idx_t'({1'b1, index_nodes[Idx1+1][NumLevels-unsigned'(level)-2:0]}) :
idx_t'({1'b0, index_nodes[Idx1][NumLevels-unsigned'(level)-2:0]});
assign data_nodes[Idx0] = (sel) ? data_nodes[Idx1+1] : data_nodes[Idx1];
assign gnt_nodes[Idx1] = gnt_nodes[Idx0] & ~sel;
assign gnt_nodes[Idx1+1] = gnt_nodes[Idx0] & sel;
end
end
end
- 初看这段代码有点吓人,两层for genvar外加这么多if生成语句。但是实际上可以暂时先忽略第2和第3个if。
- 这段代码主要实现了几个功能:
- 根据输入的req_i输出req_o. req_o其实就是req_i按位或,只是用二叉树关键路径更短。
- 根据rr_q, 也就是当前最高优先级的index生成sel选择信号。sel信号表示每次二叉时选左边还是右边。下图就是生成req_node和sel信号的一个例子,其中当前的rr_q = ‘b001.
- 根据sel信号生成gnt_o仲裁信号,gnt_o是onehot格式的。根据每一层的sel信号,可以逐步将gnt_i信号顺着二叉树放到gnt_o中对应的bit位置,下图是一个实际例子:
- 在生成gnt_o中,实际也顺便根据sel信号生成了最终仲裁结果的index. 如果需要req源还附带数据通道,需要给出仲裁后的data,则也可以顺便逐级送出被仲裁的data到data_o。
- 在for genvar循环的第一个if,也就是gen_first_level逻辑中,除了主要代码段gen_reduce之外,还有两个if,分别是gen_first和gen_out_of_range两个生成器。
- gen_first:由于二叉树的输入需要是2N的格式,才能正常往下(上)生长,所以当输入req数不是2N时,就需要进行特殊处理。例如req个数为7时,则树有三层。最顶层有7个节点,二分后第七个一定会落单,所以gen_first判断当前的节点是否是这种情况,也就是if (unsigned'(l) * 2 == NumIn-1),如果匹配的话,这个落单的节点将单独向下生长节点。
- gen_out_of_range: gen_first是处理落单的节点,而gen_out_of_range就是处理多余的节点。例如req个数为5,NumLevels = unsigned'($clog2(NumIn))=3, 树仍然有三层,此时第一层除了有第五个落单节点之外,在for循环遍历时还会遍历到7,8两个不存在的节点,因此需要虚构出这两个节点并将其赋为0.
知乎:flappylyc - 知乎 (zhihu.com)
博客园: love小酒窝 - 博客园 (cnblogs.com)
CSDN:love小酒窝的CSDN博客-IC领域博主
公众号已开,了解更多相关内容可以扫一扫下方二维码~