nmigen.lib.scheduler: add RoundRobin.
This commit is contained in:
parent
8117ef6692
commit
20f9ab9d7a
56
nmigen/compat/genlib/roundrobin.py
Normal file
56
nmigen/compat/genlib/roundrobin.py
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
from ..._utils import deprecated
|
||||||
|
from ..fhdl.module import CompatModule
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["RoundRobin", "SP_WITHDRAW", "SP_CE"]
|
||||||
|
|
||||||
|
(SP_WITHDRAW, SP_CE) = range(2)
|
||||||
|
|
||||||
|
class CompatRoundRobin(CompatModule):
|
||||||
|
def __init__(self, n, switch_policy=SP_WITHDRAW):
|
||||||
|
self.request = Signal(n)
|
||||||
|
self.grant = Signal(max=max(2, n))
|
||||||
|
self.switch_policy = switch_policy
|
||||||
|
if self.switch_policy == SP_CE:
|
||||||
|
warnings.warn("instead of `migen.genlib.roundrobin.RoundRobin`, "
|
||||||
|
"use `nmigen.lib.scheduler.RoundRobin`; note that RoundRobin does not "
|
||||||
|
"require a policy anymore but to get the same behavior as SP_CE you"
|
||||||
|
"should use an EnableInserter",
|
||||||
|
DeprecationWarning, stacklevel=1)
|
||||||
|
self.ce = Signal()
|
||||||
|
else:
|
||||||
|
warnings.warn("instead of `migen.genlib.roundrobin.RoundRobin`, "
|
||||||
|
"use `nmigen.lib.scheduler.RoundRobin`; note that RoundRobin does not "
|
||||||
|
"require a policy anymore",
|
||||||
|
DeprecationWarning, stacklevel=1)
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
if n > 1:
|
||||||
|
cases = {}
|
||||||
|
for i in range(n):
|
||||||
|
switch = []
|
||||||
|
for j in reversed(range(i+1, i+n)):
|
||||||
|
t = j % n
|
||||||
|
switch = [
|
||||||
|
If(self.request[t],
|
||||||
|
self.grant.eq(t)
|
||||||
|
).Else(
|
||||||
|
*switch
|
||||||
|
)
|
||||||
|
]
|
||||||
|
if self.switch_policy == SP_WITHDRAW:
|
||||||
|
case = [If(~self.request[i], *switch)]
|
||||||
|
else:
|
||||||
|
case = switch
|
||||||
|
cases[i] = case
|
||||||
|
statement = Case(self.grant, cases)
|
||||||
|
if self.switch_policy == SP_CE:
|
||||||
|
statement = If(self.ce, statement)
|
||||||
|
self.sync += statement
|
||||||
|
else:
|
||||||
|
self.comb += self.grant.eq(0)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
RoundRobin = CompatRoundRobin
|
60
nmigen/lib/scheduler.py
Normal file
60
nmigen/lib/scheduler.py
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
from .. import *
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["RoundRobin"]
|
||||||
|
|
||||||
|
|
||||||
|
class RoundRobin(Elaboratable):
|
||||||
|
"""Round-robin scheduler.
|
||||||
|
|
||||||
|
For a given set of requests, the round-robin scheduler will
|
||||||
|
grant one request. Once it grants a request, if any other
|
||||||
|
requests are active, it grants the next active request with
|
||||||
|
a greater number, restarting from zero once it reaches the
|
||||||
|
highest one.
|
||||||
|
|
||||||
|
Use :class:`EnableInserter` to control when the scheduler
|
||||||
|
is updated.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
count : int
|
||||||
|
Number of requests.
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
----------
|
||||||
|
requests : Signal(count), in
|
||||||
|
Set of requests.
|
||||||
|
grant : Signal(range(count)), out
|
||||||
|
Number of the granted request. Does not change if there are no
|
||||||
|
active requests.
|
||||||
|
valid : Signal(), out
|
||||||
|
Asserted if grant corresponds to an active request. Deasserted
|
||||||
|
otherwise, i.e. if no requests are active.
|
||||||
|
"""
|
||||||
|
def __init__(self, *, count):
|
||||||
|
if not isinstance(count, int) or count < 0:
|
||||||
|
raise ValueError("Count must be a non-negative integer, not {!r}"
|
||||||
|
.format(count))
|
||||||
|
self.count = count
|
||||||
|
|
||||||
|
self.requests = Signal(count)
|
||||||
|
self.grant = Signal(range(count))
|
||||||
|
self.valid = Signal()
|
||||||
|
|
||||||
|
def elaborate(self, platform):
|
||||||
|
m = Module()
|
||||||
|
|
||||||
|
with m.Switch(self.grant):
|
||||||
|
for i in range(self.count):
|
||||||
|
with m.Case(i):
|
||||||
|
for pred in reversed(range(i)):
|
||||||
|
with m.If(self.requests[pred]):
|
||||||
|
m.d.sync += self.grant.eq(pred)
|
||||||
|
for succ in reversed(range(i + 1, self.count)):
|
||||||
|
with m.If(self.requests[succ]):
|
||||||
|
m.d.sync += self.grant.eq(succ)
|
||||||
|
|
||||||
|
m.d.sync += self.valid.eq(self.requests.any())
|
||||||
|
|
||||||
|
return m
|
93
nmigen/test/test_lib_scheduler.py
Normal file
93
nmigen/test/test_lib_scheduler.py
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
# nmigen: UnusedElaboratable=no
|
||||||
|
import unittest
|
||||||
|
from .utils import *
|
||||||
|
from ..hdl import *
|
||||||
|
from ..asserts import *
|
||||||
|
from ..sim.pysim import *
|
||||||
|
from ..lib.scheduler import *
|
||||||
|
|
||||||
|
|
||||||
|
class RoundRobinTestCase(unittest.TestCase):
|
||||||
|
def test_count(self):
|
||||||
|
dut = RoundRobin(count=32)
|
||||||
|
self.assertEqual(dut.count, 32)
|
||||||
|
self.assertEqual(len(dut.requests), 32)
|
||||||
|
self.assertEqual(len(dut.grant), 5)
|
||||||
|
|
||||||
|
def test_wrong_count(self):
|
||||||
|
with self.assertRaisesRegex(ValueError, r"Count must be a non-negative integer, not 'foo'"):
|
||||||
|
dut = RoundRobin(count="foo")
|
||||||
|
with self.assertRaisesRegex(ValueError, r"Count must be a non-negative integer, not -1"):
|
||||||
|
dut = RoundRobin(count=-1)
|
||||||
|
|
||||||
|
|
||||||
|
class RoundRobinSimulationTestCase(unittest.TestCase):
|
||||||
|
def test_count_one(self):
|
||||||
|
dut = RoundRobin(count=1)
|
||||||
|
sim = Simulator(dut)
|
||||||
|
def process():
|
||||||
|
yield dut.requests.eq(0)
|
||||||
|
yield; yield Delay(1e-8)
|
||||||
|
self.assertEqual((yield dut.grant), 0)
|
||||||
|
self.assertFalse((yield dut.valid))
|
||||||
|
|
||||||
|
yield dut.requests.eq(1)
|
||||||
|
yield; yield Delay(1e-8)
|
||||||
|
self.assertEqual((yield dut.grant), 0)
|
||||||
|
self.assertTrue((yield dut.valid))
|
||||||
|
sim.add_sync_process(process)
|
||||||
|
sim.add_clock(1e-6)
|
||||||
|
with sim.write_vcd("test.vcd"):
|
||||||
|
sim.run()
|
||||||
|
|
||||||
|
def test_transitions(self):
|
||||||
|
dut = RoundRobin(count=3)
|
||||||
|
sim = Simulator(dut)
|
||||||
|
def process():
|
||||||
|
yield dut.requests.eq(0b111)
|
||||||
|
yield; yield Delay(1e-8)
|
||||||
|
self.assertEqual((yield dut.grant), 1)
|
||||||
|
self.assertTrue((yield dut.valid))
|
||||||
|
|
||||||
|
yield dut.requests.eq(0b110)
|
||||||
|
yield; yield Delay(1e-8)
|
||||||
|
self.assertEqual((yield dut.grant), 2)
|
||||||
|
self.assertTrue((yield dut.valid))
|
||||||
|
|
||||||
|
yield dut.requests.eq(0b010)
|
||||||
|
yield; yield Delay(1e-8)
|
||||||
|
self.assertEqual((yield dut.grant), 1)
|
||||||
|
self.assertTrue((yield dut.valid))
|
||||||
|
|
||||||
|
yield dut.requests.eq(0b011)
|
||||||
|
yield; yield Delay(1e-8)
|
||||||
|
self.assertEqual((yield dut.grant), 0)
|
||||||
|
self.assertTrue((yield dut.valid))
|
||||||
|
|
||||||
|
yield dut.requests.eq(0b001)
|
||||||
|
yield; yield Delay(1e-8)
|
||||||
|
self.assertEqual((yield dut.grant), 0)
|
||||||
|
self.assertTrue((yield dut.valid))
|
||||||
|
|
||||||
|
yield dut.requests.eq(0b101)
|
||||||
|
yield; yield Delay(1e-8)
|
||||||
|
self.assertEqual((yield dut.grant), 2)
|
||||||
|
self.assertTrue((yield dut.valid))
|
||||||
|
|
||||||
|
yield dut.requests.eq(0b100)
|
||||||
|
yield; yield Delay(1e-8)
|
||||||
|
self.assertEqual((yield dut.grant), 2)
|
||||||
|
self.assertTrue((yield dut.valid))
|
||||||
|
|
||||||
|
yield dut.requests.eq(0b000)
|
||||||
|
yield; yield Delay(1e-8)
|
||||||
|
self.assertFalse((yield dut.valid))
|
||||||
|
|
||||||
|
yield dut.requests.eq(0b001)
|
||||||
|
yield; yield Delay(1e-8)
|
||||||
|
self.assertEqual((yield dut.grant), 0)
|
||||||
|
self.assertTrue((yield dut.valid))
|
||||||
|
sim.add_sync_process(process)
|
||||||
|
sim.add_clock(1e-6)
|
||||||
|
with sim.write_vcd("test.vcd"):
|
||||||
|
sim.run()
|
Loading…
Reference in a new issue