soc/interconnect/axi: add burst support to AXI2Wishbone
authorFlorent Kermarrec <florent@enjoy-digital.fr>
Mon, 29 Apr 2019 14:48:42 +0000 (16:48 +0200)
committerFlorent Kermarrec <florent@enjoy-digital.fr>
Mon, 29 Apr 2019 14:49:20 +0000 (16:49 +0200)
litex/soc/interconnect/axi.py
test/test_axi.py

index f9dd2078413eb4e51df22639fa0ae8881f3467b5..75163cbcfb3d81cca16e30372b290efa58fe6cf9 100644 (file)
@@ -118,16 +118,19 @@ class AXIBurst2Beat(Module):
 # AXI to Wishbone ----------------------------------------------------------------------------------
 
 class AXI2Wishbone(Module):
-    def __init__(self, axi, wishbone, base_address):
+    def __init__(self, axi, wishbone, base_address=0x00000000):
         assert axi.data_width    == len(wishbone.dat_r)
         assert axi.address_width == len(wishbone.adr) + 2
 
+        ax_burst = stream.Endpoint(ax_description(axi.address_width, axi.id_width))
+        ax_beat = stream.Endpoint(ax_description(axi.address_width, axi.id_width))
+        ax_burst2beat = AXIBurst2Beat(ax_burst, ax_beat)
+        self.submodules += ax_burst2beat
+
         _data       = Signal(axi.data_width)
-        _read_addr  = Signal(axi.address_width)
-        _write_addr = Signal(axi.address_width)
+        _addr  = Signal(axi.address_width)
 
-        self.comb += _read_addr.eq(axi.ar.addr - base_address)
-        self.comb += _write_addr.eq(axi.aw.addr - base_address)
+        self.comb += _addr.eq(ax_beat.addr - base_address)
 
         self.submodules.fsm = fsm = FSM(reset_state="IDLE")
         fsm.act("IDLE",
@@ -138,43 +141,55 @@ class AXI2Wishbone(Module):
             )
         )
         fsm.act("DO-READ",
+            axi.ar.connect(ax_burst),
             wishbone.stb.eq(1),
             wishbone.cyc.eq(1),
-            wishbone.adr.eq(_read_addr[2:]),
+            wishbone.adr.eq(_addr[2:]),
             If(wishbone.ack,
                 NextValue(_data, wishbone.dat_r),
                 NextState("SEND-READ-RESPONSE")
             )
         )
         fsm.act("SEND-READ-RESPONSE",
+            axi.ar.connect(ax_burst),
             axi.r.valid.eq(1),
-            axi.r.last.eq(1),
             axi.r.resp.eq(RESP_OKAY),
-            axi.r.id.eq(axi.ar.id),
+            axi.r.id.eq(ax_beat.id),
             axi.r.data.eq(_data),
             If(axi.r.ready,
-                axi.ar.ready.eq(1),
-                NextState("IDLE")
+                ax_beat.ready.eq(1),
+                If(ax_beat.last,
+                    axi.r.last.eq(1),
+                    NextState("IDLE"),
+                ).Else(
+                    NextState("DO-READ")
+                )
             )
         )
         fsm.act("DO-WRITE",
-            wishbone.stb.eq(1),
-            wishbone.cyc.eq(1),
+            axi.aw.connect(ax_burst),
+            wishbone.stb.eq(axi.w.valid),
+            wishbone.cyc.eq(axi.w.valid),
             wishbone.we.eq(1),
-            wishbone.adr.eq(_write_addr[2:]),
+            wishbone.adr.eq(_addr[2:]),
             wishbone.sel.eq(axi.w.strb),
             wishbone.dat_w.eq(axi.w.data),
             If(wishbone.ack,
-                NextState("SEND-WRITE-RESPONSE")
+                ax_beat.ready.eq(1),
+                axi.w.ready.eq(1),
+                If(ax_beat.last,
+                    ax_beat.ready.eq(0),
+                    NextState("SEND-WRITE-RESPONSE")
+                )
             )
         )
         fsm.act("SEND-WRITE-RESPONSE",
+            axi.aw.connect(ax_burst),
             axi.b.valid.eq(1),
             axi.b.resp.eq(RESP_OKAY),
-            axi.b.id.eq(axi.aw.id),
+            axi.b.id.eq(ax_beat.id),
             If(axi.b.ready,
-                axi.aw.ready.eq(1),
-                axi.w.ready.eq(1),
+                ax_beat.ready.eq(1),
                 NextState("IDLE")
             )
         )
index c8d23880a4686bf5c42af6d21957d1e58ed834b3..88aa11c03962d48abeec7c361c64f29347269d46 100644 (file)
@@ -3,12 +3,19 @@ import random
 
 from migen import *
 
