Skip to content

Commit 09045a2

Browse files
wanda-phiwhitequark
authored andcommitted
vendor._lattice: merge ECP5 and MachXO[23] into one platform.
1 parent a7a7d32 commit 09045a2

9 files changed

+164
-328
lines changed

amaranth/vendor/__init__.py

+5-6
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"LatticeICE40Platform",
1212
"LatticeMachXO2Platform",
1313
"LatticeMachXO3LPlatform",
14+
"LatticePlatform",
1415
"QuicklogicPlatform",
1516
"SiliconBluePlatform",
1617
"XilinxPlatform",
@@ -28,12 +29,10 @@ def __getattr__(name):
2829
if name == "GowinPlatform":
2930
from ._gowin import GowinPlatform
3031
return GowinPlatform
31-
if name == "LatticeECP5Platform":
32-
from ._lattice_ecp5 import LatticeECP5Platform
33-
return LatticeECP5Platform
34-
if name in ("LatticeMachXO2Platform", "LatticeMachXO3LPlatform"):
35-
from ._lattice_machxo_2_3l import LatticeMachXO2Or3LPlatform
36-
return LatticeMachXO2Or3LPlatform
32+
if name in ("LatticePlatform", "LatticeECP5Platform", "LatticeMachXO2Platform",
33+
"LatticeMachXO3LPlatform"):
34+
from ._lattice import LatticePlatform
35+
return LatticePlatform
3736
if name == "QuicklogicPlatform":
3837
from ._quicklogic import QuicklogicPlatform
3938
return QuicklogicPlatform

amaranth/vendor/_lattice_ecp5.py renamed to amaranth/vendor/_lattice.py

+137-12
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ def elaborate(self, platform):
123123
return m
124124

125125

126-
class DDRBuffer(io.DDRBuffer):
126+
class DDRBufferECP5(io.DDRBuffer):
127127
def elaborate(self, platform):
128128
m = Module()
129129

@@ -164,9 +164,50 @@ def elaborate(self, platform):
164164
return m
165165

166166

167-
class LatticeECP5Platform(TemplatedPlatform):
167+
class DDRBufferMachXO2(io.DDRBuffer):
168+
def elaborate(self, platform):
169+
m = Module()
170+
171+
m.submodules.buf = buf = InnerBuffer(self.direction, self.port)
172+
inv_mask = sum(inv << bit for bit, inv in enumerate(self.port.invert))
173+
174+
if self.direction is not io.Direction.Output:
175+
i0_inv = Signal(len(self.port))
176+
i1_inv = Signal(len(self.port))
177+
for bit in range(len(self.port)):
178+
m.submodules[f"i_ddr{bit}"] = Instance("IDDRXE",
179+
i_SCLK=ClockSignal(self.i_domain),
180+
i_RST=Const(0),
181+
i_D=buf.i[bit],
182+
o_Q0=i0_inv[bit],
183+
o_Q1=i1_inv[bit],
184+
)
185+
m.d.comb += self.i[0].eq(i0_inv ^ inv_mask)
186+
m.d.comb += self.i[1].eq(i1_inv ^ inv_mask)
187+
188+
if self.direction is not io.Direction.Input:
189+
o0_inv = Signal(len(self.port))
190+
o1_inv = Signal(len(self.port))
191+
m.d.comb += [
192+
o0_inv.eq(self.o[0] ^ inv_mask),
193+
o1_inv.eq(self.o[1] ^ inv_mask),
194+
]
195+
for bit in range(len(self.port)):
196+
m.submodules[f"o_ddr{bit}"] = Instance("ODDRXE",
197+
i_SCLK=ClockSignal(self.o_domain),
198+
i_RST=Const(0),
199+
i_D0=o0_inv[bit],
200+
i_D1=o1_inv[bit],
201+
o_Q=buf.o[bit],
202+
)
203+
_make_oereg(m, self.o_domain, ~self.oe, buf.t)
204+
205+
return m
206+
207+
208+
class LatticePlatform(TemplatedPlatform):
168209
"""
169-
.. rubric:: Trellis toolchain
210+
.. rubric:: Trellis toolchain (ECP5 only)
170211
171212
Required tools:
172213
* ``yosys``
@@ -195,7 +236,7 @@ class LatticeECP5Platform(TemplatedPlatform):
195236
* ``{{name}}.bit``: binary bitstream.
196237
* ``{{name}}.svf``: JTAG programming vector.
197238
198-
.. rubric:: Diamond toolchain
239+
.. rubric:: Diamond toolchain (ECP5, MachXO2, MachXO3)
199240
200241
Required tools:
201242
* ``pnmainc``
@@ -217,8 +258,11 @@ class LatticeECP5Platform(TemplatedPlatform):
217258
218259
Build products:
219260
* ``{{name}}_impl/{{name}}_impl.htm``: consolidated log.
261+
* ``{{name}}.jed``: JEDEC fuse file (MachXO2, MachXO3 only).
220262
* ``{{name}}.bit``: binary bitstream.
221-
* ``{{name}}.svf``: JTAG programming vector.
263+
* ``{{name}}.svf``: JTAG programming vector (ECP5 only).
264+
* ``{{name}}_flash.svf``: JTAG programming vector for FLASH programming (MachXO2, MachXO3 only).
265+
* ``{{name}}_sram.svf``: JTAG programming vector for SRAM programming (MachXO2, MachXO3 only).
222266
"""
223267

