litesata: add striping module for use of multiple HDDs.
authorFlorent Kermarrec <florent@enjoy-digital.fr>
Sat, 23 May 2015 12:12:20 +0000 (14:12 +0200)
committerFlorent Kermarrec <florent@enjoy-digital.fr>
Sat, 23 May 2015 12:12:20 +0000 (14:12 +0200)
misoclib/mem/litesata/frontend/__init__.py
misoclib/mem/litesata/frontend/striping.py [new file with mode: 0644]
misoclib/mem/litesata/test/Makefile
misoclib/mem/litesata/test/striping_tb.py [new file with mode: 0644]

index 76102f2c310fe7843e6ddbd4a524c29810b5d3f9..18a7bee329a93894b1c7f119d92244abdbcafebe 100644 (file)
@@ -1,4 +1,5 @@
 from misoclib.mem.litesata.common import *
 from misoclib.mem.litesata.frontend.crossbar import LiteSATACrossbar
 from misoclib.mem.litesata.frontend.arbiter import LiteSATAArbiter
+from misoclib.mem.litesata.frontend.striping import LiteSATAStriping
 from misoclib.mem.litesata.frontend.bist import LiteSATABIST
diff --git a/misoclib/mem/litesata/frontend/striping.py b/misoclib/mem/litesata/frontend/striping.py
new file mode 100644 (file)
index 0000000..60cb205
--- /dev/null
@@ -0,0 +1,131 @@
+from misoclib.mem.litesata.common import *
+from misoclib.mem.litesata.frontend.common import *
+
+
+class LiteSATAStripingTX(Module):
+    """SATA Striping TX
+
+    Split cmds and writes data on N different controllers.
+
+    This module provides a mirroring_mode that is used by the mirroring module to
+    dispatch identicals writes to the controllers. This avoid code duplication in
+    between striping and mirroring modules. In this special case, port's data width
+    is dw (same as controllers)
+    """
+    def __init__(self, n, dw, mirroring_mode=False):
+        self.sink = sink = Sink(command_tx_description(dw*n if not mirroring_mode else dw))
+        self.sources = sources = [Source(command_tx_description(dw)) for i in range(n)]
+
+        # # #
+
+        split = Signal()
+
+        already_acked = Signal(n)
+        self.sync += If(split & sink.stb,
+                already_acked.eq(already_acked | Cat(*[s.ack for s in sources])),
+                If(sink.ack, already_acked.eq(0))
+            )
+
+        self.fsm = fsm = FSM(reset_state="IDLE")
+        self.submodules += fsm
+        fsm.act("IDLE",
+            sink.ack.eq(0),
+            If(sink.stb & sink.sop,
+                NextState("SPLIT")
+            ).Else(
+                sink.ack.eq(1)
+            )
+        )
+
+        # split data and ctrl signals (except stb & ack managed in fsm)
+        for i, s in enumerate(sources):
+            self.comb += Record.connect(sink, s, leave_out=["stb", "ack", "data"])
+            if mirroring_mode:
+                self.comb += s.data.eq(sink.data)
+            else:
+                self.comb += s.data.eq(sink.data[i*dw:(i+1)*dw])
+
+        fsm.act("SPLIT",
+            split.eq(1),
+            [s.stb.eq(sink.stb & ~already_acked[i]) for i, s in enumerate(sources)],
+            sink.ack.eq(optree("&", [s.ack | already_acked[i] for i, s in enumerate(sources)])),
+            If(sink.stb & sink.eop & sink.ack,
+                NextState("IDLE")
+            )
+        )
+
+class LiteSATAStripingRX(Module):
+    """SATA Striping RX
+
+    Combine acknowledges and reads data from N different controllers.
+
+    This module provides a mirroring_mode that is used by the mirroring module to
+    dispatch identicals writes to the controllers. This avoid code duplication in
+    between striping and mirroring modules. In this special case, port's data width
+    is dw (same as controllers)
+    """
+    def __init__(self, n, dw, mirroring_mode=False):
+        self.sinks = sinks = [Sink(command_rx_description(dw)) for i in range(n)]
+        self.source = source = Source(command_rx_description(dw*n if not mirroring_mode else dw))
+
+        # # #
+
+        sop = Signal()
+        self.comb += sop.eq(optree("&", [s.stb & s.sop for s in sinks]))
+
+        self.fsm = fsm = FSM(reset_state="IDLE")
+        self.submodules += fsm
+        fsm.act("IDLE",
+            If(sop,
+                NextState("COMBINE")
+            )
+        )
+
+        # use first sink for ctrl signals (except for stb, ack & failed)
+        self.comb += Record.connect(sinks[0], source, leave_out=["stb", "ack", "failed", "data"])
+               # combine datas
+        if mirroring_mode:
+            self.comb += source.data.eq(0) # mirroring only used for writes
+        else:
+            for i, s in enumerate(sinks):
+                self.comb += source.data[i*dw:(i+1)*dw].eq(s.data)
+
+
+        fsm.act("COMBINE",
+            source.failed.eq(optree("|", [s.failed for s in sinks])), # XXX verify this is enough
+            source.stb.eq(optree("&", [s.stb for s in sinks])),
+            [s.ack.eq(source.stb & source.ack) for s in sinks],
+            If(source.stb & source.eop & source.ack,
+                NextState("IDLE")
+            )
+        )
+
+
+class LiteSATAStriping(Module):
+    """SATA Striping
+
+    Segment data so that data is stored on N different controllers.
+                     +----> controller0 (dw)
+    port (N*dw) <----+----> controllerX (dw)
+                     +----> controllerN (dw)
+
+    Characteristics:
+        - master's visible capacity = N x controller's visible capacity
+        - master's throughput = N x (slowest) controller's throughput
+
+    Can be used to increase capacity and writes/reads throughput.
+    """
+    def __init__(self, controllers):
+
+        # # #
+        n = len(controllers)
+        dw = flen(controllers[0].sink.data)
+
+        self.submodules.tx = LiteSATAStripingTX(n, dw)
+        self.submodules.rx = LiteSATAStripingRX(n, dw)
+        for i in range(n):
+            self.comb += [
+                Record.connect(self.tx.sources[i], controllers[i].sink),
+                Record.connect(controllers[i].source, self.rx.sinks[i])
+            ]
+        self.sink, self.source = self.tx.sink, self.rx.source
index 067511bb612b18cd654ce176e3fa0e41e907cbfd..0ebebb456963386a9de7c9208ad47148091aa9e4 100644 (file)
@@ -29,5 +29,8 @@ command_tb:
 bist_tb:
        $(CMD) bist_tb.py
 
