build/lattice: cleanup/simplify (no functional changes)

icestorm still need to be cleaned up
This commit is contained in:
Florent Kermarrec 2019-12-06 12:51:50 +01:00
parent 946478a71e
commit 8fb3f9a90d
3 changed files with 141 additions and 118 deletions

View File

@ -19,7 +19,7 @@ from litex.build.lattice import common
def _produces_jedec(device):
return device.startswith("LCMX")
# IO Constraints (.lpf) ----------------------------------------------------------------------------
# Constraints (.lpf) -------------------------------------------------------------------------------
def _format_constraint(c):
if isinstance(c, Pins):
@ -176,12 +176,12 @@ class LatticeDiamondToolchain:
v_output.write(v_file)
platform.add_source(v_file)
# Generate design constraints file (.lpf)
_build_lpf(named_sc, named_pc, build_name)
# Generate design script file (.tcl)
_build_tcl(platform.device, platform.sources, platform.verilog_include_paths, build_name)
# Generate design timing constraints file (.lpf)
_build_lpf(named_sc, named_pc, build_name)
# Generate build script
script = _build_script(build_name, platform.device, toolchain_path)

View File

@ -1,4 +1,4 @@
# This file is Copyright (c) 2015-2018 Florent Kermarrec <florent@enjoy-digital.fr>
# This file is Copyright (c) 2015-2019 Florent Kermarrec <florent@enjoy-digital.fr>
# This file is Copyright (c) 2017-2018 William D. Jones <thor0505@comcast.net>
# License: BSD
@ -8,6 +8,7 @@ import subprocess
from litex.build.generic_programmer import GenericProgrammer
from litex.build import tools
# LatticeProgrammer --------------------------------------------------------------------------------
class LatticeProgrammer(GenericProgrammer):
needs_bitreverse = False
@ -21,6 +22,7 @@ class LatticeProgrammer(GenericProgrammer):
tools.write_to_file(xcf_file, xcf_content)
subprocess.call(["pgrcmd", "-infile", xcf_file])
# IceStormProgrammer -------------------------------------------------------------------------------
class IceStormProgrammer(GenericProgrammer):
needs_bitreverse = False
@ -31,6 +33,7 @@ class IceStormProgrammer(GenericProgrammer):
def load_bitstream(self, bitstream_file):
subprocess.call(["iceprog", "-S", bitstream_file])
# IceBurnProgrammer --------------------------------------------------------------------------------
class IceBurnProgrammer(GenericProgrammer):
def __init__(self, iceburn_path):
@ -42,6 +45,7 @@ class IceBurnProgrammer(GenericProgrammer):
def load_bitstream(self, bitstream_file):
subprocess.call([self.iceburn, "-evw", bitstream_file])
# TinyFpgaBProgrammer ------------------------------------------------------------------------------
class TinyFpgaBProgrammer(GenericProgrammer):
needs_bitreverse = False
@ -57,6 +61,7 @@ class TinyFpgaBProgrammer(GenericProgrammer):
def boot(self):
subprocess.call(["tinyfpgab", "-b"])
# TinyProgProgrammer -------------------------------------------------------------------------------
# Different bootloader protocol requires different application. In the basic
# case, command-line arguments are the same. Note that this programmer can
@ -85,6 +90,7 @@ class TinyProgProgrammer(GenericProgrammer):
def boot(self):
subprocess.call(["tinyprog", "-b"])
# MyStormProgrammer --------------------------------------------------------------------------------
class MyStormProgrammer(GenericProgrammer):
def __init__(self, serial_port):

View File

