Skip to content
VLSI Mentor

Wishbone · Module 17

Fixed Priority

The simplest arbitration policy, measured: what it decides, when it decides, and why a higher-priority request arriving mid-transfer must wait. Neither policy in this module is required by Wishbone.

Chapter 16.4 ended with a question it could only pose. Every ownership policy in Module 16 was chosen for being the smallest thing that runs, and every one of them had a measurable consequence.

When ownership becomes available, who gets it next?

1. The Arbitration Event

Module 16 established three things that are not the same event. A local request, ownership of the shared path, a Wishbone transfer. Module 17 splits the middle one further, because "who owns it" and "who gets it next" are different questions with different answers.

Three questions, and only the second is this module's subject.

questionanswered by
A. eligibilitywhich masters have a valid pending request?the requesters, and how the system wires them
B. selectionwhich eligible master does the policy choose?the arbitration policy
C. retentionhow long does the chosen master keep it?the ownership mechanism — Chapter 16.3

Question C was answered in Module 16 and is not reopened here. This system retains an owner while that owner's CYC_O is asserted, and releases it the clock after CYC_O negates. That rule defines when question B is even asked.

An arbitration event is a clock on which ownership can change, and in this system there are exactly two kinds:

  • nobody owns the path, or
  • the current owner has negated CYC_O.

On every other clock the owner is retained and the policy's output is ignored entirely. Section 5 measures what happens when that guard is removed, and the answer is not "the high-priority master wins sooner".

Eligibility in this system is each master's own CYC_O. That is a LOCAL ARCHITECTURE CHOICE, and the specification describes it for this case rather than requiring it — the [CYC_O] description says "In these cases, the [CYC_O] signal requests use of a common bus from an arbiter", and RECOMMENDATION 3.05 says only that arbitration logic "often" uses CYC_I to select between masters. There is no REQ/GNT pair anywhere in B3.

2. RTL — Selection, On Its Own

