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

功能

  1. 支持外部输入优先级
  2. 支持req锁存
  3. 内部优先级支持两种模式,一种是自动调整轮询顺序, 另一种是固定轮询顺序

代码分析

    1. 外部输入优先级
    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即可。

  • 选择外部优先级时不支持输入锁存和调整轮询顺序,也就是说此时该模块需要依赖外部来调整优先级,模块只会根据输入的优先级输出仲裁结果。

    1. 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
  1. 仲裁器的树型实现
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。
  • 这段代码主要实现了几个功能:
  1. 根据输入的req_i输出req_o. req_o其实就是req_i按位或,只是用二叉树关键路径更短。
  2. 根据rr_q, 也就是当前最高优先级的index生成sel选择信号。sel信号表示每次二叉时选左边还是右边。下图就是生成req_node和sel信号的一个例子,其中当前的rr_q = ‘b001.
  1. 根据sel信号生成gnt_o仲裁信号,gnt_o是onehot格式的。根据每一层的sel信号,可以逐步将gnt_i信号顺着二叉树放到gnt_o中对应的bit位置,下图是一个实际例子:
  1. 在生成gnt_o中,实际也顺便根据sel信号生成了最终仲裁结果的index. 如果需要req源还附带数据通道,需要给出仲裁后的data,则也可以顺便逐级送出被仲裁的data到data_o。
  2. 在for genvar循环的第一个if,也就是gen_first_level逻辑中,除了主要代码段gen_reduce之外,还有两个if,分别是gen_first和gen_out_of_range两个生成器。
  3. gen_first:由于二叉树的输入需要是2N的格式,才能正常往下(上)生长,所以当输入req数不是2N时,就需要进行特殊处理。例如req个数为7时,则树有三层。最顶层有7个节点,二分后第七个一定会落单,所以gen_first判断当前的节点是否是这种情况,也就是if (unsigned'(l) * 2 == NumIn-1),如果匹配的话,这个落单的节点将单独向下生长节点。
  4. 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领域博主
公众号已开,了解更多相关内容可以扫一扫下方二维码~

posted @ 2022-12-21 15:22  love小酒窝  阅读(667)  评论(1编辑  收藏  举报