Skip to content

RTL Design Patterns · Chapter 10 · Arbitration

Request/Grant Handshake

An arbiter decides who wins a cycle, but a real client needs a protocol to ask for a shared resource, hold it for a whole transaction, and hand it back. The request/grant handshake provides that. A client raises its request and keeps it high until served, the arbiter answers with a grant, ownership begins when request and grant overlap, and the client releases the resource by dropping its request. Only one client owns it at a time. This lesson builds the ownership lifecycle as a small client state machine, compares a level grant held through a burst against a pulse grant that re-arbitrates every cycle, and shows the signature bug where a grant is not held, so a higher-priority latecomer preempts a multi-cycle transfer and corrupts the shared resource.

Intermediate14 min readRTL Design PatternsRequest/GrantArbitrationOwnershipGrant LockBus Protocol

Chapter 10 · Section 10.4 · Arbitration

1. The Engineering Problem

In 10.1 you built the arbiter that decides who wins: several masters raise a request vector, and a fixed-priority scan grants the highest-priority active line as a one-hot grant. That is the whole decision — for a single cycle. But a real master rarely wants the resource for exactly one cycle. A DMA engine draining a buffer writes a burst of eight or sixteen consecutive words. A bus master doing a read-modify-write holds the address and the lock across several cycles. A memory controller opening a row keeps the port for the whole page access. Each of these needs the shared resource for a span of cycles, and — this is the crux — it needs that span to be uninterrupted: no other master may take the resource out from under it halfway through.

The arbiter of 10.1, run purely combinationally, cannot promise that. It recomputes the grant every cycle from the current request vector. So the moment a higher-priority master raises its request in the middle of a lower-priority master's burst, the grant flips away — and the lower-priority master, which had already driven three of its eight words, loses the port mid-transaction. What lands in the shared memory is half of one master's transfer and half of another's: silent corruption. The arbiter did exactly what a fixed-priority arbiter does; the problem is that deciding the winner and governing a transaction are two different jobs, and 10.1 only did the first.

So a shared resource with multi-cycle transactions needs more than a per-cycle winner. It needs a protocol on the arbiter's ports: a way for a client to ask for the resource, to know when it has been given the resource, to hold it for the whole transaction while everyone else waits, and to release it cleanly so the next client can go. Ownership must begin at a defined moment, last a defined span, and end at a defined moment — and exactly one client may own the resource at any time. That protocol is the request/grant handshake, and it is the resource-ownership cousin of the valid/ready handshake from 9.1.

the-need.v — a per-cycle winner is not an owner; a burst needs a held grant
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // 10.1's arbiter picks a winner EVERY cycle from the current request vector:
   wire [3:0] req;                       // req[i] = master i wants the shared port
   wire [3:0] grant;                     // one-hot winner THIS cycle (recomputed each cycle)
 
   // A DMA (master 2) starts an 8-word burst into a shared SRAM:
   //   cycle 0: grant=0100, DMA writes word 0
   //   cycle 1: grant=0100, DMA writes word 1
   //   cycle 2: CPU (master 0, higher priority) raises req[0] -> grant flips to 0001!
   //   cycle 2..: DMA has LOST the port mid-burst; the SRAM now holds a mix of both -> X
 
   // The need: a PROTOCOL. The client asserts req and HOLDS it; ownership begins on
   // req && grant; the grant is HELD (locked) for the whole transaction; the client
   // RELEASES by dropping req. One owner at a time. That is request/grant (this page).

2. Mental Model

3. Pattern Anatomy

Request/grant is three signals on the wire — a forward req, a backward grant, and (implicitly) the shared resource the two govern — plus one lifecycle and one policy axis. Everything hard is in the rules that say how long ownership lasts and how it ends, because those rules are what keep a multi-cycle transaction intact.

The ownership lifecycle — a small client FSM. From the client's point of view, using a shared resource is four states:

  • IDLE — the client has no work for the resource; req is low. It is not asking and does not own.
  • REQUEST — the client wants the resource: it asserts req and holds it, waiting. It does not yet own anything — it is standing at the desk with its hand up. It stays here, req held stable, until it sees grant.
  • OWNED / USE — on the cycle req && grant, ownership begins. The client now drives the resource (address, data, enable) for as many cycles as the transaction needs. It keeps req asserted throughout, which — with a held grant — keeps it the owner.
  • RELEASE — the transaction is done; the client drops req, which tells the arbiter the resource is free. It returns to IDLE. The arbiter, seeing the owner's req fall, may grant the next waiting client the following cycle.

Level grant vs pulse grant — the policy axis. The single most consequential choice is how long the grant lasts, and it is exactly the difference between a lease and a vote:

  • Level grant (held). The arbiter asserts grant and holds it on the owner for the whole transaction — the grant is a level that stays high while the owner still requests. This is what a burst needs: the owner cannot be preempted, because the grant does not move while it is held. Ownership spans the whole burst. This requires the arbiter to have state (a small FSM / lock bit) that remembers who the owner is.
  • Pulse grant (one-cycle). The arbiter asserts grant for a single cycle and re-arbitrates the next cycle from scratch. Ownership is exactly one cycle; the client must finish its transaction in that cycle or re-request. This is the purely combinational arbiter of 10.1 used directly, and it is correct only for single-cycle transactions — a burst under a pulse grant is preempted the instant a higher-priority client asks (§7).

The rules that keep exactly one owner. The signals are trivial; the rules are the pattern:

  • Grant only a requester. grant is asserted only to a client that is currently asserting req(grant & req) == grant, the same granted-implies-requesting invariant as 10.1. You never hand ownership to a client that is not asking.
  • Request stable until granted. Once a client asserts req, it holds it (unchanged) until the transfer req && grant happens. This is the valid-held-until-accepted rule of 9.1 applied to ownership: a request that flickers off before it is granted can lose its turn (§6).
  • A burst grant is held until release. For a multi-cycle transaction the grant (ownership) is locked to the owner for the whole span — no one preempts mid-transaction. The arbiter re-arbitrates only after the owner releases. This is the level-grant rule, and it is the whole difference between a correct burst and a corrupted one.
  • Release cleanly, hand off the next cycle. The owner ends its transaction by dropping req; the arbiter frees the resource and grants a waiting client the next cycle. No overlap (two owners), no lost cycle where a waiting client is skipped.

The ownership lifecycle — IDLE to REQUEST to OWNED to RELEASE, with the grant held through the burst

data flow
The ownership lifecycle — IDLE to REQUEST to OWNED to RELEASE, with the grant held through the bursthas workgrant seenmulti-cycledonehand offIDLE : req=0no work; not asking, not owningREQUEST : reqheldasks and HOLDS req until granted (like valid, 9.1)OWNED : req &&grantownership begins; drives the resourceburst : grantHELDlevel grant locked; no preemptionRELEASE : reqdropsresource freed; arbiter re-arbitrates
Read the client one state at a time. In IDLE it has no work and req is low. When it has work it enters REQUEST, asserts req, and HOLDS it stable until granted — exactly the valid-held-until-accepted discipline of 9.1. Ownership begins on the cycle req && grant overlap; the client then drives the resource. For a multi-cycle burst the arbiter HOLDS the grant (a level grant, locked to the owner) so no higher-priority latecomer can preempt the transaction. When the transaction finishes the client RELEASES by dropping req, the arbiter frees the resource, and a waiting client is granted the next cycle — a clean handoff with exactly one owner throughout. This lifecycle is identical in SystemVerilog, Verilog, and VHDL, and it is the ownership layer on top of the fixed-priority decision of 10.1.