The module below answers question B and nothing else. It does not know what a Wishbone transfer is, it cannot see ACK_I, it holds no ownership, and it cannot release anything. That separation is the design decision the whole module rests on, and Chapter 17.5 is where it pays off.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// wb_arb_policy — SELECTION, and nothing else.
//
// This module answers exactly one question: given a vector of eligible
// requesters, which one should be chosen next? It does not know what a
// Wishbone transfer is, it cannot see ACK_I, it holds no ownership, and it
// cannot release anything. That separation is the point of Chapter 17.5:
// a selection policy and an ownership mechanism are different machines,
// and mixing them is how active transfers get preempted.
//
// NEITHER POLICY IS REQUIRED BY WISHBONE. B3 mentions both exactly once,
// in the same parenthesis, in a sentence that hands the question away:
// "Arbitration methodology is defined by the end user (priority arbiter,
// round-robin arbiter, etc.)." The words "fair", "fairness", "starvation"
// and "starve" do not appear in the specification at all.
//
//   POLICY = 0  FIXED PRIORITY. The lowest eligible index wins. Index 0 is
//               the CPU in this system, so the policy is CPU > DMA. That
//               ordering is a LOCAL ARCHITECTURE CHOICE.
//
//   POLICY = 1  ROUND ROBIN. Scan cyclically starting one position past
//               the last committed grant and take the first eligible
//               requester found.
//
// ── THE HISTORY RULE, STATED ONCE AND TESTED ────────────────────────────
// last_q updates on commit_i, which the ownership mechanism asserts on the
// clock a grant is actually taken. It updates on EVERY committed grant,
// contested or not.
//
// That is a choice. "Update only on contested grants" is an equally
// defensible convention and produces a different service order after a
// sequence of uncontested tenures. Chapter 17.2 measures this one, because
// a pointer convention that is not written down is a pointer convention
// that will be re-derived incorrectly by the next person.
//
// ── THE TWO DEFECTS ─────────────────────────────────────────────────────
// Both are OFF by default and each turns this module into a targeted
// broken policy for one experiment.
//
//   PTR_ON_REQUEST      history advances when a request APPEARS instead of
//                       when a grant is taken.
//   IGNORE_ELIGIBILITY  the scan is skipped and the position one past the
//                       history is selected whether or not it is asking.
// ─────────────────────────────────────────────────────────────────────────
module wb_arb_policy #(
  parameter int unsigned NREQ               = 2,
  parameter bit          POLICY             = 1'b0,   // 0 fixed, 1 RR
  parameter bit          PTR_ON_REQUEST     = 1'b0,
  parameter bit          IGNORE_ELIGIBILITY = 1'b0,
  parameter int unsigned IW                 = (NREQ <= 2) ? 1 : $clog2(NREQ)
) (
  input  logic            clk_i,
  input  logic            rst_i,
  // eligibility: one bit per requester, asserted while it wants the resource
  input  logic [NREQ-1:0] req_i,
  // asserted by the ownership mechanism on the clock a grant is taken
  input  logic            commit_i,
  output logic [IW-1:0]   sel_o,
  output logic            valid_o,
  output logic [IW-1:0]   last_o
);
  logic [IW-1:0] last_q;
  assign last_o = last_q;

  // ── SELECTION ──
  // Combinational and stateless apart from last_q. Nothing here depends on
  // a transfer being in progress, because nothing here is entitled to know.
  logic [IW-1:0] fixed_sel, rr_sel;
  logic          fixed_hit, rr_hit;
  int unsigned   k, cand;

  always_comb begin
    fixed_sel = '0;
    fixed_hit = 1'b0;
    for (k = 0; k < NREQ; k = k + 1) begin
      if (!fixed_hit && req_i[k]) begin
        fixed_sel = IW'(k);
        fixed_hit = 1'b1;
      end
    end
  end

  always_comb begin
    rr_sel = '0;
    rr_hit = 1'b0;
    // start one position past the last committed grant and wrap
    for (k = 1; k <= NREQ; k = k + 1) begin
      cand = (last_q + k) % NREQ;
      if (!rr_hit && req_i[cand]) begin
        rr_sel = IW'(cand);
        rr_hit = 1'b1;
      end
    end
  end

  always_comb begin
    if (IGNORE_ELIGIBILITY) begin
      // DEFECT: blind rotation. The position is advanced and handed out
      // whether or not that requester is asking, so a grant can be given
      // to a master that will do nothing with it.
      sel_o   = IW'((last_q + 1) % NREQ);
      valid_o = |req_i;
    end else if (POLICY) begin
      sel_o   = rr_sel;
      valid_o = rr_hit;
    end else begin
      sel_o   = fixed_sel;
      valid_o = fixed_hit;
    end
  end

  // ── HISTORY ──
  logic [NREQ-1:0] req_q;
  logic            any_new_req;
  assign any_new_req = |(req_i & ~req_q);

  always_ff @(posedge clk_i) begin
    if (rst_i) begin
      last_q <= '0;
      req_q  <= '0;
    end else begin
      req_q <= req_i;
      if (PTR_ON_REQUEST) begin
        // DEFECT: the position moves when a request arrives. A requester
        // that merely asks can therefore push the preference past itself,
        // and a burst of arrivals rotates the pointer with no grant taken.
        if (any_new_req) last_q <= IW'((last_q + 1) % NREQ);
      end else begin
        if (commit_i && valid_o) last_q <= sel_o;
      end
    end
  end
endmodule

Reading it

Fixed priority is eight lines and it has no memory.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    for (k = 0; k < NREQ; k = k + 1) begin
      if (!fixed_hit && req_i[k]) begin
        fixed_sel = IW'(k);
        fixed_hit = 1'b1;
      end

The lowest eligible index wins. Index 0 is the CPU in this system, 1 is the DMA, 2 is an IO engine. That ordering is a choice, not a fact about CPUs, and a system whose deadline belongs to the IO engine would order it differently.

What is not in that loop is as important as what is. There is no reference to how long anyone has waited, to what was chosen last, to whether a transfer is in progress, or to how many times a requester has lost. The policy is a pure function of the eligibility vector — the same input produces the same output forever, which is exactly what makes it deterministic and exactly what makes Chapter 17.4 possible.

