soc/interconnect/axi: lock AXILiteArbiter until all requests have been responded to
authorJędrzej Boczar <jboczar@antmicro.com>
Mon, 20 Jul 2020 16:08:56 +0000 (18:08 +0200)
committerJędrzej Boczar <jboczar@antmicro.com>
Wed, 22 Jul 2020 15:16:33 +0000 (17:16 +0200)
litex/soc/interconnect/axi.py
test/test_axi.py

index 14ffe030364fb7c649aaca10b8711355fca29855..40fae8be55f174675e0e4ce22f37d4611d1671da 100644 (file)
@@ -936,15 +936,40 @@ class AXILiteInterconnectPointToPoint(Module):
     def __init__(self, master, slave):
         self.comb += master.connect(slave)
 
+
+class AXILiteRequestCounter(Module):
+    def __init__(self, request, response, max_requests=256):
+        self.counter = counter = Signal(max=max_requests)
+        self.full = full = Signal()
+        self.empty = empty = Signal()
+        self.stall = stall = Signal()
+
+        self.comb += [
+            full.eq(counter == max_requests - 1),
+            empty.eq(counter == 0),
+            stall.eq(request & full),
+        ]
+
+        self.sync += [
+            If(request & response,
+                counter.eq(counter)
+            ).Elif(request & ~full,
+                counter.eq(counter + 1)
+            ).Elif(response & ~empty,
+                counter.eq(counter - 1)
+            ),
+        ]
+
 class AXILiteArbiter(Module):
     """AXI Lite arbiter
 
     Arbitrate between master interfaces and connect one to the target.
-    Arbitration is done separately for write and read channels.
+    New master will not be selected until all requests have been responded to.
+    Arbitration for write and read channels is done separately.
     """
     def __init__(self, masters, target):
-        self.submodules.rr_write = roundrobin.RoundRobin(len(masters))
-        self.submodules.rr_read = roundrobin.RoundRobin(len(masters))
+        self.submodules.rr_write = roundrobin.RoundRobin(len(masters), roundrobin.SP_CE)
+        self.submodules.rr_read = roundrobin.RoundRobin(len(masters), roundrobin.SP_CE)
 
         def get_sig(interface, channel, name):
             return getattr(getattr(interface, channel), name)
@@ -968,9 +993,23 @@ class AXILiteArbiter(Module):
                     else:
                         self.comb += dest.eq(source)
 
+        # allow to change rr.grant only after all requests from a master have been responded to
+        self.submodules.wr_counter = wr_counter = AXILiteRequestCounter(
+            request=target.aw.valid & target.aw.ready, response=target.b.valid & target.b.ready)
+        self.submodules.rd_counter = rd_counter = AXILiteRequestCounter(
+            request=target.ar.valid & target.ar.ready, response=target.r.valid & target.r.ready)
+
+        # switch to next request only if there are no responses pending
+        self.comb += [
+            self.rr_write.ce.eq(~(target.aw.valid | target.w.valid | target.b.valid) & wr_counter.empty),
+            self.rr_read.ce.eq(~(target.ar.valid | target.r.valid) & rd_counter.empty),
+        ]
+
         # connect bus requests to round-robin selectors
-        self.comb += self.rr_write.request.eq(Cat(*[m.aw.valid | m.w.valid | m.b.valid for m in masters]))
-        self.comb += self.rr_read.request.eq(Cat(*[m.ar.valid | m.r.valid for m in masters]))
+        self.comb += [
+            self.rr_write.request.eq(Cat(*[m.aw.valid | m.w.valid | m.b.valid for m in masters])),
+            self.rr_read.request.eq(Cat(*[m.ar.valid | m.r.valid for m in masters])),
+        ]
 
 class AXILiteDecoder(Module):
     # slaves is a list of pairs:
index e0b81af2203d3cb11785f49733dc0da2eb0d109f..ee5d4107dfbba27f783c76c18a8ceaa933232c64 100644 (file)
@@ -330,32 +330,34 @@ class TestAXI(unittest.TestCase):
 # TestAXILite --------------------------------------------------------------------------------------
 
 class AXILiteChecker:
-    def __init__(self, latency=None, rdata_generator=None):
-        self.latency = latency or (lambda: 0)
+    def __init__(self, ready_latency=None, response_latency=None, rdata_generator=None):
+        self.ready_latency = ready_latency or (lambda: 0)
+        self.response_latency = response_latency or (lambda: 0)
         self.rdata_generator = rdata_generator or (lambda adr: 0xbaadc0de)
         self.writes = []  # (addr, data, strb)
         self.reads = []  # (addr, data)
 
-    def delay(self):
-        for _ in range(self.latency()):
+    def delay(self, latency):
+        for _ in range(latency()):
             yield
 
     def handle_write(self, axi_lite):
         while not (yield axi_lite.aw.valid):
             yield
-        yield from self.delay()
+        yield from self.delay(self.ready_latency)
         addr = (yield axi_lite.aw.addr)
         yield axi_lite.aw.ready.eq(1)
         yield
         yield axi_lite.aw.ready.eq(0)
         while not (yield axi_lite.w.valid):
             yield
-        yield from self.delay()
+        yield from self.delay(self.ready_latency)
         data = (yield axi_lite.w.data)
         strb = (yield axi_lite.w.strb)
         yield axi_lite.w.ready.eq(1)
         yield
         yield axi_lite.w.ready.eq(0)
+        yield from self.delay(self.response_latency)
         yield axi_lite.b.valid.eq(1)
         yield axi_lite.b.resp.eq(RESP_OKAY)
         yield
@@ -367,11 +369,12 @@ class AXILiteChecker:
     def handle_read(self, axi_lite):
         while not (yield axi_lite.ar.valid):
             yield