-from litedram.common import *
-from litedram.frontend.axi import *
+from litex.soc.interconnect.axi import *
+from litex.soc.interconnect import wishbone
 
 from litex.gen.sim import *
 
 
+def rand_wait(level):
+    prng = random.Random(42)
+    while prng.randrange(100) < level:
+        yield
+
+# Software Models ----------------------------------------------------------------------------------
+
 class Burst:
     def __init__(self, addr, type=BURST_FIXED, len=0, size=0):
         self.addr = addr
@@ -35,6 +42,22 @@ class Beat:
         self.addr = addr
 
 
+class Access(Burst):
+    def __init__(self, addr, data, id, **kwargs):
+        Burst.__init__(self, addr, **kwargs)
+        self.data = data
+        self.id = id
+
+
+class Write(Access):
+    pass
+
+
+class Read(Access):
+    pass
+
+# Tests --------------------------------------------------------------------------------------------
+
 class TestAXI(unittest.TestCase):
     def test_burst2beat(self):
         def bursts_generator(ax, bursts, valid_rand=50):
@@ -94,3 +117,207 @@ class TestAXI(unittest.TestCase):
         ]
         run_simulation(dut, generators)
         self.assertEqual(self.errors, 0)
+
+
+    def _test_axi2wishbone(self,
+        naccesses=16, simultaneous_writes_reads=False,
+        # rand_level: 0: min (no random), 100: max.
+        # burst randomness
+        id_rand_enable   = False,
+        len_rand_enable  = False,
+        data_rand_enable = False,
+        # flow valid randomness
+        aw_valid_rand_level = 0,
+        w_valid_rand_level  = 0,
+        ar_valid_rand_level = 0,
+        r_valid_rand_level  = 0,
+        # flow ready randomness
+        w_ready_rand_level  = 0,
+        b_ready_rand_level  = 0,
+        r_ready_rand_level  = 0
+        ):
+
+        def writes_cmd_generator(axi_port, writes):
+            for write in writes:
+                yield from rand_wait(aw_valid_rand_level)
+                # send command
+                yield axi_port.aw.valid.eq(1)
+                yield axi_port.aw.addr.eq(write.addr<<2)
+                yield axi_port.aw.burst.eq(write.type)
+                yield axi_port.aw.len.eq(write.len)
+                yield axi_port.aw.size.eq(write.size)
+                yield axi_port.aw.id.eq(write.id)
+                yield
+                while (yield axi_port.aw.ready) == 0:
+                    yield
+                yield axi_port.aw.valid.eq(0)
+
+        def writes_data_generator(axi_port, writes):
+            yield axi_port.w.strb.eq(2**(len(axi_port.w.data)//8) - 1)
+            for write in writes:
+                for i, data in enumerate(write.data):
+                    yield from rand_wait(w_valid_rand_level)
+                    # send data
+                    yield axi_port.w.valid.eq(1)
+                    if (i == (len(write.data) - 1)):
+                        yield axi_port.w.last.eq(1)
+                    else:
+                        yield axi_port.w.last.eq(0)
+                    yield axi_port.w.data.eq(data)
+                    yield
+                    while (yield axi_port.w.ready) == 0:
+                        yield
+                    yield axi_port.w.valid.eq(0)
+            axi_port.reads_enable = True
+
+        def writes_response_generator(axi_port, writes):
+            self.writes_id_errors = 0
+            for write in writes:
+                # wait response
+                yield axi_port.b.ready.eq(0)
+                yield
+                while (yield axi_port.b.valid) == 0:
+                    yield
+                yield from rand_wait(b_ready_rand_level)
+                yield axi_port.b.ready.eq(1)
+                yield
+                if (yield axi_port.b.id) != write.id:
+                    self.writes_id_errors += 1
+
+        def reads_cmd_generator(axi_port, reads):
+            while not axi_port.reads_enable:
+                yield
+            for read in reads:
+                yield from rand_wait(ar_valid_rand_level)
+                # send command
+                yield axi_port.ar.valid.eq(1)
+                yield axi_port.ar.addr.eq(read.addr<<2)
+                yield axi_port.ar.burst.eq(read.type)
+                yield axi_port.ar.len.eq(read.len)
+                yield axi_port.ar.size.eq(read.size)
+                yield axi_port.ar.id.eq(read.id)
+                yield
+                while (yield axi_port.ar.ready) == 0:
+                    yield
+                yield axi_port.ar.valid.eq(0)
+
+        def reads_response_data_generator(axi_port, reads):
+            self.reads_data_errors = 0
+            self.reads_id_errors = 0
+            self.reads_last_errors = 0
+            while not axi_port.reads_enable:
+                yield
+            for read in reads:
+                for i, data in enumerate(read.data):
+                    # wait data / response
+                    yield axi_port.r.ready.eq(0)
+                    yield
+                    while (yield axi_port.r.valid) == 0:
+                        yield
+                    yield from rand_wait(r_ready_rand_level)
+                    yield axi_port.r.ready.eq(1)
+                    yield
+                    if (yield axi_port.r.data) != data:
+                        self.reads_data_errors += 1
+                    if (yield axi_port.r.id) != read.id:
+                        self.reads_id_errors += 1
+                    if i == (len(read.data) - 1):
+                        if (yield axi_port.r.last) != 1:
+                            self.reads_last_errors += 1
+                    else:
+                        if (yield axi_port.r.last) != 0:
+                            self.reads_last_errors += 1
+
+        # dut
+        class DUT(Module):
+            def __init__(self):
+                self.axi = AXIInterface(data_width=32, address_width=32, id_width=8)
+                self.wishbone = wishbone.Interface(data_width=32)
+
+                axi2wishbone = AXI2Wishbone(self.axi, self.wishbone)
+                self.submodules += axi2wishbone
+
+                wishbone_mem = wishbone.SRAM(1024, bus=self.wishbone)
+                self.submodules += wishbone_mem
+
+        dut = DUT()
+
+        # generate writes/reads
+        prng = random.Random(42)
+        writes = []
+        offset = 1
+        for i in range(naccesses):
+            _id = prng.randrange(2**8) if id_rand_enable else i
+            _len = prng.randrange(32) if len_rand_enable else i
+            _data = [prng.randrange(2**32) if data_rand_enable else j for j in range(_len + 1)]
+            writes.append(Write(offset, _data, _id, type=BURST_INCR, len=_len, size=log2_int(32//8)))
+            offset += _len + 1
+        # dummy reads to ensure datas have been written before the effective reads start.
+        dummy_reads = [Read(1023, [0], 0, type=BURST_FIXED, len=0, size=log2_int(32//8)) for _ in range(32)]
+        reads = writes
+
+        # simulation
+        if simultaneous_writes_reads:
+            dut.axi.reads_enable = True
+        else:
+            dut.axi.reads_enable = False # will be set by writes_data_generator
+        generators = [
+            writes_cmd_generator(dut.axi, writes),
+            writes_data_generator(dut.axi, writes),
+            writes_response_generator(dut.axi, writes),
+            reads_cmd_generator(dut.axi, reads),
+            reads_response_data_generator(dut.axi, reads)
+        ]
+        run_simulation(dut, generators)
+        self.assertEqual(self.writes_id_errors, 0)
+        self.assertEqual(self.reads_data_errors, 0)
+        self.assertEqual(self.reads_id_errors, 0)
+        self.assertEqual(self.reads_last_errors, 0)
+
+    # test with no randomness
+    def test_axi2wishbone_writes_then_reads_no_random(self):
+        self._test_axi2wishbone(simultaneous_writes_reads=False)
+
+    # test randomness one parameter at a time
+    def test_axi2wishbone_writes_then_reads_random_bursts(self):
+        self._test_axi2wishbone(
+            simultaneous_writes_reads=False,
+            id_rand_enable=True,
+            len_rand_enable=True,
+            data_rand_enable=True)
+
+    def test_axi2wishbone_random_w_ready(self):
+        self._test_axi2wishbone(w_ready_rand_level=90)
+
+    def test_axi2wishbone_random_b_ready(self):
+        self._test_axi2wishbone(b_ready_rand_level=90)
+
+    def test_axi2wishbone_random_r_ready(self):
+        self._test_axi2wishbone(r_ready_rand_level=90)
+
+    def test_axi2wishbone_random_aw_valid(self):
+        self._test_axi2wishbone(aw_valid_rand_level=90)
+
+    def test_axi2wishbone_random_w_valid(self):
+        self._test_axi2wishbone(w_valid_rand_level=90)
+
+    def test_axi2wishbone_random_ar_valid(self):
+        self._test_axi2wishbone(ar_valid_rand_level=90)
+
+    def test_axi2wishbone_random_r_valid(self):
+        self._test_axi2wishbone(r_valid_rand_level=90)
+
+    # now let's stress things a bit... :)
+    def test_axi2wishbone_random_all(self):
+        self._test_axi2wishbone(
+            simultaneous_writes_reads=False,
+            id_rand_enable=True,
+            len_rand_enable=True,
+            aw_valid_rand_level=50,
+            w_ready_rand_level=50,
+            b_ready_rand_level=50,
+            w_valid_rand_level=50,
+            ar_valid_rand_level=90,
+            r_valid_rand_level=90,
+            r_ready_rand_level=90
+        )