Skip to content
Open
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
116 changes: 103 additions & 13 deletions litespi/clkgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# This file is part of LiteSPI
#
# Copyright (c) 2020 Antmicro <www.antmicro.com>
# Copyright (c) 2025 Fin Maaß <[email protected]>
# SPDX-License-Identifier: BSD-2-Clause

from migen import *
Expand Down Expand Up @@ -48,13 +49,14 @@ class LiteSPIClkGen(LiteXModule):
device : str
Device type for determining how to get output pin if it was not provided in pads.

cnt_width : int
Width of the internal counter ``cnt`` used for dividing the clock.
div_width : int
Width of the ``div`` used for dividing the clock.

Attributes
----------
div : Signal(8), in
Clock divisor, output clock frequency will be equal to ``sys_clk_freq/(2*(1+div))``.
Clock divisor, output clock frequency will be equal to ``sys_clk_freq/(2*((div + 1)//2))``.
It's ``sys_clk_freq/div``, but div is rounded up to the nearest even number.

posedge : Signal(), out
Outputs 1 when there is a rising edge on the generated clock, 0 otherwise.
Expand All @@ -65,18 +67,21 @@ class LiteSPIClkGen(LiteXModule):
en : Signal(), in
Clock enable input, output clock will be generated if set to 1, 0 resets the core.
"""
def __init__(self, pads, device, cnt_width=8, extra_latency=0):
self.div = div = Signal(cnt_width)
def __init__(self, pads, device, div_width=8, extra_latency=0):
self.div = div = Signal(div_width)
self.posedge = posedge = Signal()
self.negedge = negedge = Signal()
self.en = en = Signal()
cnt = Signal(cnt_width)
cnt = Signal(div_width - 1)
en_int = Signal()
clk = Signal()
half = Signal(div_width - 1)

self.comb += half.eq((div + 1) >> 1)

self.comb += [
posedge.eq(en & ~clk & (cnt == div)),
negedge.eq(en & clk & (cnt == div)),
posedge.eq(en & ~clk & (cnt <= 1)),
negedge.eq(en & clk & (cnt <= 1)),
]

# Delayed edge to account for IO register delays.
Expand All @@ -91,15 +96,15 @@ def __init__(self, pads, device, cnt_width=8, extra_latency=0):

self.sync += [
If(en | en_int,
If(cnt < div,
cnt.eq(cnt+1),
If(cnt <= 1,
clk.eq(~clk), # 50 % duty-cycle toggle.
cnt.eq(half) # reload count.
).Else(
cnt.eq(0),
clk.eq(~clk),
cnt.eq(cnt - 1) # simple down-count.
)
).Else(
clk.eq(0),
cnt.eq(0),
cnt.eq(half),
)
]

Expand Down Expand Up @@ -133,3 +138,88 @@ def __init__(self, pads, device, cnt_width=8, extra_latency=0):
raise NotImplementedError
else:
self.specials += SDROutput(i=clk, o=pads.clk)

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

The ``LiteSPIClkGen`` class provides a generic SPI DDR clock generator.

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

div_width : int
Width of the ``div`` used for dividing the clock.

Attributes
----------
div : Signal(8), in
Clock divisor, output clock frequency will be equal to ``sys_clk_freq/div``.

posedge : Signal(), out
Outputs 1 when there is a rising edge on the generated clock, 0 otherwise.

negedge : Signal(), out
Outputs 1 when there is a falling edge on the generated clock, 0 otherwise.

en : Signal(), in
Clock enable input, output clock will be generated if set to 1, 0 resets the core.
"""
def __init__(self, pads, div_width=8, extra_latency=0):
self.div = div = Signal(div_width)
self.posedge = posedge = Signal(2)
self.negedge = negedge = Signal(2)
self.en = en = Signal()
cnt = Signal(div_width + 1)
clk = Signal(2)
next_clk = Signal(2)
next_cnt = Signal(div_width + 1)

double_div = Signal(div_width + 1)
self.comb += double_div.eq(Cat(C(0,1), div))

self.comb += [
If(cnt == 2,
next_cnt.eq(double_div),
next_clk[0].eq(cnt <= div),
next_clk[1].eq(1),
).Elif(cnt <= 1,
next_cnt.eq(double_div - 1),
next_clk[0].eq(1),
next_clk[1].eq(0),
).Else(
next_cnt.eq(cnt - 2),
next_clk[0].eq(cnt <= div),
next_clk[1].eq((cnt - 1) <= div),
),
]

self.comb += [
posedge[0].eq(en & ~clk[1] & next_clk[0]),
negedge[0].eq(en & clk[1] & ~next_clk[0]),
posedge[1].eq(en & ~next_clk[0] & next_clk[1]),
negedge[1].eq(en & next_clk[0] & ~next_clk[1]),
]

# Delayed edge to account for IO register delays.
posedge_reg = Signal(2 + 2 + 2 + int(extra_latency))
self.posedge_reg2 = posedge_reg2 = Signal(2)

self.sync += [
posedge_reg.eq(Cat(posedge_reg[2:], posedge)),
]

self.comb += posedge_reg2.eq(posedge_reg[:2])

self.sync += [
If(en,
cnt.eq(next_cnt),
clk.eq(next_clk),
).Else(
clk.eq(0),
cnt.eq(double_div),
)
]

self.specials += DDROutput(i1=(en & clk[0]), i2=(en & clk[1]), o=pads.clk)
1 change: 1 addition & 0 deletions litespi/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
("len", 6),
("width", 4),
("mask", 8),
("clk_div", 8),
]
"""
Stream layout for PHY->LiteSPICore connection
Expand Down
4 changes: 4 additions & 0 deletions litespi/core/master.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ def __init__(self, cs_width=1, tx_fifo_depth=1, rx_fifo_depth=1, with_irq=False)
CSRField("rx_ready", size=1, offset=1, description="RX FIFO is not empty."),
])

# Clock Divider.
self._clk_divisor = CSRStorage(len(self.source.clk_div))

# # #

# FIFOs.
Expand All @@ -79,6 +82,7 @@ def __init__(self, cs_width=1, tx_fifo_depth=1, rx_fifo_depth=1, with_irq=False)
tx_fifo.sink.len.eq(self._phyconfig.fields.len),
tx_fifo.sink.width.eq(self._phyconfig.fields.width),
tx_fifo.sink.mask.eq(self._phyconfig.fields.mask),
tx_fifo.sink.clk_div.eq(self._clk_divisor.storage),
]

# SPI RX (MISO).
Expand Down
3 changes: 3 additions & 0 deletions litespi/core/mmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ def __init__(self, flash, clock_domain="sys", endianness="big", with_csr=True, w
cmd_bits = 8
data_bits = 32

self.clk_divisor = clk_divisor = CSRStorage(len(source.clk_div))

self._default_dummy_bits = flash.dummy_cycles * flash.addr_width if flash.fast_mode else 0

self._spi_dummy_bits = spi_dummy_bits = Signal(8)
Expand Down Expand Up @@ -134,6 +136,7 @@ def __init__(self, flash, clock_domain="sys", endianness="big", with_csr=True, w
fsm.act("IDLE",
# Keep CS active after Burst for Timeout.
burst_timeout.wait.eq(1),
NextValue(source.clk_div, clk_divisor.storage),
NextValue(burst_cs, burst_cs & ~burst_timeout.done),
cs.eq(burst_cs),
If(bus.cyc & bus.stb,
Expand Down
32 changes: 24 additions & 8 deletions litespi/cscontrol.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,33 @@
from litex.build.io import SDROutput

class LiteSPICSControl(LiteXModule):
def __init__(self, pads, cs, cs_delay):
def __init__(self, pads, cs, cs_delay=10, with_sdr_cs=True, **kwargs):
self.enable = enable = Signal()
cs_n = Signal().like(pads.cs_n)
last_cs = Signal()

self.timer = timer = WaitTimer(cs_delay + 1) # Ensure cs_delay cycles between XFers.

self.comb += timer.wait.eq(cs != 0)
self.comb += enable.eq(timer.done)

if len(pads.cs_n) > 1:
# Remember last active CS when multiple CS lines are used, so we only wait when
# the same CS line is active again
last_active_cs = Signal(len(cs))
self.sync += If(enable, last_active_cs.eq(cs))

enable_cond = timer.done | (last_active_cs != cs)
else:
enable_cond = timer.done

self.sync += last_cs.eq(cs != 0)

self.comb += timer.wait.eq(~((cs == 0) & last_cs)) # Reset wait on falling edge of cs
self.comb += enable.eq((cs != 0) & enable_cond)
self.comb += cs_n.eq(~(Replicate(enable, len(pads.cs_n)) & cs))

self.specials += SDROutput(
i = cs_n,
o = pads.cs_n
)
if with_sdr_cs:
self.specials += SDROutput(
i = cs_n,
o = pads.cs_n
)
else:
self.comb += pads.cs_n.eq(cs_n)
7 changes: 4 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_ddr_with_clk import LiteSPIDDRPHYCore2

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

Expand Down Expand Up @@ -56,12 +57,12 @@ 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", **kwargs):
def __init__(self, pads, flash, device="xc7", clock_domain="sys", rate="1:1", **kwargs):
assert rate in ["1:1", "1:2"]
if rate == "1:1":
phy = LiteSPISDRPHYCore(pads, flash, device, clock_domain, default_divisor, cs_delay, **kwargs)
phy = LiteSPISDRPHYCore(pads, flash, device, clock_domain, **kwargs)
if rate == "1:2":
phy = LiteSPIDDRPHYCore(pads, flash, cs_delay, **kwargs)
phy = LiteSPIDDRPHYCore2(pads, flash, clock_domain, **kwargs)

self.flash = flash

Expand Down
13 changes: 6 additions & 7 deletions litespi/phy/generic_ddr.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class LiteSPIDDRPHYCore(LiteXModule):
cs : Signal(), in
Flash CS signal.
"""
def __init__(self, pads, flash, cs_delay, extra_latency=0, **kwargs):
def __init__(self, pads, flash, extra_latency=0, **kwargs):
self.source = source = stream.Endpoint(spi_phy2core_layout)
self.sink = sink = stream.Endpoint(spi_core2phy_layout)
self.cs = Signal().like(pads.cs_n)
Expand All @@ -74,17 +74,17 @@ def __init__(self, pads, flash, cs_delay, extra_latency=0, **kwargs):
self.clkgen = clkgen = DDRLiteSPIClkGen(pads)

# CS control.
self.cs_control = cs_control = LiteSPICSControl(pads, self.cs, cs_delay)
self.cs_control = cs_control = LiteSPICSControl(pads, self.cs, **kwargs)

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_oe = Signal(len(pads.dq))

self.specials += DDRTristate(
io = pads.dq,
o1 = dq_o[0], o2 = dq_o[1],
oe1 = dq_oe[0], oe2 = dq_oe[1],
i1 = dq_i[0], i2 = dq_i[1]
i1 = dq_i[0], i2 = dq_i[1],
oe1 = dq_oe,
)

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

# Data Out Shift.
self.comb += [
dq_oe[1].eq(sink.mask),
dq_oe.eq(sink.mask),
Case(sink.width, {
1: dq_o[1].eq(sr_out[-1:]),
2: dq_o[1].eq(sr_out[-2:]),
Expand All @@ -109,7 +109,6 @@ def __init__(self, pads, flash, cs_delay, extra_latency=0, **kwargs):
sr_out.eq(sink.data << (len(sink.data) - sink.len))
)
self.sync += If(sr_out_shift,
dq_oe[0].eq(dq_oe[1]),
dq_o[0].eq(dq_o[1]),
Case(sink.width, {
1 : sr_out.eq(Cat(Signal(1), sr_out)),
Expand Down
Loading