valid_o is the other half of the contract. When nobody is eligible the policy says so and the ownership mechanism drops to NONE. A selection module that always names a winner would hand the bus to a master that is not asking, and Section 6 of Chapter 17.2 measures that defect.

commit_i and last_q belong to round robin and are inert here — fixed priority never reads the history. They are in the same module because the two policies are alternatives for one job, and Chapter 17.2 turns the parameter over.

The ownership state machine, and where the policy is consulted:

The ownership state machine with four states. NONE means nobody owns the shared path. CPU, DMA and IO are the three possible owners. From NONE, an arbitration event consults the policy and moves to whichever requester it selects. From each owner state, the machine returns to NONE when that owner negates its cycle signal and nobody else is eligible, or moves directly to another owner when the policy selects one at that same event. While an owner's cycle signal stays asserted there is no transition at all: the policy is not consulted and the owner is retained.NONECPU ownsDMA ownsIO ownsevent: policy picks 0event: policy picks 0event: policy picks 1event: policy picks 1event: policy picks 2event: policy picks 2CYC_O negatedCYC_O negatedCYC_O negatedCYC_O negatedCYC_O negatedCYC_O negated

The transitions that are missing are the lesson. There is no arrow leaving an owner state while that owner still asserts CYC_O. No request, of any priority, produces one — and Section 5 measures a machine that has those arrows.

3. Simulation — SIM A: One Requester at a Time

Eligibility before selection. Each requester asks on its own, and the log records every arbitration event that took a grant.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  === SIM A - fixed priority, one requester at a time ===
    LOCAL ARBITRATION POLICY: lowest index wins.
    index 0 = CPU, 1 = DMA, 2 = IO. That ordering is a choice.

    every arbitration event that took a grant
      clk  eligible  prev owner  chosen
        4    001       -           CPU
       11    100       -           IO
       18    001       -           CPU
       23    001       -           CPU
       28    001       -           CPU
       33    001       -           CPU
       35    010       CPU         DMA
       40    010       -           DMA
      eligible column is {IO,DMA,CPU}

      arbitration events 31   contested 0
      grants   CPU 5   DMA 2   IO 1
      a grant went to a requester that was not asking: 0
      selection disagreed with the reference model:    0

Reading it

Read the eligible column, which is {IO, DMA, CPU}.

001 chooses CPU. 100 chooses IO. 010 chooses DMA. Three different winners, and in every case the winner is the only requester asking. That is not priority working — it is eligibility working, and separating the two is why this simulation exists.

Contested events: 0. Not one of the 31 arbitration events in this run had more than one eligible requester, so fixed priority made no decision at all in the whole simulation. It selected, 8 times, from a set of size one.

That matters for a reason that catches people out in review. A trace in which a policy always picks the "right" master is not evidence that the policy is right. It may be evidence that the policy was never asked. The contested count is what tells you which.

The two audit lines underneath are the ones a reviewer should look at first.

A grant went to a requester that was not asking: 0. A grant must imply eligibility, and Chapter 17.2 builds a policy that violates it.

Selection disagreed with the reference model: 0. An independent model in wb_arb_probe recomputes the expected winner from the eligibility vector and compares. It is not wired to the arbiter's own signals, so an arbiter that decides correctly for the wrong reason is still caught.

4. Simulation — SIM B: Two Eligible at One Event

Now a decision. The CPU's request is timed to rise on the clock the DMA starts a phase, so both assert CYC_O together on an idle bus.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  === SIM B - fixed priority, two eligible at one event ===
    arbitration events, CPU request timed to contend
      clk  eligible  prev owner  chosen
       18    001       -           CPU
       20    010       CPU         DMA
       25    011       -           CPU
       29    010       CPU         DMA
       34    010       -           DMA
       39    010       -           DMA
      eligible column is {IO,DMA,CPU}

      contested events 1
      DMA: opportunities while eligible 5, lost 1,
           longest run of losing 1
      both operations completed: CPU read 0xaaaa0007, DMA words 2