+striping_tb:
+       $(CMD) striping_tb.py
+
 clean:
        rm crc scrambler *.vcd
diff --git a/misoclib/mem/litesata/test/striping_tb.py b/misoclib/mem/litesata/test/striping_tb.py
new file mode 100644 (file)
index 0000000..eb2fcf9
--- /dev/null
@@ -0,0 +1,68 @@
+from misoclib.mem.litesata.common import *
+from misoclib.mem.litesata.core import LiteSATACore
+from misoclib.mem.litesata.frontend.common import *
+from misoclib.mem.litesata.frontend.crossbar import LiteSATACrossbar
+from misoclib.mem.litesata.frontend.bist import LiteSATABISTGenerator, LiteSATABISTChecker
+from misoclib.mem.litesata.frontend.striping import LiteSATAStriping
+
+from misoclib.mem.litesata.test.common import *
+from misoclib.mem.litesata.test.model.hdd import *
+
+
+class TB(Module):
+    def __init__(self):
+        self.submodules.hdd0 = HDD(n=0,
+                link_debug=False, link_random_level=0,
+                transport_debug=False, transport_loopback=False,
+                hdd_debug=True)
+        self.submodules.core0 = LiteSATACore(self.hdd0.phy)
+
+        self.submodules.hdd1 = HDD(n=1,
+                link_debug=False, link_random_level=0,
+                transport_debug=False, transport_loopback=False,
+                hdd_debug=True)
+        self.submodules.core1 = LiteSATACore(self.hdd1.phy)
+
+        self.submodules.striping = LiteSATAStriping([self.core0, self.core1])
+        self.submodules.crossbar = LiteSATACrossbar(self.striping)
+
+        self.submodules.generator = LiteSATABISTGenerator(self.crossbar.get_port())
+        self.submodules.checker = LiteSATABISTChecker(self.crossbar.get_port())
+
+    def gen_simulation(self, selfp):
+        hdd0 = self.hdd0
+        hdd0.malloc(0, 64)
+        hdd1 = self.hdd1
+        hdd1.malloc(0, 64)
+        sector = 0
+        count = 1
+        generator = selfp.generator
+        checker = selfp.checker
+        while True:
+            # write data
+            generator.sector = sector
+            generator.count = count
+            generator.start = 1
+            yield
+            generator.start = 0
+            yield
+            while generator.done == 0:
+                yield
+
+            # verify data
+            checker.sector = sector
+            checker.count = count
+            checker.start = 1
+            yield
+            checker.start = 0
+            yield
+            while checker.done == 0:
+                yield
+            print("errors {}".format(checker.errors))
+
+            # prepare next iteration
+            sector += 1
+            count = max((count + 1)%8, 1)
+
+if __name__ == "__main__":
+    run_simulation(TB(), ncycles=8192*2, vcd_name="my.vcd", keep_files=True)