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