The parallel to valid/ready — and the one difference. Request/grant maps almost cleanly onto the valid/ready handshake of 9.1: req is the forward offer, held stable until accepted, just like valid; grant is the backward accept, just like ready; and the transaction begins on the overlap req && grant, exactly as a valid/ready transfer happens on valid && ready. The three composability rules carry over — the requester drives req from its own state (it wants the resource), the arbiter's grant may look at req, and the request is held stable until granted. The one difference is decisive: ready accepts a single word and is done, but grant confers exclusive ownership that persists for the whole transaction. That is why request/grant adds a lifecycle (valid/ready has none) and a level-vs-pulse policy: ownership has a duration, and defining that duration — one cycle, or the whole burst — is the extra job the grant does that ready does not.

request/grant vs valid/ready — the same overlap, plus ownership that spans the transaction

data flow
request/grant vs valid/ready — the same overlap, plus ownership that spans the transactionreqgrantburstguaranteesreq (forward)held until granted (like valid, 9.1)grant (backward)accept + confers ownership (unlike ready)ownership = req&& grantbegins on the overlap, spans the burstlevel grant :lockedheld so the span is uninterruptedexactly one ownermutual exclusion for the whole duration
req is the forward offer held stable until accepted — the same discipline as valid in 9.1 — and grant is the backward accept, the same role as ready. Ownership begins on the overlap req && grant, exactly as a valid/ready transfer happens on valid && ready. The decisive difference is that ready accepts one word and is done, whereas grant confers exclusive ownership that PERSISTS for the whole transaction: for a multi-cycle burst the grant is a LEVEL grant, locked to the owner, so the span is uninterrupted and exactly one client owns the resource for its full duration. That extra job — governing a duration, not a single word — is why request/grant adds an ownership lifecycle and a level-vs-pulse policy on top of the valid/ready contract. This is language-independent across SystemVerilog, Verilog, and VHDL.

Two facts close the anatomy. First, a request/grant protocol is incomplete until it defines ownership duration. A grant with no stated duration is ambiguous, and the ambiguity is resolved — badly — by whatever the arbiter happens to do next cycle: re-arbitrate and preempt. Deciding up front that a grant is a level held until release (for bursts) or a single pulse (for one-cycle transactions) is what makes the protocol correct. Second, a client that never releases is a liveness problem, not a safety one. A client that holds req forever hogs the resource — the grant is legitimately held, the invariant of one-owner is not violated, but every other client starves. That is a fairness failure the protocol itself cannot prevent; it is bounded by fairness policy (round-robin, 10.2) and forced release / timeout mechanisms, which is where arbiter fairness (10.5) picks up.

4. Real RTL Implementation

This is the core of the page. We build two things — (a) a request/grant arbiter with held ownership (a client-side FSM IDLE -> REQUEST -> OWNED -> RELEASE, plus an arbiter that grants from a fixed-priority core and locks the grant to the owner until release, parameterized to N clients) with a self-checking clocked testbench asserting grant-implies-request, single owner, a burst grant held through the whole transaction, and a clean handoff; and (b) the grant-not-held-mid-burst preemption bug beside its lock-until-release fix — and each is shown in SystemVerilog, Verilog, and VHDL, with an RTL block and a self-checking clocked testbench. The reasoning is identical in all three; only the syntax differs.

One discipline runs through every RTL block, inherited from 10.1 and the register pattern: state and grant registers update on the clock with non-blocking assignments, reset is synchronous and active-high, the arbiter only ever grants a client that is requesting, and once ownership is locked the grant does not move until the owner releases.

4a. A request/grant arbiter with held ownership — three ways

The arbiter has two states. IDLE arbitrates: if any client requests, it picks the highest-priority requester with the isolate-lowest-set-bit identity from 10.1 (req & (~req + 1)), latches that one-hot pick into the grant register, and enters HELD. HELD locks the grant on the owner and stays there while the owner keeps req asserted; when the owner releases (drops its req), it clears the grant and returns to IDLE, free to re-arbitrate the next cycle. The grant is a level held for the whole transaction — that is what makes a burst safe.

