Merge branch 'master' of https://github.com/enjoy-digital/litex
[litex.git] / litex / build / lattice / trellis.py
1 # This file is Copyright (c) 2018 Florent Kermarrec <florent@enjoy-digital.fr>
2 # This file is Copyright (c) 2018 William D. Jones <thor0505@comcast.net>
3 # License: BSD
4
5 import os
6 import subprocess
7 import sys
8
9 from migen.fhdl.structure import _Fragment
10
11 from litex.build.generic_platform import *
12 from litex.build import tools
13 from litex.build.lattice import common
14
15 # TODO:
16 # - check/document attr_translate.
17
18 nextpnr_ecp5_architectures = {
19 "lfe5u-25f": "25k",
20 "lfe5u-45f": "45k",
21 "lfe5u-85f": "85k",
22 "lfe5um-25f": "um-25k",
23 "lfe5um-45f": "um-45k",
24 "lfe5um-85f": "um-85k",
25 "lfe5um5g-25f": "um5g-25k",
26 "lfe5um5g-45f": "um5g-45k",
27 "lfe5um5g-85f": "um5g-85k",
28 }
29
30
31 def nextpnr_ecp5_package(package):
32 if "285" in package:
33 return "CSFBGA285"
34 elif "381" in package:
35 return "CABGA381"
36 elif "554" in package:
37 return "CABGA554"
38 elif "756" in package:
39 return "CABGA756"
40 raise ValueError("Unknown package")
41
42
43 def _format_constraint(c):
44 if isinstance(c, Pins):
45 return ("LOCATE COMP ", " SITE " + "\"" + c.identifiers[0] + "\"")
46 elif isinstance(c, IOStandard):
47 return ("IOBUF PORT ", " IO_TYPE=" + c.name)
48 elif isinstance(c, Misc):
49 return ("IOBUF PORT ", " " + c.misc)
50
51
52 def _format_lpf(signame, pin, others, resname):
53 fmt_c = [_format_constraint(c) for c in ([Pins(pin)] + others)]
54 r = ""
55 for pre, suf in fmt_c:
56 r += pre + "\"" + signame + "\"" + suf + ";\n"
57 return r
58
59
60 def _build_lpf(named_sc, named_pc):
61 r = "BLOCK RESETPATHS;\n"
62 r += "BLOCK ASYNCPATHS;\n"
63 for sig, pins, others, resname in named_sc:
64 if len(pins) > 1:
65 for i, p in enumerate(pins):
66 r += _format_lpf(sig + "[" + str(i) + "]", p, others, resname)
67 else:
68 r += _format_lpf(sig, pins[0], others, resname)
69 if named_pc:
70 r += "\n" + "\n\n".join(named_pc)
71 return r
72
73
74 def _build_script(source, build_template, build_name, architecture,
75 package, freq_constraint):
76 if sys.platform in ("win32", "cygwin"):
77 script_ext = ".bat"
78 build_script_contents = "@echo off\nrem Autogenerated by Migen\n\n"
79 fail_stmt = " || exit /b"
80 else:
81 script_ext = ".sh"
82 build_script_contents = "# Autogenerated by Migen\nset -e\n\n"
83 fail_stmt = ""
84
85 for s in build_template:
86 s_fail = s + "{fail_stmt}\n" # Required so Windows scripts fail early.
87 build_script_contents += s_fail.format(build_name=build_name,
88 architecture=architecture,
89 package=package,
90 freq_constraint=freq_constraint,
91 fail_stmt=fail_stmt)
92
93 build_script_file = "build_" + build_name + script_ext
94 tools.write_to_file(build_script_file, build_script_contents,
95 force_unix=False)
96 return build_script_file
97
98
99 def _run_script(script):
100 if sys.platform in ("win32", "cygwin"):
101 shell = ["cmd", "/c"]
102 else:
103 shell = ["bash"]
104
105 if subprocess.call(shell + [script]) != 0:
106 raise OSError("Subprocess failed")
107
108
109 def yosys_import_sources(platform):
110 includes = ""
111 reads = []
112 for path in platform.verilog_include_paths:
113 includes += " -I" + path
114 for filename, language, library in platform.sources:
115 reads.append("read_{}{} {}".format(
116 language, includes, filename))
117 return "\n".join(reads)
118
119
120 class LatticeTrellisToolchain:
121 attr_translate = {
122 # FIXME: document
123 "keep": ("keep", "true"),
124 "no_retiming": None,
125 "async_reg": None,
126 "mr_ff": None,
127 "mr_false_path": None,
128 "ars_ff1": None,
129 "ars_ff2": None,
130 "ars_false_path": None,
131 "no_shreg_extract": None
132 }
133
134 special_overrides = common.lattice_ecpx_trellis_special_overrides
135
136 def __init__(self):
137 self.yosys_template = [
138 "{read_files}",
139 "attrmap -tocase keep -imap keep=\"true\" keep=1 -imap keep=\"false\" keep=0 -remove keep=0",
140 "synth_ecp5 -json {build_name}.json -top {build_name}",
141 ]
142
143 self.build_template = [
144 "yosys -q -l {build_name}.rpt {build_name}.ys",
145 "nextpnr-ecp5 --json {build_name}.json --lpf {build_name}.lpf --textcfg {build_name}.config --{architecture} --package {package} --freq {freq_constraint}",
146 "ecppack {build_name}.config --svf {build_name}.svf --bit {build_name}.bit"
147 ]
148
149 self.freq_constraints = dict()
150
151 def build(self, platform, fragment, build_dir="build", build_name="top",
152 toolchain_path=None, run=True, **kwargs):
153 if toolchain_path is None:
154 toolchain_path = "/usr/share/trellis/"
155 os.makedirs(build_dir, exist_ok=True)
156 cwd = os.getcwd()
157 os.chdir(build_dir)
158
159 # generate verilog
160 if not isinstance(fragment, _Fragment):
161 fragment = fragment.get_fragment()
162 platform.finalize(fragment)
163
164 top_output = platform.get_verilog(fragment, name=build_name, **kwargs)
165 named_sc, named_pc = platform.resolve_signals(top_output.ns)
166 top_file = build_name + ".v"
167 top_output.write(top_file)
168 platform.add_source(top_file)
169
170 # generate constraints
171 tools.write_to_file(build_name + ".lpf",
172 _build_lpf(named_sc, named_pc))
173
174 # generate yosys script
175 yosys_script_file = build_name + ".ys"
176 yosys_script_contents = "\n".join(_.format(build_name=build_name,
177 read_files=yosys_import_sources(platform))
178 for _ in self.yosys_template)
179 tools.write_to_file(yosys_script_file, yosys_script_contents)
180
181 # transform platform.device to nextpnr's architecture
182 (family, size, package) = platform.device.split("-")
183 architecture = nextpnr_ecp5_architectures[(family + "-" + size).lower()]
184 package = nextpnr_ecp5_package(package)
185 freq_constraint = str(max(self.freq_constraints.values(),
186 default=0.0))
187
188 script = _build_script(False, self.build_template, build_name,
189 architecture, package, freq_constraint)
190
191 # run scripts
192 if run:
193 _run_script(script)
194
195 os.chdir(cwd)
196
197 return top_output.ns
198
199 # Until nextpnr-ecp5 can handle multiple clock domains, use the same
200 # approach as the icestorm and use the fastest clock for timing
201 # constraints.
202 def add_period_constraint(self, platform, clk, period):
203 platform.add_platform_command("""FREQUENCY PORT "{clk}" {freq} MHz;""".format(freq=str(float(1/period)*1000), clk="{clk}"), clk=clk)