A CPU line fill that waits behind an SBR stream is the first QoS problem NanoSoC meets, and the instinct is to reach for priority. Fixed priority, weighted shares, token buckets and aging are taught in the protocol course (see the links below); the applied question here is which of them changes the number that matters, the CPU's worst wait on a shared bank, and by how much.
Bursts cannot be pre-empted, so whichever scheme is used, a CPU request that arrives just after an SBR burst has started waits for that burst to finish: 16 beats plus the bubble, 17 cycles, 177.1 ns. Priority and regulators change who goes next, not how long the current burst runs. Fixed priority to the SBR removes the bound completely (the CPU waits for ever while the SBR always has a request). Round-robin bounds the wait at one SBR burst. A token bucket on the SBR (a regulator that lets a burst start only when the bucket holds a full burst of tokens, filled at the reserved rate) restores the CPU's guaranteed service even if the SBR has fixed priority, and caps the SBR at its share. The lever that shortens the bound itself is the SBR's burst length: 4-beat SBR bursts cut the CPU's worst wait to 5 cycles, 52.1 ns, at the price of 307.2 instead of 361.4 MB/s on the SBR stream.
# not-run: which lever changes what, NanoSoC teaching values
scheme CPU worst wait SBR rate
SBR fixed priority unbounded (starved) 361.4 MB/s
round-robin (NanoSoC) 17 cycles = 177.1 ns shared
bucket on the SBR (proposed) 17 cycles = 177.1 ns capped at its share
round-robin, SBR 4-beat chunks 5 cycles = 52.1 ns 307.2 MB/s# NanoSoC teaching values: one SRAM bank shared by CPU 4-beat fills and the SBR.
# Each burst costs beats + 1 bubble and cannot be pre-empted. The CPU asks every 50 cycles
# (illustrative); the SBR always has more to move.
CPU_BEATS, SBR_BEATS = 4, 16
CPU_PERIOD = 50
HORIZON = 5000
def simulate(policy, sbr_burst=SBR_BEATS, fill=0.75):
depth = sbr_burst
tokens = float(depth)
busy_until = 0
cpu_wait_start = None
cpu_waits = []
sbr_beats = 0
last = "cpu"
for t in range(HORIZON):
tokens = min(depth, tokens + fill)
if t % CPU_PERIOD == 0 and cpu_wait_start is None:
cpu_wait_start = t
if t < busy_until:
continue
cpu_req = cpu_wait_start is not None
sbr_req = True
if policy == "bucket" and tokens < sbr_burst:
sbr_req = False # the regulator holds the SBR back
if cpu_req and sbr_req:
if policy == "round_robin":
pick = "sbr" if last == "cpu" else "cpu"
else:
pick = "sbr" # fixed priority to the SBR, with or without the bucket
elif cpu_req:
pick = "cpu"
elif sbr_req:
pick = "sbr"
else:
continue
last = pick
if pick == "cpu":
cpu_waits.append(t - cpu_wait_start)
cpu_wait_start = None
busy_until = t + CPU_BEATS + 1
else:
if policy == "bucket":
tokens -= sbr_burst
sbr_beats += sbr_burst
busy_until = t + sbr_burst + 1
worst = max(cpu_waits) if cpu_waits else HORIZON
return worst, len(cpu_waits), sbr_beats
worst, served, _ = simulate("sbr_priority")
assert served == 0 and worst == HORIZON # the CPU is starved
worst_rr, served_rr, sbr_rr = simulate("round_robin")
assert served_rr == HORIZON // CPU_PERIOD and worst_rr <= SBR_BEATS + 1 # bound: one SBR burst
worst_b, served_b, sbr_b = simulate("bucket")
assert served_b == served_rr and worst_b <= SBR_BEATS + 1 # CPU served although SBR has priority
assert sbr_b <= 0.75 * HORIZON + SBR_BEATS and sbr_b < sbr_rr # the regulator really caps the SBR
worst_4, served_4, _ = simulate("round_robin", sbr_burst=4)
assert worst_4 <= 4 + 1 # shorter SBR bursts shorten the bound itself
ns = 1e9 / 96e6
assert abs((SBR_BEATS + 1) * ns - 177.1) < 0.1 and abs((4 + 1) * ns - 52.1) < 0.1
print("PASS: SBR priority starves the CPU, round-robin and bucket bound the wait at 17 cycles, 4-beat chunks at 5")Each slave names its scheme and the worst-case wait it guarantees the lowest master, in cycles and nanoseconds, including the longest burst of the other master.
Any fixed-priority slave lists the rate limit that keeps the top master from pinning the bus.
Regulator settings are derived from the bandwidth budget, with bucket depth covering one full burst, not just the average.
Reproduce starvation on purpose: pin the SBR at full rate and measure the CPU's worst wait against its deadline. The measured value must equal the longest SBR burst plus one: 17 cycles on NanoSoC. A longer wait means something else holds the port (a second SBR burst queued ahead, a slave stall). If you add a regulator, check it separately by setting its fill rate to zero and confirming the SBR stops after its bucket empties.
No. A burst in progress cannot be pre-empted, so the CPU still waits up to 17 cycles; priority only matters for who starts next, and raising the SBR's priority makes it worse (starvation). Only shorter SBR bursts reduce the bound.