soc/core: simplify/cleanup HyperRAM core
authorFlorent Kermarrec <florent@enjoy-digital.fr>
Fri, 16 Aug 2019 11:56:56 +0000 (13:56 +0200)
committerFlorent Kermarrec <florent@enjoy-digital.fr>
Fri, 16 Aug 2019 12:04:58 +0000 (14:04 +0200)
- rename core to hyperbus.
- change layout (cs_n with variable length instead of cs0_n, cs1_n).
- use DifferentialOutput when differential clock is used.
- add test (python3 -m unittest test.test_hyperbus).

Usage example:
from litex.soc.cores.hyperbus import HyperRAM
self.submodules.hyperram = HyperRAM(platform.request("hyperram"))
self.add_wb_slave(mem_decoder(self.mem_map["hyperram"]), self.hyperram.bus)
self.add_memory_region("hyperram", self.mem_map["hyperram"], 8*1024*1024)

litex/soc/cores/hyper_memory.py [deleted file]
litex/soc/cores/hyperbus.py [new file with mode: 0644]
test/test_hyperbus.py [new file with mode: 0644]

diff --git a/litex/soc/cores/hyper_memory.py b/litex/soc/cores/hyper_memory.py
deleted file mode 100644 (file)
index 18b1339..0000000
+++ /dev/null
@@ -1,125 +0,0 @@
-# This file is Copyright (c) 2019 Antti Lukats <antti.lukats@gmail.com>
-# This file is Copyright (c) 2016-2019 Florent Kermarrec <florent@enjoy-digital.fr>
-
-# License: BSD
-
-#
-#
-#
-
-from migen import *
-from migen.genlib.misc import timeline
-
-from litex.gen import *
-
-from litex.soc.interconnect import wishbone
-from litex.soc.interconnect.csr import *
-
-
-class HyperMemporyCommon(Module):
-    def __init__(self, pads):
-        self.pads = pads
-
-class HyperRAM(HyperMemporyCommon):
-    def __init__(self, pads):
-        """
-        HyperRAM simple core for LiteX
-        This core should always just work on any FPGA platorm it is fully vendor neutral
-        No configuration, no software setup, ready after poweron, fixed latency
-
-        """
-        HyperMemporyCommon.__init__(self, pads)
-
-        if hasattr(pads, "rst_n"):
-            self.comb += pads.rst_n.eq(1)
-        if hasattr(pads, "cs1_n"):
-            self.comb += pads.cs1_n.eq(1)
-
-        # Tristate pads
-        dq = TSTriple(8)
-        self.specials.dq = dq.get_tristate(pads.dq)
-        rwds = TSTriple(1)
-        self.specials.rwds = rwds.get_tristate(pads.rwds)
-
-        # Wishbone
-        self.bus = bus = wishbone.Interface()
-        sr = Signal(48)
-
-        dq_oe = Signal(reset=0)
-        rwds_oe = Signal(reset=0)
-        cs_int = Signal(reset=1)
-
-        self.comb += [
-            bus.dat_r.eq(sr),
-            dq.oe.eq(dq_oe),
-            dq.o.eq(sr[-8:]),
-            rwds.oe.eq(rwds_oe),
-            pads.cs0_n.eq(cs_int)
-        ]
-
-        # we generate complementaty clk out for emulated differential output
-        clk_p = Signal(1)
-        clk_n = Signal(1)
-
-        self.comb += pads.clk.eq(clk_p)
-        # if negative is defined drive complementary clock out
-        if hasattr(pads, "clk_n"):
-            self.comb += pads.clk_n.eq(clk_n)
-        # 1 sys clock delay needed to adjust input timings?
-        dqi = Signal(8)
-        self.sync += [
-                dqi.eq(dq.i)
-        ]
-        # hyper RAM clock generator and 48 bit byte shifter
-        i = Signal(max=4)
-        self.sync += [
-            If(i == 0,
-                sr.eq(Cat(dqi, sr[:-8])),
-            ),
-            If(i == 1,
-                clk_p.eq(~cs_int), # 1
-                clk_n.eq(cs_int)   # 0
-            ),
-            If(i == 2,
-                sr.eq(Cat(dqi, sr[:-8]))
-            ),
-            If(i == 3,
-                i.eq(0),
-                clk_p.eq(0),      # 1
-                clk_n.eq(1)       # 0
-            ).Else(
-                i.eq(i + 1)
-            )
-        ]
-        # signals to use CA or data to write
-        CA = Signal(48)
-        # combine bits to create CA bytes
-        self.comb += [
-            CA[47].eq(~self.bus.we),
-            CA[45].eq(1),
-            CA[16:35].eq(self.bus.adr[2:21]),
-            CA[1:3].eq(self.bus.adr[0:2]),
-            CA[0].eq(0),
-        ]
-        z = Replicate(0, 16)
-        seq = [
-            (3,        []),
-            (12,       [cs_int.eq(0), dq_oe.eq(1), sr.eq(CA)]), # 6 clock edges for command transmit
-            (44,       [dq_oe.eq(0)]),                          # 6+6 latency default
-            (2,        [dq_oe.eq(self.bus.we), rwds_oe.eq(self.bus.we), rwds.o.eq(~bus.sel[0]), sr.eq(Cat(z, self.bus.dat_w))]), # 4 edges to write data
-            (2,        [rwds.o.eq(~bus.sel[1])]), # 4 edges to write data
-            (2,        [rwds.o.eq(~bus.sel[2])]), # 4 edges to write data
-            (2,        [rwds.o.eq(~bus.sel[3])]), # 4 edges to write data
-            (2,        [cs_int.eq(1), rwds_oe.eq(0), dq_oe.eq(0)]),
-            (1,        [bus.ack.eq(1)]), # is 1 also OK?
-            (1,        [bus.ack.eq(0)]), #
-            (0,        []),
-        ]
-
-        t, tseq = 0, []
-        for dt, a in seq:
-            tseq.append((t, a))
-            t += dt
-
-        self.sync += timeline(bus.cyc & bus.stb & (i == 1), tseq)
-
diff --git a/litex/soc/cores/hyperbus.py b/litex/soc/cores/hyperbus.py
new file mode 100644 (file)
index 0000000..c1dc0b8
--- /dev/null
@@ -0,0 +1,107 @@
+# This file is Copyright (c) 2019 Antti Lukats <antti.lukats@gmail.com>
+# This file is Copyright (c) 2019 Florent Kermarrec <florent@enjoy-digital.fr>
+# License: BSD
+
+from migen import *
+from migen.genlib.misc import timeline
+from migen.genlib.io import DifferentialOutput
+
+from litex.soc.interconnect import wishbone
+
+# HyperRAM -----------------------------------------------------------------------------------------
+
+class HyperRAM(Module):
+    """HyperRAM
+
+    Provides a very simple/minimal HyperRAM core that should work with all FPGA/HyperRam chips:
+    - FPGA vendor agnostic.
+    - no setup/chip configuration (use default latency).
+
+    This core favrors portability and ease of use over performance.
+    """
+    def __init__(self, pads):
+        self.pads = pads
+        self.bus  = bus = wishbone.Interface()
+
+        # # #
+
+        clk       = Signal()
+        clk_phase = Signal(2)
+        cs        = Signal()
+        ca        = Signal(48)
+        sr        = Signal(48)
+        dq        = self.add_tristate(pads.dq) if not hasattr(pads.dq, "oe") else pads.dq
+        rwds      = self.add_tristate(pads.rwds) if not hasattr(pads.rwds, "oe") else pads.rwds
+
+        # Drive rst_n, cs_n, clk from internal signals ---------------------------------------------
+        if hasattr(pads, "rst_n"):
+            self.comb += pads.rst_n.eq(1)
+        self.comb += pads.cs_n[0].eq(~cs)
+        assert len(pads.cs_n) <= 2
+        if len(pads.cs_n) == 2:
+            self.comb += pads.cs_n[1].eq(1)
+        if hasattr(pads, "clk"):
+            self.comb += pads.clk.eq(clk)
+        else:
+            self.specials += DifferentialOutput(clk, pads.clk_p, pads.clk_n)
+
+        # Clock Generation (sys_clk/4) -------------------------------------------------------------
+        self.sync += clk_phase.eq(clk_phase + 1)
+        cases = {}
+        cases[1] = clk.eq(cs) # Set pads clk on 90° (if cs is set)
+        cases[3] = clk.eq(0)  # Clear pads clk on 270°
+        self.sync += Case(clk_phase, cases)
+
+        # Data Shift Register (for write and read) -------------------------------------------------
+        dqi = Signal(8)
+        self.sync += dqi.eq(dq.i) # Sample on 90° and 270°
+        cases = {}
+        cases[0] = sr.eq(Cat(dqi, sr[:-8])) # Shift on 0°
+        cases[2] = sr.eq(Cat(dqi, sr[:-8])) # Shift on 180°
+        self.sync += Case(clk_phase, cases)
+        self.comb += [
+            bus.dat_r.eq(sr), # To Wisbone
+            dq.o.eq(sr[-8:]), # To HyperRAM
+        ]
+
+        # Command generation -----------------------------------------------------------------------
+        self.comb += [
+            ca[47].eq(~self.bus.we),          # R/W#
+            ca[45].eq(1),                     # Burst Type (Linear)
+            ca[16:35].eq(self.bus.adr[2:21]), # Row & Upper Column Address
+            ca[1:3].eq(self.bus.adr[0:2]),    # Lower Column Address
+            ca[0].eq(0),                      # Lower Column Address
+        ]
+
+        # Sequencer --------------------------------------------------------------------------------
+        dt_seq = [
+            # DT,  Action
+            (3,    []),
+            (12,   [cs.eq(1), dq.oe.eq(1), sr.eq(ca)]),    # Command: 6 clk
+            (44,   [dq.oe.eq(0)]),                         # Latency(default): 2*6 clk
+            (2,    [dq.oe.eq(self.bus.we),                 # Write/Read data byte: 2 clk
+                    sr[:16].eq(0),
+                    sr[16:].eq(self.bus.dat_w),
+                    rwds.oe.eq(self.bus.we),
+                    rwds.o.eq(~bus.sel[0])]),
+            (2,    [rwds.o.eq(~bus.sel[1])]),              # Write/Read data byte: 2 clk
+            (2,    [rwds.o.eq(~bus.sel[2])]),              # Write/Read data byte: 2 clk
+            (2,    [rwds.o.eq(~bus.sel[3])]),              # Write/Read data byte: 2 clk
+            (2,    [cs.eq(0), rwds.oe.eq(0), dq.oe.eq(0)]),
+            (1,    [bus.ack.eq(1)]),
+            (1,    [bus.ack.eq(0)]),
+            (0,    []),
+        ]
+        # Convert delta-time sequencer to time sequencer
+        t_seq = []
+        t_seq_start = (clk_phase == 1)
+        t = 0
+        for dt, a in dt_seq:
+            t_seq.append((t, a))
+            t += dt
+        self.sync += timeline(bus.cyc & bus.stb & t_seq_start, t_seq)
+
+    def add_tristate(self, pad):
+        t = TSTriple(len(pad))
+        self.specials += t.get_tristate(pad)
+        return t
diff --git a/test/test_hyperbus.py b/test/test_hyperbus.py
new file mode 100644 (file)
index 0000000..b9209ad
--- /dev/null
@@ -0,0 +1,83 @@
+# This file is Copyright (c) 2019 Florent Kermarrec <florent@enjoy-digital.fr>
+# License: BSD
+
+import unittest
+
+from migen import *
+
+from litex.soc.cores.hyperbus import HyperRAM
+
+def c2bool(c):
+    return {"-": 1, "_": 0}[c]
+
+
+class Pads: pass
+
+
+class HyperRamPads:
+    def __init__(self):
+        self.clk  = Signal()
+        self.cs_n = Signal()
+        self.dq   = Record([("oe", 1), ("o", 8), ("i", 8)])
+        self.rwds = Record([("oe", 1), ("o", 1), ("i", 1)])
+
+
+class TestHyperBus(unittest.TestCase):
+    def test_hyperram_syntax(self):
+        pads = Record([("clk", 1), ("cs_n", 1), ("dq", 8), ("rwds", 1)])
+        hyperram = HyperRAM(pads)
+
+        pads = Record([("clk_p", 1), ("clk_n", 1), ("cs_n", 1), ("dq", 8), ("rwds", 1)])
+        hyperram = HyperRAM(pads)
+
+    def test_hyperram_write(self):
+        def fpga_gen(dut):
+            yield from dut.bus.write(0x1234, 0xdeadbeef)
+            yield
+
+        def hyperram_gen(dut):
+            clk     = "___--__--__--__--__--__--__--__--__--__--__--__--__--__--__--__--_______"
+            cs_n    = "--________________________________________________________________------"
+            dq_oe   = "__------------____________________________________________--------______"
+            dq_o    = "002000048d000000000000000000000000000000000000000000000000deadbeef000000"
+            rwds_oe = "__________________________________________________________--------______"
+            rwds_o  = "________________________________________________________________________"
+            for i in range(3):
+                yield
+            for i in range(len(clk)):
+                self.assertEqual(c2bool(clk[i]), (yield dut.pads.clk))
+                self.assertEqual(c2bool(cs_n[i]), (yield dut.pads.cs_n))
+                self.assertEqual(c2bool(dq_oe[i]), (yield dut.pads.dq.oe))
+                self.assertEqual(int(dq_o[2*(i//2):2*(i//2)+2], 16), (yield dut.pads.dq.o))
+                self.assertEqual(c2bool(rwds_oe[i]), (yield dut.pads.rwds.oe))
+                self.assertEqual(c2bool(rwds_o[i]), (yield dut.pads.rwds.o))
+                yield
+
+        dut = HyperRAM(HyperRamPads())
+        run_simulation(dut, [fpga_gen(dut), hyperram_gen(dut)])
+
+    def test_hyperram_read(self):
+        def fpga_gen(dut):
+            dat = yield from dut.bus.read(0x1234)
+            self.assertEqual(dat, 0xdeadbeef)
+
+        def hyperram_gen(dut):
+            clk     = "___--__--__--__--__--__--__--__--__--__--__--__--__--__--__--__--_______"
+            cs_n    = "--________________________________________________________________------"
+            dq_oe   = "__------------__________________________________________________________"
+            dq_o    = "00a000048d00000000000000000000000000000000000000000000000000000000000000"
+            dq_i    = "0000000000000000000000000000000000000000000000000000000000deadbeef000000"
+            rwds_oe = "________________________________________________________________________"
+            for i in range(3):
+                yield
+            for i in range(len(clk)):
+                yield dut.pads.dq.i.eq(int(dq_i[2*(i//2):2*(i//2)+2], 16))
+                self.assertEqual(c2bool(clk[i]), (yield dut.pads.clk))
+                self.assertEqual(c2bool(cs_n[i]), (yield dut.pads.cs_n))
+                self.assertEqual(c2bool(dq_oe[i]), (yield dut.pads.dq.oe))
+                self.assertEqual(int(dq_o[2*(i//2):2*(i//2)+2], 16), (yield dut.pads.dq.o))
+                self.assertEqual(c2bool(rwds_oe[i]), (yield dut.pads.rwds.oe))
+                yield
+
+        dut = HyperRAM(HyperRamPads())
+        run_simulation(dut, [fpga_gen(dut), hyperram_gen(dut)])