build/lattice/diamond: use diamondc instead of pnmainc (avoid having to set environme...
[litex.git] / litex / build / lattice / diamond.py
1 # This file is Copyright (c) 2015-2019 Florent Kermarrec <florent@enjoy-digital.fr>
2 # This file is Copyright (c) 2017-2018 Sergiusz Bazanski <q3k@q3k.org>
3 # This file is Copyright (c) 2017 William D. Jones <thor0505@comcast.net>
4 # License: BSD
5
6 import os
7 import re
8 import sys
9 import math
10 import subprocess
11 import shutil
12
13 from migen.fhdl.structure import _Fragment
14
15 from litex.gen.fhdl.verilog import DummyAttrTranslate
16
17 from litex.build.generic_platform import *
18 from litex.build import tools
19 from litex.build.lattice import common
20
21 # Helpers ------------------------------------------------------------------------------------------
22
23 def _produces_jedec(device):
24 return device.startswith("LCMX")
25
26 # Constraints (.lpf) -------------------------------------------------------------------------------
27
28 def _format_constraint(c):
29 if isinstance(c, Pins):
30 return ("LOCATE COMP ", " SITE " + "\"" + c.identifiers[0] + "\"")
31 elif isinstance(c, IOStandard):
32 return ("IOBUF PORT ", " IO_TYPE=" + c.name)
33 elif isinstance(c, Misc):
34 return ("IOBUF PORT ", " " + c.misc)
35
36
37 def _format_lpf(signame, pin, others, resname):
38 fmt_c = [_format_constraint(c) for c in ([Pins(pin)] + others)]
39 lpf = []
40 for pre, suf in fmt_c:
41 lpf.append(pre + "\"" + signame + "\"" + suf + ";")
42 return "\n".join(lpf)
43
44
45 def _build_lpf(named_sc, named_pc, clocks, vns, build_name):
46 lpf = []
47 lpf.append("BLOCK RESETPATHS;")
48 lpf.append("BLOCK ASYNCPATHS;")
49 for sig, pins, others, resname in named_sc:
50 if len(pins) > 1:
51 for i, p in enumerate(pins):
52 lpf.append(_format_lpf(sig + "[" + str(i) + "]", p, others, resname))
53 else:
54 lpf.append(_format_lpf(sig, pins[0], others, resname))
55 if named_pc:
56 lpf.append("\n".join(named_pc))
57
58 # Note: .lpf is only used post-synthesis, Synplify constraints clocks by default to 200MHz.
59 for clk, period in clocks.items():
60 clk_name = vns.get_name(clk)
61 lpf.append("FREQUENCY {} \"{}\" {} MHz;".format(
62 "PORT" if clk_name in [name for name, _, _, _ in named_sc] else "NET",
63 clk_name,
64 str(1e3/period)))
65
66 tools.write_to_file(build_name + ".lpf", "\n".join(lpf))
67
68 # Project (.tcl) -----------------------------------------------------------------------------------
69
70 def _build_tcl(device, sources, vincpaths, build_name):
71 tcl = []
72 # Create project
73 tcl.append(" ".join([
74 "prj_project",
75 "new -name \"{}\"".format(build_name),
76 "-impl \"impl\"",
77 "-dev {}".format(device),
78 "-synthesis \"synplify\""
79 ]))
80
81 def tcl_path(path): return path.replace("\\", "/")
82
83 # Add include paths
84 vincpath = ";".join(map(lambda x: tcl_path(x), vincpaths))
85 tcl.append("prj_impl option {include path} {\"" + vincpath + "\"}")
86
87 # Add sources
88 for filename, language, library in sources:
89 tcl.append("prj_src add \"{}\" -work {}".format(tcl_path(filename), library))
90
91 # Set top level
92 tcl.append("prj_impl option top \"{}\"".format(build_name))
93
94 # Save project
95 tcl.append("prj_project save")
96
97 # Build flow
98 tcl.append("prj_run Synthesis -impl impl -forceOne")
99 tcl.append("prj_run Translate -impl impl")
100 tcl.append("prj_run Map -impl impl")
101 tcl.append("prj_run PAR -impl impl")
102 tcl.append("prj_run Export -impl impl -task Bitgen")
103 if _produces_jedec(device):
104 tcl.append("prj_run Export -impl impl -task Jedecgen")
105
106 # Close project
107 tcl.append("prj_project close")
108
109 tools.write_to_file(build_name + ".tcl", "\n".join(tcl))
110
111 # Script -------------------------------------------------------------------------------------------
112
113 def _build_script(build_name, device):
114 if sys.platform in ("win32", "cygwin"):
115 script_ext = ".bat"
116 script_contents = "@echo off\nrem Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n\n"
117 copy_stmt = "copy"
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 copy_stmt = "cp"
123 fail_stmt = ""
124
125 script_contents += "diamondc {tcl_script}{fail_stmt}\n".format(
126 tcl_script = build_name + ".tcl",
127 fail_stmt = fail_stmt)
128 for ext in (".bit", ".jed"):
129 if ext == ".jed" and not _produces_jedec(device):
130 continue
131 script_contents += "{copy_stmt} {diamond_product} {migen_product} {fail_stmt}\n".format(
132 copy_stmt = copy_stmt,
133 fail_stmt = fail_stmt,
134 diamond_product = os.path.join("impl", build_name + "_impl" + ext),
135 migen_product = build_name + ext)
136
137 build_script_file = "build_" + build_name + script_ext
138 tools.write_to_file(build_script_file, script_contents, force_unix=False)
139 return build_script_file
140
141 def _run_script(script):
142 if sys.platform in ("win32", "cygwin"):
143 shell = ["cmd", "/c"]
144 else:
145 shell = ["bash"]
146
147 if subprocess.call(shell + [script]) != 0:
148 raise OSError("Subprocess failed")
149
150 def _check_timing(build_name):
151 lines = open("impl/{}_impl.par".format(build_name), "r").readlines()
152 runs = [None, None]
153 for i in range(len(lines)-1):
154 if lines[i].startswith("Level/") and lines[i+1].startswith("Cost "):
155 runs[0] = i + 2
156 if lines[i].startswith("* : Design saved.") and runs[0] is not None:
157 runs[1] = i
158 break
159 assert all(map(lambda x: x is not None, runs))
160
161 p = re.compile(r"(^\s*\S+\s+\*?\s+[0-9]+\s+)(\S+)(\s+\S+\s+)(\S+)(\s+.*)")
162 for l in lines[runs[0]:runs[1]]:
163 m = p.match(l)
164 if m is None: continue
165 limit = 1e-8
166 setup = m.group(2)
167 hold = m.group(4)
168 # If there were no freq constraints in lpf, ratings will be dashed.
169 # results will likely be terribly unreliable, so bail
170 assert not setup == hold == "-", "No timing constraints were provided"
171 setup, hold = map(float, (setup, hold))
172 if setup > limit and hold > limit:
173 # At least one run met timing
174 # XXX is this necessarily the run from which outputs will be used?
175 return
176 raise Exception("Failed to meet timing")
177
178 # LatticeDiamondToolchain --------------------------------------------------------------------------
179
180 class LatticeDiamondToolchain:
181 attr_translate = {
182 # FIXME: document
183 "keep": ("syn_keep", "true"),
184 "no_retiming": ("syn_no_retiming", "true"),
185 "async_reg": None,
186 "mr_ff": None,
187 "mr_false_path": None,
188 "ars_ff1": None,
189 "ars_ff2": None,
190 "ars_false_path": None,
191 "no_shreg_extract": None
192 }
193
194 special_overrides = common.lattice_ecp5_special_overrides
195
196 def __init__(self):
197 self.clocks = {}
198 self.false_paths = set() # FIXME: use it
199
200 def build(self, platform, fragment,
201 build_dir = "build",
202 build_name = "top",
203 run = True,
204 timingstrict = True,
205 **kwargs):
206
207 # Create build directory
208 os.makedirs(build_dir, exist_ok=True)
209 cwd = os.getcwd()
210 os.chdir(build_dir)
211
212 # Finalize design
213 if not isinstance(fragment, _Fragment):
214 fragment = fragment.get_fragment()
215 platform.finalize(fragment)
216
217 # Generate verilog
218 v_output = platform.get_verilog(fragment, name=build_name, **kwargs)
219 named_sc, named_pc = platform.resolve_signals(v_output.ns)
220 v_file = build_name + ".v"
221 v_output.write(v_file)
222 platform.add_source(v_file)
223
224 # Generate design constraints file (.lpf)
225 _build_lpf(named_sc, named_pc, self.clocks, v_output.ns, build_name)
226
227 # Generate design script file (.tcl)
228 _build_tcl(platform.device, platform.sources, platform.verilog_include_paths, build_name)
229
230 # Generate build script
231 script = _build_script(build_name, platform.device)
232
233 # Run
234 if run:
235 _run_script(script)
236 if timingstrict:
237 _check_timing(build_name)
238
239 os.chdir(cwd)
240
241 return v_output.ns
242
243 def add_period_constraint(self, platform, clk, period):
244 clk.attr.add("keep")
245 period = math.floor(period*1e3)/1e3 # round to lowest picosecond
246 if clk in self.clocks:
247 if period != self.clocks[clk]:
248 raise ValueError("Clock already constrained to {:.2f}ns, new constraint to {:.2f}ns"
249 .format(self.clocks[clk], period))
250 self.clocks[clk] = period
251
252 def add_false_path_constraint(self, platform, from_, to):
253 from_.attr.add("keep")
254 to.attr.add("keep")
255 if (to, from_) not in self.false_paths:
256 self.false_paths.add((from_, to))