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):
|
||||
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)
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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,8 +13,66 @@ from litex.build.generic_platform import *
|
|||
from litex.build import tools
|
||||
from litex.build.lattice import common
|
||||
|
||||
# TODO:
|
||||
# - check/document attr_translate.
|
||||
# IO Constraints (.lpf) ----------------------------------------------------------------------------
|
||||
|
||||
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 = {
|
||||
"lfe5u-25f" : "25k",
|
||||
|
@ -28,77 +86,52 @@ nextpnr_ecp5_architectures = {
|
|||
"lfe5um5g-85f": "um5g-85k",
|
||||
}
|
||||
|
||||
|
||||
def nextpnr_ecp5_package(package):
|
||||
if "285" in package:
|
||||
return "CSFBGA285"
|
||||
elif "256" in 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")
|
||||
raise ValueError("Unknown package {}".format(package))
|
||||
|
||||
# Script -------------------------------------------------------------------------------------------
|
||||
|
||||
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)
|
||||
_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 _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):
|
||||
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,
|
||||
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,17 +142,7 @@ 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 = {
|
||||
|
@ -138,77 +161,71 @@ class LatticeTrellisToolchain:
|
|||
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,
|
||||
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))
|
||||
|
||||
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",
|
||||
|
|
Loading…
Reference in New Issue