--- /dev/null
+# 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)
+