Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wip: phy: add qdr phy #77

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
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
24 changes: 23 additions & 1 deletion litespi/clkgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,29 @@

from litex.gen import *

from litex.build.io import SDROutput, DDROutput
from litex.build.io import SDROutput, DDROutput, QDROutput

class QDRLiteSPIClkGen(LiteXModule):
"""SPI Clock generator

The ``QDRLiteSPIClkGen`` class provides a generic SPI clock generator.

The class can be combined with QDR PHY Core.

Parameters
----------
pads : Object
SPI pads description.

Attributes
----------
en : Signal(), in
Clock enable input, output clock will be generated if set to 1, 0 resets the core.
"""
def __init__(self, pads, cd_fastclk):
self.en = en = Signal()

self.specials += QDROutput(i1=en, i2=0, i3=en, i4=0, o=pads.clk, fastclk=ClockSignal(cd_fastclk))

class DDRLiteSPIClkGen(LiteXModule):
"""SPI Clock generator
Expand Down
8 changes: 5 additions & 3 deletions litespi/phy/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

from litespi.phy.generic_sdr import LiteSPISDRPHYCore
from litespi.phy.generic_ddr import LiteSPIDDRPHYCore
from litespi.phy.generic_qdr import LiteSPIQDRPHYCore

# LiteSPI PHY --------------------------------------------------------------------------------------

Expand Down Expand Up @@ -56,13 +57,14 @@ class LiteSPIPHY(LiteXModule):
Flash CS signal from ``LiteSPIPHYCore``.
"""

def __init__(self, pads, flash, device="xc7", clock_domain="sys", default_divisor=9, cs_delay=10, rate="1:1", extra_latency=0):
assert rate in ["1:1", "1:2"]
def __init__(self, pads, flash, device="xc7", clock_domain="sys", default_divisor=9, cs_delay=10, rate="1:1", extra_latency=0, cd_fastclk="sys4x_ps"):
assert rate in ["1:1", "1:2", "1:4"]
if rate == "1:1":
phy = LiteSPISDRPHYCore(pads, flash, device, clock_domain, default_divisor, cs_delay)
if rate == "1:2":
phy = LiteSPIDDRPHYCore(pads, flash, cs_delay, extra_latency)

if rate == "1:4":
phy = LiteSPIQDRPHYCore(pads, flash, cs_delay, cd_fastclk, extra_latency)
self.flash = flash

self.source = phy.source
Expand Down
20 changes: 10 additions & 10 deletions litespi/phy/generic_ddr.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,15 @@ def __init__(self, pads, flash, cs_delay, extra_latency=0):
data_bits = 32

dq_o = Array([Signal(len(pads.dq)) for _ in range(2)])
dq_i = Array([Signal(len(pads.dq)) for _ in range(2)])
dq_oe = Array([Signal(len(pads.dq)) for _ in range(2)])
dq_i = Signal(len(pads.dq))
dq_oe = Signal(len(pads.dq))

for i in range(len(pads.dq)):
self.specials += DDRTristate(
io = pads.dq[i],
o1 = dq_o[0][i], o2 = dq_o[1][i],
oe1 = dq_oe[0][i], oe2 = dq_oe[1][i],
i1 = dq_i[0][i], i2 = dq_i[1][i]
oe1 = dq_oe[i], oe2 = dq_oe[i],
i1 = dq_i[i], i2 = Signal(),
)

# Data Shift Registers.
Expand All @@ -104,7 +104,6 @@ def __init__(self, pads, flash, cs_delay, extra_latency=0):

