Skip to content

Commit 967a65f

Browse files
author
Jean-François Nguyen
authored
wishbone.bus: add Arbiter.
1 parent f8f8982 commit 967a65f

File tree

2 files changed

+383
-1
lines changed

2 files changed

+383
-1
lines changed

nmigen_soc/test/test_wishbone_bus.py

+265
Original file line numberDiff line numberDiff line change
@@ -350,3 +350,268 @@ def sim_test():
350350
with Simulator(m, vcd_file=open("test.vcd", "w")) as sim:
351351
sim.add_process(sim_test())
352352
sim.run()
353+
354+
355+
class ArbiterTestCase(unittest.TestCase):
356+
def setUp(self):
357+
self.dut = Arbiter(addr_width=31, data_width=32, granularity=16,
358+
features={"err"})
359+
360+
def test_add_wrong(self):
361+
with self.assertRaisesRegex(TypeError,
362+
r"Initiator bus must be an instance of wishbone\.Interface, not 'foo'"):
363+
self.dut.add("foo")
364+
365+
def test_add_wrong_addr_width(self):
366+
with self.assertRaisesRegex(ValueError,
367+
r"Initiator bus has address width 15, which is not the same as arbiter "
368+
r"address width 31"):
369+
self.dut.add(Interface(addr_width=15, data_width=32, granularity=16))
370+
371+
def test_add_wrong_granularity(self):
372+
with self.assertRaisesRegex(ValueError,
373+
r"Initiator bus has granularity 8, which is lesser than "
374+
r"the arbiter granularity 16"):
375+
self.dut.add(Interface(addr_width=31, data_width=32, granularity=8))
376+
377+
def test_add_wrong_data_width(self):
378+
with self.assertRaisesRegex(ValueError,
379+
r"Initiator bus has data width 16, which is not the same as arbiter "
380+
r"data width 32"):
381+
self.dut.add(Interface(addr_width=31, data_width=16, granularity=16))
382+
383+
def test_add_wrong_optional_output(self):
384+
with self.assertRaisesRegex(ValueError,
385+
r"Arbiter has optional output 'err', but the initiator bus does "
386+
r"not have a corresponding input"):
387+
self.dut.add(Interface(addr_width=31, data_width=32, granularity=16))
388+
389+
390+
class ArbiterSimulationTestCase(unittest.TestCase):
391+
def test_simple(self):
392+
dut = Arbiter(addr_width=30, data_width=32, granularity=8,
393+
features={"err", "rty", "stall", "lock", "cti", "bte"})
394+
intr_1 = Interface(addr_width=30, data_width=32, granularity=8,
395+
features={"err", "rty"})
396+
dut.add(intr_1)
397+
intr_2 = Interface(addr_width=30, data_width=32, granularity=16,
398+
features={"err", "rty", "stall", "lock", "cti", "bte"})
399+
dut.add(intr_2)
400+
401+
def sim_test():
402+
yield intr_1.adr.eq(0x7ffffffc >> 2)
403+
yield intr_1.cyc.eq(1)
404+
yield intr_1.stb.eq(1)
405+
yield intr_1.sel.eq(0b1111)
406+
yield intr_1.we.eq(1)
407+
yield intr_1.dat_w.eq(0x12345678)
408+
yield dut.bus.dat_r.eq(0xabcdef01)
409+
yield dut.bus.ack.eq(1)
410+
yield dut.bus.err.eq(1)
411+
yield dut.bus.rty.eq(1)
412+
yield Delay(1e-7)
413+
self.assertEqual((yield dut.bus.adr), 0x7ffffffc >> 2)
414+
self.assertEqual((yield dut.bus.cyc), 1)
415+
self.assertEqual((yield dut.bus.stb), 1)
416+
self.assertEqual((yield dut.bus.sel), 0b1111)
417+
self.assertEqual((yield dut.bus.we), 1)
418+
self.assertEqual((yield dut.bus.dat_w), 0x12345678)
419+
self.assertEqual((yield dut.bus.lock), 1)
420+
self.assertEqual((yield dut.bus.cti), CycleType.CLASSIC.value)
421+
self.assertEqual((yield dut.bus.bte), BurstTypeExt.LINEAR.value)
422+
self.assertEqual((yield intr_1.dat_r), 0xabcdef01)
423+
self.assertEqual((yield intr_1.ack), 1)
424+
self.assertEqual((yield intr_1.err), 1)
425+
self.assertEqual((yield intr_1.rty), 1)
426+
427+
yield intr_1.cyc.eq(0)
428+
yield intr_2.adr.eq(0xe0000000 >> 2)
429+
yield intr_2.cyc.eq(1)
430+
yield intr_2.stb.eq(1)
431+
yield intr_2.sel.eq(0b10)
432+
yield intr_2.we.eq(1)
433+
yield intr_2.dat_w.eq(0x43218765)
434+
yield intr_2.lock.eq(0)
435+
yield intr_2.cti.eq(CycleType.INCR_BURST)
436+
yield intr_2.bte.eq(BurstTypeExt.WRAP_4)
437+
yield Tick()
438+
439+
yield dut.bus.stall.eq(0)
440+
yield Delay(1e-7)
441+
self.assertEqual((yield dut.bus.adr), 0xe0000000 >> 2)
442+
self.assertEqual((yield dut.bus.cyc), 1)
443+
self.assertEqual((yield dut.bus.stb), 1)
444+
self.assertEqual((yield dut.bus.sel), 0b1100)
445+
self.assertEqual((yield dut.bus.we), 1)
446+
self.assertEqual((yield dut.bus.dat_w), 0x43218765)
447+
self.assertEqual((yield dut.bus.lock), 0)
448+
self.assertEqual((yield dut.bus.cti), CycleType.INCR_BURST.value)
449+
self.assertEqual((yield dut.bus.bte), BurstTypeExt.WRAP_4.value)
450+
self.assertEqual((yield intr_2.dat_r), 0xabcdef01)
451+
self.assertEqual((yield intr_2.ack), 1)
452+
self.assertEqual((yield intr_2.err), 1)
453+
self.assertEqual((yield intr_2.rty), 1)
454+
self.assertEqual((yield intr_2.stall), 0)
455+
456+
with Simulator(dut, vcd_file=open("test.vcd", "w")) as sim:
457+
sim.add_clock(1e-6)
458+
sim.add_sync_process(sim_test())
459+
sim.run()
460+
461+
def test_lock(self):
462+
dut = Arbiter(addr_width=30, data_width=32, features={"lock"})
463+
intr_1 = Interface(addr_width=30, data_width=32, features={"lock"})
464+
dut.add(intr_1)
465+
intr_2 = Interface(addr_width=30, data_width=32, features={"lock"})
466+
dut.add(intr_2)
467+
468+
def sim_test():
469+
yield intr_1.cyc.eq(1)
470+
yield intr_1.lock.eq(1)
471+
yield intr_2.cyc.eq(1)
472+
yield dut.bus.ack.eq(1)
473+
yield Delay(1e-7)
474+
self.assertEqual((yield intr_1.ack), 1)
475+
self.assertEqual((yield intr_2.ack), 0)
476+
477+
yield Tick()
478+
yield Delay(1e-7)
479+
self.assertEqual((yield intr_1.ack), 1)
480+
self.assertEqual((yield intr_2.ack), 0)
481+
482+
yield intr_1.lock.eq(0)
483+
yield Tick()
484+
yield Delay(1e-7)
485+
self.assertEqual((yield intr_1.ack), 0)
486+
self.assertEqual((yield intr_2.ack), 1)
487+
488+
yield intr_2.cyc.eq(0)
489+
yield Tick()
490+
yield Delay(1e-7)
491+
self.assertEqual((yield intr_1.ack), 1)
492+
self.assertEqual((yield intr_2.ack), 0)
493+
494+
yield intr_1.stb.eq(1)
495+
yield Tick()
496+
yield Delay(1e-7)
497+
self.assertEqual((yield intr_1.ack), 1)
498+
self.assertEqual((yield intr_2.ack), 0)
499+
500+
yield intr_1.stb.eq(0)
501+
yield intr_2.cyc.eq(1)
502+
yield Tick()
503+
yield Delay(1e-7)
504+
self.assertEqual((yield intr_1.ack), 0)
505+
self.assertEqual((yield intr_2.ack), 1)
506+
507+
with Simulator(dut, vcd_file=open("test.vcd", "w")) as sim:
508+
sim.add_clock(1e-6)
509+
sim.add_sync_process(sim_test())
510+
sim.run()
511+
512+
def test_stall(self):
513+
dut = Arbiter(addr_width=30, data_width=32, features={"stall"})
514+
intr_1 = Interface(addr_width=30, data_width=32, features={"stall"})
515+
dut.add(intr_1)
516+
intr_2 = Interface(addr_width=30, data_width=32, features={"stall"})
517+
dut.add(intr_2)
518+
519+
def sim_test():
520+
yield intr_1.cyc.eq(1)
521+
yield intr_2.cyc.eq(1)
522+
yield dut.bus.stall.eq(0)
523+
yield Delay(1e-6)
524+
self.assertEqual((yield intr_1.stall), 0)
525+
self.assertEqual((yield intr_2.stall), 1)
526+
527+
yield dut.bus.stall.eq(1)
528+
yield Delay(1e-6)
529+
self.assertEqual((yield intr_1.stall), 1)
530+
self.assertEqual((yield intr_2.stall), 1)
531+
532+
with Simulator(dut, vcd_file=open("test.vcd", "w")) as sim:
533+
sim.add_process(sim_test())
534+
sim.run()
535+
536+
def test_stall_compat(self):
537+
dut = Arbiter(addr_width=30, data_width=32)
538+
intr_1 = Interface(addr_width=30, data_width=32, features={"stall"})
539+
dut.add(intr_1)
540+
intr_2 = Interface(addr_width=30, data_width=32, features={"stall"})
541+
dut.add(intr_2)
542+
543+
def sim_test():
544+
yield intr_1.cyc.eq(1)
545+
yield intr_2.cyc.eq(1)
546+
yield Delay(1e-6)
547+
self.assertEqual((yield intr_1.stall), 1)
548+
self.assertEqual((yield intr_2.stall), 1)
549+
550+
yield dut.bus.ack.eq(1)
551+
yield Delay(1e-6)
552+
self.assertEqual((yield intr_1.stall), 0)
553+
self.assertEqual((yield intr_2.stall), 1)
554+
555+
with Simulator(dut, vcd_file=open("test.vcd", "w")) as sim:
556+
sim.add_process(sim_test())
557+
sim.run()
558+
559+
def test_roundrobin(self):
560+
dut = Arbiter(addr_width=30, data_width=32)
561+
intr_1 = Interface(addr_width=30, data_width=32)
562+
dut.add(intr_1)
563+
intr_2 = Interface(addr_width=30, data_width=32)
564+
dut.add(intr_2)
565+
intr_3 = Interface(addr_width=30, data_width=32)
566+
dut.add(intr_3)
567+
568+
def sim_test():
569+
yield intr_1.cyc.eq(1)
570+
yield intr_2.cyc.eq(0)
571+
yield intr_3.cyc.eq(1)
572+
yield dut.bus.ack.eq(1)
573+
yield Delay(1e-7)
574+
self.assertEqual((yield intr_1.ack), 1)
575+
self.assertEqual((yield intr_2.ack), 0)
576+
self.assertEqual((yield intr_3.ack), 0)
577+
578+
yield intr_1.cyc.eq(0)
579+
yield intr_2.cyc.eq(0)
580+
yield intr_3.cyc.eq(1)
581+
yield Tick()
582+
yield Delay(1e-7)
583+
self.assertEqual((yield intr_1.ack), 0)
584+
self.assertEqual((yield intr_2.ack), 0)
585+
self.assertEqual((yield intr_3.ack), 1)
586+
587+
yield intr_1.cyc.eq(1)
588+
yield intr_2.cyc.eq(1)
589+
yield intr_3.cyc.eq(0)
590+
yield Tick()
591+
yield Delay(1e-7)
592+
self.assertEqual((yield intr_1.ack), 1)
593+
self.assertEqual((yield intr_2.ack), 0)
594+
self.assertEqual((yield intr_3.ack), 0)
595+
596+
yield intr_1.cyc.eq(0)
597+
yield intr_2.cyc.eq(1)
598+
yield intr_3.cyc.eq(1)
599+
yield Tick()
600+
yield Delay(1e-7)
601+
self.assertEqual((yield intr_1.ack), 0)
602+
self.assertEqual((yield intr_2.ack), 1)
603+
self.assertEqual((yield intr_3.ack), 0)
604+
605+
yield intr_1.cyc.eq(1)
606+
yield intr_2.cyc.eq(0)
607+
yield intr_3.cyc.eq(1)
608+
yield Tick()
609+
yield Delay(1e-7)
610+
self.assertEqual((yield intr_1.ack), 0)
611+
self.assertEqual((yield intr_2.ack), 0)
612+
self.assertEqual((yield intr_3.ack), 1)
613+
614+
with Simulator(dut, vcd_file=open("test.vcd", "w")) as sim:
615+
sim.add_clock(1e-6)
616+
sim.add_sync_process(sim_test())
617+
sim.run()

