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 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,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, "[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};")
|
||||
|
||||
# Timings (.sdc)
|
||||
_build_sdc(
|
||||
clocks = self.clocks,
|
||||
vns = v_output.ns
|
||||
)
|
||||
if self.named_pc:
|
||||
cst.extend(self.named_pc)
|
||||
|
||||
# Generate build script (.tcl)
|
||||
script = _build_tcl(
|
||||
name = platform.devicename,
|
||||
partnumber = platform.device,
|
||||
files = platform.sources,
|
||||
options = self.options)
|
||||
tools.write_to_file("top.cst", "\n".join(cst))
|
||||
return ("top.cst", "CST")
|
||||
|
||||
# Run
|
||||
if run:
|
||||
# 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.
|
||||
|
@ -185,16 +154,3 @@ class GowinToolchain:
|
|||
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
|
||||
|
|
|
@ -19,17 +19,78 @@ 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):
|
||||
# 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")
|
||||
|
||||
# Constraints (.lpf) -------------------------------------------------------------------------------
|
||||
# Constraints (.lpf) ---------------------------------------------------------------------------
|
||||
|
||||
def _format_constraint(c):
|
||||
@classmethod
|
||||
def _format_constraint(cls, c):
|
||||
if isinstance(c, Pins):
|
||||
return ("LOCATE COMP ", " SITE " + "\"" + c.identifiers[0] + "\"")
|
||||
elif isinstance(c, IOStandard):
|
||||
|
@ -37,63 +98,62 @@ def _format_constraint(c):
|
|||
elif isinstance(c, Misc):
|
||||
return ("IOBUF PORT ", " " + c.misc)
|
||||
|
||||
|
||||
def _format_lpf(signame, pin, others, resname):
|
||||
fmt_c = [_format_constraint(c) for c in ([Pins(pin)] + others)]
|
||||
@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_lpf(named_sc, named_pc, clocks, vns, build_name):
|
||||
def build_io_constraints(self):
|
||||
lpf = []
|
||||
lpf.append("BLOCK RESETPATHS;")
|
||||
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:
|
||||
for i, p in enumerate(pins):
|
||||
lpf.append(_format_lpf(sig + "[" + str(i) + "]", p, others, resname))
|
||||
lpf.append(self._format_lpf(sig + "[" + str(i) + "]", p, others, resname))
|
||||
else:
|
||||
lpf.append(_format_lpf(sig, pins[0], others, resname))
|
||||
if named_pc:
|
||||
lpf.append("\n".join(named_pc))
|
||||
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 clocks.items():
|
||||
clk_name = vns.get_name(clk)
|
||||
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 named_sc] else "NET",
|
||||
"PORT" if clk_name in [name for name, _, _, _ in self.named_sc] else "NET",
|
||||
clk_name,
|
||||
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 = []
|
||||
# Create project
|
||||
tcl.append(" ".join([
|
||||
"prj_project",
|
||||
"new -name \"{}\"".format(build_name),
|
||||
"new -name \"{}\"".format(self._build_name),
|
||||
"-impl \"impl\"",
|
||||
"-dev {}".format(device),
|
||||
"-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), vincpaths))
|
||||
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 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(build_name))
|
||||
tcl.append("prj_impl option top \"{}\"".format(self._build_name))
|
||||
|
||||
# Save project
|
||||
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 PAR -impl impl")
|
||||
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")
|
||||
|
||||
# Close project
|
||||
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")
|
||||
if on_windows:
|
||||
script_ext = ".bat"
|
||||
|
@ -129,22 +189,22 @@ def _build_script(build_name, device):
|
|||
|
||||
script_contents += "{tool} {tcl_script}{fail_stmt}\n".format(
|
||||
tool = "pnmainc" if on_windows else "diamondc",
|
||||
tcl_script = build_name + ".tcl",
|
||||
tcl_script = self._build_name + ".tcl",
|
||||
fail_stmt = fail_stmt)
|
||||
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
|
||||
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)
|
||||
diamond_product = os.path.join("impl", self._build_name + "_impl" + 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)
|
||||
return build_script_file
|
||||
|
||||
def _run_script(script):
|
||||
def run_script(self, script):
|
||||
on_windows = sys.platform in ("win32", "cygwin")
|
||||
if on_windows:
|
||||
shell = ["cmd", "/c"]
|
||||
|
@ -159,8 +219,11 @@ def _run_script(script):
|
|||
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()
|
||||
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 "):
|
||||
|
@ -186,75 +249,3 @@ def _check_timing(build_name):
|
|||
# XXX is this necessarily the run from which outputs will be used?
|
||||
return
|
||||
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,29 +14,70 @@ 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 = [
|
||||
# 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 = [
|
||||
"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):
|
||||
def _yosys_import_sources(self):
|
||||
includes = ""
|
||||
reads = []
|
||||
for path in platform.verilog_include_paths:
|
||||
for path in self.platform.verilog_include_paths:
|
||||
includes += " -I" + path
|
||||
for filename, language, library, *copy in platform.sources:
|
||||
for filename, language, library, *copy in self.platform.sources:
|
||||
# yosys has no such function read_systemverilog
|
||||
if language == "systemverilog":
|
||||
language = "verilog -sv"
|
||||
|
@ -44,27 +85,27 @@ def _yosys_import_sources(platform):
|
|||
language, includes, filename))
|
||||
return "\n".join(reads)
|
||||
|
||||
def _build_yosys(template, platform, nowidelut, abc9, build_name):
|
||||
def build_project(self):
|
||||
ys = []
|
||||
for l in template:
|
||||
for l in self.yosys_template:
|
||||
ys.append(l.format(
|
||||
build_name = build_name,
|
||||
nwl = "-nowidelut" if nowidelut else "",
|
||||
abc = "-abc9" if abc9 else "",
|
||||
read_files = _yosys_import_sources(platform)
|
||||
build_name = self._build_name,
|
||||
nwl = "-nowidelut" if self._nowidelut else "",
|
||||
abc = "-abc9" if self._abc9 else "",
|
||||
read_files = self._yosys_import_sources()
|
||||
))
|
||||
tools.write_to_file(build_name + ".ys", "\n".join(ys))
|
||||
tools.write_to_file(self._build_name + ".ys", "\n".join(ys))
|
||||
|
||||
# Script -------------------------------------------------------------------------------------------
|
||||
# Script ---------------------------------------------------------------------------------------
|
||||
|
||||
_build_template = [
|
||||
_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):
|
||||
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"
|
||||
|
@ -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"
|
||||
fail_stmt = ""
|
||||
|
||||
for s in build_template:
|
||||
for s in self.build_template:
|
||||
s_fail = s + "{fail_stmt}\n" # Required so Windows scripts fail early.
|
||||
script_contents += s_fail.format(
|
||||
build_name = build_name,
|
||||
device = device,
|
||||
timefailarg = "--timing-allow-fail" if not timingstrict else "",
|
||||
ignoreloops = "--ignore-loops" if ignoreloops else "",
|
||||
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 = seed,
|
||||
seed = self._seed,
|
||||
)
|
||||
script_file = "build_" + build_name + script_ext
|
||||
script_file = "build_" + self._build_name + script_ext
|
||||
tools.write_to_file(script_file, script_contents, force_unix=False)
|
||||
|
||||
return script_file
|
||||
|
||||
def _run_script(script):
|
||||
def run_script(self, script):
|
||||
if sys.platform in ("win32", "cygwin"):
|
||||
shell = ["cmd", "/c"]
|
||||
else:
|
||||
|
@ -103,90 +144,6 @@ def _run_script(script):
|
|||
if subprocess.call(shell + [script]) != 0:
|
||||
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):
|
||||
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 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,43 +66,127 @@ 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):
|
||||
# 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 = []
|
||||
# Create project
|
||||
syn = "lse" if synth_mode == "lse" else "synplify"
|
||||
syn = "lse" if self._synth_mode == "lse" else "synplify"
|
||||
tcl.append(" ".join([
|
||||
"prj_create",
|
||||
"-name \"{}\"".format(build_name),
|
||||
"-name \"{}\"".format(self._build_name),
|
||||
"-impl \"impl\"",
|
||||
"-dev {}".format(device),
|
||||
"-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), vincpaths))
|
||||
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 synth_mode == "yosys":
|
||||
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(build_name))
|
||||
tcl.append("prj_add_source \"{}_yosys.vm\" -work work".format(self._build_name))
|
||||
library = "work"
|
||||
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(pdc_file), library))
|
||||
|
||||
# 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
|
||||
tcl.append("prj_save")
|
||||
|
@ -166,11 +200,11 @@ def _build_tcl(device, sources, vincpaths, build_name, pdc_file, synth_mode):
|
|||
# Close project
|
||||
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"):
|
||||
tool = "pnmainc"
|
||||
script_ext = ".bat"
|
||||
|
@ -186,20 +220,23 @@ def _build_script(build_name, device):
|
|||
|
||||
script_contents += "{tool} {tcl_script}{fail_stmt}\n".format(
|
||||
tool = tool,
|
||||
tcl_script = build_name + ".tcl",
|
||||
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", build_name + "_impl.bit"),
|
||||
migen_product = build_name + ".bit")
|
||||
radiant_product = os.path.join("impl", self._build_name + "_impl.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)
|
||||
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"):
|
||||
shell = ["cmd", "/c"]
|
||||
tool = "pnmainc"
|
||||
|
@ -214,9 +251,11 @@ def _run_script(script):
|
|||
|
||||
if subprocess.call(shell + [script]) != 0:
|
||||
raise OSError("Error occured during Radiant's script execution.")
|
||||
if timingstrict:
|
||||
self._check_timing()
|
||||
|
||||
def _check_timing(build_name):
|
||||
lines = open("impl/{}_impl.par".format(build_name), "r").readlines()
|
||||
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 "):
|
||||
|
@ -243,81 +282,6 @@ def _check_timing(build_name):
|
|||
return
|
||||
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):
|
||||
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 import tools
|
||||
from litex.build.generic_toolchain import GenericToolchain
|
||||
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 + "}"
|
||||
|
||||
# IO Constraints (.pdc) ----------------------------------------------------------------------------
|
||||
# IO Constraints (.pdc) ------------------------------------------------------------------------
|
||||
|
||||
def _format_io_constraint(c):
|
||||
@classmethod
|
||||
def _format_io_constraint(cls, c):
|
||||
if isinstance(c, Pins):
|
||||
return "-pin_name {} ".format(c.identifiers[0])
|
||||
elif isinstance(c, IOStandard):
|
||||
|
@ -33,8 +53,8 @@ def _format_io_constraint(c):
|
|||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def _format_io_pdc(signame, pin, others):
|
||||
@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))
|
||||
|
@ -44,33 +64,35 @@ def _format_io_pdc(signame, pin, others):
|
|||
r += "\n"
|
||||
return r
|
||||
|
||||
def _build_io_pdc(named_sc, named_pc, build_name, additional_io_constraints):
|
||||
def build_io_constraints(self):
|
||||
pdc = ""
|
||||
for sig, pins, others, resname in named_sc:
|
||||
for sig, pins, others, resname in self.named_sc:
|
||||
if len(pins) > 1:
|
||||
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:
|
||||
pdc += _format_io_pdc(sig, pins[0], others)
|
||||
pdc += "\n".join(additional_io_constraints)
|
||||
tools.write_to_file(build_name + "_io.pdc", pdc)
|
||||
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")
|
||||
|
||||
# Placement Constraints (.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)
|
||||
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")
|
||||
|
||||
# Project (.tcl) -----------------------------------------------------------------------------------
|
||||
# Project (.tcl) -------------------------------------------------------------------------------
|
||||
|
||||
def _build_tcl(platform, sources, build_dir, build_name):
|
||||
def build_project(self):
|
||||
tcl = []
|
||||
|
||||
# Create project
|
||||
tcl.append(" ".join([
|
||||
"new_project",
|
||||
"-location {./impl}",
|
||||
"-name {}".format(tcl_name(build_name)),
|
||||
"-name {}".format(tcl_name(self._build_name)),
|
||||
"-project_description {}",
|
||||
"-block_mode 0",
|
||||
"-standalone_peripheral_initialization 0",
|
||||
|
@ -87,7 +109,7 @@ def _build_tcl(platform, sources, build_dir, build_name):
|
|||
"-adv_options {}"
|
||||
]))
|
||||
|
||||
die, package, speed = platform.device.split("-")
|
||||
die, package, speed = self.platform.device.split("-")
|
||||
tcl.append(" ".join([
|
||||
"set_device",
|
||||
"-family {PolarFire}",
|
||||
|
@ -111,46 +133,46 @@ def _build_tcl(platform, sources, build_dir, build_name):
|
|||
]))
|
||||
|
||||
# Add sources
|
||||
for filename, language, library, *copy in 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(build_name)))
|
||||
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(build_dir):
|
||||
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(build_name + "_io.pdc")))
|
||||
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(build_name + "_fp.pdc")))
|
||||
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(build_name + ".sdc")))
|
||||
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(build_name),
|
||||
"-module {}".format(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 {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),
|
||||
"-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(build_name),
|
||||
"-module {}".format(build_name),
|
||||
"-file impl/constraint/{}.sdc".format(self._build_name),
|
||||
"-module {}".format(self._build_name),
|
||||
"-input_type {constraint}"
|
||||
]))
|
||||
|
||||
|
@ -162,18 +184,19 @@ def _build_tcl(platform, sources, build_dir, build_name):
|
|||
tcl.append("run_tool -name {GENERATEPROGRAMMINGFILE}")
|
||||
|
||||
# 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 = []
|
||||
|
||||
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(
|
||||
"create_clock -name {clk} -period " + str(period) +
|
||||
" [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)):
|
||||
sdc.append(
|
||||
"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))
|
||||
|
||||
# generate sdc
|
||||
sdc += additional_timing_constraints
|
||||
tools.write_to_file(build_name + ".sdc", "\n".join(sdc))
|
||||
sdc += self.additional_timing_constraints
|
||||
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"):
|
||||
script_ext = ".bat"
|
||||
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"
|
||||
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,
|
||||
force_unix=False)
|
||||
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"]:
|
||||
shell = ["cmd", "/c"]
|
||||
else:
|
||||
|
@ -213,70 +241,6 @@ def _run_script(script):
|
|||
if subprocess.call(shell + [script]) != 0:
|
||||
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):
|
||||
if clk in self.clocks:
|
||||
if period != self.clocks[clk]:
|
||||
|
|
|
@ -13,59 +13,79 @@ 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):
|
||||
# F4PGAToolchain -------------------------------------------------------------------------------
|
||||
# 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"
|
||||
return r
|
||||
|
||||
def _build_io_pcf(named_sc, named_pc, build_name):
|
||||
def build_io_constraints(self):
|
||||
pcf = ""
|
||||
for sig, pins, others, resname in named_sc:
|
||||
for sig, pins, others, resname in self.named_sc:
|
||||
if len(pins) > 1:
|
||||
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:
|
||||
pcf += _format_io_pcf(sig, pins[0], others)
|
||||
tools.write_to_file(build_name + ".pcf", pcf)
|
||||
pcf += self._format_io_pcf(sig, pins[0], others)
|
||||
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 = []
|
||||
|
||||
# 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}")
|
||||
makefile.append(f"TOP_F={self._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))
|
||||
makefile.append("all: {top}_bit.h {top}.bin build/{top}.bit".format(top=self._build_name))
|
||||
# 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(
|
||||
device = platform.device,
|
||||
part = {"ql-eos-s3": "PU64"}.get(platform.device),
|
||||
verilog = f"{build_name}.v",
|
||||
top = build_name,
|
||||
pcf = f"{build_name}.pcf"
|
||||
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=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)")
|
||||
# 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)")
|
||||
|
||||
# Generate Makefile.
|
||||
tools.write_to_file("Makefile", "\n".join(makefile))
|
||||
|
||||
def _run_make():
|
||||
return "Makefile"
|
||||
|
||||
def run_script(self, script):
|
||||
make_cmd = ["make", "-j1"]
|
||||
|
||||
if which("ql_symbiflow") is None:
|
||||
|
@ -75,53 +95,3 @@ def _run_make():
|
|||
|
||||
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:
|
||||
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