An arbiter is a very common block used on HW designs.
I think I can find the best example of an arbiter at home. When my two kids were teenagers, I had only one car. On Friday and Saturday evenings, there was usually a conflict over who got to use the car. Usually, it was on me to decide (arbiter) who got the car. Not an easy task. (I still have only one car. It just happens that my kids are not teenagers anymore, and praise the Lord, they have their own cars).
HW boards are not that different. There are at least two cases where a shared (and valued, and expensive) resource is ‘wanted’ by several users:
- Common memory: It is very usual to see boards where fast memory (i.e. DDR) is shared between a processor and an FPGA. Obviously, memory cannot answer two masters at once. The processor, and the FPGA, ask the arbiter for permission to access the memory. Even if it is done at very high speed and simultaneously for our perception, in reality, the FPGA and the processor have to take turns to own the memory. NOTE: This was only a simple example, as there are many other applications where even more than two ‘intelligent’ devices (processors, DSPs, GPUs, FPGAs, ASICs) take turns accessing a common memory.
- Common bus: Many times multiple masters (and slaves) communicate through a common bus. Electrically speaking the bus is very simple. It is no more than a set of copper connections, or wires (sometimes with bus drivers). But bus ownership is important since it is impossible for two masters to own the bus at the same time (examples of multi-master buses: I2C, PCI). If two masters tried to ‘talk’ at the same time there would be a collision, and the data on the bus would be corrupted.
The arbiter is the piece of HW who decides who gets to use the common, valued resource, at any given time. However, and unlike the soccer arbiter, the VHDL arbiter will never take one of the devices out of the ‘game’ (well… almost never. There are cases where HW arbiters decide that one device is behaving badly and decide to get it out of the game. One example is a pluggable card accessing a common bus, that can be unplugged at a critical moment, and if the arbiter didn’t recognize the failure, the bus could remain stuck over the -now missing- device).
The arbiter receives two types of signals from its clients:
- Request: Asserted by each one of the devices that want to own the common resource. There can be many request signals asserted at the same time, as is the case when many devices ask for permission to own the common resource. On the other hand, there can be only one, and even no request signals asserted. The latter would be the case when at a specific moment, no one needs the shared resource.
- Grant: Asserted by the arbiter, there is one grant signal for each master. Usually, there will be ONLY ONE grant signal asserted at any given time.
The first arbiter we will analyze has three request inputs and three grant outputs. It also has a fixed priority for the masters. The lower the master number, the higher its priority.The block also has a busy signal. Arbitration of the bus is done only while it is inactive. If the bus has already been granted to an agent, even if a bigger priority master requests the bus, the current transaction must complete before the arbiter grants the bus to another master.
The logic for generating the grant signals (on process arbiter_pr) is quite simple. If the first master (master 0) asserts a request, it is awarded a grant. Master 1 is given a grant only if it requests the bus and master 0 doesn't request the bus.Master 2 is awarded a grant only if it requests the bus and neither master 0 nor master 1 requests the bus.
The gnt signal changes only if the bus is not busy. The process busy_pr and associated logic detect the falling edge of the busy signal. After busy goes low, all grant signals are de-asserted for one clock, and then the logic for choosing the next bus master is activated.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity arbiter is
port (
clk : in std_logic;
rst : in std_logic;
-- inputs
req : in std_logic_vector(2 downto 0);
busy : in std_logic;
-- outputs
gnt : out std_logic_vector(2 downto 0)
);
end arbiter;
architecture rtl of arbiter is
signal busy_d : std_logic := '0';
signal busy_fe : std_logic;
begin
busy_pr : process (clk)
begin
if (rising_edge(clk)) then
busy_d <= busy;
end if;
end process busy_pr;
-- Falling edge of busy signal
busy_fe <= '1' when busy = '0' and busy_d = '1' else '0';
arbiter_pr : process (clk, rst)
begin
if (rst = '1') then
gnt <= (others => '0');
elsif (rising_edge(clk)) then
if (busy_fe = '1') then
gnt <= (others => '0');
elsif (busy = '0') then
gnt(0) <= req(0);
gnt(1) <= req(1) and not req(0);
gnt(2) <= req(2) and not (req(0) or req(1));
end if;
end if;
end process arbiter_pr;
end rtl;
The gnt signal changes only if the bus is not busy. The process busy_pr and associated logic detect the falling edge of the busy signal. After busy goes low, all grant signals are de-asserted for one clock, and then the logic for choosing the next bus master is activated.
Upon reset release, there are no outstanding requests so the arbiter also doesn't assert any grant signals. Later on in the simulation, several masters ask permission from the arbiter (request asserted) and are given grants according to their priority.
Between 300 and 400ns, master '1' asserts its request signal. Two clock cycles later, req from master '0' is asserted. So even if master '0' arrived later, when the arbiter is free to assign the bus, it will assign it to master '0'.
Notice that there is always a 'rest' clock between gnt signals. Each master uses the bus for four clocks and relinquishes the bus (this can be seen in the duration of the busy signal).
Later on both master '2' and '0' request the bus and, as expected, the bus is granted to master '0'.
The VHDL source for the arbiter 'simple implementation', testbench, and Modelsim files are available on GitHub
Second implementation - Variable size, fixed priorityThe logic of the arbiter presented above is of fixed size. With a few changes, and by using unconstrained ports (look at req and gnt ports), we can make a generic arbiter whose size can be decided upon implementation.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity arbiter_unc is
port (
clk : in std_logic;
rst : in std_logic;
-- inputs
req : in std_logic_vector;
busy : in std_logic;
-- outputs
gnt : out std_logic_vector
);
end arbiter_unc;
architecture rtl of arbiter_unc is
signal busy_d : std_logic;
signal busy_fe : std_logic;
begin
busy_pr : process (clk)
begin
if (rising_edge(clk)) then
busy_d <= busy;
end if;
end process busy_pr;
-- Falling edge of busy signal
busy_fe <= '1' when busy = '0' and busy_d = '1' else '0';
arbiter_pr : process (clk)
variable prio_req : std_logic;
begin
if (rising_edge(clk)) then
if (rst = '1') then
gnt <= (others => '0');
else
if (busy_fe = '1') then
gnt <= (others => '0');
elsif (busy = '0') then
gnt(0) <= req(0);
for I in 1 to req'left - 1 loop
prio_req := '0';
for J in 1 to I loop
prio_req := prio_req or req(J - 1);
end loop;
gnt(I) <= req(I) and not prio_req;
end loop;
end if;
end if;
end if;
end process arbiter_pr;
end rtl;
Vivado simulation of the variable size arbiter, instantiated with size = 4
The RTL representations show the growing combinatorial complexity needed to take care of an increasing number of ports and were generated with Quartus Prime 15.1. Note that some blocks (like the output FFs) are not single but stacked primitive instantiations.As was described, if several masters request the bus, the master with the lowest number is given gnt (recall that only one master shall receive gnt at any given time). This arbiter has a fixed priority. Although it could be possible to use such an arbiter for certain applications, the most common type of arbiters don't have fixed priority.I will talk about more complex arbiters (round-robin), in a future article.
Proposed exercises- As explained, this simple arbiter has a fixed priority. If several masters assert their request signals, the lowest numbered master is given gnt . Design a fixed priority master where the highest numbered master has the highest priority.
- In this arbiter, as long as a master requests a bus, the bus is granted to it. Add timeout logic. If one master asserts req for more than 10 clock cycles, de-assert the grant signal to this master, if other requests are outstanding.
- Some arbiters have a park feature. Park means that if there are no outstanding requests, the gnt signal is given to the last master who received it. In another version, if no master asserts req, the gnt signal is assigned to a 'default master'. Design code for each one of these two options.
The VHDL source for the arbiter 'unconstrainedimplementation', testbench, and Modelsim files are available on GitHub
You are invited to visit my website where you can find more VHDL projects, articles, and legally free books and courses.
Comments
Please log in or sign up to comment.