@ -1,4 +1,4 @@
# This file is Copyright (c) 2018 Florent Kermarrec <florent@enjoy-digital.fr>
# This file is Copyright (c) 2018-2019 Florent Kermarrec <florent@enjoy-digital.fr>
# This file is Copyright (c) 2018-2019 David Shah <dave@ds0.me>
# This file is Copyright (c) 2018 William D. Jones <thor0505@comcast.net>
# License: BSD
@ -13,35 +13,7 @@ from litex.build.generic_platform import *
from litex.build import tools
from litex.build.lattice import common
# TODO:
# - check/document attr_translate.
nextpnr_ecp5_architectures = {
"lfe5u-25f": "25k",
"lfe5u-45f": "45k",
"lfe5u-85f": "85k",
"lfe5um-25f": "um-25k",
"lfe5um-45f": "um-45k",
"lfe5um-85f": "um-85k",
"lfe5um5g-25f": "um5g-25k",
"lfe5um5g-45f": "um5g-45k",
"lfe5um5g-85f": "um5g-85k",
}
def nextpnr_ecp5_package(package):
if "285" in package:
return "CSFBGA285"
elif "256" in package:
return "CABGA256"
elif "381" in package:
return "CABGA381"
elif "554" in package:
return "CABGA554"
elif "756" in package:
return "CABGA756"
raise ValueError("Unknown package")
# IO Constraints (.lpf) ----------------------------------------------------------------------------
def _format_constraint(c):
if isinstance(c, Pins):
@ -54,51 +26,112 @@ def _format_constraint(c):
def _format_lpf(signame, pin, others, resname):
fmt_c = [_format_constraint(c) for c in ([Pins(pin)] + others)]
r = ""
lpf = []
for pre, suf in fmt_c:
r += pre + "\"" + signame + "\"" + suf + ";\n"
return r
lpf.append(pre + "\"" + signame + "\"" + suf + ";")
return "\n".join(lpf)
def _build_lpf(named_sc, named_pc):
r = "BLOCK RESETPATHS;\n"
r += "BLOCK ASYNCPATHS;\n"
def _build_lpf(named_sc, named_pc, build_name):
lpf = []
lpf.append("BLOCK RESETPATHS;")
lpf.append("BLOCK ASYNCPATHS;")
for sig, pins, others, resname in named_sc:
if len(pins) > 1:
for i, p in enumerate(pins):
r += _format_lpf(sig + "[" + str(i) + "]", p, others, resname)
lpf.append(_format_lpf(sig + "[" + str(i) + "]", p, others, resname))
else:
r += _format_lpf(sig, pins[0], others, resname)
lpf.append(_format_lpf(sig, pins[0], others, resname))
if named_pc:
r += "\n" + "\n\n".join(named_pc)
return r
lpf.append("\n\n".join(named_pc))
tools.write_to_file(build_name + ".lpf", "\n".join(lpf))
# Yosys/Nextpnr Helpers/Templates ------------------------------------------------------------------
def _build_script(source, build_template, build_name, architecture,
package, freq_constraint, timingstrict):
_yosys_template = [
"{read_files}",
"attrmap -tocase keep -imap keep=\"true\" keep=1 -imap keep=\"false\" keep=0 -remove keep=0",
"synth_ecp5 -abc9 {nwl} -json {build_name}.json -top {build_name}",
]
def _yosys_import_sources(platform):
includes = ""
reads = []
for path in platform.verilog_include_paths:
includes += " -I" + path
for filename, language, library in platform.sources:
reads.append("read_{}{} {}".format(
language, includes, filename))
return "\n".join(reads)
def _build_yosys(template, platform, nowidelut, build_name):
ys = []
for l in template:
ys.append(l.format(
build_name = build_name,
nwl = "-nowidelut" if nowidelut else "",
read_files = _yosys_import_sources(platform)
))
tools.write_to_file(build_name + ".ys", "\n".join(ys))
nextpnr_ecp5_architectures = {
"lfe5u-25f" : "25k",
"lfe5u-45f" : "45k",
"lfe5u-85f" : "85k",
"lfe5um-25f" : "um-25k",
"lfe5um-45f" : "um-45k",
"lfe5um-85f" : "um-85k",
"lfe5um5g-25f": "um5g-25k",
"lfe5um5g-45f": "um5g-45k",
"lfe5um5g-85f": "um5g-85k",
}
def nextpnr_ecp5_package(package):
if "256" in package:
return "CABGA256"
elif "285" in package:
return "CSFBGA285"
elif "381" in package:
return "CABGA381"
elif "554" in package:
return "CABGA554"
elif "756" in package:
return "CABGA756"
raise ValueError("Unknown package {}".format(package))
# Script -------------------------------------------------------------------------------------------
_build_template = [
"yosys -q -l {build_name}.rpt {build_name}.ys",
"nextpnr-ecp5 --json {build_name}.json --lpf {build_name}.lpf --textcfg {build_name}.config \
--{architecture} --package {package} --freq {freq_constraint} {timefailarg}",
"ecppack {build_name}.config --svf {build_name}.svf --bit {build_name}.bit"
]
def _build_script(source, build_template, build_name, architecture, package, freq_constraint, timingstrict):
if sys.platform in ("win32", "cygwin"):
script_ext = ".bat"
build_script_contents = "@echo off\nrem Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n\n"
script_contents = "@echo off\nrem Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n\n"
fail_stmt = " || exit /b"
else:
script_ext = ".sh"
build_script_contents = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\nset -e\n"
script_contents = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\nset -e\n"
fail_stmt = ""
for s in build_template:
s_fail = s + "{fail_stmt}\n" # Required so Windows scripts fail early.
build_script_contents += s_fail.format(build_name=build_name,
architecture=architecture,
package=package,
freq_constraint=freq_constraint,
timefailarg="--timing-allow-fail" if not timingstrict else "",
fail_stmt=fail_stmt)
script_contents += s_fail.format(
build_name = build_name,
architecture = architecture,
package = package,
freq_constraint = freq_constraint,
timefailarg = "--timing-allow-fail" if not timingstrict else "",
fail_stmt = fail_stmt)
build_script_file = "build_" + build_name + script_ext
tools.write_to_file(build_script_file, build_script_contents,
force_unix=False)
return build_script_file
script_file = "build_" + build_name + script_ext
tools.write_to_file(script_file, script_contents, force_unix=False)
return script_file
def _run_script(script):
if sys.platform in ("win32", "cygwin"):
@ -109,106 +142,90 @@ def _run_script(script):
if subprocess.call(shell + [script]) != 0:
raise OSError("Subprocess failed")
def yosys_import_sources(platform):
includes = ""
reads = []
for path in platform.verilog_include_paths:
includes += " -I" + path
for filename, language, library in platform.sources:
reads.append("read_{}{} {}".format(
language, includes, filename))
return "\n".join(reads)
# LatticeTrellisToolchain --------------------------------------------------------------------------
class LatticeTrellisToolchain:
attr_translate = {
# FIXME: document
"keep": ("keep", "true"),
"no_retiming": None,
"async_reg": None,
"mr_ff": None,
"mr_false_path": None,
"ars_ff1": None,
"ars_ff2": None,
"ars_false_path": None,
"no_retiming": None,
"async_reg": None,
"mr_ff": None,
"mr_false_path": None,
"ars_ff1": None,
"ars_ff2": None,
"ars_false_path": None,
"no_shreg_extract": None
}
special_overrides = common.lattice_ecpx_trellis_special_overrides
def __init__(self):
self.yosys_template = [
"{read_files}",
"attrmap -tocase keep -imap keep=\"true\" keep=1 -imap keep=\"false\" keep=0 -remove keep=0",
"synth_ecp5 -abc9 {nwl} -json {build_name}.json -top {build_name}",
]
self.build_template = [
"yosys -q -l {build_name}.rpt {build_name}.ys",
"nextpnr-ecp5 --json {build_name}.json --lpf {build_name}.lpf --textcfg {build_name}.config --{architecture} --package {package} --freq {freq_constraint} {timefailarg}",
"ecppack {build_name}.config --svf {build_name}.svf --bit {build_name}.bit"
]
self.yosys_template = _yosys_template
self.build_template = _build_template
self.freq_constraints = dict()
def build(self, platform, fragment, build_dir="build", build_name="top",
toolchain_path=None, run=True,
nowidelut=False, timingstrict=False,
**kwargs):
def build(self, platform, fragment,
build_dir = "build",
build_name = "top",
toolchain_path = None,
run = True,
nowidelut = False,
timingstrict = False,
**kwargs):
# Get default toolchain path (if not specified)
if toolchain_path is None:
toolchain_path = "/usr/share/trellis/"
# Create build directory
os.makedirs(build_dir, exist_ok=True)
cwd = os.getcwd()
os.chdir(build_dir)
# generate verilog
# Finalize design
if not isinstance(fragment, _Fragment):
fragment = fragment.get_fragment()
platform.finalize(fragment)
top_output = platform.get_verilog(fragment, name=build_name, **kwargs)
named_sc, named_pc = platform.resolve_signals(top_output.ns)
# Generate verilog
v_output = platform.get_verilog(fragment, name=build_name, **kwargs)
named_sc, named_pc = platform.resolve_signals(v_output.ns)
top_file = build_name + ".v"
top_output.write(top_file)
v_output.write(top_file)
platform.add_source(top_file)
# generate constraints
tools.write_to_file(build_name + ".lpf",
_build_lpf(named_sc, named_pc))
# Generate design constraints file (.lpf)
_build_lpf(named_sc, named_pc, build_name)
# generate yosys script
yosys_script_file = build_name + ".ys"
yosys_script_contents = "\n".join(_.format(build_name=build_name,
nwl="-nowidelut" if nowidelut else "",
read_files=yosys_import_sources(platform))
for _ in self.yosys_template)
tools.write_to_file(yosys_script_file, yosys_script_contents)
# Generate Yosys script
_build_yosys(self.yosys_template, platform, nowidelut, build_name)
# transform platform.device to nextpnr's architecture
# Translate device to Nextpnr architecture/package
(family, size, package) = platform.device.split("-")
architecture = nextpnr_ecp5_architectures[(family + "-" + size).lower()]
package = nextpnr_ecp5_package(package)
freq_constraint = str(max(self.freq_constraints.values(),
default=0.0))
package = nextpnr_ecp5_package(package)
script = _build_script(False, self.build_template, build_name,
architecture, package, freq_constraint,
timingstrict)
freq_constraint = str(max(self.freq_constraints.values(), default=0.0))
# run scripts
# Generate build script
script = _build_script(False, self.build_template, build_name, architecture, package,
freq_constraint, timingstrict)
# Run
if run:
_run_script(script)
os.chdir(cwd)
return top_output.ns
return v_output.ns
# Until nextpnr-ecp5 can handle multiple clock domains, use the same
# approach as the icestorm and use the fastest clock for timing
# constraints.
def add_period_constraint(self, platform, clk, period):
platform.add_platform_command("""FREQUENCY PORT "{clk}" {freq} MHz;""".format(freq=str(float(1/period)*1000), clk="{clk}"), clk=clk)
platform.add_platform_command("""FREQUENCY PORT "{clk}" {freq} MHz;""".format(
freq=str(float(1/period)*1000), clk="{clk}"), clk=clk)
def trellis_args(parser):
parser.add_argument("--yosys-nowidelut", action="store_true",
@ -218,6 +235,6 @@ def trellis_args(parser):
def trellis_argdict(args):
return {
"nowidelut": args.yosys_nowidelut,
"nowidelut": args.yosys_nowidelut,
"timingstrict": args.nextpnr_timingstrict,
}