Skip to content

Commit a09c0f7

Browse files
committed
Serwb: Add s6serdes
1 parent ab7b3a0 commit a09c0f7

File tree

3 files changed

+260
-2
lines changed

3 files changed

+260
-2
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ design flow by generating the verilog rtl that you will use as a standard core.
3131
-----------
3232
SerWB:
3333
- Wishbone over 3 LVDS I/Os (high speed) or 3 Single-Ended I/Os (low speed).
34-
- Artix7, Kintex7, Virtex7, Kintex Ultrascale high-speed PHYs.
34+
- Spartan6, Artix7, Kintex7, Virtex7, Kintex Ultrascale high-speed PHYs.
3535
- Vendor agnostic low-speed PHY.
3636
- 8B/10B, integrated gearbox.
3737
- Up to 1.25Gbps linerate / 32 bits @ 31.25Mhz user interface.

liteiclink/serwb/phy.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
from liteiclink.serwb.kuserdes import KUSerdes
1616
from liteiclink.serwb.s7serdes import S7Serdes
17+
from liteiclink.serwb.s6serdes import S6Serdes
1718
from liteiclink.serwb.efinixserdes import EfinixSerdes
1819

1920

@@ -417,7 +418,18 @@ def __init__(self, device, pads, mode="master", init_timeout=2**16, clk="sys", c
417418
assert clk_ratio == "1:1"
418419
taps = 32
419420
self.serdes = S7Serdes(pads, mode, serdes_data_width)
420-
421+
422+
# Xilinx Spartan6
423+
elif device[:4] == "xc6s":
424+
# See Design Advisory for Spartan-6 Table2:
425+
# https://support.xilinx.com/s/article/38408?language=en_US
426+
max_taps = {188: 107, 200: 101, 266:72, 333: 54, 400: 43, 533: 28, 625: 22,
427+
667: 20, 800: 14, 945: 9, 1000: 8, 1050: 7, 1080: 6}
428+
sys_freq = 50 # todo: how to get that information?
429+
datarate = sys_freq * serdes_data_width
430+
datarate_in_max_taps = max((x for x in max_taps.keys() if x <= datarate))
431+
taps = max_taps[datarate_in_max_taps]
432+
self.serdes = S6Serdes(pads, mode, serdes_data_width)
421433

422434
# Efinix Titanium.
423435
elif device[:2] == "Ti":

liteiclink/serwb/s6serdes.py

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
#
2+
# This file is part of LiteICLink.
3+
#
4+
# Copyright (c) 2023 Christian Klarhorst <cklarhor@techfak.uni-bielefeld.de>
5+
# Copyright (c) 2023 Florent Kermarrec <florent@enjoy-digital.fr>
6+
# SPDX-License-Identifier: BSD-2-Clause
7+
8+
from migen import *
9+
10+
from litex.gen import *
11+
from litex.gen.genlib.misc import BitSlip, WaitTimer
12+
13+
from litex.build.io import *
14+
15+
from litex.soc.interconnect import stream
16+
from litex.soc.cores.code_8b10b import Encoder, Decoder
17+
18+
from liteiclink.serwb.datapath import TXDatapath, RXDatapath
19+
20+
# S6 SerDes Clocking -------------------------------------------------------------------------------
21+
22+
class _S6SerdesClockingSimple(LiteXModule):
23+
def __init__(self, pads, mode="master"):
24+
self.refclk = Signal()
25+
26+
# In Master mode, generate the clock with 180° phase shift so that Slave can use this clock
27+
# to sample data.
28+
if mode == "master":
29+
self.specials += DDROutput(0, 1, self.refclk)
30+
if hasattr(pads, "clk_p"):
31+
self.specials += DifferentialOutput(self.refclk, pads.clk_p, pads.clk_n)
32+
else:
33+
self.comb += pads.clk.eq(self.refclk)
34+
35+
# In Slave mode, use the clock provided by Master.
36+
elif mode == "slave":
37+
if hasattr(pads, "clk_p"):
38+
self.specials += DifferentialInput(pads.clk_p, pads.clk_n, self.refclk)
39+
else:
40+
self.comb += self.refclk.eq(pads.clk)
41+
else: # Unknown mode
42+
raise ValueError
43+
44+
class _S6SerdesClocking(LiteXModule):
45+
def __init__(self, pads, mode="master", data_width=4):
46+
assert data_width in [1,2,3,4] # valid serdes2 sdr rates
47+
self.refclk = Signal()
48+
49+
self.serdes_clk = Signal()
50+
self.serdes_strobe = Signal()
51+
52+
# In Master mode, generate the linerate/10 clock. Slave will re-multiply it.
53+
if mode == "master":
54+
self.converter = converter = stream.Converter(40, data_width)
55+
self.comb += [
56+
converter.sink.valid.eq(1),
57+
converter.source.ready.eq(1),
58+
converter.sink.data.eq(Replicate(Signal(10, reset=0b1111100000), 4)),
59+
]
60+
self.test = Signal()
61+
self.sync += [self.test.eq(~self.test)]
62+
self.specials += [
63+
Instance("OSERDES2",
64+
p_DATA_WIDTH = data_width,
65+
p_DATA_RATE_OQ = "SDR",
66+
p_DATA_RATE_OT = "SDR",
67+
p_SERDES_MODE = "None",
68+
p_OUTPUT_MODE = "SINGLE_ENDED",
69+
70+
i_OCE = 1,
71+
i_IOCE = ClockSignal("strobe"),
72+
i_RST = 0,
73+
i_CLK0 = ClockSignal(f"sys{data_width}x"),
74+
i_CLK1 = 0,
75+
i_CLKDIV = ClockSignal("sys"),
76+
**{f"i_D{i+1}" : converter.source.data[i] for i in range(data_width)},
77+
i_TRAIN = 0,
78+
i_TCE=0,
79+
i_SHIFTIN1=0,
80+
i_SHIFTIN2=0,
81+
i_SHIFTIN3=0,
82+
i_SHIFTIN4=0,
83+
o_OQ = self.refclk,
84+
)
85+
]
86+
if hasattr(pads, "clk_p"):
87+
self.specials += DifferentialOutput(self.refclk, pads.clk_p, pads.clk_n)
88+
else:
89+
self.comb += self.refclk.eq(pads.clk)
90+
91+
# In Slave mode, multiply the clock provided by Master with a PLL/MMCM.
92+
elif mode == "slave":
93+
if hasattr(pads, "clk_p"):
94+
self.specials += DifferentialInput(pads.clk_p, pads.clk_n, self.refclk)
95+
else:
96+
self.comb += self.refclk.eq(pads.clk)
97+
98+
# S6 SerDes TX -------------------------------------------------------------------------------------
99+
100+
class _S6SerdesTX(LiteXModule):
101+
def __init__(self, pads, data_width=4):
102+
assert data_width in [1,2,3,4] # valid serdes2 sdr rates
103+
# Control
104+
self.idle = idle = Signal()
105+
self.comma = comma = Signal()
106+
107+
# Datapath
108+
self.sink = sink = stream.Endpoint([("data", 32)])
109+
110+
self.datapath = datapath = TXDatapath(data_width)
111+
self.comb += [
112+
sink.connect(datapath.sink),
113+
datapath.source.ready.eq(1),
114+
datapath.idle.eq(idle),
115+
datapath.comma.eq(comma)
116+
]
117+
118+
# Data output
119+
self.data = data = Signal(data_width)
120+
data_serialized = Signal()
121+
self.comb += data.eq(datapath.source.data)
122+
self.specials += [
123+
Instance("OSERDES2",
124+
p_DATA_WIDTH = data_width,
125+
p_DATA_RATE_OQ = "SDR",
126+
p_DATA_RATE_OT = "SDR",
127+
p_SERDES_MODE = "NONE",
128+
p_OUTPUT_MODE = "SINGLE_ENDED",
129+
130+
i_OCE = 1,
131+
i_IOCE = ClockSignal("strobe"),
132+
i_RST = 0,
133+
i_CLK0 = ClockSignal(f"sys{data_width}x"),
134+
i_CLK1 = 0,
135+
i_CLKDIV = ClockSignal("sys"),
136+
**{f"i_D{i+1}" : data[i] for i in range(data_width)},
137+
i_TRAIN = 0,
138+
i_TCE=0,
139+
i_SHIFTIN1=0,
140+
i_SHIFTIN2=0,
141+
i_SHIFTIN3=0,
142+
i_SHIFTIN4=0,
143+
o_OQ = data_serialized,
144+
)
145+
]
146+
if hasattr(pads, "tx_p"):
147+
self.specials += DifferentialOutput(data_serialized, pads.tx_p, pads.tx_n)
148+
else:
149+
self.comb += pads.tx.eq(data_serialized)
150+
151+
# S6 SerDes RX -------------------------------------------------------------------------------------
152+
153+
class _S6SerdesRX(LiteXModule):
154+
def __init__(self, pads, data_width=4):
155+
assert data_width in [1,2,3,4] # valid serdes2 sdr rates
156+
# Control
157+
self.delay_rst = delay_rst = Signal()
158+
self.delay_inc = delay_inc = Signal()
159+
self.shift_inc = shift_inc = Signal()
160+
161+
# Status
162+
self.idle = idle = Signal()
163+
self.comma = comma = Signal()
164+
165+
# Datapath
166+
self.source = source = stream.Endpoint([("data", 32)])
167+
168+
_shift = Signal(max=data_width)
169+
self.sync += If(self.shift_inc, _shift.eq(_shift + 1))
170+
171+
# Data input
172+
data_nodelay = Signal()
173+
data_delayed = Signal()
174+
self.data = data = Signal(data_width)
175+
176+
if hasattr(pads, "rx_p"):
177+
self.specials += DifferentialInput(pads.rx_p, pads.rx_n, data_nodelay)
178+
else:
179+
self.comb += data_nodelay.eq(pads.rx)
180+
181+
self.specials += [
182+
Instance("IODELAY2",
183+
p_DELAY_SRC = "IDATAIN",
184+
p_DATA_RATE = "SDR",
185+
p_IDELAY_TYPE = "VARIABLE_FROM_ZERO",
186+
p_IDELAY_VALUE = 0,
187+
p_IDELAY2_VALUE = 0,
188+
p_IDELAY_MODE = "NORMAL",
189+
p_ODELAY_VALUE = 0,
190+
p_COUNTER_WRAPAROUND = "STAY_AT_LIMIT",
191+
p_SERDES_MODE = "NONE",
192+
193+
i_T = 1,
194+
i_ODATAIN = 0,
195+
i_CAL = ResetSignal("sys"), # calibrate once on reset
196+
i_CLK = ClockSignal("sys"),
197+
i_IOCLK0 = ClockSignal(f"sys{data_width}x"),
198+
i_IOCLK1 = 0,
199+
i_RST = delay_rst,
200+
i_CE = delay_inc,
201+
i_INC = 1,
202+
i_IDATAIN = data_nodelay,
203+
o_DATAOUT = data_delayed,
204+
),
205+
Instance("ISERDES2",
206+
p_BITSLIP_ENABLE = 1,
207+
p_DATA_WIDTH = data_width,
208+
p_DATA_RATE = "SDR",
209+
p_SERDES_MODE = "NONE",
210+
p_INTERFACE_TYPE = "RETIMED",
211+
212+
i_CLK0 = ClockSignal(f"sys{data_width}x"),
213+
i_CLK1 = 0,
214+
i_CLKDIV = ClockSignal("sys"),
215+
i_CE0 = 1,
216+
i_BITSLIP = shift_inc,
217+
i_D = data_delayed,
218+
i_RST = ResetSignal("sys"),
219+
i_IOCE = ClockSignal("strobe"),
220+
i_SHIFTIN=0,
221+
**{f"o_Q{i+1}" : data[i] for i in range(data_width)},
222+
)
223+
]
224+
225+
# Datapath
226+
self.datapath = datapath = RXDatapath(data_width)
227+
self.comb += [
228+
datapath.sink.valid.eq(1),
229+
datapath.sink.data.eq(data),
230+
datapath.shift_inc.eq(self.shift_inc & (_shift == data_width-1)),
231+
datapath.source.connect(source),
232+
idle.eq(datapath.idle),
233+
comma.eq(datapath.comma)
234+
]
235+
236+
# S6 SerDes ----------------------------------------------------------------------------------------
237+
238+
@ResetInserter()
239+
class S6Serdes(LiteXModule):
240+
def __init__(self, pads, mode="master", data_width=4, simple_clk=True):
241+
if simple_clk:
242+
self.clocking = _S6SerdesClockingSimple(pads, mode)
243+
else:
244+
self.clocking = _S6SerdesClocking(pads, mode, data_width)
245+
self.tx = _S6SerdesTX(pads, data_width)
246+
self.rx = _S6SerdesRX(pads, data_width)

0 commit comments

Comments
 (0)