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

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

View File

@ -13,85 +13,25 @@ from shutil import which, copyfile
from migen.fhdl.structure import _Fragment from 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

View File

@ -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))

View File

@ -14,29 +14,70 @@ 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 ------------------------------------------------------------------
_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 -push",
"verilog_defaults -add -defer", "verilog_defaults -add -defer",
"{read_files}", "{read_files}",
"verilog_defaults -pop", "verilog_defaults -pop",
"attrmap -tocase keep -imap keep=\"true\" keep=1 -imap keep=\"false\" keep=0 -remove keep=0", "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}", "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,27 +85,27 @@ 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",
"nextpnr-nexus --json {build_name}.json --pdc {build_name}.pdc --fasm {build_name}.fasm \ "nextpnr-nexus --json {build_name}.json --pdc {build_name}.pdc --fasm {build_name}.fasm \
--device {device} {timefailarg} {ignoreloops} --seed {seed}", --device {device} {timefailarg} {ignoreloops} --seed {seed}",
"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")

View File

@ -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")

View File

@ -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]:

View File

@ -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