From bd702397d1439887bdb49ea6b71da2bcaa90a506 Mon Sep 17 00:00:00 2001 From: Mariusz Glebocki Date: Mon, 1 Jun 2020 13:39:33 +0200 Subject: [PATCH 1/4] build/xilinx: add Symbiflow toolchain support Signed-off-by: Mariusz Glebocki --- litex/build/xilinx/platform.py | 4 +- litex/build/xilinx/symbiflow.py | 321 ++++++++++++++++++++++++++++++++ 2 files changed, 324 insertions(+), 1 deletion(-) create mode 100644 litex/build/xilinx/symbiflow.py diff --git a/litex/build/xilinx/platform.py b/litex/build/xilinx/platform.py index c2b769ffa..57d9ef50b 100644 --- a/litex/build/xilinx/platform.py +++ b/litex/build/xilinx/platform.py @@ -5,7 +5,7 @@ import os from litex.build.generic_platform import GenericPlatform -from litex.build.xilinx import common, vivado, ise +from litex.build.xilinx import common, vivado, ise, symbiflow # XilinxPlatform ----------------------------------------------------------------------------------- @@ -20,6 +20,8 @@ class XilinxPlatform(GenericPlatform): self.toolchain = ise.XilinxISEToolchain() elif toolchain == "vivado": self.toolchain = vivado.XilinxVivadoToolchain() + elif toolchain == "symbiflow": + self.toolchain = symbiflow.SymbiflowToolchain() else: raise ValueError("Unknown toolchain") diff --git a/litex/build/xilinx/symbiflow.py b/litex/build/xilinx/symbiflow.py new file mode 100644 index 000000000..c8653b9ea --- /dev/null +++ b/litex/build/xilinx/symbiflow.py @@ -0,0 +1,321 @@ +# This file is Copyright (c) 2014-2020 Florent Kermarrec +# This file is Copyright (c) 2020 Antmicro +# License: BSD + +import os +import subprocess +import sys +import math +from typing import NamedTuple, Union, List +import re + +from migen.fhdl.structure import _Fragment, wrap, Constant +from migen.fhdl.specials import Instance + +from litex.build.generic_platform import * +from litex.build import tools + + +def _unwrap(value): + return value.value if isinstance(value, Constant) else value + +# Constraints (.xdc) ------------------------------------------------------------------------------- + +def _xdc_separator(msg): + r = "#"*80 + "\n" + r += "# " + msg + "\n" + r += "#"*80 + "\n" + return r + + +def _format_xdc_constraint(c): + if isinstance(c, Pins): + return "set_property LOC " + c.identifiers[0] + elif isinstance(c, IOStandard): + return "set_property IOSTANDARD " + c.name + elif isinstance(c, Drive): + return "set_property DRIVE " + str(c.strength) + elif isinstance(c, Misc): + return "set_property " + c.misc.replace("=", " ") + elif isinstance(c, Inverted): + return None + else: + raise ValueError("unknown constraint {}".format(c)) + + +def _format_xdc(signame, resname, *constraints): + fmt_c = [_format_xdc_constraint(c) for c in constraints] + fmt_r = resname[0] + ":" + str(resname[1]) + if resname[2] is not None: + fmt_r += "." + resname[2] + r = "# {}\n".format(fmt_r) + for c in fmt_c: + if c is not None: + r += c + " [get_ports {" + signame + "}]\n" + r += "\n" + return r + + +def _build_xdc(named_sc): + r = "" + for sig, pins, others, resname in named_sc: + if len(pins) > 1: + for i, p in enumerate(pins): + r += _format_xdc(sig + "[" + str(i) + "]", resname, Pins(p), *others) + elif pins: + r += _format_xdc(sig, resname, Pins(pins[0]), *others) + else: + r += _format_xdc(sig, resname, *others) + return r + +# PCF ---------------------------------------------------------------------------------------------- + +def _build_pcf(named_sc): + r = "" + current_resname = "" + for sig, pins, _, resname in named_sc: + if current_resname != resname[0]: + if current_resname: + r += "\n" + current_resname = resname[0] + r += f"# {current_resname}\n" + if len(pins) > 1: + for i, p in enumerate(pins): + r += f"set_io {sig}[{i}] {Pins(p).identifiers[0]}\n" + elif pins: + r += f"set_io {sig} {Pins(pins[0]).identifiers[0]}\n" + return r + +# SDC ---------------------------------------------------------------------------------------------- + +def _build_sdc(named_pc): + return "\n".join(named_pc) if named_pc else "" + +# 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(): + if tools.subprocess_call_filtered("make", []) != 0: + raise OSError("Subprocess failed") + +# 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 + + def _check_properties(self, platform): + if not self.symbiflow_device: + raise ValueError(f"symbiflow_device is not specified") + if not self.bitstream_device: + try: + self.bitstream_device = { + "xc7a": "artix7" + }[platform.device[:4]] + except KeyError: + raise ValueError(f"Unsupported 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", platform.device), + 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("PCF", f"{build_name}.pcf"), + 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=[ + "synth -t $(TOP) -v $(VERILOG) -d $(BITSTREAM_DEVICE) -p $(PARTNAME) -x $(XDC) > /dev/null" + ]), + Rule("$(TOP).net", ["$(TOP).eblif", "$(SDC)"], commands=[ + "pack -e $(TOP).eblif -d $(DEVICE) -s $(SDC) > /dev/null" + ]), + Rule("$(TOP).place", ["$(TOP).net", "$(PCF)"], commands=[ + "place -e $(TOP).eblif -d $(DEVICE) -p $(PCF) -n $(TOP).net -P $(PARTNAME) -s $(SDC) > /dev/null" + ]), + Rule("$(TOP).route", ["$(TOP).place"], commands=[ + "route -e $(TOP).eblif -d $(DEVICE) -s $(SDC) > /dev/null" + ]), + Rule("$(TOP).fasm", ["$(TOP).route"], commands=[ + "write_fasm -e $(TOP).eblif -d $(DEVICE) > /dev/null" + ]), + Rule("$(TOP).bit", ["$(TOP).fasm"], commands=[ + "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): + for clk, (period, phase) in sorted(self.clocks.items(), key=lambda x: x[0].duid): + rising_edge = math.floor(period/360.0 * phase * 1e3)/1e3 + falling_edge = math.floor(((rising_edge + period/2) % period) * 1.e3)/1e3 + platform.add_platform_command(f"create_clock -period {period} {{clk}} -waveform {{{{{rising_edge} {falling_edge}}}}}", clk=clk) + for from_, to in sorted(self.false_paths, key=lambda x: (x[0].duid, x[1].duid)): + platform.add_platform_command("set_clock_groups -exclusive -group {{{from_}}} -group {{{to}}}", from_=from_, to=to) + # Make sure add_*_constraint cannot be used again + del self.clocks + del self.false_paths + + # Yosys has limited support for real type. It requires that some values be multiplied + # by 1000 and passed as integers. For details, see: + # https://github.com/SymbiFlow/symbiflow-arch-defs/blob/master/xc/xc7/techmap/cells_map.v + def _fix_instance(self, instance): + if instance.of == "PLLE2_ADV": + for item in instance.items: + if isinstance(item, Instance.Parameter) and re.fullmatch("CLKOUT[0-9]_(PHASE|DUTY_CYCLE)", item.name): + item.value = wrap(math.floor(_unwrap(item.value) * 1000)) + + 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)) + tools.write_to_file(build_name + ".pcf", _build_pcf(named_sc)) + tools.write_to_file(build_name + ".sdc", _build_sdc(named_pc)) + + if run: + _run_make() + + os.chdir(cwd) + + return v_output.ns + + def add_period_constraint(self, platform, clk, period, phase=0): + clk.attr.add("keep") + phase = math.floor(phase % 360.0 * 1e3)/1e3 + period = math.floor(period*1e3)/1e3 # round to lowest picosecond + if clk in self.clocks: + if period != self.clocks[clk][0]: + raise ValueError("Clock already constrained to {:.2f}ns, new constraint to {:.2f}ns" + .format(self.clocks[clk][0], period)) + if phase != self.clocks[clk][1]: + raise ValueError("Clock already constrained with phase {:.2f}deg, new phase {:.2f}deg" + .format(self.clocks[clk][1], phase)) + self.clocks[clk] = (period, phase) + + def add_false_path_constraint(self, platform, from_, to): + if (from_, to) in self.false_paths or (to, from_) in self.false_paths: + return + from_.attr.add("keep") + to.attr.add("keep") + self.false_paths.add((from_, to)) + + +def symbiflow_build_args(parser): + pass + + +def symbiflow_build_argdict(args): + return dict() From 2bb2fbdbea0a288f173f0b5c505d67f104a1ac97 Mon Sep 17 00:00:00 2001 From: Mariusz Glebocki Date: Mon, 1 Jun 2020 13:40:32 +0200 Subject: [PATCH 2/4] platforms: add arty_symbiflow Signed-off-by: Mariusz Glebocki --- litex/boards/platforms/arty_symbiflow.py | 35 ++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 litex/boards/platforms/arty_symbiflow.py diff --git a/litex/boards/platforms/arty_symbiflow.py b/litex/boards/platforms/arty_symbiflow.py new file mode 100644 index 000000000..c931c34a2 --- /dev/null +++ b/litex/boards/platforms/arty_symbiflow.py @@ -0,0 +1,35 @@ +# This file is Copyright (c) 2015-2019 Florent Kermarrec +# This file is Copyright (c) 2020 Antmicro +# License: BSD + +from litex.build.generic_platform import * +from litex.build.xilinx import XilinxPlatform +from litex.build.openocd import OpenOCD +from litex.boards.platforms.arty import _io, _connectors + +# Platform ----------------------------------------------------------------------------------------- + +class Platform(XilinxPlatform): + default_clk_name = "clk100" + default_clk_period = 1e9/100e6 + + def __init__(self, variant="a7-35"): + device = { + "a7-35": {"part": "xc7a35tcsg324-1", "symbiflow-device": "xc7a50t_test"}, + }[variant] + XilinxPlatform.__init__(self, device["part"], _io, _connectors, toolchain="symbiflow") + self.toolchain.symbiflow_device = device["symbiflow-device"] + + def create_programmer(self): + bscan_spi = "bscan_spi_xc7a100t.bit" if "xc7a100t" in self.device else "bscan_spi_xc7a35t.bit" + return OpenOCD("openocd_xc7_ft2232.cfg", bscan_spi) + + def do_finalize(self, fragment): + # Prevent GenericPlatform from creating period constraint on input clock + pass + + def add_period_constraint(self, clk, period, phase=0): + if clk is None: return + if hasattr(clk, "p"): + clk = clk.p + self.toolchain.add_period_constraint(self, clk, period, phase) From ae121aacdf201b1aa34f3ddde93fc06d19d69cab Mon Sep 17 00:00:00 2001 From: Mariusz Glebocki Date: Mon, 1 Jun 2020 13:41:49 +0200 Subject: [PATCH 3/4] targets: add arty_symbiflow Signed-off-by: Mariusz Glebocki --- litex/boards/targets/arty_symbiflow.py | 78 ++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100755 litex/boards/targets/arty_symbiflow.py diff --git a/litex/boards/targets/arty_symbiflow.py b/litex/boards/targets/arty_symbiflow.py new file mode 100755 index 000000000..0869b1ed3 --- /dev/null +++ b/litex/boards/targets/arty_symbiflow.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 + +# This file is Copyright (c) 2015-2019 Florent Kermarrec +# This file is Copyright (c) 2020 Antmicro +# License: BSD + +import os +import argparse + +from migen import * + +from litex.boards.platforms import arty_symbiflow +from litex.build.xilinx.symbiflow import symbiflow_build_args, symbiflow_build_argdict + +from litex.soc.cores.clock import * +from litex.soc.integration.soc_core import * +from litex.soc.integration.soc_sdram import * +from litex.soc.integration.builder import * +from litex.soc.cores.led import LedChaser + +# CRG ---------------------------------------------------------------------------------------------- + +class _CRG(Module): + def __init__(self, platform, sys_clk_freq): + self.clock_domains.cd_sys = ClockDomain() + + clk100_ibuf = Signal() + clk100_buf = Signal() + self.specials += Instance("IBUF", i_I=platform.request("clk100"), o_O=clk100_ibuf) + self.specials += Instance("BUFG", i_I=clk100_ibuf, o_O=clk100_buf) + + self.submodules.pll = pll = S7PLL(speedgrade=-1) + self.comb += pll.reset.eq(~platform.request("cpu_reset")) + pll.register_clkin(clk100_buf, 100e6) + pll.create_clkout(self.cd_sys, sys_clk_freq) + + platform.add_period_constraint(clk100_buf, 1e9/100e6, 0) + platform.add_period_constraint(self.cd_sys.clk, 1e9/sys_clk_freq, 0) + platform.add_false_path_constraints(clk100_buf, self.cd_sys.clk) + +# BaseSoC ------------------------------------------------------------------------------------------ + +class BaseSoC(SoCCore): + def __init__(self, sys_clk_freq=int(60e6), with_ethernet=False, with_etherbone=False, **kwargs): + platform = arty_symbiflow.Platform() + + # SoCCore ---------------------------------------------------------------------------------- + SoCCore.__init__(self, platform, clk_freq=sys_clk_freq, **kwargs) + + # CRG -------------------------------------------------------------------------------------- + self.submodules.crg = _CRG(platform, sys_clk_freq) + + self.submodules.leds = LedChaser( + pads = Cat(*[platform.request("user_led", i) for i in range(4)]), + sys_clk_freq = sys_clk_freq) + self.add_csr("leds") + +# Build -------------------------------------------------------------------------------------------- + +def main(): + parser = argparse.ArgumentParser(description="LiteX SoC on Arty A7") + parser.add_argument("--build", action="store_true", help="Build bitstream") + parser.add_argument("--load", action="store_true", help="Load bitstream") + builder_args(parser) + soc_core_args(parser) + symbiflow_build_args(parser) + args = parser.parse_args() + + soc = BaseSoC(**soc_core_argdict(args)) + builder = Builder(soc, **builder_argdict(args)) + builder.build(**symbiflow_build_argdict(args), run=args.build) + + if args.load: + prog = soc.platform.create_programmer() + prog.load_bitstream(os.path.join(builder.gateware_dir, soc.build_name + ".bit")) + +if __name__ == "__main__": + main() From 7434376c07e2a697f65c09e809bfadcf557e4ac6 Mon Sep 17 00:00:00 2001 From: Mariusz Glebocki Date: Mon, 1 Jun 2020 13:58:44 +0200 Subject: [PATCH 4/4] test/test_targets: add arty_symbiflow Signed-off-by: Mariusz Glebocki --- test/test_targets.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/test/test_targets.py b/test/test_targets.py index 055954f10..ec767b15f 100644 --- a/test/test_targets.py +++ b/test/test_targets.py @@ -73,6 +73,13 @@ class TestTargets(unittest.TestCase): ]) self.assertEqual(errors, 0) + def test_arty_symbiflow(self): + from litex.boards.targets.arty_symbiflow import BaseSoC + errors = build_test([ + BaseSoC(**test_kwargs) + ]) + self.assertEqual(errors, 0) + # Kintex-7 def test_genesys2(self): from litex.boards.targets.genesys2 import BaseSoC @@ -112,21 +119,21 @@ class TestTargets(unittest.TestCase): def test_simple(self): platforms = [] # Xilinx - platforms += ["minispartan6"] # Spartan6 - platforms += ["arty", "netv2", "nexys4ddr", "nexys_video"] # Artix7 - platforms += ["kc705", "genesys2"] # Kintex7 - platforms += ["kcu105"] # Kintex Ultrascale + platforms += ["minispartan6"] # Spartan6 + platforms += ["arty", "netv2", "nexys4ddr", "nexys_video", "arty_symbiflow"] # Artix7 + platforms += ["kc705", "genesys2"] # Kintex7 + platforms += ["kcu105"] # Kintex Ultrascale # Altera/Intel - platforms += ["de0nano"] # Cyclone4 + platforms += ["de0nano"] # Cyclone4 # Lattice - platforms += ["tinyfpga_bx"] # iCE40 - platforms += ["machxo3"] # MachXO3 - platforms += ["versa_ecp5", "ulx3s"] # ECP5 + platforms += ["tinyfpga_bx"] # iCE40 + platforms += ["machxo3"] # MachXO3 + platforms += ["versa_ecp5", "ulx3s"] # ECP5 # Microsemi - platforms += ["avalanche"] # PolarFire + platforms += ["avalanche"] # PolarFire for p in platforms: with self.subTest(platform=p):