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,11 +15,27 @@ 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
# AlteraQuartusToolchain ---------------------------------------------------------------------------
class AlteraQuartusToolchain(GenericToolchain):
attr_translate = {}
def __init__(self):
super().__init__()
self.additional_sdc_commands = []
self.additional_qsf_commands = []
self.cst = []
def build(self, platform, fragment, **kwargs):
return self._build(platform, fragment, **kwargs)
# IO/Placement Constraints (.qsf) ------------------------------------------------------------------
def _format_constraint(c, signame, fmt_r):
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}"
@ -39,14 +55,14 @@ def _format_constraint(c, signame, fmt_r):
tpl = "set_instance_assignment -comment \"{name}\" -name {misc} -to {signame}"
return tpl.format(signame=signame, name=fmt_r, misc=c.misc)
def _format_qsf_constraint(signame, pin, others, resname):
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 = [_format_constraint(c, signame, fmt_r) for c in ([Pins(pin)] + others)]
fmt_c = [self._format_constraint(c, signame, fmt_r) for c in ([Pins(pin)] + others)]
return '\n'.join(fmt_c)
def _is_virtual_pin(pin_name):
def _is_virtual_pin(self, pin_name):
return pin_name in (
"altera_reserved_tms",
"altera_reserved_tck",
@ -54,31 +70,29 @@ def _is_virtual_pin(pin_name):
"altera_reserved_tdo",
)
def _build_qsf_constraints(named_sc, named_pc):
qsf = []
def build_constr_file(self, named_sc, named_pc):
for sig, pins, others, resname in named_sc:
if len(pins) > 1:
for i, p in enumerate(pins):
if _is_virtual_pin(p):
if self._is_virtual_pin(p):
continue
qsf.append(_format_qsf_constraint("{}[{}]".format(sig, i), p, others, resname))
self.cst.append(self._format_qsf_constraint("{}[{}]".format(sig, i), p, others, resname))
else:
if _is_virtual_pin(pins[0]):
if self._is_virtual_pin(pins[0]):
continue
qsf.append(_format_qsf_constraint(sig, pins[0], others, resname))
self.cst.append(self._format_qsf_constraint(sig, pins[0], others, resname))
if named_pc:
qsf.append("\n\n".join(named_pc))
return "\n".join(qsf)
self.cst.append("\n\n".join(named_pc))
# Timing Constraints (.sdc) ------------------------------------------------------------------------
def _build_sdc(clocks, false_paths, vns, named_sc, build_name, additional_sdc_commands):
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 named_sc:
for sig, pins, others, resname in self.named_sc:
if sig == vns.get_name(clk):
is_port = True
if is_port:
@ -92,26 +106,26 @@ def _build_sdc(clocks, false_paths, vns, named_sc, build_name, additional_sdc_co
sdc.append("derive_pll_clocks")
# False path constraints
for from_, to in sorted(false_paths, key=lambda x: (x[0].duid, x[1].duid)):
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 += additional_sdc_commands
sdc += self.additional_sdc_commands
# Generate .sdc
tools.write_to_file("{}.sdc".format(build_name), "\n".join(sdc))
tools.write_to_file("{}.sdc".format(self._build_name), "\n".join(sdc))
# Project (.qsf) -----------------------------------------------------------------------------------
def _build_qsf(device, ips, sources, vincpaths, named_sc, named_pc, build_name, additional_qsf_commands):
def build_project(self):
qsf = []
# Set device
qsf.append("set_global_assignment -name DEVICE {}".format(device))
qsf.append("set_global_assignment -name DEVICE {}".format(self.platform.device))
# Add sources
for filename, language, library, *copy in 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
@ -122,36 +136,40 @@ def _build_qsf(device, ips, sources, vincpaths, named_sc, named_pc, build_name,
else:
if filename.endswith(".svh") or filename.endswith(".vh"):
fpath = os.path.dirname(filename)
if fpath not in vincpaths:
vincpaths.append(fpath)
if fpath not in platform.verilog_include_paths:
platform.verilog_include_paths.append(fpath)
# Add ips
for filename in 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 vincpaths:
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 " + build_name)
qsf.append("set_global_assignment -name top_level_entity " + self._build_name)
# Add io, placement constraints
qsf.append(_build_qsf_constraints(named_sc, named_pc))
qsf.append("\n".join(self.cst))
# Set timing constraints
qsf.append("set_global_assignment -name SDC_FILE {}.sdc".format(build_name))
qsf.append("set_global_assignment -name SDC_FILE {}.sdc".format(self._build_name))
# Add additional commands
qsf += additional_qsf_commands
qsf += self.additional_qsf_commands
# Generate .qsf
tools.write_to_file("{}.qsf".format(build_name), "\n".join(qsf))
tools.write_to_file("{}.qsf".format(self._build_name), "\n".join(qsf))
# Script -------------------------------------------------------------------------------------------
def _build_script(build_name, create_rbf):
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"
@ -165,7 +183,7 @@ quartus_map --read_settings_files=on --write_settings_files=off {build_name} -c
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 self.platform.create_rbf:
if sys.platform in ["win32", "cygwin"]:
script_contents += """
if exist "{build_name}.sof" (
@ -184,7 +202,7 @@ fi
return script_file
def _run_script(script):
def run_script(self, script):
if sys.platform in ["win32", "cygwin"]:
shell = ["cmd", "/c"]
else:
@ -198,71 +216,6 @@ def _run_script(script):
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
def add_period_constraint(self, platform, clk, period):
clk.attr.add("keep")
period = math.floor(period*1e3)/1e3 # round to lowest picosecond

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,10 +16,37 @@ 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.generic_toolchain import GenericToolchain
# IO Constraints (.pcf) ----------------------------------------------------------------------------
# LatticeIceStormToolchain -------------------------------------------------------------------------
def _build_pcf(named_sc, named_pc):
class LatticeIceStormToolchain(GenericToolchain):
attr_translate = {
"keep": ("keep", "true"),
}
special_overrides = common.lattice_ice40_special_overrides
def __init__(self):
super().__init__()
self.yosys_template = self._yosys_template
self.build_template = self._build_template
def build(self, platform, fragment,
timingstrict = False,
ignoreloops = False,
seed = 1,
**kwargs):
self.timingstrict = timingstrict
self.ignoreloops = ignoreloops
self.seed = seed
return self._build(platform, fragment, **kwargs)
# IO Constraints (.pcf) ------------------------------------------------------------------------
def build_constr_file(self, named_sc, named_pc):
r = ""
for sig, pins, others, resname in named_sc:
if len(pins) > 1:
@ -29,18 +56,33 @@ def _build_pcf(named_sc, named_pc):
r += "set_io {} {}\n".format(sig, pins[0])
if named_pc:
r += "\n" + "\n\n".join(named_pc)
return r
tools.write_to_file(self._build_name + "pcf", r)
# Timing Constraints (in pre_pack file) ------------------------------------------------------------
def _build_pre_pack(vns, clocks):
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)
return r
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",
@ -50,51 +92,15 @@ _yosys_template = [
"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):
def build_project(self):
ys = []
for l in template:
for l in self._yosys_template:
ys.append(l.format(
build_name = build_name,
read_files = _yosys_import_sources(platform),
synth_opts = synth_opts
build_name = self._build_name,
read_files = self._yosys_import_sources(),
synth_opts = self._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)
tools.write_to_file(self._build_name + ".ys", "\n".join(ys))
# Script -------------------------------------------------------------------------------------------
@ -105,7 +111,10 @@ _build_template = [
"icepack -s {build_name}.txt {build_name}.bin"
]
def _build_script(build_template, build_name, architecture, package, timingstrict, ignoreloops, seed):
def build_script(self):
# Translate device to Nextpnr architecture/package
(family, architecture, package) = self.parse_device()
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"
@ -115,23 +124,23 @@ def _build_script(build_template, build_name, architecture, package, timingstric
script_contents = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\nset -e\n"
fail_stmt = ""
for s in build_template:
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 = build_name,
build_name = self._build_name,
architecture = architecture,
package = package,
timefailarg = "--timing-allow-fail" if not timingstrict else "",
ignoreloops = "--ignore-loops" if ignoreloops else "",
timefailarg = "--timing-allow-fail" if not self.timingstrict else "",
ignoreloops = "--ignore-loops" if self.ignoreloops else "",
fail_stmt = fail_stmt,
seed = seed)
seed = self.seed)
script_file = "build_" + build_name + script_ext
script_file = "build_" + self._build_name + script_ext
tools.write_to_file(script_file, script_contents, force_unix=False)
return script_file
def _run_script(script):
def run_script(script):
if sys.platform in ("win32", "cygwin"):
shell = ["cmd", "/c"]
else:
@ -145,77 +154,29 @@ def _run_script(script):
if subprocess.call(shell + [script]) != 0:
raise OSError("Error occured during Yosys/Nextpnr's script execution.")
# LatticeIceStormToolchain -------------------------------------------------------------------------
class LatticeIceStormToolchain:
attr_translate = {
"keep": ("keep", "true"),
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"],
}
special_overrides = common.lattice_ice40_special_overrides
(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 __init__(self):
self.yosys_template = _yosys_template
self.build_template = _build_template
self.clocks = dict()
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)
# 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 io constraints file (.pcf)
tools.write_to_file(build_name + ".pcf",_build_pcf(named_sc, named_pc))
# 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))
# Generate Yosys script
_build_yosys(self.yosys_template, platform, build_name, synth_opts=synth_opts)
# Translate device to Nextpnr architecture/package
(family, architecture, package) = parse_device(platform.device)
# Generate build script
script = _build_script(self.build_template, build_name, architecture, package, timingstrict, ignoreloops, seed)
# Run
if run:
_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
def icestorm_args(parser):
toolchain_group = parser.add_argument_group(title="Toolchain options")

View File

@ -16,10 +16,48 @@ 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.generic_toolchain import GenericToolchain
# LatticeTrellisToolchain --------------------------------------------------------------------------
class LatticeTrellisToolchain(GenericToolchain):
attr_translate = {
"keep": ("keep", "true"),
}
special_overrides = common.lattice_ecp5_trellis_special_overrides
def __init__(self):
super().__init__()
self.yosys_template = self._yosys_template
self.build_template = self._build_template
def build(self, platform, fragment,
nowidelut = False,
abc9 = False,
timingstrict = False,
ignoreloops = False,
bootaddr = 0,
seed = 1,
spimode = None,
compress = True,
**kwargs):
self._nowidelut = nowidelut
self._abc9 = abc9
self._timingstrict = timingstrict
self._ignoreloops = ignoreloops
self._bootaddr = bootaddr
self._seed = seed
self._spimode = spimode
self._compress = compress
return self._build(platform, fragment, **kwargs)
# IO Constraints (.lpf) ----------------------------------------------------------------------------
def _format_constraint(c):
@classmethod
def _format_constraint(cls, c):
if isinstance(c, Pins):
return ("LOCATE COMP ", " SITE " + "\"" + c.identifiers[0] + "\"")
elif isinstance(c, IOStandard):
@ -27,28 +65,26 @@ def _format_constraint(c):
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)]
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_lpf(named_sc, named_pc, build_name):
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(_format_lpf(sig + "[" + str(i) + "]", p, others, resname))
lpf.append(self._format_lpf(sig + "[" + str(i) + "]", p, others, resname))
else:
lpf.append(_format_lpf(sig, pins[0], others, resname))
lpf.append(self._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))
tools.write_to_file(self._build_name + ".lpf", "\n".join(lpf))
# Yosys/Nextpnr Helpers/Templates ------------------------------------------------------------------
@ -61,12 +97,12 @@ _yosys_template = [
"synth_ecp5 {nwl} {abc} -json {build_name}.json -top {build_name}",
]
def _yosys_import_sources(platform):
def _yosys_import_sources(self):
includes = ""
reads = []
for path in platform.verilog_include_paths:
for path in self.platform.verilog_include_paths:
includes += " -I" + path
for filename, language, library, *copy in platform.sources:
for filename, language, library, *copy in self.platform.sources:
# yosys has no such function read_systemverilog
if language == "systemverilog":
language = "verilog -sv"
@ -74,18 +110,18 @@ def _yosys_import_sources(platform):
language, includes, filename))
return "\n".join(reads)
def _build_yosys(template, platform, nowidelut, abc9, build_name):
def _build_yosys(self):
ys = []
for l in template:
for l in self._yosys_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)
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(build_name + ".ys", "\n".join(ys))
tools.write_to_file(self._build_name + ".ys", "\n".join(ys))
def nextpnr_ecp5_parse_device(device):
def nextpnr_ecp5_parse_device(self, device):
device = device.lower()
family = device.split("-")[0]
size = device.split("-")[1]
@ -129,7 +165,13 @@ _build_template = [
"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):
def build_script(self):
# Generate Yosys script
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)]
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"
@ -139,27 +181,27 @@ def _build_script(source, build_template, build_name, architecture, package, spe
script_contents = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\nset -e\n"
fail_stmt = ""
for s in build_template:
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 = build_name,
build_name = self._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,
timefailarg = "--timing-allow-fail" if not self._timingstrict else "",
ignoreloops = "--ignore-loops" if self._ignoreloops else "",
bootaddr = self._bootaddr,
fail_stmt = fail_stmt,
seed = seed,
spimode = "" if spimode is None else "--spimode {}".format(spimode),
compress = "" if not compress else "--compress")
seed = self._seed,
spimode = "" if self._spimode is None else f"--spimode {self._spimode}",
compress = "" if not self._compress else "--compress")
script_file = "build_" + build_name + script_ext
script_file = "build_" + self._build_name + script_ext
tools.write_to_file(script_file, script_contents, force_unix=False)
return script_file
def _run_script(script):
def run_script(script):
if sys.platform in ("win32", "cygwin"):
shell = ["cmd", "/c"]
else:
@ -173,72 +215,6 @@ def _run_script(script):
if subprocess.call(shell + [script]) != 0:
raise OSError("Error occured during Yosys/Nextpnr's script execution.")
# LatticeTrellisToolchain --------------------------------------------------------------------------
class LatticeTrellisToolchain:
attr_translate = {
"keep": ("keep", "true"),
}
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
def build(self, platform, fragment,
build_dir = "build",
build_name = "top",
run = True,
nowidelut = False,
abc9 = False,
timingstrict = False,
ignoreloops = False,
bootaddr = 0,
seed = 1,
spimode = None,
compress = 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)
top_file = build_name + ".v"
v_output.write(top_file)
platform.add_source(top_file)
# Generate design constraints file (.lpf)
_build_lpf(named_sc, named_pc, build_name)
# Generate Yosys script
_build_yosys(self.yosys_template, platform, nowidelut, abc9, build_name)
# 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)]
# 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)
os.chdir(cwd)
return v_output.ns
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)