# Data Out Shift.
self.comb += [
dq_oe[1].eq(sink.mask),
Case(sink.width, {
1: dq_o[1].eq(sr_out[-1:]),
2: dq_o[1].eq(sr_out[-2:]),
Expand All @@ -116,7 +115,7 @@ def __init__(self, pads, flash, cs_delay, extra_latency=0):
sr_out.eq(sink.data << (len(sink.data) - sink.len))
)
self.sync += If(sr_out_shift,
dq_oe[0].eq(dq_oe[1]),
dq_oe.eq(sink.mask),
dq_o[0].eq(dq_o[1]),
Case(sink.width, {
1 : sr_out.eq(Cat(Signal(1), sr_out)),
Expand All @@ -129,10 +128,10 @@ def __init__(self, pads, flash, cs_delay, extra_latency=0):
# Data In Shift.
self.sync += If(sr_in_shift,
Case(sink.width, {
1 : sr_in.eq(Cat(dq_i[0][1], sr_in)), # 1: pads.miso
2 : sr_in.eq(Cat(dq_i[0][:2], sr_in)),
4 : sr_in.eq(Cat(dq_i[0][:4], sr_in)),
8 : sr_in.eq(Cat(dq_i[0][:8], sr_in)),
1 : sr_in.eq(Cat(dq_i[1], sr_in)), # 1: pads.miso
2 : sr_in.eq(Cat(dq_i[:2], sr_in)),
4 : sr_in.eq(Cat(dq_i[:4], sr_in)),
8 : sr_in.eq(Cat(dq_i[:8], sr_in)),
})
)

Expand Down Expand Up @@ -172,6 +171,7 @@ def __init__(self, pads, flash, cs_delay, extra_latency=0):
fsm.act("XFER-END",
# Stop Clk.
NextValue(clkgen.en, 0),
NextValue(dq_oe, 0),

# Data In Shift.
sr_in_shift.eq(1),
Expand Down
191 changes: 191 additions & 0 deletions litespi/phy/generic_qdr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
#
# This file is part of LiteSPI
#
# Copyright (c) 2020 Antmicro <www.antmicro.com>
# Copyright (c) 2024 Fin Maaß <[email protected]>
# SPDX-License-Identifier: BSD-2-Clause

from migen import *

from litex.gen import *

from litex.gen.genlib.misc import WaitTimer

from litespi.common import *
from litespi.clkgen import QDRLiteSPIClkGen

from litex.soc.interconnect import stream

from litex.build.io import QDRTristate

# LiteSPI QDR PHY Core -----------------------------------------------------------------------------

class LiteSPIQDRPHYCore(LiteXModule):
"""LiteSPI PHY QDR instantiator

The ``QDRLiteSPIPHYCore`` class provides a generic PHY that can be connected to the ``LiteSPICore``.

It supports single/dual/quad/octal output reads from the flash chips.

You can use this class only with devices that supports the QDR primitives.

Parameters
----------
pads : Object
SPI pads description.

flash : SpiNorFlashModule
SpiNorFlashModule configuration object.

Attributes
----------
source : Endpoint(spi_phy2core_layout), out
Data stream.

sink : Endpoint(spi_core2phy_layout), in
Control stream.

cs : Signal(), in
Flash CS signal.
"""
def __init__(self, pads, flash, cs_delay, cd_fastclk, extra_latency=0):
self.source = source = stream.Endpoint(spi_phy2core_layout)
self.sink = sink = stream.Endpoint(spi_core2phy_layout)
self.cs = Signal()

if hasattr(pads, "miso"):
bus_width = 1
pads.dq = [pads.mosi, pads.miso]
else:
bus_width = len(pads.dq)

assert bus_width in [1, 2, 4, 8]

if flash:
# Check if number of pads matches configured mode.
assert flash.check_bus_width(bus_width)
assert not flash.ddr

# Clock Generator.
self.clkgen = clkgen = QDRLiteSPIClkGen(pads, cd_fastclk)

# CS control.
self.cs_timer = cs_timer = WaitTimer(cs_delay + 1) # Ensure cs_delay cycles between XFers.
cs_enable = Signal()
self.comb += cs_timer.wait.eq(self.cs)
self.comb += cs_enable.eq(cs_timer.done)
self.comb += pads.cs_n.eq(~cs_enable)

# I/Os.
data_bits = 32

dq_o = Array([Signal(len(pads.dq)) for _ in range(3)])
dq_i = Array([Signal(len(pads.dq)) for _ in range(2)])
dq_oe = Signal(len(pads.dq))

for i in range(len(pads.dq)):
self.specials += QDRTristate(
io = pads.dq[i],
o1 = dq_o[0][i], o2 = dq_o[1][i], o3 = dq_o[1][i], o4= dq_o[2][i],
oe1 = dq_oe[i],
i1 = dq_i[0][i], i2 = Signal(), i3 = dq_i[1][i], i4 = Signal(),
fastclk = ClockSignal(cd_fastclk),
)

# Data Shift Registers.
sr_cnt = Signal(8, reset_less=True)
sr_out_load = Signal()
sr_out_shift = Signal()
sr_out = Signal(len(sink.data), reset_less=True)
sr_in_shift = Signal()
sr_in = Signal(len(sink.data), reset_less=True)

# Data Out Shift.
self.comb += [
Case(sink.width, {
1: {dq_o[1].eq(sr_out[-1:]), dq_o[2].eq(sr_out[-2:-1])},
2: {dq_o[1].eq(sr_out[-2:]), dq_o[2].eq(sr_out[-4:-2])},
4: {dq_o[1].eq(sr_out[-4:]), dq_o[2].eq(sr_out[-8:-4])},
8: {dq_o[1].eq(sr_out[-8:]), dq_o[2].eq(sr_out[-16:-8])},
})
]
self.sync += If(sr_out_load,
sr_out.eq(sink.data << (len(sink.data) - sink.len))
)
self.sync += If(sr_out_shift,
dq_oe.eq(sink.mask),
dq_o[0].eq(dq_o[2]),
Case(sink.width, {
1 : sr_out.eq(Cat(Signal(2), sr_out)),
2 : sr_out.eq(Cat(Signal(4), sr_out)),
4 : sr_out.eq(Cat(Signal(8), sr_out)),
8 : sr_out.eq(Cat(Signal(16), sr_out)),
})
)

# Data In Shift.
self.sync += If(sr_in_shift,
Case(sink.width, {
1 : sr_in.eq(Cat(dq_i[1][1], dq_i[0][1], sr_in)), # 1: pads.miso
2 : sr_in.eq(Cat(dq_i[1][:2], dq_i[0][:2], sr_in)),
4 : sr_in.eq(Cat(dq_i[1][:4], dq_i[0][:4], sr_in)),
8 : sr_in.eq(Cat(dq_i[1][:8], dq_i[0][:8], sr_in)),
})
)

# FSM.
self.fsm = fsm = FSM(reset_state="WAIT-CMD-DATA")
fsm.act("WAIT-CMD-DATA",
# Stop Clk.
NextValue(clkgen.en, 0),
# Wait for CS and a CMD from the Core.
If(cs_enable & sink.valid,
# Load Shift Register Count/Data Out.
NextValue(sr_cnt, sink.len - sink.width),
sr_out_load.eq(1),
# Start XFER.
NextState("XFER")
)
)

fsm.act("XFER",
# Generate Clk.
NextValue(clkgen.en, 1),

# Data In Shift.
sr_in_shift.eq(1),

# Data Out Shift.
sr_out_shift.eq(1),

# Shift Register Count Update/Check.
NextValue(sr_cnt, sr_cnt - (2 * sink.width)),
# End XFer.
If(sr_cnt == 0,
NextValue(sr_cnt, (2 + 2*extra_latency)*(2 * sink.width)), # FIXME: Explain magic numbers.
NextState("XFER-END"),
),
)
fsm.act("XFER-END",
# Stop Clk.
NextValue(clkgen.en, 0),

# Data In Shift.
sr_in_shift.eq(1),

# Shift Register Count Update/Check.
NextValue(sr_cnt, sr_cnt - (2 * sink.width)),
If(sr_cnt == 0,
sink.ready.eq(1),
NextState("SEND-STATUS-DATA"),
),
)
self.comb += source.data.eq(sr_in)
fsm.act("SEND-STATUS-DATA",
# Send Data In to Core and return to WAIT when accepted.
source.valid.eq(1),
source.last.eq(1),
If(source.ready,
NextState("WAIT-CMD-DATA"),
)
)
Loading