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 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
|
||||||
|
|
|
@ -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.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")
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue