add first version of hyperram.py
authorLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Tue, 15 Mar 2022 10:06:48 +0000 (10:06 +0000)
committerLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Tue, 15 Mar 2022 10:06:48 +0000 (10:06 +0000)
lambdasoc/periph/hyperram.py [new file with mode: 0644]

diff --git a/lambdasoc/periph/hyperram.py b/lambdasoc/periph/hyperram.py
new file mode 100644 (file)
index 0000000..4e056d5
--- /dev/null
@@ -0,0 +1,203 @@
+# Basic Implementation of HyperRAM
+# 
+# Copyright (c) 2019 Antti Lukats <antti.lukats@gmail.com>
+# Copyright (c) 2019 Florent Kermarrec <florent@enjoy-digital.fr>
+# Copyright (c) 2021 gatecat <gatecat@ds0.me> [nmigen-soc port]
+# Copyright (C) 2021 Luke Kenneth Casson Leighton <lkcl@lkcl.net>
+#
+# Code from Lukats, Kermarrec and gatecat is Licensed BSD-2-Clause
+#
+# Modifications for the Libre-SOC Project funded by NLnet and NGI POINTER
+# under EU Grants 871528 and 957073, and Licensed under the LGPLv3+ License
+
+
+from nmigen import (Elaboratable, Module, Signal, Record, Cat)
+from nmigen.cli import rtlil
+
+from nmigen_soc import wishbone
+from nmigen_soc.memory import MemoryMap
+from lambdasoc.periph import Peripheral
+
+
+# for Migen compat
+def timeline(m, trigger, events):
+    lastevent = max([e[0] for e in events])
+    counter = Signal(range(lastevent+1))
+
+    # insert counter reset if it doesn't naturally overflow
+    # (test if lastevent+1 is a power of 2)
+    with m.If(((lastevent & (lastevent + 1)) != 0) & (counter == lastevent)):
+        m.d.sync += counter.eq(0)
+    with m.Elif(counter != 0):
+        m.d.sync += counter.eq(counter + 1)
+    with m.Elif(trigger):
+        m.d.sync += counter.eq(1)
+
+    def get_cond(e):
+        if e[0] == 0:
+            return trigger & (counter == 0)
+        else:
+            return counter == e[0]
+    for ev in events:
+        with m.If(get_cond(ev)):
+            m.d.sync += ev[1]
+
+
+# HyperRAM ASIC PHY -----------------------------------------------------------
+
+class HyperRAMASICPhy(Elaboratable):
+    def __init__(self, io):
+        self.io = io
+        self.clk = clk = Signal()
+        self.cs  = cs = Signal()
+
+        self.dq_o  = dq_o  = Signal(8)
+        self.dq_i  = dq_i  = Signal(8)
+        self.dq_oe = dq_oe = Signal()
+
+        self.rwds_o  = rwds_o  = Signal.like(self.io["rwds_o"])
+        self.rwds_oe = rwds_oe = Signal()
+
+    def elaborate(self, platform):
+        m = Module()
+        comb = m.d.comb
+        clk, cs = self.clk, self.cs
+        dq_o, dq_i, dq_oe = self.dq_o, self.dq_i, self.dq_oe
+        rwds_o, rwds_oe = self.rwds_o, self.rwds_oe
+
+        comb += [
+            self.io["rwds_o"].eq(rwds_o),
+            self.io["csn_o"].eq(~cs),
+            self.io["csn_oe"].eq(0),
+            self.io["clk_o"].eq(clk),
+            self.io["clk_oe"].eq(0),
+            self.io["rwds_oe"].eq(~rwds_oe),
+        ]
+
+        for i in range(8):
+            comb += [
+                self.io[f"d{i}_o"].eq(dq_o[i]),
+                self.io[f"d{i}_oe"].eq(~dq_oe),
+                dq_i[i].eq(self.io[f"d{i}_i"])
+            ]
+
+        return m
+
+    def ports(self):
+        return list(self.io.fields.values())
+
+# HyperRAM --------------------------------------------------------------------
+
+class HyperRAM(Peripheral, Elaboratable):
+    """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 favors portability and ease of use over performance.
+    """
+    def __init__(self, *, io, phy_kls):
+        super().__init__()
+        self.io = io
+        self.phy = phy_kls(io)
+        self.bus = wishbone.Interface(addr_width=21,
+                                      data_width=32, granularity=8)
+        mmap = MemoryMap(addr_width=23, data_width=8)
+        mmap.add_resource(object(), name="hyperram", size=2**23)
+        self.bus.memory_map = mmap
+        self.size = 2**23
+        # # #
+
+    def elaborate(self, platform):
+        m = Module()
+        m.submodules.phy = self.phy
+
+        clk       = self.phy.clk
+        clk_phase = Signal(2)
+        cs        = self.phy.cs
+        ca        = Signal(48)
+        sr        = Signal(48)
+
+        dq_o = self.phy.dq_o
+        dq_i = self.phy.dq_i
+        dq_oe = self.phy.dq_oe
+
+        rwds_o = self.phy.rwds_o
+        rwds_oe = self.phy.rwds_oe
+
+        # Clock Generation (sys_clk/4) -----------------------------------
+        m.d.sync += clk_phase.eq(clk_phase + 1)
+        with m.Switch(clk_phase):
+            with m.Case(1):
+                m.d.sync += clk.eq(cs)
+            with m.Case(3):
+                m.d.sync += clk.eq(0)
+
+        # Data Shift Register (for write and read) ------------------------
+        dqi = Signal(8)
+        m.d.sync += dqi.eq(dq_i) # Sample on 90° and 270°
+        with m.Switch(clk_phase):
+            with m.Case(0, 2):
+                m.d.sync += sr.eq(Cat(dqi, sr[:-8]))
+
+        m.d.comb += [
+            self.bus.dat_r.eq(sr), # To Wisbone
+            dq_o.eq(sr[-8:]), # To HyperRAM
+        ]
+
+        # Command generation ----------------------------------------------
+        m.d.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(dflt): 2*6 clk
+         (2,    [dq_oe.eq(self.bus.we),              # Wr/Rd data byte: 2 clk
+                 sr[:16].eq(0),
+                 sr[16:].eq(self.bus.dat_w),
+                 rwds_oe.eq(self.bus.we),
+                 rwds_o.eq(~self.bus.sel[3])]),
+         (2,    [rwds_o.eq(~self.bus.sel[2])]),      # Wr/Rd data byte: 2 clk
+         (2,    [rwds_o.eq(~self.bus.sel[1])]),      # Wr/Rd data byte: 2 clk
+         (2,    [rwds_o.eq(~self.bus.sel[0])]),      # Wr/Rd data byte: 2 clk
+         (2,    [cs.eq(0), rwds_oe.eq(0), dq_oe.eq(0)]),
+         (1,    [self.bus.ack.eq(1)]),
+         (1,    [self.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
+        timeline(m, self.bus.cyc & self.bus.stb & t_seq_start, t_seq)
+        return m
+
+    def ports(self):
+        return self.phy.ports() + list(self.bus.fields.values())
+
+
+if __name__ == '__main__':
+    layout=[('rwds_o', 1), ('rwds_oe', 1),
+            ('csn_o', 1), ('csn_oe', 1),
+            ('clk_o', 1), ('clk_oe', 1)]
+    for i in range(8):
+        layout += [('d%d_o' % i, 1), ('d%d_oe' % i, 1), ('d%d_i' % i, 1)]
+    io = Record(layout=layout)
+    dut = HyperRAM(io=io, phy_kls=HyperRAMASICPhy)
+    vl = rtlil.convert(dut, ports=dut.ports())
+    with open("test_hyperram.il", "w") as f:
+        f.write(vl)
+