Reading it

Clock 25: eligible 011, previous owner -, chosen CPU.

That is the whole of fixed priority. Two requesters eligible, the lower index wins, and the decision took no state and consulted no history.

The DMA then waited exactly one opportunity and was served at clock 29. Opportunities while eligible 5, lost 1, longest run of losing 1. Losing once is not starvationChapter 17.4 is precise about the difference, and it needs a workload this one does not have.

Both operations completed. The CPU read 0xAAAA0007; the DMA copied its two words. Contention is a delay, not a faultChapter 16.2 measured that and it has not changed.

One structural detail from the event log deserves a sentence, because Chapter 17.2 turns on it. Look at clocks 20, 29, 34 and 39: eligible 010, one requester. Only clock 25 is contested. An event caused by a release is almost never contested, because the master that caused it is by definition no longer asking — and with only two active requesters that leaves exactly one candidate.

5. Fixed Priority Is Not Preemption

Here is the mistake this chapter exists to prevent.

The DMA owns the shared path. The slave is inserting wait states, so the phase is presented and unanswered. The CPU — the higher-priority requester — asserts CYC_O.

A priority encoder answers "CPU" immediately. Wire its output to the owner mux and the owner changes on the next clock, mid-transfer, with the slave still counting. That is not fixed-priority arbitration. It is Chapter 16.3's preemption defect wearing an arbitration label, and it is what "high priority means it can take the bus" turns into when somebody builds it.

The guard against it is one term, and it is in wb_owner_arb3 rather than in the policy:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  assign arb_event = PREEMPT ? 1'b1
                             : ((owner_q == OWN_NONE) || !cur_cyc);

With PREEMPT cleared, the policy's output is simply not read on any clock where the owner still asserts CYC_O. The policy may be screaming "CPU" — nothing is listening.

And the reason this is not merely untidy is RULE 3.25:

[CYC_O] MUST be asserted no later than the rising [CLK_I] edge that qualifies the assertion of [STB_O]. [CYC_O] MUST be negated no earlier than the rising [CLK_I] edge that qualifies the negation of [STB_O].

A master's cycle is a defined extent, and ownership must cover it. Taking the path away in the middle leaves a phase presented by a master that no longer reaches the slave, and a slave about to answer a transfer whose originator has been disconnected. Neither party did anything wrong.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  === SIM C - the higher-priority request that must wait ===
    the shared RAM inserts 2 wait states. The CPU's request is
    timed to arrive while the DMA holds an unanswered phase.

    rig        owner changed mid-phase  context split  CPU read
    correct              0                   0        0xaaaa000c
    PREEMPT              1                   0        0xaaaa000c

    arbitration events:  correct 28   PREEMPT 47
    DMA words copied:    correct 2   PREEMPT 2
    -> fixed priority selects at arbitration events. It is not
       a licence to displace an owner mid-transfer.

Reading it

Two rigs, one stimulus, one term of difference.

The correct arbiter changed owner mid-phase 0 times. The PREEMPT arbiter did it once, and once is enough: an owner was displaced while a slave was still counting out its wait states.

Look at the arbitration-event counts: 28 against 47. The broken rig held 19 more arbitration events over the same workload, because it holds one on every clock. It is not arbitrating more carefully; it is arbitrating when it has no business doing so.

And now the uncomfortable line: both rigs read 0xAAAA000C, and both copied 2 DMA words. The preempting arbiter corrupted nothing in this run.

That is the honest case for structural checking, and it is the same finding Chapter 16.3 reported for the same defect. Whether a mid-phase handover damages anything depends on the slave, the wait-state profile and which master's phase is dropped. A functional test saw a working system. The structural checker named the clock. Shipping on the strength of the first is how this defect reaches silicon.

6. Common Mistakes

"Fixed priority means the high-priority master can preempt."

Why it is wrong: priority decides which eligible requester is selected at an arbitration event. It says nothing about when an event occurs. Preemption is a property of the ownership mechanism, and giving it to a policy produces the rig above.

