Merge pull request #709 from daveshah1/oxide-build
Add Yosys/nextpnr-nexus/oxide flow for CrossLink-NX
This commit is contained in:
commit
896d1ba988
|
@ -0,0 +1,218 @@
|
||||||
|
#
|
||||||
|
# This file is part of LiteX.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2018-2019 Florent Kermarrec <florent@enjoy-digital.fr>
|
||||||
|
# Copyright (c) 2019-2020 David Shah <dave@ds0.me>
|
||||||
|
# Copyright (c) 2018 William D. Jones <thor0505@comcast.net>
|
||||||
|
# SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from shutil import which
|
||||||
|
|
||||||
|
from migen.fhdl.structure import _Fragment
|
||||||
|
|
||||||
|
from litex.build.generic_platform import *
|
||||||
|
from litex.build import tools
|
||||||
|
from litex.build.lattice import common
|
||||||
|
from litex.build.lattice.radiant import _format_constraint, _format_ldc, _build_pdc
|
||||||
|
|
||||||
|
import math
|
||||||
|
|
||||||
|
# Yosys/Nextpnr Helpers/Templates ------------------------------------------------------------------
|
||||||
|
|
||||||
|
_yosys_template = [
|
||||||
|
"verilog_defaults -push",
|
||||||
|
"verilog_defaults -add -defer",
|
||||||
|
"{read_files}",
|
||||||
|
"verilog_defaults -pop",
|
||||||
|
"attrmap -tocase keep -imap keep=\"true\" keep=1 -imap keep=\"false\" keep=0 -remove keep=0",
|
||||||
|
"synth_nexus -flatten {nwl} {abc} -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, abc9, build_name):
|
||||||
|
ys = []
|
||||||
|
for l in template:
|
||||||
|
ys.append(l.format(
|
||||||
|
build_name = build_name,
|
||||||
|
nwl = "-nowidelut" if nowidelut else "",
|
||||||
|
abc = "-abc9" if abc9 else "",
|
||||||
|
read_files = _yosys_import_sources(platform)
|
||||||
|
))
|
||||||
|
tools.write_to_file(build_name + ".ys", "\n".join(ys))
|
||||||
|
|
||||||
|
# Script -------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
_build_template = [
|
||||||
|
"yosys -l {build_name}.rpt {build_name}.ys",
|
||||||
|
"nextpnr-nexus --json {build_name}.json --pdc {build_name}.pdc --fasm {build_name}.fasm \
|
||||||
|
--device {device} {timefailarg} {ignoreloops} --seed {seed}",
|
||||||
|
"prjoxide pack {build_name}.fasm {build_name}.bit"
|
||||||
|
]
|
||||||
|
|
||||||
|
def _build_script(source, build_template, build_name, device, timingstrict, ignoreloops, seed):
|
||||||
|
if sys.platform in ("win32", "cygwin"):
|
||||||
|
script_ext = ".bat"
|
||||||
|
script_contents = "@echo off\nrem Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n\n"
|
||||||
|
fail_stmt = " || exit /b"
|
||||||
|
else:
|
||||||
|
script_ext = ".sh"
|
||||||
|
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.
|
||||||
|
script_contents += s_fail.format(
|
||||||
|
build_name = build_name,
|
||||||
|
device = device,
|
||||||
|
timefailarg = "--timing-allow-fail" if not timingstrict else "",
|
||||||
|
ignoreloops = "--ignore-loops" if ignoreloops else "",
|
||||||
|
fail_stmt = fail_stmt,
|
||||||
|
seed = seed,
|
||||||
|
)
|
||||||
|
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"):
|
||||||
|
shell = ["cmd", "/c"]
|
||||||
|
else:
|
||||||
|
shell = ["bash"]
|
||||||
|
|
||||||
|
if which("yosys") is None or which("nextpnr-nexus") is None:
|
||||||
|
msg = "Unable to find Yosys/Nextpnr toolchain, please:\n"
|
||||||
|
msg += "- Add Yosys/Nextpnr toolchain to your $PATH."
|
||||||
|
raise OSError(msg)
|
||||||
|
|
||||||
|
if subprocess.call(shell + [script]) != 0:
|
||||||
|
raise OSError("Error occured during Yosys/Nextpnr's script execution.")
|
||||||
|
|
||||||
|
# LatticeOxideToolchain --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class LatticeOxideToolchain:
|
||||||
|
attr_translate = {
|
||||||
|
# FIXME: document
|
||||||
|
"keep": ("keep", "true"),
|
||||||
|
"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_NX_special_overrides
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.yosys_template = _yosys_template
|
||||||
|
self.build_template = _build_template
|
||||||
|
self.clocks = {}
|
||||||
|
self.false_paths = set() # FIXME: use it
|
||||||
|
|
||||||
|
def build(self, platform, fragment,
|
||||||
|
build_dir = "build",
|
||||||
|
build_name = "top",
|
||||||
|
run = True,
|
||||||
|
nowidelut = False,
|
||||||
|
abc9 = False,
|
||||||
|
timingstrict = False,
|
||||||
|
ignoreloops = False,
|
||||||
|
seed = 1,
|
||||||
|
es_device = False,
|
||||||
|
**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)
|
||||||
|
top_file = build_name + ".v"
|
||||||
|
v_output.write(top_file)
|
||||||
|
platform.add_source(top_file)
|
||||||
|
|
||||||
|
# Generate design constraints file (.pdc)
|
||||||
|
_build_pdc(named_sc, named_pc, self.clocks, v_output.ns, build_name)
|
||||||
|
|
||||||
|
# Generate Yosys script
|
||||||
|
_build_yosys(self.yosys_template, platform, nowidelut, abc9, build_name)
|
||||||
|
|
||||||
|
# N.B. Radiant does not allow a choice between ES1/production, this is determined
|
||||||
|
# solely by the installed Radiant version. nextpnr/oxide supports both, so we
|
||||||
|
# must choose what we are dealing with
|
||||||
|
device = platform.device
|
||||||
|
if es_device:
|
||||||
|
device += "ES"
|
||||||
|
|
||||||
|
# Generate build script
|
||||||
|
script = _build_script(False, self.build_template, build_name, device,
|
||||||
|
timingstrict, ignoreloops, seed)
|
||||||
|
# Run
|
||||||
|
if run:
|
||||||
|
_run_script(script)
|
||||||
|
|
||||||
|
os.chdir(cwd)
|
||||||
|
|
||||||
|
return v_output.ns
|
||||||
|
|
||||||
|
# N.B. these are currently ignored, but will be supported very soon
|
||||||
|
def add_period_constraint(self, platform, clk, period):
|
||||||
|
clk.attr.add("keep")
|
||||||
|
period = math.floor(period*1e3)/1e3 # round to lowest picosecond
|
||||||
|
if clk in self.clocks:
|
||||||
|
if period != self.clocks[clk]:
|
||||||
|
raise ValueError("Clock already constrained to {:.2f}ns, new constraint to {:.2f}ns"
|
||||||
|
.format(self.clocks[clk], period))
|
||||||
|
self.clocks[clk] = period
|
||||||
|
|
||||||
|
def add_false_path_constraint(self, platform, from_, to):
|
||||||
|
from_.attr.add("keep")
|
||||||
|
to.attr.add("keep")
|
||||||
|
if (to, from_) not in self.false_paths:
|
||||||
|
self.false_paths.add((from_, to))
|
||||||
|
|
||||||
|
def oxide_args(parser):
|
||||||
|
parser.add_argument("--yosys-nowidelut", action="store_true",
|
||||||
|
help="pass '-nowidelut' to yosys synth_nexus")
|
||||||
|
parser.add_argument("--yosys-abc9", action="store_true",
|
||||||
|
help="pass '-abc9' to yosys synth_nexus")
|
||||||
|
parser.add_argument("--nextpnr-timingstrict", action="store_true",
|
||||||
|
help="fail if timing not met, i.e., do NOT pass '--timing-allow-fail' to nextpnr")
|
||||||
|
parser.add_argument("--nextpnr-ignoreloops", action="store_true",
|
||||||
|
help="ignore combinational loops in timing analysis, i.e. pass '--ignore-loops' to nextpnr")
|
||||||
|
parser.add_argument("--nextpnr-seed", default=1, type=int,
|
||||||
|
help="seed to pass to nextpnr")
|
||||||
|
parser.add_argument("--nexus-es-device", action="store_true",
|
||||||
|
help="device is a ES1 Nexus part")
|
||||||
|
|
||||||
|
def oxide_argdict(args):
|
||||||
|
return {
|
||||||
|
"nowidelut": args.yosys_nowidelut,
|
||||||
|
"abc9": args.yosys_abc9,
|
||||||
|
"timingstrict": args.nextpnr_timingstrict,
|
||||||
|
"ignoreloops": args.nextpnr_ignoreloops,
|
||||||
|
"seed": args.nextpnr_seed,
|
||||||
|
"es_device": args.nexus_es_device,
|
||||||
|
}
|
|
@ -6,7 +6,7 @@
|
||||||
# SPDX-License-Identifier: BSD-2-Clause
|
# SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
|
||||||
from litex.build.generic_platform import GenericPlatform
|
from litex.build.generic_platform import GenericPlatform
|
||||||
from litex.build.lattice import common, diamond, icestorm, trellis, radiant
|
from litex.build.lattice import common, diamond, icestorm, trellis, radiant, oxide
|
||||||
|
|
||||||
# LatticePlatform ----------------------------------------------------------------------------------
|
# LatticePlatform ----------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -24,6 +24,8 @@ class LatticePlatform(GenericPlatform):
|
||||||
self.toolchain = icestorm.LatticeIceStormToolchain()
|
self.toolchain = icestorm.LatticeIceStormToolchain()
|
||||||
elif toolchain == "radiant":
|
elif toolchain == "radiant":
|
||||||
self.toolchain = radiant.LatticeRadiantToolchain()
|
self.toolchain = radiant.LatticeRadiantToolchain()
|
||||||
|
elif toolchain == "oxide":
|
||||||
|
self.toolchain = oxide.LatticeOxideToolchain()
|
||||||
else:
|
else:
|
||||||
raise ValueError("Unknown toolchain")
|
raise ValueError("Unknown toolchain")
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue