# License: BSD
import os
+import re
import sys
import subprocess
import shutil
return "\n".join(lpf)
-def _build_lpf(named_sc, named_pc, build_name):
+def _build_lpf(named_sc, named_pc, cc, build_name):
lpf = []
lpf.append("BLOCK RESETPATHS;")
lpf.append("BLOCK ASYNCPATHS;")
+ clk_ports = set()
for sig, pins, others, resname in named_sc:
if len(pins) > 1:
for i, p in enumerate(pins):
lpf.append(_format_lpf(sig + "[" + str(i) + "]", p, others, resname))
else:
lpf.append(_format_lpf(sig, pins[0], others, resname))
+ if sig in cc.keys():
+ clk_ports.add(sig)
if named_pc:
- lpf.append("\n\n".join(named_pc))
+ lpf.append("\n".join(named_pc))
+
+ # NOTE: The lpf is only used post-synthesis. Currently Synplify is fed no constraints,
+ # so defaults to inferring clocks and trying to hit 200MHz on all of them.
+ # NOTE: For additional options (PAR_ADJ, etc), use add_platform_command
+ for clk, freq in cc.items():
+ sig_type = "PORT" if clk in clk_ports else "NET"
+ lpf.append("FREQUENCY {} \"{}\" {} MHz;".format(sig_type, clk, freq))
+
tools.write_to_file(build_name + ".lpf", "\n".join(lpf))
# Project (.tcl) -----------------------------------------------------------------------------------
"-synthesis \"synplify\""
]))
+ def tcl_path(path): return path.replace("\\", "/")
+
# Add include paths
- vincpath = ';'.join(map(lambda x: x.replace('\\', '/'), vincpaths))
+ vincpath = ";".join(map(lambda x: tcl_path(x), vincpaths))
tcl.append("prj_impl option {include path} {\"" + vincpath + "\"}")
# Add sources
for filename, language, library in sources:
- tcl.append("prj_src add \"" + filename.replace("\\", "/") + "\" -work " + library)
+ tcl.append("prj_src add \"{}\" -work {}".format(tcl_path(filename), library))
# Set top level
tcl.append("prj_impl option top \"{}\"".format(build_name))
tcl.append("prj_run Export -impl impl -task Bitgen")
if _produces_jedec(device):
tcl.append("prj_run Export -impl impl -task Jedecgen")
+
tools.write_to_file(build_name + ".tcl", "\n".join(tcl))
# Script -------------------------------------------------------------------------------------------
if subprocess.call(shell + [script]) != 0:
raise OSError("Subprocess failed")
+# This operates the same way tmcheck does, but tmcheck isn't usable without gui
+def _check_timing(build_name):
+ lines = open("impl/{}_impl.par".format(build_name), "r").readlines()
+ runs = [None, None]
+ for i in range(len(lines)-1):
+ if lines[i].startswith("Level/") and lines[i+1].startswith("Cost "):
+ runs[0] = i + 2
+ if lines[i].startswith("* : Design saved.") and runs[0] is not None:
+ runs[1] = i
+ break
+ assert all(map(lambda x: x is not None, runs))
+
+ p = re.compile(r"(^\s*\S+\s+\*?\s+[0-9]+\s+)(\S+)(\s+\S+\s+)(\S+)(\s+.*)")
+ for l in lines[runs[0]:runs[1]]:
+ m = p.match(l)
+ if m is None: continue
+ limit = 1e-8
+ setup = m.group(2)
+ hold = m.group(4)
+ # if there were no freq constraints in lpf, ratings will be dashed.
+ # results will likely be terribly unreliable, so bail
+ assert not setup == hold == "-", "No timing constraints were provided"
+ setup, hold = map(float, (setup, hold))
+ if setup > limit and hold > limit:
+ # at least one run met timing
+ # XXX is this necessarily the run from which outputs will be used?
+ return
+ raise Exception("Failed to meet timing")
+
# LatticeDiamondToolchain --------------------------------------------------------------------------
class LatticeDiamondToolchain:
special_overrides = common.lattice_ecp5_special_overrides
def __init__(self):
+ self.period_constraints = []
self.false_paths = set() # FIXME: use it
def build(self, platform, fragment,
build_dir = "build",
build_name = "top",
run = True,
+ timingstrict = True,
**kwargs):
# Create build directory
v_output.write(v_file)
platform.add_source(v_file)
+ cc = {}
+ for (clk, freq) in self.period_constraints:
+ clk_name = v_output.ns.get_name(clk)
+ if clk_name in cc and cc[clk_name] != freq:
+ raise ConstraintError("Differing period constraints on {}".format(clk_name))
+ cc[clk_name] = freq
+
# Generate design constraints file (.lpf)
- _build_lpf(named_sc, named_pc, build_name)
+ _build_lpf(named_sc, named_pc, cc, build_name)
# Generate design script file (.tcl)
_build_tcl(platform.device, platform.sources, platform.verilog_include_paths, build_name)
# Run
if run:
_run_script(script)
+ if timingstrict:
+ _check_timing(build_name)
os.chdir(cwd)
return v_output.ns
def add_period_constraint(self, platform, clk, period):
- clk.attr.add("keep")
# TODO: handle differential clk
- platform.add_platform_command("""FREQUENCY PORT "{clk}" {freq} MHz;""".format(
- freq=str(float(1/period)*1000), clk="{clk}"), clk=clk)
+ clk.attr.add("keep")
+ freq = str(float(1/period)*1000)
+ self.period_constraints.append((clk, freq))
def add_false_path_constraint(self, platform, from_, to):
from_.attr.add("keep")