build/lattice: cleanup/simplify (no functional changes)
icestorm still need to be cleaned up
This commit is contained in:
parent
946478a71e
commit
8fb3f9a90d
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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,35 +13,7 @@ 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.
|
|
||||||
|
|
||||||
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")
|
|
||||||
|
|
||||||
|
|
||||||
def _format_constraint(c):
|
def _format_constraint(c):
|
||||||
if isinstance(c, Pins):
|
if isinstance(c, Pins):
|
||||||
|
@ -54,51 +26,112 @@ def _format_constraint(c):
|
||||||
|
|
||||||
def _format_lpf(signame, pin, others, resname):
|
def _format_lpf(signame, pin, others, resname):
|
||||||
fmt_c = [_format_constraint(c) for c in ([Pins(pin)] + others)]
|
fmt_c = [_format_constraint(c) for c in ([Pins(pin)] + others)]
|
||||||
r = ""
|
lpf = []
|
||||||
for pre, suf in fmt_c:
|
for pre, suf in fmt_c:
|
||||||
r += pre + "\"" + signame + "\"" + suf + ";\n"
|
lpf.append(pre + "\"" + signame + "\"" + suf + ";")
|
||||||
return r
|
return "\n".join(lpf)
|
||||||
|
|
||||||
|
|
||||||
def _build_lpf(named_sc, named_pc):
|
def _build_lpf(named_sc, named_pc, build_name):
|
||||||
r = "BLOCK RESETPATHS;\n"
|
lpf = []
|
||||||
r += "BLOCK ASYNCPATHS;\n"
|
lpf.append("BLOCK RESETPATHS;")
|
||||||
|
lpf.append("BLOCK ASYNCPATHS;")
|
||||||
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):
|
||||||
r += _format_lpf(sig + "[" + str(i) + "]", p, others, resname)
|
lpf.append(_format_lpf(sig + "[" + str(i) + "]", p, others, resname))
|
||||||
else:
|
else:
|
||||||
r += _format_lpf(sig, pins[0], others, resname)
|
lpf.append(_format_lpf(sig, pins[0], others, resname))
|
||||||
if named_pc:
|
if named_pc:
|
||||||
r += "\n" + "\n\n".join(named_pc)
|
lpf.append("\n\n".join(named_pc))
|
||||||
return r
|
tools.write_to_file(build_name + ".lpf", "\n".join(lpf))
|
||||||
|
|
||||||
|
# Yosys/Nextpnr Helpers/Templates ------------------------------------------------------------------
|
||||||
|
|
||||||
def _build_script(source, build_template, build_name, architecture,
|
_yosys_template = [
|
||||||
package, freq_constraint, timingstrict):
|
"{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"):
|
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(
|
||||||
architecture=architecture,
|
build_name = build_name,
|
||||||
package=package,
|
architecture = architecture,
|
||||||
freq_constraint=freq_constraint,
|
package = package,
|
||||||
timefailarg="--timing-allow-fail" if not timingstrict else "",
|
freq_constraint = freq_constraint,
|
||||||
fail_stmt=fail_stmt)
|
timefailarg = "--timing-allow-fail" if not timingstrict else "",
|
||||||
|
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",
|
||||||
|
|
Loading…
Reference in New Issue