Arbiter is an important piece of hardware which is present in all designs. Basically, it is used to provide access of a shared resource to one out of many numbers of competing processes. Assume you have single bus in a design and a lot of blocks are trying to access a single memory resource present in the design using the only bus. Obviously, all blocks cannot be allowed to use the bus at the same time. What do we do? We introduce an arbiter between the blocks and the bus. Now it is up to arbiter to give the access of the bus to any block if the block requests it, provided the access is given to only one block at any given time. The arbiter may assume the blocks having a priority. When more than one blocks are requesting for access, the access will be given to the block having the highest priority (Fixed Priority Arbiter). Or the arbiter can work in a Round-Robin fashion, which we will discuss in another article.
This is how an arbiter will look in a design explained above:
To understand more clearly, let us assume a 4-bit bus, meaning there are 4 blocks which will try to request the access of a shared resource. It is clear with this information that the output will also be of 4-bits. Also assume LSB is having the highest priority and MSB has the lowest priority. So, if the input is 4’b1010, the output will be 4’b0010 because LSB has the highest priority. If input is 4’b1101, output will be 4’b0001. Basically, our aim is to output a one-hot signal depicting the block which will get access to the resource.
Simple enough, right? Let us try to write a SystemVerilog code for a fixed priority arbiter explained in the example.
module bit_fixed_pri_arb(
input logic [3:0] req,
output logic [3:0] grant
);
always_comb begin
if (req[0] == 1'b1) assign grant = 4'b0001; //LSB has the highest priority
if (req[1] == 1'b1) assign grant = 4'b0010;
if (req[2] == 1'b1) assign grant = 4'b0100;
if (req[3] == 1'b1) assign grant = 4'b1000;
end
endmodule
As you can see, it is a purely combinational logic, you do not need any clock or reset for this. You can also code it using casez statements and the results will be the same.
Now, imagine how you would modify the code if the input were a bus of 8-bits? Imagine what would you do for 32-bits bus input? Got Carpal Tunnel Syndrome yet?
So, we agree that this method of coding is not scalable. Even if we leave the physical effects aside, just imagine the number of MUXes required for a 32-bit bus input. We need to find a way to make it parameterized that can improve the efficiency of the design.
See the code below:
module fixed_pri_arbiter #(
parameter N = 32
) (
input logic [N-1:0] req,
output logic [N-1:0] gnt
);
logic [N-1:0] higher_pri_req;
assign higher_pri_req[0] = 1'b0; //LSB has the highest priority
for (genvar i=0; i<N-1; i++) begin
assign higher_pri_req[i+1] = higher_pri_req[i] | req[i];
end
assign gnt[N-1:0] = req[N-1:0] & ~higher_pri_req[N-1:0];
endmodule
So, what we are doing here is we are finding the index of first 1 from LSB, and every other 1 after that index will be 0. There are two steps involved in doing that.
First, we are finding the index of first 1 in req, and every other index value after that index will be 1 and we are saving this value to higher_pri_req. For example, if req is 4'b1010, the value of higher_pri_req will be 4'b1100. Let us see how we are doing that:
Let us assume we have a 4-bit req bus. higher_pri_req is checking if any bits before the current bit in higher_pri_req is 1 OR if any bits before the current bit in the req is 1.
Meaning, if we are checking the value for bit number 2 in higher_pri_req, it will be 1 if bits 0-1 in higher_pri_req is 1 OR if bits 0-1 in req is 1.
The bit number 0 for higher_pri_req will be set to 0 every time.
Second thing to do is mask the input req with the generated value to get a one-hot value of grant output.
Now that we have the higher_pri_req value, we can just invert it and AND it with input req which will give us the required grant.
Assuming, input req = 4'b1010
higher_pri_req = 4'b1100 //generated from step 1
output gnt = req & ~higher_pri_req //step 2
= 4'b1010 & ~(4b1100)
= 4'b1010 & 4b0011
= 4'b0010
Now we know how to code a parameterized fixed priority arbiter module and the operation of the module as well. If it is still not clear to you, take a pen and a paper, assume any 4-bit bus value, and write the values of all the signals after every for loop. It should get cleared up instantly.
We can still optimize it and there is indeed a better way to do it. I have explained it in my Interview QnA page. Go ahead and check that out!
See you in the next article!