toolchain: implement generic toolchain and devices toolchain refactoring
This commit is contained in:
parent
ee1af96ab7
commit
9db1d9e49f
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue