Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 53 additions & 19 deletions litedram/phy/nxddrphy.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

from litedram.common import *
from litedram.phy.dfi import *
from litedram.phy.utils import ConstBitSlip

# BitSlip ------------------------------------------------------------------------------------------

Expand All @@ -49,6 +50,18 @@ def __init__(self, dw, rst=None, slp=None, cycles=1):
cases[i] = self.o.eq(r[i:dw+i])
self.comb += Case(value, cases)

# Nexus DDR PHY Write BitSlip ----------------------------------------------------------------------

class _NexusDDRPHYWriteBitSlip(ConstBitSlip):
def __init__(self, dw):
assert (dw % 2) == 0
ConstBitSlip.__init__(self,
dw = dw,
slp = dw//2,
cycles = 1,
register = False,
)

# Lattice Nexus DDR PHY Initialization --------------------------------------------------------------

class NexusDDRPHYInit(Module):
Expand Down Expand Up @@ -170,10 +183,6 @@ def __init__(self, pads,
rdphase = get_sys_phase(nphases, cl_sys_latency, cl)
wrphase = get_sys_phase(nphases, cwl_sys_latency, cwl)

# Otherwise writes are off by half a SCLK cycle
# TODO: should we implement write DQ/DQS bitslip like s7ddrphy?
cwl += 1

self.settings = PhySettings(
phytype = "NexusDDRPHY",
memtype = memtype,
Expand All @@ -186,7 +195,7 @@ def __init__(self, pads,
cl = cl,
cwl = cwl,
read_latency = cl_sys_latency + 9,
write_latency = cwl_sys_latency,
write_latency = cwl_sys_latency - 1,
read_leveling = True,
bitslips = 4,
delays = 16,
Expand Down Expand Up @@ -329,28 +338,37 @@ def __init__(self, pads,
]

# DQS ----------------------------------------------------------------------------------
dqs = Signal()
dqs_oe_n = Signal()
dqs = Signal()
dqs_oe_n = Signal()
dqs_o_data = Signal(4)
dqs_oe_n_data = Signal(2)
dqs_o_bitslip = _NexusDDRPHYWriteBitSlip(4)
dqs_oe_n_bitslip = _NexusDDRPHYWriteBitSlip(2)
self.submodules += dqs_o_bitslip, dqs_oe_n_bitslip
self.comb += [
dqs_o_data.eq(Cat(0, dqs_oe, 0, dqs_oe | dqs_preamble)),
dqs_oe_n_data.eq(Cat(
~(dqs_oe | dqs_postamble),
~(dqs_oe | dqs_preamble),
)),
dqs_o_bitslip.i.eq(dqs_o_data),
dqs_oe_n_bitslip.i.eq(dqs_oe_n_data),
]
self.specials += [
Instance("ODDRX2DQS",
i_RST = ResetSignal("sys"),
i_SCLK = ClockSignal("sys"),
i_ECLK = ClockSignal("sys2x"),
i_DQSW = dqsw,
i_D0 = 0,
i_D1 = dqs_oe,
i_D2 = 0,
i_D3 = dqs_oe | dqs_preamble,
# **{f"i_D{n}": (0b1010 >> n) & 0b1 for n in range(4)},
**{f"i_D{n}": dqs_o_bitslip.o[n] for n in range(4)},
o_Q = dqs
),
Instance("TSHX2DQS",
i_RST = ResetSignal("sys"),
i_SCLK = ClockSignal("sys"),
i_ECLK = ClockSignal("sys2x"),
i_DQSW = dqsw,
i_T0 = ~(dqs_oe | dqs_postamble),
i_T1 = ~(dqs_oe | dqs_preamble),
**{f"i_T{n}": dqs_oe_n_bitslip.o[n] for n in range(2)},
o_Q = dqs_oe_n
),
Tristate(pads.dqs_p[i], dqs, ~dqs_oe_n, dqs_i)
Expand All @@ -360,8 +378,11 @@ def __init__(self, pads,
dm_o_data = Signal(8)
dm_o_data_d = Signal(8)
dm_o_data_muxed = Signal(4)
dm_o_bitslip = _NexusDDRPHYWriteBitSlip(4)
self.submodules += dm_o_bitslip
for n in range(8):
self.comb += dm_o_data[n].eq(dfi.phases[n//4].wrdata_mask[n%4*databits//8+dm_remapping.get(i, i)])
self.comb += dm_o_bitslip.i.eq(dm_o_data_muxed)
self.sync += dm_o_data_d.eq(dm_o_data)
dm_bl8_cases = {}
dm_bl8_cases[0] = dm_o_data_muxed.eq(dm_o_data[:4])
Expand All @@ -372,7 +393,7 @@ def __init__(self, pads,
i_SCLK = ClockSignal("sys"),
i_ECLK = ClockSignal("sys2x"),
i_DQSW270 = dqsw270,
**{f"i_D{n}": dm_o_data_muxed[n] for n in range(4)},
**{f"i_D{n}": dm_o_bitslip.o[n] for n in range(4)},
o_Q = pads.dm[i]
)

Expand All @@ -386,8 +407,20 @@ def __init__(self, pads,
dq_o_data = Signal(8)
dq_o_data_d = Signal(8)
dq_o_data_muxed = Signal(4)
dq_oe_n_data = Signal(2)
dq_o_bitslip = _NexusDDRPHYWriteBitSlip(4)
dq_oe_n_bitslip = _NexusDDRPHYWriteBitSlip(2)
self.submodules += dq_o_bitslip, dq_oe_n_bitslip
for n in range(8):
self.comb += dq_o_data[n].eq(dfi.phases[n//4].wrdata[n%4*databits+j])
self.comb += [
dq_oe_n_data.eq(Cat(
~(dq_oe | dqs_postamble),
~(dq_oe | dqs_preamble),
)),
dq_o_bitslip.i.eq(dq_o_data_muxed),
dq_oe_n_bitslip.i.eq(dq_oe_n_data),
]
self.sync += dq_o_data_d.eq(dq_o_data)
dq_bl8_cases = {}
dq_bl8_cases[0] = dq_o_data_muxed.eq(dq_o_data[:4])
Expand All @@ -399,7 +432,7 @@ def __init__(self, pads,
i_SCLK = ClockSignal("sys"),
i_ECLK = ClockSignal("sys2x"),
i_DQSW270 = dqsw270,
**{f"i_D{n}": dq_o_data_muxed[n] for n in range(4)},
**{f"i_D{n}": dq_o_bitslip.o[n] for n in range(4)},
o_Q = dq_o
)
]
Expand Down Expand Up @@ -437,8 +470,7 @@ def __init__(self, pads,
i_SCLK = ClockSignal("sys"),
i_ECLK = ClockSignal("sys2x"),
i_DQSW270 = dqsw270,
i_T0 = ~(dq_oe | dqs_postamble),
i_T1 = ~(dq_oe | dqs_preamble),
**{f"i_T{n}": dq_oe_n_bitslip.o[n] for n in range(2)},
o_Q = dq_oe_n,
),
Tristate(pads.dq[j], dq_o, ~dq_oe_n, dq_i)
Expand Down Expand Up @@ -466,7 +498,9 @@ def __init__(self, pads,
self.comb += dqs_re.eq(rddata_en.taps[rdtap] | rddata_en.taps[rdtap + 1])

# Write Control Path -----------------------------------------------------------------------
wrtap = cwl_sys_latency
# The Nexus write path is one memory-clock late. Start one sys_clk cycle early and use the
# fixed half-word bitslips above to place DQ, DM, DQS and their tristates at the JEDEC CWL.
wrtap = cwl_sys_latency - 1

# Create a delay line of write commands coming from the DFI interface. This taps are used to
# control DQ/DQS tristates and to select write data of the DRAM burst from the DFI interface.
Expand Down
89 changes: 89 additions & 0 deletions test/test_nxddrphy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#
# This file is part of LiteDRAM.
#
# Copyright (c) 2026 Florent Kermarrec <florent@enjoy-digital.fr>
# SPDX-License-Identifier: BSD-2-Clause

import unittest

from migen import *
from migen.sim import run_simulation

from litedram.phy.nxddrphy import NexusDDRPHY, _NexusDDRPHYWriteBitSlip


class TestNexusDDRPHYWriteBitSlip(unittest.TestCase):
@staticmethod
def run_bitslip(dw, inputs, idle=0):
dut = _NexusDDRPHYWriteBitSlip(dw)
outputs = []

def generator():
# Fill the history with the inactive value used by the stream.
yield dut.i.eq(idle)
yield
for current in inputs:
yield dut.i.eq(current)
yield
outputs.append((yield dut.o))

run_simulation(dut, generator())
return outputs

def test_half_word_shift(self):
for dw, inputs in [
(4, [0b1101, 0b0011, 0b1010, 0b0110]),
(2, [0b11, 0b00, 0b10, 0b01]),
]:
with self.subTest(dw=dw):
outputs = self.run_bitslip(dw, inputs)

half = dw//2
mask = (1 << half) - 1
expected = []
previous = 0
for current in inputs:
expected.append((previous >> half) | ((current & mask) << half))
previous = current
self.assertEqual(outputs, expected)

def test_one_memory_clock_advance(self):
streams = [
# DQS data: idle, preamble, data, data, postamble, idle.
(4, 0b0000, [0b0000, 0b1000, 0b1010, 0b1010, 0b0000, 0b0000]),
# DQS tristate: T0 is serialized before T1.
(2, 0b0011, [0b0011, 0b0001, 0b0000, 0b0000, 0b0010, 0b0011]),
]
for dw, idle, original in streams:
with self.subTest(dw=dw):
early = original[1:] + [idle]
shifted = self.run_bitslip(dw, early, idle=idle)

original_bits = [((word >> n) & 1) for word in original for n in range(dw)]
shifted_bits = [((word >> n) & 1) for word in shifted for n in range(dw)]
half = dw//2
idle_bits = [((idle >> n) & 1) for n in range(half)]
self.assertEqual(shifted_bits, original_bits[half:] + idle_bits)


class TestNexusDDRPHYSettings(unittest.TestCase):
@staticmethod
def get_pads():
return Record([
("a", 14),
("ba", 3),
("ras_n", 1),
("cas_n", 1),
("we_n", 1),
("clk_p", 1),
("dq", 8),
("dm", 1),
("dqs_p", 1),
])

def test_standard_cwl(self):
dut = NexusDDRPHY(self.get_pads(), sys_clk_freq=75e6)

self.assertEqual(dut.settings.cwl, 5)
self.assertEqual(dut.settings.wrphase, 1)
self.assertEqual(dut.settings.write_latency, 2)
Loading