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>
13 from migen
.fhdl
.structure
import _Fragment
15 from litex
.gen
.fhdl
.verilog
import DummyAttrTranslate
17 from litex
.build
.generic_platform
import *
18 from litex
.build
import tools
19 from litex
.build
.lattice
import common
21 # Helpers ------------------------------------------------------------------------------------------
23 def _produces_jedec(device
):
24 return device
.startswith("LCMX")
26 # Constraints (.lpf) -------------------------------------------------------------------------------
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
)
37 def _format_lpf(signame
, pin
, others
, resname
):
38 fmt_c
= [_format_constraint(c
) for c
in ([Pins(pin
)] + others
)]
40 for pre
, suf
in fmt_c
:
41 lpf
.append(pre
+ "\"" + signame
+ "\"" + suf
+ ";")
45 def _build_lpf(named_sc
, named_pc
, clocks
, vns
, build_name
):
47 lpf
.append("BLOCK RESETPATHS;")
48 lpf
.append("BLOCK ASYNCPATHS;")
49 for sig
, pins
, others
, resname
in named_sc
:
51 for i
, p
in enumerate(pins
):
52 lpf
.append(_format_lpf(sig
+ "[" + str(i
) + "]", p
, others
, resname
))
54 lpf
.append(_format_lpf(sig
, pins
[0], others
, resname
))
56 lpf
.append("\n".join(named_pc
))
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",
66 tools
.write_to_file(build_name
+ ".lpf", "\n".join(lpf
))
68 # Project (.tcl) -----------------------------------------------------------------------------------
70 def _build_tcl(device
, sources
, vincpaths
, build_name
):
75 "new -name \"{}\"".format(build_name
),
77 "-dev {}".format(device
),
78 "-synthesis \"synplify\""
81 def tcl_path(path
): return path
.replace("\\", "/")
84 vincpath
= ";".join(map(lambda x
: tcl_path(x
), vincpaths
))
85 tcl
.append("prj_impl option {include path} {\"" + vincpath
+ "\"}")
88 for filename
, language
, library
in sources
:
89 tcl
.append("prj_src add \"{}\" -work {}".format(tcl_path(filename
), library
))
92 tcl
.append("prj_impl option top \"{}\"".format(build_name
))
95 tcl
.append("prj_project save")
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")
107 tcl
.append("prj_project close")
109 tools
.write_to_file(build_name
+ ".tcl", "\n".join(tcl
))
111 # Script -------------------------------------------------------------------------------------------
113 def _build_script(build_name
, device
):
114 if sys
.platform
in ("win32", "cygwin"):
116 script_contents
= "@echo off\nrem Autogenerated by LiteX / git: " + tools
.get_litex_git_revision() + "\n\n"
118 fail_stmt
= " || exit /b"
121 script_contents
= "# Autogenerated by LiteX / git: " + tools
.get_litex_git_revision() + "\nset -e\n"
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
):
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
)
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
141 def _run_script(script
):
142 if sys
.platform
in ("win32", "cygwin"):
143 shell
= ["cmd", "/c"]
147 if subprocess
.call(shell
+ [script
]) != 0:
148 raise OSError("Subprocess failed")
150 def _check_timing(build_name
):
151 lines
= open("impl/{}_impl.par".format(build_name
), "r").readlines()
153 for i
in range(len(lines
)-1):
154 if lines
[i
].startswith("Level/") and lines
[i
+1].startswith("Cost "):
156 if lines
[i
].startswith("* : Design saved.") and runs
[0] is not None:
159 assert all(map(lambda x
: x
is not None, runs
))
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]]:
164 if m
is None: continue
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?
176 raise Exception("Failed to meet timing")
178 # LatticeDiamondToolchain --------------------------------------------------------------------------
180 class LatticeDiamondToolchain
:
183 "keep": ("syn_keep", "true"),
184 "no_retiming": ("syn_no_retiming", "true"),
187 "mr_false_path": None,
190 "ars_false_path": None,
191 "no_shreg_extract": None
194 special_overrides
= common
.lattice_ecp5_special_overrides
198 self
.false_paths
= set() # FIXME: use it
200 def build(self
, platform
, fragment
,
207 # Create build directory
208 os
.makedirs(build_dir
, exist_ok
=True)
213 if not isinstance(fragment
, _Fragment
):
214 fragment
= fragment
.get_fragment()
215 platform
.finalize(fragment
)
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
)
224 # Generate design constraints file (.lpf)
225 _build_lpf(named_sc
, named_pc
, self
.clocks
, v_output
.ns
, build_name
)
227 # Generate design script file (.tcl)
228 _build_tcl(platform
.device
, platform
.sources
, platform
.verilog_include_paths
, build_name
)
230 # Generate build script
231 script
= _build_script(build_name
, platform
.device
)
237 _check_timing(build_name
)
243 def add_period_constraint(self
, platform
, clk
, period
):
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
252 def add_false_path_constraint(self
, platform
, from_
, to
):
253 from_
.attr
.add("keep")
255 if (to
, from_
) not in self
.false_paths
:
256 self
.false_paths
.add((from_
, to
))