-        yield from self.delay()
+        yield from self.delay(self.ready_latency)
         addr = (yield axi_lite.ar.addr)
         yield axi_lite.ar.ready.eq(1)
         yield
         yield axi_lite.ar.ready.eq(0)
+        yield from self.delay(self.response_latency)
         data = self.rdata_generator(addr)
         yield axi_lite.r.valid.eq(1)
         yield axi_lite.r.resp.eq(RESP_OKAY)
@@ -560,7 +563,7 @@ class TestAXILite(unittest.TestCase):
             return _latency
 
         dut = DUT(width_from=width_from, width_to=width_to)
-        checker = AXILiteChecker(latency, rdata_generator)
+        checker = AXILiteChecker(ready_latency=latency, rdata_generator=rdata_generator)
         run_simulation(dut, [generator(dut.master), checker.handler(dut.slave)])
         self.assertEqual(checker.writes, write_expected)
         self.assertEqual(checker.reads, read_expected)
@@ -644,6 +647,9 @@ class TestAXILite(unittest.TestCase):
         self.converter_test(width_from=32, width_to=16,
                             write_pattern=write_pattern, write_expected=write_expected)
 
+# TestAXILiteInterconnet ---------------------------------------------------------------------------
+
+class TestAXILiteInterconnect(unittest.TestCase):
     def axilite_pattern_generator(self, axi_lite, pattern):
         for rw, addr, data in pattern:
             assert rw in ["w", "r"]
@@ -657,7 +663,7 @@ class TestAXILite(unittest.TestCase):
         for _ in range(16):
             yield
 
-    def test_axilite_interconnect_p2p(self):
+    def test_interconnect_p2p(self):
         class DUT(Module):
             def __init__(self):
                 self.master = master = AXILiteInterface()
@@ -687,7 +693,7 @@ class TestAXILite(unittest.TestCase):
         self.assertEqual(checker.writes, [(addr, data, 0b1111) for rw, addr, data in pattern if rw == "w"])
         self.assertEqual(checker.reads, [(addr, data) for rw, addr, data in pattern if rw == "r"])
 
-    def test_axilite_timeout(self):
+    def test_timeout(self):
         class DUT(Module):
             def __init__(self):
                 self.master = master = AXILiteInterface()
@@ -727,7 +733,7 @@ class TestAXILite(unittest.TestCase):
         ]
         run_simulation(dut, generators)
 
-    def test_axilite_arbiter(self):
+    def test_arbiter_order(self):
         class DUT(Module):
             def __init__(self, n_masters):
                 self.masters = [AXILiteInterface() for _ in range(n_masters)]
@@ -759,7 +765,7 @@ class TestAXILite(unittest.TestCase):
             checker = AXILiteChecker()
             generators = [generator(i, master, delay=0) for i, master in enumerate(dut.masters)]
             generators += [timeout(300), checker.handler(dut.slave)]
-            run_simulation(dut, generators, vcd_name='sim.vcd')
+            run_simulation(dut, generators)
             order = [0, 1, 2, 3, 100, 101, 102, 103, 200, 201, 202, 203]
             self.assertEqual([addr for addr, data, strb in checker.writes], order)
             self.assertEqual([addr for addr, data in checker.reads], order)
@@ -774,3 +780,52 @@ class TestAXILite(unittest.TestCase):
             order = [0, 100, 200, 1, 101, 201, 2, 102, 202, 3, 103, 203]
             self.assertEqual([addr for addr, data, strb in checker.writes], order)
             self.assertEqual([addr for addr, data in checker.reads], order)
+
+    def test_arbiter_holds_grant_until_response(self):
+        class DUT(Module):
+            def __init__(self, n_masters):
+                self.masters = [AXILiteInterface() for _ in range(n_masters)]
+                self.slave   = AXILiteInterface()
+                self.submodules.arbiter = AXILiteArbiter(self.masters, self.slave)
+
+        def generator(n, axi_lite, delay=0):
+            def gen(i):
+                return 100*n + i
+
+            for i in range(4):
+                resp = (yield from axi_lite.write(gen(i), gen(i)))
+                self.assertEqual(resp, RESP_OKAY)
+                for _ in range(delay):
+                    yield
+            for i in range(4):
+                data, resp = (yield from axi_lite.read(gen(i)))
+                self.assertEqual(resp, RESP_OKAY)
+                for _ in range(delay):
+                    yield
+            for _ in range(8):
+                yield
+
+
+        n_masters = 3
+
+        # with no delay each master will do all transfers at once
+        with self.subTest(delay=0):
+            dut = DUT(n_masters)
+            checker = AXILiteChecker(response_latency=lambda: 3)
+            generators = [generator(i, master, delay=0) for i, master in enumerate(dut.masters)]
+            generators += [timeout(300), checker.handler(dut.slave)]
+            run_simulation(dut, generators)
+            order = [0, 1, 2, 3, 100, 101, 102, 103, 200, 201, 202, 203]
+            self.assertEqual([addr for addr, data, strb in checker.writes], order)
+            self.assertEqual([addr for addr, data in checker.reads], order)
+
+        # with some delay, the round-robin arbiter will iterate over masters
+        with self.subTest(delay=1):
+            dut = DUT(n_masters)
+            checker = AXILiteChecker(response_latency=lambda: 3)
+            generators = [generator(i, master, delay=1) for i, master in enumerate(dut.masters)]
+            generators += [timeout(300), checker.handler(dut.slave)]
+            run_simulation(dut, generators, vcd_name='sim.vcd')
+            order = [0, 100, 200, 1, 101, 201, 2, 102, 202, 3, 103, 203]
+            self.assertEqual([addr for addr, data, strb in checker.writes], order)
+            self.assertEqual([addr for addr, data in checker.reads], order)