From 767afb0334668555d3522e8b25fde2c160d847e2 Mon Sep 17 00:00:00 2001 From: Luke Kenneth Casson Leighton Date: Tue, 15 Mar 2022 10:06:48 +0000 Subject: [PATCH] add first version of hyperram.py --- lambdasoc/periph/hyperram.py | 203 +++++++++++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 lambdasoc/periph/hyperram.py diff --git a/lambdasoc/periph/hyperram.py b/lambdasoc/periph/hyperram.py new file mode 100644 index 0000000..4e056d5 --- /dev/null +++ b/lambdasoc/periph/hyperram.py @@ -0,0 +1,203 @@ +# Basic Implementation of HyperRAM +# +# Copyright (c) 2019 Antti Lukats +# Copyright (c) 2019 Florent Kermarrec +# Copyright (c) 2021 gatecat [nmigen-soc port] +# Copyright (C) 2021 Luke Kenneth Casson Leighton +# +# 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) + -- 2.30.2