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:
Florent Kermarrec 2019-08-16 13:56:56 +02:00
parent d1502d4195
commit 4990bf33c0
3 changed files with 190 additions and 125 deletions

View File

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

107
litex/soc/cores/hyperbus.py Normal file
View File

@ -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

83
test/test_hyperbus.py Normal file
View File

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