From fac2b2a9fb0509bc0c7457b70565f1b9c671ce6d Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Mon, 22 Nov 2021 19:08:58 +0100 Subject: [PATCH] Add initial support for Anlogic devices --- litex/build/anlogic/__init__.py | 0 litex/build/anlogic/anlogic.py | 256 ++++++++++++++++++++++++++++++++ litex/build/anlogic/common.py | 51 +++++++ litex/build/anlogic/platform.py | 38 +++++ 4 files changed, 345 insertions(+) create mode 100644 litex/build/anlogic/__init__.py create mode 100644 litex/build/anlogic/anlogic.py create mode 100644 litex/build/anlogic/common.py create mode 100644 litex/build/anlogic/platform.py diff --git a/litex/build/anlogic/__init__.py b/litex/build/anlogic/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/litex/build/anlogic/anlogic.py b/litex/build/anlogic/anlogic.py new file mode 100644 index 000000000..fc00a6081 --- /dev/null +++ b/litex/build/anlogic/anlogic.py @@ -0,0 +1,256 @@ +# +# This file is part of LiteX. +# +# Copyright (c) 2021 Miodrag Milanovic +# Copyright (c) 2015-2021 Florent Kermarrec +# SPDX-License-Identifier: BSD-2-Clause + +import os +import math +import subprocess +import datetime +from shutil import which + +from migen.fhdl.structure import _Fragment + +from litex.build.generic_platform import * +from litex.build import tools + +# Constraints (.adc and .sdc) ---------------------------------------------------------------------- + +def _build_adc(named_sc, named_pc): + adc = [] + + 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: + line = f"set_pin_assignment {{{name}}} {{ LOCATION = {pin}; " + for c in other: + if isinstance(c, IOStandard): + line += f" IOSTANDARD = {c.name}; " + line += f"}}" + adc.append(line) + + if named_pc: + adc.extend(named_pc) + + with open("top.adc", "w") as f: + f.write("\n".join(adc)) + +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_al(name, family, device, files): + xml = [] + + date = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + # Set Device. + xml.append(f"") + xml.append(f"") + xml.append(f" {date}") + xml.append(f" 5.0.28716") + xml.append(f" 00000000") + xml.append(f" {name}") + xml.append(f" ") + xml.append(f" {family}") + xml.append(f" {device}") + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + # Add Sources. + for f, typ, lib in files: + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + + # Add IOs Constraints. + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + xml.append(f" {name}") + xml.append(f" auto") + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + xml.append(f" ") + xml.append(f" {date}") + xml.append(f" 0") + xml.append(f" true") + xml.append(f" ") + xml.append(f"") + + # Generate .al. + with open(name + ".al", "w") as f: + f.write("\n".join(xml)) + +def _build_tcl(name, architecture, package): + tcl = [] + + # Set Device. + tcl.append(f"import_device {architecture}.db -package {package}") + + # Add project. + tcl.append(f"open_project {name}.al") + # Rlaborate + tcl.append(f"elaborate -top {name}") + + # Add IOs Constraints. + tcl.append("read_adc top.adc") + + tcl.append("optimize_rtl") + + # Add SDC Constraints. + tcl.append("read_sdc top.sdc") + + # Perform PnR + tcl.append("optimize_gate") + tcl.append("legalize_phy_inst") + tcl.append("place") + tcl.append("route") + tcl.append(f"bitgen -bit \"{name}.bit\" -version 0X00 -g ucode:000000000000000000000000") + + # Generate .tcl. + with open("run.tcl", "w") as f: + f.write("\n".join(tcl)) + + +# TangDinastyToolchain ----------------------------------------------------------------------------------- + +def parse_device(device): + + devices = { + "EG4S20BG256" :[ "eagle_s20", "EG4", "BG256" ], + } + + if device not in devices.keys(): + raise ValueError("Invalid device {}".format(device)) + + (architecture, family, package) = devices[device] + return (architecture, family, package) + +class TangDinastyToolchain: + attr_translate = {} + + def __init__(self): + self.clocks = dict() + + def build(self, platform, fragment, + build_dir = "build", + build_name = "top", + run = True, + **kwargs): + + # Create build directory. + cwd = os.getcwd() + os.makedirs(build_dir, exist_ok=True) + 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 constraints file. + # IOs (.adc). + _build_adc( + named_sc = named_sc, + named_pc = named_pc + ) + + # Timings (.sdc) + _build_sdc( + clocks = self.clocks, + vns = v_output.ns + ) + + architecture, family, package = parse_device(platform.device) + # Generate project file (.al) + al = _build_al( + name = build_name, + family = family, + device = platform.device, + files = platform.sources) + + # Generate build script (.tcl) + script = _build_tcl( + name = build_name, + architecture = architecture, + package = package) + + # Run + if run: + if which("td") is None: + msg = "Unable to find Tang Dinasty toolchain, please:\n" + msg += "- Add Tang Dinasty toolchain to your $PATH." + raise OSError(msg) + + if subprocess.call(["td", "run.tcl"]) != 0: + raise OSError("Error occured during Tang Dinasty's script execution.") + + 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 diff --git a/litex/build/anlogic/common.py b/litex/build/anlogic/common.py new file mode 100644 index 000000000..9a5f5a479 --- /dev/null +++ b/litex/build/anlogic/common.py @@ -0,0 +1,51 @@ +# +# This file is part of LiteX. +# +# Copyright (c) 2021 Miodrag Milanovic +# Copyright (c) 2015-2021 Florent Kermarrec +# SPDX-License-Identifier: BSD-2-Clause + +from migen.fhdl.module import Module +from migen.genlib.resetsync import AsyncResetSynchronizer + +from litex.build.io import * + +# Anlogic AsyncResetSynchronizer --------------------------------------------------------------------- + +class AnlogicAsyncResetSynchronizerImpl(Module): + def __init__(self, cd, async_reset): + rst1 = Signal() + self.specials += [ + Instance("AL_MAP_SEQ", + p_DFFMODE = "FF", + p_REGSET = "SET", + p_SRMUX = "SR", + p_SRMODE = "ASYNC", + i_ce = 1, + i_d = 0, + i_sr = async_reset, + i_clk = cd.clk, + o_q = rst1), + Instance("AL_MAP_SEQ", + p_DFFMODE = "FF", + p_REGSET = "SET", + p_SRMUX = "SR", + p_SRMODE = "ASYNC", + i_ce = 1, + i_d = rst1, + i_sr = async_reset, + i_clk = cd.clk, + o_q = cd.rst) + ] + + +class AnlogicAsyncResetSynchronizer: + @staticmethod + def lower(dr): + return AnlogicAsyncResetSynchronizerImpl(dr.cd, dr.async_reset) + +# Anlogic Special Overrides -------------------------------------------------------------------------- + +anlogic_special_overrides = { + AsyncResetSynchronizer: AnlogicAsyncResetSynchronizer, +} diff --git a/litex/build/anlogic/platform.py b/litex/build/anlogic/platform.py new file mode 100644 index 000000000..b82092b93 --- /dev/null +++ b/litex/build/anlogic/platform.py @@ -0,0 +1,38 @@ +# +# This file is part of LiteX. +# +# Copyright (c) 2021 Miodrag Milanovic +# Copyright (c) 2015-2021 Florent Kermarrec +# SPDX-License-Identifier: BSD-2-Clause + +import os + +from litex.build.generic_platform import GenericPlatform +from litex.build.anlogic import common, anlogic + +# AnlogicPlatform ----------------------------------------------------------------------------------- + +class AnlogicPlatform(GenericPlatform): + bitstream_ext = ".fs" + + def __init__(self, device, *args, toolchain="td", **kwargs): + GenericPlatform.__init__(self, device, *args, **kwargs) + if toolchain == "td": + self.toolchain = anlogic.TangDinastyToolchain() + else: + raise ValueError("Unknown toolchain") + + def get_verilog(self, *args, special_overrides=dict(), **kwargs): + so = dict(common.anlogic_special_overrides) + so.update(special_overrides) + return GenericPlatform.get_verilog(self, *args, + special_overrides = so, + attr_translate = self.toolchain.attr_translate, + **kwargs) + + def build(self, *args, **kwargs): + return self.toolchain.build(self, *args, **kwargs) + + def add_period_constraint(self, clk, period): + if clk is None: return + self.toolchain.add_period_constraint(self, clk, period)