Skip to content

Commit 2e21144

Browse files
Merge pull request #115 from litex-hub/fix-cscontrol-switch-glitch
cscontrol: Keep different chip-select asserted
2 parents 8ca711b + 2ef1f5f commit 2e21144

2 files changed

Lines changed: 95 additions & 6 deletions

File tree

litespi/cscontrol.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,27 @@ class LiteSPICSControl(LiteXModule):
1414
def __init__(self, pads, cs, cs_delay=10, with_sdr_cs=True, **kwargs):
1515
self.enable = enable = Signal()
1616
cs_n = Signal().like(pads.cs_n)
17-
last_cs = Signal()
17+
last_cs = Signal().like(cs)
1818

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

2121
if len(pads.cs_n) > 1:
22-
# Remember last active CS when multiple CS lines are used, so we only wait when
23-
# the same CS line is active again
22+
# Remember the last completed CS selection, so the delay only applies when the same
23+
# CS line is selected again. Update it after the transfer to keep a different-CS
24+
# bypass active for the complete transfer.
2425
last_active_cs = Signal(len(cs))
25-
self.sync += If(enable, last_active_cs.eq(cs))
26+
self.sync += If((cs == 0) & (last_cs != 0),
27+
last_active_cs.eq(last_cs)
28+
)
2629

2730
enable_cond = timer.done | (last_active_cs != cs)
2831
else:
2932
enable_cond = timer.done
3033

31-
self.sync += last_cs.eq(cs != 0)
34+
self.sync += last_cs.eq(cs)
3235

33-
self.comb += timer.wait.eq(~((cs == 0) & last_cs)) # Reset wait on falling edge of cs
36+
# Reset wait on falling edge of CS.
37+
self.comb += timer.wait.eq(~((cs == 0) & (last_cs != 0)))
3438
self.comb += enable.eq((cs != 0) & enable_cond)
3539
self.comb += cs_n.eq(~(Replicate(enable, len(pads.cs_n)) & cs))
3640

test/test_cscontrol.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#
2+
# This file is part of LiteSPI.
3+
#
4+
# Copyright (c) 2026 Florent Kermarrec <florent@enjoy-digital.fr>
5+
# SPDX-License-Identifier: BSD-2-Clause
6+
7+
import unittest
8+
9+
from migen import *
10+
11+
from litespi.cscontrol import LiteSPICSControl
12+
13+
14+
class _LiteSPICSControlDUT(Module):
15+
def __init__(self, cs_delay=4):
16+
self.pads = pads = Record([
17+
("cs_n", 2),
18+
])
19+
self.cs = cs = Signal(2)
20+
21+
self.submodules.cs_control = LiteSPICSControl(
22+
pads = pads,
23+
cs = cs,
24+
cs_delay = cs_delay,
25+
with_sdr_cs = False,
26+
)
27+
28+
29+
class TestLiteSPICSControl(unittest.TestCase):
30+
def test_first_selection_stays_asserted(self):
31+
for cs, cs_n in [(0b01, 0b10), (0b10, 0b01)]:
32+
with self.subTest(cs=cs):
33+
dut = _LiteSPICSControlDUT()
34+
35+
def generator():
36+
yield dut.cs.eq(cs)
37+
for _ in range(8):
38+
yield
39+
self.assertEqual((yield dut.pads.cs_n), cs_n)
40+
41+
run_simulation(dut, generator())
42+
43+
def test_different_selection_stays_asserted(self):
44+
for first_cs, second_cs, second_cs_n in [
45+
(0b01, 0b10, 0b01),
46+
(0b10, 0b01, 0b10),
47+
]:
48+
with self.subTest(first_cs=first_cs, second_cs=second_cs):
49+
dut = _LiteSPICSControlDUT()
50+
51+
def generator():
52+
# Complete a transfer and start the inter-transfer delay.
53+
yield dut.cs.eq(first_cs)
54+
for _ in range(8):
55+
yield
56+
yield dut.cs.eq(0)
57+
yield
58+
59+
# A different chip can be selected immediately and must remain selected.
60+
yield dut.cs.eq(second_cs)
61+
for _ in range(8):
62+
yield
63+
self.assertEqual((yield dut.pads.cs_n), second_cs_n)
64+
65+
run_simulation(dut, generator())
66+
67+
def test_same_selection_observes_delay(self):
68+
dut = _LiteSPICSControlDUT()
69+
70+
def generator():
71+
yield dut.cs.eq(0b01)
72+
for _ in range(8):
73+
yield
74+
yield dut.cs.eq(0)
75+
yield
76+
77+
yield dut.cs.eq(0b01)
78+
yield
79+
self.assertEqual((yield dut.pads.cs_n), 0b11)
80+
81+
for _ in range(8):
82+
yield
83+
self.assertEqual((yield dut.pads.cs_n), 0b10)
84+
85+
run_simulation(dut, generator())

0 commit comments

Comments
 (0)