Round-Robin Arbiter Design in Verilog and SystemVerilog

Are you working on a project that requires multiple requests to access a shared resource? Then you may need an arbiter to decide which request to grant access to the resource. One of the most common types of arbiters used in digital designs is the round-robin arbiter. In this blog post, we will discuss how to design a round-robin arbiter in Verilog and SystemVerilog.

Why Round-Robin Arbiter is Important

Before we dive into the design of the round-robin arbiter, let's first discuss why it is important. The round-robin arbiter is used to prevent starvation and provide statistical fairness in a system. Starvation occurs when a request is repeatedly denied access to the shared resource, even though other requests are being granted access. The round-robin arbiter solves this problem by granting access to each request in a circular fashion, ensuring that no request is consistently denied access to the resource.

Statistical fairness is also important in systems with multiple requests for a shared resource. Without statistical fairness, a small number of requests may be granted access to the resource more often than other requests. This can lead to performance degradation or even system failure. The round-robin arbiter ensures that each request has an equal chance of being granted access to the resource, which promotes statistical fairness.

Designing the Round-Robin Arbiter in Verilog and SystemVerilog

Now let's move on to the design of the round-robin arbiter in Verilog and SystemVerilog. We will use the following design:

module RoundRobinArbiter #(
  parameter NumRequests = 8
) (
  input  logic                   clk,
  input  logic                   rstN,
  input  logic [NumRequests-1:0] req,
  output logic [NumRequests-1:0] grant
);

  logic [NumRequests-1:0] mask, maskNext;
  logic [NumRequests-1:0] maskedReq;
  logic [NumRequests-1:0] unmaskedGrant;
  logic [NumRequests-1:0] maskedGrant;

  assign maskedReq = req & mask;

  Arbiter #(
    .NumRequests(NumRequests)
  ) arbiter (
    .request(req),
    .grant  (unmaskedGrant)
  );

  Arbiter #(
    .NumRequests(NumRequests)
  ) maskedArbiter (
    .request(maskedReq),
    .grant  (maskedGrant)
  );

  assign grant = (maskedReq == '0) ? unmaskedGrant : maskedGrant;

  always_comb begin
    if (grant == '0) begin
      maskNext = mask;
    end
    else begin
      maskNext = '1;

      for (int i = 0; i < NumRequests; i++) begin
        maskNext[i] = 1'b0;
        if (grant[i]) break;
      end
    end
  end

  always_ff @(posedge clk or negedge rstN) begin
    if (!rstN) mask <= '1;
    else mask <= maskNext;
  end
endmodule

In this Verilog and SystemVerilog design of the round-robin arbiter, two fixed priority arbiters are utilized to manage requests for a shared resource. The first arbiter, the unmasked arbiter, processes the original request. The second arbiter, the masked arbiter, processes the request ANDed with a mask. The masked arbiter takes priority over the unmasked one. If there is no masked request, the unmasked arbiter's result is used. However, if there is a masked request, the masked arbiter's result takes precedence. The mask determines which request has priority.

The challenge in the round-robin arbiter design lies in the mask update logic. When the N-th bit is granted, the subsequent bits above N must have priority in the next cycle. To accomplish this, the MSB:N+1 bits are set to 1, while the remaining bits are set to 0. For example, if the grant vector is 00001000, the mask will be 11110000 in the next cycle. Requests in the MSB 4 bits are granted access, but if there are no requests in these bits, the unmasked arbiter's result is used. If no grant occurs during the cycle, the mask remains unchanged.

The maskedReq expression is a bitwise AND between the original request req and the mask. This variable ensures that only requests with priority (i.e., those with a set mask bit) are processed by the masked arbiter. The grant output is determined by the ternary operator, which checks whether the maskedReq expression is 0. If it is, the unmasked arbiter's result is used. If it is not, the masked arbiter's result is used.

The maskNext variable is updated in the combinational block. When a request is granted, the bits above the granted bit are given priority in the next cycle. The for loop in the combinational block iterates through the granted bits to determine which bit has the highest priority. The mask variable is updated in the synchronous block using a flip-flop, which is triggered by a positive edge clock or negative edge reset signal.

Conclusion

In conclusion, the round-robin arbiter is an important component in digital designs that manage multiple requests for a shared resource. The design we discussed in this blog post utilizes two fixed priority arbiters, with the masked arbiter taking priority over the unmasked one. The mask update logic is a crucial element of the design, which ensures that requests are processed in a circular fashion, promoting statistical fairness and preventing starvation. By implementing this design in Verilog and SystemVerilog, engineers can ensure that their designs provide equal access to shared resources for all requests, leading to optimal system performance.