toolchain: implement generic toolchain and devices toolchain refactoring

This commit is contained in:
Gwenhael Goavec-Merou 2022-06-23 08:13:07 +02:00 committed by Florent Kermarrec
parent ee1af96ab7
commit 9db1d9e49f
4 changed files with 565 additions and 579 deletions

View File

@ -15,253 +15,206 @@ from shutil import which
from migen.fhdl.structure import _Fragment
from litex.build.generic_platform import Pins, IOStandard, Misc
from litex.build.generic_toolchain import GenericToolchain
from litex.build import tools
# IO/Placement Constraints (.qsf) ------------------------------------------------------------------
# AlteraQuartusToolchain ---------------------------------------------------------------------------
def _format_constraint(c, signame, fmt_r):
# IO location constraints
if isinstance(c, Pins):
tpl = "set_location_assignment -comment \"{name}\" -to {signame} Pin_{pin}"
return tpl.format(signame=signame, name=fmt_r, pin=c.identifiers[0])
class AlteraQuartusToolchain(GenericToolchain):
attr_translate = {}
# IO standard constraints
elif isinstance(c, IOStandard):
tpl = "set_instance_assignment -name io_standard -comment \"{name}\" \"{std}\" -to {signame}"
return tpl.format(signame=signame, name=fmt_r, std=c.name)
def __init__(self):
super().__init__()
self.additional_sdc_commands = []
self.additional_qsf_commands = []
self.cst = []
# Others constraints
elif isinstance(c, Misc):
if not isinstance(c.misc, str) and len(c.misc) == 2:
tpl = "set_instance_assignment -comment \"{name}\" -name {misc[0]} \"{misc[1]}\" -to {signame}"
return tpl.format(signame=signame, name=fmt_r, misc=c.misc)
else:
tpl = "set_instance_assignment -comment \"{name}\" -name {misc} -to {signame}"
return tpl.format(signame=signame, name=fmt_r, misc=c.misc)
def build(self, platform, fragment, **kwargs):
def _format_qsf_constraint(signame, pin, others, resname):
fmt_r = "{}:{}".format(*resname[:2])
if resname[2] is not None:
fmt_r += "." + resname[2]
fmt_c = [_format_constraint(c, signame, fmt_r) for c in ([Pins(pin)] + others)]
return '\n'.join(fmt_c)
return self._build(platform, fragment, **kwargs)
def _is_virtual_pin(pin_name):
return pin_name in (
"altera_reserved_tms",
"altera_reserved_tck",
"altera_reserved_tdi",
"altera_reserved_tdo",
)
# IO/Placement Constraints (.qsf) ------------------------------------------------------------------
def _build_qsf_constraints(named_sc, named_pc):
qsf = []
for sig, pins, others, resname in named_sc:
if len(pins) > 1:
for i, p in enumerate(pins):
if _is_virtual_pin(p):
continue
qsf.append(_format_qsf_constraint("{}[{}]".format(sig, i), p, others, resname))
else:
if _is_virtual_pin(pins[0]):
continue
qsf.append(_format_qsf_constraint(sig, pins[0], others, resname))
if named_pc:
qsf.append("\n\n".join(named_pc))
return "\n".join(qsf)
def _format_constraint(self, c, signame, fmt_r):
# IO location constraints
if isinstance(c, Pins):
tpl = "set_location_assignment -comment \"{name}\" -to {signame} Pin_{pin}"
return tpl.format(signame=signame, name=fmt_r, pin=c.identifiers[0])
# Timing Constraints (.sdc) ------------------------------------------------------------------------
# IO standard constraints
elif isinstance(c, IOStandard):
tpl = "set_instance_assignment -name io_standard -comment \"{name}\" \"{std}\" -to {signame}"
return tpl.format(signame=signame, name=fmt_r, std=c.name)
def _build_sdc(clocks, false_paths, vns, named_sc, build_name, additional_sdc_commands):
sdc = []
# Others constraints
elif isinstance(c, Misc):
if not isinstance(c.misc, str) and len(c.misc) == 2:
tpl = "set_instance_assignment -comment \"{name}\" -name {misc[0]} \"{misc[1]}\" -to {signame}"
return tpl.format(signame=signame, name=fmt_r, misc=c.misc)
else:
tpl = "set_instance_assignment -comment \"{name}\" -name {misc} -to {signame}"
return tpl.format(signame=signame, name=fmt_r, misc=c.misc)
# Clock constraints
for clk, period in sorted(clocks.items(), key=lambda x: x[0].duid):
is_port = False
def _format_qsf_constraint(self, signame, pin, others, resname):
fmt_r = "{}:{}".format(*resname[:2])
if resname[2] is not None:
fmt_r += "." + resname[2]
fmt_c = [self._format_constraint(c, signame, fmt_r) for c in ([Pins(pin)] + others)]
return '\n'.join(fmt_c)
def _is_virtual_pin(self, pin_name):
return pin_name in (
"altera_reserved_tms",
"altera_reserved_tck",
"altera_reserved_tdi",
"altera_reserved_tdo",
)
def build_constr_file(self, named_sc, named_pc):
for sig, pins, others, resname in named_sc:
if sig == vns.get_name(clk):
is_port = True
if is_port:
tpl = "create_clock -name {clk} -period {period} [get_ports {{{clk}}}]"
sdc.append(tpl.format(clk=vns.get_name(clk), period=str(period)))
if len(pins) > 1:
for i, p in enumerate(pins):
if self._is_virtual_pin(p):
continue
self.cst.append(self._format_qsf_constraint("{}[{}]".format(sig, i), p, others, resname))
else:
if self._is_virtual_pin(pins[0]):
continue
self.cst.append(self._format_qsf_constraint(sig, pins[0], others, resname))
if named_pc:
self.cst.append("\n\n".join(named_pc))
# Timing Constraints (.sdc) ------------------------------------------------------------------------
def build_timing_constr(self, vns, clocks):
sdc = []
# Clock constraints
for clk, period in sorted(clocks.items(), key=lambda x: x[0].duid):
is_port = False
for sig, pins, others, resname in self.named_sc:
if sig == vns.get_name(clk):
is_port = True
if is_port:
tpl = "create_clock -name {clk} -period {period} [get_ports {{{clk}}}]"
sdc.append(tpl.format(clk=vns.get_name(clk), period=str(period)))
else:
tpl = "create_clock -name {clk} -period {period} [get_nets {{{clk}}}]"
sdc.append(tpl.format(clk=vns.get_name(clk), period=str(period)))
# Enable automatical constraint generation for PLLs
sdc.append("derive_pll_clocks")
# False path constraints
for from_, to in sorted(self.false_paths, key=lambda x: (x[0].duid, x[1].duid)):
tpl = "set_false_path -from [get_clocks {{{from_}}}] -to [get_clocks {{{to}}}]"
sdc.append(tpl.format(from_=vns.get_name(from_), to=vns.get_name(to)))
# Add additional commands
sdc += self.additional_sdc_commands
# Generate .sdc
tools.write_to_file("{}.sdc".format(self._build_name), "\n".join(sdc))
# Project (.qsf) -----------------------------------------------------------------------------------
def build_project(self):
qsf = []
# Set device
qsf.append("set_global_assignment -name DEVICE {}".format(self.platform.device))
# Add sources
for filename, language, library, *copy in self.platform.sources:
if language == "verilog": language = "systemverilog" # Enforce use of SystemVerilog
tpl = "set_global_assignment -name {lang}_FILE {path} -library {lib}"
# Do not add None type files
if language is not None:
qsf.append(tpl.format(lang=language.upper(), path=filename.replace("\\", "/"), lib=library))
# Check if the file is a header. Those should not be explicitly added to qsf,
# but rather included in include search_path
else:
if filename.endswith(".svh") or filename.endswith(".vh"):
fpath = os.path.dirname(filename)
if fpath not in platform.verilog_include_paths:
platform.verilog_include_paths.append(fpath)
# Add ips
for filename in self.platform.ips:
tpl = "set_global_assignment -name QSYS_FILE {filename}"
qsf.append(tpl.replace(filename=filename.replace("\\", "/")))
# Add include paths
for path in self.platform.verilog_include_paths:
qsf.append("set_global_assignment -name SEARCH_PATH {}".format(path.replace("\\", "/")))
# Set top level
qsf.append("set_global_assignment -name top_level_entity " + self._build_name)
# Add io, placement constraints
qsf.append("\n".join(self.cst))
# Set timing constraints
qsf.append("set_global_assignment -name SDC_FILE {}.sdc".format(self._build_name))
# Add additional commands
qsf += self.additional_qsf_commands
# Generate .qsf
tools.write_to_file("{}.qsf".format(self._build_name), "\n".join(qsf))
# Script -------------------------------------------------------------------------------------------
def build_script(self):
build_name = self._build_name
self._build_qsf()
if sys.platform in ["win32", "cygwin"]:
script_file = "build_" + build_name + ".bat"
script_contents = "REM Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n"
else:
tpl = "create_clock -name {clk} -period {period} [get_nets {{{clk}}}]"
sdc.append(tpl.format(clk=vns.get_name(clk), period=str(period)))
# Enable automatical constraint generation for PLLs
sdc.append("derive_pll_clocks")
# False path constraints
for from_, to in sorted(false_paths, key=lambda x: (x[0].duid, x[1].duid)):
tpl = "set_false_path -from [get_clocks {{{from_}}}] -to [get_clocks {{{to}}}]"
sdc.append(tpl.format(from_=vns.get_name(from_), to=vns.get_name(to)))
# Add additional commands
sdc += additional_sdc_commands
# Generate .sdc
tools.write_to_file("{}.sdc".format(build_name), "\n".join(sdc))
# Project (.qsf) -----------------------------------------------------------------------------------
def _build_qsf(device, ips, sources, vincpaths, named_sc, named_pc, build_name, additional_qsf_commands):
qsf = []
# Set device
qsf.append("set_global_assignment -name DEVICE {}".format(device))
# Add sources
for filename, language, library, *copy in sources:
if language == "verilog": language = "systemverilog" # Enforce use of SystemVerilog
tpl = "set_global_assignment -name {lang}_FILE {path} -library {lib}"
# Do not add None type files
if language is not None:
qsf.append(tpl.format(lang=language.upper(), path=filename.replace("\\", "/"), lib=library))
# Check if the file is a header. Those should not be explicitly added to qsf,
# but rather included in include search_path
else:
if filename.endswith(".svh") or filename.endswith(".vh"):
fpath = os.path.dirname(filename)
if fpath not in vincpaths:
vincpaths.append(fpath)
# Add ips
for filename in ips:
tpl = "set_global_assignment -name QSYS_FILE {filename}"
qsf.append(tpl.replace(filename=filename.replace("\\", "/")))
# Add include paths
for path in vincpaths:
qsf.append("set_global_assignment -name SEARCH_PATH {}".format(path.replace("\\", "/")))
# Set top level
qsf.append("set_global_assignment -name top_level_entity " + build_name)
# Add io, placement constraints
qsf.append(_build_qsf_constraints(named_sc, named_pc))
# Set timing constraints
qsf.append("set_global_assignment -name SDC_FILE {}.sdc".format(build_name))
# Add additional commands
qsf += additional_qsf_commands
# Generate .qsf
tools.write_to_file("{}.qsf".format(build_name), "\n".join(qsf))
# Script -------------------------------------------------------------------------------------------
def _build_script(build_name, create_rbf):
if sys.platform in ["win32", "cygwin"]:
script_file = "build_" + build_name + ".bat"
script_contents = "REM Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n"
else:
script_file = "build_" + build_name + ".sh"
script_contents = "#!/usr/bin/env bash\n"
script_contents += "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n"
script_contents += "set -e -u -x -o pipefail\n"
script_contents += """
script_file = "build_" + build_name + ".sh"
script_contents = "#!/usr/bin/env bash\n"
script_contents += "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n"
script_contents += "set -e -u -x -o pipefail\n"
script_contents += """
quartus_map --read_settings_files=on --write_settings_files=off {build_name} -c {build_name}
quartus_fit --read_settings_files=off --write_settings_files=off {build_name} -c {build_name}
quartus_asm --read_settings_files=off --write_settings_files=off {build_name} -c {build_name}
quartus_sta {build_name} -c {build_name}"""
if create_rbf:
if sys.platform in ["win32", "cygwin"]:
script_contents += """
if self.platform.create_rbf:
if sys.platform in ["win32", "cygwin"]:
script_contents += """
if exist "{build_name}.sof" (
quartus_cpf -c {build_name}.sof {build_name}.rbf
)
"""
else:
script_contents += """
else:
script_contents += """
if [ -f "{build_name}.sof" ]
then
quartus_cpf -c {build_name}.sof {build_name}.rbf
fi
"""
script_contents = script_contents.format(build_name=build_name)
tools.write_to_file(script_file, script_contents, force_unix=True)
script_contents = script_contents.format(build_name=build_name)
tools.write_to_file(script_file, script_contents, force_unix=True)
return script_file
return script_file
def _run_script(script):
if sys.platform in ["win32", "cygwin"]:
shell = ["cmd", "/c"]
else:
shell = ["bash"]
def run_script(self, script):
if sys.platform in ["win32", "cygwin"]:
shell = ["cmd", "/c"]
else:
shell = ["bash"]
if which("quartus_map") is None:
msg = "Unable to find Quartus toolchain, please:\n"
msg += "- Add Quartus toolchain to your $PATH."
raise OSError(msg)
if which("quartus_map") is None:
msg = "Unable to find Quartus toolchain, please:\n"
msg += "- Add Quartus toolchain to your $PATH."
raise OSError(msg)
if subprocess.call(shell + [script]) != 0:
raise OSError("Error occured during Quartus's script execution.")
# AlteraQuartusToolchain ---------------------------------------------------------------------------
class AlteraQuartusToolchain:
attr_translate = {}
def __init__(self):
self.clocks = dict()
self.false_paths = set()
self.additional_sdc_commands = []
self.additional_qsf_commands = []
def build(self, platform, fragment,
build_dir = "build",
build_name = "top",
run = True,
**kwargs):
# Create build directory
cwd = os.getcwd()
os.makedirs(build_dir, exist_ok=True)
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)
# Generate design timing constraints file (.sdc)
_build_sdc(
clocks = self.clocks,
false_paths = self.false_paths,
vns = v_output.ns,
named_sc = named_sc,
build_name = build_name,
additional_sdc_commands = self.additional_sdc_commands)
# Generate design project and location constraints file (.qsf)
_build_qsf(
device = platform.device,
ips = platform.ips,
sources = platform.sources,
vincpaths = platform.verilog_include_paths,
named_sc = named_sc,
named_pc = named_pc,
build_name = build_name,
additional_qsf_commands = self.additional_qsf_commands)
# Generate build script
script = _build_script(build_name, platform.create_rbf)
# Run
if run:
_run_script(script)
os.chdir(cwd)
return v_output.ns
if subprocess.call(shell + [script]) != 0:
raise OSError("Error occured during Quartus's script execution.")
def add_period_constraint(self, platform, clk, period):
clk.attr.add("keep")

View File

@ -0,0 +1,96 @@
#
# This file is part of LiteX.
#
# Copyright (c) 2017-2018 William D. Jones <thor0505@comcast.net>
# Copyright (c) 2019 Florent Kermarrec <florent@enjoy-digital.fr>
# Copyright (c) 2022 Gwenhael Goavec-Merou <gwenhael.goavec-merou@trabucayre.com>
# SPDX-License-Identifier: BSD-2-Clause
import os
import sys
import subprocess
from shutil import which
from migen.fhdl.structure import _Fragment
from litex.build.generic_platform import *
from litex.build import tools
# GenericToolchain -------------------------------------------------------------------------
class GenericToolchain:
attr_translate = {
"keep": ("keep", "true"),
}
def __init__(self):
self.clocks = dict()
self.false_paths = set() # FIXME: use it
self.named_pc = []
self.named_sc = []
# method use to build timing params
def build_timing_constr(self, vns, clocks):
pass
# method use to build project (if required)
def build_timing_constr(self, vns, clocks):
pass
def _build(self, platform, fragment,
build_dir = "build",
build_name = "top",
synth_opts = "",
run = True,
**kwargs):
self._build_name = build_name
self._synth_opts = synth_opts
self.platform = platform
# 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)
self.named_sc, self.named_pc = platform.resolve_signals(v_output.ns)
v_file = build_name + ".v"
v_output.write(v_file)
platform.add_source(v_file)
# Generate design io constraints file
self.build_constr_file(self.named_sc, self.named_pc)
# Generate design timing constraints file (in timing_constr file)
self.build_timing_constr(v_output.ns, self.clocks)
# Generate project
self.build_project()
# Generate build script
script = self.build_script()
# Run
if run:
self.run_script(script)
os.chdir(cwd)
return v_output.ns
def add_period_constraint(self, platform, clk, period):
clk.attr.add("keep")
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

View File

@ -16,138 +16,11 @@ from migen.fhdl.structure import _Fragment
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 = ""
for sig, pins, others, resname in named_sc:
if len(pins) > 1:
for bit, pin in enumerate(pins):
r += "set_io {}[{}] {}\n".format(sig, bit, pin)
else:
r += "set_io {} {}\n".format(sig, pins[0])
if named_pc:
r += "\n" + "\n\n".join(named_pc)
return r
# Timing Constraints (in pre_pack file) ------------------------------------------------------------
def _build_pre_pack(vns, clocks):
r = ""
for clk, period in clocks.items():
r += """ctx.addClock("{}", {})\n""".format(vns.get_name(clk), 1e3/period)
return r
# 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_ice40 {synth_opts} -json {build_name}.json -top {build_name} -dsp",
]
def _yosys_import_sources(platform):
includes = ""
reads = []
for path in platform.verilog_include_paths:
includes += " -I" + path
for filename, language, library, *copy in platform.sources:
# yosys has no such function read_systemverilog
if language == "systemverilog":
language = "verilog -sv"
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"],
"u4k": ["sg48"],
}
(family, architecture, package) = device.split("-")
if family not in ["ice40"]:
raise ValueError("Unknown device family {}".format(family))
if architecture not in packages.keys():
raise ValueError("Invalid device architecture {}".format(architecture))
if package not in packages[architecture]:
raise ValueError("Invalid device package {}".format(package))
return (family, architecture, package)
# Script -------------------------------------------------------------------------------------------
_build_template = [
"yosys -l {build_name}.rpt {build_name}.ys",
"nextpnr-ice40 --json {build_name}.json --pcf {build_name}.pcf --asc {build_name}.txt \
--pre-pack {build_name}_pre_pack.py --{architecture} --package {package} {timefailarg} {ignoreloops} --seed {seed}",
"icepack -s {build_name}.txt {build_name}.bin"
]
def _build_script(build_template, build_name, architecture, package, 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,
architecture = architecture,
package = package,
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-ice40") 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.")
from litex.build.generic_toolchain import GenericToolchain
# LatticeIceStormToolchain -------------------------------------------------------------------------
class LatticeIceStormToolchain:
class LatticeIceStormToolchain(GenericToolchain):
attr_translate = {
"keep": ("keep", "true"),
}
@ -155,67 +28,155 @@ class LatticeIceStormToolchain:
special_overrides = common.lattice_ice40_special_overrides
def __init__(self):
self.yosys_template = _yosys_template
self.build_template = _build_template
self.clocks = dict()
super().__init__()
self.yosys_template = self._yosys_template
self.build_template = self._build_template
def build(self, platform, fragment,
build_dir = "build",
build_name = "top",
synth_opts = "",
run = True,
timingstrict = False,
ignoreloops = False,
seed = 1,
**kwargs):
# Create build directory
os.makedirs(build_dir, exist_ok=True)
cwd = os.getcwd()
os.chdir(build_dir)
self.timingstrict = timingstrict
self.ignoreloops = ignoreloops
self.seed = seed
# Finalize design
if not isinstance(fragment, _Fragment):
fragment = fragment.get_fragment()
platform.finalize(fragment)
return self._build(platform, fragment, **kwargs)
# 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)
# IO Constraints (.pcf) ------------------------------------------------------------------------
# Generate design io constraints file (.pcf)
tools.write_to_file(build_name + ".pcf",_build_pcf(named_sc, named_pc))
def build_constr_file(self, named_sc, named_pc):
r = ""
for sig, pins, others, resname in named_sc:
if len(pins) > 1:
for bit, pin in enumerate(pins):
r += "set_io {}[{}] {}\n".format(sig, bit, pin)
else:
r += "set_io {} {}\n".format(sig, pins[0])
if named_pc:
r += "\n" + "\n\n".join(named_pc)
tools.write_to_file(self._build_name + "pcf", r)
# 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))
# Timing Constraints (in pre_pack file) ------------------------------------------------------------
# Generate Yosys script
_build_yosys(self.yosys_template, platform, build_name, synth_opts=synth_opts)
def build_timing_constr(self, vns, clocks):
r = ""
for clk, period in clocks.items():
r += """ctx.addClock("{}", {})\n""".format(vns.get_name(clk), 1e3/period)
tools.write_to_file(self._build_name + "_pre_pack.py", r)
# Yosys/Nextpnr Helpers/Templates ------------------------------------------------------------------
def _yosys_import_sources(self):
includes = ""
reads = []
for path in self.platform.verilog_include_paths:
includes += " -I" + path
for filename, language, library, *copy in self.platform.sources:
# yosys has no such function read_systemverilog
if language == "systemverilog":
language = "verilog -sv"
reads.append("read_{}{} {}".format(
language, includes, filename))
return "\n".join(reads)
# Project (ys) -------------------------------------------------------------------------------------
_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_ice40 {synth_opts} -json {build_name}.json -top {build_name} -dsp",
]
def build_project(self):
ys = []
for l in self._yosys_template:
ys.append(l.format(
build_name = self._build_name,
read_files = self._yosys_import_sources(),
synth_opts = self._synth_opts
))
tools.write_to_file(self._build_name + ".ys", "\n".join(ys))
# Script -------------------------------------------------------------------------------------------
_build_template = [
"yosys -l {build_name}.rpt {build_name}.ys",
"nextpnr-ice40 --json {build_name}.json --pcf {build_name}.pcf --asc {build_name}.txt \
--pre-pack {build_name}_pre_pack.py --{architecture} --package {package} {timefailarg} {ignoreloops} --seed {seed}",
"icepack -s {build_name}.txt {build_name}.bin"
]
def build_script(self):
# Translate device to Nextpnr architecture/package
(family, architecture, package) = parse_device(platform.device)
(family, architecture, package) = self.parse_device()
# Generate build script
script = _build_script(self.build_template, build_name, architecture, package, 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 = ""
# Run
if run:
_run_script(script)
for s in self.build_template:
s_fail = s + "{fail_stmt}\n" # Required so Windows scripts fail early.
script_contents += s_fail.format(
build_name = self._build_name,
architecture = architecture,
package = package,
timefailarg = "--timing-allow-fail" if not self.timingstrict else "",
ignoreloops = "--ignore-loops" if self.ignoreloops else "",
fail_stmt = fail_stmt,
seed = self.seed)
os.chdir(cwd)
script_file = "build_" + self._build_name + script_ext
tools.write_to_file(script_file, script_contents, force_unix=False)
return v_output.ns
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-ice40") 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.")
def parse_device(self):
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"],
"u4k": ["sg48"],
}
(family, architecture, package) = self.platform.device.split("-")
if family not in ["ice40"]:
raise ValueError("Unknown device family {}".format(family))
if architecture not in packages.keys():
raise ValueError("Invalid device architecture {}".format(architecture))
if package not in packages[architecture]:
raise ValueError("Invalid device package {}".format(package))
return (family, architecture, package)
def add_period_constraint(self, platform, clk, period):
clk.attr.add("keep")
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 icestorm_args(parser):
toolchain_group = parser.add_argument_group(title="Toolchain options")

View File

@ -16,166 +16,11 @@ from migen.fhdl.structure import _Fragment
from litex.build.generic_platform import *
from litex.build import tools
from litex.build.lattice import common
# 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 = [
"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_ecp5 {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, *copy in platform.sources:
# yosys has no such function read_systemverilog
if language == "systemverilog":
language = "verilog -sv"
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))
def nextpnr_ecp5_parse_device(device):
device = device.lower()
family = device.split("-")[0]
size = device.split("-")[1]
speed_grade = device.split("-")[2][0]
if speed_grade not in ["6", "7", "8"]:
raise ValueError("Invalid speed grade {}".format(speed_grade))
package = device.split("-")[2][1:]
if "256" in package:
package = "CABGA256"
elif "285" in package:
package = "CSFBGA285"
elif "381" in package:
package = "CABGA381"
elif "554" in package:
package = "CABGA554"
elif "756" in package:
package = "CABGA756"
else:
raise ValueError("Invalid package {}".format(package))
return (family, size, speed_grade, package)
nextpnr_ecp5_architectures = {
"lfe5u-12f" : "12k",
"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",
}
# Script -------------------------------------------------------------------------------------------
_build_template = [
"yosys -l {build_name}.rpt {build_name}.ys",
"nextpnr-ecp5 --json {build_name}.json --lpf {build_name}.lpf --textcfg {build_name}.config \
--{architecture} --package {package} --speed {speed_grade} {timefailarg} {ignoreloops} --seed {seed}",
"ecppack {build_name}.config --svf {build_name}.svf --bit {build_name}.bit --bootaddr {bootaddr} {spimode} {compress}"
]
def _build_script(source, build_template, build_name, architecture, package, speed_grade, timingstrict, ignoreloops, bootaddr, seed, spimode, compress):
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,
architecture = architecture,
package = package,
speed_grade = speed_grade,
timefailarg = "--timing-allow-fail" if not timingstrict else "",
ignoreloops = "--ignore-loops" if ignoreloops else "",
bootaddr = bootaddr,
fail_stmt = fail_stmt,
seed = seed,
spimode = "" if spimode is None else "--spimode {}".format(spimode),
compress = "" if not compress else "--compress")
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-ecp5") 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.")
from litex.build.generic_toolchain import GenericToolchain
# LatticeTrellisToolchain --------------------------------------------------------------------------
class LatticeTrellisToolchain:
class LatticeTrellisToolchain(GenericToolchain):
attr_translate = {
"keep": ("keep", "true"),
}
@ -183,14 +28,11 @@ class LatticeTrellisToolchain:
special_overrides = common.lattice_ecp5_trellis_special_overrides
def __init__(self):
self.yosys_template = _yosys_template
self.build_template = _build_template
self.false_paths = set() # FIXME: use it
super().__init__()
self.yosys_template = self._yosys_template
self.build_template = self._build_template
def build(self, platform, fragment,
build_dir = "build",
build_name = "top",
run = True,
nowidelut = False,
abc9 = False,
timingstrict = False,
@ -201,43 +43,177 @@ class LatticeTrellisToolchain:
compress = True,
**kwargs):
# Create build directory
os.makedirs(build_dir, exist_ok=True)
cwd = os.getcwd()
os.chdir(build_dir)
self._nowidelut = nowidelut
self._abc9 = abc9
self._timingstrict = timingstrict
self._ignoreloops = ignoreloops
self._bootaddr = bootaddr
self._seed = seed
self._spimode = spimode
self._compress = compress
# Finalize design
if not isinstance(fragment, _Fragment):
fragment = fragment.get_fragment()
platform.finalize(fragment)
return self._build(platform, fragment, **kwargs)
# 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)
# IO Constraints (.lpf) ----------------------------------------------------------------------------
# Generate design constraints file (.lpf)
_build_lpf(named_sc, named_pc, build_name)
@classmethod
def _format_constraint(cls, 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(self, signame, pin, others, resname):
fmt_c = [self._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_constr_file(self, named_sc, named_pc):
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(self._format_lpf(sig + "[" + str(i) + "]", p, others, resname))
else:
lpf.append(self._format_lpf(sig, pins[0], others, resname))
if named_pc:
lpf.append("\n\n".join(named_pc))
tools.write_to_file(self._build_name + ".lpf", "\n".join(lpf))
# 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_ecp5 {nwl} {abc} -json {build_name}.json -top {build_name}",
]
def _yosys_import_sources(self):
includes = ""
reads = []
for path in self.platform.verilog_include_paths:
includes += " -I" + path
for filename, language, library, *copy in self.platform.sources:
# yosys has no such function read_systemverilog
if language == "systemverilog":
language = "verilog -sv"
reads.append("read_{}{} {}".format(
language, includes, filename))
return "\n".join(reads)
def _build_yosys(self):
ys = []
for l in self._yosys_template:
ys.append(l.format(
build_name = self._build_name,
nwl = "-nowidelut" if self._nowidelut else "",
abc = "-abc9" if self._abc9 else "",
read_files = self._yosys_import_sources()
))
tools.write_to_file(self._build_name + ".ys", "\n".join(ys))
def nextpnr_ecp5_parse_device(self, device):
device = device.lower()
family = device.split("-")[0]
size = device.split("-")[1]
speed_grade = device.split("-")[2][0]
if speed_grade not in ["6", "7", "8"]:
raise ValueError("Invalid speed grade {}".format(speed_grade))
package = device.split("-")[2][1:]
if "256" in package:
package = "CABGA256"
elif "285" in package:
package = "CSFBGA285"
elif "381" in package:
package = "CABGA381"
elif "554" in package:
package = "CABGA554"
elif "756" in package:
package = "CABGA756"
else:
raise ValueError("Invalid package {}".format(package))
return (family, size, speed_grade, package)
nextpnr_ecp5_architectures = {
"lfe5u-12f" : "12k",
"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",
}
# Script -------------------------------------------------------------------------------------------
_build_template = [
"yosys -l {build_name}.rpt {build_name}.ys",
"nextpnr-ecp5 --json {build_name}.json --lpf {build_name}.lpf --textcfg {build_name}.config \
--{architecture} --package {package} --speed {speed_grade} {timefailarg} {ignoreloops} --seed {seed}",
"ecppack {build_name}.config --svf {build_name}.svf --bit {build_name}.bit --bootaddr {bootaddr} {spimode} {compress}"
]
def build_script(self):
# Generate Yosys script
_build_yosys(self.yosys_template, platform, nowidelut, abc9, build_name)
self._build_yosys()
# Translate device to Nextpnr architecture/package
(family, size, speed_grade, package) = self.nextpnr_ecp5_parse_device(self.platform.device)
architecture = self.nextpnr_ecp5_architectures[(family + "-" + size)]
# Translate device to Nextpnr architecture/package/speed_grade
(family, size, speed_grade, package) = nextpnr_ecp5_parse_device(platform.device)
architecture = nextpnr_ecp5_architectures[(family + "-" + size)]
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 = ""
# Generate build script
script = _build_script(False, self.build_template, build_name, architecture, package,
speed_grade, timingstrict, ignoreloops, bootaddr, seed, spimode, compress)
# Run
if run:
_run_script(script)
for s in self._build_template:
s_fail = s + "{fail_stmt}\n" # Required so Windows scripts fail early.
script_contents += s_fail.format(
build_name = self._build_name,
architecture = architecture,
package = package,
speed_grade = speed_grade,
timefailarg = "--timing-allow-fail" if not self._timingstrict else "",
ignoreloops = "--ignore-loops" if self._ignoreloops else "",
bootaddr = self._bootaddr,
fail_stmt = fail_stmt,
seed = self._seed,
spimode = "" if self._spimode is None else f"--spimode {self._spimode}",
compress = "" if not self._compress else "--compress")
os.chdir(cwd)
script_file = "build_" + self._build_name + script_ext
tools.write_to_file(script_file, script_contents, force_unix=False)
return v_output.ns
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-ecp5") 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.")
def add_period_constraint(self, platform, clk, period):
platform.add_platform_command("""FREQUENCY PORT "{clk}" {freq} MHz;""".format(