build: gowin lattice/diamond lattice/oxide lattice/radiant microsemi quicklogic move to GenericToolchain

This commit is contained in:
Gwenhael Goavec-Merou 2022-06-26 21:40:56 +02:00
parent 1f9bf1bd06
commit 6541a6c93b
6 changed files with 849 additions and 1047 deletions

View File

@ -13,85 +13,25 @@ from shutil import which, copyfile
from migen.fhdl.structure import _Fragment
from litex.build.generic_toolchain import GenericToolchain
from litex.build.generic_platform import *
from litex.build import tools
# Constraints (.cst and .tcl) ----------------------------------------------------------------------
def _build_cst(named_sc, named_pc):
cst = []
flat_sc = []
for name, pins, other, resource in named_sc:
if len(pins) > 1:
for i, p in enumerate(pins):
flat_sc.append((f"{name}[{i}]", p, other))
else:
flat_sc.append((name, pins[0], other))
for name, pin, other in flat_sc:
if pin != "X":
cst.append(f"IO_LOC \"{name}\" {pin};")
for c in other:
if isinstance(c, IOStandard):
cst.append(f"IO_PORT \"{name}\" IO_TYPE={c.name};")
elif isinstance(c, Misc):
cst.append(f"IO_PORT \"{name}\" {c.misc};")
if named_pc:
cst.extend(named_pc)
with open("top.cst", "w") as f:
f.write("\n".join(cst))
def _build_sdc(clocks, vns):
sdc = []
for clk, period in sorted(clocks.items(), key=lambda x: x[0].duid):
sdc.append(f"create_clock -name {vns.get_name(clk)} -period {str(period)} [get_ports {{{vns.get_name(clk)}}}]")
with open("top.sdc", "w") as f:
f.write("\n".join(sdc))
# Script -------------------------------------------------------------------------------------------
def _build_tcl(name, partnumber, files, options):
tcl = []
# Set Device.
tcl.append(f"set_device -name {name} {partnumber}")
# Add IOs Constraints.
tcl.append("add_file top.cst")
# Add Timings Constraints.
tcl.append("add_file top.sdc")
# Add Sources.
for f, typ, lib in files:
# Support windows/powershell
if sys.platform == "win32":
f = f.replace("\\", "\\\\")
tcl.append(f"add_file {f}")
# Set Options.
for opt, val in options.items():
tcl.append(f"set_option -{opt} {val}")
# Run.
tcl.append("run all")
# Generate .tcl.
with open("run.tcl", "w") as f:
f.write("\n".join(tcl))
# GowinToolchain -----------------------------------------------------------------------------------
class GowinToolchain:
class GowinToolchain(GenericToolchain):
attr_translate = {}
def __init__(self):
super().__init__()
self.options = {}
self.clocks = dict()
def finalize(self):
if self.platform.verilog_include_paths:
self.options["include_path"] = "{" + ";".join(self.platform.verilog_include_paths) + "}"
self.apply_hyperram_integration_hack(self._build_name + ".v")
def apply_hyperram_integration_hack(self, v_file):
# FIXME: Gowin EDA expects a very specific HypeRAM integration pattern, modify generated verilog to match it.
@ -116,85 +56,101 @@ class GowinToolchain:
tools.replace_in_file(v_file, "[1:0] IO_psram_rwds,", "[1:0] IO_psram_rwds, /* synthesis syn_tristate = 1 */")
tools.replace_in_file(v_file, "[15:0] IO_psram_dq,", "[15:0] IO_psram_dq, /* synthesis syn_tristate = 1 */")
def build(self, platform, fragment,
build_dir = "build",
build_name = "top",
run = True,
**kwargs):
def build(self, platform, fragment, **kwargs):
return self._build(platform, fragment, **kwargs)
# Create build directory.
cwd = os.getcwd()
os.makedirs(build_dir, exist_ok=True)
os.chdir(build_dir)
# Constraints (.cst ) --------------------------------------------------------------------------
# Finalize design
if not isinstance(fragment, _Fragment):
fragment = fragment.get_fragment()
platform.finalize(fragment)
def build_io_constraints(self):
cst = []
# 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)
self.apply_hyperram_integration_hack(v_file)
flat_sc = []
for name, pins, other, resource in self.named_sc:
if len(pins) > 1:
for i, p in enumerate(pins):
flat_sc.append((f"{name}[{i}]", p, other))
else:
flat_sc.append((name, pins[0], other))
if platform.verilog_include_paths:
self.options["include_path"] = "{" + ";".join(platform.verilog_include_paths) + "}"
for name, pin, other in flat_sc:
if pin != "X":
cst.append(f"IO_LOC \"{name}\" {pin};")
# Generate constraints file.
# IOs (.cst).
_build_cst(
named_sc = named_sc,
named_pc = named_pc
for c in other:
if isinstance(c, IOStandard):
cst.append(f"IO_PORT \"{name}\" IO_TYPE={c.name};")
elif isinstance(c, Misc):
cst.append(f"IO_PORT \"{name}\" {c.misc};")
if self.named_pc:
cst.extend(self.named_pc)
tools.write_to_file("top.cst", "\n".join(cst))
return ("top.cst", "CST")
# Timing Constraints (.sdc ) -------------------------------------------------------------------
def build_timing_constraints(self, vns):
sdc = []
for clk, period in sorted(self.clocks.items(), key=lambda x: x[0].duid):
sdc.append(f"create_clock -name {vns.get_name(clk)} -period {str(period)} [get_ports {{{vns.get_name(clk)}}}]")
tools.write_to_file("top.sdc", "\n".join(sdc))
return ("top.sdc", "SDC")
# Project (tcl) --------------------------------------------------------------------------------
def build_project(self):
tcl = []
# Set Device.
tcl.append(f"set_device -name {self.platform.devicename} {self.platform.device}")
# Add IOs Constraints.
tcl.append("add_file top.cst")
# Add Timings Constraints.
tcl.append("add_file top.sdc")
# Add Sources.
for f, typ, lib in self.platform.sources:
# Support windows/powershell
if sys.platform == "win32":
f = f.replace("\\", "\\\\")
tcl.append(f"add_file {f}")
# Set Options.
for opt, val in self.options.items():
tcl.append(f"set_option -{opt} {val}")
# Run.
tcl.append("run all")
# Generate .tcl.
tools.write_to_file("run.tcl", "\n".join(tcl))
# Script ---------------------------------------------------------------------------------------
def build_script(self):
return "" # gw_sh use
def run_script(self, script):
# Support Powershell/WSL platform
# Some python distros for windows (e.g, oss-cad-suite)
# which does not have 'os.uname' support, we should check 'sys.platform' firstly.
gw_sh = "gw_sh"
if sys.platform.find("linux") >= 0:
if os.uname().release.find("WSL") > 0:
gw_sh += ".exe"
if which(gw_sh) is None:
msg = "Unable to find Gowin toolchain, please:\n"
msg += "- Add Gowin toolchain to your $PATH."
raise OSError(msg)
if subprocess.call([gw_sh, "run.tcl"]) != 0:
raise OSError("Error occured during Gowin's script execution.")
# Copy Bitstream to from impl to gateware directory.
copyfile(
os.path.join("impl", "pnr", "project.fs"),
os.path.join(build_name + ".fs")
)
# Timings (.sdc)
_build_sdc(
clocks = self.clocks,
vns = v_output.ns
)
# Generate build script (.tcl)
script = _build_tcl(
name = platform.devicename,
partnumber = platform.device,
files = platform.sources,
options = self.options)
# Run
if run:
# Support Powershell/WSL platform
# Some python distros for windows (e.g, oss-cad-suite)
# which does not have 'os.uname' support, we should check 'sys.platform' firstly.
gw_sh = "gw_sh"
if sys.platform.find("linux") >= 0:
if os.uname().release.find("WSL") > 0:
gw_sh += ".exe"
if which(gw_sh) is None:
msg = "Unable to find Gowin toolchain, please:\n"
msg += "- Add Gowin toolchain to your $PATH."
raise OSError(msg)
if subprocess.call([gw_sh, "run.tcl"]) != 0:
raise OSError("Error occured during Gowin's script execution.")
# Copy Bitstream to from impl to gateware directory.
copyfile(
os.path.join("impl", "pnr", "project.fs"),
os.path.join(build_name + ".fs")
)
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
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

@ -19,177 +19,14 @@ from migen.fhdl.structure import _Fragment
from litex.gen.fhdl.verilog import DummyAttrTranslate
from litex.build.generic_platform import *
from litex.build.generic_toolchain import GenericToolchain
from litex.build import tools
from litex.build.lattice import common
# Helpers ------------------------------------------------------------------------------------------
def _produces_jedec(device):
return device.startswith("LCMX")
# Constraints (.lpf) -------------------------------------------------------------------------------
def _format_constraint(c):
if isinstance(c, Pins):
return ("LOCATE COMP ", " SITE " + "\"" + c.identifiers[0] + "\"")
elif isinstance(c, IOStandard):
return ("IOBUF PORT ", " IO_TYPE=" + c.name)
elif isinstance(c, Misc):
return ("IOBUF PORT ", " " + c.misc)
def _format_lpf(signame, pin, others, resname):
fmt_c = [_format_constraint(c) for c in ([Pins(pin)] + others)]
lpf = []
for pre, suf in fmt_c:
lpf.append(pre + "\"" + signame + "\"" + suf + ";")
return "\n".join(lpf)
def _build_lpf(named_sc, named_pc, clocks, vns, build_name):
lpf = []
lpf.append("BLOCK RESETPATHS;")
lpf.append("BLOCK ASYNCPATHS;")
for sig, pins, others, resname in named_sc:
if len(pins) > 1:
for i, p in enumerate(pins):
lpf.append(_format_lpf(sig + "[" + str(i) + "]", p, others, resname))
else:
lpf.append(_format_lpf(sig, pins[0], others, resname))
if named_pc:
lpf.append("\n".join(named_pc))
# Note: .lpf is only used post-synthesis, Synplify constraints clocks by default to 200MHz.
for clk, period in clocks.items():
clk_name = vns.get_name(clk)
lpf.append("FREQUENCY {} \"{}\" {} MHz;".format(
"PORT" if clk_name in [name for name, _, _, _ in named_sc] else "NET",
clk_name,
str(1e3/period)))
tools.write_to_file(build_name + ".lpf", "\n".join(lpf))
# Project (.tcl) -----------------------------------------------------------------------------------
def _build_tcl(device, sources, vincpaths, build_name):
tcl = []
# Create project
tcl.append(" ".join([
"prj_project",
"new -name \"{}\"".format(build_name),
"-impl \"impl\"",
"-dev {}".format(device),
"-synthesis \"synplify\""
]))
def tcl_path(path): return path.replace("\\", "/")
# Add include paths
vincpath = ";".join(map(lambda x: tcl_path(x), vincpaths))
tcl.append("prj_impl option {include path} {\"" + vincpath + "\"}")
# Add sources
for filename, language, library, *copy in sources:
tcl.append("prj_src add \"{}\" -work {}".format(tcl_path(filename), library))
# Set top level
tcl.append("prj_impl option top \"{}\"".format(build_name))
# Save project
tcl.append("prj_project save")
# Build flow
tcl.append("prj_run Synthesis -impl impl -forceOne")
tcl.append("prj_run Translate -impl impl")
tcl.append("prj_run Map -impl impl")
tcl.append("prj_run PAR -impl impl")
tcl.append("prj_run Export -impl impl -task Bitgen")
if _produces_jedec(device):
tcl.append("prj_run Export -impl impl -task Jedecgen")
# Close project
tcl.append("prj_project close")
tools.write_to_file(build_name + ".tcl", "\n".join(tcl))
# Script -------------------------------------------------------------------------------------------
def _build_script(build_name, device):
on_windows = sys.platform in ("win32", "cygwin")
if on_windows:
script_ext = ".bat"
script_contents = "@echo off\nrem Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n\n"
copy_stmt = "copy"
fail_stmt = " || exit /b"
else:
script_ext = ".sh"
script_contents = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\nset -e\n"
copy_stmt = "cp"
fail_stmt = ""
script_contents += "{tool} {tcl_script}{fail_stmt}\n".format(
tool = "pnmainc" if on_windows else "diamondc",
tcl_script = build_name + ".tcl",
fail_stmt = fail_stmt)
for ext in (".bit", ".jed"):
if ext == ".jed" and not _produces_jedec(device):
continue
script_contents += "{copy_stmt} {diamond_product} {migen_product} {fail_stmt}\n".format(
copy_stmt = copy_stmt,
fail_stmt = fail_stmt,
diamond_product = os.path.join("impl", build_name + "_impl" + ext),
migen_product = build_name + ext)
build_script_file = "build_" + build_name + script_ext
tools.write_to_file(build_script_file, script_contents, force_unix=False)
return build_script_file
def _run_script(script):
on_windows = sys.platform in ("win32", "cygwin")
if on_windows:
shell = ["cmd", "/c"]
else:
shell = ["bash"]
if which("pnmainc" if on_windows else "diamondc") is None:
msg = "Unable to find Diamond toolchain, please:\n"
msg += "- Add Diamond toolchain to your $PATH.\n"
raise OSError(msg)
if subprocess.call(shell + [script]) != 0:
raise OSError("Error occured during Diamond's script execution.")
def _check_timing(build_name):
lines = open("impl/{}_impl.par".format(build_name), "r").readlines()
runs = [None, None]
for i in range(len(lines)-1):
if lines[i].startswith("Level/") and lines[i+1].startswith("Cost "):
runs[0] = i + 2
if lines[i].startswith("* : Design saved.") and runs[0] is not None:
runs[1] = i
break
assert all(map(lambda x: x is not None, runs))
p = re.compile(r"(^\s*\S+\s+\*?\s+[0-9]+\s+)(\S+)(\s+\S+\s+)(\S+)(\s+.*)")
for l in lines[runs[0]:runs[1]]:
m = p.match(l)
if m is None: continue
limit = 1e-8
setup = m.group(2)
hold = m.group(4)
# If there were no freq constraints in lpf, ratings will be dashed.
# results will likely be terribly unreliable, so bail
assert not setup == hold == "-", "No timing constraints were provided"
setup, hold = map(float, (setup, hold))
if setup > limit and hold > limit:
# At least one run met timing
# XXX is this necessarily the run from which outputs will be used?
return
raise Exception("Failed to meet timing")
# LatticeDiamondToolchain --------------------------------------------------------------------------
class LatticeDiamondToolchain:
class LatticeDiamondToolchain(GenericToolchain):
attr_translate = {
"keep": ("syn_keep", "true"),
"no_retiming": ("syn_no_retiming", "true"),
@ -198,63 +35,217 @@ class LatticeDiamondToolchain:
special_overrides = common.lattice_ecp5_special_overrides
def __init__(self):
self.clocks = {}
self.false_paths = set() # FIXME: use it
super().__init__()
def build(self, platform, fragment,
build_dir = "build",
build_name = "top",
run = True,
timingstrict = False,
**kwargs):
# Create build directory
os.makedirs(build_dir, exist_ok=True)
cwd = os.getcwd()
os.chdir(build_dir)
self._timinstrict = timingstrict
# Finalize design
if not isinstance(fragment, _Fragment):
fragment = fragment.get_fragment()
platform.finalize(fragment)
return self._build(platform, fragment, **kwargs)
# Generate verilog
v_output = platform.get_verilog(fragment, name=build_name, **kwargs)
named_sc, named_pc = platform.resolve_signals(v_output.ns)
v_file = build_name + ".v"
v_output.write(v_file)
platform.add_source(v_file)
## Create build directory
#os.makedirs(build_dir, exist_ok=True)
#cwd = os.getcwd()
#os.chdir(build_dir)
# Generate design constraints file (.lpf)
_build_lpf(named_sc, named_pc, self.clocks, v_output.ns, build_name)
## Finalize design
#if not isinstance(fragment, _Fragment):
# fragment = fragment.get_fragment()
#platform.finalize(fragment)
# Generate design script file (.tcl)
_build_tcl(platform.device, platform.sources, platform.verilog_include_paths, build_name)
## 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 build script
script = _build_script(build_name, platform.device)
## Generate design constraints file (.lpf)
#_build_lpf(named_sc, named_pc, self.clocks, v_output.ns, build_name)
# Run
if run:
_run_script(script)
if timingstrict:
_check_timing(build_name)
## Generate design script file (.tcl)
#_build_tcl(platform.device, platform.sources, platform.verilog_include_paths, build_name)
os.chdir(cwd)
## Generate build script
#script = _build_script(build_name, platform.device)
return v_output.ns
## Run
#if run:
# _run_script(script)
# if timingstrict:
# _check_timing(build_name)
def add_period_constraint(self, platform, clk, period):
clk.attr.add("keep")
period = math.floor(period*1e3)/1e3 # round to lowest picosecond
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
#os.chdir(cwd)
def add_false_path_constraint(self, platform, from_, to):
from_.attr.add("keep")
to.attr.add("keep")
if (to, from_) not in self.false_paths:
self.false_paths.add((from_, to))
#return v_output.ns
# Helpers --------------------------------------------------------------------------------------
@classmethod
def _produces_jedec(cls, device):
return device.startswith("LCMX")
# Constraints (.lpf) ---------------------------------------------------------------------------
@classmethod
def _format_constraint(cls, c):
if isinstance(c, Pins):
return ("LOCATE COMP ", " SITE " + "\"" + c.identifiers[0] + "\"")
elif isinstance(c, IOStandard):
return ("IOBUF PORT ", " IO_TYPE=" + c.name)
elif isinstance(c, Misc):
return ("IOBUF PORT ", " " + c.misc)
@classmethod
def _format_lpf(cls, signame, pin, others, resname):
fmt_c = [cls._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_io_constraints(self):
lpf = []
lpf.append("BLOCK RESETPATHS;")
lpf.append("BLOCK ASYNCPATHS;")
for sig, pins, others, resname in self.named_sc:
if len(pins) > 1:
for i, p in enumerate(pins):
lpf.append(self._format_lpf(sig + "[" + str(i) + "]", p, others, resname))
else:
lpf.append(self._format_lpf(sig, pins[0], others, resname))
if self.named_pc:
lpf.append("\n".join(self.named_pc))
# Note: .lpf is only used post-synthesis, Synplify constraints clocks by default to 200MHz.
for clk, period in self.clocks.items():
clk_name = self._vns.get_name(clk)
lpf.append("FREQUENCY {} \"{}\" {} MHz;".format(
"PORT" if clk_name in [name for name, _, _, _ in self.named_sc] else "NET",
clk_name,
str(1e3/period)))
tools.write_to_file(self._build_name + ".lpf", "\n".join(lpf))
# Project (.tcl) -------------------------------------------------------------------------------
def build_project(self):
tcl = []
# Create project
tcl.append(" ".join([
"prj_project",
"new -name \"{}\"".format(self._build_name),
"-impl \"impl\"",
"-dev {}".format(self.platform.device),
"-synthesis \"synplify\""
]))
def tcl_path(path): return path.replace("\\", "/")
# Add include paths
vincpath = ";".join(map(lambda x: tcl_path(x), self.platform.verilog_include_paths))
tcl.append("prj_impl option {include path} {\"" + vincpath + "\"}")
# Add sources
for filename, language, library, *copy in self.platform.sources:
tcl.append("prj_src add \"{}\" -work {}".format(tcl_path(filename), library))
# Set top level
tcl.append("prj_impl option top \"{}\"".format(self._build_name))
# Save project
tcl.append("prj_project save")
# Build flow
tcl.append("prj_run Synthesis -impl impl -forceOne")
tcl.append("prj_run Translate -impl impl")
tcl.append("prj_run Map -impl impl")
tcl.append("prj_run PAR -impl impl")
tcl.append("prj_run Export -impl impl -task Bitgen")
if self._produces_jedec(self.platform.device):
tcl.append("prj_run Export -impl impl -task Jedecgen")
# Close project
tcl.append("prj_project close")
tools.write_to_file(self._build_name + ".tcl", "\n".join(tcl))
# Script ---------------------------------------------------------------------------------------
def build_script(self):
on_windows = sys.platform in ("win32", "cygwin")
if on_windows:
script_ext = ".bat"
script_contents = "@echo off\nrem Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n\n"
copy_stmt = "copy"
fail_stmt = " || exit /b"
else:
script_ext = ".sh"
script_contents = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\nset -e\n"
copy_stmt = "cp"
fail_stmt = ""
script_contents += "{tool} {tcl_script}{fail_stmt}\n".format(
tool = "pnmainc" if on_windows else "diamondc",
tcl_script = self._build_name + ".tcl",
fail_stmt = fail_stmt)
for ext in (".bit", ".jed"):
if ext == ".jed" and not self._produces_jedec(self.platform.device):
continue
script_contents += "{copy_stmt} {diamond_product} {migen_product} {fail_stmt}\n".format(
copy_stmt = copy_stmt,
fail_stmt = fail_stmt,
diamond_product = os.path.join("impl", self._build_name + "_impl" + ext),
migen_product = self._build_name + ext)
build_script_file = "build_" + self._build_name + script_ext
tools.write_to_file(build_script_file, script_contents, force_unix=False)
return build_script_file
def run_script(self, script):
on_windows = sys.platform in ("win32", "cygwin")
if on_windows:
shell = ["cmd", "/c"]
else:
shell = ["bash"]
if which("pnmainc" if on_windows else "diamondc") is None:
msg = "Unable to find Diamond toolchain, please:\n"
msg += "- Add Diamond toolchain to your $PATH.\n"
raise OSError(msg)
if subprocess.call(shell + [script]) != 0:
raise OSError("Error occured during Diamond's script execution.")
if self.timingstrict:
self._check_timing()
def _check_timing(self):
lines = open("impl/{}_impl.par".format(self._build_name), "r").readlines()
runs = [None, None]
for i in range(len(lines)-1):
if lines[i].startswith("Level/") and lines[i+1].startswith("Cost "):
runs[0] = i + 2
if lines[i].startswith("* : Design saved.") and runs[0] is not None:
runs[1] = i
break
assert all(map(lambda x: x is not None, runs))
p = re.compile(r"(^\s*\S+\s+\*?\s+[0-9]+\s+)(\S+)(\s+\S+\s+)(\S+)(\s+.*)")
for l in lines[runs[0]:runs[1]]:
m = p.match(l)
if m is None: continue
limit = 1e-8
setup = m.group(2)
hold = m.group(4)
# If there were no freq constraints in lpf, ratings will be dashed.
# results will likely be terribly unreliable, so bail
assert not setup == hold == "-", "No timing constraints were provided"
setup, hold = map(float, (setup, hold))
if setup > limit and hold > limit:
# At least one run met timing
# XXX is this necessarily the run from which outputs will be used?
return
raise Exception("Failed to meet timing")

View File

@ -14,98 +14,17 @@ from shutil import which
from migen.fhdl.structure import _Fragment
from litex.build.generic_platform import *
from litex.build.generic_toolchain import GenericToolchain
from litex.build import tools
from litex.build.lattice import common
from litex.build.lattice.radiant import _format_constraint, _format_ldc, _build_pdc
import math
# Yosys/Nextpnr Helpers/Templates ------------------------------------------------------------------
_yosys_template = [
"verilog_defaults -push",
"verilog_defaults -add -defer",
"{read_files}",
"verilog_defaults -pop",
"attrmap -tocase keep -imap keep=\"true\" keep=1 -imap keep=\"false\" keep=0 -remove keep=0",
"synth_nexus -flatten {nwl} {abc} -json {build_name}.json -top {build_name}",
]
def _yosys_import_sources(platform):
includes = ""
reads = []
for path in platform.verilog_include_paths:
includes += " -I" + path
for filename, language, library, *copy in platform.sources:
# yosys has no such function read_systemverilog
if language == "systemverilog":
language = "verilog -sv"
reads.append("read_{}{} {}".format(
language, includes, filename))
return "\n".join(reads)
def _build_yosys(template, platform, nowidelut, abc9, build_name):
ys = []
for l in template:
ys.append(l.format(
build_name = build_name,
nwl = "-nowidelut" if nowidelut else "",
abc = "-abc9" if abc9 else "",
read_files = _yosys_import_sources(platform)
))
tools.write_to_file(build_name + ".ys", "\n".join(ys))
# Script -------------------------------------------------------------------------------------------
_build_template = [
"yosys -l {build_name}.rpt {build_name}.ys",
"nextpnr-nexus --json {build_name}.json --pdc {build_name}.pdc --fasm {build_name}.fasm \
--device {device} {timefailarg} {ignoreloops} --seed {seed}",
"prjoxide pack {build_name}.fasm {build_name}.bit"
]
def _build_script(source, build_template, build_name, device, timingstrict, ignoreloops, seed):
if sys.platform in ("win32", "cygwin"):
script_ext = ".bat"
script_contents = "@echo off\nrem Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n\n"
fail_stmt = " || exit /b"
else:
script_ext = ".sh"
script_contents = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\nset -e\n"
fail_stmt = ""
for s in build_template:
s_fail = s + "{fail_stmt}\n" # Required so Windows scripts fail early.
script_contents += s_fail.format(
build_name = build_name,
device = device,
timefailarg = "--timing-allow-fail" if not timingstrict else "",
ignoreloops = "--ignore-loops" if ignoreloops else "",
fail_stmt = fail_stmt,
seed = seed,
)
script_file = "build_" + build_name + script_ext
tools.write_to_file(script_file, script_contents, force_unix=False)
return script_file
def _run_script(script):
if sys.platform in ("win32", "cygwin"):
shell = ["cmd", "/c"]
else:
shell = ["bash"]
if which("yosys") is None or which("nextpnr-nexus") is None:
msg = "Unable to find Yosys/Nextpnr toolchain, please:\n"
msg += "- Add Yosys/Nextpnr toolchain to your $PATH."
raise OSError(msg)
if subprocess.call(shell + [script]) != 0:
raise OSError("Error occured during Yosys/Nextpnr's script execution.")
# LatticeOxideToolchain --------------------------------------------------------------------------
class LatticeOxideToolchain:
class LatticeOxideToolchain(GenericToolchain):
attr_translate = {
"keep": ("keep", "true"),
"syn_useioff": ("syn_useioff", 1),
@ -114,15 +33,11 @@ class LatticeOxideToolchain:
special_overrides = common.lattice_NX_special_overrides_for_oxide
def __init__(self):
self.yosys_template = _yosys_template
self.build_template = _build_template
self.clocks = {}
self.false_paths = set() # FIXME: use it
super().__init__()
self.yosys_template = self._yosys_template
self.build_template = self._build_template
def build(self, platform, fragment,
build_dir = "build",
build_name = "top",
run = True,
nowidelut = False,
abc9 = False,
timingstrict = False,
@ -131,62 +46,104 @@ class LatticeOxideToolchain:
es_device = False,
**kwargs):
# Create build directory
os.makedirs(build_dir, exist_ok=True)
cwd = os.getcwd()
os.chdir(build_dir)
self._nowidelut = nowidelut
self._abc9 = abc9
self._timingstrict = timingstrict
self._ignoreloops = ignoreloops
self._seed = seed
self._es_device = es_device
# Finalize design
if not isinstance(fragment, _Fragment):
fragment = fragment.get_fragment()
platform.finalize(fragment)
return self._build(platform, fragment, **kwargs)
# Generate verilog
v_output = platform.get_verilog(fragment, name=build_name, **kwargs)
named_sc, named_pc = platform.resolve_signals(v_output.ns)
top_file = build_name + ".v"
v_output.write(top_file)
platform.add_source(top_file)
# Constraints (.ldc) ---------------------------------------------------------------------------
# Generate design constraints file (.pdc)
_build_pdc(named_sc, named_pc, self.clocks, v_output.ns, build_name)
def build_io_constraints(self):
_build_pdc(self.named_sc, self.named_pc, self.clocks, self._vns, self._build_name)
return (self._build_name + ".pdc", "PDC")
# Generate Yosys script
_build_yosys(self.yosys_template, platform, nowidelut, abc9, build_name)
# Yosys/Nextpnr Helpers/Templates --------------------------------------------------------------
# N.B. Radiant does not allow a choice between ES1/production, this is determined
# solely by the installed Radiant version. nextpnr/oxide supports both, so we
# must choose what we are dealing with
device = platform.device
if es_device:
device += "ES"
_yosys_template = [
"verilog_defaults -push",
"verilog_defaults -add -defer",
"{read_files}",
"verilog_defaults -pop",
"attrmap -tocase keep -imap keep=\"true\" keep=1 -imap keep=\"false\" keep=0 -remove keep=0",
"synth_nexus -flatten {nwl} {abc} -json {build_name}.json -top {build_name}",
]
# Generate build script
script = _build_script(False, self.build_template, build_name, device,
timingstrict, ignoreloops, seed)
# Run
if run:
_run_script(script)
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)
os.chdir(cwd)
def build_project(self):
ys = []
for l in self.yosys_template:
ys.append(l.format(
build_name = self._build_name,
nwl = "-nowidelut" if self._nowidelut else "",
abc = "-abc9" if self._abc9 else "",
read_files = self._yosys_import_sources()
))
tools.write_to_file(self._build_name + ".ys", "\n".join(ys))
return v_output.ns
# Script ---------------------------------------------------------------------------------------
# N.B. these are currently ignored, but will be supported very soon
def add_period_constraint(self, platform, clk, period):
clk.attr.add("keep")
period = math.floor(period*1e3)/1e3 # round to lowest picosecond
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
_build_template = [
"yosys -l {build_name}.rpt {build_name}.ys",
"nextpnr-nexus --json {build_name}.json --pdc {build_name}.pdc --fasm {build_name}.fasm \
--device {device} {timefailarg} {ignoreloops} --seed {seed}",
"prjoxide pack {build_name}.fasm {build_name}.bit"
]
def build_script(self):
if sys.platform in ("win32", "cygwin"):
script_ext = ".bat"
script_contents = "@echo off\nrem Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n\n"
fail_stmt = " || exit /b"
else:
script_ext = ".sh"
script_contents = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\nset -e\n"
fail_stmt = ""
for s in self.build_template:
s_fail = s + "{fail_stmt}\n" # Required so Windows scripts fail early.
script_contents += s_fail.format(
build_name = self._build_name,
device = "ES" if self._es_device else self.platform.device,
timefailarg = "--timing-allow-fail" if not self._timingstrict else "",
ignoreloops = "--ignore-loops" if self._ignoreloops else "",
fail_stmt = fail_stmt,
seed = self._seed,
)
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(self, script):
if sys.platform in ("win32", "cygwin"):
shell = ["cmd", "/c"]
else:
shell = ["bash"]
if which("yosys") is None or which("nextpnr-nexus") is None:
msg = "Unable to find Yosys/Nextpnr toolchain, please:\n"
msg += "- Add Yosys/Nextpnr toolchain to your $PATH."
raise OSError(msg)
if subprocess.call(shell + [script]) != 0:
raise OSError("Error occured during Yosys/Nextpnr's script execution.")
def add_false_path_constraint(self, platform, from_, to):
from_.attr.add("keep")
to.attr.add("keep")
if (to, from_) not in self.false_paths:
self.false_paths.add((from_, to))
def oxide_args(parser):
toolchain_group = parser.add_argument_group(title="Toolchain options")

View File

@ -22,59 +22,9 @@ from litex.gen.fhdl.verilog import DummyAttrTranslate
from litex.build.generic_platform import *
from litex.build import tools
from litex.build.lattice import common
from litex.build.generic_toolchain import GenericToolchain
# Mixed Radiant+Yosys support
def _run_yosys(device, sources, vincpaths, build_name):
ys_contents = ""
incflags = ""
for path in vincpaths:
incflags += " -I" + path
for filename, language, library, *copy in sources:
assert language != "vhdl"
ys_contents += "read_{}{} {}\n".format(language, incflags, filename)
ys_contents += """\
hierarchy -top {build_name}
# Map keep to keep=1 for yosys
log
log XX. Converting (* keep = "xxxx" *) attribute for Yosys
log
attrmap -tocase keep -imap keep="true" keep=1 -imap keep="false" keep=0 -remove keep=0
select -list a:keep=1
# Add keep=1 for yosys to objects which have dont_touch="true" attribute.
log
log XX. Converting (* dont_touch = "true" *) attribute for Yosys
log
select -list a:dont_touch=true
setattr -set keep 1 a:dont_touch=true
# Convert (* async_reg = "true" *) to async registers for Yosys.
# (* async_reg = "true", dont_touch = "true" *) reg xilinxmultiregimpl0_regs1 = 1'd0;
log
log XX. Converting (* async_reg = "true" *) attribute to async registers for Yosys
log
select -list a:async_reg=true
setattr -set keep 1 a:async_reg=true
synth_nexus -top {build_name} -vm {build_name}_yosys.vm
""".format(build_name=build_name)
ys_name = build_name + ".ys"
tools.write_to_file(ys_name, ys_contents)
if which("yosys") is None:
msg = "Unable to find Yosys toolchain, please:\n"
msg += "- Add Yosys toolchain to your $PATH."
raise OSError(msg)
if subprocess.call(["yosys", ys_name]) != 0:
raise OSError("Subprocess failed")
# Constraints (.ldc) -------------------------------------------------------------------------------
# Required by oxide too (FIXME)
def _format_constraint(c):
if isinstance(c, Pins):
return ("ldc_set_location -site {" + c.identifiers[0] + "} [get_ports ","]")
@ -116,136 +66,10 @@ def _build_pdc(named_sc, named_pc, clocks, vns, build_name):
tools.write_to_file(build_name + ".pdc", "\n".join(pdc))
# Project (.tcl) -----------------------------------------------------------------------------------
def _build_tcl(device, sources, vincpaths, build_name, pdc_file, synth_mode):
tcl = []
# Create project
syn = "lse" if synth_mode == "lse" else "synplify"
tcl.append(" ".join([
"prj_create",
"-name \"{}\"".format(build_name),
"-impl \"impl\"",
"-dev {}".format(device),
"-synthesis \"" + syn + "\""
]))
def tcl_path(path): return path.replace("\\", "/")
# Add include paths
vincpath = ";".join(map(lambda x: tcl_path(x), vincpaths))
tcl.append("prj_set_impl_opt {include path} {\"" + vincpath + "\"}")
# Add sources
if synth_mode == "yosys":
# NOTE: it is seemingly impossible to skip synthesis using the Tcl flow
# so we give Synplify the structural netlist from Yosys which it won't actually touch
# The other option is to call the low level Radiant commands starting from 'map'
# with the structural netlist from Yosys, but this would be harder to do in a cross
# platform way.
tcl.append("prj_add_source \"{}_yosys.vm\" -work work".format(build_name))
library = "work"
else:
for filename, language, library, *copy in sources:
tcl.append("prj_add_source \"{}\" -work {}".format(tcl_path(filename), library))
tcl.append("prj_add_source \"{}\" -work {}".format(tcl_path(pdc_file), library))
# Set top level
tcl.append("prj_set_impl_opt top \"{}\"".format(build_name))
# Save project
tcl.append("prj_save")
# Build flow
tcl.append("prj_run Synthesis -impl impl -forceOne")
tcl.append("prj_run Map -impl impl")
tcl.append("prj_run PAR -impl impl")
tcl.append("prj_run Export -impl impl -task Bitgen")
# Close project
tcl.append("prj_close")
tools.write_to_file(build_name + ".tcl", "\n".join(tcl))
# Script -------------------------------------------------------------------------------------------
def _build_script(build_name, device):
if sys.platform in ("win32", "cygwin"):
tool = "pnmainc"
script_ext = ".bat"
script_contents = "@echo off\nrem Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n\n"
copy_stmt = "copy"
fail_stmt = " || exit /b"
else:
tool = "radiantc"
script_ext = ".sh"
script_contents = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\nset -e\n"
copy_stmt = "cp"
fail_stmt = ""
script_contents += "{tool} {tcl_script}{fail_stmt}\n".format(
tool = tool,
tcl_script = build_name + ".tcl",
fail_stmt = fail_stmt)
script_contents += "{copy_stmt} {radiant_product} {migen_product} {fail_stmt}\n".format(
copy_stmt = copy_stmt,
fail_stmt = fail_stmt,
radiant_product = os.path.join("impl", build_name + "_impl.bit"),
migen_product = build_name + ".bit")
build_script_file = "build_" + build_name + script_ext
tools.write_to_file(build_script_file, script_contents, force_unix=False)
return build_script_file
def _run_script(script):
if sys.platform in ("win32", "cygwin"):
shell = ["cmd", "/c"]
tool = "pnmainc"
else:
shell = ["bash"]
tool = "radiantc"
if which(tool) is None:
msg = "Unable to find Radiant toolchain, please:\n"
msg += "- Add Radiant toolchain to your $PATH."
raise OSError(msg)
if subprocess.call(shell + [script]) != 0:
raise OSError("Error occured during Radiant's script execution.")
def _check_timing(build_name):
lines = open("impl/{}_impl.par".format(build_name), "r").readlines()
runs = [None, None]
for i in range(len(lines)-1):
if lines[i].startswith("Level/") and lines[i+1].startswith("Cost "):
runs[0] = i + 2
if lines[i].startswith("* : Design saved.") and runs[0] is not None:
runs[1] = i
break
assert all(map(lambda x: x is not None, runs))
p = re.compile(r"(^\s*\S+\s+\*?\s+[0-9]+\s+)(\S+)(\s+\S+\s+)(\S+)(\s+.*)")
for l in lines[runs[0]:runs[1]]:
m = p.match(l)
if m is None: continue
limit = 1e-8
setup = m.group(2)
hold = m.group(4)
# If there were no freq constraints in ldc, ratings will be dashed.
# results will likely be terribly unreliable, so bail
assert not setup == hold == "-", "No timing constraints were provided"
setup, hold = map(float, (setup, hold))
if setup > limit and hold > limit:
# At least one run met timing
# XXX is this necessarily the run from which outputs will be used?
return
raise Exception("Failed to meet timing")
# LatticeRadiantToolchain --------------------------------------------------------------------------
class LatticeRadiantToolchain:
class LatticeRadiantToolchain(GenericToolchain):
attr_translate = {
"keep": ("syn_keep", "true"),
"no_retiming": ("syn_no_retiming", "true"),
@ -254,70 +78,210 @@ class LatticeRadiantToolchain:
special_overrides = common.lattice_NX_special_overrides
def __init__(self):
self.clocks = {}
self.false_paths = set() # FIXME: use it
super().__init__()
self._timingstrict = True
self._synth_mode = "radiant"
def build(self, platform, fragment,
build_dir = "build",
build_name = "top",
run = True,
timingstrict = True,
synth_mode = "radiant",
**kwargs):
# Create build directory
os.makedirs(build_dir, exist_ok=True)
cwd = os.getcwd()
os.chdir(build_dir)
self._timingstrict = timingstrict
self._synth_mode = synth_mode
# Finalize design
if not isinstance(fragment, _Fragment):
fragment = fragment.get_fragment()
platform.finalize(fragment)
return self._build(platform, fragment, **kwargs)
# Generate verilog
v_output = platform.get_verilog(fragment, name=build_name, **kwargs)
named_sc, named_pc = platform.resolve_signals(v_output.ns)
v_file = build_name + ".v"
v_output.write(v_file)
platform.add_source(v_file)
# Mixed Radiant+Yosys support ------------------------------------------------------------------
# Generate design constraints file (.pdc)
_build_pdc(named_sc, named_pc, self.clocks, v_output.ns, build_name)
pdc_file = build_dir + "\\" + build_name + ".pdc"
def _run_yosys(self):
ys_contents = ""
incflags = ""
for path in self.platform.verilog_include_paths:
incflags += " -I" + path
for filename, language, library, *copy in self.platform.sources:
assert language != "vhdl"
ys_contents += "read_{}{} {}\n".format(language, incflags, filename)
# Generate design script file (.tcl)
_build_tcl(platform.device, platform.sources, platform.verilog_include_paths, build_name, pdc_file, synth_mode)
ys_contents += """\
hierarchy -top {build_name}
# Generate build script
script = _build_script(build_name, platform.device)
# Map keep to keep=1 for yosys
log
log XX. Converting (* keep = "xxxx" *) attribute for Yosys
log
attrmap -tocase keep -imap keep="true" keep=1 -imap keep="false" keep=0 -remove keep=0
select -list a:keep=1
# Run
if run:
if synth_mode == "yosys":
_run_yosys(platform.device, platform.sources, platform.verilog_include_paths, build_name)
_run_script(script)
if timingstrict:
_check_timing(build_name)
# Add keep=1 for yosys to objects which have dont_touch="true" attribute.
log
log XX. Converting (* dont_touch = "true" *) attribute for Yosys
log
select -list a:dont_touch=true
setattr -set keep 1 a:dont_touch=true
os.chdir(cwd)
# Convert (* async_reg = "true" *) to async registers for Yosys.
# (* async_reg = "true", dont_touch = "true" *) reg xilinxmultiregimpl0_regs1 = 1'd0;
log
log XX. Converting (* async_reg = "true" *) attribute to async registers for Yosys
log
select -list a:async_reg=true
setattr -set keep 1 a:async_reg=true
return v_output.ns
synth_nexus -top {build_name} -vm {build_name}_yosys.vm
""".format(build_name=self._build_name)
def add_period_constraint(self, platform, clk, period):
clk.attr.add("keep")
period = math.floor(period*1e3)/1e3 # round to lowest picosecond
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
ys_name = self._build_name + ".ys"
tools.write_to_file(ys_name, ys_contents)
if which("yosys") is None:
msg = "Unable to find Yosys toolchain, please:\n"
msg += "- Add Yosys toolchain to your $PATH."
raise OSError(msg)
if subprocess.call(["yosys", ys_name]) != 0:
raise OSError("Subprocess failed")
# Constraints (.ldc) ---------------------------------------------------------------------------
def build_io_constraints(self):
_build_pdc(self.named_sc, self.named_pc, self.clocks, self._vns, self._build_name)
return (self._build_name + ".pdc", "PDC")
# Project (.tcl) -------------------------------------------------------------------------------
def build_project(self):
pdc_file = os.path.join(self._build_dir, self._build_name + ".pdc")
tcl = []
# Create project
syn = "lse" if self._synth_mode == "lse" else "synplify"
tcl.append(" ".join([
"prj_create",
"-name \"{}\"".format(self._build_name),
"-impl \"impl\"",
"-dev {}".format(self.platform.device),
"-synthesis \"" + syn + "\""
]))
def tcl_path(path): return path.replace("\\", "/")
# Add include paths
vincpath = ";".join(map(lambda x: tcl_path(x), self.platform.verilog_include_paths))
tcl.append("prj_set_impl_opt {include path} {\"" + vincpath + "\"}")
# Add sources
if self._synth_mode == "yosys":
# NOTE: it is seemingly impossible to skip synthesis using the Tcl flow
# so we give Synplify the structural netlist from Yosys which it won't actually touch
# The other option is to call the low level Radiant commands starting from 'map'
# with the structural netlist from Yosys, but this would be harder to do in a cross
# platform way.
tcl.append("prj_add_source \"{}_yosys.vm\" -work work".format(self._build_name))
library = "work"
else:
for filename, language, library, *copy in self.platform.sources:
tcl.append("prj_add_source \"{}\" -work {}".format(tcl_path(filename), library))
tcl.append("prj_add_source \"{}\" -work {}".format(tcl_path(pdc_file), library))
# Set top level
tcl.append("prj_set_impl_opt top \"{}\"".format(self._build_name))
# Save project
tcl.append("prj_save")
# Build flow
tcl.append("prj_run Synthesis -impl impl -forceOne")
tcl.append("prj_run Map -impl impl")
tcl.append("prj_run PAR -impl impl")
tcl.append("prj_run Export -impl impl -task Bitgen")
# Close project
tcl.append("prj_close")
tools.write_to_file(self._build_name + ".tcl", "\n".join(tcl))
# Script ---------------------------------------------------------------------------------------
def build_script(self):
if sys.platform in ("win32", "cygwin"):
tool = "pnmainc"
script_ext = ".bat"
script_contents = "@echo off\nrem Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n\n"
copy_stmt = "copy"
fail_stmt = " || exit /b"
else:
tool = "radiantc"
script_ext = ".sh"
script_contents = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\nset -e\n"
copy_stmt = "cp"
fail_stmt = ""
script_contents += "{tool} {tcl_script}{fail_stmt}\n".format(
tool = tool,
tcl_script = self._build_name + ".tcl",
fail_stmt = fail_stmt)
script_contents += "{copy_stmt} {radiant_product} {migen_product} {fail_stmt}\n".format(
copy_stmt = copy_stmt,
fail_stmt = fail_stmt,
radiant_product = os.path.join("impl", self._build_name + "_impl.bit"),
migen_product = self._build_name + ".bit")
build_script_file = "build_" + self._build_name + script_ext
tools.write_to_file(build_script_file, script_contents, force_unix=False)
return build_script_file
def run_script(self, script):
if synth_mode == "yosys":
self._run_yosys()
if sys.platform in ("win32", "cygwin"):
shell = ["cmd", "/c"]
tool = "pnmainc"
else:
shell = ["bash"]
tool = "radiantc"
if which(tool) is None:
msg = "Unable to find Radiant toolchain, please:\n"
msg += "- Add Radiant toolchain to your $PATH."
raise OSError(msg)
if subprocess.call(shell + [script]) != 0:
raise OSError("Error occured during Radiant's script execution.")
if timingstrict:
self._check_timing()
def _check_timing(self):
lines = open("impl/{}_impl.par".format(self._build_name), "r").readlines()
runs = [None, None]
for i in range(len(lines)-1):
if lines[i].startswith("Level/") and lines[i+1].startswith("Cost "):
runs[0] = i + 2
if lines[i].startswith("* : Design saved.") and runs[0] is not None:
runs[1] = i
break
assert all(map(lambda x: x is not None, runs))
p = re.compile(r"(^\s*\S+\s+\*?\s+[0-9]+\s+)(\S+)(\s+\S+\s+)(\S+)(\s+.*)")
for l in lines[runs[0]:runs[1]]:
m = p.match(l)
if m is None: continue
limit = 1e-8
setup = m.group(2)
hold = m.group(4)
# If there were no freq constraints in ldc, ratings will be dashed.
# results will likely be terribly unreliable, so bail
assert not setup == hold == "-", "No timing constraints were provided"
setup, hold = map(float, (setup, hold))
if setup > limit and hold > limit:
# At least one run met timing
# XXX is this necessarily the run from which outputs will be used?
return
raise Exception("Failed to meet timing")
def add_false_path_constraint(self, platform, from_, to):
from_.attr.add("keep")
to.attr.add("keep")
if (to, from_) not in self.false_paths:
self.false_paths.add((from_, to))
def radiant_build_args(parser):
toolchain_group = parser.add_argument_group(title="Toolchain options")

View File

@ -13,269 +13,233 @@ from migen.fhdl.structure import _Fragment
from litex.build.generic_platform import *
from litex.build import tools
from litex.build.generic_toolchain import GenericToolchain
from litex.build.microsemi import common
# Helpers ------------------------------------------------------------------------------------------
def tcl_name(name):
return "{" + name + "}"
# IO Constraints (.pdc) ----------------------------------------------------------------------------
def _format_io_constraint(c):
if isinstance(c, Pins):
return "-pin_name {} ".format(c.identifiers[0])
elif isinstance(c, IOStandard):
return "-io_std {} ".format(c.name)
elif isinstance(c, Misc):
return "-RES_PULL {} ".format(c.misc)
else:
raise NotImplementedError
def _format_io_pdc(signame, pin, others):
fmt_c = [_format_io_constraint(c) for c in ([Pins(pin)] + others)]
r = "set_io "
r += "-port_name {} ".format(tcl_name(signame))
for c in ([Pins(pin)] + others):
r += _format_io_constraint(c)
r += "-fixed true "
r += "\n"
return r
def _build_io_pdc(named_sc, named_pc, build_name, additional_io_constraints):
pdc = ""
for sig, pins, others, resname in named_sc:
if len(pins) > 1:
for i, p in enumerate(pins):
pdc += _format_io_pdc(sig + "[" + str(i) + "]", p, others)
else:
pdc += _format_io_pdc(sig, pins[0], others)
pdc += "\n".join(additional_io_constraints)
tools.write_to_file(build_name + "_io.pdc", pdc)
# Placement Constraints (.pdc) ---------------------------------------------------------------------
def _build_fp_pdc(build_name, additional_fp_constraints):
pdc = "\n".join(additional_fp_constraints)
tools.write_to_file(build_name + "_fp.pdc", pdc)
# Project (.tcl) -----------------------------------------------------------------------------------
def _build_tcl(platform, sources, build_dir, build_name):
tcl = []
# Create project
tcl.append(" ".join([
"new_project",
"-location {./impl}",
"-name {}".format(tcl_name(build_name)),
"-project_description {}",
"-block_mode 0",
"-standalone_peripheral_initialization 0",
"-instantiate_in_smartdesign 1",
"-ondemand_build_dh 0",
"-use_enhanced_constraint_flow 1",
"-hdl {VERILOG}",
"-family {PolarFire}",
"-die {}",
"-package {}",
"-speed {}",
"-die_voltage {}",
"-part_range {}",
"-adv_options {}"
]))
die, package, speed = platform.device.split("-")
tcl.append(" ".join([
"set_device",
"-family {PolarFire}",
"-die {}".format(tcl_name(die)),
"-package {}".format(tcl_name(package)),
"-speed {}".format(tcl_name("-" + speed)),
# FIXME: common to all PolarFire devices?
"-die_voltage {1.0}",
"-part_range {EXT}",
"-adv_options {IO_DEFT_STD:LVCMOS 1.8V}",
"-adv_options {RESTRICTPROBEPINS:1}",
"-adv_options {RESTRICTSPIPINS:0}",
"-adv_options {TEMPR:EXT}",
"-adv_options {UNUSED_MSS_IO_RESISTOR_PULL:None}",
"-adv_options {VCCI_1.2_VOLTR:EXT}",
"-adv_options {VCCI_1.5_VOLTR:EXT}",
"-adv_options {VCCI_1.8_VOLTR:EXT}",
"-adv_options {VCCI_2.5_VOLTR:EXT}",
"-adv_options {VCCI_3.3_VOLTR:EXT}",
"-adv_options {VOLTR:EXT} "
]))
# Add sources
for filename, language, library, *copy in sources:
filename_tcl = "{" + filename + "}"
tcl.append("import_files -hdl_source " + filename_tcl)
# Set top level
tcl.append("set_root -module {}".format(tcl_name(build_name)))
# Copy init files FIXME: support for include path on LiberoSoC?
for file in os.listdir(build_dir):
if file.endswith(".init"):
tcl.append("file copy -- {} impl/synthesis".format(file))
# Import io constraints
tcl.append("import_files -io_pdc {}".format(tcl_name(build_name + "_io.pdc")))
# Import floorplanner constraints
tcl.append("import_files -fp_pdc {}".format(tcl_name(build_name + "_fp.pdc")))
# Import timing constraints
tcl.append("import_files -convert_EDN_to_HDL 0 -sdc {}".format(tcl_name(build_name + ".sdc")))
# Associate constraints with tools
tcl.append(" ".join(["organize_tool_files",
"-tool {SYNTHESIZE}",
"-file impl/constraint/{}.sdc".format(build_name),
"-module {}".format(build_name),
"-input_type {constraint}"
]))
tcl.append(" ".join(["organize_tool_files",
"-tool {PLACEROUTE}",
"-file impl/constraint/io/{}_io.pdc".format(build_name),
"-file impl/constraint/fp/{}_fp.pdc".format(build_name),
"-file impl/constraint/{}.sdc".format(build_name),
"-module {}".format(build_name),
"-input_type {constraint}"
]))
tcl.append(" ".join(["organize_tool_files",
"-tool {VERIFYTIMING}",
"-file impl/constraint/{}.sdc".format(build_name),
"-module {}".format(build_name),
"-input_type {constraint}"
]))
# Build flow
tcl.append("run_tool -name {CONSTRAINT_MANAGEMENT}")
tcl.append("run_tool -name {SYNTHESIZE}")
tcl.append("run_tool -name {PLACEROUTE}")
tcl.append("run_tool -name {GENERATEPROGRAMMINGDATA}")
tcl.append("run_tool -name {GENERATEPROGRAMMINGFILE}")
# Generate tcl
tools.write_to_file(build_name + ".tcl", "\n".join(tcl))
# Timing Constraints (.sdc) ------------------------------------------------------------------------
def _build_timing_sdc(vns, clocks, false_paths, build_name, additional_timing_constraints):
sdc = []
for clk, period in sorted(clocks.items(), key=lambda x: x[0].duid):
sdc.append(
"create_clock -name {clk} -period " + str(period) +
" [get_nets {clk}]".format(clk=vns.get_name(clk)))
for from_, to in sorted(false_paths,
key=lambda x: (x[0].duid, x[1].duid)):
sdc.append(
"set_clock_groups "
"-group [get_clocks -include_generated_clocks -of [get_nets {from_}]] "
"-group [get_clocks -include_generated_clocks -of [get_nets {to}]] "
"-asynchronous".format(from_=from_, to=to))
# generate sdc
sdc += additional_timing_constraints
tools.write_to_file(build_name + ".sdc", "\n".join(sdc))
# Script -------------------------------------------------------------------------------------------
def _build_script(build_name, 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"
copy_stmt = "copy"
fail_stmt = " || exit /b"
else:
script_ext = ".sh"
script_contents = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n"
copy_stmt = "cp"
fail_stmt = " || exit 1"
script_file = "build_" + build_name + script_ext
tools.write_to_file(script_file, script_contents,
force_unix=False)
return script_file
def _run_script(script):
if sys.platform in ["win32", "cygwin"]:
shell = ["cmd", "/c"]
else:
shell = ["bash"]
if subprocess.call(shell + [script]) != 0:
raise OSError("Subprocess failed")
# MicrosemiLiberoSoCPolarfireToolchain -------------------------------------------------------------
class MicrosemiLiberoSoCPolarfireToolchain:
class MicrosemiLiberoSoCPolarfireToolchain(GenericToolchain):
attr_translate = {}
special_overrides = common.microsemi_polarfire_special_overrides
def __init__(self):
self.clocks = dict()
self.false_paths = set()
super().__init__()
self.additional_io_constraints = []
self.additional_fp_constraints = []
self.additional_timing_constraints = []
def build(self, platform, fragment,
build_dir = "build",
build_name = "top",
run = False,
**kwargs):
def build(self, platform, fragment, **kwargs):
# Create build directory
os.makedirs(build_dir, exist_ok=True)
cwd = os.getcwd()
os.chdir(build_dir)
return self._build(platform, fragment, **kwargs)
# Finalize design
if not isinstance(fragment, _Fragment):
fragment = fragment.get_fragment()
platform.finalize(fragment)
# Helpers --------------------------------------------------------------------------------------
# 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)
@classmethod
def tcl_name(cls, name):
return "{" + name + "}"
# Generate design script file (.tcl)
_build_tcl(platform, platform.sources, build_dir, build_name)
# IO Constraints (.pdc) ------------------------------------------------------------------------
# Generate design io constraints file (.pdc)
_build_io_pdc(named_sc, named_pc, build_name, self.additional_io_constraints)
@classmethod
def _format_io_constraint(cls, c):
if isinstance(c, Pins):
return "-pin_name {} ".format(c.identifiers[0])
elif isinstance(c, IOStandard):
return "-io_std {} ".format(c.name)
elif isinstance(c, Misc):
return "-RES_PULL {} ".format(c.misc)
else:
raise NotImplementedError
# Generate design placement constraints file (.pdc)
_build_fp_pdc(build_name, self.additional_fp_constraints)
@classmethod
def _format_io_pdc(cls, signame, pin, others):
fmt_c = [_format_io_constraint(c) for c in ([Pins(pin)] + others)]
r = "set_io "
r += "-port_name {} ".format(tcl_name(signame))
for c in ([Pins(pin)] + others):
r += _format_io_constraint(c)
r += "-fixed true "
r += "\n"
return r
# Generate design timing constraints file (.sdc)
_build_timing_sdc(v_output.ns, self.clocks, self.false_paths, build_name,
self.additional_timing_constraints)
def build_io_constraints(self):
pdc = ""
for sig, pins, others, resname in self.named_sc:
if len(pins) > 1:
for i, p in enumerate(pins):
pdc += self._format_io_pdc(sig + "[" + str(i) + "]", p, others)
else:
pdc += self._format_io_pdc(sig, pins[0], others)
pdc += "\n".join(self.additional_io_constraints)
tools.write_to_file(self._build_name + "_io.pdc", pdc)
return (self._build_name + "_io.pdc", "PDC")
# Generate build script
script = _build_script(build_name, platform.device)
# Placement Constraints (.pdc) -----------------------------------------------------------------
# Run
if run:
# Delete previous impl
if os.path.exists("impl"):
shutil.rmtree("impl")
_run_script(script)
def build_placement_constraints(self):
pdc = "\n".join(self.additional_fp_constraints)
tools.write_to_file(self._build_name + "_fp.pdc", pdc)
return (self._build_name + "_fp.pdc", "PDC")
os.chdir(cwd)
# Project (.tcl) -------------------------------------------------------------------------------
return v_output.ns
def build_project(self):
tcl = []
# Create project
tcl.append(" ".join([
"new_project",
"-location {./impl}",
"-name {}".format(tcl_name(self._build_name)),
"-project_description {}",
"-block_mode 0",
"-standalone_peripheral_initialization 0",
"-instantiate_in_smartdesign 1",
"-ondemand_build_dh 0",
"-use_enhanced_constraint_flow 1",
"-hdl {VERILOG}",
"-family {PolarFire}",
"-die {}",
"-package {}",
"-speed {}",
"-die_voltage {}",
"-part_range {}",
"-adv_options {}"
]))
die, package, speed = self.platform.device.split("-")
tcl.append(" ".join([
"set_device",
"-family {PolarFire}",
"-die {}".format(tcl_name(die)),
"-package {}".format(tcl_name(package)),
"-speed {}".format(tcl_name("-" + speed)),
# FIXME: common to all PolarFire devices?
"-die_voltage {1.0}",
"-part_range {EXT}",
"-adv_options {IO_DEFT_STD:LVCMOS 1.8V}",
"-adv_options {RESTRICTPROBEPINS:1}",
"-adv_options {RESTRICTSPIPINS:0}",
"-adv_options {TEMPR:EXT}",
"-adv_options {UNUSED_MSS_IO_RESISTOR_PULL:None}",
"-adv_options {VCCI_1.2_VOLTR:EXT}",
"-adv_options {VCCI_1.5_VOLTR:EXT}",
"-adv_options {VCCI_1.8_VOLTR:EXT}",
"-adv_options {VCCI_2.5_VOLTR:EXT}",
"-adv_options {VCCI_3.3_VOLTR:EXT}",
"-adv_options {VOLTR:EXT} "
]))
# Add sources
for filename, language, library, *copy in self.platform.sources:
filename_tcl = "{" + filename + "}"
tcl.append("import_files -hdl_source " + filename_tcl)
# Set top level
tcl.append("set_root -module {}".format(tcl_name(self._build_name)))
# Copy init files FIXME: support for include path on LiberoSoC?
for file in os.listdir(self._build_dir):
if file.endswith(".init"):
tcl.append("file copy -- {} impl/synthesis".format(file))
# Import io constraints
tcl.append("import_files -io_pdc {}".format(tcl_name(self._build_name + "_io.pdc")))
# Import floorplanner constraints
tcl.append("import_files -fp_pdc {}".format(tcl_name(self._build_name + "_fp.pdc")))
# Import timing constraints
tcl.append("import_files -convert_EDN_to_HDL 0 -sdc {}".format(tcl_name(self._build_name + ".sdc")))
# Associate constraints with tools
tcl.append(" ".join(["organize_tool_files",
"-tool {SYNTHESIZE}",
"-file impl/constraint/{}.sdc".format(self._build_name),
"-module {}".format(self._build_name),
"-input_type {constraint}"
]))
tcl.append(" ".join(["organize_tool_files",
"-tool {PLACEROUTE}",
"-file impl/constraint/io/{}_io.pdc".format(self._build_name),
"-file impl/constraint/fp/{}_fp.pdc".format(self._build_name),
"-file impl/constraint/{}.sdc".format(self._build_name),
"-module {}".format(self._build_name),
"-input_type {constraint}"
]))
tcl.append(" ".join(["organize_tool_files",
"-tool {VERIFYTIMING}",
"-file impl/constraint/{}.sdc".format(self._build_name),
"-module {}".format(self._build_name),
"-input_type {constraint}"
]))
# Build flow
tcl.append("run_tool -name {CONSTRAINT_MANAGEMENT}")
tcl.append("run_tool -name {SYNTHESIZE}")
tcl.append("run_tool -name {PLACEROUTE}")
tcl.append("run_tool -name {GENERATEPROGRAMMINGDATA}")
tcl.append("run_tool -name {GENERATEPROGRAMMINGFILE}")
# Generate tcl
tools.write_to_file(self._build_name + ".tcl", "\n".join(tcl))
return self._build_name + ".tcl"
# Timing Constraints (.sdc) --------------------------------------------------------------------
def build_timing_constraints(self, vns):
sdc = []
for clk, period in sorted(self.clocks.items(), key=lambda x: x[0].duid):
sdc.append(
"create_clock -name {clk} -period " + str(period) +
" [get_nets {clk}]".format(clk=vns.get_name(clk)))
for from_, to in sorted(self.false_paths,
key=lambda x: (x[0].duid, x[1].duid)):
sdc.append(
"set_clock_groups "
"-group [get_clocks -include_generated_clocks -of [get_nets {from_}]] "
"-group [get_clocks -include_generated_clocks -of [get_nets {to}]] "
"-asynchronous".format(from_=from_, to=to))
# generate sdc
sdc += self.additional_timing_constraints
tools.write_to_file(self._build_name + ".sdc", "\n".join(sdc))
return (self._build_name + ".sdc", "SDC")
# Script ---------------------------------------------------------------------------------------
def build_script(self):
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"
copy_stmt = "copy"
fail_stmt = " || exit /b"
else:
script_ext = ".sh"
script_contents = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n"
copy_stmt = "cp"
fail_stmt = " || exit 1"
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(self.script):
# Delete previous impl
if os.path.exists("impl"):
shutil.rmtree("impl")
if sys.platform in ["win32", "cygwin"]:
shell = ["cmd", "/c"]
else:
shell = ["bash"]
if subprocess.call(shell + [script]) != 0:
raise OSError("Subprocess failed")
def add_period_constraint(self, platform, clk, period):
if clk in self.clocks:

View File

@ -13,115 +13,85 @@ from shutil import which
from migen.fhdl.structure import _Fragment
from litex.build.generic_platform import *
from litex.build.generic_toolchain import GenericToolchain
from litex.build import tools
from litex.build.quicklogic import common
# IO Constraints (.pcf) ----------------------------------------------------------------------------
def _format_io_pcf(signame, pin, others):
r = f"set_io {signame} {Pins(pin).identifiers[0]}\n"
return r
def _build_io_pcf(named_sc, named_pc, build_name):
pcf = ""
for sig, pins, others, resname in named_sc:
if len(pins) > 1:
for i, p in enumerate(pins):
pcf += _format_io_pcf(sig + "(" + str(i) + ")", p, others)
else:
pcf += _format_io_pcf(sig, pins[0], others)
tools.write_to_file(build_name + ".pcf", pcf)
# Build Makefile -----------------------------------------------------------------------------------
def _build_makefile(platform, sources, build_dir, build_name):
makefile = []
# Define Paths.
makefile.append("mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))")
makefile.append("current_dir := $(patsubst %/,%,$(dir $(mkfile_path)))")
# bit -> h and bit -> bin requires TOP_F
makefile.append(f"TOP_F={build_name}")
# Create Project.
# FIXME: Only use top file for now and ignore .init files.
makefile.append("all: {top}_bit.h {top}.bin build/{top}.bit".format(top=build_name))
# build bit file (default)
makefile.append(f"build/{build_name}.bit:")
makefile.append("\tql_symbiflow -compile -d {device} -P {part} -v {verilog} -t {top} -p {pcf}".format(
device = platform.device,
part = {"ql-eos-s3": "PU64"}.get(platform.device),
verilog = f"{build_name}.v",
top = build_name,
pcf = f"{build_name}.pcf"
))
# build header to include in CPU firmware
makefile.append("{top}_bit.h: build/{top}.bit".format(top=build_name))
makefile.append(f"\t(cd build; TOP_F=$(TOP_F) symbiflow_write_bitheader)")
# build binary to write in dedicated FLASH area
makefile.append("{top}.bin: build/{top}.bit".format(top=build_name))
makefile.append(f"\t(cd build; TOP_F=$(TOP_F) symbiflow_write_binary)")
# Generate Makefile.
tools.write_to_file("Makefile", "\n".join(makefile))
def _run_make():
make_cmd = ["make", "-j1"]
if which("ql_symbiflow") is None:
msg = "Unable to find QuickLogic Symbiflow toolchain, please:\n"
msg += "- Add QuickLogic Symbiflow toolchain to your $PATH."
raise OSError(msg)
if subprocess.call(make_cmd) != 0:
raise OSError("Error occured during QuickLogic Symbiflow's script execution.")
# F4PGAToolchain -------------------------------------------------------------------------------
# Formerly SymbiflowToolchain, Symbiflow has been renamed to F4PGA -----------------------------
class F4PGAToolchain:
class F4PGAToolchain(GenericToolchain):
attr_translate = {}
special_overrides = common.quicklogic_special_overrides
def __init__(self):
self.clocks = dict()
self.false_paths = set()
super().__init__()
def build(self, platform, fragment,
build_dir = "build",
build_name = "top",
run = False,
**kwargs):
def build(self, platform, fragment, **kwargs):
return self._build(platform, fragment, **kwargs)
# Create build directory.
os.makedirs(build_dir, exist_ok=True)
cwd = os.getcwd()
os.chdir(build_dir)
# IO Constraints (.pcf) ------------------------------------------------------------------------
# Finalize design.
if not isinstance(fragment, _Fragment):
fragment = fragment.get_fragment()
platform.finalize(fragment)
@classmethod
def _format_io_pcf(cls, signame, pin, others):
r = f"set_io {signame} {Pins(pin).identifiers[0]}\n"
return r
# 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)
def build_io_constraints(self):
pcf = ""
for sig, pins, others, resname in self.named_sc:
if len(pins) > 1:
for i, p in enumerate(pins):
pcf += self._format_io_pcf(sig + "(" + str(i) + ")", p, others)
else:
pcf += self._format_io_pcf(sig, pins[0], others)
tools.write_to_file(self._build_name + ".pcf", pcf)
return (self._build_name + ".pcf", "PCF")
# Generate .pcf IO constraints file.
_build_io_pcf(named_sc, named_pc, build_name)
# Build Makefile -------------------------------------------------------------------------------
# Generate Makefie.
_build_makefile(platform, platform.sources, build_dir, build_name)
def build_script(self):
makefile = []
# Run.
if run:
_run_make()
# Define Paths.
makefile.append("mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))")
makefile.append("current_dir := $(patsubst %/,%,$(dir $(mkfile_path)))")
# bit -> h and bit -> bin requires TOP_F
makefile.append(f"TOP_F={self._build_name}")
os.chdir(cwd)
# Create Project.
# FIXME: Only use top file for now and ignore .init files.
makefile.append("all: {top}_bit.h {top}.bin build/{top}.bit".format(top=self._build_name))
# build bit file (default)
makefile.append(f"build/{self._build_name}.bit:")
makefile.append("\tql_symbiflow -compile -d {device} -P {part} -v {verilog} -t {top} -p {pcf}".format(
device = self.platform.device,
part = {"ql-eos-s3": "PU64"}.get(self.platform.device),
verilog = f"{self._build_name}.v",
top = self._build_name,
pcf = f"{self._build_name}.pcf"
))
# build header to include in CPU firmware
makefile.append("{top}_bit.h: build/{top}.bit".format(top=self._build_name))
makefile.append(f"\t(cd build; TOP_F=$(TOP_F) symbiflow_write_bitheader)")
# build binary to write in dedicated FLASH area
makefile.append("{top}.bin: build/{top}.bit".format(top=self._build_name))
makefile.append(f"\t(cd build; TOP_F=$(TOP_F) symbiflow_write_binary)")
return v_output.ns
# Generate Makefile.
tools.write_to_file("Makefile", "\n".join(makefile))
return "Makefile"
def run_script(self, script):
make_cmd = ["make", "-j1"]
if which("ql_symbiflow") is None:
msg = "Unable to find QuickLogic Symbiflow toolchain, please:\n"
msg += "- Add QuickLogic Symbiflow toolchain to your $PATH."
raise OSError(msg)
if subprocess.call(make_cmd) != 0:
raise OSError("Error occured during QuickLogic Symbiflow's script execution.")