VHDL arbiter – part II

In the first article of this series, we defined what an HW arbiter is.
In this entry of the tutorial, we will see a simple implementation of a VHDL arbiter.

The arbiter of this example has three request inputs and three grant outputs. It 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 (lines 34-47) 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 and logic on lines 24 to 42 detect the falling edge of the busy signal. After busy goes low, all grant signals are de-asserted for one clock (line 40), 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 logic for gnt selection is a priority encoder where only one gnt bit is asserted for any combination of req signals. Let’s see a simulation of the thee-input arbiter:

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

Variable width arbiter

The logic of the arbiter presented above is of fixed size. With a few changes, and by using unconstrained 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 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

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.

In part three of this tutorial, we will see how to implement a round-robin VHDL arbiter.

The VHDL source for the unconstrained arbiter (with variable size) and testbench is available on GitHub

Leave a Reply

Your email address will not be published. Required fields are marked *