"The CPU wins because CPUs have priority."

Why it is wrong: the CPU wins because it is index 0 in this system's ordering, and that ordering was chosen. Nothing in Wishbone ranks masters. A system whose real-time deadline belongs to a display engine puts the display engine at index 0.

"A trace where the right master always wins proves the policy is right."

Why it is wrong: SIM A had 31 arbitration events and zero contested ones. The policy chose from a set of size one, eight times. Count the contested events before believing anything about selection.

"ACK tells the arbiter who should win next."

Why it is wrong: ACK terminates a transfer. In this system it does not even reach the arbiter — the policy has no ACK input. What ends a tenure here is CYC_O negating, which is a different signal at a different time, and the BLOCK section describes exactly that arbiter: "an arbiter for that memory can determine when one MASTER is done with it so that another can gain access."

"Wishbone requires fixed priority."

Why it is wrong: Wishbone requires no arbitration policy at all. The word "priority" occurs once in the specification, inside "(priority arbiter, round-robin arbiter, etc.)". The "etc." is doing as much work as the two named policies.

"If both requesters are pending, both have Wishbone transfers active."

Why it is wrong: Chapter 16.2 separated a pending local request from a presented transfer, and that separation still holds. The shared path carries one transfer. Two eligible requesters is a fact about an arbitration event, not about the bus.

7. Interview Reasoning

"What is arbitration actually choosing?"

Which eligible requester becomes the next owner of a shared resource, at the moment ownership becomes available. It is not choosing when that moment occurs, how long the winner keeps it, or what the winner does with it. Those are the ownership mechanism's job.

"What is the difference between a grant and ownership?"

A grant is a decision; ownership is a state. In this design the grant is combinational output from the policy and the ownership is a register written under a guard. The grant is ignored on most clocks — that is what the guard is for.

"Why must fixed priority not preempt an active Wishbone transfer?"

Because a cycle is a defined extent — RULE 3.25 — and ownership has to cover it. Taking the path away mid-phase disconnects a master from a slave that is about to answer it. Neither of them can detect this, which is why the interconnect must not do it.

"How would you review an arbiter you did not write?"

Four questions, in order. Where is the arbitration event defined, and can it fire while an owner holds an unanswered phase? Does a grant imply the winner was eligible? Does the selection match the policy the datasheet claims? And how many contested events did your test actually produce — because a policy that was never asked to decide has not been tested.

8. Understanding Check

In SIM A the arbiter selected 8 times and made 0 decisions. Explain.

Every event had exactly one eligible requester. Selection ran; there was nothing to select between. Contested events are the only ones that exercise the policy.

The PREEMPT rig produced correct data. Is it acceptable?

No. It changed owner inside an unanswered phase, which is unsound regardless of whether this particular trace was damaged. The correctness of the data was a property of the stimulus, not of the design.

Your arbiter's policy output is wired straight to the owner mux with no register and no guard. What have you built?

A preemption defect. The policy is a pure function of the request lines, so the owner follows the request lines, so any arrival changes the owner. The bug is not in the policy — it is in the absence of the event guard.

Where would you put a requester whose deadline is hard, under fixed priority?

At the lowest index — and then ask what the other requesters lose, because fixed priority has no mechanism for giving anything back. Chapter 17.4 measures exactly what that costs the requester at the other end.

9. What's Next

Fixed priority decides with no state at all, and that is both its virtue and its whole problem.

How can selection remember who was served last?

Chapter 17.2 — Round Robin adds history, and it starts with a measurement that explains why this module has three requesters rather than two: under sustained demand from two masters, the arbiter recorded zero contested events, and neither policy could have made any difference at all.

Continue learning

Standards & specifications

Governing standard
Wishbone SoC Interconnection Architecture (OpenCores)(opens OpenCores in a new tab)

Defines the Wishbone signal set, the bus cycles built from it and the interface rules a portable IP core must follow. It deliberately leaves interconnect topology, address map and arbitration policy to the integrator, so those are system decisions rather than requirements of the specification.

This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.

Where this fits

Part of the Wishbone curriculum.