build/lattice/icestorm: cleanup/simplify (and remove arachne-pnr support)

This commit is contained in:
Florent Kermarrec 2019-12-07 21:43:15 +01:00
parent b1b920531a
commit 0931ccc919
2 changed files with 101 additions and 141 deletions

View File

@ -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

View File

@ -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"