mirror of
https://github.com/enjoy-digital/litex.git
synced 2025-01-04 09:52:26 -05:00
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:
parent
6c93db0f14
commit
dbde036162
2 changed files with 286 additions and 0 deletions
198
litex/soc/cores/hyperbus.py
Normal file
198
litex/soc/cores/hyperbus.py
Normal 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
88
test/test_hyperbus.py
Normal 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")
|
Loading…
Reference in a new issue