rg_arbiter.sv — request/grant arbiter with LOCKED ownership (held until release)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module rg_arbiter #(
       parameter int N = 4                            // number of clients
   )(
       input  logic         clk,
       input  logic         rst,                      // synchronous, active-high
       input  logic [N-1:0] req,                      // req[i] = client i wants the resource
       output logic [N-1:0] grant,                    // one-hot owner, HELD across the transaction
       output logic         busy                      // 1 = the resource is owned this cycle
   );
       typedef enum logic {IDLE, HELD} state_t;
       state_t       state;
       logic [N-1:0] owner;                            // registered one-hot owner (the LOCK)
 
       // Fixed-priority pick (isolate lowest set bit) — one-hot by construction (10.1).
       wire [N-1:0] pick = req & (~req + 1'b1);
 
       always_ff @(posedge clk) begin
           if (rst) begin
               state <= IDLE;
               owner <= '0;
           end else begin
               unique case (state)
                   IDLE: if (|req) begin
                             owner <= pick;             // LATCH the winner as owner...
                             state <= HELD;             // ...and LOCK it (enter HELD)
                         end
                   HELD: if ((owner & req) == '0) begin // owner dropped its req -> RELEASE
                             owner <= '0;               // free the resource
                             state <= IDLE;             // re-arbitrate next cycle
                         end
                                                        // else: HOLD -- owner unchanged,
                                                        //   grant stays locked on it
               endcase
           end
       end
 
       assign grant = owner;                            // grant is the HELD one-hot owner
       assign busy  = (state == HELD);                  // resource is owned while HELD
   endmodule

The client is the mirror image: an FSM that walks IDLE -> REQUEST -> OWNED -> RELEASE. It asserts req in REQUEST and holds it until it sees its grant bit; on req && grant it enters OWNED and drives the resource for BURST cycles, counting them down; when the burst is done it drops req (RELEASE) and returns to IDLE. Holding req throughout OWNED is what keeps the arbiter's grant locked on this client.

rg_client.sv — client FSM: IDLE -> REQUEST -> OWNED (burst) -> RELEASE
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module rg_client #(
       parameter int BURST = 4                          // how many cycles the transaction needs
   )(
       input  logic clk,
       input  logic rst,
       input  logic start,                              // pulse: the client has work to do
       input  logic grant,                              // this client's grant bit from the arbiter
       output logic req,                                // held while REQUEST or OWNED
       output logic owning                              // 1 while this client owns the resource
   );
       typedef enum logic [1:0] {IDLE, REQUEST, OWNED, RELEASE} cstate_t;
       cstate_t                         state;
       logic [$clog2(BURST+1)-1:0]      count;          // cycles left in the burst
 
       always_ff @(posedge clk) begin
           if (rst) begin
               state <= IDLE; count <= '0;
           end else begin
               unique case (state)
                   IDLE:    if (start)          state <= REQUEST;   // work arrived -> ask
                   REQUEST: if (grant) begin                        // ownership begins on req && grant
                                state <= OWNED; count <= BURST-1;    //   drive the resource for BURST cycles
                            end
                   OWNED:   if (count == '0)    state <= RELEASE;    // burst finished
                            else                count <= count-1'b1; //   ...still driving; hold req
                   RELEASE:                     state <= IDLE;       // req dropped this cycle -> free
               endcase
           end
       end
 
       // req is HELD (stable) while asking AND while owning -- that is what keeps the
       // arbiter's grant locked on us for the whole burst (Rule: request stable until
       // granted; grant held through the transaction).
       assign req    = (state == REQUEST) || (state == OWNED);
       assign owning = (state == OWNED);
   endmodule

The self-checking testbench wires two clients into the arbiter, starts a low-priority client on a multi-cycle burst, then raises a higher-priority client mid-burst, and asserts the four properties that define a correct request/grant protocol: grant-implies-request every cycle, at most one owner every cycle, the burst grant held on the original owner until it releases (no preemption), and a clean handoff to the waiting client the cycle after release.

rg_arbiter_tb.sv — grant-implies-req, single owner, burst held, clean handoff
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module rg_arbiter_tb;
       localparam int N = 2, BURST = 4;
       logic          clk = 0, rst;
       logic [N-1:0]  req, grant;
       logic          busy;
       logic          start0, start1;
       int            errors = 0;
       always #5 clk = ~clk;
 
       // Client 0 is HIGHER priority (lowest index wins); client 1 is lower priority.
       rg_client #(.BURST(BURST)) c0 (.clk(clk), .rst(rst), .start(start0),
                                      .grant(grant[0]), .req(req[0]), .owning());
       rg_client #(.BURST(BURST)) c1 (.clk(clk), .rst(rst), .start(start1),
                                      .grant(grant[1]), .req(req[1]), .owning());
       rg_arbiter #(.N(N)) arb (.clk(clk), .rst(rst), .req(req), .grant(grant), .busy(busy));
 
       // Every cycle: grant-implies-req and one-hot-or-zero (single owner).
       always @(posedge clk) if (!rst) begin
           assert ((grant & req) == grant)
               else begin $error("grant a non-requester: req=%b grant=%b", req, grant); errors++; end
           assert ($onehot0(grant))
               else begin $error("TWO owners: grant=%b", grant); errors++; end
       end
 
       initial begin
           rst = 1; start0 = 0; start1 = 0; @(posedge clk); #1; rst = 0;
           // Lower-priority client 1 starts its burst first, alone.
           start1 = 1; @(posedge clk); #1; start1 = 0;
           @(posedge clk); #1;                          // c1 now REQUEST -> gets grant, OWNED
           assert (grant == 2'b10)
               else begin $error("c1 should own: grant=%b", grant); errors++; end
           // Mid-burst, higher-priority client 0 asks -- it must NOT preempt the owner.
           start0 = 1; @(posedge clk); #1; start0 = 0;
           repeat (2) @(posedge clk); #1;
           assert (grant == 2'b10)                       // grant STILL on c1 -- held through burst
               else begin $error("burst preempted: grant=%b exp=10", grant); errors++; end
           // Let c1 finish; after it releases, c0 must be granted (clean handoff).
           repeat (4) @(posedge clk); #1;
           assert (grant == 2'b01)
               else begin $error("no clean handoff to c0: grant=%b exp=01", grant); errors++; end
           if (errors == 0) $display("PASS: grant->req, one owner, burst held, clean handoff");
           else             $display("FAIL: %0d errors", errors);
           $finish;
       end
   endmodule

The Verilog form is the same structure with reg/wire typing and explicit state encodings; the arbiter still latches pick into owner and holds it until (owner & req) == 0, and the client still holds req through REQUEST and OWNED.

rg_arbiter.v — request/grant arbiter with locked ownership in Verilog
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module rg_arbiter #(
       parameter N = 4
   )(
       input  wire         clk,
       input  wire         rst,
       input  wire [N-1:0] req,
       output wire [N-1:0] grant,
       output wire         busy
   );
       localparam IDLE = 1'b0, HELD = 1'b1;
       reg              state;
       reg  [N-1:0]     owner;                          // registered one-hot owner (LOCK)
       wire [N-1:0]     pick = req & (~req + 1'b1);      // fixed-priority pick (one-hot)
 
       always @(posedge clk) begin
           if (rst) begin
               state <= IDLE; owner <= {N{1'b0}};
           end else begin
               case (state)
                   IDLE: if (|req) begin
                             owner <= pick;              // latch the winner
                             state <= HELD;              // and lock it
                         end
                   HELD: if ((owner & req) == {N{1'b0}}) begin  // owner released
                             owner <= {N{1'b0}};
                             state <= IDLE;
                         end
                   // else HOLD: owner unchanged, grant stays locked
               endcase
           end
       end
 
       assign grant = owner;                            // HELD one-hot owner
       assign busy  = (state == HELD);
   endmodule
rg_client.v — client FSM in Verilog (IDLE/REQUEST/OWNED/RELEASE)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module rg_client #(
       parameter BURST = 4
   )(
       input  wire clk, rst, start, grant,
       output wire req, owning
   );
       localparam IDLE = 2'd0, REQUEST = 2'd1, OWNED = 2'd2, RELEASE = 2'd3;
       reg [1:0]  state;
       reg [31:0] count;
 
       always @(posedge clk) begin
           if (rst) begin
               state <= IDLE; count <= 0;
           end else begin
               case (state)
                   IDLE:    if (start)       state <= REQUEST;
                   REQUEST: if (grant) begin state <= OWNED; count <= BURST-1; end  // req && grant
                   OWNED:   if (count == 0)  state <= RELEASE;
                            else             count <= count - 1;
                   RELEASE:                  state <= IDLE;
               endcase
           end
       end
 
       assign req    = (state == REQUEST) || (state == OWNED);   // held through the burst
       assign owning = (state == OWNED);
   endmodule
rg_arbiter_tb.v — self-checking: grant->req, single owner, burst held, clean handoff
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module rg_arbiter_tb;
       parameter N = 2, BURST = 4;
       reg          clk, rst, start0, start1;
       wire [N-1:0] req, grant;
       wire         busy;
       integer      errors;
 
       rg_client #(.BURST(BURST)) c0 (.clk(clk), .rst(rst), .start(start0),
                                      .grant(grant[0]), .req(req[0]), .owning());
       rg_client #(.BURST(BURST)) c1 (.clk(clk), .rst(rst), .start(start1),
                                      .grant(grant[1]), .req(req[1]), .owning());
       rg_arbiter #(.N(N)) arb (.clk(clk), .rst(rst), .req(req), .grant(grant), .busy(busy));
 
       initial begin clk = 0; forever #5 clk = ~clk; end
 
       // Every cycle: grant-implies-req and one owner (g & (g-1))==0.
       always @(posedge clk) if (!rst) begin
           if ((grant & req) !== grant) begin
               $display("FAIL: grant a non-requester req=%b grant=%b", req, grant); errors = errors+1;
           end
           if ((grant & (grant - 1)) != 0) begin
               $display("FAIL: TWO owners grant=%b", grant); errors = errors+1;
           end
       end
 
       initial begin
           errors = 0; rst = 1; start0 = 0; start1 = 0; @(posedge clk); #1; rst = 0;
           start1 = 1; @(posedge clk); #1; start1 = 0;       // lower-priority c1 asks first
           @(posedge clk); #1;
           if (grant !== 2'b10) begin $display("FAIL: c1 should own grant=%b", grant); errors=errors+1; end
           start0 = 1; @(posedge clk); #1; start0 = 0;       // higher-priority c0 asks mid-burst
           repeat (2) @(posedge clk); #1;
           if (grant !== 2'b10) begin                         // must NOT preempt
               $display("FAIL: burst preempted grant=%b exp=10", grant); errors=errors+1;
           end
           repeat (4) @(posedge clk); #1;                     // c1 finishes and releases
           if (grant !== 2'b01) begin                         // clean handoff to c0
               $display("FAIL: no handoff to c0 grant=%b exp=01", grant); errors=errors+1;
           end
           if (errors == 0) $display("PASS: grant->req, one owner, burst held, clean handoff");
           else             $display("FAIL: %0d errors", errors);
           $finish;
       end
   endmodule

In VHDL the arbiter is a clocked process (clk) with a state_t enumeration and a registered owner vector; the fixed-priority pick uses the numeric_std two's-complement negation, and the lock/hold/release logic mirrors the other two languages exactly.

rg_arbiter.vhd — request/grant arbiter with locked ownership in VHDL (numeric_std)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;
 
   entity rg_arbiter is
       generic ( N : positive := 4 );
       port (
           clk   : in  std_logic;
           rst   : in  std_logic;                              -- synchronous, active-high
           req   : in  std_logic_vector(N-1 downto 0);
           grant : out std_logic_vector(N-1 downto 0);         -- one-hot, HELD across the transaction
           busy  : out std_logic
       );
   end entity;
 
   architecture rtl of rg_arbiter is
       type state_t is (IDLE, HELD);
       signal state : state_t := IDLE;
       signal owner : std_logic_vector(N-1 downto 0) := (others => '0');
       signal pick  : std_logic_vector(N-1 downto 0);
   begin
       -- Fixed-priority pick: isolate the lowest set bit -> one-hot by construction (10.1).
       pick <= req and std_logic_vector(unsigned(not req) + 1);
 
       process (clk)
       begin
           if rising_edge(clk) then
               if rst = '1' then
                   state <= IDLE;
                   owner <= (others => '0');
               else
                   case state is
                       when IDLE =>
                           if unsigned(req) /= 0 then
                               owner <= pick;                  -- latch the winner as owner
                               state <= HELD;                  -- and lock it
                           end if;
                       when HELD =>
                           if unsigned(owner and req) = 0 then -- owner released its req
                               owner <= (others => '0');
                               state <= IDLE;                  -- re-arbitrate next cycle
                           end if;
                           -- else HOLD: owner unchanged, grant stays locked
                   end case;
               end if;
           end if;
       end process;
 
       grant <= owner;                                         -- HELD one-hot owner
       busy  <= '1' when state = HELD else '0';
   end architecture;
rg_client.vhd — client FSM in VHDL (IDLE/REQUEST/OWNED/RELEASE)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;
 
   entity rg_client is
       generic ( BURST : positive := 4 );
       port (
           clk    : in  std_logic;
           rst    : in  std_logic;
           start  : in  std_logic;                             -- work arrived
           grant  : in  std_logic;                             -- this client's grant bit
           req    : out std_logic;                             -- held through REQUEST and OWNED
           owning : out std_logic
       );
   end entity;
 
   architecture rtl of rg_client is
       type cstate_t is (IDLE, REQUEST, OWNED, RELEASE);
       signal state : cstate_t := IDLE;
       signal count : integer range 0 to BURST := 0;
   begin
       process (clk)
       begin
           if rising_edge(clk) then
               if rst = '1' then
                   state <= IDLE; count <= 0;
               else
                   case state is
                       when IDLE =>
                           if start = '1' then state <= REQUEST; end if;
                       when REQUEST =>
                           if grant = '1' then                 -- ownership begins on req && grant
                               state <= OWNED; count <= BURST-1;
                           end if;
                       when OWNED =>
                           if count = 0 then state <= RELEASE;  -- burst finished
                           else              count <= count-1;  -- still driving; hold req
                           end if;
                       when RELEASE =>
                           state <= IDLE;                       -- req dropped -> free
                   end case;
               end if;
           end if;
       end process;
 
       -- req held stable while asking AND while owning -> keeps the grant locked on us.
       req    <= '1' when (state = REQUEST or state = OWNED) else '0';
       owning <= '1' when state = OWNED else '0';
   end architecture;
rg_arbiter_tb.vhd — self-checking: grant->req, single owner, burst held, clean handoff
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;
 
   entity rg_arbiter_tb is
   end entity;
 
   architecture sim of rg_arbiter_tb is
       constant N     : positive := 2;
       constant BURST : positive := 4;
       signal clk   : std_logic := '0';
       signal rst   : std_logic;
       signal req   : std_logic_vector(N-1 downto 0);
       signal grant : std_logic_vector(N-1 downto 0);
       signal busy  : std_logic;
       signal start0, start1 : std_logic := '0';
 
       function popcount (v : std_logic_vector) return natural is
           variable c : natural := 0;
       begin
           for i in v'range loop if v(i) = '1' then c := c + 1; end if; end loop;
           return c;
       end function;
   begin
       clk <= not clk after 5 ns;
 
       -- Client 0 higher priority (lowest index wins); client 1 lower priority.
       c0 : entity work.rg_client generic map (BURST => BURST)
            port map (clk => clk, rst => rst, start => start0, grant => grant(0),
                      req => req(0), owning => open);
       c1 : entity work.rg_client generic map (BURST => BURST)
            port map (clk => clk, rst => rst, start => start1, grant => grant(1),
                      req => req(1), owning => open);
       arb : entity work.rg_arbiter generic map (N => N)
            port map (clk => clk, rst => rst, req => req, grant => grant, busy => busy);
 
       -- Every cycle: grant-implies-req and at most one owner.
       check : process (clk)
       begin
           if rising_edge(clk) and rst = '0' then
               assert (grant and req) = grant
                   report "arbiter granted a non-requester" severity error;
               assert popcount(grant) <= 1
                   report "TWO owners at once" severity error;
           end if;
       end process;
 
       stim : process
       begin
           rst <= '1'; wait until rising_edge(clk); wait for 1 ns; rst <= '0';
           start1 <= '1'; wait until rising_edge(clk); wait for 1 ns; start1 <= '0';  -- c1 asks first
           wait until rising_edge(clk); wait for 1 ns;
           assert grant = "10" report "c1 should own" severity error;
           start0 <= '1'; wait until rising_edge(clk); wait for 1 ns; start0 <= '0';  -- c0 asks mid-burst
           wait until rising_edge(clk); wait until rising_edge(clk); wait for 1 ns;
           assert grant = "10"                                  -- must NOT preempt
               report "burst preempted by higher-priority latecomer" severity error;
           for k in 0 to 3 loop wait until rising_edge(clk); end loop; wait for 1 ns;
           assert grant = "01"                                  -- clean handoff to c0
               report "no clean handoff to c0 after release" severity error;
           report "rg_arbiter self-check complete" severity note;
           wait;
       end process;
   end architecture;

4b. The grant-not-held-mid-burst preemption bug — buggy vs fixed, in all three HDLs

Pattern (b) is the signature failure, shown here as buggy vs fixed RTL in each language and dramatized in §7. The bug is a pulse grant used for a burst: the arbiter drives grant as a pure combinational fixed-priority pick of the current request vector, re-arbitrating every cycle with no lock. Single-cycle transactions pass — one grant, one transfer, done. But when a lower-priority client is mid-burst and a higher-priority client raises its request, the combinational grant flips away that same cycle and the burst is preempted. The fix is the held (locked) grant of 4a: latch ownership on grant and hold it until the owner releases.

rg_preempt.sv — BUGGY (pulse grant, re-arbitrates) vs FIXED (locked until release)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // BUGGY: grant is a PURE COMBINATIONAL fixed-priority pick -- re-arbitrated every
   //        cycle with NO lock. A higher-priority latecomer preempts a live burst.
   module rg_arbiter_bad #(parameter int N = 4) (
       input  logic         clk, rst,                  // clk/rst unused: no state (that IS the bug)
       input  logic [N-1:0] req,
       output logic [N-1:0] grant,
       output logic         busy
   );
       assign grant = req & (~req + 1'b1);              // pulse grant: recomputed every cycle
       assign busy  = |grant;
   endmodule
 
   // FIXED: latch the winner as OWNER and LOCK the grant until the owner releases.
   module rg_arbiter_good #(parameter int N = 4) (
       input  logic         clk, rst,
       input  logic [N-1:0] req,
       output logic [N-1:0] grant,
       output logic         busy
   );
       typedef enum logic {IDLE, HELD} state_t;
       state_t       state;
       logic [N-1:0] owner;
       wire  [N-1:0] pick = req & (~req + 1'b1);
 
       always_ff @(posedge clk)
           if (rst) begin state <= IDLE; owner <= '0; end
           else unique case (state)
               IDLE: if (|req)             begin owner <= pick; state <= HELD; end
               HELD: if ((owner & req)=='0) begin owner <= '0;  state <= IDLE; end   // release
           endcase
 
       assign grant = owner;                            // HELD -> no mid-burst preemption
       assign busy  = (state == HELD);
   endmodule

The testbench proves the split: it starts a low-priority client's multi-cycle burst, raises a higher-priority request mid-burst, and asserts the grant stays on the original owner. The buggy (pulse) arbiter fails — the grant jumps to the higher-priority client mid-burst; the fixed (locked) arbiter passes.

rg_preempt_tb.sv — a higher-priority latecomer must NOT steal a live burst
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module rg_preempt_tb;
       localparam int N = 4;
       logic         clk = 0, rst;
       logic [N-1:0] req, grant;
       logic         busy;
       int           errors = 0;
       always #5 clk = ~clk;
 
       // Bind to rg_arbiter_good to PASS; to rg_arbiter_bad to WATCH the burst get preempted.
       rg_arbiter_good #(.N(N)) dut (.clk(clk), .rst(rst), .req(req), .grant(grant), .busy(busy));
 
       initial begin
           rst = 1; req = '0; @(posedge clk); #1; rst = 0;
           // Low-priority client 2 owns the resource for a multi-cycle burst (nobody else asks).
           req = 4'b0100; @(posedge clk); #1;
           assert (grant == 4'b0100) else begin $error("start: grant=%b exp=0100", grant); errors++; end
           // Mid-burst, higher-priority client 0 raises its request -- client 2 still busy.
           req = 4'b0101; repeat (2) @(posedge clk); #1;
           // The grant must STAY on client 2 for the whole burst -- no preemption.
           assert (grant == 4'b0100)
               else begin $error("PREEMPTED mid-burst: grant=%b exp=0100 (bus corrupted)", grant); errors++; end
           // Client 2 finishes and releases; only now may client 0 win.
           req = 4'b0001; repeat (2) @(posedge clk); #1;
           assert (grant == 4'b0001)
               else begin $error("after release: grant=%b exp=0001", grant); errors++; end
           if (errors == 0) $display("PASS: burst grant held; no mid-burst preemption");
           else             $display("FAIL: %0d errors (grant not held through the burst)", errors);
           $finish;
       end
   endmodule

The Verilog buggy/fixed pair is the same story: the buggy arbiter is a bare combinational req & -req with no state; the fixed one latches owner and holds it until (owner & req) == 0.

rg_preempt.v — BUGGY (pulse, re-arbitrates) vs FIXED (locked) in Verilog
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // BUGGY: pure combinational pulse grant -- no lock, so a burst is preempted.
   module rg_arbiter_bad #(parameter N = 4) (
       input  wire clk, rst,
       input  wire [N-1:0] req,
       output wire [N-1:0] grant,
       output wire         busy
   );
       assign grant = req & (~req + 1'b1);              // recomputed every cycle
       assign busy  = |grant;
   endmodule
 
   // FIXED: latch owner, lock the grant until release.
   module rg_arbiter_good #(parameter N = 4) (
       input  wire clk, rst,
       input  wire [N-1:0] req,
       output wire [N-1:0] grant,
       output wire         busy
   );
       localparam IDLE = 1'b0, HELD = 1'b1;
       reg          state;
       reg  [N-1:0] owner;
       wire [N-1:0] pick = req & (~req + 1'b1);
 
       always @(posedge clk)
           if (rst) begin state <= IDLE; owner <= {N{1'b0}}; end
           else case (state)
               IDLE: if (|req) begin owner <= pick; state <= HELD; end
               HELD: if ((owner & req) == {N{1'b0}}) begin owner <= {N{1'b0}}; state <= IDLE; end
           endcase
 
       assign grant = owner;                            // HELD -> no preemption
       assign busy  = (state == HELD);
   endmodule
rg_preempt_tb.v — self-checking; the pulse arbiter preempts, the locked one does not
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module rg_preempt_tb;
       parameter N = 4;
       reg          clk, rst;
       reg  [N-1:0] req;
       wire [N-1:0] grant;
       wire         busy;
       integer      errors;
 
       // Bind to rg_arbiter_good to PASS; rg_arbiter_bad preempts the burst.
       rg_arbiter_good #(.N(N)) dut (.clk(clk), .rst(rst), .req(req), .grant(grant), .busy(busy));
 
       initial begin clk = 0; forever #5 clk = ~clk; end
 
       initial begin
           errors = 0;
           rst = 1; req = {N{1'b0}}; @(posedge clk); #1; rst = 0;
           req = 4'b0100; @(posedge clk); #1;                 // client 2 starts a burst
           if (grant !== 4'b0100) begin $display("FAIL: start grant=%b", grant); errors=errors+1; end
           req = 4'b0101; repeat (2) @(posedge clk); #1;       // client 0 (higher) asks mid-burst
           if (grant !== 4'b0100) begin                        // must NOT be preempted
               $display("FAIL: PREEMPTED mid-burst grant=%b exp=0100", grant); errors=errors+1;
           end
           req = 4'b0001; repeat (2) @(posedge clk); #1;       // client 2 releases
           if (grant !== 4'b0001) begin
               $display("FAIL: after release grant=%b exp=0001", grant); errors=errors+1;
           end
           if (errors == 0) $display("PASS: burst grant held; no mid-burst preemption");
           else             $display("FAIL: %0d errors", errors);
           $finish;
       end
   endmodule

In VHDL the buggy arbiter is a single concurrent assignment (req and -req) with no process; the fixed one is the clocked FSM that latches owner and holds it until the owner releases.

rg_preempt.vhd — BUGGY (pulse) vs FIXED (locked) in VHDL
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;
 
   -- BUGGY: pure combinational pulse grant -- no lock -> a burst is preempted.
   entity rg_arbiter_bad is
       generic ( N : positive := 4 );
       port ( clk, rst : in std_logic;
              req   : in  std_logic_vector(N-1 downto 0);
              grant : out std_logic_vector(N-1 downto 0);
              busy  : out std_logic );
   end entity;
   architecture rtl of rg_arbiter_bad is
       signal g : std_logic_vector(N-1 downto 0);
   begin
       g     <= req and std_logic_vector(unsigned(not req) + 1);   -- recomputed every cycle
       grant <= g;
       busy  <= '1' when unsigned(g) /= 0 else '0';
   end architecture;
 
   -- FIXED: latch owner, lock the grant until release.
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;
   entity rg_arbiter_good is
       generic ( N : positive := 4 );
       port ( clk, rst : in std_logic;
              req   : in  std_logic_vector(N-1 downto 0);
              grant : out std_logic_vector(N-1 downto 0);
              busy  : out std_logic );
   end entity;
   architecture rtl of rg_arbiter_good is
       type state_t is (IDLE, HELD);
       signal state : state_t := IDLE;
       signal owner : std_logic_vector(N-1 downto 0) := (others => '0');
       signal pick  : std_logic_vector(N-1 downto 0);
   begin
       pick <= req and std_logic_vector(unsigned(not req) + 1);
       process (clk) begin
           if rising_edge(clk) then
               if rst = '1' then state <= IDLE; owner <= (others => '0');
               else
                   case state is
                       when IDLE => if unsigned(req) /= 0 then owner <= pick; state <= HELD; end if;
                       when HELD => if unsigned(owner and req) = 0 then
                                        owner <= (others => '0'); state <= IDLE;    -- release
                                    end if;
                   end case;
               end if;
           end if;
       end process;
       grant <= owner;                                            -- HELD -> no preemption
       busy  <= '1' when state = HELD else '0';
   end architecture;
rg_preempt_tb.vhd — self-checking; the locked arbiter holds the burst (report severity)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;
 
   entity rg_preempt_tb is
   end entity;
 
   architecture sim of rg_preempt_tb is
       constant N   : positive := 4;
       signal clk   : std_logic := '0';
       signal rst   : std_logic;
       signal req   : std_logic_vector(N-1 downto 0);
       signal grant : std_logic_vector(N-1 downto 0);
       signal busy  : std_logic;
   begin
       clk <= not clk after 5 ns;
 
       -- Bind to rg_arbiter_good to PASS; rg_arbiter_bad preempts the burst.
       dut : entity work.rg_arbiter_good generic map (N => N)
             port map (clk => clk, rst => rst, req => req, grant => grant, busy => busy);
 
       stim : process
       begin
           rst <= '1'; req <= (others => '0');
           wait until rising_edge(clk); wait for 1 ns; rst <= '0';
           req <= "0100";                                          -- client 2 starts a burst
           wait until rising_edge(clk); wait for 1 ns;
           assert grant = "0100" report "start: grant /= 0100" severity error;
           req <= "0101";                                          -- client 0 (higher) asks mid-burst
           wait until rising_edge(clk); wait until rising_edge(clk); wait for 1 ns;
           assert grant = "0100"                                   -- must NOT be preempted
               report "burst PREEMPTED mid-transaction (bus corrupted)" severity error;
           req <= "0001";                                          -- client 2 releases
           wait until rising_edge(clk); wait until rising_edge(clk); wait for 1 ns;
           assert grant = "0001" report "after release: grant /= 0001" severity error;
           report "rg_preempt self-check complete" severity note;
           wait;
       end process;
   end architecture;

Across all three languages the lesson is one sentence: a request/grant protocol must define ownership duration — for a multi-cycle transaction the grant is HELD (locked) until the owner releases; re-arbitrating every cycle silently corrupts bursts, and a client that never releases starves the rest.

5. Verification Strategy

Request/grant is an ownership protocol, so verification lives at ownership: connect clients to the arbiter, drive overlapping requests and multi-cycle bursts, and prove the properties that define correct ownership — grant is only ever given to a requester; at most one client owns the resource at a time; a burst owner keeps the grant for the whole transaction; the handoff after release is clean; and no turn is lost when request and grant coincide.

Every cycle, (grant & req) == grant (grant only a requester) and grant is one-hot-or-zero (a single owner). While a client owns the resource and keeps requesting, its grant bit stays high — a higher-priority request arriving mid-burst does not preempt it. When the owner releases (drops req), a waiting client is granted the next cycle (clean handoff), and a client whose req coincides with its grant is not skipped (no lost turn).

Everything below makes that invariant checkable and maps onto the testbenches in §4.

  • Grant-implies-request, every cycle. Assert (grant & req) == grant on every clock — the arbiter must never grant a client that is not asking. This is the same granted-implies-requesting invariant as 10.1, checked continuously: SV assert ((grant & req) == grant), Verilog the if ((grant & req) !== grant) $display("FAIL…") form, VHDL assert (grant and req) = grant severity error. The §4a testbenches check it in an always/process block sampled every cycle, not just at scenario checkpoints.
  • Single owner, every cycle. Assert grant is one-hot-or-zero on every clock — at most one client ever owns the shared resource, so at most one drives it. $onehot0(grant) (SV), (grant & (grant-1)) == 0 (Verilog), a bit-count <= 1 (VHDL). Two owners is the same catastrophe as the double grant of 10.1 — two masters on the bus — so this invariant is non-negotiable and is checked alongside grant-implies-request.
  • Burst grant held through the transaction (the central property). Drive a low-priority client into a multi-cycle burst, then raise a higher-priority request mid-burst, and assert the grant does not move off the original owner until it releases. This is the property a pulse-grant arbiter fails and a locked-grant arbiter passes — the §4b test is exactly this scenario, and it is the single check that separates a correct burst protocol from a corrupting one. Single-cycle transactions pass under both designs, so this multi-cycle-under-contention case must be a named test, not an afterthought.
  • Clean handoff after release. After the owner drops req, assert a waiting client is granted the next cycle — no dead cycle where the resource is idle while a client waits, and no overlap where the old and new owner both hold it. The §4a testbench checks that after client 1 releases, client 0 is granted; extend it to confirm the grant is asserted on exactly the cycle after release, not two cycles later (a lazy re-arbitration wastes a cycle of bandwidth).
  • No lost turn on coincident request/grant. Check the boundary where a client's req and its grant rise together: the client must take ownership (enter OWNED), not treat the grant as belonging to nobody. A client that de-asserts req the same cycle it is granted (the §6 / §7 race) creates a turn owned by no one — assert that whenever grant[i] is high, client i was requesting and does become the owner, so the granted cycle is never wasted.
  • The never-releases / fairness observation (a directed scenario, not a pass/fail). Drive one client to hold req forever after being granted, and observe that no other client ever gets the resource — the grant is legitimately held, one-owner is not violated, but everyone else starves. This is not an assertion failure; it is the liveness gap the protocol cannot close alone, and exercising it is how a learner sees why fairness (round-robin, 10.2) and forced release / timeouts (arbiter fairness, 10.5) exist. If a client must not be locked out, this scenario is the evidence the design needs a fairness policy, not just a correct lock.
  • Expected waveform. On a correct run, a client's req rises and stays high (stable) until its grant bit rises; ownership begins on that overlap; the grant bit then stays high for the whole burst while a higher-priority req may pulse underneath it without stealing the grant; and grant drops the cycle after the owner drops req, with the next owner's grant rising immediately. The visual signature of the §7 bug is a grant bit that jumps from the burst owner to a higher-priority latecomer mid-burst; of a lost turn, a grant pulse on a cycle no client enters OWNED; of a hog, a single grant bit high forever while others' req stay high and un-granted.

6. Common Mistakes

Request dropped the same cycle as grant (the coincident-drop race). A client that de-asserts req on the exact cycle the arbiter asserts grant — because its internal 'I still want it' logic and the incoming grant are both combinational and settle in the wrong order, or because it treats grant as 'already done' — creates a turn that belongs to no one. The arbiter granted the resource, but the client walked away the same cycle, so the ownership cycle is wasted and, worse, the client may never actually perform its transaction (it thinks it is done; the arbiter thinks it was served). The rule: hold req asserted until you have observed grant and taken ownership — enter OWNED on req && grant, and only then begin counting down toward release. Never drop req on the granting cycle itself.

Grant not held during a burst (mid-burst preemption). The signature failure. Using a pulse grant — a purely combinational fixed-priority pick re-arbitrated every cycle — for a multi-cycle transaction. It passes every single-cycle test, then, the instant a higher-priority client requests during a lower-priority client's burst, the grant flips away mid-transaction: the shared resource (a bus, a memory word being written across several cycles) ends up holding a mix of two masters' data — silent corruption. The rule: for a multi-cycle transaction the grant is a level held (locked) on the owner until it releases — the arbiter must have state (a lock bit / owner register) that freezes arbitration for the duration. Re-arbitrate every cycle only when every transaction is exactly one cycle. (Post-mortem in §7.)

A client that never releases (the hog / liveness bug). A client that, once granted, holds req forever — because its release logic is missing, its transaction never terminates, or it simply keeps finding new work — legitimately keeps the grant locked, so one-owner is never violated and no assertion fires. But every other client starves: they request and wait indefinitely. This is a fairness/liveness failure, not a safety one, and the request/grant protocol alone cannot prevent it. The rule: ownership must be bounded — either the client is trusted to release promptly, or the arbiter enforces a maximum-hold timeout that forces a release, or a fair policy (round-robin, 10.2) rotates priority so no client is locked out. Recognizing that a correct lock still permits a hog is exactly what motivates arbiter fairness (10.5).

A combinational request-to-grant-to-request loop. If the arbiter's grant is a combinational function of req (a pulse grant), and a client makes its req a combinational function of grant (say it drops req the instant it sees grant, combinationally), the two form a loop: grant depends on req, req depends on grant, and the values chase each other with no register to break the cycle — an oscillation or an X in simulation, a timing loop in synthesis. This is the request/grant version of the valid/ready deadlock (9.1). The rule: break the loop with a register — the client's req must come from its own registered state (its FSM), not combinationally from grant, exactly as valid must come from local state and never combinationally from ready.

Treating grant as ownership without checking it is still yours. A client that assumes it owns the resource for a fixed number of cycles after a single grant — without re-checking that its grant bit is still high — will drive the bus even after a (buggy or preemptive) arbiter has taken ownership away. Ownership is defined by the grant being held; a well-behaved client drives the resource only while it actually owns it. In a correct locked-grant design the grant stays high for the whole burst, so this is safe, but a client that ignores grant entirely after the first cycle cannot detect a protocol violation and will collide on a preempting arbiter. Drive the resource on owning (grant held), not on a private countdown that ignores grant.

Forgetting to define ownership duration at all. The root cause behind most of the above: shipping a request/grant interface without deciding, up front, whether a grant is a single pulse or a level held until release. An undefined duration is resolved by accident — whatever the arbiter does next cycle — and for a burst that accident is preemption. Every request/grant interface must document its grant semantics: pulse (one-cycle, re-request each cycle) or level (held until the client releases), and every client must obey the same contract. The protocol is not complete until the duration is specified.

7. DebugLab

The burst that got stolen — a grant that was not held through a multi-cycle transaction

The engineering lesson: a request/grant protocol must define how long a grant lasts, and a grant that is not held through a multi-cycle transaction will be stolen the instant a higher-priority client asks — corrupting the burst. The tell is exactly like the double-grant of 10.1: memory or bus corruption that appears only under contention and only for multi-cycle transactions, and vanishes the moment you test one requester, or one cycle, at a time. Single-cycle tests can never see a mid-burst preemption, because it takes a live multi-cycle owner and a competing higher-priority request to expose a grant that re-arbitrates underneath a transaction. Two habits make it impossible, and they are identical across SystemVerilog, Verilog, and VHDL: define ownership duration up front — a level grant held (locked) until release for bursts, a pulse only when every transaction is one cycle — and give the arbiter the state to enforce it — a lock bit or owner register that freezes arbitration for the transaction's span, released only when the owner drops req. And remember the limit even a correct lock carries: a client that never releases hogs the resource and starves the rest — one-owner is preserved, but liveness is not — which is exactly why fair arbitration and forced-release timeouts exist.

8. Interview Q&A

9. Exercises

Design and reasoning exercises — no syntax drills. Each rehearses the ownership-lifecycle / level-vs-pulse / hold-until-release reasoning behind request/grant.

Exercise 1 — Trace the ownership lifecycle

Two clients share a resource through a locked-grant arbiter (client 0 higher priority). Client 1 starts a 4-cycle burst at cycle 0; client 0 raises its request at cycle 2 (mid-burst). Hand-trace req[0], req[1], grant, and the owning client for cycles 0 through 8. Mark the exact cycle ownership begins for each client, confirm the grant stays on client 1 for all four burst cycles despite client 0 requesting, and state in one line why client 0 is not served until after client 1 releases.

Exercise 2 — Level vs pulse, and when each corrupts

For each shared resource, state whether a pulse grant (re-arbitrate every cycle) or a level grant (held until release) is correct, and defend it in one line: (a) a single-cycle register-file write port; (b) an 8-beat DMA burst into an SRAM; (c) a read-modify-write that holds a lock across three cycles; (d) a combinational read of a shared ROM that completes in the same cycle. Then, for the case(s) where a pulse grant would corrupt the transaction, describe exactly what lands in the shared resource when a higher-priority client preempts mid-transaction.

Exercise 3 — Find the lost turn and the loop

An engineer writes a client that drops req on the same cycle it sees grant (thinking the grant means the transaction is already done), and an arbiter whose grant is a pure combinational pulse. Describe two distinct failures: (i) the lost turn — show how a granted cycle ends up owned by no client, and why the client may never perform its transaction; and (ii) the combinational loop — show how grant depending on req while req depends on grant chases itself with no register to break it. Give the one-line fix for each (hold req until ownership is taken; source req from registered state).

Exercise 4 — Bound the hog

A locked-grant arbiter is correct — one owner, grant held, clean handoff — yet one client, once granted, never drops req and starves the other three. Explain (i) why no safety assertion (grant-implies-req, single owner) ever fires, so this is a liveness bug not a safety bug; (ii) three mechanisms that bound ownership (trusted prompt release, a maximum-hold timeout that forces release, and a fair policy that rotates priority); (iii) what the timeout mechanism must do on the cycle it fires (force grant off the hog and re-arbitrate) and one risk it introduces (truncating a legitimate long transaction); and (iv) which later pattern (round-robin, 10.2, or arbiter fairness, 10.5) you would reach for and why.

Continue in RTL Design Patterns (sibling and forward links unlock as they ship):

  • Fixed-Priority Arbiters — Chapter 10.1; the direct prerequisite — the arbiter core that decides the winner and produces the one-hot grant this protocol governs. The held-grant FSM there is the seed of the locked ownership here.
  • Round-Robin Arbiter — Chapter 10.2; the fair policy that answers the never-releases / starvation gap this page ends on — rotating priority so no requesting client is locked out.
  • Weighted / LRU Arbiter — Chapter 10.3; arbitration that bounds worst-case ownership latency while still favouring some clients — the middle ground between fixed priority and pure round-robin.
  • Arbiter Fairness — Chapter 10.5; the formal notion of fairness, bounded waiting, and forced-release / timeout mechanisms that bound a hog the request/grant protocol alone cannot. (unlocks as it ships.)
  • Backpressure & Stall — Chapter 9.2; the flow-control view of the same held-until-served idea — a low ready stalls a producer just as a withheld grant stalls a requester.

Backward / method:

  • The valid/ready Handshake — Chapter 9.1; the direct cousin — request/grant is valid/ready wearing an ownership badge: req is valid held until accepted, grant is ready that also confers exclusive ownership for the transaction.
  • FSM Fundamentals — Chapter 4.1; the IDLE/REQUEST/OWNED/RELEASE client machine and the IDLE/HELD arbiter lock are built from this — where the protocol acquires the state that holds ownership across cycles.
  • FSM Coding Styles — Chapter 4; the one-/two-/three-process styles the client and arbiter FSMs follow, and the latch-free next-state discipline they depend on.
  • The RTL Design Mindset — Chapter 0.2; the Interface and State lenses this page applies — request/grant is the ownership interface, and the lock bit is the state that governs its duration.
  • What Is an RTL Design Pattern? — Chapter 0.1; why request/grant earns the name — a recurring problem (governing a shared resource across a transaction), a reusable form, and a signature failure (mid-burst preemption).

Verilog v1 prerequisites (the grammar this pattern transcribes into):

  • Bitwise Operators — the AND / NOT behind the fixed-priority pick req & (~req + 1) and the owner & req release condition.
  • Case Statements — the construct behind the IDLE/HELD arbiter decode and the client's IDLE/REQUEST/OWNED/RELEASE state machine.
  • If-Else Statements — the reset / capture / hold / release priority the arbiter and client FSMs transcribe into.
  • Blocking and Non-Blocking Assignments — why the state and owner registers update with non-blocking (<=) so ownership advances atomically on the clock edge, while the combinational pick uses blocking.

11. Summary

  • An arbiter decides who wins; request/grant is the protocol that gives the winner ownership. Fixed priority (10.1) picks a per-cycle winner, but a real transaction needs to ask for the resource, hold it for the whole transaction, and release it. The request/grant handshake supplies that: a client asserts req while it wants the resource and holds it until granted, the arbiter asserts grant to the winner, ownership begins on the cycle req && grant, the client uses the resource and then releases by dropping req, and exactly one client owns the resource at a time.
  • Ownership is a four-state lifecycle. IDLE (no work, req low) to REQUEST (asks and holds req stable until granted, like valid in 9.1) to OWNED (drives the resource on req && grant, holding req through the burst) to RELEASE (drops req, freeing the resource). The arbiter mirrors it: IDLE arbitrates and latches the winner as owner, HELD locks the grant on that owner and returns to IDLE only when the owner releases.
  • Level grant for bursts, pulse grant for single-cycle transactions. A level (held, locked) grant keeps ownership on the winner for the whole multi-cycle transaction so no one preempts it — this needs state (an owner register / lock bit). A pulse (one-cycle, re-arbitrated) grant is correct only when every transaction is a single cycle. The whole difference between a correct burst and a corrupted one is which you choose, and a request/grant interface is incomplete until it defines ownership duration.
  • Request/grant is valid/ready plus ownership. req is valid held until accepted, grant is ready — but grant also confers exclusive ownership that persists for the transaction. Same overlap-is-the-transfer idea, same asymmetry (drive req from local registered state, never combinationally from grant, or you get a request-grant-request loop), plus a lifecycle and a duration that valid/ready never needed.
  • The signature bug is mid-burst preemption; the residual limit is the hog. A grant not held through a burst is stolen the instant a higher-priority client asks, interleaving two masters' data on the shared resource — invisible in single-cycle tests, fatal for a burst under contention; the fix is a locked grant held until release. And even a correct lock permits a client that never releases to hog the resource and starve the rest — a liveness gap the protocol cannot close, which is exactly why fair arbitration (round-robin, 10.2) and forced-release timeouts (arbiter fairness, 10.5) exist. Built and self-check-verified here in SystemVerilog, Verilog, and VHDL.