diff --git a/litex/build/xilinx/f4pga.py b/litex/build/xilinx/f4pga.py new file mode 100644 index 000000000..f2c5d6b9b --- /dev/null +++ b/litex/build/xilinx/f4pga.py @@ -0,0 +1,159 @@ +# +# This file is part of LiteX. +# +# Copyright (c) 2020 Antmicro +# Copyright (c) 2020 Florent Kermarrec +# SPDX-License-Identifier: BSD-2-Clause + +import os +import math + +from migen.fhdl.structure import _Fragment + +from litex.build.generic_platform import * +from litex.build.xilinx.vivado import _xdc_separator, _build_xdc +from litex.build import tools + +try: + from f4pga import Flow, make_flow_config + from f4pga.common import set_verbosity_level + from f4pga.cache import F4Cache + from f4pga.flow_config import ProjectFlowConfig +except ModuleNotFoundError as e: + raise ModuleNotFoundError("Try getting/updating F4PGA tool (https://github.com/chipsalliance/f4pga/)") from e + +F4CACHEPATH = '.f4cache' + + +# F4PGAToolchain ------------------------------------------------------------------------------- +# Formerly SymbiflowToolchain, Symbiflow has been renamed to F4PGA ----------------------------- + +class F4PGAToolchain: + attr_translate = { + "keep": ("dont_touch", "true"), + "no_retiming": ("dont_touch", "true"), + "async_reg": ("async_reg", "true"), + "mr_ff": ("mr_ff", "true"), # user-defined attribute + "ars_ff1": ("ars_ff1", "true"), # user-defined attribute + "ars_ff2": ("ars_ff2", "true"), # user-defined attribute + "no_shreg_extract": None + } + + def __init__(self): + self.clocks = dict() + self.false_paths = set() + self._partname = None + + def _generate_prj_flow(self, platform, build_name): + target = "bitstream" + + prj_flow_cfg_dict = {} + prj_flow_cfg_dict["dependencies"] = {} + prj_flow_cfg_dict["values"] = {} + prj_flow_cfg_dict[self._partname] = {} + + deps_cfg = prj_flow_cfg_dict["dependencies"] + deps_cfg["sources"] = [f for f,language,_ in platform.sources if language in ["verilog", "system_verilog"]] + deps_cfg["xdc"] = f"{build_name}.xdc" + deps_cfg["sdc"] = f"{build_name}.sdc" + deps_cfg["build_dir"] = os.getcwd() + deps_cfg["synth_log"] = f"{build_name}_synth.log" + deps_cfg["pack_log"] = f"{build_name}_pack.log" + deps_cfg["json"] = f"{build_name}.json" + + values_cfg = prj_flow_cfg_dict["values"] + values_cfg["top"] = build_name + values_cfg["part_name"] = self._partname + + prj_flow_cfg = ProjectFlowConfig("") + prj_flow_cfg.flow_cfg = prj_flow_cfg_dict + + flow_cfg = make_flow_config(prj_flow_cfg, self._partname) + + flow = Flow( + target=target, + cfg=flow_cfg, + f4cache=F4Cache(F4CACHEPATH) + ) + + print("\nProject status:") + flow.print_resolved_dependencies(0) + print("") + + return flow + + def _build_clock_constraints(self, platform): + platform.add_platform_command(_xdc_separator("Clock constraints")) + for clk, period in sorted(self.clocks.items(), key=lambda x: x[0].duid): + platform.add_platform_command( + "create_clock -period " + str(period) + + " {clk}", clk=clk) + + def build(self, platform, fragment, + build_dir = "build", + build_name = "top", + run = True, + enable_xpm = False, + **kwargs): + + # FIXME: prjxray-db doesn't have xc7a35ticsg324-1L and xc7a200t-sbg484-1 + # use closest replacement + self._partname = { + "xc7a35ticsg324-1L" : "xc7a35tcsg324-1", + "xc7a200t-sbg484-1" : "xc7a200tsbg484-1", + }.get(platform.device, platform.device) + + # 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 timing constraints + self._build_clock_constraints(platform) + + # 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) + + set_verbosity_level(2) + + # Generate design constraints + tools.write_to_file(build_name + ".xdc", _build_xdc(named_sc, named_pc)) + + flow = self._generate_prj_flow( + platform = platform, + build_name = build_name + ) + + if run: + try: + flow.execute() + except Exception as e: + print(e) + + flow.f4cache.save() + + 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): + # FIXME: false path constraints are currently not supported by the F4PGA toolchain + return diff --git a/litex/build/xilinx/platform.py b/litex/build/xilinx/platform.py index 38c840b8d..7f989a374 100644 --- a/litex/build/xilinx/platform.py +++ b/litex/build/xilinx/platform.py @@ -26,9 +26,9 @@ class XilinxPlatform(GenericPlatform): elif toolchain == "vivado": from litex.build.xilinx import vivado self.toolchain = vivado.XilinxVivadoToolchain() - elif toolchain == "symbiflow": - from litex.build.xilinx import symbiflow - self.toolchain = symbiflow.SymbiflowToolchain() + elif toolchain == "symbiflow" or toolchain == "f4pga": + from litex.build.xilinx import f4pga + self.toolchain = f4pga.F4PGAToolchain() elif toolchain == "yosys+nextpnr": from litex.build.xilinx import yosys_nextpnr self.toolchain = yosys_nextpnr.YosysNextpnrToolchain() diff --git a/litex/build/xilinx/symbiflow.py b/litex/build/xilinx/symbiflow.py deleted file mode 100644 index c3ba716aa..000000000 --- a/litex/build/xilinx/symbiflow.py +++ /dev/null @@ -1,265 +0,0 @@ -# -# This file is part of LiteX. -# -# Copyright (c) 2020 Antmicro -# Copyright (c) 2020 Florent Kermarrec -# SPDX-License-Identifier: BSD-2-Clause - -import os -import subprocess -import sys -import math -from typing import NamedTuple, Union, List -import re -from shutil import which - -from migen.fhdl.structure import _Fragment, wrap, Constant -from migen.fhdl.specials import Instance - -from litex.build.generic_platform import * -from litex.build.xilinx.vivado import _xdc_separator, _format_xdc, _build_xdc -from litex.build import tools -from litex.build.xilinx import common - - -def _unwrap(value): - return value.value if isinstance(value, Constant) else value - -# Makefile ----------------------------------------------------------------------------------------- - -class _MakefileGenerator: - class Var(NamedTuple): - name: str - value: Union[str, List[str]] = "" - - class Rule(NamedTuple): - target: str - prerequisites: List[str] = [] - commands: List[str] = [] - phony: bool = False - - def __init__(self, ast): - self.ast = ast - - def generate(self): - makefile = [] - for entry in self.ast: - if isinstance(entry, str): - makefile.append(entry) - elif isinstance(entry, self.Var): - if not entry.value: - makefile.append(f"{entry.name} :=") - elif isinstance(entry.value, list): - indent = " " * (len(entry.name) + len(" := ")) - line = f"{entry.name} := {entry.value[0]}" - for value in entry.value[1:]: - line += " \\" - makefile.append(line) - line = indent + value - makefile.append(line) - elif isinstance(entry.value, str): - makefile.append(f"{entry.name} := {entry.value}") - else: - raise - elif isinstance(entry, self.Rule): - makefile.append("") - if entry.phony: - makefile.append(f".PHONY: {entry.target}") - makefile.append(" ".join([f"{entry.target}:", *entry.prerequisites])) - for cmd in entry.commands: - makefile.append(f"\t{cmd}") - - return "\n".join(makefile) - - -def _run_make(): - make_cmd = ["make", "-j1"] - - if which("symbiflow_synth") is None: - msg = "Unable to find Symbiflow toolchain, please:\n" - msg += "- Add Symbiflow toolchain to your $PATH." - raise OSError(msg) - - if tools.subprocess_call_filtered(make_cmd, common.colors) != 0: - raise OSError("Error occured during Symbiflow's script execution.") - -# SymbiflowToolchain ------------------------------------------------------------------------------- - -class SymbiflowToolchain: - attr_translate = { - "keep": ("dont_touch", "true"), - "no_retiming": ("dont_touch", "true"), - "async_reg": ("async_reg", "true"), - "mr_ff": ("mr_ff", "true"), # user-defined attribute - "ars_ff1": ("ars_ff1", "true"), # user-defined attribute - "ars_ff2": ("ars_ff2", "true"), # user-defined attribute - "no_shreg_extract": None - } - - def __init__(self): - self.clocks = dict() - self.false_paths = set() - self.symbiflow_device = None - self.bitstream_device = None - self._partname = None - - def _check_properties(self, platform): - if not self.symbiflow_device: - try: - self.symbiflow_device = { - # FIXME: fine for now since only a few devices are supported, do more clever device re-mapping. - "xc7a35tcpg236-1" : "xc7a50t_test", - "xc7a35ticsg324-1L" : "xc7a50t_test", - "xc7a100tcsg324-1" : "xc7a100t_test", - "xc7a200t-sbg484-1" : "xc7a200t_test", - "xc7z010clg400-1" : "xc7z010_test", - "xc7z020clg400-1" : "xc7z020_test", - }[platform.device] - except KeyError: - raise ValueError(f"symbiflow_device is not specified") - if not self.bitstream_device: - try: - # bitstream_device points to a directory in prjxray database - # available bitstream_devices: artix7, kintex7, zynq7 - self.bitstream_device = { - "xc7a": "artix7", # xc7a35t, xc7a50t, xc7a100t, xc7a200t - "xc7z": "zynq7", # xc7z010, xc7z020 - }[platform.device[:4]] - except KeyError: - raise ValueError(f"Unsupported device: {platform.device}") - # FIXME: prjxray-db doesn't have xc7a35ticsg324-1L - use closest replacement - self._partname = { - "xc7a35ticsg324-1L" : "xc7a35tcsg324-1", - "xc7a100tcsg324-1" : "xc7a100tcsg324-1", - "xc7a200t-sbg484-1" : "xc7a200tsbg484-1", - "xc7z010clg400-1" : "xc7z010clg400-1", - "xc7z020clg400-1" : "xc7z020clg400-1", - }.get(platform.device, platform.device) - - def _generate_makefile(self, platform, build_name): - Var = _MakefileGenerator.Var - Rule = _MakefileGenerator.Rule - - makefile = _MakefileGenerator([ - "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n", - Var("TOP", build_name), - Var("PARTNAME", self._partname), - Var("DEVICE", self.symbiflow_device), - Var("BITSTREAM_DEVICE", self.bitstream_device), - "", - Var("VERILOG", [f for f,language,_ in platform.sources if language in ["verilog", "system_verilog"]]), - Var("MEM_INIT", [f"{name}" for name in os.listdir() if name.endswith(".init")]), - Var("SDC", f"{build_name}.sdc"), - Var("XDC", f"{build_name}.xdc"), - Var("ARTIFACTS", [ - "$(TOP).eblif", "$(TOP).frames", "$(TOP).ioplace", "$(TOP).net", - "$(TOP).place", "$(TOP).route", "$(TOP)_synth.*", - "*.bit", "*.fasm", "*.json", "*.log", "*.rpt", - "constraints.place" - ]), - - Rule("all", ["$(TOP).bit"], phony=True), - Rule("$(TOP).eblif", ["$(VERILOG)", "$(MEM_INIT)", "$(XDC)"], commands=[ - "symbiflow_synth -t $(TOP) -v $(VERILOG) -d $(BITSTREAM_DEVICE) -p $(PARTNAME) -x $(XDC) > /dev/null" - ]), - #Rule("$(TOP).net", ["$(TOP).eblif", "$(SDC)"], commands=[ #SDC conflicts with make -j2 and seems not needed - Rule("$(TOP).net", ["$(TOP).eblif"], commands=[ - "symbiflow_pack -e $(TOP).eblif -d $(DEVICE) -s $(SDC) > /dev/null" - ]), - Rule("$(TOP).place", ["$(TOP).net"], commands=[ - "symbiflow_place -e $(TOP).eblif -d $(DEVICE) -n $(TOP).net -P $(PARTNAME) -s $(SDC) > /dev/null" - ]), - Rule("$(TOP).route", ["$(TOP).place"], commands=[ - "symbiflow_route -e $(TOP).eblif -d $(DEVICE) -s $(SDC) > /dev/null" - ]), - Rule("$(TOP).fasm", ["$(TOP).route"], commands=[ - "symbiflow_write_fasm -e $(TOP).eblif -d $(DEVICE) > /dev/null" - ]), - Rule("$(TOP).bit", ["$(TOP).fasm"], commands=[ - "symbiflow_write_bitstream -d $(BITSTREAM_DEVICE) -f $(TOP).fasm -p $(PARTNAME) -b $(TOP).bit > /dev/null" - ]), - Rule("clean", phony=True, commands=[ - "rm -f $(ARTIFACTS)" - ]), - ]) - - tools.write_to_file("Makefile", makefile.generate()) - - def _build_clock_constraints(self, platform): - platform.add_platform_command(_xdc_separator("Clock constraints")) - for clk, period in sorted(self.clocks.items(), key=lambda x: x[0].duid): - platform.add_platform_command( - "create_clock -period " + str(period) + - " {clk}", clk=clk) - - def _fix_instance(self, instance): - pass - - def build(self, platform, fragment, - build_dir = "build", - build_name = "top", - run = True, - enable_xpm = False, - **kwargs): - - self._check_properties(platform) - - # Create build directory - os.makedirs(build_dir, exist_ok=True) - cwd = os.getcwd() - os.chdir(build_dir) - - # Finalize design - if not isinstance(fragment, _Fragment): - fragment = fragment.get_fragment() - platform.finalize(fragment) - - # Symbiflow-specific fixes - for instance in fragment.specials: - if isinstance(instance, Instance): - self._fix_instance(instance) - - # Generate timing constraints - self._build_clock_constraints(platform) - - # 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._generate_makefile( - platform = platform, - build_name = build_name - ) - - # Generate design constraints - tools.write_to_file(build_name + ".xdc", _build_xdc(named_sc, named_pc)) - - if run: - _run_make() - - 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): - # FIXME: false path constraints are currently not supported by the symbiflow toolchain - return - -def symbiflow_build_args(parser): - pass - - -def symbiflow_build_argdict(args): - return dict() diff --git a/litex/build/xilinx/yosys_nextpnr.py b/litex/build/xilinx/yosys_nextpnr.py index a1214cd1d..ddbce69d1 100644 --- a/litex/build/xilinx/yosys_nextpnr.py +++ b/litex/build/xilinx/yosys_nextpnr.py @@ -100,14 +100,14 @@ class YosysNextpnrToolchain: def __init__(self): self.clocks = dict() self.false_paths = set() - self.symbiflow_device = None + self.f4pga_device = None self.bitstream_device = None self._partname = None def _check_properties(self, platform): - if not self.symbiflow_device: + if not self.f4pga_device: try: - self.symbiflow_device = { + self.f4pga_device = { # FIXME: fine for now since only a few devices are supported, do more clever device re-mapping. "xc7a35ticsg324-1L" : "xc7a35t", "xc7a100tcsg324-1" : "xc7a35t", @@ -115,7 +115,7 @@ class YosysNextpnrToolchain: "xc7z020clg400-1" : "xc7z020", }[platform.device] except KeyError: - raise ValueError(f"symbiflow_device is not specified") + raise ValueError(f"f4pga_device is not specified") if not self.bitstream_device: try: # bitstream_device points to a directory in prjxray database @@ -143,7 +143,7 @@ class YosysNextpnrToolchain: "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n", Var("TOP", build_name), Var("PARTNAME", self._partname), - Var("DEVICE", self.symbiflow_device), + Var("DEVICE", self.f4pga_device), Var("BITSTREAM_DEVICE", self.bitstream_device), "", Var("DB_DIR", "/usr/share/nextpnr/prjxray-db"), #FIXME: resolve path @@ -253,12 +253,12 @@ class YosysNextpnrToolchain: self.clocks[clk] = period def add_false_path_constraint(self, platform, from_, to): - # FIXME: false path constraints are currently not supported by the symbiflow toolchain + # FIXME: false path constraints are currently not supported by the F4PGA toolchain return -def symbiflow_build_args(parser): +def f4pga_build_args(parser): pass -def symbiflow_build_argdict(args): +def f4pga_build_argdict(args): return dict()