mirror of
https://github.com/enjoy-digital/litex.git
synced 2025-01-04 09:52:26 -05:00
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:
parent
fbbbdf03b5
commit
9b782bd7da
1 changed files with 63 additions and 8 deletions
|
@ -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")
|
||||
|
|
Loading…
Reference in a new issue