Merge pull request #336 from kbeckmann/trellis-speed
[litex.git] / litex / build / lattice / trellis.py
1 # This file is Copyright (c) 2018-2019 Florent Kermarrec <florent@enjoy-digital.fr>
2 # This file is Copyright (c) 2018-2019 David Shah <dave@ds0.me>
3 # This file is Copyright (c) 2018 William D. Jones <thor0505@comcast.net>
4 # License: BSD
5
6 import os
7 import subprocess
8 import sys
9
10 from migen.fhdl.structure import _Fragment
11
12 from litex.build.generic_platform import *
13 from litex.build import tools
14 from litex.build.lattice import common
15
16 # IO Constraints (.lpf) ----------------------------------------------------------------------------
17
18 def _format_constraint(c):
19 if isinstance(c, Pins):
20 return ("LOCATE COMP ", " SITE " + "\"" + c.identifiers[0] + "\"")
21 elif isinstance(c, IOStandard):
22 return ("IOBUF PORT ", " IO_TYPE=" + c.name)
23 elif isinstance(c, Misc):
24 return ("IOBUF PORT ", " " + c.misc)
25
26
27 def _format_lpf(signame, pin, others, resname):
28 fmt_c = [_format_constraint(c) for c in ([Pins(pin)] + others)]
29 lpf = []
30 for pre, suf in fmt_c:
31 lpf.append(pre + "\"" + signame + "\"" + suf + ";")
32 return "\n".join(lpf)
33
34
35 def _build_lpf(named_sc, named_pc, build_name):
36 lpf = []
37 lpf.append("BLOCK RESETPATHS;")
38 lpf.append("BLOCK ASYNCPATHS;")
39 for sig, pins, others, resname in named_sc:
40 if len(pins) > 1:
41 for i, p in enumerate(pins):
42 lpf.append(_format_lpf(sig + "[" + str(i) + "]", p, others, resname))
43 else:
44 lpf.append(_format_lpf(sig, pins[0], others, resname))
45 if named_pc:
46 lpf.append("\n\n".join(named_pc))
47 tools.write_to_file(build_name + ".lpf", "\n".join(lpf))
48
49 # Yosys/Nextpnr Helpers/Templates ------------------------------------------------------------------
50
51 _yosys_template = [
52 "{read_files}",
53 "attrmap -tocase keep -imap keep=\"true\" keep=1 -imap keep=\"false\" keep=0 -remove keep=0",
54 "synth_ecp5 -abc9 {nwl} -json {build_name}.json -top {build_name}",
55 ]
56
57 def _yosys_import_sources(platform):
58 includes = ""
59 reads = []
60 for path in platform.verilog_include_paths:
61 includes += " -I" + path
62 for filename, language, library in platform.sources:
63 reads.append("read_{}{} {}".format(
64 language, includes, filename))
65 return "\n".join(reads)
66
67 def _build_yosys(template, platform, nowidelut, build_name):
68 ys = []
69 for l in template:
70 ys.append(l.format(
71 build_name = build_name,
72 nwl = "-nowidelut" if nowidelut else "",
73 read_files = _yosys_import_sources(platform)
74 ))
75 tools.write_to_file(build_name + ".ys", "\n".join(ys))
76
77 nextpnr_ecp5_architectures = {
78 "lfe5u-25f" : "25k",
79 "lfe5u-45f" : "45k",
80 "lfe5u-85f" : "85k",
81 "lfe5um-25f" : "um-25k",
82 "lfe5um-45f" : "um-45k",
83 "lfe5um-85f" : "um-85k",
84 "lfe5um5g-25f": "um5g-25k",
85 "lfe5um5g-45f": "um5g-45k",
86 "lfe5um5g-85f": "um5g-85k",
87 }
88
89 def nextpnr_ecp5_package(package):
90 if "256" in package:
91 return "CABGA256"
92 elif "285" in package:
93 return "CSFBGA285"
94 elif "381" in package:
95 return "CABGA381"
96 elif "554" in package:
97 return "CABGA554"
98 elif "756" in package:
99 return "CABGA756"
100 raise ValueError("Unknown package {}".format(package))
101
102 def nextpnr_ecp5_speed_grade(package):
103 return package[0] if package[0] in ["6", "7", "8"] else "6"
104
105 # Script -------------------------------------------------------------------------------------------
106
107 _build_template = [
108 "yosys -q -l {build_name}.rpt {build_name}.ys",
109 "nextpnr-ecp5 --json {build_name}.json --lpf {build_name}.lpf --textcfg {build_name}.config \
110 --{architecture} --package {package} --speed {speed_grade} {timefailarg}",
111 "ecppack {build_name}.config --svf {build_name}.svf --bit {build_name}.bit"
112 ]
113
114 def _build_script(source, build_template, build_name, architecture, package, speed_grade, timingstrict):
115 if sys.platform in ("win32", "cygwin"):
116 script_ext = ".bat"
117 script_contents = "@echo off\nrem Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n\n"
118 fail_stmt = " || exit /b"
119 else:
120 script_ext = ".sh"
121 script_contents = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\nset -e\n"
122 fail_stmt = ""
123
124 for s in build_template:
125 s_fail = s + "{fail_stmt}\n" # Required so Windows scripts fail early.
126 script_contents += s_fail.format(
127 build_name = build_name,
128 architecture = architecture,
129 package = package,
130 speed_grade = speed_grade,
131 timefailarg = "--timing-allow-fail" if not timingstrict else "",
132 fail_stmt = fail_stmt)
133
134 script_file = "build_" + build_name + script_ext
135 tools.write_to_file(script_file, script_contents, force_unix=False)
136
137 return script_file
138
139 def _run_script(script):
140 if sys.platform in ("win32", "cygwin"):
141 shell = ["cmd", "/c"]
142 else:
143 shell = ["bash"]
144
145 if subprocess.call(shell + [script]) != 0:
146 raise OSError("Subprocess failed")
147
148 # LatticeTrellisToolchain --------------------------------------------------------------------------
149
150 class LatticeTrellisToolchain:
151 attr_translate = {
152 # FIXME: document
153 "keep": ("keep", "true"),
154 "no_retiming": None,
155 "async_reg": None,
156 "mr_ff": None,
157 "mr_false_path": None,
158 "ars_ff1": None,
159 "ars_ff2": None,
160 "ars_false_path": None,
161 "no_shreg_extract": None
162 }
163
164 special_overrides = common.lattice_ecpx_trellis_special_overrides
165
166 def __init__(self):
167 self.yosys_template = _yosys_template
168 self.build_template = _build_template
169
170 def build(self, platform, fragment,
171 build_dir = "build",
172 build_name = "top",
173 toolchain_path = None,
174 run = True,
175 nowidelut = False,
176 timingstrict = False,
177 **kwargs):
178
179 # Get default toolchain path (if not specified)
180 if toolchain_path is None:
181 toolchain_path = "/usr/share/trellis/"
182
183 # Create build directory
184 os.makedirs(build_dir, exist_ok=True)
185 cwd = os.getcwd()
186 os.chdir(build_dir)
187
188 # Finalize design
189 if not isinstance(fragment, _Fragment):
190 fragment = fragment.get_fragment()
191 platform.finalize(fragment)
192
193 # Generate verilog
194 v_output = platform.get_verilog(fragment, name=build_name, **kwargs)
195 named_sc, named_pc = platform.resolve_signals(v_output.ns)
196 top_file = build_name + ".v"
197 v_output.write(top_file)
198 platform.add_source(top_file)
199
200 # Generate design constraints file (.lpf)
201 _build_lpf(named_sc, named_pc, build_name)
202
203 # Generate Yosys script
204 _build_yosys(self.yosys_template, platform, nowidelut, build_name)
205
206 # Translate device to Nextpnr architecture/package
207 (family, size, package) = platform.device.split("-")
208 architecture = nextpnr_ecp5_architectures[(family + "-" + size).lower()]
209 speed_grade = nextpnr_ecp5_speed_grade(package)
210 package = nextpnr_ecp5_package(package)
211
212 # Generate build script
213 script = _build_script(False, self.build_template, build_name, architecture, package,
214 speed_grade, timingstrict)
215
216 # Run
217 if run:
218 _run_script(script)
219
220 os.chdir(cwd)
221
222 return v_output.ns
223
224 def add_period_constraint(self, platform, clk, period):
225 platform.add_platform_command("""FREQUENCY PORT "{clk}" {freq} MHz;""".format(
226 freq=str(float(1/period)*1000), clk="{clk}"), clk=clk)
227
228 def trellis_args(parser):
229 parser.add_argument("--yosys-nowidelut", action="store_true",
230 help="pass '-nowidelut' to yosys synth_ecp5")
231 parser.add_argument("--nextpnr-timingstrict", action="store_true",
232 help="fail if timing not met, i.e., do NOT pass '--timing-allow-fail' to nextpnr")
233
234 def trellis_argdict(args):
235 return {
236 "nowidelut": args.yosys_nowidelut,
237 "timingstrict": args.nextpnr_timingstrict,
238 }