Skip to content

Commit cc37db0

Browse files
committed
gateware.pll_deprecated: remove.
1 parent f979568 commit cc37db0

3 files changed

Lines changed: 198 additions & 122 deletions

File tree

software/glasgow/gateware/pll_deprecated.py

Lines changed: 0 additions & 21 deletions
This file was deleted.

software/glasgow/hardware/platform/ecp5.py

Lines changed: 198 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,201 @@ class GlasgowECP5Platform(GlasgowPlatform, LatticeECP5Platform):
1111
def bitstream_filename(self, design_name):
1212
return f"{design_name}.bit"
1313

14-
def get_pll(self, pll):
15-
raise NotImplementedError("get_pll() not implemented for ECP5")
14+
15+
# This is copied from `amaranth.vendor._lattice.InnerBuffer` as-is.
16+
class _InnerBuffer(wiring.Component):
17+
"""A private component used to implement ``lib.io`` buffers.
18+
19+
Works like ``lib.io.Buffer``, with the following differences:
20+
21+
- ``port.invert`` is ignored (handling the inversion is the outer buffer's responsibility)
22+
- ``t`` is per-pin inverted output enable
23+
"""
24+
25+
def __init__(self, direction, port):
26+
self.direction = direction
27+
self.port = port
28+
members = {}
29+
if direction is not io.Direction.Output:
30+
members["i"] = wiring.In(len(port))
31+
if direction is not io.Direction.Input:
32+
members["o"] = wiring.Out(len(port))
33+
members["t"] = wiring.Out(len(port))
34+
super().__init__(wiring.Signature(members).flip())
35+
36+
def elaborate(self, platform):
37+
m = Module()
38+
39+
if isinstance(self.port, io.SingleEndedPort):
40+
io_port = self.port.io
41+
elif isinstance(self.port, io.DifferentialPort):
42+
io_port = self.port.p
43+
else:
44+
raise TypeError(f"Unknown port type {self.port!r}")
45+
46+
for bit in range(len(self.port)):
47+
name = f"buf{bit}"
48+
if self.direction is io.Direction.Input:
49+
m.submodules[name] = Instance("IB",
50+
i_I=io_port[bit],
51+
o_O=self.i[bit],
52+
)
53+
elif self.direction is io.Direction.Output:
54+
m.submodules[name] = Instance("OBZ",
55+
i_T=self.t[bit],
56+
i_I=self.o[bit],
57+
o_O=io_port[bit],
58+
)
59+
elif self.direction is io.Direction.Bidir:
60+
m.submodules[name] = Instance("BB",
61+
i_T=self.t[bit],
62+
i_I=self.o[bit],
63+
o_O=self.i[bit],
64+
io_B=io_port[bit],
65+
)
66+
else:
67+
assert False # :nocov:
68+
69+
return m
70+
71+
72+
# Patterned after `io.DDRBuffer`, but with somewhat more questionable design choices. Would likely
73+
# not be accepted upstream as-is for a number of reasons; a major one is that IDDRX2F.ALIGNWD input
74+
# is not connected at all.
75+
class QDRBuffer(wiring.Component):
76+
class Signature(wiring.Signature):
77+
def __init__(self, direction, width):
78+
self._direction = io.Direction(direction)
79+
self._width = operator.index(width)
80+
members = {}
81+
if self._direction is not io.Direction.Output:
82+
members["i"] = wiring.In(data.ArrayLayout(self._width, 4))
83+
if self._direction is not io.Direction.Input:
84+
members["o"] = wiring.Out(data.ArrayLayout(self._width, 4))
85+
members["oe"] = wiring.Out(1, init=int(self._direction is io.Direction.Output))
86+
super().__init__(members)
87+
88+
@property
89+
def direction(self):
90+
return self._direction
91+
92+
@property
93+
def width(self):
94+
return self._width
95+
96+
def __eq__(self, other):
97+
return (type(self) is type(other) and self.direction == other.direction and
98+
self.width == other.width)
99+
100+
def __repr__(self):
101+
return f"QDRBuffer.Signature({self.direction}, {self.width})"
102+
103+
def __init__(self, direction, port, *, i_domain=None, o_domain=None):
104+
if not isinstance(port, io.PortLike):
105+
raise TypeError(f"'port' must be a 'PortLike', not {port!r}")
106+
self._port = port
107+
super().__init__(QDRBuffer.Signature(direction, len(port)).flip())
108+
if self.signature.direction is not io.Direction.Output:
109+
self._i_domain = i_domain or "sync"
110+
else:
111+
if i_domain is not None:
112+
raise ValueError("Output buffer doesn't have an input domain")
113+
self._i_domain = None
114+
if self.signature.direction is not io.Direction.Input:
115+
self._o_domain = o_domain or "sync"
116+
else:
117+
if o_domain is not None:
118+
raise ValueError("Input buffer doesn't have an output domain")
119+
self._o_domain = None
120+
if port.direction is io.Direction.Input and self.direction is not io.Direction.Input:
121+
raise ValueError(f"Input port cannot be used with {self.direction.name} buffer")
122+
if port.direction is io.Direction.Output and self.direction is not io.Direction.Output:
123+
raise ValueError(f"Output port cannot be used with {self.direction.name} buffer")
124+
125+
@property
126+
def port(self):
127+
return self._port
128+
129+
@property
130+
def direction(self):
131+
return self.signature.direction
132+
133+
@property
134+
def i_domain(self):
135+
return self._i_domain
136+
137+
@property
138+
def o_domain(self):
139+
return self._o_domain
140+
141+
def elaborate(self, platform):
142+
assert isinstance(platform, LatticeECP5Platform), "QDR buffers are only supported on ECP5"
143+
144+
m = Module()
145+
146+
m.submodules.buf = buf = _InnerBuffer(self.direction, self.port)
147+
inv_mask = sum(inv << bit for bit, inv in enumerate(self.port.invert))
148+
149+
if self.direction is not io.Direction.Output:
150+
m.submodules += RequirePosedge(self.i_domain)
151+
i0_inv = Signal(len(self.port))
152+
i1_inv = Signal(len(self.port))
153+
i2_inv = Signal(len(self.port))
154+
i3_inv = Signal(len(self.port))
155+
for bit in range(len(self.port)):
156+
m.submodules[f"i_ddr{bit}"] = Instance("IDDRX2F",
157+
# https://github.com/YosysHQ/nextpnr/issues/1749
158+
# i_ALIGNWD=0,
159+
i_SCLK=ClockSignal(self.i_domain),
160+
i_ECLK=ClockSignal("edge"),
161+
i_RST=ResetSignal("edge"),
162+
i_D=buf.i[bit],
163+
o_Q0=i0_inv[bit],
164+
o_Q1=i1_inv[bit],
165+
o_Q2=i2_inv[bit],
166+
o_Q3=i3_inv[bit],
167+
)
168+
m.d.comb += self.i[0].eq(i0_inv ^ inv_mask)
169+
m.d.comb += self.i[1].eq(i1_inv ^ inv_mask)
170+
m.d.comb += self.i[2].eq(i2_inv ^ inv_mask)
171+
m.d.comb += self.i[3].eq(i3_inv ^ inv_mask)
172+
173+
if self.direction is not io.Direction.Input:
174+
m.submodules += RequirePosedge(self.o_domain)
175+
o0_inv = Signal(len(self.port))
176+
o1_inv = Signal(len(self.port))
177+
o2_inv = Signal(len(self.port))
178+
o3_inv = Signal(len(self.port))
179+
m.d.comb += [
180+
o0_inv.eq(self.o[0] ^ inv_mask),
181+
o1_inv.eq(self.o[1] ^ inv_mask),
182+
o2_inv.eq(self.o[2] ^ inv_mask),
183+
o3_inv.eq(self.o[3] ^ inv_mask),
184+
]
185+
for bit in range(len(self.port)):
186+
m.submodules[f"o_ddr{bit}"] = Instance("ODDRX2F",
187+
i_SCLK=ClockSignal(self.o_domain),
188+
i_ECLK=ClockSignal("edge"),
189+
i_RST=ResetSignal("edge"),
190+
i_D0=o0_inv[bit],
191+
i_D1=o1_inv[bit],
192+
i_D2=o2_inv[bit],
193+
i_D3=o3_inv[bit],
194+
o_Q=buf.o[bit],
195+
)
196+
197+
oe = ~self.oe
198+
for stage in range(2):
199+
oe_reg = Signal(name=f"oe_delay{stage}")
200+
m.d[self.o_domain] += oe_reg.eq(oe)
201+
oe = oe_reg
202+
for bit in range(len(buf.t)):
203+
m.submodules[f"oe_ff{bit}"] = Instance("OFS1P3DX",
204+
i_SCLK=ClockSignal(self.o_domain),
205+
i_SP=Const(1),
206+
i_CD=ResetSignal("edge"),
207+
i_D=oe,
208+
o_Q=buf.t[bit],
209+
)
210+
211+
return m
Lines changed: 0 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
from amaranth import *
2-
from amaranth.lib.cdc import ResetSynchronizer
32
from amaranth.vendor import LatticeICE40Platform
43

