Merge pull request #502 from shuffle2/master
diamond: project generation improvements
This commit is contained in:
commit
ebcf67c10f
|
@ -4,6 +4,7 @@
|
||||||
# License: BSD
|
# License: BSD
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
import subprocess
|
import subprocess
|
||||||
import shutil
|
import shutil
|
||||||
|
@ -40,18 +41,29 @@ def _format_lpf(signame, pin, others, resname):
|
||||||
return "\n".join(lpf)
|
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 = []
|
||||||
lpf.append("BLOCK RESETPATHS;")
|
lpf.append("BLOCK RESETPATHS;")
|
||||||
lpf.append("BLOCK ASYNCPATHS;")
|
lpf.append("BLOCK ASYNCPATHS;")
|
||||||
|
clk_ports = set()
|
||||||
for sig, pins, others, resname in named_sc:
|
for sig, pins, others, resname in named_sc:
|
||||||
if len(pins) > 1:
|
if len(pins) > 1:
|
||||||
for i, p in enumerate(pins):
|
for i, p in enumerate(pins):
|
||||||
lpf.append(_format_lpf(sig + "[" + str(i) + "]", p, others, resname))
|
lpf.append(_format_lpf(sig + "[" + str(i) + "]", p, others, resname))
|
||||||
else:
|
else:
|
||||||
lpf.append(_format_lpf(sig, pins[0], others, resname))
|
lpf.append(_format_lpf(sig, pins[0], others, resname))
|
||||||
|
if sig in cc.keys():
|
||||||
|
clk_ports.add(sig)
|
||||||
if named_pc:
|
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))
|
tools.write_to_file(build_name + ".lpf", "\n".join(lpf))
|
||||||
|
|
||||||
# Project (.tcl) -----------------------------------------------------------------------------------
|
# Project (.tcl) -----------------------------------------------------------------------------------
|
||||||
|
@ -67,13 +79,15 @@ def _build_tcl(device, sources, vincpaths, build_name):
|
||||||
"-synthesis \"synplify\""
|
"-synthesis \"synplify\""
|
||||||
]))
|
]))
|
||||||
|
|
||||||
|
def tcl_path(path): return path.replace("\\", "/")
|
||||||
|
|
||||||
# Add include paths
|
# 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 + "\"}")
|
tcl.append("prj_impl option {include path} {\"" + vincpath + "\"}")
|
||||||
|
|
||||||
# Add sources
|
# Add sources
|
||||||
for filename, language, library in 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
|
# Set top level
|
||||||
tcl.append("prj_impl option top \"{}\"".format(build_name))
|
tcl.append("prj_impl option top \"{}\"".format(build_name))
|
||||||
|
@ -89,6 +103,10 @@ def _build_tcl(device, sources, vincpaths, build_name):
|
||||||
tcl.append("prj_run Export -impl impl -task Bitgen")
|
tcl.append("prj_run Export -impl impl -task Bitgen")
|
||||||
if _produces_jedec(device):
|
if _produces_jedec(device):
|
||||||
tcl.append("prj_run Export -impl impl -task Jedecgen")
|
tcl.append("prj_run Export -impl impl -task Jedecgen")
|
||||||
|
|
||||||
|
# Cleanly close the project
|
||||||
|
tcl.append("prj_project close")
|
||||||
|
|
||||||
tools.write_to_file(build_name + ".tcl", "\n".join(tcl))
|
tools.write_to_file(build_name + ".tcl", "\n".join(tcl))
|
||||||
|
|
||||||
# Script -------------------------------------------------------------------------------------------
|
# Script -------------------------------------------------------------------------------------------
|
||||||
|
@ -130,6 +148,35 @@ def _run_script(script):
|
||||||
if subprocess.call(shell + [script]) != 0:
|
if subprocess.call(shell + [script]) != 0:
|
||||||
raise OSError("Subprocess failed")
|
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 --------------------------------------------------------------------------
|
# LatticeDiamondToolchain --------------------------------------------------------------------------
|
||||||
|
|
||||||
class LatticeDiamondToolchain:
|
class LatticeDiamondToolchain:
|
||||||
|
@ -149,12 +196,14 @@ class LatticeDiamondToolchain:
|
||||||
special_overrides = common.lattice_ecp5_special_overrides
|
special_overrides = common.lattice_ecp5_special_overrides
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
self.period_constraints = []
|
||||||
self.false_paths = set() # FIXME: use it
|
self.false_paths = set() # FIXME: use it
|
||||||
|
|
||||||
def build(self, platform, fragment,
|
def build(self, platform, fragment,
|
||||||
build_dir = "build",
|
build_dir = "build",
|
||||||
build_name = "top",
|
build_name = "top",
|
||||||
run = True,
|
run = True,
|
||||||
|
timingstrict = True,
|
||||||
**kwargs):
|
**kwargs):
|
||||||
|
|
||||||
# Create build directory
|
# Create build directory
|
||||||
|
@ -174,8 +223,15 @@ class LatticeDiamondToolchain:
|
||||||
v_output.write(v_file)
|
v_output.write(v_file)
|
||||||
platform.add_source(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)
|
# 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)
|
# Generate design script file (.tcl)
|
||||||
_build_tcl(platform.device, platform.sources, platform.verilog_include_paths, build_name)
|
_build_tcl(platform.device, platform.sources, platform.verilog_include_paths, build_name)
|
||||||
|
@ -186,16 +242,18 @@ class LatticeDiamondToolchain:
|
||||||
# Run
|
# Run
|
||||||
if run:
|
if run:
|
||||||
_run_script(script)
|
_run_script(script)
|
||||||
|
if timingstrict:
|
||||||
|
_check_timing(build_name)
|
||||||
|
|
||||||
os.chdir(cwd)
|
os.chdir(cwd)
|
||||||
|
|
||||||
return v_output.ns
|
return v_output.ns
|
||||||
|
|
||||||
def add_period_constraint(self, platform, clk, period):
|
def add_period_constraint(self, platform, clk, period):
|
||||||
clk.attr.add("keep")
|
|
||||||
# TODO: handle differential clk
|
# TODO: handle differential clk
|
||||||
platform.add_platform_command("""FREQUENCY PORT "{clk}" {freq} MHz;""".format(
|
clk.attr.add("keep")
|
||||||
freq=str(float(1/period)*1000), clk="{clk}"), clk=clk)
|
freq = str(float(1/period)*1000)
|
||||||
|
self.period_constraints.append((clk, freq))
|
||||||
|
|
||||||
def add_false_path_constraint(self, platform, from_, to):
|
def add_false_path_constraint(self, platform, from_, to):
|
||||||
from_.attr.add("keep")
|
from_.attr.add("keep")
|
||||||
|
|
Loading…
Reference in New Issue