nmigen_soc/wishbone/bus.py

+118-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from ..memory import MemoryMap
77

88

9-
__all__ = ["CycleType", "BurstTypeExt", "Interface", "Decoder"]
9+
__all__ = ["CycleType", "BurstTypeExt", "Interface", "Decoder", "Arbiter"]
1010

1111

1212
class CycleType(Enum):
@@ -311,3 +311,120 @@ def elaborate(self, platform):
311311
m.d.comb += self.bus.stall.eq(stall_fanin)
312312

313313
return m
314+
315+
316+
class Arbiter(Elaboratable):
317+
"""Wishbone bus arbiter.
318+
319+
A round-robin arbiter for initiators accessing a shared Wishbone bus.
320+
321+
Parameters
322+
----------
323+
addr_width : int
324+
Address width. See :class:`Interface`.
325+
data_width : int
326+
Data width. See :class:`Interface`.
327+
granularity : int
328+
Granularity. See :class:`Interface`
329+
features : iter(str)
330+
Optional signal set. See :class:`Interface`.
331+
332+
Attributes
333+
----------
334+
bus : :class:`Interface`
335+
Shared Wishbone bus.
336+
"""
337+
def __init__(self, *, addr_width, data_width, granularity=None, features=frozenset()):
338+
self.bus = Interface(addr_width=addr_width, data_width=data_width,
339+
granularity=granularity, features=features)
340+
self._intrs = []
341+
342+
def add(self, intr_bus):
343+
"""Add an initiator bus to the arbiter.
344+
345+
The initiator bus must have the same address width and data width as the arbiter. The
346+
granularity of the initiator bus must be greater than or equal to the granularity of
347+
the arbiter.
348+
"""
349+
if not isinstance(intr_bus, Interface):
350+
raise TypeError("Initiator bus must be an instance of wishbone.Interface, not {!r}"
351+
.format(intr_bus))
352+
if intr_bus.addr_width != self.bus.addr_width:
353+
raise ValueError("Initiator bus has address width {}, which is not the same as "
354+
"arbiter address width {}"
355+
.format(intr_bus.addr_width, self.bus.addr_width))
356+
if intr_bus.granularity < self.bus.granularity:
357+
raise ValueError("Initiator bus has granularity {}, which is lesser than the "
358+
"arbiter granularity {}"
359+
.format(intr_bus.granularity, self.bus.granularity))
360+
if intr_bus.data_width != self.bus.data_width:
361+
raise ValueError("Initiator bus has data width {}, which is not the same as "
362+
"arbiter data width {}"
363+
.format(intr_bus.data_width, self.bus.data_width))
364+
for opt_output in {"err", "rty"}:
365+
if hasattr(self.bus, opt_output) and not hasattr(intr_bus, opt_output):
366+
raise ValueError("Arbiter has optional output {!r}, but the initiator bus "
367+
"does not have a corresponding input"
368+
.format(opt_output))
369+
370+
self._intrs.append(intr_bus)
371+
372+
def elaborate(self, platform):
373+
m = Module()
374+
375+
requests = Signal(len(self._intrs))
376+
grant = Signal(range(len(self._intrs)))
377+
m.d.comb += requests.eq(Cat(intr_bus.cyc for intr_bus in self._intrs))
378+
379+
bus_busy = self.bus.cyc
380+
if hasattr(self.bus, "lock"):
381+
# If LOCK is not asserted, we also wait for STB to be deasserted before granting bus
382+
# ownership to the next initiator. If we didn't, the next bus owner could receive
383+
# an ACK (or ERR, RTY) from the previous transaction when targeting the same
384+
# peripheral.
385+
bus_busy &= self.bus.lock | self.bus.stb
386+
387+
with m.If(~bus_busy):
388+
with m.Switch(grant):
389+
for i in range(len(requests)):
390+
with m.Case(i):
391+
for pred in reversed(range(i)):
392+
with m.If(requests[pred]):
393+
m.d.sync += grant.eq(pred)
394+
for succ in reversed(range(i + 1, len(requests))):
395+
with m.If(requests[succ]):
396+
m.d.sync += grant.eq(succ)
397+
398+
with m.Switch(grant):
399+
for i, intr_bus in enumerate(self._intrs):
400+
m.d.comb += intr_bus.dat_r.eq(self.bus.dat_r)
401+
if hasattr(intr_bus, "stall"):
402+
intr_bus_stall = Signal(reset=1)
403+
m.d.comb += intr_bus.stall.eq(intr_bus_stall)
404+
405+
with m.Case(i):
406+
ratio = intr_bus.granularity // self.bus.granularity
407+
m.d.comb += [
408+
self.bus.adr.eq(intr_bus.adr),
409+
self.bus.dat_w.eq(intr_bus.dat_w),
410+
self.bus.sel.eq(Cat(Repl(sel, ratio) for sel in intr_bus.sel)),
411+
self.bus.we.eq(intr_bus.we),
412+
self.bus.stb.eq(intr_bus.stb),
413+
]
414+
m.d.comb += self.bus.cyc.eq(intr_bus.cyc)
415+
if hasattr(self.bus, "lock"):
416+
m.d.comb += self.bus.lock.eq(getattr(intr_bus, "lock", 1))
417+
if hasattr(self.bus, "cti"):
418+
m.d.comb += self.bus.cti.eq(getattr(intr_bus, "cti", CycleType.CLASSIC))
419+
if hasattr(self.bus, "bte"):
420+
m.d.comb += self.bus.bte.eq(getattr(intr_bus, "bte", BurstTypeExt.LINEAR))
421+
422+
m.d.comb += intr_bus.ack.eq(self.bus.ack)
423+
if hasattr(intr_bus, "err"):
424+
m.d.comb += intr_bus.err.eq(getattr(self.bus, "err", 0))
425+
if hasattr(intr_bus, "rty"):
426+
m.d.comb += intr_bus.rty.eq(getattr(self.bus, "rty", 0))
427+
if hasattr(intr_bus, "stall"):
428+
m.d.comb += intr_bus_stall.eq(getattr(self.bus, "stall", ~self.bus.ack))
429+
430+
return m

0 commit comments

Comments
 (0)