From 4990bf33c0f921d870d08d5633ff5921e1d0d2e6 Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Fri, 16 Aug 2019 13:56:56 +0200 Subject: [PATCH] soc/core: simplify/cleanup HyperRAM core - 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 | 125 -------------------------------- litex/soc/cores/hyperbus.py | 107 +++++++++++++++++++++++++++ test/test_hyperbus.py | 83 +++++++++++++++++++++ 3 files changed, 190 insertions(+), 125 deletions(-) delete mode 100644 litex/soc/cores/hyper_memory.py create mode 100644 litex/soc/cores/hyperbus.py create mode 100644 test/test_hyperbus.py diff --git a/litex/soc/cores/hyper_memory.py b/litex/soc/cores/hyper_memory.py deleted file mode 100644 index 18b13393e..000000000 --- a/litex/soc/cores/hyper_memory.py +++ /dev/null @@ -1,125 +0,0 @@ -# This file is Copyright (c) 2019 Antti Lukats -# This file is Copyright (c) 2016-2019 Florent Kermarrec - -# 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 index 000000000..c1dc0b8cd --- /dev/null +++ b/litex/soc/cores/hyperbus.py @@ -0,0 +1,107 @@ +# This file is Copyright (c) 2019 Antti Lukats +# This file is Copyright (c) 2019 Florent Kermarrec +# 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 index 000000000..b9209ad2d --- /dev/null +++ b/test/test_hyperbus.py @@ -0,0 +1,83 @@ +# This file is Copyright (c) 2019 Florent Kermarrec +# 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)])