build: gowin lattice/diamond lattice/oxide lattice/radiant microsemi quicklogic move to GenericToolchain
This commit is contained in:
parent
1f9bf1bd06
commit
6541a6c93b
|
@ -13,85 +13,25 @@ from shutil import which, copyfile
|
||||||
|
|
||||||
from migen.fhdl.structure import _Fragment
|
from migen.fhdl.structure import _Fragment
|
||||||
|
|
||||||
|
from litex.build.generic_toolchain import GenericToolchain
|
||||||
from litex.build.generic_platform import *
|
from litex.build.generic_platform import *
|
||||||
from litex.build import tools
|
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 -----------------------------------------------------------------------------------
|
# GowinToolchain -----------------------------------------------------------------------------------
|
||||||
|
|
||||||
class GowinToolchain:
|
class GowinToolchain(GenericToolchain):
|
||||||
attr_translate = {}
|
attr_translate = {}
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
self.options = {}
|
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):
|
def apply_hyperram_integration_hack(self, v_file):
|
||||||
# FIXME: Gowin EDA expects a very specific HypeRAM integration pattern, modify generated verilog to match it.
|
# FIXME: Gowin EDA expects a very specific HypeRAM integration pattern, modify generated verilog to match it.
|
||||||
|
@ -116,55 +56,84 @@ 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, "[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 */")
|
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,
|
def build(self, platform, fragment, **kwargs):
|
||||||
build_dir = "build",
|
return self._build(platform, fragment, **kwargs)
|
||||||
build_name = "top",
|
|
||||||
run = True,
|
|
||||||
**kwargs):
|
|
||||||
|
|
||||||
# Create build directory.
|
# Constraints (.cst ) --------------------------------------------------------------------------
|
||||||
cwd = os.getcwd()
|
|
||||||
os.makedirs(build_dir, exist_ok=True)
|
|
||||||
os.chdir(build_dir)
|
|
||||||
|
|
||||||
# Finalize design
|
def build_io_constraints(self):
|
||||||
if not isinstance(fragment, _Fragment):
|
cst = []
|
||||||
fragment = fragment.get_fragment()
|
|
||||||
platform.finalize(fragment)
|
|
||||||
|
|
||||||
# Generate verilog
|
flat_sc = []
|
||||||
v_output = platform.get_verilog(fragment, name=build_name, **kwargs)
|
for name, pins, other, resource in self.named_sc:
|
||||||
named_sc, named_pc = platform.resolve_signals(v_output.ns)
|
if len(pins) > 1:
|
||||||
v_file = build_name + ".v"
|
for i, p in enumerate(pins):
|
||||||
v_output.write(v_file)
|
flat_sc.append((f"{name}[{i}]", p, other))
|
||||||
platform.add_source(v_file)
|
else:
|
||||||
self.apply_hyperram_integration_hack(v_file)
|
flat_sc.append((name, pins[0], other))
|
||||||
|
|
||||||
if platform.verilog_include_paths:
|
for name, pin, other in flat_sc:
|
||||||
self.options["include_path"] = "{" + ";".join(platform.verilog_include_paths) + "}"
|
if pin != "X":
|
||||||
|
cst.append(f"IO_LOC \"{name}\" {pin};")
|
||||||
|
|
||||||
# Generate constraints file.
|
for c in other:
|
||||||
# IOs (.cst).
|
if isinstance(c, IOStandard):
|
||||||
_build_cst(
|
cst.append(f"IO_PORT \"{name}\" IO_TYPE={c.name};")
|
||||||
named_sc = named_sc,
|
elif isinstance(c, Misc):
|
||||||
named_pc = named_pc
|
cst.append(f"IO_PORT \"{name}\" {c.misc};")
|
||||||
)
|
|
||||||
|
|
||||||
# Timings (.sdc)
|
if self.named_pc:
|
||||||
_build_sdc(
|
cst.extend(self.named_pc)
|
||||||
clocks = self.clocks,
|
|
||||||
vns = v_output.ns
|
|
||||||
)
|
|
||||||
|
|
||||||
# Generate build script (.tcl)
|
tools.write_to_file("top.cst", "\n".join(cst))
|
||||||
script = _build_tcl(
|
return ("top.cst", "CST")
|
||||||
name = platform.devicename,
|
|
||||||
partnumber = platform.device,
|
|
||||||
files = platform.sources,
|
|
||||||
options = self.options)
|
|
||||||
|
|
||||||
# Run
|
# Timing Constraints (.sdc ) -------------------------------------------------------------------
|
||||||
if run:
|
|
||||||
|
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
|
# Support Powershell/WSL platform
|
||||||
# Some python distros for windows (e.g, oss-cad-suite)
|
# Some python distros for windows (e.g, oss-cad-suite)
|
||||||
# which does not have 'os.uname' support, we should check 'sys.platform' firstly.
|
# which does not have 'os.uname' support, we should check 'sys.platform' firstly.
|
||||||
|
@ -185,16 +154,3 @@ class GowinToolchain:
|
||||||
os.path.join("impl", "pnr", "project.fs"),
|
os.path.join("impl", "pnr", "project.fs"),
|
||||||
os.path.join(build_name + ".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
|
|
||||||
|
|
|
@ -19,17 +19,78 @@ from migen.fhdl.structure import _Fragment
|
||||||
from litex.gen.fhdl.verilog import DummyAttrTranslate
|
from litex.gen.fhdl.verilog import DummyAttrTranslate
|
||||||
|
|
||||||
from litex.build.generic_platform import *
|
from litex.build.generic_platform import *
|
||||||
|
from litex.build.generic_toolchain import GenericToolchain
|
||||||
from litex.build import tools
|
from litex.build import tools
|
||||||
from litex.build.lattice import common
|
from litex.build.lattice import common
|
||||||
|
|
||||||
# Helpers ------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def _produces_jedec(device):
|
# LatticeDiamondToolchain --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class LatticeDiamondToolchain(GenericToolchain):
|
||||||
|
attr_translate = {
|
||||||
|
"keep": ("syn_keep", "true"),
|
||||||
|
"no_retiming": ("syn_no_retiming", "true"),
|
||||||
|
}
|
||||||
|
|
||||||
|
special_overrides = common.lattice_ecp5_special_overrides
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def build(self, platform, fragment,
|
||||||
|
timingstrict = False,
|
||||||
|
**kwargs):
|
||||||
|
|
||||||
|
self._timinstrict = timingstrict
|
||||||
|
|
||||||
|
return self._build(platform, fragment, **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 constraints file (.lpf)
|
||||||
|
#_build_lpf(named_sc, named_pc, self.clocks, v_output.ns, build_name)
|
||||||
|
|
||||||
|
## Generate design script file (.tcl)
|
||||||
|
#_build_tcl(platform.device, platform.sources, platform.verilog_include_paths, build_name)
|
||||||
|
|
||||||
|
## Generate build script
|
||||||
|
#script = _build_script(build_name, platform.device)
|
||||||
|
|
||||||
|
## Run
|
||||||
|
#if run:
|
||||||
|
# _run_script(script)
|
||||||
|
# if timingstrict:
|
||||||
|
# _check_timing(build_name)
|
||||||
|
|
||||||
|
#os.chdir(cwd)
|
||||||
|
|
||||||
|
#return v_output.ns
|
||||||
|
|
||||||
|
# Helpers --------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _produces_jedec(cls, device):
|
||||||
return device.startswith("LCMX")
|
return device.startswith("LCMX")
|
||||||
|
|
||||||
# Constraints (.lpf) -------------------------------------------------------------------------------
|
# 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):
|
||||||
|
@ -37,63 +98,62 @@ def _format_constraint(c):
|
||||||
elif isinstance(c, Misc):
|
elif isinstance(c, Misc):
|
||||||
return ("IOBUF PORT ", " " + c.misc)
|
return ("IOBUF PORT ", " " + c.misc)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
def _format_lpf(signame, pin, others, resname):
|
def _format_lpf(cls, signame, pin, others, resname):
|
||||||
fmt_c = [_format_constraint(c) for c in ([Pins(pin)] + others)]
|
fmt_c = [cls._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_io_constraints(self):
|
||||||
def _build_lpf(named_sc, named_pc, clocks, vns, 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 self.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 self.named_pc:
|
||||||
lpf.append("\n".join(named_pc))
|
lpf.append("\n".join(self.named_pc))
|
||||||
|
|
||||||
# Note: .lpf is only used post-synthesis, Synplify constraints clocks by default to 200MHz.
|
# Note: .lpf is only used post-synthesis, Synplify constraints clocks by default to 200MHz.
|
||||||
for clk, period in clocks.items():
|
for clk, period in self.clocks.items():
|
||||||
clk_name = vns.get_name(clk)
|
clk_name = self._vns.get_name(clk)
|
||||||
lpf.append("FREQUENCY {} \"{}\" {} MHz;".format(
|
lpf.append("FREQUENCY {} \"{}\" {} MHz;".format(
|
||||||
"PORT" if clk_name in [name for name, _, _, _ in named_sc] else "NET",
|
"PORT" if clk_name in [name for name, _, _, _ in self.named_sc] else "NET",
|
||||||
clk_name,
|
clk_name,
|
||||||
str(1e3/period)))
|
str(1e3/period)))
|
||||||
|
|
||||||
tools.write_to_file(build_name + ".lpf", "\n".join(lpf))
|
tools.write_to_file(self._build_name + ".lpf", "\n".join(lpf))
|
||||||
|
|
||||||
# Project (.tcl) -----------------------------------------------------------------------------------
|
# Project (.tcl) -------------------------------------------------------------------------------
|
||||||
|
|
||||||
def _build_tcl(device, sources, vincpaths, build_name):
|
def build_project(self):
|
||||||
tcl = []
|
tcl = []
|
||||||
# Create project
|
# Create project
|
||||||
tcl.append(" ".join([
|
tcl.append(" ".join([
|
||||||
"prj_project",
|
"prj_project",
|
||||||
"new -name \"{}\"".format(build_name),
|
"new -name \"{}\"".format(self._build_name),
|
||||||
"-impl \"impl\"",
|
"-impl \"impl\"",
|
||||||
"-dev {}".format(device),
|
"-dev {}".format(self.platform.device),
|
||||||
"-synthesis \"synplify\""
|
"-synthesis \"synplify\""
|
||||||
]))
|
]))
|
||||||
|
|
||||||
def tcl_path(path): return path.replace("\\", "/")
|
def tcl_path(path): return path.replace("\\", "/")
|
||||||
|
|
||||||
# Add include paths
|
# Add include paths
|
||||||
vincpath = ";".join(map(lambda x: tcl_path(x), vincpaths))
|
vincpath = ";".join(map(lambda x: tcl_path(x), self.platform.verilog_include_paths))
|
||||||
tcl.append("prj_impl option {include path} {\"" + vincpath + "\"}")
|
tcl.append("prj_impl option {include path} {\"" + vincpath + "\"}")
|
||||||
|
|
||||||
# Add sources
|
# Add sources
|
||||||
for filename, language, library, *copy in sources:
|
for filename, language, library, *copy in self.platform.sources:
|
||||||
tcl.append("prj_src add \"{}\" -work {}".format(tcl_path(filename), library))
|
tcl.append("prj_src add \"{}\" -work {}".format(tcl_path(filename), library))
|
||||||
|
|
||||||
# Set top level
|
# Set top level
|
||||||
tcl.append("prj_impl option top \"{}\"".format(build_name))
|
tcl.append("prj_impl option top \"{}\"".format(self._build_name))
|
||||||
|
|
||||||
# Save project
|
# Save project
|
||||||
tcl.append("prj_project save")
|
tcl.append("prj_project save")
|
||||||
|
@ -104,17 +164,17 @@ def _build_tcl(device, sources, vincpaths, build_name):
|
||||||
tcl.append("prj_run Map -impl impl")
|
tcl.append("prj_run Map -impl impl")
|
||||||
tcl.append("prj_run PAR -impl impl")
|
tcl.append("prj_run PAR -impl impl")
|
||||||
tcl.append("prj_run Export -impl impl -task Bitgen")
|
tcl.append("prj_run Export -impl impl -task Bitgen")
|
||||||
if _produces_jedec(device):
|
if self._produces_jedec(self.platform.device):
|
||||||
tcl.append("prj_run Export -impl impl -task Jedecgen")
|
tcl.append("prj_run Export -impl impl -task Jedecgen")
|
||||||
|
|
||||||
# Close project
|
# Close project
|
||||||
tcl.append("prj_project close")
|
tcl.append("prj_project close")
|
||||||
|
|
||||||
tools.write_to_file(build_name + ".tcl", "\n".join(tcl))
|
tools.write_to_file(self._build_name + ".tcl", "\n".join(tcl))
|
||||||
|
|
||||||
# Script -------------------------------------------------------------------------------------------
|
# Script ---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
def _build_script(build_name, device):
|
def build_script(self):
|
||||||
on_windows = sys.platform in ("win32", "cygwin")
|
on_windows = sys.platform in ("win32", "cygwin")
|
||||||
if on_windows:
|
if on_windows:
|
||||||
script_ext = ".bat"
|
script_ext = ".bat"
|
||||||
|
@ -129,22 +189,22 @@ def _build_script(build_name, device):
|
||||||
|
|
||||||
script_contents += "{tool} {tcl_script}{fail_stmt}\n".format(
|
script_contents += "{tool} {tcl_script}{fail_stmt}\n".format(
|
||||||
tool = "pnmainc" if on_windows else "diamondc",
|
tool = "pnmainc" if on_windows else "diamondc",
|
||||||
tcl_script = build_name + ".tcl",
|
tcl_script = self._build_name + ".tcl",
|
||||||
fail_stmt = fail_stmt)
|
fail_stmt = fail_stmt)
|
||||||
for ext in (".bit", ".jed"):
|
for ext in (".bit", ".jed"):
|
||||||
if ext == ".jed" and not _produces_jedec(device):
|
if ext == ".jed" and not self._produces_jedec(self.platform.device):
|
||||||
continue
|
continue
|
||||||
script_contents += "{copy_stmt} {diamond_product} {migen_product} {fail_stmt}\n".format(
|
script_contents += "{copy_stmt} {diamond_product} {migen_product} {fail_stmt}\n".format(
|
||||||
copy_stmt = copy_stmt,
|
copy_stmt = copy_stmt,
|
||||||
fail_stmt = fail_stmt,
|
fail_stmt = fail_stmt,
|
||||||
diamond_product = os.path.join("impl", build_name + "_impl" + ext),
|
diamond_product = os.path.join("impl", self._build_name + "_impl" + ext),
|
||||||
migen_product = build_name + ext)
|
migen_product = self._build_name + ext)
|
||||||
|
|
||||||
build_script_file = "build_" + build_name + script_ext
|
build_script_file = "build_" + self._build_name + script_ext
|
||||||
tools.write_to_file(build_script_file, script_contents, force_unix=False)
|
tools.write_to_file(build_script_file, script_contents, force_unix=False)
|
||||||
return build_script_file
|
return build_script_file
|
||||||
|
|
||||||
def _run_script(script):
|
def run_script(self, script):
|
||||||
on_windows = sys.platform in ("win32", "cygwin")
|
on_windows = sys.platform in ("win32", "cygwin")
|
||||||
if on_windows:
|
if on_windows:
|
||||||
shell = ["cmd", "/c"]
|
shell = ["cmd", "/c"]
|
||||||
|
@ -159,8 +219,11 @@ def _run_script(script):
|
||||||
if subprocess.call(shell + [script]) != 0:
|
if subprocess.call(shell + [script]) != 0:
|
||||||
raise OSError("Error occured during Diamond's script execution.")
|
raise OSError("Error occured during Diamond's script execution.")
|
||||||
|
|
||||||
def _check_timing(build_name):
|
if self.timingstrict:
|
||||||
lines = open("impl/{}_impl.par".format(build_name), "r").readlines()
|
self._check_timing()
|
||||||
|
|
||||||
|
def _check_timing(self):
|
||||||
|
lines = open("impl/{}_impl.par".format(self._build_name), "r").readlines()
|
||||||
runs = [None, None]
|
runs = [None, None]
|
||||||
for i in range(len(lines)-1):
|
for i in range(len(lines)-1):
|
||||||
if lines[i].startswith("Level/") and lines[i+1].startswith("Cost "):
|
if lines[i].startswith("Level/") and lines[i+1].startswith("Cost "):
|
||||||
|
@ -186,75 +249,3 @@ def _check_timing(build_name):
|
||||||
# XXX is this necessarily the run from which outputs will be used?
|
# XXX is this necessarily the run from which outputs will be used?
|
||||||
return
|
return
|
||||||
raise Exception("Failed to meet timing")
|
raise Exception("Failed to meet timing")
|
||||||
|
|
||||||
# LatticeDiamondToolchain --------------------------------------------------------------------------
|
|
||||||
|
|
||||||
class LatticeDiamondToolchain:
|
|
||||||
attr_translate = {
|
|
||||||
"keep": ("syn_keep", "true"),
|
|
||||||
"no_retiming": ("syn_no_retiming", "true"),
|
|
||||||
}
|
|
||||||
|
|
||||||
special_overrides = common.lattice_ecp5_special_overrides
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.clocks = {}
|
|
||||||
self.false_paths = set() # FIXME: use it
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
# 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 constraints file (.lpf)
|
|
||||||
_build_lpf(named_sc, named_pc, self.clocks, v_output.ns, build_name)
|
|
||||||
|
|
||||||
# Generate design script file (.tcl)
|
|
||||||
_build_tcl(platform.device, platform.sources, platform.verilog_include_paths, build_name)
|
|
||||||
|
|
||||||
# Generate build script
|
|
||||||
script = _build_script(build_name, platform.device)
|
|
||||||
|
|
||||||
# Run
|
|
||||||
if run:
|
|
||||||
_run_script(script)
|
|
||||||
if timingstrict:
|
|
||||||
_check_timing(build_name)
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
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))
|
|
||||||
|
|
|
@ -14,13 +14,54 @@ from shutil import which
|
||||||
from migen.fhdl.structure import _Fragment
|
from migen.fhdl.structure import _Fragment
|
||||||
|
|
||||||
from litex.build.generic_platform import *
|
from litex.build.generic_platform import *
|
||||||
|
from litex.build.generic_toolchain import GenericToolchain
|
||||||
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.lattice.radiant import _format_constraint, _format_ldc, _build_pdc
|
from litex.build.lattice.radiant import _format_constraint, _format_ldc, _build_pdc
|
||||||
|
|
||||||
import math
|
import math
|
||||||
|
|
||||||
# Yosys/Nextpnr Helpers/Templates ------------------------------------------------------------------
|
|
||||||
|
# LatticeOxideToolchain --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class LatticeOxideToolchain(GenericToolchain):
|
||||||
|
attr_translate = {
|
||||||
|
"keep": ("keep", "true"),
|
||||||
|
"syn_useioff": ("syn_useioff", 1),
|
||||||
|
}
|
||||||
|
|
||||||
|
special_overrides = common.lattice_NX_special_overrides_for_oxide
|
||||||
|
|
||||||
|
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,
|
||||||
|
seed = 1,
|
||||||
|
es_device = False,
|
||||||
|
**kwargs):
|
||||||
|
|
||||||
|
self._nowidelut = nowidelut
|
||||||
|
self._abc9 = abc9
|
||||||
|
self._timingstrict = timingstrict
|
||||||
|
self._ignoreloops = ignoreloops
|
||||||
|
self._seed = seed
|
||||||
|
self._es_device = es_device
|
||||||
|
|
||||||
|
return self._build(platform, fragment, **kwargs)
|
||||||
|
|
||||||
|
# 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")
|
||||||
|
|
||||||
|
# Yosys/Nextpnr Helpers/Templates --------------------------------------------------------------
|
||||||
|
|
||||||
_yosys_template = [
|
_yosys_template = [
|
||||||
"verilog_defaults -push",
|
"verilog_defaults -push",
|
||||||
|
@ -31,12 +72,12 @@ _yosys_template = [
|
||||||
"synth_nexus -flatten {nwl} {abc} -json {build_name}.json -top {build_name}",
|
"synth_nexus -flatten {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"
|
||||||
|
@ -44,18 +85,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_project(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))
|
||||||
|
|
||||||
# Script -------------------------------------------------------------------------------------------
|
# Script ---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
_build_template = [
|
_build_template = [
|
||||||
"yosys -l {build_name}.rpt {build_name}.ys",
|
"yosys -l {build_name}.rpt {build_name}.ys",
|
||||||
|
@ -64,7 +105,7 @@ _build_template = [
|
||||||
"prjoxide pack {build_name}.fasm {build_name}.bit"
|
"prjoxide pack {build_name}.fasm {build_name}.bit"
|
||||||
]
|
]
|
||||||
|
|
||||||
def _build_script(source, build_template, build_name, device, timingstrict, ignoreloops, seed):
|
def build_script(self):
|
||||||
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"
|
||||||
|
@ -74,22 +115,22 @@ def _build_script(source, build_template, build_name, device, timingstrict, igno
|
||||||
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,
|
||||||
device = device,
|
device = "ES" if self._es_device else self.platform.device,
|
||||||
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(self, script):
|
||||||
if sys.platform in ("win32", "cygwin"):
|
if sys.platform in ("win32", "cygwin"):
|
||||||
shell = ["cmd", "/c"]
|
shell = ["cmd", "/c"]
|
||||||
else:
|
else:
|
||||||
|
@ -103,90 +144,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.")
|
||||||
|
|
||||||
# LatticeOxideToolchain --------------------------------------------------------------------------
|
|
||||||
|
|
||||||
class LatticeOxideToolchain:
|
|
||||||
attr_translate = {
|
|
||||||
"keep": ("keep", "true"),
|
|
||||||
"syn_useioff": ("syn_useioff", 1),
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
def build(self, platform, fragment,
|
|
||||||
build_dir = "build",
|
|
||||||
build_name = "top",
|
|
||||||
run = True,
|
|
||||||
nowidelut = False,
|
|
||||||
abc9 = False,
|
|
||||||
timingstrict = False,
|
|
||||||
ignoreloops = False,
|
|
||||||
seed = 1,
|
|
||||||
es_device = False,
|
|
||||||
**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 (.pdc)
|
|
||||||
_build_pdc(named_sc, named_pc, self.clocks, v_output.ns, build_name)
|
|
||||||
|
|
||||||
# Generate Yosys script
|
|
||||||
_build_yosys(self.yosys_template, platform, nowidelut, abc9, build_name)
|
|
||||||
|
|
||||||
# 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"
|
|
||||||
|
|
||||||
# Generate build script
|
|
||||||
script = _build_script(False, self.build_template, build_name, device,
|
|
||||||
timingstrict, ignoreloops, seed)
|
|
||||||
# Run
|
|
||||||
if run:
|
|
||||||
_run_script(script)
|
|
||||||
|
|
||||||
os.chdir(cwd)
|
|
||||||
|
|
||||||
return v_output.ns
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
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):
|
def oxide_args(parser):
|
||||||
toolchain_group = parser.add_argument_group(title="Toolchain options")
|
toolchain_group = parser.add_argument_group(title="Toolchain options")
|
||||||
|
|
|
@ -22,59 +22,9 @@ from litex.gen.fhdl.verilog import DummyAttrTranslate
|
||||||
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
|
||||||
|
|
||||||
# Mixed Radiant+Yosys support
|
# Required by oxide too (FIXME)
|
||||||
|
|
||||||
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) -------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def _format_constraint(c):
|
def _format_constraint(c):
|
||||||
if isinstance(c, Pins):
|
if isinstance(c, Pins):
|
||||||
return ("ldc_set_location -site {" + c.identifiers[0] + "} [get_ports ","]")
|
return ("ldc_set_location -site {" + c.identifiers[0] + "} [get_ports ","]")
|
||||||
|
@ -116,43 +66,127 @@ def _build_pdc(named_sc, named_pc, clocks, vns, build_name):
|
||||||
|
|
||||||
tools.write_to_file(build_name + ".pdc", "\n".join(pdc))
|
tools.write_to_file(build_name + ".pdc", "\n".join(pdc))
|
||||||
|
|
||||||
# Project (.tcl) -----------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def _build_tcl(device, sources, vincpaths, build_name, pdc_file, synth_mode):
|
# LatticeRadiantToolchain --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class LatticeRadiantToolchain(GenericToolchain):
|
||||||
|
attr_translate = {
|
||||||
|
"keep": ("syn_keep", "true"),
|
||||||
|
"no_retiming": ("syn_no_retiming", "true"),
|
||||||
|
}
|
||||||
|
|
||||||
|
special_overrides = common.lattice_NX_special_overrides
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self._timingstrict = True
|
||||||
|
self._synth_mode = "radiant"
|
||||||
|
|
||||||
|
def build(self, platform, fragment,
|
||||||
|
timingstrict = True,
|
||||||
|
synth_mode = "radiant",
|
||||||
|
**kwargs):
|
||||||
|
|
||||||
|
self._timingstrict = timingstrict
|
||||||
|
self._synth_mode = synth_mode
|
||||||
|
|
||||||
|
return self._build(platform, fragment, **kwargs)
|
||||||
|
|
||||||
|
# Mixed Radiant+Yosys support ------------------------------------------------------------------
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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=self._build_name)
|
||||||
|
|
||||||
|
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 = []
|
tcl = []
|
||||||
# Create project
|
# Create project
|
||||||
syn = "lse" if synth_mode == "lse" else "synplify"
|
syn = "lse" if self._synth_mode == "lse" else "synplify"
|
||||||
tcl.append(" ".join([
|
tcl.append(" ".join([
|
||||||
"prj_create",
|
"prj_create",
|
||||||
"-name \"{}\"".format(build_name),
|
"-name \"{}\"".format(self._build_name),
|
||||||
"-impl \"impl\"",
|
"-impl \"impl\"",
|
||||||
"-dev {}".format(device),
|
"-dev {}".format(self.platform.device),
|
||||||
"-synthesis \"" + syn + "\""
|
"-synthesis \"" + syn + "\""
|
||||||
]))
|
]))
|
||||||
|
|
||||||
def tcl_path(path): return path.replace("\\", "/")
|
def tcl_path(path): return path.replace("\\", "/")
|
||||||
|
|
||||||
# Add include paths
|
# Add include paths
|
||||||
vincpath = ";".join(map(lambda x: tcl_path(x), vincpaths))
|
vincpath = ";".join(map(lambda x: tcl_path(x), self.platform.verilog_include_paths))
|
||||||
tcl.append("prj_set_impl_opt {include path} {\"" + vincpath + "\"}")
|
tcl.append("prj_set_impl_opt {include path} {\"" + vincpath + "\"}")
|
||||||
|
|
||||||
# Add sources
|
# Add sources
|
||||||
if synth_mode == "yosys":
|
if self._synth_mode == "yosys":
|
||||||
# NOTE: it is seemingly impossible to skip synthesis using the Tcl flow
|
# 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
|
# 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'
|
# 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
|
# with the structural netlist from Yosys, but this would be harder to do in a cross
|
||||||
# platform way.
|
# platform way.
|
||||||
tcl.append("prj_add_source \"{}_yosys.vm\" -work work".format(build_name))
|
tcl.append("prj_add_source \"{}_yosys.vm\" -work work".format(self._build_name))
|
||||||
library = "work"
|
library = "work"
|
||||||
else:
|
else:
|
||||||
for filename, language, library, *copy in sources:
|
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(filename), library))
|
||||||
|
|
||||||
tcl.append("prj_add_source \"{}\" -work {}".format(tcl_path(pdc_file), library))
|
tcl.append("prj_add_source \"{}\" -work {}".format(tcl_path(pdc_file), library))
|
||||||
|
|
||||||
# Set top level
|
# Set top level
|
||||||
tcl.append("prj_set_impl_opt top \"{}\"".format(build_name))
|
tcl.append("prj_set_impl_opt top \"{}\"".format(self._build_name))
|
||||||
|
|
||||||
# Save project
|
# Save project
|
||||||
tcl.append("prj_save")
|
tcl.append("prj_save")
|
||||||
|
@ -166,11 +200,11 @@ def _build_tcl(device, sources, vincpaths, build_name, pdc_file, synth_mode):
|
||||||
# Close project
|
# Close project
|
||||||
tcl.append("prj_close")
|
tcl.append("prj_close")
|
||||||
|
|
||||||
tools.write_to_file(build_name + ".tcl", "\n".join(tcl))
|
tools.write_to_file(self._build_name + ".tcl", "\n".join(tcl))
|
||||||
|
|
||||||
# Script -------------------------------------------------------------------------------------------
|
# Script ---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
def _build_script(build_name, device):
|
def build_script(self):
|
||||||
if sys.platform in ("win32", "cygwin"):
|
if sys.platform in ("win32", "cygwin"):
|
||||||
tool = "pnmainc"
|
tool = "pnmainc"
|
||||||
script_ext = ".bat"
|
script_ext = ".bat"
|
||||||
|
@ -186,20 +220,23 @@ def _build_script(build_name, device):
|
||||||
|
|
||||||
script_contents += "{tool} {tcl_script}{fail_stmt}\n".format(
|
script_contents += "{tool} {tcl_script}{fail_stmt}\n".format(
|
||||||
tool = tool,
|
tool = tool,
|
||||||
tcl_script = build_name + ".tcl",
|
tcl_script = self._build_name + ".tcl",
|
||||||
fail_stmt = fail_stmt)
|
fail_stmt = fail_stmt)
|
||||||
|
|
||||||
script_contents += "{copy_stmt} {radiant_product} {migen_product} {fail_stmt}\n".format(
|
script_contents += "{copy_stmt} {radiant_product} {migen_product} {fail_stmt}\n".format(
|
||||||
copy_stmt = copy_stmt,
|
copy_stmt = copy_stmt,
|
||||||
fail_stmt = fail_stmt,
|
fail_stmt = fail_stmt,
|
||||||
radiant_product = os.path.join("impl", build_name + "_impl.bit"),
|
radiant_product = os.path.join("impl", self._build_name + "_impl.bit"),
|
||||||
migen_product = build_name + ".bit")
|
migen_product = self._build_name + ".bit")
|
||||||
|
|
||||||
build_script_file = "build_" + build_name + script_ext
|
build_script_file = "build_" + self._build_name + script_ext
|
||||||
tools.write_to_file(build_script_file, script_contents, force_unix=False)
|
tools.write_to_file(build_script_file, script_contents, force_unix=False)
|
||||||
return build_script_file
|
return build_script_file
|
||||||
|
|
||||||
def _run_script(script):
|
def run_script(self, script):
|
||||||
|
if synth_mode == "yosys":
|
||||||
|
self._run_yosys()
|
||||||
|
|
||||||
if sys.platform in ("win32", "cygwin"):
|
if sys.platform in ("win32", "cygwin"):
|
||||||
shell = ["cmd", "/c"]
|
shell = ["cmd", "/c"]
|
||||||
tool = "pnmainc"
|
tool = "pnmainc"
|
||||||
|
@ -214,9 +251,11 @@ def _run_script(script):
|
||||||
|
|
||||||
if subprocess.call(shell + [script]) != 0:
|
if subprocess.call(shell + [script]) != 0:
|
||||||
raise OSError("Error occured during Radiant's script execution.")
|
raise OSError("Error occured during Radiant's script execution.")
|
||||||
|
if timingstrict:
|
||||||
|
self._check_timing()
|
||||||
|
|
||||||
def _check_timing(build_name):
|
def _check_timing(self):
|
||||||
lines = open("impl/{}_impl.par".format(build_name), "r").readlines()
|
lines = open("impl/{}_impl.par".format(self._build_name), "r").readlines()
|
||||||
runs = [None, None]
|
runs = [None, None]
|
||||||
for i in range(len(lines)-1):
|
for i in range(len(lines)-1):
|
||||||
if lines[i].startswith("Level/") and lines[i+1].startswith("Cost "):
|
if lines[i].startswith("Level/") and lines[i+1].startswith("Cost "):
|
||||||
|
@ -243,81 +282,6 @@ def _check_timing(build_name):
|
||||||
return
|
return
|
||||||
raise Exception("Failed to meet timing")
|
raise Exception("Failed to meet timing")
|
||||||
|
|
||||||
# LatticeRadiantToolchain --------------------------------------------------------------------------
|
|
||||||
|
|
||||||
class LatticeRadiantToolchain:
|
|
||||||
attr_translate = {
|
|
||||||
"keep": ("syn_keep", "true"),
|
|
||||||
"no_retiming": ("syn_no_retiming", "true"),
|
|
||||||
}
|
|
||||||
|
|
||||||
special_overrides = common.lattice_NX_special_overrides
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.clocks = {}
|
|
||||||
self.false_paths = set() # FIXME: use it
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
# 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 constraints file (.pdc)
|
|
||||||
_build_pdc(named_sc, named_pc, self.clocks, v_output.ns, build_name)
|
|
||||||
pdc_file = build_dir + "\\" + build_name + ".pdc"
|
|
||||||
|
|
||||||
# Generate design script file (.tcl)
|
|
||||||
_build_tcl(platform.device, platform.sources, platform.verilog_include_paths, build_name, pdc_file, synth_mode)
|
|
||||||
|
|
||||||
# Generate build script
|
|
||||||
script = _build_script(build_name, platform.device)
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
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):
|
def radiant_build_args(parser):
|
||||||
toolchain_group = parser.add_argument_group(title="Toolchain options")
|
toolchain_group = parser.add_argument_group(title="Toolchain options")
|
||||||
|
|
|
@ -13,17 +13,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.generic_toolchain import GenericToolchain
|
||||||
from litex.build.microsemi import common
|
from litex.build.microsemi import common
|
||||||
|
|
||||||
|
|
||||||
# Helpers ------------------------------------------------------------------------------------------
|
# MicrosemiLiberoSoCPolarfireToolchain -------------------------------------------------------------
|
||||||
|
|
||||||
def tcl_name(name):
|
class MicrosemiLiberoSoCPolarfireToolchain(GenericToolchain):
|
||||||
|
attr_translate = {}
|
||||||
|
|
||||||
|
special_overrides = common.microsemi_polarfire_special_overrides
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.additional_io_constraints = []
|
||||||
|
self.additional_fp_constraints = []
|
||||||
|
self.additional_timing_constraints = []
|
||||||
|
|
||||||
|
def build(self, platform, fragment, **kwargs):
|
||||||
|
|
||||||
|
return self._build(platform, fragment, **kwargs)
|
||||||
|
|
||||||
|
# Helpers --------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tcl_name(cls, name):
|
||||||
return "{" + name + "}"
|
return "{" + name + "}"
|
||||||
|
|
||||||
# IO Constraints (.pdc) ----------------------------------------------------------------------------
|
# IO Constraints (.pdc) ------------------------------------------------------------------------
|
||||||
|
|
||||||
def _format_io_constraint(c):
|
@classmethod
|
||||||
|
def _format_io_constraint(cls, c):
|
||||||
if isinstance(c, Pins):
|
if isinstance(c, Pins):
|
||||||
return "-pin_name {} ".format(c.identifiers[0])
|
return "-pin_name {} ".format(c.identifiers[0])
|
||||||
elif isinstance(c, IOStandard):
|
elif isinstance(c, IOStandard):
|
||||||
|
@ -33,8 +53,8 @@ def _format_io_constraint(c):
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@classmethod
|
||||||
def _format_io_pdc(signame, pin, others):
|
def _format_io_pdc(cls, signame, pin, others):
|
||||||
fmt_c = [_format_io_constraint(c) for c in ([Pins(pin)] + others)]
|
fmt_c = [_format_io_constraint(c) for c in ([Pins(pin)] + others)]
|
||||||
r = "set_io "
|
r = "set_io "
|
||||||
r += "-port_name {} ".format(tcl_name(signame))
|
r += "-port_name {} ".format(tcl_name(signame))
|
||||||
|
@ -44,33 +64,35 @@ def _format_io_pdc(signame, pin, others):
|
||||||
r += "\n"
|
r += "\n"
|
||||||
return r
|
return r
|
||||||
|
|
||||||
def _build_io_pdc(named_sc, named_pc, build_name, additional_io_constraints):
|
def build_io_constraints(self):
|
||||||
pdc = ""
|
pdc = ""
|
||||||
for sig, pins, others, resname in named_sc:
|
for sig, pins, others, resname in self.named_sc:
|
||||||
if len(pins) > 1:
|
if len(pins) > 1:
|
||||||
for i, p in enumerate(pins):
|
for i, p in enumerate(pins):
|
||||||
pdc += _format_io_pdc(sig + "[" + str(i) + "]", p, others)
|
pdc += self._format_io_pdc(sig + "[" + str(i) + "]", p, others)
|
||||||
else:
|
else:
|
||||||
pdc += _format_io_pdc(sig, pins[0], others)
|
pdc += self._format_io_pdc(sig, pins[0], others)
|
||||||
pdc += "\n".join(additional_io_constraints)
|
pdc += "\n".join(self.additional_io_constraints)
|
||||||
tools.write_to_file(build_name + "_io.pdc", pdc)
|
tools.write_to_file(self._build_name + "_io.pdc", pdc)
|
||||||
|
return (self._build_name + "_io.pdc", "PDC")
|
||||||
|
|
||||||
# Placement Constraints (.pdc) ---------------------------------------------------------------------
|
# Placement Constraints (.pdc) -----------------------------------------------------------------
|
||||||
|
|
||||||
def _build_fp_pdc(build_name, additional_fp_constraints):
|
def build_placement_constraints(self):
|
||||||
pdc = "\n".join(additional_fp_constraints)
|
pdc = "\n".join(self.additional_fp_constraints)
|
||||||
tools.write_to_file(build_name + "_fp.pdc", pdc)
|
tools.write_to_file(self._build_name + "_fp.pdc", pdc)
|
||||||
|
return (self._build_name + "_fp.pdc", "PDC")
|
||||||
|
|
||||||
# Project (.tcl) -----------------------------------------------------------------------------------
|
# Project (.tcl) -------------------------------------------------------------------------------
|
||||||
|
|
||||||
def _build_tcl(platform, sources, build_dir, build_name):
|
def build_project(self):
|
||||||
tcl = []
|
tcl = []
|
||||||
|
|
||||||
# Create project
|
# Create project
|
||||||
tcl.append(" ".join([
|
tcl.append(" ".join([
|
||||||
"new_project",
|
"new_project",
|
||||||
"-location {./impl}",
|
"-location {./impl}",
|
||||||
"-name {}".format(tcl_name(build_name)),
|
"-name {}".format(tcl_name(self._build_name)),
|
||||||
"-project_description {}",
|
"-project_description {}",
|
||||||
"-block_mode 0",
|
"-block_mode 0",
|
||||||
"-standalone_peripheral_initialization 0",
|
"-standalone_peripheral_initialization 0",
|
||||||
|
@ -87,7 +109,7 @@ def _build_tcl(platform, sources, build_dir, build_name):
|
||||||
"-adv_options {}"
|
"-adv_options {}"
|
||||||
]))
|
]))
|
||||||
|
|
||||||
die, package, speed = platform.device.split("-")
|
die, package, speed = self.platform.device.split("-")
|
||||||
tcl.append(" ".join([
|
tcl.append(" ".join([
|
||||||
"set_device",
|
"set_device",
|
||||||
"-family {PolarFire}",
|
"-family {PolarFire}",
|
||||||
|
@ -111,46 +133,46 @@ def _build_tcl(platform, sources, build_dir, build_name):
|
||||||
]))
|
]))
|
||||||
|
|
||||||
# Add sources
|
# Add sources
|
||||||
for filename, language, library, *copy in sources:
|
for filename, language, library, *copy in self.platform.sources:
|
||||||
filename_tcl = "{" + filename + "}"
|
filename_tcl = "{" + filename + "}"
|
||||||
tcl.append("import_files -hdl_source " + filename_tcl)
|
tcl.append("import_files -hdl_source " + filename_tcl)
|
||||||
|
|
||||||
# Set top level
|
# Set top level
|
||||||
tcl.append("set_root -module {}".format(tcl_name(build_name)))
|
tcl.append("set_root -module {}".format(tcl_name(self._build_name)))
|
||||||
|
|
||||||
# Copy init files FIXME: support for include path on LiberoSoC?
|
# Copy init files FIXME: support for include path on LiberoSoC?
|
||||||
for file in os.listdir(build_dir):
|
for file in os.listdir(self._build_dir):
|
||||||
if file.endswith(".init"):
|
if file.endswith(".init"):
|
||||||
tcl.append("file copy -- {} impl/synthesis".format(file))
|
tcl.append("file copy -- {} impl/synthesis".format(file))
|
||||||
|
|
||||||
# Import io constraints
|
# Import io constraints
|
||||||
tcl.append("import_files -io_pdc {}".format(tcl_name(build_name + "_io.pdc")))
|
tcl.append("import_files -io_pdc {}".format(tcl_name(self._build_name + "_io.pdc")))
|
||||||
|
|
||||||
# Import floorplanner constraints
|
# Import floorplanner constraints
|
||||||
tcl.append("import_files -fp_pdc {}".format(tcl_name(build_name + "_fp.pdc")))
|
tcl.append("import_files -fp_pdc {}".format(tcl_name(self._build_name + "_fp.pdc")))
|
||||||
|
|
||||||
# Import timing constraints
|
# Import timing constraints
|
||||||
tcl.append("import_files -convert_EDN_to_HDL 0 -sdc {}".format(tcl_name(build_name + ".sdc")))
|
tcl.append("import_files -convert_EDN_to_HDL 0 -sdc {}".format(tcl_name(self._build_name + ".sdc")))
|
||||||
|
|
||||||
# Associate constraints with tools
|
# Associate constraints with tools
|
||||||
tcl.append(" ".join(["organize_tool_files",
|
tcl.append(" ".join(["organize_tool_files",
|
||||||
"-tool {SYNTHESIZE}",
|
"-tool {SYNTHESIZE}",
|
||||||
"-file impl/constraint/{}.sdc".format(build_name),
|
"-file impl/constraint/{}.sdc".format(self._build_name),
|
||||||
"-module {}".format(build_name),
|
"-module {}".format(self._build_name),
|
||||||
"-input_type {constraint}"
|
"-input_type {constraint}"
|
||||||
]))
|
]))
|
||||||
tcl.append(" ".join(["organize_tool_files",
|
tcl.append(" ".join(["organize_tool_files",
|
||||||
"-tool {PLACEROUTE}",
|
"-tool {PLACEROUTE}",
|
||||||
"-file impl/constraint/io/{}_io.pdc".format(build_name),
|
"-file impl/constraint/io/{}_io.pdc".format(self._build_name),
|
||||||
"-file impl/constraint/fp/{}_fp.pdc".format(build_name),
|
"-file impl/constraint/fp/{}_fp.pdc".format(self._build_name),
|
||||||
"-file impl/constraint/{}.sdc".format(build_name),
|
"-file impl/constraint/{}.sdc".format(self._build_name),
|
||||||
"-module {}".format(build_name),
|
"-module {}".format(self._build_name),
|
||||||
"-input_type {constraint}"
|
"-input_type {constraint}"
|
||||||
]))
|
]))
|
||||||
tcl.append(" ".join(["organize_tool_files",
|
tcl.append(" ".join(["organize_tool_files",
|
||||||
"-tool {VERIFYTIMING}",
|
"-tool {VERIFYTIMING}",
|
||||||
"-file impl/constraint/{}.sdc".format(build_name),
|
"-file impl/constraint/{}.sdc".format(self._build_name),
|
||||||
"-module {}".format(build_name),
|
"-module {}".format(self._build_name),
|
||||||
"-input_type {constraint}"
|
"-input_type {constraint}"
|
||||||
]))
|
]))
|
||||||
|
|
||||||
|
@ -162,18 +184,19 @@ def _build_tcl(platform, sources, build_dir, build_name):
|
||||||
tcl.append("run_tool -name {GENERATEPROGRAMMINGFILE}")
|
tcl.append("run_tool -name {GENERATEPROGRAMMINGFILE}")
|
||||||
|
|
||||||
# Generate tcl
|
# Generate tcl
|
||||||
tools.write_to_file(build_name + ".tcl", "\n".join(tcl))
|
tools.write_to_file(self._build_name + ".tcl", "\n".join(tcl))
|
||||||
|
return self._build_name + ".tcl"
|
||||||
|
|
||||||
# Timing Constraints (.sdc) ------------------------------------------------------------------------
|
# Timing Constraints (.sdc) --------------------------------------------------------------------
|
||||||
|
|
||||||
def _build_timing_sdc(vns, clocks, false_paths, build_name, additional_timing_constraints):
|
def build_timing_constraints(self, vns):
|
||||||
sdc = []
|
sdc = []
|
||||||
|
|
||||||
for clk, period in sorted(clocks.items(), key=lambda x: x[0].duid):
|
for clk, period in sorted(self.clocks.items(), key=lambda x: x[0].duid):
|
||||||
sdc.append(
|
sdc.append(
|
||||||
"create_clock -name {clk} -period " + str(period) +
|
"create_clock -name {clk} -period " + str(period) +
|
||||||
" [get_nets {clk}]".format(clk=vns.get_name(clk)))
|
" [get_nets {clk}]".format(clk=vns.get_name(clk)))
|
||||||
for from_, to in sorted(false_paths,
|
for from_, to in sorted(self.false_paths,
|
||||||
key=lambda x: (x[0].duid, x[1].duid)):
|
key=lambda x: (x[0].duid, x[1].duid)):
|
||||||
sdc.append(
|
sdc.append(
|
||||||
"set_clock_groups "
|
"set_clock_groups "
|
||||||
|
@ -182,12 +205,13 @@ def _build_timing_sdc(vns, clocks, false_paths, build_name, additional_timing_co
|
||||||
"-asynchronous".format(from_=from_, to=to))
|
"-asynchronous".format(from_=from_, to=to))
|
||||||
|
|
||||||
# generate sdc
|
# generate sdc
|
||||||
sdc += additional_timing_constraints
|
sdc += self.additional_timing_constraints
|
||||||
tools.write_to_file(build_name + ".sdc", "\n".join(sdc))
|
tools.write_to_file(self._build_name + ".sdc", "\n".join(sdc))
|
||||||
|
return (self._build_name + ".sdc", "SDC")
|
||||||
|
|
||||||
# Script -------------------------------------------------------------------------------------------
|
# Script ---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
def _build_script(build_name, device):
|
def build_script(self):
|
||||||
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"
|
||||||
|
@ -199,12 +223,16 @@ def _build_script(build_name, device):
|
||||||
copy_stmt = "cp"
|
copy_stmt = "cp"
|
||||||
fail_stmt = " || exit 1"
|
fail_stmt = " || exit 1"
|
||||||
|
|
||||||
script_file = "build_" + build_name + script_ext
|
script_file = "build_" + self._build_name + script_ext
|
||||||
tools.write_to_file(script_file, script_contents,
|
tools.write_to_file(script_file, script_contents,
|
||||||
force_unix=False)
|
force_unix=False)
|
||||||
return script_file
|
return script_file
|
||||||
|
|
||||||
def _run_script(script):
|
def run_script(self.script):
|
||||||
|
# Delete previous impl
|
||||||
|
if os.path.exists("impl"):
|
||||||
|
shutil.rmtree("impl")
|
||||||
|
|
||||||
if sys.platform in ["win32", "cygwin"]:
|
if sys.platform in ["win32", "cygwin"]:
|
||||||
shell = ["cmd", "/c"]
|
shell = ["cmd", "/c"]
|
||||||
else:
|
else:
|
||||||
|
@ -213,70 +241,6 @@ def _run_script(script):
|
||||||
if subprocess.call(shell + [script]) != 0:
|
if subprocess.call(shell + [script]) != 0:
|
||||||
raise OSError("Subprocess failed")
|
raise OSError("Subprocess failed")
|
||||||
|
|
||||||
# MicrosemiLiberoSoCPolarfireToolchain -------------------------------------------------------------
|
|
||||||
|
|
||||||
class MicrosemiLiberoSoCPolarfireToolchain:
|
|
||||||
attr_translate = {}
|
|
||||||
|
|
||||||
special_overrides = common.microsemi_polarfire_special_overrides
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.clocks = dict()
|
|
||||||
self.false_paths = set()
|
|
||||||
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):
|
|
||||||
|
|
||||||
# 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 script file (.tcl)
|
|
||||||
_build_tcl(platform, platform.sources, build_dir, build_name)
|
|
||||||
|
|
||||||
# Generate design io constraints file (.pdc)
|
|
||||||
_build_io_pdc(named_sc, named_pc, build_name, self.additional_io_constraints)
|
|
||||||
|
|
||||||
# Generate design placement constraints file (.pdc)
|
|
||||||
_build_fp_pdc(build_name, self.additional_fp_constraints)
|
|
||||||
|
|
||||||
# Generate design timing constraints file (.sdc)
|
|
||||||
_build_timing_sdc(v_output.ns, self.clocks, self.false_paths, build_name,
|
|
||||||
self.additional_timing_constraints)
|
|
||||||
|
|
||||||
# Generate build script
|
|
||||||
script = _build_script(build_name, platform.device)
|
|
||||||
|
|
||||||
# Run
|
|
||||||
if run:
|
|
||||||
# Delete previous impl
|
|
||||||
if os.path.exists("impl"):
|
|
||||||
shutil.rmtree("impl")
|
|
||||||
_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):
|
||||||
if clk in self.clocks:
|
if clk in self.clocks:
|
||||||
if period != self.clocks[clk]:
|
if period != self.clocks[clk]:
|
||||||
|
|
|
@ -13,59 +13,79 @@ from shutil import which
|
||||||
from migen.fhdl.structure import _Fragment
|
from migen.fhdl.structure import _Fragment
|
||||||
|
|
||||||
from litex.build.generic_platform import *
|
from litex.build.generic_platform import *
|
||||||
|
from litex.build.generic_toolchain import GenericToolchain
|
||||||
from litex.build import tools
|
from litex.build import tools
|
||||||
from litex.build.quicklogic import common
|
from litex.build.quicklogic import common
|
||||||
|
|
||||||
|
|
||||||
# IO Constraints (.pcf) ----------------------------------------------------------------------------
|
# F4PGAToolchain -------------------------------------------------------------------------------
|
||||||
def _format_io_pcf(signame, pin, others):
|
# Formerly SymbiflowToolchain, Symbiflow has been renamed to F4PGA -----------------------------
|
||||||
|
|
||||||
|
class F4PGAToolchain(GenericToolchain):
|
||||||
|
attr_translate = {}
|
||||||
|
|
||||||
|
special_overrides = common.quicklogic_special_overrides
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def build(self, platform, fragment, **kwargs):
|
||||||
|
return self._build(platform, fragment, **kwargs)
|
||||||
|
|
||||||
|
# IO Constraints (.pcf) ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _format_io_pcf(cls, signame, pin, others):
|
||||||
r = f"set_io {signame} {Pins(pin).identifiers[0]}\n"
|
r = f"set_io {signame} {Pins(pin).identifiers[0]}\n"
|
||||||
return r
|
return r
|
||||||
|
|
||||||
def _build_io_pcf(named_sc, named_pc, build_name):
|
def build_io_constraints(self):
|
||||||
pcf = ""
|
pcf = ""
|
||||||
for sig, pins, others, resname in named_sc:
|
for sig, pins, others, resname in self.named_sc:
|
||||||
if len(pins) > 1:
|
if len(pins) > 1:
|
||||||
for i, p in enumerate(pins):
|
for i, p in enumerate(pins):
|
||||||
pcf += _format_io_pcf(sig + "(" + str(i) + ")", p, others)
|
pcf += self._format_io_pcf(sig + "(" + str(i) + ")", p, others)
|
||||||
else:
|
else:
|
||||||
pcf += _format_io_pcf(sig, pins[0], others)
|
pcf += self._format_io_pcf(sig, pins[0], others)
|
||||||
tools.write_to_file(build_name + ".pcf", pcf)
|
tools.write_to_file(self._build_name + ".pcf", pcf)
|
||||||
|
return (self._build_name + ".pcf", "PCF")
|
||||||
|
|
||||||
# Build Makefile -----------------------------------------------------------------------------------
|
# Build Makefile -------------------------------------------------------------------------------
|
||||||
|
|
||||||
def _build_makefile(platform, sources, build_dir, build_name):
|
def build_script(self):
|
||||||
makefile = []
|
makefile = []
|
||||||
|
|
||||||
# Define Paths.
|
# Define Paths.
|
||||||
makefile.append("mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))")
|
makefile.append("mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))")
|
||||||
makefile.append("current_dir := $(patsubst %/,%,$(dir $(mkfile_path)))")
|
makefile.append("current_dir := $(patsubst %/,%,$(dir $(mkfile_path)))")
|
||||||
# bit -> h and bit -> bin requires TOP_F
|
# bit -> h and bit -> bin requires TOP_F
|
||||||
makefile.append(f"TOP_F={build_name}")
|
makefile.append(f"TOP_F={self._build_name}")
|
||||||
|
|
||||||
# Create Project.
|
# Create Project.
|
||||||
# FIXME: Only use top file for now and ignore .init files.
|
# 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))
|
makefile.append("all: {top}_bit.h {top}.bin build/{top}.bit".format(top=self._build_name))
|
||||||
# build bit file (default)
|
# build bit file (default)
|
||||||
makefile.append(f"build/{build_name}.bit:")
|
makefile.append(f"build/{self._build_name}.bit:")
|
||||||
makefile.append("\tql_symbiflow -compile -d {device} -P {part} -v {verilog} -t {top} -p {pcf}".format(
|
makefile.append("\tql_symbiflow -compile -d {device} -P {part} -v {verilog} -t {top} -p {pcf}".format(
|
||||||
device = platform.device,
|
device = self.platform.device,
|
||||||
part = {"ql-eos-s3": "PU64"}.get(platform.device),
|
part = {"ql-eos-s3": "PU64"}.get(self.platform.device),
|
||||||
verilog = f"{build_name}.v",
|
verilog = f"{self._build_name}.v",
|
||||||
top = build_name,
|
top = self._build_name,
|
||||||
pcf = f"{build_name}.pcf"
|
pcf = f"{self._build_name}.pcf"
|
||||||
))
|
))
|
||||||
# build header to include in CPU firmware
|
# build header to include in CPU firmware
|
||||||
makefile.append("{top}_bit.h: build/{top}.bit".format(top=build_name))
|
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)")
|
makefile.append(f"\t(cd build; TOP_F=$(TOP_F) symbiflow_write_bitheader)")
|
||||||
# build binary to write in dedicated FLASH area
|
# build binary to write in dedicated FLASH area
|
||||||
makefile.append("{top}.bin: build/{top}.bit".format(top=build_name))
|
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)")
|
makefile.append(f"\t(cd build; TOP_F=$(TOP_F) symbiflow_write_binary)")
|
||||||
|
|
||||||
# Generate Makefile.
|
# Generate Makefile.
|
||||||
tools.write_to_file("Makefile", "\n".join(makefile))
|
tools.write_to_file("Makefile", "\n".join(makefile))
|
||||||
|
|
||||||
def _run_make():
|
return "Makefile"
|
||||||
|
|
||||||
|
def run_script(self, script):
|
||||||
make_cmd = ["make", "-j1"]
|
make_cmd = ["make", "-j1"]
|
||||||
|
|
||||||
if which("ql_symbiflow") is None:
|
if which("ql_symbiflow") is None:
|
||||||
|
@ -75,53 +95,3 @@ def _run_make():
|
||||||
|
|
||||||
if subprocess.call(make_cmd) != 0:
|
if subprocess.call(make_cmd) != 0:
|
||||||
raise OSError("Error occured during QuickLogic Symbiflow's script execution.")
|
raise OSError("Error occured during QuickLogic Symbiflow's script execution.")
|
||||||
|
|
||||||
|
|
||||||
# F4PGAToolchain -------------------------------------------------------------------------------
|
|
||||||
# Formerly SymbiflowToolchain, Symbiflow has been renamed to F4PGA -----------------------------
|
|
||||||
|
|
||||||
class F4PGAToolchain:
|
|
||||||
attr_translate = {}
|
|
||||||
|
|
||||||
special_overrides = common.quicklogic_special_overrides
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.clocks = dict()
|
|
||||||
self.false_paths = set()
|
|
||||||
|
|
||||||
def build(self, platform, fragment,
|
|
||||||
build_dir = "build",
|
|
||||||
build_name = "top",
|
|
||||||
run = False,
|
|
||||||
**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 .pcf IO constraints file.
|
|
||||||
_build_io_pcf(named_sc, named_pc, build_name)
|
|
||||||
|
|
||||||
# Generate Makefie.
|
|
||||||
_build_makefile(platform, platform.sources, build_dir, build_name)
|
|
||||||
|
|
||||||
# Run.
|
|
||||||
if run:
|
|
||||||
_run_make()
|
|
||||||
|
|
||||||
os.chdir(cwd)
|
|
||||||
|
|
||||||
return v_output.ns
|
|
||||||
|
|
Loading…
Reference in New Issue