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 migen.fhdl.structure import _Fragment
from litex.build.generic_platform import Pins, IOStandard, Misc from litex.build.generic_platform import Pins, IOStandard, Misc
from litex.build.generic_toolchain import GenericToolchain
from litex.build import tools 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) ------------------------------------------------------------------ # IO/Placement Constraints (.qsf) ------------------------------------------------------------------
def _format_constraint(c, signame, fmt_r): def _format_constraint(self, c, signame, fmt_r):
# IO location constraints # IO location constraints
if isinstance(c, Pins): if isinstance(c, Pins):
tpl = "set_location_assignment -comment \"{name}\" -to {signame} Pin_{pin}" 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}" tpl = "set_instance_assignment -comment \"{name}\" -name {misc} -to {signame}"
return tpl.format(signame=signame, name=fmt_r, misc=c.misc) 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]) fmt_r = "{}:{}".format(*resname[:2])
if resname[2] is not None: if resname[2] is not None:
fmt_r += "." + resname[2] 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) return '\n'.join(fmt_c)
def _is_virtual_pin(pin_name): def _is_virtual_pin(self, pin_name):
return pin_name in ( return pin_name in (
"altera_reserved_tms", "altera_reserved_tms",
"altera_reserved_tck", "altera_reserved_tck",
@ -54,31 +70,29 @@ def _is_virtual_pin(pin_name):
"altera_reserved_tdo", "altera_reserved_tdo",
) )
def _build_qsf_constraints(named_sc, named_pc): def build_constr_file(self, named_sc, named_pc):
qsf = []
for sig, pins, others, resname in named_sc: for sig, pins, others, resname in named_sc:
if len(pins) > 1: if len(pins) > 1:
for i, p in enumerate(pins): for i, p in enumerate(pins):
if _is_virtual_pin(p): if self._is_virtual_pin(p):
continue 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: else:
if _is_virtual_pin(pins[0]): if self._is_virtual_pin(pins[0]):
continue 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: if named_pc:
qsf.append("\n\n".join(named_pc)) self.cst.append("\n\n".join(named_pc))
return "\n".join(qsf)
# Timing Constraints (.sdc) ------------------------------------------------------------------------ # 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 = [] sdc = []
# Clock constraints # Clock constraints
for clk, period in sorted(clocks.items(), key=lambda x: x[0].duid): for clk, period in sorted(clocks.items(), key=lambda x: x[0].duid):
is_port = False 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): if sig == vns.get_name(clk):
is_port = True is_port = True
if is_port: 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") sdc.append("derive_pll_clocks")
# False path constraints # 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}}}]" 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))) sdc.append(tpl.format(from_=vns.get_name(from_), to=vns.get_name(to)))
# Add additional commands # Add additional commands
sdc += additional_sdc_commands sdc += self.additional_sdc_commands
# Generate .sdc # 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) ----------------------------------------------------------------------------------- # Project (.qsf) -----------------------------------------------------------------------------------
def _build_qsf(device, ips, sources, vincpaths, named_sc, named_pc, build_name, additional_qsf_commands): def build_project(self):
qsf = [] qsf = []
# Set device # Set device
qsf.append("set_global_assignment -name DEVICE {}".format(device)) qsf.append("set_global_assignment -name DEVICE {}".format(self.platform.device))
# Add sources # 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 if language == "verilog": language = "systemverilog" # Enforce use of SystemVerilog
tpl = "set_global_assignment -name {lang}_FILE {path} -library {lib}" tpl = "set_global_assignment -name {lang}_FILE {path} -library {lib}"
# Do not add None type files # Do not add None type files
@ -122,36 +136,40 @@ def _build_qsf(device, ips, sources, vincpaths, named_sc, named_pc, build_name,
else: else:
if filename.endswith(".svh") or filename.endswith(".vh"): if filename.endswith(".svh") or filename.endswith(".vh"):
fpath = os.path.dirname(filename) fpath = os.path.dirname(filename)
if fpath not in vincpaths: if fpath not in platform.verilog_include_paths:
vincpaths.append(fpath) platform.verilog_include_paths.append(fpath)
# Add ips # Add ips
for filename in ips: for filename in self.platform.ips:
tpl = "set_global_assignment -name QSYS_FILE {filename}" tpl = "set_global_assignment -name QSYS_FILE {filename}"
qsf.append(tpl.replace(filename=filename.replace("\\", "/"))) qsf.append(tpl.replace(filename=filename.replace("\\", "/")))
# Add include paths # 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("\\", "/"))) qsf.append("set_global_assignment -name SEARCH_PATH {}".format(path.replace("\\", "/")))
# Set top level # 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 # Add io, placement constraints
qsf.append(_build_qsf_constraints(named_sc, named_pc)) qsf.append("\n".join(self.cst))
# Set timing constraints # 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 # Add additional commands
qsf += additional_qsf_commands qsf += self.additional_qsf_commands
# Generate .qsf # 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 ------------------------------------------------------------------------------------------- # 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"]: if sys.platform in ["win32", "cygwin"]:
script_file = "build_" + build_name + ".bat" script_file = "build_" + build_name + ".bat"
script_contents = "REM Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n" 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_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_asm --read_settings_files=off --write_settings_files=off {build_name} -c {build_name}
quartus_sta {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"]: if sys.platform in ["win32", "cygwin"]:
script_contents += """ script_contents += """
if exist "{build_name}.sof" ( if exist "{build_name}.sof" (
@ -184,7 +202,7 @@ fi
return script_file return script_file
def _run_script(script): def run_script(self, script):
if sys.platform in ["win32", "cygwin"]: if sys.platform in ["win32", "cygwin"]:
shell = ["cmd", "/c"] shell = ["cmd", "/c"]
else: else:
@ -198,71 +216,6 @@ def _run_script(script):
if subprocess.call(shell + [script]) != 0: if subprocess.call(shell + [script]) != 0:
raise OSError("Error occured during Quartus's script execution.") 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): def add_period_constraint(self, platform, clk, period):
clk.attr.add("keep") clk.attr.add("keep")
period = math.floor(period*1e3)/1e3 # round to lowest picosecond 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.generic_platform import *
from litex.build import tools from litex.build import tools
from litex.build.lattice import common 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 = "" r = ""
for sig, pins, others, resname in named_sc: for sig, pins, others, resname in named_sc:
if len(pins) > 1: if len(pins) > 1:
@ -29,18 +56,33 @@ def _build_pcf(named_sc, named_pc):
r += "set_io {} {}\n".format(sig, pins[0]) r += "set_io {} {}\n".format(sig, pins[0])
if named_pc: if named_pc:
r += "\n" + "\n\n".join(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) ------------------------------------------------------------ # Timing Constraints (in pre_pack file) ------------------------------------------------------------
def _build_pre_pack(vns, clocks): def build_timing_constr(self, vns, clocks):
r = "" r = ""
for clk, period in clocks.items(): for clk, period in clocks.items():
r += """ctx.addClock("{}", {})\n""".format(vns.get_name(clk), 1e3/period) 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 ------------------------------------------------------------------ # 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 = [ _yosys_template = [
"verilog_defaults -push", "verilog_defaults -push",
"verilog_defaults -add -defer", "verilog_defaults -add -defer",
@ -50,51 +92,15 @@ _yosys_template = [
"synth_ice40 {synth_opts} -json {build_name}.json -top {build_name} -dsp", "synth_ice40 {synth_opts} -json {build_name}.json -top {build_name} -dsp",
] ]
def _yosys_import_sources(platform): def build_project(self):
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 = [] ys = []
for l in template: for l in self._yosys_template:
ys.append(l.format( ys.append(l.format(
build_name = build_name, build_name = self._build_name,
read_files = _yosys_import_sources(platform), read_files = self._yosys_import_sources(),
synth_opts = synth_opts synth_opts = self._synth_opts
)) ))
tools.write_to_file(build_name + ".ys", "\n".join(ys)) tools.write_to_file(self._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 ------------------------------------------------------------------------------------------- # Script -------------------------------------------------------------------------------------------
@ -105,7 +111,10 @@ _build_template = [
"icepack -s {build_name}.txt {build_name}.bin" "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"): if sys.platform in ("win32", "cygwin"):
script_ext = ".bat" script_ext = ".bat"
script_contents = "@echo off\nrem Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n\n" 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" script_contents = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\nset -e\n"
fail_stmt = "" 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. s_fail = s + "{fail_stmt}\n" # Required so Windows scripts fail early.
script_contents += s_fail.format( script_contents += s_fail.format(
build_name = build_name, build_name = self._build_name,
architecture = architecture, architecture = architecture,
package = package, package = package,
timefailarg = "--timing-allow-fail" if not timingstrict else "", timefailarg = "--timing-allow-fail" if not self.timingstrict else "",
ignoreloops = "--ignore-loops" if ignoreloops else "", ignoreloops = "--ignore-loops" if self.ignoreloops else "",
fail_stmt = fail_stmt, 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) tools.write_to_file(script_file, script_contents, force_unix=False)
return script_file return script_file
def _run_script(script): def run_script(script):
if sys.platform in ("win32", "cygwin"): if sys.platform in ("win32", "cygwin"):
shell = ["cmd", "/c"] shell = ["cmd", "/c"]
else: else:
@ -145,77 +154,29 @@ def _run_script(script):
if subprocess.call(shell + [script]) != 0: if subprocess.call(shell + [script]) != 0:
raise OSError("Error occured during Yosys/Nextpnr's script execution.") raise OSError("Error occured during Yosys/Nextpnr's script execution.")
# LatticeIceStormToolchain ------------------------------------------------------------------------- def parse_device(self):
packages = {
class LatticeIceStormToolchain: "lp384": ["qn32", "cm36", "cm49"],
attr_translate = { "lp1k": ["swg16tr", "cm36", "cm49", "cm81", "cb81", "qn84", "cm121", "cb121"],
"keep": ("keep", "true"), "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): def icestorm_args(parser):
toolchain_group = parser.add_argument_group(title="Toolchain options") 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.generic_platform import *
from litex.build import tools from litex.build import tools
from litex.build.lattice import common 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) ---------------------------------------------------------------------------- # IO Constraints (.lpf) ----------------------------------------------------------------------------
def _format_constraint(c): @classmethod
def _format_constraint(cls, c):
if isinstance(c, Pins): if isinstance(c, Pins):
return ("LOCATE COMP ", " SITE " + "\"" + c.identifiers[0] + "\"") return ("LOCATE COMP ", " SITE " + "\"" + c.identifiers[0] + "\"")
elif isinstance(c, IOStandard): elif isinstance(c, IOStandard):
@ -27,28 +65,26 @@ def _format_constraint(c):
elif isinstance(c, Misc): elif isinstance(c, Misc):
return ("IOBUF PORT ", " " + c.misc) return ("IOBUF PORT ", " " + c.misc)
def _format_lpf(self, signame, pin, others, resname):
def _format_lpf(signame, pin, others, resname): fmt_c = [self._format_constraint(c) for c in ([Pins(pin)] + others)]
fmt_c = [_format_constraint(c) for c in ([Pins(pin)] + others)]
lpf = [] lpf = []
for pre, suf in fmt_c: for pre, suf in fmt_c:
lpf.append(pre + "\"" + signame + "\"" + suf + ";") lpf.append(pre + "\"" + signame + "\"" + suf + ";")
return "\n".join(lpf) return "\n".join(lpf)
def build_constr_file(self, named_sc, named_pc):
def _build_lpf(named_sc, named_pc, build_name):
lpf = [] lpf = []
lpf.append("BLOCK RESETPATHS;") lpf.append("BLOCK RESETPATHS;")
lpf.append("BLOCK ASYNCPATHS;") lpf.append("BLOCK ASYNCPATHS;")
for sig, pins, others, resname in named_sc: for sig, pins, others, resname in named_sc:
if len(pins) > 1: if len(pins) > 1:
for i, p in enumerate(pins): 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: else:
lpf.append(_format_lpf(sig, pins[0], others, resname)) lpf.append(self._format_lpf(sig, pins[0], others, resname))
if named_pc: if named_pc:
lpf.append("\n\n".join(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 ------------------------------------------------------------------ # Yosys/Nextpnr Helpers/Templates ------------------------------------------------------------------
@ -61,12 +97,12 @@ _yosys_template = [
"synth_ecp5 {nwl} {abc} -json {build_name}.json -top {build_name}", "synth_ecp5 {nwl} {abc} -json {build_name}.json -top {build_name}",
] ]
def _yosys_import_sources(platform): def _yosys_import_sources(self):
includes = "" includes = ""
reads = [] reads = []
for path in platform.verilog_include_paths: for path in self.platform.verilog_include_paths:
includes += " -I" + path 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 # yosys has no such function read_systemverilog
if language == "systemverilog": if language == "systemverilog":
language = "verilog -sv" language = "verilog -sv"
@ -74,18 +110,18 @@ def _yosys_import_sources(platform):
language, includes, filename)) language, includes, filename))
return "\n".join(reads) return "\n".join(reads)
def _build_yosys(template, platform, nowidelut, abc9, build_name): def _build_yosys(self):
ys = [] ys = []
for l in template: for l in self._yosys_template:
ys.append(l.format( ys.append(l.format(
build_name = build_name, build_name = self._build_name,
nwl = "-nowidelut" if nowidelut else "", nwl = "-nowidelut" if self._nowidelut else "",
abc = "-abc9" if abc9 else "", abc = "-abc9" if self._abc9 else "",
read_files = _yosys_import_sources(platform) 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() device = device.lower()
family = device.split("-")[0] family = device.split("-")[0]
size = device.split("-")[1] 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}" "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"): if sys.platform in ("win32", "cygwin"):
script_ext = ".bat" script_ext = ".bat"
script_contents = "@echo off\nrem Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n\n" 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" script_contents = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\nset -e\n"
fail_stmt = "" 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. s_fail = s + "{fail_stmt}\n" # Required so Windows scripts fail early.
script_contents += s_fail.format( script_contents += s_fail.format(
build_name = build_name, build_name = self._build_name,
architecture = architecture, architecture = architecture,
package = package, package = package,
speed_grade = speed_grade, speed_grade = speed_grade,
timefailarg = "--timing-allow-fail" if not timingstrict else "", timefailarg = "--timing-allow-fail" if not self._timingstrict else "",
ignoreloops = "--ignore-loops" if ignoreloops else "", ignoreloops = "--ignore-loops" if self._ignoreloops else "",
bootaddr = bootaddr, bootaddr = self._bootaddr,
fail_stmt = fail_stmt, fail_stmt = fail_stmt,
seed = seed, seed = self._seed,
spimode = "" if spimode is None else "--spimode {}".format(spimode), spimode = "" if self._spimode is None else f"--spimode {self._spimode}",
compress = "" if not compress else "--compress") 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) tools.write_to_file(script_file, script_contents, force_unix=False)
return script_file return script_file
def _run_script(script): def run_script(script):
if sys.platform in ("win32", "cygwin"): if sys.platform in ("win32", "cygwin"):
shell = ["cmd", "/c"] shell = ["cmd", "/c"]
else: else:
@ -173,72 +215,6 @@ def _run_script(script):
if subprocess.call(shell + [script]) != 0: if subprocess.call(shell + [script]) != 0:
raise OSError("Error occured during Yosys/Nextpnr's script execution.") 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): def add_period_constraint(self, platform, clk, period):
platform.add_platform_command("""FREQUENCY PORT "{clk}" {freq} MHz;""".format( platform.add_platform_command("""FREQUENCY PORT "{clk}" {freq} MHz;""".format(
freq=str(float(1/period)*1000), clk="{clk}"), clk=clk) freq=str(float(1/period)*1000), clk="{clk}"), clk=clk)