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.
| question | answered by | |
|---|---|---|
| A. eligibility | which masters have a valid pending request? | the requesters, and how the system wires them |
| B. selection | which eligible master does the policy choose? | the arbitration policy |
| C. retention | how 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.
// ─────────────────────────────────────────────────────────────────────────
// 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
endmoduleReading it
Fixed priority is eight lines and it has no memory.
for (k = 0; k < NREQ; k = k + 1) begin
if (!fixed_hit && req_i[k]) begin
fixed_sel = IW'(k);
fixed_hit = 1'b1;
endThe 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 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.
=== 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: 0Reading 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.
=== 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 2Reading 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 starvation — Chapter 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 fault — Chapter 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:
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.
=== 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
Related tutorials
- Related topic
Shared Resources
Two initiators wired to one target is not a wiring problem with a wiring solution. A single-port target has one address input and one completion output, so access must be serialised — and the rule that matters most is not who goes first but that ownership cannot change while a transaction is in flight.
- Related topic
Arbitration Cost
A master waiting for the bus and a master waiting for a slave look identical and have different fixes. Total handover grows with burst length while per-beat handover falls.
- Related topic
Arbitration Failures
Contention, starvation and corruption are not synonyms. Ninety-nine terminations delivered to the master that did not own the bus, zero specification violations, and a starvation hypothesis the experiment refuted.
- Related topic
Advanced Questions
Four acknowledged register operations that never reached the register bank, one scoreboard symptom produced twice, and the five ownership questions a debugger must answer separately.
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.
