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)
This commit is contained in:
parent
d1502d4195
commit
4990bf33c0
|
@ -1,125 +0,0 @@
|
||||||
# This file is Copyright (c) 2019 Antti Lukats <antti.lukats@gmail.com>
|
|
||||||
# This file is Copyright (c) 2016-2019 Florent Kermarrec <florent@enjoy-digital.fr>
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
# This file is Copyright (c) 2019 Antti Lukats <antti.lukats@gmail.com>
|
||||||
|
# This file is Copyright (c) 2019 Florent Kermarrec <florent@enjoy-digital.fr>
|
||||||
|
# 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
|
|
@ -0,0 +1,83 @@
|
||||||
|
# This file is Copyright (c) 2019 Florent Kermarrec <florent@enjoy-digital.fr>
|
||||||
|
# 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)])
|
Loading…
Reference in New Issue