5-
from ..build_plan import GatewareBuildError
64
from . import GlasgowPlatform
75

86

@@ -12,100 +10,3 @@
1210
class GlasgowICE40Platform(GlasgowPlatform, LatticeICE40Platform):
1311
def bitstream_filename(self, design_name):
1412
return f"{design_name}.bin"
15-
16-
def get_pll(self, pll, simple_feedback=True):
17-
if not 10e6 <= pll.f_in <= 133e6:
18-
pll.logger.error("PLL: f_in (%.3f MHz) must be between 10 and 133 MHz",
19-
pll.f_in / 1e6)
20-
raise GatewareBuildError("PLL f_in out of range")
21-
22-
if not 16e6 <= pll.f_out <= 275e6:
23-
pll.logger.error("PLL: f_out (%.3f MHz) must be between 16 and 275 MHz",
24-
pll.f_out / 1e6)
25-
raise GatewareBuildError("PLL f_out out of range")
26-
27-
# The documentation in the iCE40 PLL Usage Guide incorrectly lists the
28-
# maximum value of DIVF as 63, when it is only limited to 63 when using
29-
# feedback modes other that SIMPLE.
30-
if simple_feedback:
31-
divf_max = 128
32-
else:
33-
divf_max = 64
34-
35-
variants = []
36-
for divr in range(0, 16):
37-
f_pfd = pll.f_in / (divr + 1)
38-
if not 10e6 <= f_pfd <= 133e6:
39-
continue
40-
41-
for divf in range(0, divf_max):
42-
if simple_feedback:
43-
f_vco = f_pfd * (divf + 1)
44-
if not 533e6 <= f_vco <= 1066e6:
45-
continue
46-
47-
for divq in range(1, 7):
48-
f_out = f_vco * (2 ** -divq)
49-
variants.append((divr, divf, divq, f_pfd, f_out))
50-
51-
else:
52-
for divq in range(1, 7):
53-
f_vco = f_pfd * (divf + 1) * (2 ** divq)
54-
if not 533e6 <= f_vco <= 1066e6:
55-
continue
56-
57-
f_out = f_vco * (2 ** -divq)
58-
variants.append((divr, divf, divq, f_pfd, f_out))
59-
60-
if not variants:
61-
pll.logger.error("PLL: f_in (%.3f MHz) to f_out (%.3f) constraints not satisfiable",
62-
pll.f_in / 1e6, pll.f_out / 1e6)
63-
raise GatewareBuildError("PLL f_in/f_out out of range")
64-
65-
def f_out_diff(variant):
66-
*_, f_out = variant
67-
return abs(f_out - pll.f_out)
68-
divr, divf, divq, f_pfd, f_out = min(variants, key=f_out_diff)
69-
70-
if f_pfd < 17:
71-
filter_range = 1
72-
elif f_pfd < 26:
73-
filter_range = 2
74-
elif f_pfd < 44:
75-
filter_range = 3
76-
elif f_pfd < 66:
77-
filter_range = 4
78-
elif f_pfd < 101:
79-
filter_range = 5
80-
else:
81-
filter_range = 6
82-
83-
if simple_feedback:
84-
feedback_path = "SIMPLE"
85-
else:
86-
feedback_path = "NON_SIMPLE"
87-
88-
ppm = abs(pll.f_out - f_out) / pll.f_out * 1e6
89-
90-
pll.logger.debug("PLL: f_in=%.3f f_out(req)=%.3f f_out(act)=%.3f [MHz] ppm=%d",
91-
pll.f_in / 1e6, pll.f_out / 1e6, f_out / 1e6, ppm)
92-
pll.logger.trace("iCE40 PLL: feedback_path=%s divr=%d divf=%d divq=%d filter_range=%d",
93-
feedback_path, divr, divf, divq, filter_range)
94-
95-
m = Module()
96-
locked = Signal()
97-
m.submodules.reset_sync = ResetSynchronizer(~locked, domain=pll.odomain)
98-
m.submodules.pll_core = Instance("SB_PLL40_CORE",
99-
p_FEEDBACK_PATH=feedback_path,
100-
p_PLLOUT_SELECT="GENCLK",
101-
p_DIVR=divr,
102-
p_DIVF=divf,
103-
p_DIVQ=divq,
104-
p_FILTER_RANGE=filter_range,
105-
i_REFERENCECLK=ClockSignal(pll.idomain),
106-
o_PLLOUTCORE=ClockSignal(pll.odomain),
107-
i_RESETB=~ResetSignal(pll.idomain),
108-
o_LOCK=locked,
109-
i_BYPASS=Const(0),
110-
)
111-
return m

0 commit comments

Comments
 (0)