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): def _produces_jedec(device):
return device.startswith("LCMX") return device.startswith("LCMX")
# IO Constraints (.lpf) ---------------------------------------------------------------------------- # Constraints (.lpf) -------------------------------------------------------------------------------
def _format_constraint(c): def _format_constraint(c):
if isinstance(c, Pins): if isinstance(c, Pins):
@ -176,12 +176,12 @@ class LatticeDiamondToolchain:
v_output.write(v_file) v_output.write(v_file)
platform.add_source(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) # 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)
# Generate design timing constraints file (.lpf)
_build_lpf(named_sc, named_pc, build_name)
# Generate build script # Generate build script
script = _build_script(build_name, platform.device, toolchain_path) 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> # This file is Copyright (c) 2017-2018 William D. Jones <thor0505@comcast.net>
# License: BSD # License: BSD
@ -8,6 +8,7 @@ import subprocess
from litex.build.generic_programmer import GenericProgrammer from litex.build.generic_programmer import GenericProgrammer
from litex.build import tools from litex.build import tools
# LatticeProgrammer --------------------------------------------------------------------------------
class LatticeProgrammer(GenericProgrammer): class LatticeProgrammer(GenericProgrammer):
needs_bitreverse = False needs_bitreverse = False
@ -21,6 +22,7 @@ class LatticeProgrammer(GenericProgrammer):
tools.write_to_file(xcf_file, xcf_content) tools.write_to_file(xcf_file, xcf_content)
subprocess.call(["pgrcmd", "-infile", xcf_file]) subprocess.call(["pgrcmd", "-infile", xcf_file])
# IceStormProgrammer -------------------------------------------------------------------------------
class IceStormProgrammer(GenericProgrammer): class IceStormProgrammer(GenericProgrammer):
needs_bitreverse = False needs_bitreverse = False
@ -31,6 +33,7 @@ class IceStormProgrammer(GenericProgrammer):
def load_bitstream(self, bitstream_file): def load_bitstream(self, bitstream_file):
subprocess.call(["iceprog", "-S", bitstream_file]) subprocess.call(["iceprog", "-S", bitstream_file])
# IceBurnProgrammer --------------------------------------------------------------------------------
class IceBurnProgrammer(GenericProgrammer): class IceBurnProgrammer(GenericProgrammer):
def __init__(self, iceburn_path): def __init__(self, iceburn_path):
@ -42,6 +45,7 @@ class IceBurnProgrammer(GenericProgrammer):
def load_bitstream(self, bitstream_file): def load_bitstream(self, bitstream_file):
subprocess.call([self.iceburn, "-evw", bitstream_file]) subprocess.call([self.iceburn, "-evw", bitstream_file])
# TinyFpgaBProgrammer ------------------------------------------------------------------------------
class TinyFpgaBProgrammer(GenericProgrammer): class TinyFpgaBProgrammer(GenericProgrammer):
needs_bitreverse = False needs_bitreverse = False
@ -57,6 +61,7 @@ class TinyFpgaBProgrammer(GenericProgrammer):
def boot(self): def boot(self):
subprocess.call(["tinyfpgab", "-b"]) subprocess.call(["tinyfpgab", "-b"])
# TinyProgProgrammer -------------------------------------------------------------------------------
# Different bootloader protocol requires different application. In the basic # Different bootloader protocol requires different application. In the basic
# case, command-line arguments are the same. Note that this programmer can # case, command-line arguments are the same. Note that this programmer can
@ -85,6 +90,7 @@ class TinyProgProgrammer(GenericProgrammer):
def boot(self): def boot(self):
subprocess.call(["tinyprog", "-b"]) subprocess.call(["tinyprog", "-b"])
# MyStormProgrammer --------------------------------------------------------------------------------
class MyStormProgrammer(GenericProgrammer): class MyStormProgrammer(GenericProgrammer):
def __init__(self, serial_port): 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-2019 David Shah <dave@ds0.me>
# This file is Copyright (c) 2018 William D. Jones <thor0505@comcast.net> # This file is Copyright (c) 2018 William D. Jones <thor0505@comcast.net>
# License: BSD # License: BSD
@ -13,8 +13,66 @@ from litex.build.generic_platform import *
from litex.build import tools from litex.build import tools
from litex.build.lattice import common from litex.build.lattice import common
# TODO: # IO Constraints (.lpf) ----------------------------------------------------------------------------
# - check/document attr_translate.
def _format_constraint(c):
if isinstance(c, Pins):
return ("LOCATE COMP ", " SITE " + "\"" + c.identifiers[0] + "\"")
elif isinstance(c, IOStandard):
return ("IOBUF PORT ", " IO_TYPE=" + c.name)
elif isinstance(c, Misc):
return ("IOBUF PORT ", " " + c.misc)
def _format_lpf(signame, pin, others, resname):
fmt_c = [_format_constraint(c) for c in ([Pins(pin)] + others)]
lpf = []
for pre, suf in fmt_c:
lpf.append(pre + "\"" + signame + "\"" + suf + ";")
return "\n".join(lpf)
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):
lpf.append(_format_lpf(sig + "[" + str(i) + "]", p, others, resname))
else:
lpf.append(_format_lpf(sig, pins[0], others, resname))
if named_pc:
lpf.append("\n\n".join(named_pc))
tools.write_to_file(build_name + ".lpf", "\n".join(lpf))
# Yosys/Nextpnr Helpers/Templates ------------------------------------------------------------------
_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 = { nextpnr_ecp5_architectures = {
"lfe5u-25f" : "25k", "lfe5u-25f" : "25k",
@ -28,77 +86,52 @@ nextpnr_ecp5_architectures = {
"lfe5um5g-85f": "um5g-85k", "lfe5um5g-85f": "um5g-85k",
} }
def nextpnr_ecp5_package(package): def nextpnr_ecp5_package(package):
if "285" in package: if "256" in package:
return "CSFBGA285"
elif "256" in package:
return "CABGA256" return "CABGA256"
elif "285" in package:
return "CSFBGA285"
elif "381" in package: elif "381" in package:
return "CABGA381" return "CABGA381"
elif "554" in package: elif "554" in package:
return "CABGA554" return "CABGA554"
elif "756" in package: elif "756" in package:
return "CABGA756" return "CABGA756"
raise ValueError("Unknown package") raise ValueError("Unknown package {}".format(package))
# Script -------------------------------------------------------------------------------------------
def _format_constraint(c): _build_template = [
if isinstance(c, Pins): "yosys -q -l {build_name}.rpt {build_name}.ys",
return ("LOCATE COMP ", " SITE " + "\"" + c.identifiers[0] + "\"") "nextpnr-ecp5 --json {build_name}.json --lpf {build_name}.lpf --textcfg {build_name}.config \
elif isinstance(c, IOStandard): --{architecture} --package {package} --freq {freq_constraint} {timefailarg}",
return ("IOBUF PORT ", " IO_TYPE=" + c.name) "ecppack {build_name}.config --svf {build_name}.svf --bit {build_name}.bit"
elif isinstance(c, Misc): ]
return ("IOBUF PORT ", " " + c.misc)
def _build_script(source, build_template, build_name, architecture, package, freq_constraint, timingstrict):
def _format_lpf(signame, pin, others, resname):
fmt_c = [_format_constraint(c) for c in ([Pins(pin)] + others)]
r = ""
for pre, suf in fmt_c:
r += pre + "\"" + signame + "\"" + suf + ";\n"
return r
def _build_lpf(named_sc, named_pc):
r = "BLOCK RESETPATHS;\n"
r += "BLOCK ASYNCPATHS;\n"
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)
else:
r += _format_lpf(sig, pins[0], others, resname)
if named_pc:
r += "\n" + "\n\n".join(named_pc)
return r
def _build_script(source, build_template, build_name, architecture,
package, freq_constraint, timingstrict):
if sys.platform in ("win32", "cygwin"): if sys.platform in ("win32", "cygwin"):
script_ext = ".bat" 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" fail_stmt = " || exit /b"
else: else:
script_ext = ".sh" 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 = "" fail_stmt = ""
for s in build_template: for s in build_template:
s_fail = s + "{fail_stmt}\n" # Required so Windows scripts fail early. s_fail = s + "{fail_stmt}\n" # Required so Windows scripts fail early.
build_script_contents += s_fail.format(build_name=build_name, script_contents += s_fail.format(
build_name = build_name,
architecture = architecture, architecture = architecture,
package = package, package = package,
freq_constraint = freq_constraint, freq_constraint = freq_constraint,
timefailarg = "--timing-allow-fail" if not timingstrict else "", timefailarg = "--timing-allow-fail" if not timingstrict else "",
fail_stmt = fail_stmt) fail_stmt = fail_stmt)
build_script_file = "build_" + build_name + script_ext script_file = "build_" + build_name + script_ext
tools.write_to_file(build_script_file, build_script_contents, tools.write_to_file(script_file, script_contents, force_unix=False)
force_unix=False)
return build_script_file
return script_file
def _run_script(script): def _run_script(script):
if sys.platform in ("win32", "cygwin"): if sys.platform in ("win32", "cygwin"):
@ -109,17 +142,7 @@ def _run_script(script):
if subprocess.call(shell + [script]) != 0: if subprocess.call(shell + [script]) != 0:
raise OSError("Subprocess failed") raise OSError("Subprocess failed")
# LatticeTrellisToolchain --------------------------------------------------------------------------
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)
class LatticeTrellisToolchain: class LatticeTrellisToolchain:
attr_translate = { attr_translate = {
@ -138,77 +161,71 @@ class LatticeTrellisToolchain:
special_overrides = common.lattice_ecpx_trellis_special_overrides special_overrides = common.lattice_ecpx_trellis_special_overrides
def __init__(self): def __init__(self):
self.yosys_template = [ self.yosys_template = _yosys_template
"{read_files}", self.build_template = _build_template
"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.freq_constraints = dict() self.freq_constraints = dict()
def build(self, platform, fragment, build_dir="build", build_name="top", def build(self, platform, fragment,
toolchain_path=None, run=True, build_dir = "build",
nowidelut=False, timingstrict=False, build_name = "top",
toolchain_path = None,
run = True,
nowidelut = False,
timingstrict = False,
**kwargs): **kwargs):
# Get default toolchain path (if not specified)
if toolchain_path is None: if toolchain_path is None:
toolchain_path = "/usr/share/trellis/" toolchain_path = "/usr/share/trellis/"
# Create build directory
os.makedirs(build_dir, exist_ok=True) os.makedirs(build_dir, exist_ok=True)
cwd = os.getcwd() cwd = os.getcwd()
os.chdir(build_dir) os.chdir(build_dir)
# generate verilog # Finalize design
if not isinstance(fragment, _Fragment): if not isinstance(fragment, _Fragment):
fragment = fragment.get_fragment() fragment = fragment.get_fragment()
platform.finalize(fragment) platform.finalize(fragment)
top_output = platform.get_verilog(fragment, name=build_name, **kwargs) # Generate verilog
named_sc, named_pc = platform.resolve_signals(top_output.ns) 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_file = build_name + ".v"
top_output.write(top_file) v_output.write(top_file)
platform.add_source(top_file) platform.add_source(top_file)
# generate constraints # Generate design constraints file (.lpf)
tools.write_to_file(build_name + ".lpf", _build_lpf(named_sc, named_pc, build_name)
_build_lpf(named_sc, named_pc))
# generate yosys script # Generate Yosys script
yosys_script_file = build_name + ".ys" _build_yosys(self.yosys_template, platform, nowidelut, build_name)
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)
# transform platform.device to nextpnr's architecture # Translate device to Nextpnr architecture/package
(family, size, package) = platform.device.split("-") (family, size, package) = platform.device.split("-")
architecture = nextpnr_ecp5_architectures[(family + "-" + size).lower()] architecture = nextpnr_ecp5_architectures[(family + "-" + size).lower()]
package = nextpnr_ecp5_package(package) package = nextpnr_ecp5_package(package)
freq_constraint = str(max(self.freq_constraints.values(),
default=0.0))
script = _build_script(False, self.build_template, build_name, freq_constraint = str(max(self.freq_constraints.values(), default=0.0))
architecture, package, freq_constraint,
timingstrict)
# run scripts # Generate build script
script = _build_script(False, self.build_template, build_name, architecture, package,
freq_constraint, timingstrict)
# Run
if run: if run:
_run_script(script) _run_script(script)
os.chdir(cwd) os.chdir(cwd)
return top_output.ns return v_output.ns
# Until nextpnr-ecp5 can handle multiple clock domains, use the same # Until nextpnr-ecp5 can handle multiple clock domains, use the same
# approach as the icestorm and use the fastest clock for timing # approach as the icestorm and use the fastest clock for timing
# constraints. # constraints.
def add_period_constraint(self, platform, clk, period): 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): def trellis_args(parser):
parser.add_argument("--yosys-nowidelut", action="store_true", parser.add_argument("--yosys-nowidelut", action="store_true",