build/lattice/icestorm: cleanup/simplify (and remove arachne-pnr support)
This commit is contained in:
parent
b1b920531a
commit
0931ccc919
|
@ -1,4 +1,5 @@
|
|||
# This file is Copyright (c) 2017-2018 William D. Jones <thor0505@comcast.net>
|
||||
# This file is Copyright (c) 2019 Florent Kermarrec <florent@enjoy-digital.fr>
|
||||
# License: BSD
|
||||
|
||||
|
||||
|
@ -12,6 +13,7 @@ from litex.build.generic_platform import *
|
|||
from litex.build import tools
|
||||
from litex.build.lattice import common
|
||||
|
||||
# IO Constraints (.pcf) ----------------------------------------------------------------------------
|
||||
|
||||
def _build_pcf(named_sc, named_pc):
|
||||
r = ""
|
||||
|
@ -25,6 +27,7 @@ def _build_pcf(named_sc, named_pc):
|
|||
r += "\n" + "\n\n".join(named_pc)
|
||||
return r
|
||||
|
||||
# Timing Constraints (in pre_pack file) ------------------------------------------------------------
|
||||
|
||||
def _build_pre_pack(vns, freq_cstrs):
|
||||
r = ""
|
||||
|
@ -32,8 +35,65 @@ def _build_pre_pack(vns, freq_cstrs):
|
|||
r += """ctx.addClock("{}", {})\n""".format(vns.get_name(sig), freq_cstrs[sig])
|
||||
return r
|
||||
|
||||
# Yosys/Nextpnr Helpers/Templates ------------------------------------------------------------------
|
||||
|
||||
def _build_script(source, build_template, build_name, **kwargs):
|
||||
yosys_template = [
|
||||
"{read_files}",
|
||||
"attrmap -tocase keep -imap keep=\"true\" keep=1 -imap keep=\"false\" keep=0 -remove keep=0",
|
||||
"synth_ice40 {synth_opts} -top {build_name} -json {build_name}.json",
|
||||
]
|
||||
|
||||
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, build_name, synth_opts):
|
||||
ys = []
|
||||
for l in template:
|
||||
ys.append(l.format(
|
||||
build_name = build_name,
|
||||
read_files = _yosys_import_sources(platform),
|
||||
synth_opts = synth_opts
|
||||
))
|
||||
tools.write_to_file(build_name + ".ys", "\n".join(ys))
|
||||
|
||||
def parse_device(device):
|
||||
packages = {
|
||||
"lp384": ["qn32", "cm36", "cm49"],
|
||||
"lp1k": ["swg16tr", "cm36", "cm49", "cm81", "cb81", "qn84", "cm121", "cb121"],
|
||||
"hx1k": ["vq100", "cb132", "tq144"],
|
||||
"lp8k": ["cm81", "cm81:4k", "cm121", "cm121:4k", "cm225", "cm225:4k"],
|
||||
"hx8k": ["bg121", "bg121:4k", "cb132", "cb132:4k", "cm121",
|
||||
"cm121:4k", "cm225", "cm225:4k", "cm81", "cm81:4k",
|
||||
"ct256", "tq144:4k"],
|
||||
"up3k": ["sg48", "uwg30"],
|
||||
"up5k": ["sg48", "uwg30"],
|
||||
}
|
||||
|
||||
(family, serie, package) = device.split("-")
|
||||
if family not in ["ice40"]:
|
||||
raise ValueError("Unknown device family {}".format(family))
|
||||
if serie not in ["lp384", "lp1k", "hx1k", "lp8k", "hx8k", "up5k"]:
|
||||
raise ValueError("Invalid device serie {}".format(serie))
|
||||
if package not in packages[serie]:
|
||||
raise ValueError("Invalid device package {}".format(package))
|
||||
return (family, serie, package)
|
||||
|
||||
# Script -------------------------------------------------------------------------------------------
|
||||
|
||||
build_template = [
|
||||
"yosys -q -l {build_name}.rpt {build_name}.ys",
|
||||
"nextpnr-ice40 {pnr_pkg_opts} --pcf {build_name}.pcf --json {build_name}.json --asc {build_name}.txt --pre-pack {build_name}_pre_pack.py",
|
||||
"icepack {build_name}.txt {build_name}.bin"
|
||||
]
|
||||
|
||||
def _build_script(build_template, build_name, **kwargs):
|
||||
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"
|
||||
|
@ -45,16 +105,12 @@ def _build_script(source, build_template, build_name, **kwargs):
|
|||
|
||||
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,
|
||||
fail_stmt=fail_stmt,
|
||||
**kwargs)
|
||||
build_script_contents += s_fail.format(build_name=build_name, fail_stmt=fail_stmt, **kwargs)
|
||||
|
||||
build_script_file = "build_" + build_name + script_ext
|
||||
tools.write_to_file(build_script_file, build_script_contents,
|
||||
force_unix=False)
|
||||
tools.write_to_file(build_script_file, build_script_contents,force_unix=False)
|
||||
return build_script_file
|
||||
|
||||
|
||||
def _run_script(script):
|
||||
if sys.platform in ("win32", "cygwin"):
|
||||
shell = ["cmd", "/c"]
|
||||
|
@ -64,123 +120,71 @@ def _run_script(script):
|
|||
if subprocess.call(shell + [script]) != 0:
|
||||
raise OSError("Subprocess failed")
|
||||
|
||||
# LatticeIceStormToolchain -------------------------------------------------------------------------
|
||||
|
||||
class LatticeIceStormToolchain:
|
||||
attr_translate = {
|
||||
# FIXME: document
|
||||
"keep": ("keep", "true"),
|
||||
"no_retiming": None, # yosys can do retiming via the (non-default)
|
||||
# "-retime" option to "synth_ice40", but
|
||||
# yosys does not check for an equivalent
|
||||
# constraint to prevent retiming on signals.
|
||||
"async_reg": None, # yosys has no equivalent, and arachne-pnr
|
||||
# wouldn't take advantage of it anyway.
|
||||
|
||||
# While custom attributes are supported in yosys, neither
|
||||
# arachne-pnr nor icetime currently can take advantage of them
|
||||
# to add fine-grained timing constraints.
|
||||
"mr_ff": None, # user-defined attribute
|
||||
"mr_false_path": None, # user-defined attribute
|
||||
"ars_ff1": None, # user-defined attribute
|
||||
"ars_ff2": None, # user-defined attribute
|
||||
"ars_false_path": None, # user-defined attribute
|
||||
|
||||
# ice40 does not have a shift register primitive.
|
||||
"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_ice40_special_overrides
|
||||
|
||||
def __init__(self):
|
||||
# Variables within replacement fields should be backend-aware and
|
||||
# update their syntax accordingly. Currently, only {pnr_pkg_opts}
|
||||
# needs this functionality.
|
||||
self.yosys_template = yosys_template
|
||||
self.build_template = build_template
|
||||
self.clocks = dict()
|
||||
|
||||
self.yosys_template = [
|
||||
"{read_files}",
|
||||
"attrmap -tocase keep -imap keep=\"true\" keep=1 -imap keep=\"false\" keep=0 -remove keep=0",
|
||||
"synth_ice40 -top {build_name} -blif {build_name}.blif",
|
||||
]
|
||||
def build(self, platform, fragment,
|
||||
build_dir = "build",
|
||||
build_name = "top",
|
||||
toolchain_path = None,
|
||||
synth_opts = "",
|
||||
run = True,
|
||||
**kwargs):
|
||||
|
||||
self.build_template = [
|
||||
"yosys -q -l {build_name}.rpt {build_name}.ys",
|
||||
"arachne-pnr -q -l {pnr_pkg_opts} -p {build_name}.pcf {build_name}.blif -o {build_name}.txt",
|
||||
"icetime {icetime_pkg_opts} -c {freq_constraint} -t -p {build_name}.pcf -r {build_name}.tim {build_name}.txt",
|
||||
"icepack {build_name}.txt {build_name}.bin"
|
||||
]
|
||||
|
||||
self.nextpnr_yosys_template = [
|
||||
"{read_files}",
|
||||
"attrmap -tocase keep -imap keep=\"true\" keep=1 -imap keep=\"false\" keep=0 -remove keep=0",
|
||||
"synth_ice40 {synth_opts} -top {build_name} -json {build_name}.json",
|
||||
]
|
||||
|
||||
self.nextpnr_build_template = [
|
||||
"yosys -q -l {build_name}.rpt {build_name}.ys",
|
||||
"nextpnr-ice40 {pnr_pkg_opts} --pcf {build_name}.pcf --json {build_name}.json --asc {build_name}.txt --pre-pack {build_name}_pre_pack.py",
|
||||
"icepack {build_name}.txt {build_name}.bin"
|
||||
]
|
||||
|
||||
self.freq_constraints = dict()
|
||||
|
||||
# platform.device should be of the form "ice40-{lp384, hx1k, etc}-{tq144, etc}"
|
||||
def build(self, platform, fragment, build_dir="build", build_name="top",
|
||||
toolchain_path=None, use_nextpnr=True, synth_opts="", run=True, **kwargs):
|
||||
# Create build directory
|
||||
os.makedirs(build_dir, exist_ok=True)
|
||||
cwd = os.getcwd()
|
||||
os.chdir(build_dir)
|
||||
|
||||
# Finalize design
|
||||
if not isinstance(fragment, _Fragment):
|
||||
fragment = fragment.get_fragment()
|
||||
platform.finalize(fragment)
|
||||
|
||||
# Generate verilog
|
||||
v_output = platform.get_verilog(fragment, name=build_name, **kwargs)
|
||||
named_sc, named_pc = platform.resolve_signals(v_output.ns)
|
||||
v_file = build_name + ".v"
|
||||
v_output.write(v_file)
|
||||
platform.add_source(v_file)
|
||||
|
||||
if use_nextpnr:
|
||||
chosen_yosys_template = self.nextpnr_yosys_template
|
||||
else:
|
||||
chosen_yosys_template = self.yosys_template
|
||||
ys_contents = "\n".join(_.format(build_name=build_name,
|
||||
read_files=self.gen_read_files(platform),
|
||||
synth_opts=synth_opts)
|
||||
for _ in chosen_yosys_template)
|
||||
# Generate design io constraints file (.pcf)
|
||||
tools.write_to_file(build_name + ".pcf",_build_pcf(named_sc, named_pc))
|
||||
|
||||
ys_name = build_name + ".ys"
|
||||
tools.write_to_file(ys_name, ys_contents)
|
||||
# Generate design timing constraints file (in pre_pack file)
|
||||
tools.write_to_file(build_name + "_pre_pack.py", _build_pre_pack(v_output.ns, self.clocks))
|
||||
|
||||
tools.write_to_file(build_name + ".pcf",
|
||||
_build_pcf(named_sc, named_pc))
|
||||
(family, series_size, package) = self.parse_device_string(platform.device)
|
||||
if use_nextpnr:
|
||||
pnr_pkg_opts = "--" + series_size + " --package " + package
|
||||
else:
|
||||
pnr_pkg_opts = "-d " + self.get_size_string(series_size) + \
|
||||
" -P " + package
|
||||
icetime_pkg_opts = "-P " + package + " -d " + series_size
|
||||
# Generate Yosys script
|
||||
_build_yosys(self.yosys_template, platform, build_name, synth_opts=synth_opts)
|
||||
|
||||
if use_nextpnr:
|
||||
tools.write_to_file(build_name + "_pre_pack.py",
|
||||
_build_pre_pack(v_output.ns, self.freq_constraints))
|
||||
# icetime can only handle a single global constraint, so we test against the fastest
|
||||
# clock; though imprecise, if the global design satisfies the fastest clock, we can
|
||||
# be sure all other constraints are satisfied.
|
||||
freq_constraint = str(max(self.freq_constraints.values(),
|
||||
default=0.0))
|
||||
# Translate device to Nextpnr architecture/package
|
||||
(family, serie, package) = parse_device(platform.device)
|
||||
pnr_pkg_opts = "--" + serie + " --package " + package
|
||||
|
||||
if use_nextpnr:
|
||||
chosen_build_template = self.nextpnr_build_template
|
||||
else:
|
||||
chosen_build_template = self.build_template
|
||||
script = _build_script(source=False,
|
||||
build_template=chosen_build_template,
|
||||
build_name=build_name,
|
||||
pnr_pkg_opts=pnr_pkg_opts,
|
||||
icetime_pkg_opts=icetime_pkg_opts,
|
||||
freq_constraint=freq_constraint)
|
||||
# Generate build script
|
||||
script = _build_script(self.build_template, build_name, pnr_pkg_opts=pnr_pkg_opts)
|
||||
|
||||
# Run
|
||||
if run:
|
||||
_run_script(script)
|
||||
|
||||
|
@ -188,52 +192,8 @@ class LatticeIceStormToolchain:
|
|||
|
||||
return v_output.ns
|
||||
|
||||
def parse_device_string(self, device_str):
|
||||
# Arachne only understands packages based on the device size, but
|
||||
# LP for a given size supports packages that HX for the same size
|
||||
# doesn't and vice versa; we need to know the device series due to
|
||||
# icetime.
|
||||
valid_packages = {
|
||||
"lp384": ["qn32", "cm36", "cm49"],
|
||||
"lp1k": ["swg16tr", "cm36", "cm49", "cm81", "cb81", "qn84",
|
||||
"cm121", "cb121"],
|
||||
"hx1k": ["vq100", "cb132", "tq144"],
|
||||
"lp8k": ["cm81", "cm81:4k", "cm121", "cm121:4k", "cm225",
|
||||
"cm225:4k"],
|
||||
"hx8k": ["bg121", "bg121:4k", "cb132", "cb132:4k", "cm121",
|
||||
"cm121:4k", "cm225", "cm225:4k", "cm81", "cm81:4k",
|
||||
"ct256", "tq144:4k"],
|
||||
"up3k": ["sg48", "uwg30"],
|
||||
"up5k": ["sg48", "uwg30"],
|
||||
}
|
||||
|
||||
(family, series_size, package) = device_str.split("-")
|
||||
if family not in ["ice40"]:
|
||||
raise ValueError("Unknown device family")
|
||||
if series_size not in ["lp384", "lp1k", "hx1k", "lp8k", "hx8k", "up5k"]:
|
||||
raise ValueError("Invalid device series/size")
|
||||
if package not in valid_packages[series_size]:
|
||||
raise ValueError("Invalid device package")
|
||||
return (family, series_size, package)
|
||||
|
||||
def get_size_string(self, series_size_str):
|
||||
return series_size_str[2:]
|
||||
|
||||
def gen_read_files(self, platform):
|
||||
incflags = ""
|
||||
read_files = list()
|
||||
for path in platform.verilog_include_paths:
|
||||
incflags += " -I" + path
|
||||
for filename, language, library in platform.sources:
|
||||
read_files.append("read_{}{} {}".format(language,
|
||||
incflags,
|
||||
filename))
|
||||
return "\n".join(read_files)
|
||||
|
||||
def add_period_constraint(self, platform, clk, period):
|
||||
clk.attr.add("keep")
|
||||
new_freq = 1000.0/period
|
||||
if clk not in self.freq_constraints.keys():
|
||||
self.freq_constraints[clk] = new_freq
|
||||
else:
|
||||
raise ConstraintError("Period constraint already added to signal.")
|
||||
if clk in self.clocks:
|
||||
raise ValueError("A period constraint already exists")
|
||||
self.clocks[clk] = 1e3/period
|
||||
|
|
|
@ -186,7 +186,7 @@ class LatticeTrellisToolchain:
|
|||
fragment = fragment.get_fragment()
|
||||
platform.finalize(fragment)
|
||||
|
||||
# Generate verilog
|
||||
# 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"
|
||||
|
|
Loading…
Reference in New Issue