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)])