soc/cores: Re-integrated generic/portable HyperBus/HyperRAM core from LiteHyperBus.

The generic version of the HyperRAM core is simple enough to be directly integrated in LiteX
which avoid an additional dependency.
This commit is contained in:
Florent Kermarrec 2022-03-01 08:59:00 +01:00
parent 6c93db0f14
commit dbde036162
2 changed files with 286 additions and 0 deletions

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

@ -0,0 +1,198 @@
#
# This file is part of LiteHyperBus
#
# Copyright (c) 2019-2022 Florent Kermarrec <florent@enjoy-digital.fr>
# Copyright (c) 2019 Antti Lukats <antti.lukats@gmail.com>
# Copyright (c) 2021 Franck Jullien <franck.jullien@collshade.fr>
# SPDX-License-Identifier: BSD-2-Clause
from migen import *
from migen.genlib.misc import timeline
from litex.build.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 favors portability and ease of use over performance.
"""
def __init__(self, pads, latency=6):
self.pads = pads
self.bus = bus = wishbone.Interface()
# # #
clk = Signal()
clk_phase = Signal(2)
cs = Signal()
ca = Signal(48)
ca_active = Signal()
sr = Signal(48)
sr_new = 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
dw = len(pads.dq) if not hasattr(pads.dq, "oe") else len(pads.dq.o)
assert dw in [8, 16]
# Drive Control Signals --------------------------------------------------------------------
# Rst.
if hasattr(pads, "rst_n"):
self.comb += pads.rst_n.eq(1)
# CSn.
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)
# Clk.
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-In Register -------------------------------------------------------------------
dqi = Signal(dw)
self.sync += dqi.eq(dq.i) # Sample on 90° and 270°
self.comb += [
sr_new.eq(Cat(dqi, sr[:-dw])),
If(ca_active,
sr_new.eq(Cat(dqi[:8], sr[:-8])) # Only 8-bit during Command/Address.
)
]
self.sync += If(clk_phase[0] == 0, sr.eq(sr_new)) # Shift on 0° and 180°
# Data Shift-Out Register ------------------------------------------------------------------
self.comb += [
bus.dat_r.eq(sr_new),
If(dq.oe,
dq.o.eq(sr[-dw:]),
If(ca_active,
dq.o.eq(sr[-8:]) # Only 8-bit during Command/Address.
)
)
]
# Command generation -----------------------------------------------------------------------
ashift = {8:1, 16:0}[dw]
self.comb += [
ca[47].eq(~bus.we), # R/W#
ca[45].eq(1), # Burst Type (Linear)
ca[16:45].eq(bus.adr[3-ashift:]), # Row & Upper Column Address
ca[ashift:3].eq(bus.adr), # Lower Column Address
]
# Latency count starts from the middle of the command (thus the -4). In fixed latency mode
# (default), latency is 2 x Latency count. We have 4 x sys_clk per RAM clock:
latency_cycles = (latency * 2 * 4) - 4
# Bus Latch --------------------------------------------------------------------------------
bus_adr = Signal(32)
bus_we = Signal()
bus_sel = Signal(4)
bus_latch = Signal()
self.sync += If(bus_latch,
If(bus.we,
sr.eq(Cat(Signal(16), bus.dat_w)),
),
bus_we.eq(bus.we),
bus_sel.eq(bus.sel),
bus_adr.eq(bus.adr)
)
# FSM (Sequencer) --------------------------------------------------------------------------
cycles = Signal(8)
first = Signal()
self.submodules.fsm = fsm = FSM(reset_state="IDLE")
fsm.act("IDLE",
NextValue(first, 1),
If(bus.cyc & bus.stb,
If(clk_phase == 0,
NextValue(sr, ca),
NextState("SEND-COMMAND-ADDRESS")
)
)
)
fsm.act("SEND-COMMAND-ADDRESS",
# Set CSn.
cs.eq(1),
# Send Command on DQ.
ca_active.eq(1),
dq.oe.eq(1),
# Wait for 6*2 cycles...
If(cycles == (6*2 - 1),
NextState("WAIT-LATENCY")
)
)
fsm.act("WAIT-LATENCY",
# Set CSn.
cs.eq(1),
# Wait for Latency cycles...
If(cycles == (latency_cycles - 1),
# Latch Bus.
bus_latch.eq(1),
# Early Write Ack (to allow bursting).
bus.ack.eq(bus.we),
NextState("READ-WRITE-DATA0")
)
)
states = {8:4, 16:2}[dw]
for n in range(states):
fsm.act(f"READ-WRITE-DATA{n}",
# Set CSn.
cs.eq(1),
# Send Data on DQ/RWDS (for write).
If(bus_we,
dq.oe.eq(1),
rwds.oe.eq(1),
*[rwds.o[dw//8-1-i].eq(~bus_sel[4-1-n*dw//8-i]) for i in range(dw//8)],
),
# Wait for 2 cycles (since HyperRAM's Clk = sys_clk/4).
If(cycles == (2 - 1),
# Set next default state (with rollover for bursts).
NextState(f"READ-WRITE-DATA{(n + 1)%states}"),
# On last state, see if we can continue the burst or if we should end it.
If(n == (states - 1),
NextValue(first, 0),
# Continue burst when a consecutive access is ready.
If(bus.stb & bus.cyc & (bus.we == bus_we) & (bus.adr == (bus_adr + 1)),
# Latch Bus.
bus_latch.eq(1),
# Early Write Ack (to allow bursting).
bus.ack.eq(bus.we)
# Else end the burst.
).Elif(bus_we | ~first,
NextState("IDLE")
)
),
# Read Ack (when dat_r ready).
If((n == 0) & ~first,
bus.ack.eq(~bus_we),
)
)
)
fsm.finalize()
self.sync += cycles.eq(cycles + 1)
self.sync += If(fsm.next_state != fsm.state, cycles.eq(0))
def add_tristate(self, pad):
t = TSTriple(len(pad))
self.specials += t.get_tristate(pad)
return t

88
test/test_hyperbus.py Normal file
View File

@ -0,0 +1,88 @@
#
# This file is part of LiteHyperBus
#
# Copyright (c) 2019-2022 Florent Kermarrec <florent@enjoy-digital.fr>
# SPDX-License-Identifier: BSD-2-Clause
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, dw=8):
self.clk = Signal()
self.cs_n = Signal()
self.dq = Record([("oe", 1), ("o", dw), ("i", dw)])
self.rwds = Record([("oe", 1), ("o", dw//8), ("i", dw//8)])
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, sel=0b1001)
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)], vcd_name="sim.vcd")
def test_hyperram_read(self):
def fpga_gen(dut):
dat = yield from dut.bus.read(0x1234)
self.assertEqual(dat, 0xdeadbeef)
dat = yield from dut.bus.read(0x1235)
self.assertEqual(dat, 0xcafefade)
def hyperram_gen(dut):
clk = "___--__--__--__--__--__--__--__--__--__--__--__--__--__--__--__--__--__--__--__--_"
cs_n = "--________________________________________________________________________________"
dq_oe = "__------------____________________________________________________________________"
dq_o = "00a000048d000000000000000000000000000000000000000000000000000000000000000000000000"
dq_i = "0000000000000000000000000000000000000000000000000000000000deadbeefcafefade00000000"
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)], vcd_name="sim.vcd")