224268
toolchain = None # selected when creating platform
@@ -378,6 +422,9 @@ class LatticeECP5Platform(TemplatedPlatform):
378422
prj_run Map -impl impl
379423
prj_run PAR -impl impl
380424
prj_run Export -impl impl -task Bitgen
425+
{% if family == "machxo2" -%}
426+
prj_run Export -impl impl -task Jedecgen
427+
{% endif %}
381428
{{get_override("script_after_export")|default("# (script_after_export placeholder)")}}
382429
""",
383430
"{{name}}.lpf": r"""
@@ -405,7 +452,7 @@ class LatticeECP5Platform(TemplatedPlatform):
405452
{{get_override("add_constraints")|default("# (add_constraints placeholder)")}}
406453
""",
407454
}
408-
_diamond_command_templates = [
455+
_diamond_command_templates_ecp5 = [
409456
# These don't have any usable command-line option overrides.
410457
r"""
411458
{{invoke_tool("pnmainc")}}
@@ -422,12 +469,54 @@ class LatticeECP5Platform(TemplatedPlatform):
422469
-if {{name}}_impl/{{name}}_impl.bit -of {{name}}.svf
423470
""",
424471
]
472+
_diamond_command_templates_machxo2 = [
473+
# These don't have any usable command-line option overrides.
474+
r"""
475+
{{invoke_tool("pnmainc")}}
476+
{{name}}.tcl
477+
""",
478+
r"""
479+
{{invoke_tool("ddtcmd")}}
480+
-oft -bit
481+
-if {{name}}_impl/{{name}}_impl.bit -of {{name}}.bit
482+
""",
483+
r"""
484+
{{invoke_tool("ddtcmd")}}
485+
-oft -jed
486+
-dev {{platform.device}}-{{platform.speed}}{{platform.package}}{{platform.grade}}
487+
-if {{name}}_impl/{{name}}_impl.jed -of {{name}}.jed
488+
""",
489+
r"""
490+
{{invoke_tool("ddtcmd")}}
491+
-oft -svfsingle -revd -op "FLASH Erase,Program,Verify"
492+
-if {{name}}_impl/{{name}}_impl.jed -of {{name}}_flash.svf
493+
""",
494+
r"""
495+
{{invoke_tool("ddtcmd")}}
496+
-oft -svfsingle -revd -op "SRAM Fast Program"
497+
-if {{name}}_impl/{{name}}_impl.bit -of {{name}}_sram.svf
498+
""",
499+
]
425500

426501
# Common logic
427502

428-
def __init__(self, *, toolchain="Trellis"):
503+
def __init__(self, *, toolchain=None):
429504
super().__init__()
430505

506+
device = self.device.lower()
507+
if device.startswith(("lfe5", "lae5")):
508+
self.family = "ecp5"
509+
elif device.startswith(("lcmxo2-", "lcmxo3l", "lcmxo3d", "lamxo2-", "lamxo3l", "lamxo3d", "lfmnx-")):
510+
self.family = "machxo2"
511+
else:
512+
raise ValueError(f"Device '{self.device}' is not recognized")
513+
514+
if toolchain is None:
515+
if self.family == "ecp5":
516+
toolchain = "Trellis"
517+
else:
518+
toolchain = "Diamond"
519+
431520
assert toolchain in ("Trellis", "Diamond")
432521
self.toolchain = toolchain
433522

@@ -452,23 +541,46 @@ def command_templates(self):
452541
if self.toolchain == "Trellis":
453542
return self._trellis_command_templates
454543
if self.toolchain == "Diamond":
455-
return self._diamond_command_templates
544+
if self.family == "ecp5":
545+
return self._diamond_command_templates_ecp5
546+
if self.family == "machxo2":
547+
return self._diamond_command_templates_machxo2
456548
assert False
457549

550+
# These numbers were extracted from
551+
# "MachXO2 sysCLOCK PLL Design and Usage Guide"
552+
_supported_osch_freqs = [
553+
2.08, 2.15, 2.22, 2.29, 2.38, 2.46, 2.56, 2.66, 2.77, 2.89,
554+
3.02, 3.17, 3.33, 3.50, 3.69, 3.91, 4.16, 4.29, 4.43, 4.59,
555+
4.75, 4.93, 5.12, 5.32, 5.54, 5.78, 6.05, 6.33, 6.65, 7.00,
556+
7.39, 7.82, 8.31, 8.58, 8.87, 9.17, 9.50, 9.85, 10.23, 10.64,
557+
11.08, 11.57, 12.09, 12.67, 13.30, 14.00, 14.78, 15.65, 15.65, 16.63,
558+
17.73, 19.00, 20.46, 22.17, 24.18, 26.60, 29.56, 33.25, 38.00, 44.33,
559+
53.20, 66.50, 88.67, 133.00
560+
]
561+
458562
@property
459563
def default_clk_constraint(self):
460564
if self.default_clk == "OSCG":
565+
# Internal high-speed oscillator on ECP5 devices.
461566
return Clock(310e6 / self.oscg_div)
567+
if self.default_clk == "OSCH":
568+
# Internal high-speed oscillator on MachXO2/MachXO3L devices.
569+
# It can have a range of frequencies.
570+
assert self.osch_frequency in self._supported_osch_freqs
571+
return Clock(int(self.osch_frequency * 1e6))
572+
# Otherwise, use the defined Clock resource.
462573
return super().default_clk_constraint
463574

464575
def create_missing_domain(self, name):
465-
# Lattice ECP5 devices have two global set/reset signals: PUR, which is driven at startup
576+
# Lattice devices have two global set/reset signals: PUR, which is driven at startup
466577
# by the configuration logic and unconditionally resets every storage element, and GSR,
467578
# which is driven by user logic and each storage element may be configured as affected or
468579
# unaffected by GSR. PUR is purely asynchronous, so even though it is a low-skew global
469580
# network, its deassertion may violate a setup/hold constraint with relation to a user
470581
# clock. To avoid this, a GSR/SGSR instance should be driven synchronized to user clock.
471582
if name == "sync" and self.default_clk is not None:
583+
using_osch = False
472584
m = Module()
473585
if self.default_clk == "OSCG":
474586
if not hasattr(self, "oscg_div"):
@@ -480,6 +592,14 @@ def create_missing_domain(self, name):
480592
.format(self.oscg_div))
481593
clk_i = Signal()
482594
m.submodules += Instance("OSCG", p_DIV=self.oscg_div, o_OSC=clk_i)
595+
elif self.default_clk == "OSCH":
596+
osch_freq = self.osch_frequency
597+
if osch_freq not in self._supported_osch_freqs:
598+
raise ValueError("Frequency {!r} is not valid for OSCH clock. Valid frequencies are {!r}"
599+
.format(osch_freq, self._supported_osch_freqs))
600+
osch_freq_param = f"{float(osch_freq):.2f}"
601+
clk_i = Signal()
602+
m.submodules += [ Instance("OSCH", p_NOM_FREQ=osch_freq_param, i_STDBY=Const(0), o_OSC=clk_i, o_SEDSTDBY=Signal()) ]
483603
else:
484604
clk_i = self.request(self.default_clk).i
485605
if self.default_rst is not None:
@@ -489,7 +609,7 @@ def create_missing_domain(self, name):
489609

490610
gsr0 = Signal()
491611
gsr1 = Signal()
492-
# There is no end-of-startup signal on ECP5, but PUR is released after IOB enable, so
612+
# There is no end-of-startup signal on Lattice, but PUR is released after IOB enable, so
493613
# a simple reset synchronizer (with PUR as the asynchronous reset) does the job.
494614
m.submodules += [
495615
Instance("FD1S3AX", p_GSR="DISABLED", i_CK=clk_i, i_D=~rst_i, o_Q=gsr0),
@@ -512,7 +632,12 @@ def get_io_buffer(self, buffer):
512632
elif isinstance(buffer, io.FFBuffer):
513633
result = FFBuffer(buffer.direction, buffer.port)
514634
elif isinstance(buffer, io.DDRBuffer):
515-
result = DDRBuffer(buffer.direction, buffer.port)
635+
if self.family == "ecp5":
636+
result = DDRBufferECP5(buffer.direction, buffer.port)
637+
elif self.family == "machxo2":
638+
result = DDRBufferMachXO2(buffer.direction, buffer.port)
639+
else:
640+
raise NotImplementedError # :nocov:
516641
else:
517642
raise TypeError(f"Unsupported buffer type {buffer!r}") # :nocov:
518643
if buffer.direction is not io.Direction.Output:
@@ -522,5 +647,5 @@ def get_io_buffer(self, buffer):
522647
result.oe = buffer.oe
523648
return result
524649

525-
# CDC primitives are not currently specialized for ECP5.
650+
# CDC primitives are not currently specialized for Lattice.
526651
# While Diamond supports false path constraints; nextpnr-ecp5 does not.

0 commit comments

Comments
 (0)