diamond: clock constraint improvements

Specify NET or PORT for freq constraints

Add equivalent timing closure check that diamond ui uses,
and default to asserting check has passed
This commit is contained in:
Shawn Hoffman 2020-05-08 21:00:24 -07:00
parent fbbbdf03b5
commit 9b782bd7da

View file

@ -4,6 +4,7 @@
# License: BSD
import os
import re
import sys
import subprocess
import shutil
@ -40,18 +41,29 @@ def _format_lpf(signame, pin, others, resname):
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) -----------------------------------------------------------------------------------
@ -67,13 +79,15 @@ def _build_tcl(device, sources, vincpaths, build_name):
"-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))
@ -89,6 +103,7 @@ def _build_tcl(device, sources, vincpaths, 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 -------------------------------------------------------------------------------------------
@ -130,6 +145,35 @@ def _run_script(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:
@ -149,12 +193,14 @@ 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
@ -174,8 +220,15 @@ class LatticeDiamondToolchain:
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)
@ -186,16 +239,18 @@ class LatticeDiamondToolchain:
# 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")