From 000aabf85b5319680c74d760155cda6bfceb7f27 Mon Sep 17 00:00:00 2001 From: Franck Jullien Date: Fri, 17 Sep 2021 09:29:53 +0200 Subject: [PATCH 01/22] Initial Efinix Trion support --- litex/build/efinix/__init__.py | 1 + litex/build/efinix/common.py | 8 + litex/build/efinix/efinity.py | 375 +++++++++++++++++++++++++++++++ litex/build/efinix/platform.py | 52 +++++ litex/build/efinix/programmer.py | 27 +++ 5 files changed, 463 insertions(+) create mode 100644 litex/build/efinix/__init__.py create mode 100644 litex/build/efinix/common.py create mode 100644 litex/build/efinix/efinity.py create mode 100644 litex/build/efinix/platform.py create mode 100644 litex/build/efinix/programmer.py diff --git a/litex/build/efinix/__init__.py b/litex/build/efinix/__init__.py new file mode 100644 index 000000000..3d6f985ec --- /dev/null +++ b/litex/build/efinix/__init__.py @@ -0,0 +1 @@ +from litex.build.efinix.programmer import EfinixProgrammer \ No newline at end of file diff --git a/litex/build/efinix/common.py b/litex/build/efinix/common.py new file mode 100644 index 000000000..9c104ab69 --- /dev/null +++ b/litex/build/efinix/common.py @@ -0,0 +1,8 @@ +# +# This file is part of LiteX. +# +# Copyright (c) 2021 Franck Jullien +# Copyright (c) 2015-2018 Florent Kermarrec +# SPDX-License-Identifier: BSD-2-Clause + +efinix_special_overrides = {} \ No newline at end of file diff --git a/litex/build/efinix/efinity.py b/litex/build/efinix/efinity.py new file mode 100644 index 000000000..74ecae303 --- /dev/null +++ b/litex/build/efinix/efinity.py @@ -0,0 +1,375 @@ +# +# This file is part of LiteX. +# +# Copyright (c) 2021 Franck Jullien +# Copyright (c) 2015-2018 Florent Kermarrec +# SPDX-License-Identifier: BSD-2-Clause + +import os +import subprocess +import pathlib +import math +import sys +import site +import subprocess +import inspect + +from lxml import etree + +from litex.build.generic_platform import * + +from migen.fhdl.structure import _Fragment +from migen.fhdl.tools import * +from migen.fhdl.namer import build_namespace + +from litex.build.generic_platform import Pins, IOStandard, Misc +from litex.build import tools + +_reserved_keywords = { + "always", "and", "assign", "automatic", "begin", "buf", "bufif0", "bufif1", + "case", "casex", "casez", "cell", "cmos", "config", "deassign", "default", + "defparam", "design", "disable", "edge", "else", "end", "endcase", + "endconfig", "endfunction", "endgenerate", "endmodule", "endprimitive", + "endspecify", "endtable", "endtask", "event", "for", "force", "forever", + "fork", "function", "generate", "genvar", "highz0", "highz1", "if", + "ifnone", "incdir", "include", "initial", "inout", "input", + "instance", "integer", "join", "large", "liblist", "library", "localparam", + "macromodule", "medium", "module", "nand", "negedge", "nmos", "nor", + "noshowcancelled", "not", "notif0", "notif1", "or", "output", "parameter", + "pmos", "posedge", "primitive", "pull0", "pull1" "pulldown", + "pullup", "pulsestyle_onevent", "pulsestyle_ondetect", "remos", "real", + "realtime", "reg", "release", "repeat", "rnmos", "rpmos", "rtran", + "rtranif0", "rtranif1", "scalared", "showcancelled", "signed", "small", + "specify", "specparam", "strong0", "strong1", "supply0", "supply1", + "table", "task", "time", "tran", "tranif0", "tranif1", "tri", "tri0", + "tri1", "triand", "trior", "trireg", "unsigned", "use", "vectored", "wait", + "wand", "weak0", "weak1", "while", "wire", "wor","xnor", "xor", "do" +} + +def get_pin_direction(fragment, platform, pinname): + ios = platform.constraint_manager.get_io_signals() + sigs = list_signals(fragment) | list_special_ios(fragment, True, True, True) + special_outs = list_special_ios(fragment, False, True, True) + inouts = list_special_ios(fragment, False, False, True) + targets = list_targets(fragment) | special_outs + + ns = build_namespace(list_signals(fragment) \ + | list_special_ios(fragment, True, True, True) \ + | ios, _reserved_keywords) + ns.clock_domains = fragment.clock_domains + + dir = "Unknown" + + for sig in sorted(ios, key=lambda x: x.duid): + # Better idea ??? + if (pinname.split('[')[0] == ns.get_name(sig)): + if sig in inouts: + dir = "inout" + elif sig in targets: + dir = "output" + else: + dir = "input" + + return dir + +# Timing Constraints (.sdc) ------------------------------------------------------------------------ + +def _build_sdc(clocks, false_paths, vns, named_sc, build_name, additional_sdc_commands): + sdc = [] + + # Clock constraints + for clk, period in sorted(clocks.items(), key=lambda x: x[0].duid): + is_port = False + for sig, pins, others, resname in named_sc: + if sig == vns.get_name(clk): + is_port = True + if is_port: + tpl = "create_clock -name {clk} -period {period} [get_ports {{{clk}}}]" + sdc.append(tpl.format(clk=vns.get_name(clk), period=str(period))) + else: + tpl = "create_clock -name {clk} -period {period} [get_nets {{{clk}}}]" + sdc.append(tpl.format(clk=vns.get_name(clk), period=str(period))) + + # False path constraints + for from_, to in sorted(false_paths, key=lambda x: (x[0].duid, x[1].duid)): + tpl = "set_false_path -from [get_clocks {{{from_}}}] -to [get_clocks {{{to}}}]" + sdc.append(tpl.format(from_=vns.get_name(from_), to=vns.get_name(to))) + + # Add additional commands + sdc += additional_sdc_commands + + # Generate .sdc + tools.write_to_file("{}.sdc".format(build_name), "\n".join(sdc)) + +# Peripheral configuration ------------------------------------------------------------------------ + +def _create_gpio_instance(fragment, platform, sig, pins): + l = "" + if len(pins) > 1: + l = ',{},0'.format(len(pins) - 1) + d = get_pin_direction(fragment, platform, sig) + return 'design.create_{d}_gpio("{name}"{len})'.format(d =d, name=sig, len=l) + +def _format_constraint(c, signame, fmt_r, fragment, platform): + # IO location constraints + if isinstance(c, Pins): + tpl = 'design.assign_pkg_pin("{signame}","{pin}")\n' + return tpl.format(signame=signame, name=fmt_r, pin=c.identifiers[0]) + + # IO standard property + elif isinstance(c, IOStandard): + prop = "" + valid = ['3.3_V_LVTTL_/_LVCMOS', '2.5_V_LVCMOS', '1.8_V_LVCMOS'] + if c.name in valid: + prop = 'IO_STANDARD' + + if prop == "": + print("{} has a wrong IOStandard format [{}]".format(signame, c.name)) + print("Sould be selected from {}\n".format(valid)) + # Print error, warning ?? + return "" + + tpl = 'design.set_property( "{signame}","{prop}","{val}")\n' + return tpl.format(signame=signame, prop=prop, val=c.name) + + # Others constraints + elif isinstance(c, Misc): + prop = "" + if c.misc in ['WEAK_PULLUP', 'WEAK_PULLDOWN']: + prop = 'PULL_OPTION' + val = c.misc + + if 'DRIVE_STRENGTH' in c.misc: + prop = 'DRIVE_STRENGTH' + val = c.misc.split('=')[1] + + if prop == "": + # Print error, warning ?? + return "" + + tpl = 'design.set_property( "{signame}","{prop}","{val}")\n' + return tpl.format(signame=signame, prop=prop, val=val) + +def _format_conf_constraint(signame, pin, others, resname, fragment, platform): + fmt_r = "{}:{}".format(*resname[:2]) + if resname[2] is not None: + fmt_r += "." + resname[2] + fmt_c = [_format_constraint(c, signame, fmt_r, fragment, platform) for c in ([Pins(pin)] + others)] + return ''.join(fmt_c) + +def _build_iface_conf(named_sc, named_pc, fragment, platform): + conf = [] + inst = [] + + # GPIO + for sig, pins, others, resname in named_sc: + inst.append(_create_gpio_instance(fragment, platform, sig, pins)) + if len(pins) > 1: + for i, p in enumerate(pins): + conf.append(_format_conf_constraint("{}[{}]".format(sig, i), p, others, resname, fragment, platform)) + else: + conf.append(_format_conf_constraint(sig, pins[0], others, resname, fragment, platform)) + if named_pc: + conf.append("\n\n".join(named_pc)) + + # PLL + #inst.append() + + conf = inst + conf + + return "\n".join(conf) + +def _build_peri(efinity_path, build_name, partnumber, named_sc, named_pc, fragment, platform): + pythonpath = "" + + header = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + + header += """ +import os +import sys + +home = '{0}' + +os.environ['EFXPT_HOME'] = home + '/pt' +os.environ['EFXPGM_HOME'] = home + '/pgm' +os.environ['EFXDBG_HOME'] = home + '/debugger' +os.environ['EFXIPM_HOME'] = home + '/ipm' + +sys.path.append(home + '/pt/bin') +sys.path.append(home + '/lib/python3.8/site-packages') + +from api_service.design import DesignAPI +from api_service.device import DeviceAPI + +is_verbose = {1} + +design = DesignAPI(is_verbose) +device = DeviceAPI(is_verbose) + +design.create('{2}', '{3}', './../build', overwrite=True) + +""" + + header = header.format(efinity_path, 'True', build_name, partnumber) + + conf = _build_iface_conf(named_sc, named_pc, fragment, platform) + + footer = """ +# Check design, generate constraints and reports + #design.generate(enable_bitstream=True) +# Save the configured periphery design +design.save() + """ + + tools.write_to_file("iface.py", header + conf + footer) + + subprocess.call([efinity_path + '/bin/python3', 'iface.py']) + +# Project configuration ------------------------------------------------------------------------ + +def _build_xml(name, partnumber, build_name, sources, additional_xml_commands): + + test = ' +# Copyright (c) 2015-2018 Florent Kermarrec +# SPDX-License-Identifier: BSD-2-Clause + +import os + +from litex.build.generic_platform import GenericPlatform +from litex.build.efinix import common, efinity + +# EfinixPlatform ----------------------------------------------------------------------------------- + +class EfinixPlatform(GenericPlatform): + bitstream_ext = ".bit" + + def __init__(self, *args, toolchain="efinity", **kwargs): + GenericPlatform.__init__(self, *args, **kwargs) + + if 'LITEX_ENV_EFINITY' in os.environ: + self.efinity_path = os.environ['LITEX_ENV_EFINITY'].rstrip('/') + os.environ['EFINITY_HOME'] = self.efinity_path + else: + raise OSError('Unable to find Efinity toolchain, please set LITEX_ENV_EFINITY to ${install_dir}') + + if toolchain == "efinity": + self.toolchain = efinity.EfinityToolchain(self.efinity_path) + else: + raise ValueError("Unknown toolchain") + + def get_verilog(self, *args, special_overrides=dict(), **kwargs): + so = dict(common.efinix_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 + if hasattr(clk, "p"): + clk = clk.p + self.toolchain.add_period_constraint(self, clk, period) + + def add_false_path_constraint(self, from_, to): + if hasattr(from_, "p"): + from_ = from_.p + if hasattr(to, "p"): + to = to.p + self.toolchain.add_false_path_constraint(self, from_, to) \ No newline at end of file diff --git a/litex/build/efinix/programmer.py b/litex/build/efinix/programmer.py new file mode 100644 index 000000000..1981b5fa3 --- /dev/null +++ b/litex/build/efinix/programmer.py @@ -0,0 +1,27 @@ +# +# This file is part of LiteX. +# +# Copyright (c) 2021 Franck Jullien +# SPDX-License-Identifier: BSD-2-Clause + +import os +import sys + +from litex.build.generic_programmer import GenericProgrammer + +class EfinixProgrammer(GenericProgrammer): + + def __init__(self, cable_name=""): + self.cable_name = cable_name + if 'LITEX_ENV_EFINITY' in os.environ: + self.efinity_path = os.environ['LITEX_ENV_EFINITY'].rstrip('/') + os.environ['EFINITY_HOME'] = self.efinity_path + else: + raise OSError('Unable to find Efinity toolchain, please set LITEX_ENV_EFINITY to ${install_dir}') + + def load_bitstream(self, bitstream_file, cable_suffix=""): + os.environ['EFXPGM_HOME'] = self.efinity_path + '/pgm' + self.call([self.efinity_path + '/bin/python3', self.efinity_path + + 'pgm/bin/efx_pgm/ftdi_program.py', bitstream_file, + "-m", "jtag" + ]) From 0278d3eee85858843823bf8b85f67779baf18b8e Mon Sep 17 00:00:00 2001 From: Franck Jullien Date: Mon, 20 Sep 2021 07:51:26 +0200 Subject: [PATCH 02/22] generic_platform: add a method to delete a constraint --- litex/build/generic_platform.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/litex/build/generic_platform.py b/litex/build/generic_platform.py index 4b4511cd7..a09156e34 100644 --- a/litex/build/generic_platform.py +++ b/litex/build/generic_platform.py @@ -190,6 +190,11 @@ class ConstraintManager: def add_extension(self, io): self.available.extend(io) + def delete(self, signal): + for res, obj in self.matched: + if obj == signal: + self.matched.remove((res, obj)) + def request(self, name, number=None, loose=False): resource = _lookup(self.available, name, number, loose) if resource is None: @@ -313,6 +318,9 @@ class GenericPlatform: def request(self, *args, **kwargs): return self.constraint_manager.request(*args, **kwargs) + def delete(self, *args, **kwargs): + return self.constraint_manager.delete(*args, **kwargs) + def request_all(self, *args, **kwargs): return self.constraint_manager.request_all(*args, **kwargs) From 9b6ae2ff03b0d4e3e793c4b97583c28709823e9c Mon Sep 17 00:00:00 2001 From: Franck Jullien Date: Mon, 20 Sep 2021 07:56:53 +0200 Subject: [PATCH 03/22] efinix: support PLL, add dbparser and ifacewriter --- litex/build/efinix/__init__.py | 4 +- litex/build/efinix/dbparser.py | 96 +++++++++++++++++++++++++++ litex/build/efinix/efinity.py | 85 +++++++++--------------- litex/build/efinix/ifacewriter.py | 95 ++++++++++++++++++++++++++ litex/build/efinix/platform.py | 19 +++++- litex/soc/cores/clock/__init__.py | 3 + litex/soc/cores/clock/efinix_trion.py | 92 +++++++++++++++++++++++++ 7 files changed, 338 insertions(+), 56 deletions(-) create mode 100644 litex/build/efinix/dbparser.py create mode 100644 litex/build/efinix/ifacewriter.py create mode 100644 litex/soc/cores/clock/efinix_trion.py diff --git a/litex/build/efinix/__init__.py b/litex/build/efinix/__init__.py index 3d6f985ec..c0efa1e9e 100644 --- a/litex/build/efinix/__init__.py +++ b/litex/build/efinix/__init__.py @@ -1 +1,3 @@ -from litex.build.efinix.programmer import EfinixProgrammer \ No newline at end of file +from litex.build.efinix.programmer import EfinixProgrammer +from litex.build.efinix.dbparser import EfinixDbParser +from litex.build.efinix.ifacewriter import InterfaceWriter \ No newline at end of file diff --git a/litex/build/efinix/dbparser.py b/litex/build/efinix/dbparser.py new file mode 100644 index 000000000..100b92e5f --- /dev/null +++ b/litex/build/efinix/dbparser.py @@ -0,0 +1,96 @@ +import os +import csv +import re + +import xml.etree.ElementTree as et + +namespaces = { 'efxpt' : 'http://www.efinixinc.com/peri_device_db', + 'xi' : 'http://www.w3.org/2001/XInclude' +} + +class EfinixDbParser(): + def __init__(self, efinity_path, device): + self.efinity_db_path = efinity_path + '/pt/db/' + self.device = device + + def get_device_map(self, device): + with open(self.efinity_db_path + 'devicemap.csv') as f: + reader = csv.reader(f) + data = list(reader) + + for d in data: + if d[0] == device: + print(d) + return d + + return None + + def get_package_file_name(self, dmap): + tree = et.parse(self.efinity_db_path + dmap[2]) + root = tree.getroot() + inc = root.findall('xi:include', namespaces) + for i in inc: + if 'package' in i.get('href'): + return i.get('href').split('/')[1] + + return None + + def get_die_file_name(self, dmap): + tree = et.parse(self.efinity_db_path + dmap[2]) + root = tree.getroot() + inc = root.findall('xi:include', namespaces) + for i in inc: + if 'die' in i.get('href'): + return i.get('href').split('/')[1] + + return None + + def get_pad_name_xml(self, dmap, pin): + package_file = self.get_package_file_name(dmap) + tree = et.parse(self.efinity_db_path + 'package/' + package_file) + root = tree.getroot() + + pm = root.findall('efxpt:package_map', namespaces) + for p in pm: + if p.get('package_pin') == pin: + return (p.get('pad_name')) + + return None + + def get_instance_name_xml(self, dmap, pad): + die = self.get_die_file_name(dmap) + tree = et.parse(self.efinity_db_path + 'die/' + die) + root = tree.getroot() + + ipd = root.find('efxpt:io_pad_definition', namespaces) + ios = ipd.findall('efxpt:io_pad_map', namespaces) + for io in ios: + if io.get('pad_name') == pad: + return (io.get('instance')) + + return None + + def get_pll_inst_from_gpio_inst(self, dmap, inst): + die = self.get_die_file_name(dmap) + tree = et.parse(self.efinity_db_path + 'die/' + die) + root = tree.getroot() + + peri = root.findall('efxpt:periphery_instance', namespaces) + for p in peri: + if p.get('block') == 'pll': + conn = p.findall('efxpt:single_conn', namespaces) + for c in conn: + if c.get('instance') == inst: + refclk_no = 0 + if c.get('index') == '3': + refclk_no = 1 + return (p.get('name'), refclk_no) + + return None + + def get_pll_inst_from_pin(self, pin): + dmap = self.get_device_map(self.device) + pad = self.get_pad_name_xml(dmap, pin) + inst = self.get_instance_name_xml(dmap, pad) + + return self.get_pll_inst_from_gpio_inst(dmap, inst) diff --git a/litex/build/efinix/efinity.py b/litex/build/efinix/efinity.py index 74ecae303..517620c6f 100644 --- a/litex/build/efinix/efinity.py +++ b/litex/build/efinix/efinity.py @@ -25,6 +25,8 @@ from migen.fhdl.namer import build_namespace from litex.build.generic_platform import Pins, IOStandard, Misc from litex.build import tools +from litex.build.efinix import InterfaceWriter + _reserved_keywords = { "always", "and", "assign", "automatic", "begin", "buf", "bufif0", "bufif1", "case", "casex", "casez", "cell", "cmos", "config", "deassign", "default", @@ -108,7 +110,7 @@ def _create_gpio_instance(fragment, platform, sig, pins): if len(pins) > 1: l = ',{},0'.format(len(pins) - 1) d = get_pin_direction(fragment, platform, sig) - return 'design.create_{d}_gpio("{name}"{len})'.format(d =d, name=sig, len=l) + return 'design.create_{d}_gpio("{name}"{len})'.format(d=d, name=sig, len=l) def _format_constraint(c, signame, fmt_r, fragment, platform): # IO location constraints @@ -157,13 +159,16 @@ def _format_conf_constraint(signame, pin, others, resname, fragment, platform): fmt_c = [_format_constraint(c, signame, fmt_r, fragment, platform) for c in ([Pins(pin)] + others)] return ''.join(fmt_c) -def _build_iface_conf(named_sc, named_pc, fragment, platform): +def _build_iface_gpio(named_sc, named_pc, fragment, platform, specials_gpios): conf = [] inst = [] # GPIO for sig, pins, others, resname in named_sc: - inst.append(_create_gpio_instance(fragment, platform, sig, pins)) + if sig not in specials_gpios: + inst.append(_create_gpio_instance(fragment, platform, sig, pins)) + else: + continue if len(pins) > 1: for i, p in enumerate(pins): conf.append(_format_conf_constraint("{}[{}]".format(sig, i), p, others, resname, fragment, platform)) @@ -172,56 +177,20 @@ def _build_iface_conf(named_sc, named_pc, fragment, platform): if named_pc: conf.append("\n\n".join(named_pc)) - # PLL - #inst.append() - conf = inst + conf return "\n".join(conf) -def _build_peri(efinity_path, build_name, partnumber, named_sc, named_pc, fragment, platform): +def _build_peri(efinity_path, build_name, partnumber, named_sc, named_pc, fragment, platform, additional_iface_commands, specials_gpios): pythonpath = "" - header = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + header = platform.toolchain.ifacewriter.header(build_name, partnumber) + gen = platform.toolchain.ifacewriter.generate() + gpio = _build_iface_gpio(named_sc, named_pc, fragment, platform, specials_gpios) + add = '\n'.join(additional_iface_commands) + footer = platform.toolchain.ifacewriter.footer() - header += """ -import os -import sys - -home = '{0}' - -os.environ['EFXPT_HOME'] = home + '/pt' -os.environ['EFXPGM_HOME'] = home + '/pgm' -os.environ['EFXDBG_HOME'] = home + '/debugger' -os.environ['EFXIPM_HOME'] = home + '/ipm' - -sys.path.append(home + '/pt/bin') -sys.path.append(home + '/lib/python3.8/site-packages') - -from api_service.design import DesignAPI -from api_service.device import DeviceAPI - -is_verbose = {1} - -design = DesignAPI(is_verbose) -device = DeviceAPI(is_verbose) - -design.create('{2}', '{3}', './../build', overwrite=True) - -""" - - header = header.format(efinity_path, 'True', build_name, partnumber) - - conf = _build_iface_conf(named_sc, named_pc, fragment, platform) - - footer = """ -# Check design, generate constraints and reports - #design.generate(enable_bitstream=True) -# Save the configured periphery design -design.save() - """ - - tools.write_to_file("iface.py", header + conf + footer) + tools.write_to_file("iface.py", header + gen + gpio + add + footer) subprocess.call([efinity_path + '/bin/python3', 'iface.py']) @@ -295,6 +264,9 @@ class EfinityToolchain(): self.efinity_path = efinity_path self.additional_sdc_commands = [] self.additional_xml_commands = [] + self.ifacewriter = InterfaceWriter("iface.py", efinity_path) + self.specials_gpios = [] + self.additional_iface_commands = [] def build(self, platform, fragment, build_dir = "build", @@ -319,6 +291,9 @@ class EfinityToolchain(): v_output.write(v_file) platform.add_source(v_file) + sc = platform.constraint_manager.get_sig_constraints() + self.specials_gpios = [(v_output.ns.get_name(sig)) for sig in self.specials_gpios] + if platform.verilog_include_paths: self.options['includ_path'] = '{' + ';'.join(platform.verilog_include_paths) + '}' @@ -343,13 +318,15 @@ class EfinityToolchain(): # Generate constraints file (.peri.xml) _build_peri( - efinity_path = self.efinity_path, - build_name = build_name, - partnumber = platform.device, - named_sc = named_sc, - named_pc = named_pc, - fragment = fragment, - platform = platform) + efinity_path = self.efinity_path, + build_name = build_name, + partnumber = platform.device, + named_sc = named_sc, + named_pc = named_pc, + fragment = fragment, + platform = platform, + additional_iface_commands = self.additional_iface_commands, + specials_gpios = self.specials_gpios) # Run if run: @@ -372,4 +349,4 @@ class EfinityToolchain(): from_.attr.add("keep") to.attr.add("keep") if (to, from_) not in self.false_paths: - self.false_paths.add((from_, to)) \ No newline at end of file + self.false_paths.add((from_, to)) diff --git a/litex/build/efinix/ifacewriter.py b/litex/build/efinix/ifacewriter.py new file mode 100644 index 000000000..2aaf80a24 --- /dev/null +++ b/litex/build/efinix/ifacewriter.py @@ -0,0 +1,95 @@ +import os +from litex.build import tools + +class InterfaceWriter(): + def __init__(self, filename, efinity_path): + self.file = filename + self.efinity_path = efinity_path + self.blocks = [] + + def header(self, build_name, partnumber): + header = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + header += """ +import os +import sys +import pprint + +home = '{0}' + +os.environ['EFXPT_HOME'] = home + '/pt' +os.environ['EFXPGM_HOME'] = home + '/pgm' +os.environ['EFXDBG_HOME'] = home + '/debugger' +os.environ['EFXIPM_HOME'] = home + '/ipm' + +sys.path.append(home + '/pt/bin') +sys.path.append(home + '/lib/python3.8/site-packages') + +from api_service.design import DesignAPI +from api_service.device import DeviceAPI + +is_verbose = {1} + +design = DesignAPI(is_verbose) +device = DeviceAPI(is_verbose) + +design.create('{2}', '{3}', './../build', overwrite=True) + +""" + return header.format(self.efinity_path, 'True', build_name, partnumber) + + def get_block(self, name): + for b in self.blocks: + if b['name'] == name: + return b + return None + + def generate_pll(self, block, verbose=True): + name = block['name'] + cmd = '# ---------- PLL {} ---------\n'.format(name) + cmd += 'design.create_block("{}", block_type="PLL")\n'.format(name) + cmd += 'design.gen_pll_ref_clock("{}", pll_res="{}", refclk_src="{}", refclk_name="{}", ext_refclk_no="{}")\n\n' \ + .format(name, block['resource'], block['input_clock'], block['input_clock_name'], block['clock_no']) + + cmd += 'pll_config = {{ "REFCLK_FREQ":"{}" }}\n'.format(block['input_freq'] / 1e6) + cmd += 'design.set_property("{}", pll_config, block_type="PLL")\n\n'.format(name) + + # Output clock 0 is enabled by default + for i, clock in enumerate(block['clk_out']): + if i > 0: + cmd += 'pll_config = {{ "CLKOUT{}_EN":"1", "CLKOUT{}_PIN":"{}" }}\n'.format(i, i, clock[0]) + cmd += 'design.set_property("{}", pll_config, block_type="PLL")\n\n'.format(name) + + cmd += 'target_freq = {\n' + for i, clock in enumerate(block['clk_out']): + cmd += ' "CLKOUT{}_FREQ": "{}",\n'.format(i, clock[1] / 1e6) + cmd += ' "CLKOUT{}_PHASE": "{}",\n'.format(i, clock[2]) + cmd += '}\n' + cmd += 'calc_result = design.auto_calc_pll_clock("{}", target_freq)\n\n'.format(name) + + + if verbose: + cmd += 'print("#### {} ####")\n'.format(name) + cmd += 'clksrc_info = design.trace_ref_clock("{}", block_type="PLL")\n'.format(name) + cmd += 'pprint.pprint(clksrc_info)\n' + cmd += 'clock_source_prop = ["REFCLK_SOURCE", "EXT_CLK", "CLKOUT0_EN", "CLKOUT1_EN","REFCLK_FREQ", "RESOURCE"]\n' + cmd += 'clock_source_prop += ["M", "N", "O", "CLKOUT0_DIV", "CLKOUT2_DIV", "VCO_FREQ", "PLL_FREQ"]\n' + cmd += 'prop_map = design.get_property("{}", clock_source_prop, block_type="PLL")\n'.format(name) + cmd += 'pprint.pprint(prop_map)\n' + + cmd += '# ---------- END PLL {} ---------\n\n'.format(name) + return cmd + + def generate(self): + output = '' + for b in self.blocks: + if b['type'] == 'PLL': + output += self.generate_pll(b) + return output + + def footer(self): + return """ +# Check design, generate constraints and reports +#design.generate(enable_bitstream=True) +# Save the configured periphery design +design.save()""" + diff --git a/litex/build/efinix/platform.py b/litex/build/efinix/platform.py index fd85b938e..6f223bcdc 100644 --- a/litex/build/efinix/platform.py +++ b/litex/build/efinix/platform.py @@ -49,4 +49,21 @@ class EfinixPlatform(GenericPlatform): from_ = from_.p if hasattr(to, "p"): to = to.p - self.toolchain.add_false_path_constraint(self, from_, to) \ No newline at end of file + self.toolchain.add_false_path_constraint(self, from_, to) + + # TODO: fix this when pin is like p = platform.request("sdios") + # get_pin_location(p[1]) + # not tested with subsignal like get_pin_location(p.clk) + def get_pin_location(self, sig): + sc = self.constraint_manager.get_sig_constraints() + for s, pins, others, resource in sc: + if s == sig: + return pins[0] + return None + + def get_pin_name(self, sig): + sc = self.constraint_manager.get_sig_constraints() + for s, pins, others, resource in sc: + if s == sig: + return resource[0] + return None \ No newline at end of file diff --git a/litex/soc/cores/clock/__init__.py b/litex/soc/cores/clock/__init__.py index f8096c2b0..afed6e015 100644 --- a/litex/soc/cores/clock/__init__.py +++ b/litex/soc/cores/clock/__init__.py @@ -14,3 +14,6 @@ from litex.soc.cores.clock.intel_cyclone10 import Cyclone10LPPLL from litex.soc.cores.clock.lattice_ice40 import iCE40PLL from litex.soc.cores.clock.lattice_ecp5 import ECP5PLL from litex.soc.cores.clock.lattice_nx import NXOSCA, NXPLL + +# Efinix +from litex.soc.cores.clock.efinix_trion import TRIONPLL \ No newline at end of file diff --git a/litex/soc/cores/clock/efinix_trion.py b/litex/soc/cores/clock/efinix_trion.py new file mode 100644 index 000000000..b3c23ebb2 --- /dev/null +++ b/litex/soc/cores/clock/efinix_trion.py @@ -0,0 +1,92 @@ +# +# This file is part of LiteX. +# +# Copyright (c) 2021 Franck Jullien +# Copyright (c) 2021 Florent Kermarrec +# SPDX-License-Identifier: BSD-2-Clause + +from migen import * +from migen.genlib.resetsync import AsyncResetSynchronizer + +from litex.build.generic_platform import * +from litex.soc.cores.clock.common import * + +from litex.build.efinix import EfinixDbParser + +class Open(Signal): pass + +#TODO: do somthing else +count = 0 + +# Efinix / TRIONPLL ---------------------------------------------------------------------------------- + +class TRIONPLL(Module): + nclkouts_max = 4 + def __init__(self, platform): + global count + self.logger = logging.getLogger("TRIONPLL") + self.logger.info("Creating TRIONPLL.".format()) + self.platform = platform + self.nclkouts = 0 + self.pll_name = "pll{}".format(count) + + block = {} + count += 1 + + block['type'] = 'PLL' + block['name'] = self.pll_name + block['clk_out'] = [] + + self.platform.toolchain.ifacewriter.blocks.append(block) + + def register_clkin(self, clkin, freq): + block = self.platform.toolchain.ifacewriter.get_block(self.pll_name) + + # If clkin has resource, PLL clock input is EXTERNAL + # When PLL clock is external, it must not be present in the top file + # Add a test on clkin resource here + block['input_clock_name'] = self.platform.get_pin_name(clkin) + pin_name = self.platform.get_pin_location(clkin) + + self.platform.delete(clkin) + + #tpl = "create_clock -name {clk} -period {period} [get_ports {{{clk}}}]" + #sdc = self.platform.toolchain.additional_sdc_commands + #sdc.append(tpl.format(clk=block['input_clock_name'], period=1/freq)) + + parser = EfinixDbParser(self.platform.efinity_path, self.platform.device) + (pll_res, clock_no) = parser.get_pll_inst_from_pin(pin_name) + + block['input_clock'] = 'EXTERNAL' + block['input_freq'] = freq + block['resource'] = pll_res + block['clock_no'] = clock_no + + self.logger.info("Using {}".format(pll_res)) + self.logger.info("Clock source: {}, using EXT_CLK{}".format(block['input_clock'], clock_no)) + + def create_clkout(self, cd, freq, phase=0, margin=1e-2, with_reset=True): + assert self.nclkouts < self.nclkouts_max + + clk_out_name = '{}_CLKOUT{}'.format(self.pll_name, self.nclkouts) + + self.platform.add_extension([(clk_out_name, 0, Pins(1))]) + tmp = self.platform.request(clk_out_name) + + # We don't want this IO to be in the interface configuration file as a simple GPIO + self.platform.toolchain.specials_gpios.append(tmp) + self.comb += cd.clk.eq(tmp) + create_clkout_log(self.logger, cd.name, freq, margin, self.nclkouts) + self.nclkouts += 1 + + block = self.platform.toolchain.ifacewriter.get_block(self.pll_name) + block['clk_out'].append([clk_out_name, freq, phase, margin]) + + def compute_config(self): + pass + + def set_configuration(self): + pass + + def do_finalize(self): + pass \ No newline at end of file From 106b1f29a723b0c6372ee83315b9a65a5dfa379f Mon Sep 17 00:00:00 2001 From: Franck Jullien Date: Mon, 20 Sep 2021 08:42:27 +0200 Subject: [PATCH 04/22] efinix: fix programmer load_bitstream --- litex/build/efinix/programmer.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/litex/build/efinix/programmer.py b/litex/build/efinix/programmer.py index 1981b5fa3..10a3eaff4 100644 --- a/litex/build/efinix/programmer.py +++ b/litex/build/efinix/programmer.py @@ -6,6 +6,7 @@ import os import sys +import subprocess from litex.build.generic_programmer import GenericProgrammer @@ -21,7 +22,11 @@ class EfinixProgrammer(GenericProgrammer): def load_bitstream(self, bitstream_file, cable_suffix=""): os.environ['EFXPGM_HOME'] = self.efinity_path + '/pgm' - self.call([self.efinity_path + '/bin/python3', self.efinity_path + - 'pgm/bin/efx_pgm/ftdi_program.py', bitstream_file, - "-m", "jtag" - ]) + if (subprocess.call([self.efinity_path + '/bin/python3', self.efinity_path + + '/pgm/bin/efx_pgm/ftdi_program.py', bitstream_file, + "-m", "jtag"], env=os.environ.copy()) != 0): + msg = f"Error occured during {self.__class__.__name__}'s call, please check:\n" + msg += f"- {self.__class__.__name__} installation.\n" + msg += f"- access permissions.\n" + msg += f"- hardware and cable." + raise OSError(msg) From 08be77caaf0d933be338598386a546c8a372d662 Mon Sep 17 00:00:00 2001 From: Franck Jullien Date: Mon, 20 Sep 2021 10:41:00 +0200 Subject: [PATCH 05/22] efinix: ifacewriter, enable design generation --- litex/build/efinix/ifacewriter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litex/build/efinix/ifacewriter.py b/litex/build/efinix/ifacewriter.py index 2aaf80a24..747a7ede9 100644 --- a/litex/build/efinix/ifacewriter.py +++ b/litex/build/efinix/ifacewriter.py @@ -89,7 +89,7 @@ design.create('{2}', '{3}', './../build', overwrite=True) def footer(self): return """ # Check design, generate constraints and reports -#design.generate(enable_bitstream=True) +design.generate(enable_bitstream=True) # Save the configured periphery design design.save()""" From a026dd89468e0c995bc69b6d7ef1a40b5718e06a Mon Sep 17 00:00:00 2001 From: Franck Jullien Date: Mon, 20 Sep 2021 10:41:59 +0200 Subject: [PATCH 06/22] efinix: add AsyncResetSynchronizer --- litex/build/efinix/common.py | 37 +++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/litex/build/efinix/common.py b/litex/build/efinix/common.py index 9c104ab69..9a4ef0c2b 100644 --- a/litex/build/efinix/common.py +++ b/litex/build/efinix/common.py @@ -5,4 +5,39 @@ # Copyright (c) 2015-2018 Florent Kermarrec # SPDX-License-Identifier: BSD-2-Clause -efinix_special_overrides = {} \ No newline at end of file +from migen.fhdl.module import Module +from migen.genlib.resetsync import AsyncResetSynchronizer + +from litex.build.io import * + +# Efinix AsyncResetSynchronizer --------------------------------------------------------------------- + +class EfinixAsyncResetSynchronizerImpl(Module): + def __init__(self, cd, async_reset): + rst1 = Signal() + self.specials += [ + Instance("EFX_FF", + i_D = 0, + i_SR = async_reset, + i_CLK = cd.clk, + i_CE = 1, + o_Q = rst1), + Instance("EFX_FF", + i_D = rst1, + i_SR = async_reset, + i_CLK = cd.clk, + i_CE = 1, + o_Q = cd.rst) + ] + + +class EfinixAsyncResetSynchronizer: + @staticmethod + def lower(dr): + return EfinixAsyncResetSynchronizerImpl(dr.cd, dr.async_reset) + +# Gowin Special Overrides -------------------------------------------------------------------------- + +efinix_special_overrides = { + AsyncResetSynchronizer: EfinixAsyncResetSynchronizer +} From efebefecea5e7c60f43080904d90c4d161eeedb4 Mon Sep 17 00:00:00 2001 From: Franck Jullien Date: Mon, 20 Sep 2021 10:42:27 +0200 Subject: [PATCH 07/22] efinix: add PLL reset and locked pins --- litex/build/efinix/ifacewriter.py | 4 ++++ litex/build/efinix/platform.py | 11 +++++++++-- litex/soc/cores/clock/efinix_trion.py | 19 ++++++++++++++++++- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/litex/build/efinix/ifacewriter.py b/litex/build/efinix/ifacewriter.py index 747a7ede9..51860d908 100644 --- a/litex/build/efinix/ifacewriter.py +++ b/litex/build/efinix/ifacewriter.py @@ -53,6 +53,10 @@ design.create('{2}', '{3}', './../build', overwrite=True) cmd += 'pll_config = {{ "REFCLK_FREQ":"{}" }}\n'.format(block['input_freq'] / 1e6) cmd += 'design.set_property("{}", pll_config, block_type="PLL")\n\n'.format(name) + cmd += 'design.set_property("{}","LOCKED_PIN","{}", block_type="PLL")\n'.format(name, block['locked']) + if block['reset'] != '': + cmd += 'design.set_property("{}","RSTN_PIN","{}", block_type="PLL")\n\n'.format(name, block['reset']) + # Output clock 0 is enabled by default for i, clock in enumerate(block['clk_out']): if i > 0: diff --git a/litex/build/efinix/platform.py b/litex/build/efinix/platform.py index 6f223bcdc..efd8432a0 100644 --- a/litex/build/efinix/platform.py +++ b/litex/build/efinix/platform.py @@ -7,7 +7,7 @@ import os -from litex.build.generic_platform import GenericPlatform +from litex.build.generic_platform import * from litex.build.efinix import common, efinity # EfinixPlatform ----------------------------------------------------------------------------------- @@ -66,4 +66,11 @@ class EfinixPlatform(GenericPlatform): for s, pins, others, resource in sc: if s == sig: return resource[0] - return None \ No newline at end of file + return None + + def add_iface_io(self, name, size=1): + self.add_extension([(name, 0, Pins(size))]) + tmp = self.request(name) + # We don't want this IO to be in the interface configuration file as a simple GPIO + self.toolchain.specials_gpios.append(tmp) + return tmp \ No newline at end of file diff --git a/litex/soc/cores/clock/efinix_trion.py b/litex/soc/cores/clock/efinix_trion.py index b3c23ebb2..ec3a948fe 100644 --- a/litex/soc/cores/clock/efinix_trion.py +++ b/litex/soc/cores/clock/efinix_trion.py @@ -22,13 +22,15 @@ count = 0 class TRIONPLL(Module): nclkouts_max = 4 - def __init__(self, platform): + def __init__(self, platform, with_reset=False): global count self.logger = logging.getLogger("TRIONPLL") self.logger.info("Creating TRIONPLL.".format()) self.platform = platform self.nclkouts = 0 self.pll_name = "pll{}".format(count) + self.reset = Signal() + self.locked = Signal() block = {} count += 1 @@ -37,6 +39,18 @@ class TRIONPLL(Module): block['name'] = self.pll_name block['clk_out'] = [] + pll_locked_name = self.pll_name + '_locked' + block['locked'] = pll_locked_name + io = self.platform.add_iface_io(pll_locked_name) + self.comb += self.locked.eq(io) + + block['reset'] = '' + if with_reset: + pll_reset_name = self.pll_name + '_reset' + block['reset'] = pll_reset_name + io = self.platform.add_iface_io(pll_reset_name) + self.comb += io.eq(self.reset) + self.platform.toolchain.ifacewriter.blocks.append(block) def register_clkin(self, clkin, freq): @@ -73,6 +87,9 @@ class TRIONPLL(Module): self.platform.add_extension([(clk_out_name, 0, Pins(1))]) tmp = self.platform.request(clk_out_name) + if with_reset: + self.specials += AsyncResetSynchronizer(cd, ~self.locked) + # We don't want this IO to be in the interface configuration file as a simple GPIO self.platform.toolchain.specials_gpios.append(tmp) self.comb += cd.clk.eq(tmp) From b9e99f576c9036c9bc9e29f7cf1c228cef36a62a Mon Sep 17 00:00:00 2001 From: Franck Jullien Date: Mon, 20 Sep 2021 13:35:41 +0200 Subject: [PATCH 08/22] efinix: use proper xml to create project file --- litex/build/efinix/efinity.py | 80 ++++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 35 deletions(-) diff --git a/litex/build/efinix/efinity.py b/litex/build/efinix/efinity.py index 517620c6f..68a3ddddf 100644 --- a/litex/build/efinix/efinity.py +++ b/litex/build/efinix/efinity.py @@ -13,8 +13,10 @@ import sys import site import subprocess import inspect +import datetime -from lxml import etree +from xml.dom import expatbuilder +import xml.etree.ElementTree as et from litex.build.generic_platform import * @@ -198,62 +200,70 @@ def _build_peri(efinity_path, build_name, partnumber, named_sc, named_pc, fragme def _build_xml(name, partnumber, build_name, sources, additional_xml_commands): - test = ' Date: Tue, 21 Sep 2021 10:58:54 +0200 Subject: [PATCH 09/22] efinix: add preliminary DDR support (WIP) --- litex/build/efinix/__init__.py | 3 +- litex/build/efinix/ddr.py | 97 ++++++++++++++++++++++ litex/build/efinix/efinity.py | 7 +- litex/build/efinix/ifacewriter.py | 114 +++++++++++++++++++++++++- litex/build/efinix/platform.py | 9 +- litex/soc/cores/clock/efinix_trion.py | 20 +++-- 6 files changed, 235 insertions(+), 15 deletions(-) create mode 100644 litex/build/efinix/ddr.py diff --git a/litex/build/efinix/__init__.py b/litex/build/efinix/__init__.py index c0efa1e9e..eda581876 100644 --- a/litex/build/efinix/__init__.py +++ b/litex/build/efinix/__init__.py @@ -1,3 +1,4 @@ from litex.build.efinix.programmer import EfinixProgrammer from litex.build.efinix.dbparser import EfinixDbParser -from litex.build.efinix.ifacewriter import InterfaceWriter \ No newline at end of file +from litex.build.efinix.ifacewriter import InterfaceWriter +from litex.build.efinix.ddr import EfinixDDR \ No newline at end of file diff --git a/litex/build/efinix/ddr.py b/litex/build/efinix/ddr.py new file mode 100644 index 000000000..ffe3dc463 --- /dev/null +++ b/litex/build/efinix/ddr.py @@ -0,0 +1,97 @@ +import os + +from migen import * +from migen.genlib.cdc import * +from migen import ClockDomain + +from litex.build.generic_platform import * +from litex.soc.interconnect import axi + +from litex.build import tools + +class EfinixDDR(Module): + def __init__(self, platform, config): + self.blocks = [] + self.platform = platform + self.config = config + self.nb_ports = 1 + + if config['ports'] != None: + self.nb_ports = self.config['ports'] + + self.clock_domains.cd_axi_ddr = ClockDomain() + + self.port0 = port0 = axi.AXIInterface(data_width=256, address_width=32, id_width=8, clock_domain="axi_ddr") + + if self.nb_ports == 2: + self.port1 = port1 = axi.AXIInterface(data_width=256, address_width=32, id_width=8, clock_domain="axi_ddr") + + axi_clk = platform.add_iface_io('axi_user_clk') + self.cd_axi_ddr.clk.eq(axi_clk), + + for i in range (0, self.nb_ports): + ios = [('axi', i, + Subsignal('wdata', Pins(256)), + Subsignal('wready', Pins(1)), + Subsignal('wid', Pins(8)), + Subsignal('bready', Pins(1)), + Subsignal('rdata', Pins(256)), + Subsignal('aid', Pins(8)), + Subsignal('bvalid', Pins(1)), + Subsignal('rlast', Pins(1)), + Subsignal('bid', Pins(8)), + Subsignal('asize', Pins(3)), + Subsignal('atype', Pins(1)), + Subsignal('aburst', Pins(2)), + Subsignal('wvalid', Pins(1)), + Subsignal('aaddr', Pins(32)), + Subsignal('rid', Pins(8)), + Subsignal('avalid', Pins(1)), + Subsignal('rvalid', Pins(1)), + Subsignal('alock', Pins(2)), + Subsignal('rready', Pins(1)), + Subsignal('rresp', Pins(2)), + Subsignal('wstrb', Pins(32)), + Subsignal('aready', Pins(1)), + Subsignal('alen', Pins(8)), + Subsignal('wlast', Pins(1)), + )] + + io = platform.add_iface_ios(ios) + + port = port0 + if i == 0: + port = port1 + + is_read = port.ar.valid + self.comb += [io.aaddr.eq(Mux(is_read, port.ar.addr, port.aw.addr)), + io.aid.eq(Mux(is_read, port.ar.id, port.aw.id)), + io.alen.eq(Mux(is_read, port.ar.len, port.aw.len)), + io.asize.eq(Mux(is_read, port.ar.size[0:4], port.aw.size[0:4])), #TODO: check + io.aburst.eq(Mux(is_read, port.ar.burst, port.aw.burst)), + io.alock.eq(Mux(is_read, port.ar.lock, port.aw.lock)), + io.avalid.eq(Mux(is_read, port.ar.valid, port.aw.valid)), + + io.atype.eq(~is_read), + port.aw.ready.eq(io.aready), + port.ar.ready.eq(io.aready), + + io.wid.eq(port.w.id), + io.wstrb.eq(port.w.strb), + io.wdata.eq(port.w.data), + io.wlast.eq(port.w.last), + io.wvalid.eq(port.w.valid), + port.w.ready.eq(io.wready), + + port.r.id.eq(io.rid), + port.r.data.eq(io.rdata), + port.r.last.eq(io.rlast), + port.r.resp.eq(io.rresp), + port.r.valid.eq(io.rvalid), + io.rready.eq(port.r.ready), + + port.b.id.eq(io.bid), + port.b.valid.eq(io.bvalid), + io.bready.eq(port.b.ready), + # port.b.resp ?? + ] diff --git a/litex/build/efinix/efinity.py b/litex/build/efinix/efinity.py index 68a3ddddf..e02deffc3 100644 --- a/litex/build/efinix/efinity.py +++ b/litex/build/efinix/efinity.py @@ -188,6 +188,7 @@ def _build_peri(efinity_path, build_name, partnumber, named_sc, named_pc, fragme header = platform.toolchain.ifacewriter.header(build_name, partnumber) gen = platform.toolchain.ifacewriter.generate() + #TODO: move this to ifacewriter gpio = _build_iface_gpio(named_sc, named_pc, fragment, platform, specials_gpios) add = '\n'.join(additional_iface_commands) footer = platform.toolchain.ifacewriter.footer() @@ -274,7 +275,7 @@ class EfinityToolchain(): self.efinity_path = efinity_path self.additional_sdc_commands = [] self.additional_xml_commands = [] - self.ifacewriter = InterfaceWriter("iface.py", efinity_path) + self.ifacewriter = InterfaceWriter(efinity_path) self.specials_gpios = [] self.additional_iface_commands = [] @@ -338,6 +339,10 @@ class EfinityToolchain(): additional_iface_commands = self.additional_iface_commands, specials_gpios = self.specials_gpios) + # DDR doesn't have Python API so we need to configure it + # directly in the peri.xml file + self.ifacewriter.add_ddr_xml(build_name) + # Run if run: subprocess.call([self.efinity_path + '/scripts/efx_run.py', build_name + '.xml', '-f', 'compile']) diff --git a/litex/build/efinix/ifacewriter.py b/litex/build/efinix/ifacewriter.py index 51860d908..e10e66083 100644 --- a/litex/build/efinix/ifacewriter.py +++ b/litex/build/efinix/ifacewriter.py @@ -1,12 +1,120 @@ import os +import csv +import re +import datetime + +from xml.dom import expatbuilder +import xml.etree.ElementTree as et + from litex.build import tools +namespaces = { 'efxpt' : 'http://www.efinixinc.com/peri_design_db', + 'xi' : 'http://www.w3.org/2001/XInclude' +} + class InterfaceWriter(): - def __init__(self, filename, efinity_path): - self.file = filename + def __init__(self, efinity_path): self.efinity_path = efinity_path self.blocks = [] + def add_ddr_xml(self, filename): + et.register_namespace('efxpt', "http://www.efinixinc.com/peri_design_db") + tree = et.parse(filename + '.peri.xml') + root = tree.getroot() + ddr_info = root.find('efxpt:ddr_info', namespaces) + + et.SubElement(ddr_info, 'efxpt:ddr', + name = 'ddr_inst1', + ddr_def = 'DDR_0', + cs_preset_id = '173', + cs_mem_type = 'LPDDR3', + cs_ctrl_width = 'x32', + cs_dram_width = 'x32', + cs_dram_density = '8G', + cs_speedbin = '800', + target0_enable = 'true', + target1_enable = 'false', + ctrl_type = 'none') + + axi_suffix = '' # '_1' for second port + type_suffix = '_0' # '_1' for second port + + gen_pin_target0 = et.SubElement(ddr_info, 'efxpt:gen_pin_target0') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_wdata{}'.format(axi_suffix), type_name='WDATA{}'.format(type_suffix), is_bus = 'true') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_wready{}'.format(axi_suffix), type_name='WREADY{}'.format(type_suffix), is_bus = 'false') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_wid{}'.format(axi_suffix), type_name='WID{}'.format(type_suffix), is_bus = 'true') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_bready{}'.format(axi_suffix), type_name='BREADY{}'.format(type_suffix), is_bus = 'false') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_rdata{}'.format(axi_suffix), type_name='RDATA{}'.format(type_suffix), is_bus = 'true') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_aid{}'.format(axi_suffix), type_name='AID{}'.format(type_suffix), is_bus = 'true') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_bvalid{}'.format(axi_suffix), type_name='BVALID{}'.format(type_suffix), is_bus = 'false') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_rlast{}'.format(axi_suffix), type_name='RLAST{}'.format(type_suffix), is_bus = 'false') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_bid{}'.format(axi_suffix), type_name='BID{}'.format(type_suffix), is_bus = 'true') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_asize{}'.format(axi_suffix), type_name='ASIZE{}'.format(type_suffix), is_bus = 'true') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_atype{}'.format(axi_suffix), type_name='ATYPE{}'.format(type_suffix), is_bus = 'false') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_aburst{}'.format(axi_suffix), type_name='ABURST{}'.format(type_suffix), is_bus = 'true') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_wvalid{}'.format(axi_suffix), type_name='WVALID{}'.format(type_suffix), is_bus = 'false') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_wlast{}'.format(axi_suffix), type_name='WLAST{}'.format(type_suffix), is_bus = 'false') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_aaddr{}'.format(axi_suffix), type_name='AADDR{}'.format(type_suffix), is_bus = 'true') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_rid{}'.format(axi_suffix), type_name='RID{}'.format(type_suffix), is_bus = 'true') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_avalid{}'.format(axi_suffix), type_name='AVALID{}'.format(type_suffix), is_bus = 'false') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_rvalid{}'.format(axi_suffix), type_name='RVALID{}'.format(type_suffix), is_bus = 'false') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_alock{}'.format(axi_suffix), type_name='ALOCK{}'.format(type_suffix), is_bus = 'true') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_rready{}'.format(axi_suffix), type_name='RREADY{}'.format(type_suffix), is_bus = 'false') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_rresp{}'.format(axi_suffix), type_name='RRESP{}'.format(type_suffix), is_bus = 'true') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_wstrb{}'.format(axi_suffix), type_name='WSTRB{}'.format(type_suffix), is_bus = 'true') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_aready{}'.format(axi_suffix), type_name='AREADY{}'.format(type_suffix), is_bus = 'false') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_alen{}'.format(axi_suffix), type_name='ALEN{}'.format(type_suffix), is_bus = 'true') + + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_clk', type_name='ACLK_0', is_bus = 'false', is_clk = 'true', is_clk_invert = 'false') + + gen_pin_config = et.SubElement(ddr_info, 'efxpt:gen_pin_config') + et.SubElement(gen_pin_config, 'efxpt:pin', name='', type_name='CFG_SEQ_RST', is_bus = 'false') + et.SubElement(gen_pin_config, 'efxpt:pin', name='', type_name='CFG_SCL_IN', is_bus = 'false') + et.SubElement(gen_pin_config, 'efxpt:pin', name='', type_name='CFG_SEQ_START', is_bus = 'false') + et.SubElement(gen_pin_config, 'efxpt:pin', name='', type_name='RSTN', is_bus = 'false') + et.SubElement(gen_pin_config, 'efxpt:pin', name='', type_name='CFG_SDA_IN', is_bus = 'false') + et.SubElement(gen_pin_config, 'efxpt:pin', name='', type_name='CFG_SDA_OEN', is_bus = 'false') + + cs_fpga = et.SubElement(ddr_info, 'efxpt:cs_fpga') + et.SubElement(cs_fpga, 'efxpt:param', name='FPGA_ITERM', value='120', value_type = 'str') + et.SubElement(cs_fpga, 'efxpt:param', name='FPGA_OTERM', value='34', value_type = 'str') + + cs_memory = et.SubElement(ddr_info, 'efxpt:cs_memory') + et.SubElement(cs_memory, 'efxpt:param', name='RTT_NOM', value='RZQ/2', value_type = 'str') + et.SubElement(cs_memory, 'efxpt:param', name='MEM_OTERM', value='40', value_type = 'str') + et.SubElement(cs_memory, 'efxpt:param', name='CL', value='RL=6/WL=3', value_type = 'str') + + timing = et.SubElement(ddr_info, 'efxpt:cs_memory_timing') + et.SubElement(timing, 'efxpt:param', name='tRAS', value= '42.000', value_type='float') + et.SubElement(timing, 'efxpt:param', name='tRC', value= '60.000', value_type='float') + et.SubElement(timing, 'efxpt:param', name='tRP', value= '18.000', value_type='float') + et.SubElement(timing, 'efxpt:param', name='tRCD', value= '18.000', value_type='float') + et.SubElement(timing, 'efxpt:param', name='tREFI', value= '3.900', value_type='float') + et.SubElement(timing, 'efxpt:param', name='tRFC', value= '210.000', value_type='float') + et.SubElement(timing, 'efxpt:param', name='tRTP', value= '10.000', value_type='float') + et.SubElement(timing, 'efxpt:param', name='tWTR', value= '10.000', value_type='float') + et.SubElement(timing, 'efxpt:param', name='tRRD', value= '10.000', value_type='float') + et.SubElement(timing, 'efxpt:param', name='tFAW', value= '50.000', value_type='float') + + cs_control = et.SubElement(ddr_info, 'efxpt:cs_control') + et.SubElement(cs_control, 'efxpt:param', name='AMAP', value= 'ROW-COL_HIGH-BANK-COL_LOW', value_type='str') + et.SubElement(cs_control, 'efxpt:param', name='EN_AUTO_PWR_DN', value= 'Off', value_type='str') + et.SubElement(cs_control, 'efxpt:param', name='EN_AUTO_SELF_REF', value= 'No', value_type='str') + + cs_gate_delay = et.SubElement(ddr_info, 'efxpt:cs_gate_delay') + et.SubElement(cs_control, 'efxpt:param', name='EN_DLY_OVR', value= 'No', value_type='str') + et.SubElement(cs_control, 'efxpt:param', name='GATE_C_DLY', value= '3', value_type='str') + et.SubElement(cs_control, 'efxpt:param', name='GATE_F_DLY', value= '0', value_type='str') + + xml_string = et.tostring(root, 'utf-8') + reparsed = expatbuilder.parseString(xml_string, False) + print_string = reparsed.toprettyxml(indent=" ") + + # Remove lines with only whitespaces. Not sure why they are here + print_string = os.linesep.join([s for s in print_string.splitlines() if s.strip()]) + + tools.write_to_file("{}.peri.xml".format(filename), print_string) + def header(self, build_name, partnumber): header = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() header += """ @@ -93,7 +201,7 @@ design.create('{2}', '{3}', './../build', overwrite=True) def footer(self): return """ # Check design, generate constraints and reports -design.generate(enable_bitstream=True) +#design.generate(enable_bitstream=True) # Save the configured periphery design design.save()""" diff --git a/litex/build/efinix/platform.py b/litex/build/efinix/platform.py index efd8432a0..aadc324bd 100644 --- a/litex/build/efinix/platform.py +++ b/litex/build/efinix/platform.py @@ -73,4 +73,11 @@ class EfinixPlatform(GenericPlatform): tmp = self.request(name) # We don't want this IO to be in the interface configuration file as a simple GPIO self.toolchain.specials_gpios.append(tmp) - return tmp \ No newline at end of file + return tmp + + def add_iface_ios(self, io): + self.add_extension(io) + tmp = self.request(io[0][0]) + for s in tmp.flatten(): + self.toolchain.specials_gpios.append(s) + return tmp diff --git a/litex/soc/cores/clock/efinix_trion.py b/litex/soc/cores/clock/efinix_trion.py index ec3a948fe..1b5c099ab 100644 --- a/litex/soc/cores/clock/efinix_trion.py +++ b/litex/soc/cores/clock/efinix_trion.py @@ -79,21 +79,23 @@ class TRIONPLL(Module): self.logger.info("Using {}".format(pll_res)) self.logger.info("Clock source: {}, using EXT_CLK{}".format(block['input_clock'], clock_no)) - def create_clkout(self, cd, freq, phase=0, margin=1e-2, with_reset=True): + def create_clkout(self, cd, freq, phase=0, margin=1e-2, with_reset=False, user_clk=True): assert self.nclkouts < self.nclkouts_max clk_out_name = '{}_CLKOUT{}'.format(self.pll_name, self.nclkouts) - self.platform.add_extension([(clk_out_name, 0, Pins(1))]) - tmp = self.platform.request(clk_out_name) + if user_clk == True: + self.platform.add_extension([(clk_out_name, 0, Pins(1))]) + tmp = self.platform.request(clk_out_name) - if with_reset: - self.specials += AsyncResetSynchronizer(cd, ~self.locked) + if with_reset: + self.specials += AsyncResetSynchronizer(cd, ~self.locked) + + # We don't want this IO to be in the interface configuration file as a simple GPIO + self.platform.toolchain.specials_gpios.append(tmp) + self.comb += cd.clk.eq(tmp) + create_clkout_log(self.logger, cd.name, freq, margin, self.nclkouts) - # We don't want this IO to be in the interface configuration file as a simple GPIO - self.platform.toolchain.specials_gpios.append(tmp) - self.comb += cd.clk.eq(tmp) - create_clkout_log(self.logger, cd.name, freq, margin, self.nclkouts) self.nclkouts += 1 block = self.platform.toolchain.ifacewriter.get_block(self.pll_name) From 7a5f5a3682556206f3e316dd014b273c804edf2a Mon Sep 17 00:00:00 2001 From: Franck Jullien Date: Tue, 21 Sep 2021 14:22:17 +0200 Subject: [PATCH 10/22] efinix: remove redundant param in _build_xml --- litex/build/efinix/efinity.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/litex/build/efinix/efinity.py b/litex/build/efinix/efinity.py index e02deffc3..71947b152 100644 --- a/litex/build/efinix/efinity.py +++ b/litex/build/efinix/efinity.py @@ -199,7 +199,7 @@ def _build_peri(efinity_path, build_name, partnumber, named_sc, named_pc, fragme # Project configuration ------------------------------------------------------------------------ -def _build_xml(name, partnumber, build_name, sources, additional_xml_commands): +def _build_xml(partnumber, build_name, sources, additional_xml_commands): root = et.Element('efx:project') @@ -209,7 +209,7 @@ def _build_xml(name, partnumber, build_name, sources, additional_xml_commands): # Add the required attributes root.attrib['xmlns:efx'] = 'http://www.efinixinc.com/enf_proj' root.attrib['xmlns:xsi'] = "http://www.w3.org/2001/XMLSchema-instance" - root.attrib['name'] = name + root.attrib['name'] = build_name root.attrib['description'] = '' root.attrib['last_change_date'] = date_str root.attrib['location'] = str(pathlib.Path().resolve()) @@ -321,7 +321,6 @@ class EfinityToolchain(): # Generate project file (.xml) _build_xml( - name = build_name, partnumber = platform.device, build_name = build_name, sources = platform.sources, From b765bdf34e623ae663fe9e048e875ce61a7c2fce Mon Sep 17 00:00:00 2001 From: Franck Jullien Date: Tue, 21 Sep 2021 14:23:00 +0200 Subject: [PATCH 11/22] efinix: pll: allow output name to be changed --- litex/soc/cores/clock/efinix_trion.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/litex/soc/cores/clock/efinix_trion.py b/litex/soc/cores/clock/efinix_trion.py index 1b5c099ab..a179e1857 100644 --- a/litex/soc/cores/clock/efinix_trion.py +++ b/litex/soc/cores/clock/efinix_trion.py @@ -79,10 +79,13 @@ class TRIONPLL(Module): self.logger.info("Using {}".format(pll_res)) self.logger.info("Clock source: {}, using EXT_CLK{}".format(block['input_clock'], clock_no)) - def create_clkout(self, cd, freq, phase=0, margin=1e-2, with_reset=False, user_clk=True): + def create_clkout(self, cd, freq, phase=0, margin=1e-2, name='', with_reset=False, user_clk=True): assert self.nclkouts < self.nclkouts_max - clk_out_name = '{}_CLKOUT{}'.format(self.pll_name, self.nclkouts) + if name != '': + clk_out_name = name + else: + clk_out_name = '{}_CLKOUT{}'.format(self.pll_name, self.nclkouts) if user_clk == True: self.platform.add_extension([(clk_out_name, 0, Pins(1))]) From bd71dc663fecc409129a7dc6bd5de06fdaef4804 Mon Sep 17 00:00:00 2001 From: Franck Jullien Date: Tue, 21 Sep 2021 14:23:36 +0200 Subject: [PATCH 12/22] efinix: more DDR work, still WIP --- litex/build/efinix/ddr.py | 14 +++----- litex/build/efinix/ifacewriter.py | 60 +++++++++++++++++++++++-------- 2 files changed, 51 insertions(+), 23 deletions(-) diff --git a/litex/build/efinix/ddr.py b/litex/build/efinix/ddr.py index ffe3dc463..97452b05a 100644 --- a/litex/build/efinix/ddr.py +++ b/litex/build/efinix/ddr.py @@ -10,7 +10,7 @@ from litex.soc.interconnect import axi from litex.build import tools class EfinixDDR(Module): - def __init__(self, platform, config): + def __init__(self, platform, config, cd): self.blocks = [] self.platform = platform self.config = config @@ -19,15 +19,11 @@ class EfinixDDR(Module): if config['ports'] != None: self.nb_ports = self.config['ports'] - self.clock_domains.cd_axi_ddr = ClockDomain() - - self.port0 = port0 = axi.AXIInterface(data_width=256, address_width=32, id_width=8, clock_domain="axi_ddr") + # TODO: set clock_domain ? + self.port0 = port0 = axi.AXIInterface(data_width=256, address_width=32, id_width=8) if self.nb_ports == 2: - self.port1 = port1 = axi.AXIInterface(data_width=256, address_width=32, id_width=8, clock_domain="axi_ddr") - - axi_clk = platform.add_iface_io('axi_user_clk') - self.cd_axi_ddr.clk.eq(axi_clk), + self.port1 = port1 = axi.AXIInterface(data_width=256, address_width=32, id_width=8) for i in range (0, self.nb_ports): ios = [('axi', i, @@ -60,7 +56,7 @@ class EfinixDDR(Module): io = platform.add_iface_ios(ios) port = port0 - if i == 0: + if i == 1: port = port1 is_read = port.ar.valid diff --git a/litex/build/efinix/ifacewriter.py b/litex/build/efinix/ifacewriter.py index e10e66083..6c2d7439d 100644 --- a/litex/build/efinix/ifacewriter.py +++ b/litex/build/efinix/ifacewriter.py @@ -23,7 +23,7 @@ class InterfaceWriter(): root = tree.getroot() ddr_info = root.find('efxpt:ddr_info', namespaces) - et.SubElement(ddr_info, 'efxpt:ddr', + ddr = et.SubElement(ddr_info, 'efxpt:ddr', name = 'ddr_inst1', ddr_def = 'DDR_0', cs_preset_id = '173', @@ -39,7 +39,7 @@ class InterfaceWriter(): axi_suffix = '' # '_1' for second port type_suffix = '_0' # '_1' for second port - gen_pin_target0 = et.SubElement(ddr_info, 'efxpt:gen_pin_target0') + gen_pin_target0 = et.SubElement(ddr, 'efxpt:gen_pin_target0') et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_wdata{}'.format(axi_suffix), type_name='WDATA{}'.format(type_suffix), is_bus = 'true') et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_wready{}'.format(axi_suffix), type_name='WREADY{}'.format(type_suffix), is_bus = 'false') et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_wid{}'.format(axi_suffix), type_name='WID{}'.format(type_suffix), is_bus = 'true') @@ -64,10 +64,39 @@ class InterfaceWriter(): et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_wstrb{}'.format(axi_suffix), type_name='WSTRB{}'.format(type_suffix), is_bus = 'true') et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_aready{}'.format(axi_suffix), type_name='AREADY{}'.format(type_suffix), is_bus = 'false') et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_alen{}'.format(axi_suffix), type_name='ALEN{}'.format(type_suffix), is_bus = 'true') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_clk', type_name='ACLK{}'.format(type_suffix), is_bus = 'false', is_clk = 'true', is_clk_invert = 'false') - et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_clk', type_name='ACLK_0', is_bus = 'false', is_clk = 'true', is_clk_invert = 'false') + axi_suffix = '_1' # '_1' for second port + type_suffix = '_1' # '_1' for second port - gen_pin_config = et.SubElement(ddr_info, 'efxpt:gen_pin_config') + gen_pin_target1 = et.SubElement(ddr, 'efxpt:gen_pin_target1') + et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_wdata{}'.format(axi_suffix), type_name='WDATA{}'.format(type_suffix), is_bus = 'true') + et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_wready{}'.format(axi_suffix), type_name='WREADY{}'.format(type_suffix), is_bus = 'false') + et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_wid{}'.format(axi_suffix), type_name='WID{}'.format(type_suffix), is_bus = 'true') + et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_bready{}'.format(axi_suffix), type_name='BREADY{}'.format(type_suffix), is_bus = 'false') + et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_rdata{}'.format(axi_suffix), type_name='RDATA{}'.format(type_suffix), is_bus = 'true') + et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_aid{}'.format(axi_suffix), type_name='AID{}'.format(type_suffix), is_bus = 'true') + et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_bvalid{}'.format(axi_suffix), type_name='BVALID{}'.format(type_suffix), is_bus = 'false') + et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_rlast{}'.format(axi_suffix), type_name='RLAST{}'.format(type_suffix), is_bus = 'false') + et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_bid{}'.format(axi_suffix), type_name='BID{}'.format(type_suffix), is_bus = 'true') + et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_asize{}'.format(axi_suffix), type_name='ASIZE{}'.format(type_suffix), is_bus = 'true') + et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_atype{}'.format(axi_suffix), type_name='ATYPE{}'.format(type_suffix), is_bus = 'false') + et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_aburst{}'.format(axi_suffix), type_name='ABURST{}'.format(type_suffix), is_bus = 'true') + et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_wvalid{}'.format(axi_suffix), type_name='WVALID{}'.format(type_suffix), is_bus = 'false') + et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_wlast{}'.format(axi_suffix), type_name='WLAST{}'.format(type_suffix), is_bus = 'false') + et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_aaddr{}'.format(axi_suffix), type_name='AADDR{}'.format(type_suffix), is_bus = 'true') + et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_rid{}'.format(axi_suffix), type_name='RID{}'.format(type_suffix), is_bus = 'true') + et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_avalid{}'.format(axi_suffix), type_name='AVALID{}'.format(type_suffix), is_bus = 'false') + et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_rvalid{}'.format(axi_suffix), type_name='RVALID{}'.format(type_suffix), is_bus = 'false') + et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_alock{}'.format(axi_suffix), type_name='ALOCK{}'.format(type_suffix), is_bus = 'true') + et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_rready{}'.format(axi_suffix), type_name='RREADY{}'.format(type_suffix), is_bus = 'false') + et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_rresp{}'.format(axi_suffix), type_name='RRESP{}'.format(type_suffix), is_bus = 'true') + et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_wstrb{}'.format(axi_suffix), type_name='WSTRB{}'.format(type_suffix), is_bus = 'true') + et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_aready{}'.format(axi_suffix), type_name='AREADY{}'.format(type_suffix), is_bus = 'false') + et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_alen{}'.format(axi_suffix), type_name='ALEN{}'.format(type_suffix), is_bus = 'true') + et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_clk', type_name='ACLK{}'.format(type_suffix), is_bus = 'false', is_clk = 'true', is_clk_invert = 'false') + + gen_pin_config = et.SubElement(ddr, 'efxpt:gen_pin_config') et.SubElement(gen_pin_config, 'efxpt:pin', name='', type_name='CFG_SEQ_RST', is_bus = 'false') et.SubElement(gen_pin_config, 'efxpt:pin', name='', type_name='CFG_SCL_IN', is_bus = 'false') et.SubElement(gen_pin_config, 'efxpt:pin', name='', type_name='CFG_SEQ_START', is_bus = 'false') @@ -75,16 +104,16 @@ class InterfaceWriter(): et.SubElement(gen_pin_config, 'efxpt:pin', name='', type_name='CFG_SDA_IN', is_bus = 'false') et.SubElement(gen_pin_config, 'efxpt:pin', name='', type_name='CFG_SDA_OEN', is_bus = 'false') - cs_fpga = et.SubElement(ddr_info, 'efxpt:cs_fpga') + cs_fpga = et.SubElement(ddr, 'efxpt:cs_fpga') et.SubElement(cs_fpga, 'efxpt:param', name='FPGA_ITERM', value='120', value_type = 'str') et.SubElement(cs_fpga, 'efxpt:param', name='FPGA_OTERM', value='34', value_type = 'str') - cs_memory = et.SubElement(ddr_info, 'efxpt:cs_memory') + cs_memory = et.SubElement(ddr, 'efxpt:cs_memory') et.SubElement(cs_memory, 'efxpt:param', name='RTT_NOM', value='RZQ/2', value_type = 'str') et.SubElement(cs_memory, 'efxpt:param', name='MEM_OTERM', value='40', value_type = 'str') et.SubElement(cs_memory, 'efxpt:param', name='CL', value='RL=6/WL=3', value_type = 'str') - timing = et.SubElement(ddr_info, 'efxpt:cs_memory_timing') + timing = et.SubElement(ddr, 'efxpt:cs_memory_timing') et.SubElement(timing, 'efxpt:param', name='tRAS', value= '42.000', value_type='float') et.SubElement(timing, 'efxpt:param', name='tRC', value= '60.000', value_type='float') et.SubElement(timing, 'efxpt:param', name='tRP', value= '18.000', value_type='float') @@ -96,19 +125,19 @@ class InterfaceWriter(): et.SubElement(timing, 'efxpt:param', name='tRRD', value= '10.000', value_type='float') et.SubElement(timing, 'efxpt:param', name='tFAW', value= '50.000', value_type='float') - cs_control = et.SubElement(ddr_info, 'efxpt:cs_control') + cs_control = et.SubElement(ddr, 'efxpt:cs_control') et.SubElement(cs_control, 'efxpt:param', name='AMAP', value= 'ROW-COL_HIGH-BANK-COL_LOW', value_type='str') et.SubElement(cs_control, 'efxpt:param', name='EN_AUTO_PWR_DN', value= 'Off', value_type='str') et.SubElement(cs_control, 'efxpt:param', name='EN_AUTO_SELF_REF', value= 'No', value_type='str') - cs_gate_delay = et.SubElement(ddr_info, 'efxpt:cs_gate_delay') - et.SubElement(cs_control, 'efxpt:param', name='EN_DLY_OVR', value= 'No', value_type='str') - et.SubElement(cs_control, 'efxpt:param', name='GATE_C_DLY', value= '3', value_type='str') - et.SubElement(cs_control, 'efxpt:param', name='GATE_F_DLY', value= '0', value_type='str') + cs_gate_delay = et.SubElement(ddr, 'efxpt:cs_gate_delay') + et.SubElement(cs_gate_delay, 'efxpt:param', name='EN_DLY_OVR', value= 'No', value_type='str') + et.SubElement(cs_gate_delay, 'efxpt:param', name='GATE_C_DLY', value= '3', value_type='int') + et.SubElement(cs_gate_delay, 'efxpt:param', name='GATE_F_DLY', value= '0', value_type='int') xml_string = et.tostring(root, 'utf-8') reparsed = expatbuilder.parseString(xml_string, False) - print_string = reparsed.toprettyxml(indent=" ") + print_string = reparsed.toprettyxml(indent=" ") # Remove lines with only whitespaces. Not sure why they are here print_string = os.linesep.join([s for s in print_string.splitlines() if s.strip()]) @@ -170,6 +199,9 @@ design.create('{2}', '{3}', './../build', overwrite=True) if i > 0: cmd += 'pll_config = {{ "CLKOUT{}_EN":"1", "CLKOUT{}_PIN":"{}" }}\n'.format(i, i, clock[0]) cmd += 'design.set_property("{}", pll_config, block_type="PLL")\n\n'.format(name) + else: + cmd += 'pll_config = {{ "CLKOUT{}_PIN":"{}" }}\n'.format(i, clock[0]) + cmd += 'design.set_property("{}", pll_config, block_type="PLL")\n\n'.format(name) cmd += 'target_freq = {\n' for i, clock in enumerate(block['clk_out']): @@ -201,7 +233,7 @@ design.create('{2}', '{3}', './../build', overwrite=True) def footer(self): return """ # Check design, generate constraints and reports -#design.generate(enable_bitstream=True) +design.generate(enable_bitstream=True) # Save the configured periphery design design.save()""" From b24475b07daf99361b20e779dca0b43d7a955e47 Mon Sep 17 00:00:00 2001 From: Franck Jullien Date: Wed, 22 Sep 2021 09:47:47 +0200 Subject: [PATCH 13/22] Add an hacked no we memory for Efinix Efinity synthesizer cannot infer RAM blocks with write enable. In order to workaround this (at least for the Litex SoC intergrated RAM/ROM) a dirty modified Memory class has been created. This class needs to be rewrite ! --- litex/gen/fhdl/memory.py | 175 +++++++++++++++++++++++++++++ litex/soc/integration/soc.py | 8 +- litex/soc/integration/soc_core.py | 6 +- litex/soc/interconnect/axi.py | 2 +- litex/soc/interconnect/wishbone.py | 9 +- 5 files changed, 191 insertions(+), 9 deletions(-) create mode 100644 litex/gen/fhdl/memory.py diff --git a/litex/gen/fhdl/memory.py b/litex/gen/fhdl/memory.py new file mode 100644 index 000000000..4ed681750 --- /dev/null +++ b/litex/gen/fhdl/memory.py @@ -0,0 +1,175 @@ +from migen.fhdl.structure import * +from migen.fhdl.module import * +from migen.fhdl.bitcontainer import bits_for +from migen.fhdl.tools import * +from migen.fhdl.tracer import get_obj_var_name +from migen.fhdl.verilog import _printexpr as verilog_printexpr +from migen.fhdl.specials import Special, _MemoryPort, _MemoryLocation + +(READ_FIRST, WRITE_FIRST, NO_CHANGE) = range(3) + +class Memory(Special): + def __init__(self, width, depth, init=None, name=None): + Special.__init__(self) + self.width = width + self.depth = depth + self.ports = [] + self.init = init + self.name_override = get_obj_var_name(name, "mem") + + def __getitem__(self, index): + # simulation only + return _MemoryLocation(self, index) + + def get_port(self, write_capable=False, async_read=False, + has_re=False, we_granularity=0, mode=WRITE_FIRST, + clock_domain="sys"): + if we_granularity >= self.width: + we_granularity = 0 + adr = Signal(max=self.depth) + dat_r = Signal(self.width) + if write_capable: + if we_granularity: + we = Signal(self.width//we_granularity) + else: + we = Signal() + dat_w = Signal(self.width) + else: + we = None + dat_w = None + if has_re: + re = Signal() + else: + re = None + mp = _MemoryPort(adr, dat_r, we, dat_w, + async_read, re, we_granularity, mode, + clock_domain) + self.ports.append(mp) + return mp + + @staticmethod + def emit_verilog(memory, ns, add_data_file): + r = "" + def gn(e): + if isinstance(e, Memory): + return ns.get_name(e) + else: + return verilog_printexpr(ns, e)[0] + adrbits = bits_for(memory.depth-1) + + for i in range(memory.width // 8): + r += "reg [" + str((memory.width//4)-1) + ":0] " \ + + gn(memory) + '_' + str(i) \ + + "[0:" + str(memory.depth-1) + "];\n" + + adr_regs = {} + data_regs = {} + for port in memory.ports: + if not port.async_read: + if port.mode == WRITE_FIRST: + adr_reg = Signal(name_override="memadr") + r += "reg [" + str(adrbits-1) + ":0] " \ + + gn(adr_reg) + ";\n" + adr_regs[id(port)] = adr_reg + else: + data_reg = Signal(name_override="memdat") + r += "reg [" + str(memory.width-1) + ":0] " \ + + gn(data_reg) + ";\n" + data_regs[id(port)] = data_reg + + for port in memory.ports: + r += "always @(posedge " + gn(port.clock) + ") begin\n" + if port.we is not None: + if port.we_granularity: + n = memory.width//port.we_granularity + for i in range(n): + if (i > 0): + r += "always @(posedge " + gn(port.clock) + ") begin\n" + m = i*port.we_granularity + M = (i+1)*port.we_granularity-1 + sl = "[" + str(M) + ":" + str(m) + "]" + r += "\tif (" + gn(port.we) + "[" + str(i) + "])\n" + r += "\t\t" + gn(memory) + '_' + str(i) + "[" + gn(port.adr) + "]" + " <= " + gn(port.dat_w) + sl + ";\n" + r += "end\n" + else: + r += "\tif (" + gn(port.we) + ")\n" + r += "\t\t" + gn(memory) + "[" + gn(port.adr) + "] <= " + gn(port.dat_w) + ";\n" + if not port.async_read: + if port.mode == WRITE_FIRST: + r += "always @(posedge " + gn(port.clock) + ") begin\n" + rd = "\t" + gn(adr_regs[id(port)]) + " <= " + gn(port.adr) + ";\n" + else: + bassign = "" + for i in range(memory.width // 8): + m = i*port.we_granularity + M = (i+1)*port.we_granularity-1 + sl = "[" + str(M) + ":" + str(m) + "]" + bassign += gn(data_regs[id(port)]) + sl + " <= " + gn(memory) + "_" + str(i) + "[" + gn(port.adr) + "];\n" + if port.mode == READ_FIRST: + rd = "\t" + bassign + elif port.mode == NO_CHANGE: + rd = "\tif (!" + gn(port.we) + ")\n" \ + + "\t\t" + bassign + if port.re is None: + r += rd + else: + r += "\tif (" + gn(port.re) + ")\n" + r += "\t" + rd.replace("\n\t", "\n\t\t") + r += "end\n\n" + + for port in memory.ports: + if port.async_read: + r += "assign " + gn(port.dat_r) + " = " + gn(memory) + "[" + gn(port.adr) + "];\n" + else: + if port.mode == WRITE_FIRST: + for i in range(memory.width // 8): + m = i*port.we_granularity + M = (i+1)*port.we_granularity-1 + sl = "[" + str(M) + ":" + str(m) + "]" + r += "assign " + gn(port.dat_r) + sl + " = " + gn(memory) + "_" + str(i) + "[" + gn(adr_regs[id(port)]) + "];\n" + else: + r += "assign " + gn(port.dat_r) + " = " + gn(data_regs[id(port)]) + ";\n" + r += "\n" + + if memory.init is not None: + content_7_0 = "" + content_15_8 = "" + content_23_16 = "" + content_31_24 = "" + formatter = "{:0" + str(int(memory.width / 4)) + "X}\n" + + init_7_0 = [] + init_15_8 = [] + init_23_16 = [] + init_31_24 = [] + + for w in memory.init: + init_7_0.append(w & 0xff) + init_15_8.append((w >> 8) & 0xff) + init_23_16.append((w >> 16) & 0xff) + init_31_24.append((w >> 24) & 0xff) + + for d in init_7_0: + content_7_0 += formatter.format(d) + + for d in init_15_8: + content_15_8 += formatter.format(d) + + for d in init_23_16: + content_23_16 += formatter.format(d) + + for d in init_31_24: + content_31_24 += formatter.format(d) + + memory_filename1 = add_data_file(gn(memory) + "1.init", content_7_0) + memory_filename2 = add_data_file(gn(memory) + "2.init", content_15_8) + memory_filename3 = add_data_file(gn(memory) + "3.init", content_23_16) + memory_filename4 = add_data_file(gn(memory) + "4.init", content_31_24) + r += "initial begin\n" + r += "\t$readmemh(\"" + memory_filename1 + "\", " + gn(memory)+ "_0" + ");\n" + r += "\t$readmemh(\"" + memory_filename2 + "\", " + gn(memory)+ "_1" + ");\n" + r += "\t$readmemh(\"" + memory_filename3 + "\", " + gn(memory)+ "_2" + ");\n" + r += "\t$readmemh(\"" + memory_filename4 + "\", " + gn(memory)+ "_3" + ");\n" + r += "end\n\n" + + return r diff --git a/litex/soc/integration/soc.py b/litex/soc/integration/soc.py index cc8173522..ddbb9fda1 100644 --- a/litex/soc/integration/soc.py +++ b/litex/soc/integration/soc.py @@ -815,7 +815,7 @@ class SoC(Module): self.check_if_exists(name) setattr(self.submodules, name, SoCController(**kwargs)) - def add_ram(self, name, origin, size, contents=[], mode="rw"): + def add_ram(self, name, origin, size, contents=[], mode="rw", no_we=False): ram_cls = { "wishbone": wishbone.SRAM, "axi-lite": axi.AXILiteSRAM, @@ -825,7 +825,7 @@ class SoC(Module): "axi-lite": axi.AXILiteInterface, }[self.bus.standard] ram_bus = interface_cls(data_width=self.bus.data_width) - ram = ram_cls(size, bus=ram_bus, init=contents, read_only=(mode == "r")) + ram = ram_cls(size, bus=ram_bus, init=contents, read_only=(mode == "r"), no_we=no_we) self.bus.add_slave(name, ram.bus, SoCRegion(origin=origin, size=size, mode=mode)) self.check_if_exists(name) self.logger.info("RAM {} {} {}.".format( @@ -834,8 +834,8 @@ class SoC(Module): self.bus.regions[name])) setattr(self.submodules, name, ram) - def add_rom(self, name, origin, size, contents=[], mode="r"): - self.add_ram(name, origin, size, contents, mode=mode) + def add_rom(self, name, origin, size, contents=[], mode="r", no_we=False): + self.add_ram(name, origin, size, contents, mode=mode, no_we=no_we) def init_rom(self, name, contents=[], auto_size=True): self.logger.info("Initializing ROM {} with contents (Size: {}).".format( diff --git a/litex/soc/integration/soc_core.py b/litex/soc/integration/soc_core.py index 94c3b3ae9..043ce4ead 100644 --- a/litex/soc/integration/soc_core.py +++ b/litex/soc/integration/soc_core.py @@ -79,10 +79,12 @@ class SoCCore(LiteXSoC): integrated_rom_size = 0, integrated_rom_mode = "r", integrated_rom_init = [], + integrated_rom_no_we = False, # SRAM parameters integrated_sram_size = 0x2000, integrated_sram_init = [], + integrated_sram_no_we = False, # MAIN_RAM parameters integrated_main_ram_size = 0, @@ -197,11 +199,11 @@ class SoCCore(LiteXSoC): # Add integrated ROM if integrated_rom_size: - self.add_rom("rom", self.cpu.reset_address, integrated_rom_size, integrated_rom_init, integrated_rom_mode) + self.add_rom("rom", self.cpu.reset_address, integrated_rom_size, integrated_rom_init, integrated_rom_mode, no_we=integrated_rom_no_we) # Add integrated SRAM if integrated_sram_size: - self.add_ram("sram", self.mem_map["sram"], integrated_sram_size) + self.add_ram("sram", self.mem_map["sram"], integrated_sram_size, no_we=integrated_sram_no_we) # Add integrated MAIN_RAM (only useful when no external SRAM/SDRAM is available) if integrated_main_ram_size: diff --git a/litex/soc/interconnect/axi.py b/litex/soc/interconnect/axi.py index b40532f0d..0117b49e4 100644 --- a/litex/soc/interconnect/axi.py +++ b/litex/soc/interconnect/axi.py @@ -783,7 +783,7 @@ class AXILite2CSR(Module): # AXILite SRAM ------------------------------------------------------------------------------------- class AXILiteSRAM(Module): - def __init__(self, mem_or_size, read_only=None, init=None, bus=None): + def __init__(self, mem_or_size, read_only=None, init=None, bus=None, no_we=False): if bus is None: bus = AXILiteInterface() self.bus = bus diff --git a/litex/soc/interconnect/wishbone.py b/litex/soc/interconnect/wishbone.py index 85d9c6b5f..88d0bb79a 100644 --- a/litex/soc/interconnect/wishbone.py +++ b/litex/soc/interconnect/wishbone.py @@ -329,7 +329,7 @@ class Converter(Module): # Wishbone SRAM ------------------------------------------------------------------------------------ class SRAM(Module): - def __init__(self, mem_or_size, read_only=None, init=None, bus=None): + def __init__(self, mem_or_size, read_only=None, init=None, bus=None, no_we=False): if bus is None: bus = Interface() self.bus = bus @@ -338,7 +338,12 @@ class SRAM(Module): assert(mem_or_size.width <= bus_data_width) self.mem = mem_or_size else: - self.mem = Memory(bus_data_width, mem_or_size//(bus_data_width//8), init=init) + if no_we: + from litex.gen.fhdl.memory import Memory as NoWeMemory + self.mem = NoWeMemory(bus_data_width, mem_or_size//(bus_data_width//8), init=init) + else: + self.mem = Memory(bus_data_width, mem_or_size//(bus_data_width//8), init=init) + if read_only is None: if hasattr(self.mem, "bus_read_only"): read_only = self.mem.bus_read_only From 109dbd1d622704f66453ccb244d1d47811d914d3 Mon Sep 17 00:00:00 2001 From: Franck Jullien Date: Wed, 22 Sep 2021 09:53:22 +0200 Subject: [PATCH 14/22] efinity: small fixes - do not include *.vh files in project, - add self.options to class EfinityToolchain - remove unconditional call to self.ifacewriter.add_ddr_xml --- litex/build/efinix/efinity.py | 8 +++++--- litex/build/efinix/ifacewriter.py | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/litex/build/efinix/efinity.py b/litex/build/efinix/efinity.py index 71947b152..c473f031c 100644 --- a/litex/build/efinix/efinity.py +++ b/litex/build/efinix/efinity.py @@ -231,8 +231,9 @@ def _build_xml(partnumber, build_name, sources, additional_xml_commands): design_info = et.SubElement(root, 'efx:design_info') et.SubElement(design_info, "efx:top_module", name = build_name) for filename, language, library in sources: - val = {'name':filename, 'version':'default', 'library':'default'} - et.SubElement(design_info, "efx:design_file", val) + if '.vh' not in filename: + val = {'name':filename, 'version':'default', 'library':'default'} + et.SubElement(design_info, "efx:design_file", val) et.SubElement(design_info, "efx:top_vhdl_arch", name = "") constraint_info = et.SubElement(root, "efx:constraint_info") @@ -270,6 +271,7 @@ class EfinityToolchain(): attr_translate = {} def __init__(self, efinity_path): + self.options = {} self.clocks = dict() self.false_paths = set() self.efinity_path = efinity_path @@ -340,7 +342,7 @@ class EfinityToolchain(): # DDR doesn't have Python API so we need to configure it # directly in the peri.xml file - self.ifacewriter.add_ddr_xml(build_name) + # self.ifacewriter.add_ddr_xml(build_name) # Run if run: diff --git a/litex/build/efinix/ifacewriter.py b/litex/build/efinix/ifacewriter.py index 6c2d7439d..9d709e25e 100644 --- a/litex/build/efinix/ifacewriter.py +++ b/litex/build/efinix/ifacewriter.py @@ -169,7 +169,7 @@ is_verbose = {1} design = DesignAPI(is_verbose) device = DeviceAPI(is_verbose) -design.create('{2}', '{3}', './../build', overwrite=True) +design.create('{2}', '{3}', './../gateware', overwrite=True) """ return header.format(self.efinity_path, 'True', build_name, partnumber) From 179a8018b341308cdd39af95953511aaafd19986 Mon Sep 17 00:00:00 2001 From: Franck Jullien Date: Thu, 23 Sep 2021 17:21:12 +0200 Subject: [PATCH 15/22] efinix: RGMII phy should be operational (no tested) PLL infrastructure should be complete now. We can also use DDIO input and outputs. However, there is problem (bug) during P&R: ERROR(1): [Line 52] Block auto_eth_tx_delayed_clk is an output pad but sub-block 1 is not an output pad location. Inderface Designer validation doesn't report any problem. I have a test project with the same configuration (I compared the reports for blocks configuration) and it works. --- litex/build/efinix/ifacewriter.py | 74 ++++++++-- litex/build/efinix/platform.py | 53 +++++-- litex/build/efinix/rgmii.py | 194 ++++++++++++++++++++++++++ litex/build/generic_platform.py | 20 ++- litex/soc/cores/clock/efinix_trion.py | 47 ++++--- 5 files changed, 348 insertions(+), 40 deletions(-) create mode 100644 litex/build/efinix/rgmii.py diff --git a/litex/build/efinix/ifacewriter.py b/litex/build/efinix/ifacewriter.py index 9d709e25e..828c56a26 100644 --- a/litex/build/efinix/ifacewriter.py +++ b/litex/build/efinix/ifacewriter.py @@ -180,15 +180,70 @@ design.create('{2}', '{3}', './../gateware', overwrite=True) return b return None + def generate_gpio(self, block, verbose=True): + name = block['name'] + mode = block['mode'] + cmd = '' + + +# TODO: {'type': 'GPIO', 'mode': 'OUTPUT', 'location': 'U16', 'size': 4, 'in_reg': 'DDIO_RESYNC', 'out_clk_pin': '', 'is_inclk_inverted': False, 'name': 'auto_ethtx_tx_data_d1', 'name_d2': 'auto_ethtx_tx_data_d2'} + + if mode == 'INPUT': + if len(block['location']) == 1: + cmd += 'design.create_input_gpio("{}")\n'.format(name) + cmd += 'design.assign_pkg_pin("{}","{}")\n'.format(name, block['location'][0]) + else: + cmd += 'design.create_input_gpio("{}",{},0)\n'.format(name, block['size']-1) + for i, pad in enumerate(block['location']): + cmd += 'design.assign_pkg_pin("{}[{}]","{}")\n'.format(name, i, pad) + if 'in_reg' in block: + cmd += 'design.set_property("{}","IN_REG","{}")\n'.format(name, block['in_reg']) + cmd += 'design.set_property("{}","IN_CLK_PIN","{}")\n\n'.format(name, block['in_clk_pin']) + return cmd + + if mode == 'OUTPUT': + if len(block['location']) == 1: + cmd += 'design.create_output_gpio("{}")\n'.format(name) + cmd += 'design.assign_pkg_pin("{}","{}")\n'.format(name, block['location'][0]) + else: + cmd += 'design.create_input_gpio("{}",{},0)\n'.format(name, block['size']-1) + for i, pad in enumerate(block['location']): + cmd += 'design.assign_pkg_pin("{}[{}]","{}")\n'.format(name, i, pad) + if 'out_reg' in block: + cmd += 'design.set_property("{}","OUT_REG","{}")\n'.format(name, block['out_reg']) + cmd += 'design.set_property("{}","OUT_CLK_PIN","{}")\n\n'.format(name, block['out_clk_pin']) + return cmd + + if mode == 'INPUT_CLK': + cmd += 'design.create_input_clock_gpio("{}")\n'.format(name) + cmd += 'design.set_property("{}","IN_PIN","{}")\n'.format(name, name) + cmd += 'design.assign_pkg_pin("{}","{}")\n\n'.format(name, block['location']) + return cmd + + if mode == 'OUTPUT_CLK': + cmd += 'design.create_clockout_gpio("{}")\n'.format(name) + cmd += 'design.set_property("{}","OUT_CLK_PIN","{}")\n'.format(name, name) + cmd += 'design.assign_pkg_pin("{}","{}")\n\n'.format(name, block['location']) + return cmd + + cmd = '# TODO: ' + str(block) +'\n' + return cmd + def generate_pll(self, block, verbose=True): name = block['name'] cmd = '# ---------- PLL {} ---------\n'.format(name) cmd += 'design.create_block("{}", block_type="PLL")\n'.format(name) - cmd += 'design.gen_pll_ref_clock("{}", pll_res="{}", refclk_src="{}", refclk_name="{}", ext_refclk_no="{}")\n\n' \ - .format(name, block['resource'], block['input_clock'], block['input_clock_name'], block['clock_no']) - cmd += 'pll_config = {{ "REFCLK_FREQ":"{}" }}\n'.format(block['input_freq'] / 1e6) - cmd += 'design.set_property("{}", pll_config, block_type="PLL")\n\n'.format(name) + + if block['input_clock'] == 'EXTERNAL': + cmd += 'design.gen_pll_ref_clock("{}", pll_res="{}", refclk_src="{}", refclk_name="{}", ext_refclk_no="{}")\n\n' \ + .format(name, block['resource'], block['input_clock'], block['input_clock_name'], block['clock_no']) + else: + cmd += 'design.gen_pll_ref_clock("{}", pll_res="{}", refclk_name="{}", refclk_src="CORE")\n'.format(name, block['resource'], block['input_signal']) + cmd += 'design.set_property("{}", "CORE_CLK_PIN", "{}", block_type="PLL")\n\n'.format(name, block['input_signal']) + + cmd += 'pll_config = {{ "REFCLK_FREQ":"{}" }}\n'.format(block['input_freq'] / 1e6) + cmd += 'design.set_property("{}", pll_config, block_type="PLL")\n\n'.format(name) cmd += 'design.set_property("{}","LOCKED_PIN","{}", block_type="PLL")\n'.format(name, block['locked']) if block['reset'] != '': @@ -198,10 +253,10 @@ design.create('{2}', '{3}', './../gateware', overwrite=True) for i, clock in enumerate(block['clk_out']): if i > 0: cmd += 'pll_config = {{ "CLKOUT{}_EN":"1", "CLKOUT{}_PIN":"{}" }}\n'.format(i, i, clock[0]) - cmd += 'design.set_property("{}", pll_config, block_type="PLL")\n\n'.format(name) else: cmd += 'pll_config = {{ "CLKOUT{}_PIN":"{}" }}\n'.format(i, clock[0]) - cmd += 'design.set_property("{}", pll_config, block_type="PLL")\n\n'.format(name) + + cmd += 'design.set_property("{}", pll_config, block_type="PLL")\n\n'.format(name) cmd += 'target_freq = {\n' for i, clock in enumerate(block['clk_out']): @@ -215,8 +270,8 @@ design.create('{2}', '{3}', './../gateware', overwrite=True) cmd += 'print("#### {} ####")\n'.format(name) cmd += 'clksrc_info = design.trace_ref_clock("{}", block_type="PLL")\n'.format(name) cmd += 'pprint.pprint(clksrc_info)\n' - cmd += 'clock_source_prop = ["REFCLK_SOURCE", "EXT_CLK", "CLKOUT0_EN", "CLKOUT1_EN","REFCLK_FREQ", "RESOURCE"]\n' - cmd += 'clock_source_prop += ["M", "N", "O", "CLKOUT0_DIV", "CLKOUT2_DIV", "VCO_FREQ", "PLL_FREQ"]\n' + cmd += 'clock_source_prop = ["REFCLK_SOURCE", "CORE_CLK_PIN", "EXT_CLK", "CLKOUT0_EN", "CLKOUT1_EN","REFCLK_FREQ", "RESOURCE"]\n' + cmd += 'clock_source_prop += ["CLKOUT0_FREQ", "CLKOUT1_FREQ", "CLKOUT2_FREQ"]\n' cmd += 'prop_map = design.get_property("{}", clock_source_prop, block_type="PLL")\n'.format(name) cmd += 'pprint.pprint(prop_map)\n' @@ -228,6 +283,9 @@ design.create('{2}', '{3}', './../gateware', overwrite=True) for b in self.blocks: if b['type'] == 'PLL': output += self.generate_pll(b) + if b['type'] == 'GPIO': + output += self.generate_gpio(b) + return output def footer(self): diff --git a/litex/build/efinix/platform.py b/litex/build/efinix/platform.py index aadc324bd..3b2aa0957 100644 --- a/litex/build/efinix/platform.py +++ b/litex/build/efinix/platform.py @@ -18,6 +18,9 @@ class EfinixPlatform(GenericPlatform): def __init__(self, *args, toolchain="efinity", **kwargs): GenericPlatform.__init__(self, *args, **kwargs) + self.pll_available = ['PLL_TL0', 'PLL_TR0', 'PLL_TR1', 'PLL_TR2', 'PLL_TR3', 'PLL_BR0', 'PLL_BR1', 'PLL_BR2', 'PLL_BL0'] + self.pll_used = [] + if 'LITEX_ENV_EFINITY' in os.environ: self.efinity_path = os.environ['LITEX_ENV_EFINITY'].rstrip('/') os.environ['EFINITY_HOME'] = self.efinity_path @@ -55,29 +58,63 @@ class EfinixPlatform(GenericPlatform): # get_pin_location(p[1]) # not tested with subsignal like get_pin_location(p.clk) def get_pin_location(self, sig): + if sig is None: + return None sc = self.constraint_manager.get_sig_constraints() for s, pins, others, resource in sc: - if s == sig: - return pins[0] + if (s == sig) and (pins[0] != 'X'): + return pins return None def get_pin_name(self, sig): + if sig is None: + return None sc = self.constraint_manager.get_sig_constraints() for s, pins, others, resource in sc: if s == sig: - return resource[0] + if resource[2]: + return resource[0] + '_' + resource[2] + else: + return resource[0] return None - def add_iface_io(self, name, size=1): + def get_sig_constraint(self, sig): + sc = self.constraint_manager.get_sig_constraints() + for s, pins, others, resource in sc: + if s == sig: + return sc + return None + + def add_iface_io(self, name, size=1, append=True): self.add_extension([(name, 0, Pins(size))]) tmp = self.request(name) # We don't want this IO to be in the interface configuration file as a simple GPIO - self.toolchain.specials_gpios.append(tmp) + if append: + self.toolchain.specials_gpios.append(tmp) return tmp - def add_iface_ios(self, io): + def add_iface_ios(self, io, append=True): self.add_extension(io) tmp = self.request(io[0][0]) - for s in tmp.flatten(): - self.toolchain.specials_gpios.append(s) + if append: + for s in tmp.flatten(): + self.toolchain.specials_gpios.append(s) return tmp + + def del_record_signal(self, record, sig): + for pos, (name, item) in enumerate(vars(record).items()): + if isinstance(item, Signal): + if item == sig: + # Two first pos are name and layout + del record.layout[pos-2] + delattr(record, name) + break + + def get_pll_resource(self, name): + self.pll_used.append(name) + self.pll_available.remove(name) + print('Pll used : ' + str(self.pll_used)) + print('Pll pll_available: ' + str(self.pll_available)) + + def get_free_pll_resource(self): + return self.pll_available[0] \ No newline at end of file diff --git a/litex/build/efinix/rgmii.py b/litex/build/efinix/rgmii.py new file mode 100644 index 000000000..ac6db1549 --- /dev/null +++ b/litex/build/efinix/rgmii.py @@ -0,0 +1,194 @@ +# +# This file is part of LiteEth. +# +# Copyright (c) 2015-2020 Florent Kermarrec +# SPDX-License-Identifier: BSD-2-Clause + +# RGMII PHY for 7-Series Xilinx FPGA + +from migen import * +from migen.genlib.resetsync import AsyncResetSynchronizer + +from litex.build.generic_platform import * +from litex.soc.cores.clock import * + +from liteeth.common import * +from liteeth.phy.common import * + + +class LiteEthPHYRGMIITX(Module): + def __init__(self, platform, pads): + self.sink = sink = stream.Endpoint(eth_phy_description(8)) + + # # # + + name = platform.get_pin_name(pads.tx_data) + pad = platform.get_pin_location(pads.tx_data) + name = 'auto_' + name + tx_data_d1 = [] + tx_data_d2 = [] + # This a workaround, we could use signals with 4 bits but there is + # a problem with the Python API that prevents it + + #tx_data_d1 = platform.add_iface_io(name + '_HI', 4) + #tx_data_d2 = platform.add_iface_io(name + '_LO', 4) + + for i in range(4): + tx_data_d1.append(platform.add_iface_io(name + str(i) + '_HI')) + tx_data_d2.append(platform.add_iface_io(name + str(i) + '_LO')) + + block = {'type':'GPIO', + 'mode':'OUTPUT', + 'name':name + str(i), + #'name':name, + 'location':[pad[i]], + 'size':1, + #'location':pad, + #'size':4, + 'out_reg':'DDIO_RESYNC', + 'out_clk_pin':'auto_eth_tx_clk', # -------------------------- TODO + 'is_inclk_inverted':False + } + platform.toolchain.ifacewriter.blocks.append(block) + + platform.del_record_signal(pads, pads.tx_data) + + #self.comb += pads.tx_ctl.eq(sink.valid) + #self.comb += tx_data_d1.eq(sink.data[0:4]) + #self.comb += tx_data_d2.eq(sink.data[4:8]) + #self.comb += sink.ready.eq(1) + + self.comb += [ pads.tx_ctl.eq(sink.valid), + tx_data_d1[0].eq(sink.data[0]), + tx_data_d1[1].eq(sink.data[1]), + tx_data_d1[2].eq(sink.data[2]), + tx_data_d1[3].eq(sink.data[3]), + tx_data_d2[0].eq(sink.data[4]), + tx_data_d2[1].eq(sink.data[5]), + tx_data_d2[2].eq(sink.data[6]), + tx_data_d2[3].eq(sink.data[7]), + sink.ready.eq(1), + ] + +class LiteEthPHYRGMIIRX(Module): + def __init__(self, platform, pads): + self.source = source = stream.Endpoint(eth_phy_description(8)) + + # # # + + rx_ctl_d = Signal() + rx_data = Signal(8) + + # pads.rx_ctl can't be connected to a special GPIO (DDIO) because + # of this board layout. + + # Add a DDIO_RESYNC input block with 'auto_eth_rx_clk' as clock + name = platform.get_pin_name(pads.rx_data) + pad = platform.get_pin_location(pads.rx_data) + name = 'auto_' + name + rx_data_d1 = [] + rx_data_d2 = [] + # This a workaround, we could use signals with 4 bits but there is + # a problem with the Python API that prevents it + for i in range(4): + rx_data_d1.append(platform.add_iface_io(name + str(i) + '_HI')) + rx_data_d2.append(platform.add_iface_io(name + str(i) + '_LO')) + + block = {'type':'GPIO', + 'mode':'INPUT', + 'name':name + str(i), + 'location':[pad[i]], + 'size':1, + 'in_reg':'DDIO_RESYNC', + 'in_clk_pin':'auto_eth_rx_clk', + 'is_inclk_inverted':False + } + platform.toolchain.ifacewriter.blocks.append(block) + + platform.del_record_signal(pads, pads.rx_data) + + self.comb += rx_data.eq(Cat(rx_data_d1[0], rx_data_d1[1], rx_data_d1[2], rx_data_d1[3], + rx_data_d2[0], rx_data_d2[1], rx_data_d2[2], rx_data_d2[3])) + + self.sync += rx_ctl_d.eq(pads.rx_ctl) + + last = Signal() + self.comb += last.eq(~pads.rx_ctl & rx_ctl_d) + self.sync += [ + source.valid.eq(pads.rx_ctl), + source.data.eq(rx_data) + ] + self.comb += source.last.eq(last) + +class LiteEthPHYRGMIICRG(Module, AutoCSR): + def __init__(self, platform, clock_pads, with_hw_init_reset, tx_delay=2e-9, hw_reset_cycles=256): + self._reset = CSRStorage() + + # # # + + # Clocks + + self.clock_domains.cd_eth_rx = ClockDomain() + self.clock_domains.cd_eth_tx = ClockDomain() + self.clock_domains.cd_eth_tx_delayed = ClockDomain(reset_less=True) + + # Add a GPIO block with clock input property + # Add a input 'auto_eth_rx_clk' to the top.v + clkrx = platform.add_iface_io('auto_eth_rx_clk') + block = {'type':'GPIO', + 'size':1, + # Get the location from the original resource + 'location': platform.get_pin_location(clock_pads.rx)[0], + 'name':platform.get_pin_name(clkrx), + 'mode':'INPUT_CLK' + } + platform.toolchain.ifacewriter.blocks.append(block) + self.comb += self.cd_eth_rx.clk.eq(clkrx) + + clktx = platform.add_iface_io('auto_eth_tx_delayed_clk') + block = {'type':'GPIO', + 'size':1, + # Get the location from the original resource + 'location': platform.get_pin_location(clock_pads.tx)[0], + 'name':platform.get_pin_name(clktx), + 'mode':'OUTPUT_CLK' + } + platform.toolchain.ifacewriter.blocks.append(block) + self.comb += clktx.eq(self.cd_eth_tx.clk) + + self.submodules.pll = pll = TRIONPLL(platform) + # Internal clock must come from a named signal + pll.register_clkin(None, 125e6, name='auto_eth_rx_clk') + pll.create_clkout(None, 125e6, phase=90, name='auto_eth_tx_delayed_clk') + pll.create_clkout(None, 125e6, name='auto_eth_tx_clk') + + platform.delete(clock_pads) + + ## Reset + #self.reset = reset = Signal() + #if with_hw_init_reset: + # self.submodules.hw_reset = LiteEthPHYHWReset(cycles=hw_reset_cycles) + # self.comb += reset.eq(self._reset.storage | self.hw_reset.reset) + #else: + # self.comb += reset.eq(self._reset.storage) + #if hasattr(clock_pads, "rst_n"): + # self.comb += clock_pads.rst_n.eq(~reset) + #self.specials += [ + # AsyncResetSynchronizer(self.cd_eth_tx, reset), + # AsyncResetSynchronizer(self.cd_eth_rx, reset), + #] + + +class LiteEthPHYRGMII(Module, AutoCSR): + dw = 8 + tx_clk_freq = 125e6 + rx_clk_freq = 125e6 + def __init__(self, platform, clock_pads, pads, with_hw_init_reset=True, tx_delay=2e-9, rx_delay=2e-9, + iodelay_clk_freq=200e6, hw_reset_cycles=256): + self.submodules.crg = LiteEthPHYRGMIICRG(platform, clock_pads, with_hw_init_reset, tx_delay, hw_reset_cycles) + self.submodules.tx = ClockDomainsRenamer("eth_tx")(LiteEthPHYRGMIITX(platform, pads)) + self.submodules.rx = ClockDomainsRenamer("eth_rx")(LiteEthPHYRGMIIRX(platform, pads)) + self.sink, self.source = self.tx.sink, self.rx.source + + #if hasattr(pads, "mdc"): + # self.submodules.mdio = LiteEthPHYMDIO(pads) diff --git a/litex/build/generic_platform.py b/litex/build/generic_platform.py index a09156e34..12461ed62 100644 --- a/litex/build/generic_platform.py +++ b/litex/build/generic_platform.py @@ -192,6 +192,12 @@ class ConstraintManager: def delete(self, signal): for res, obj in self.matched: + if isinstance(signal, Record): + if isinstance(obj, Signal): + continue + else: + if isinstance(obj, Record): + continue if obj == signal: self.matched.remove((res, obj)) @@ -280,12 +286,14 @@ class ConstraintManager: if has_subsignals: for element in resource[2:]: if isinstance(element, Subsignal): - sig = getattr(obj, element.name) - pins, others = _separate_pins(top_constraints + - element.constraints) - pins = self.connector_manager.resolve_identifiers(pins) - r.append((sig, pins, others, - (name, number, element.name))) + # Because we could have removed one Signal From the record + if hasattr(obj, element.name): + sig = getattr(obj, element.name) + pins, others = _separate_pins(top_constraints + + element.constraints) + pins = self.connector_manager.resolve_identifiers(pins) + r.append((sig, pins, others, + (name, number, element.name))) else: pins, others = _separate_pins(top_constraints) pins = self.connector_manager.resolve_identifiers(pins) diff --git a/litex/soc/cores/clock/efinix_trion.py b/litex/soc/cores/clock/efinix_trion.py index a179e1857..3811b7d63 100644 --- a/litex/soc/cores/clock/efinix_trion.py +++ b/litex/soc/cores/clock/efinix_trion.py @@ -53,33 +53,44 @@ class TRIONPLL(Module): self.platform.toolchain.ifacewriter.blocks.append(block) - def register_clkin(self, clkin, freq): + def register_clkin(self, clkin, freq, name= ''): block = self.platform.toolchain.ifacewriter.get_block(self.pll_name) - # If clkin has resource, PLL clock input is EXTERNAL - # When PLL clock is external, it must not be present in the top file - # Add a test on clkin resource here block['input_clock_name'] = self.platform.get_pin_name(clkin) - pin_name = self.platform.get_pin_location(clkin) - self.platform.delete(clkin) + # If clkin has a pin number, PLL clock input is EXTERNAL + if self.platform.get_pin_location(clkin): + + pad_name = self.platform.get_pin_location(clkin) + self.platform.delete(clkin) - #tpl = "create_clock -name {clk} -period {period} [get_ports {{{clk}}}]" - #sdc = self.platform.toolchain.additional_sdc_commands - #sdc.append(tpl.format(clk=block['input_clock_name'], period=1/freq)) + #tpl = "create_clock -name {clk} -period {period} [get_ports {{{clk}}}]" + #sdc = self.platform.toolchain.additional_sdc_commands + #sdc.append(tpl.format(clk=block['input_clock_name'], period=1/freq)) - parser = EfinixDbParser(self.platform.efinity_path, self.platform.device) - (pll_res, clock_no) = parser.get_pll_inst_from_pin(pin_name) + parser = EfinixDbParser(self.platform.efinity_path, self.platform.device) + try: + (pll_res, clock_no) = parser.get_pll_inst_from_pin(pad_name) + except: + self.logger.error("Cannot find a pll with {} as input".format(pad_name)) + quit() + + block['input_clock'] = 'EXTERNAL' + block['resource'] = pll_res + block['clock_no'] = clock_no + self.logger.info("Clock source: {}, using EXT_CLK{}".format(block['input_clock'], clock_no)) + self.platform.get_pll_resource(pll_res) + else: + block['input_clock'] = 'INTERNAL' + block['resource'] = self.platform.get_free_pll_resource() + block['input_signal'] = name + self.logger.info("Clock source: {}".format(block['input_clock'])) - block['input_clock'] = 'EXTERNAL' block['input_freq'] = freq - block['resource'] = pll_res - block['clock_no'] = clock_no - self.logger.info("Using {}".format(pll_res)) - self.logger.info("Clock source: {}, using EXT_CLK{}".format(block['input_clock'], clock_no)) + self.logger.info("Using {}".format(block['resource'])) - def create_clkout(self, cd, freq, phase=0, margin=1e-2, name='', with_reset=False, user_clk=True): + def create_clkout(self, cd, freq, phase=0, margin=1e-2, name='', with_reset=False): assert self.nclkouts < self.nclkouts_max if name != '': @@ -87,7 +98,7 @@ class TRIONPLL(Module): else: clk_out_name = '{}_CLKOUT{}'.format(self.pll_name, self.nclkouts) - if user_clk == True: + if cd != None: self.platform.add_extension([(clk_out_name, 0, Pins(1))]) tmp = self.platform.request(clk_out_name) From 1ea0797c82f484743d33eeaf2bf0e7138c6079c3 Mon Sep 17 00:00:00 2001 From: Franck Jullien Date: Mon, 27 Sep 2021 10:11:00 +0200 Subject: [PATCH 16/22] efinix: ifacewriter: fix DRIVE_STRENGTH and REFCLK_FREQ --- litex/build/efinix/ifacewriter.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/litex/build/efinix/ifacewriter.py b/litex/build/efinix/ifacewriter.py index 828c56a26..670e4aac2 100644 --- a/litex/build/efinix/ifacewriter.py +++ b/litex/build/efinix/ifacewriter.py @@ -209,9 +209,15 @@ design.create('{2}', '{3}', './../gateware', overwrite=True) cmd += 'design.create_input_gpio("{}",{},0)\n'.format(name, block['size']-1) for i, pad in enumerate(block['location']): cmd += 'design.assign_pkg_pin("{}[{}]","{}")\n'.format(name, i, pad) + if 'out_reg' in block: cmd += 'design.set_property("{}","OUT_REG","{}")\n'.format(name, block['out_reg']) - cmd += 'design.set_property("{}","OUT_CLK_PIN","{}")\n\n'.format(name, block['out_clk_pin']) + cmd += 'design.set_property("{}","OUT_CLK_PIN","{}")\n'.format(name, block['out_clk_pin']) + + if 'drive_strength' in block: + cmd += 'design.set_property("{}","DRIVE_STRENGTH","4")\n'.format(name, block['drive_strength']) + + cmd += '\n' return cmd if mode == 'INPUT_CLK': @@ -234,6 +240,7 @@ design.create('{2}', '{3}', './../gateware', overwrite=True) cmd = '# ---------- PLL {} ---------\n'.format(name) cmd += 'design.create_block("{}", block_type="PLL")\n'.format(name) cmd += 'pll_config = {{ "REFCLK_FREQ":"{}" }}\n'.format(block['input_freq'] / 1e6) + cmd += 'design.set_property("{}", pll_config, block_type="PLL")\n\n'.format(name) if block['input_clock'] == 'EXTERNAL': cmd += 'design.gen_pll_ref_clock("{}", pll_res="{}", refclk_src="{}", refclk_name="{}", ext_refclk_no="{}")\n\n' \ @@ -242,9 +249,6 @@ design.create('{2}', '{3}', './../gateware', overwrite=True) cmd += 'design.gen_pll_ref_clock("{}", pll_res="{}", refclk_name="{}", refclk_src="CORE")\n'.format(name, block['resource'], block['input_signal']) cmd += 'design.set_property("{}", "CORE_CLK_PIN", "{}", block_type="PLL")\n\n'.format(name, block['input_signal']) - cmd += 'pll_config = {{ "REFCLK_FREQ":"{}" }}\n'.format(block['input_freq'] / 1e6) - cmd += 'design.set_property("{}", pll_config, block_type="PLL")\n\n'.format(name) - cmd += 'design.set_property("{}","LOCKED_PIN","{}", block_type="PLL")\n'.format(name, block['locked']) if block['reset'] != '': cmd += 'design.set_property("{}","RSTN_PIN","{}", block_type="PLL")\n\n'.format(name, block['reset']) From 06ff638f7a90922b781dd2eeb3c76042b55cb64d Mon Sep 17 00:00:00 2001 From: Franck Jullien Date: Mon, 27 Sep 2021 10:11:58 +0200 Subject: [PATCH 17/22] efinix: rgmii: fix, it's in a working state --- litex/build/efinix/rgmii.py | 152 +++++++++++++++++++++--------------- 1 file changed, 89 insertions(+), 63 deletions(-) diff --git a/litex/build/efinix/rgmii.py b/litex/build/efinix/rgmii.py index ac6db1549..82ca6f3ac 100644 --- a/litex/build/efinix/rgmii.py +++ b/litex/build/efinix/rgmii.py @@ -1,6 +1,7 @@ # # This file is part of LiteEth. # +# Copyright (c) 2021 Franck Jullien # Copyright (c) 2015-2020 Florent Kermarrec # SPDX-License-Identifier: BSD-2-Clause @@ -15,7 +16,6 @@ from litex.soc.cores.clock import * from liteeth.common import * from liteeth.phy.common import * - class LiteEthPHYRGMIITX(Module): def __init__(self, platform, pads): self.sink = sink = stream.Endpoint(eth_phy_description(8)) @@ -25,74 +25,81 @@ class LiteEthPHYRGMIITX(Module): name = platform.get_pin_name(pads.tx_data) pad = platform.get_pin_location(pads.tx_data) name = 'auto_' + name - tx_data_d1 = [] - tx_data_d2 = [] + tx_data_h = [] + tx_data_l = [] + # This a workaround, we could use signals with 4 bits but there is # a problem with the Python API that prevents it - - #tx_data_d1 = platform.add_iface_io(name + '_HI', 4) - #tx_data_d2 = platform.add_iface_io(name + '_LO', 4) - for i in range(4): - tx_data_d1.append(platform.add_iface_io(name + str(i) + '_HI')) - tx_data_d2.append(platform.add_iface_io(name + str(i) + '_LO')) + tx_data_h.append(platform.add_iface_io(name + str(i) + '_HI')) + tx_data_l.append(platform.add_iface_io(name + str(i) + '_LO')) block = {'type':'GPIO', 'mode':'OUTPUT', 'name':name + str(i), - #'name':name, 'location':[pad[i]], 'size':1, - #'location':pad, - #'size':4, 'out_reg':'DDIO_RESYNC', - 'out_clk_pin':'auto_eth_tx_clk', # -------------------------- TODO - 'is_inclk_inverted':False + 'out_clk_pin':'auto_eth_tx_clk', + 'is_inclk_inverted':False, + 'drive_strength':4 # TODO: get this from pin constraints } platform.toolchain.ifacewriter.blocks.append(block) platform.del_record_signal(pads, pads.tx_data) - #self.comb += pads.tx_ctl.eq(sink.valid) - #self.comb += tx_data_d1.eq(sink.data[0:4]) - #self.comb += tx_data_d2.eq(sink.data[4:8]) - #self.comb += sink.ready.eq(1) + name = platform.get_pin_name(pads.tx_ctl) + pad = platform.get_pin_location(pads.tx_ctl) + name = 'auto_' + name + tx_ctl_h = platform.add_iface_io(name + '_HI') + tx_ctl_l = platform.add_iface_io(name + '_LO') - self.comb += [ pads.tx_ctl.eq(sink.valid), - tx_data_d1[0].eq(sink.data[0]), - tx_data_d1[1].eq(sink.data[1]), - tx_data_d1[2].eq(sink.data[2]), - tx_data_d1[3].eq(sink.data[3]), - tx_data_d2[0].eq(sink.data[4]), - tx_data_d2[1].eq(sink.data[5]), - tx_data_d2[2].eq(sink.data[6]), - tx_data_d2[3].eq(sink.data[7]), - sink.ready.eq(1), + block = {'type':'GPIO', + 'mode':'OUTPUT', + 'name':name, + 'location':[pad[0]], + 'size':1, + 'out_reg':'DDIO_RESYNC', + 'out_clk_pin':'auto_eth_tx_clk', + 'is_inclk_inverted':False, + 'drive_strength':4 # TODO: get this from pin constraints + } + platform.toolchain.ifacewriter.blocks.append(block) + platform.del_record_signal(pads, pads.tx_ctl) + + self.sync += [ tx_data_h[0].eq(sink.data[0]), + tx_data_h[1].eq(sink.data[1]), + tx_data_h[2].eq(sink.data[2]), + tx_data_h[3].eq(sink.data[3]), + tx_data_l[0].eq(sink.data[4]), + tx_data_l[1].eq(sink.data[5]), + tx_data_l[2].eq(sink.data[6]), + tx_data_l[3].eq(sink.data[7]), + tx_ctl_h.eq(sink.valid), + tx_ctl_l.eq(sink.valid), ] + self.comb += sink.ready.eq(1) + class LiteEthPHYRGMIIRX(Module): def __init__(self, platform, pads): self.source = source = stream.Endpoint(eth_phy_description(8)) # # # - rx_ctl_d = Signal() rx_data = Signal(8) - # pads.rx_ctl can't be connected to a special GPIO (DDIO) because - # of this board layout. - # Add a DDIO_RESYNC input block with 'auto_eth_rx_clk' as clock name = platform.get_pin_name(pads.rx_data) pad = platform.get_pin_location(pads.rx_data) name = 'auto_' + name - rx_data_d1 = [] - rx_data_d2 = [] + rx_data_h = [] + rx_data_l = [] # This a workaround, we could use signals with 4 bits but there is # a problem with the Python API that prevents it for i in range(4): - rx_data_d1.append(platform.add_iface_io(name + str(i) + '_HI')) - rx_data_d2.append(platform.add_iface_io(name + str(i) + '_LO')) + rx_data_h.append(platform.add_iface_io(name + str(i) + '_HI')) + rx_data_l.append(platform.add_iface_io(name + str(i) + '_LO')) block = {'type':'GPIO', 'mode':'INPUT', @@ -107,18 +114,19 @@ class LiteEthPHYRGMIIRX(Module): platform.del_record_signal(pads, pads.rx_data) - self.comb += rx_data.eq(Cat(rx_data_d1[0], rx_data_d1[1], rx_data_d1[2], rx_data_d1[3], - rx_data_d2[0], rx_data_d2[1], rx_data_d2[2], rx_data_d2[3])) + self.comb += rx_data.eq(Cat(rx_data_l[0], rx_data_l[1], rx_data_l[2], rx_data_l[3], + rx_data_h[0], rx_data_h[1], rx_data_h[2], rx_data_h[3])) + rx_ctl_d = Signal() self.sync += rx_ctl_d.eq(pads.rx_ctl) last = Signal() self.comb += last.eq(~pads.rx_ctl & rx_ctl_d) self.sync += [ - source.valid.eq(pads.rx_ctl), - source.data.eq(rx_data) + source.valid.eq(rx_ctl_d), + source.data.eq(rx_data), + source.last.eq(last), ] - self.comb += source.last.eq(last) class LiteEthPHYRGMIICRG(Module, AutoCSR): def __init__(self, platform, clock_pads, with_hw_init_reset, tx_delay=2e-9, hw_reset_cycles=256): @@ -128,9 +136,12 @@ class LiteEthPHYRGMIICRG(Module, AutoCSR): # Clocks - self.clock_domains.cd_eth_rx = ClockDomain() - self.clock_domains.cd_eth_tx = ClockDomain() - self.clock_domains.cd_eth_tx_delayed = ClockDomain(reset_less=True) + self.clock_domains.cd_eth_rx = ClockDomain() + self.clock_domains.cd_eth_tx = ClockDomain() + + # ************************* + # * RX CLOCK * + # ************************* # Add a GPIO block with clock input property # Add a input 'auto_eth_rx_clk' to the top.v @@ -145,39 +156,54 @@ class LiteEthPHYRGMIICRG(Module, AutoCSR): platform.toolchain.ifacewriter.blocks.append(block) self.comb += self.cd_eth_rx.clk.eq(clkrx) - clktx = platform.add_iface_io('auto_eth_tx_delayed_clk') + cmd = "create_clock -period {} auto_eth_rx_clk".format(1e9/125e6) + platform.toolchain.additional_sdc_commands.append(cmd) + + # ************************* + # * TX CLOCK PIN * + # ************************* + block = {'type':'GPIO', 'size':1, # Get the location from the original resource 'location': platform.get_pin_location(clock_pads.tx)[0], - 'name':platform.get_pin_name(clktx), + 'name':'auto_eth_tx_delayed_clk', 'mode':'OUTPUT_CLK' } platform.toolchain.ifacewriter.blocks.append(block) - self.comb += clktx.eq(self.cd_eth_tx.clk) + + # ************************* + # * TX CLOCK * + # ************************* self.submodules.pll = pll = TRIONPLL(platform) - # Internal clock must come from a named signal pll.register_clkin(None, 125e6, name='auto_eth_rx_clk') - pll.create_clkout(None, 125e6, phase=90, name='auto_eth_tx_delayed_clk') - pll.create_clkout(None, 125e6, name='auto_eth_tx_clk') + pll.create_clkout(None, 125e6, phase=0, name='auto_eth_tx_delayed_clk') + pll.create_clkout(self.cd_eth_tx, 125e6, name='auto_eth_tx_clk') + + cmd = "create_clock -period {} auto_eth_tx_clk".format(1e9/125e6) + platform.toolchain.additional_sdc_commands.append(cmd) platform.delete(clock_pads) - ## Reset - #self.reset = reset = Signal() - #if with_hw_init_reset: - # self.submodules.hw_reset = LiteEthPHYHWReset(cycles=hw_reset_cycles) - # self.comb += reset.eq(self._reset.storage | self.hw_reset.reset) - #else: - # self.comb += reset.eq(self._reset.storage) - #if hasattr(clock_pads, "rst_n"): - # self.comb += clock_pads.rst_n.eq(~reset) - #self.specials += [ - # AsyncResetSynchronizer(self.cd_eth_tx, reset), - # AsyncResetSynchronizer(self.cd_eth_rx, reset), - #] + # ************************* + # * RESET * + # ************************* + self.reset = reset = Signal() + if with_hw_init_reset: + self.submodules.hw_reset = LiteEthPHYHWReset(cycles=hw_reset_cycles) + self.comb += reset.eq(self._reset.storage | self.hw_reset.reset) + else: + self.comb += reset.eq(self._reset.storage) + if hasattr(clock_pads, "rst_n"): + self.comb += clock_pads.rst_n.eq(~reset) + self.specials += [ + AsyncResetSynchronizer(self.cd_eth_tx, reset), + AsyncResetSynchronizer(self.cd_eth_rx, reset), + ] + + #platform.add_false_path_constraints(ClockSignal('sys'), self.cd_eth_rx.clk, self.cd_eth_tx.clk) class LiteEthPHYRGMII(Module, AutoCSR): dw = 8 From 32f4d246f48d0ddc8fce3eedebaf0a16f115e3be Mon Sep 17 00:00:00 2001 From: Franck Jullien Date: Tue, 28 Sep 2021 18:04:27 +0200 Subject: [PATCH 18/22] Efinic ConstraintManager improve delete method --- litex/build/generic_platform.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/litex/build/generic_platform.py b/litex/build/generic_platform.py index 12461ed62..6b0d4c66a 100644 --- a/litex/build/generic_platform.py +++ b/litex/build/generic_platform.py @@ -192,14 +192,19 @@ class ConstraintManager: def delete(self, signal): for res, obj in self.matched: - if isinstance(signal, Record): - if isinstance(obj, Signal): - continue + if isinstance(obj, Record): + for pos, (name, item) in enumerate(vars(obj).items()): + if isinstance(item, Signal): + if item == signal: + # Two first pos are name and layout + del obj.layout[pos-2] + delattr(obj, name) + if len(obj.layout) == 0: + self.matched.remove((res, obj)) + break else: - if isinstance(obj, Record): - continue - if obj == signal: - self.matched.remove((res, obj)) + if obj == signal: + self.matched.remove((res, obj)) def request(self, name, number=None, loose=False): resource = _lookup(self.available, name, number, loose) From b2e09832e5fe665931e5d4e6d32a080b7e5dcf15 Mon Sep 17 00:00:00 2001 From: Franck Jullien Date: Tue, 28 Sep 2021 18:04:49 +0200 Subject: [PATCH 19/22] Efinix: dbparser, add get_gpio_instance_from_pin --- litex/build/efinix/dbparser.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/litex/build/efinix/dbparser.py b/litex/build/efinix/dbparser.py index 100b92e5f..d8426dc7d 100644 --- a/litex/build/efinix/dbparser.py +++ b/litex/build/efinix/dbparser.py @@ -20,7 +20,6 @@ class EfinixDbParser(): for d in data: if d[0] == device: - print(d) return d return None @@ -94,3 +93,7 @@ class EfinixDbParser(): inst = self.get_instance_name_xml(dmap, pad) return self.get_pll_inst_from_gpio_inst(dmap, inst) + + def get_gpio_instance_from_pin(self, pin): + dmap = self.get_device_map(self.device) + return self.get_pad_name_xml(dmap, pin) From 45961f733b4c96e1a0f283e636c81c84526fd1eb Mon Sep 17 00:00:00 2001 From: Franck Jullien Date: Tue, 28 Sep 2021 18:06:23 +0200 Subject: [PATCH 20/22] Efinix: instance of dbparser class now in platform --- litex/build/efinix/platform.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/litex/build/efinix/platform.py b/litex/build/efinix/platform.py index 3b2aa0957..5f73823b8 100644 --- a/litex/build/efinix/platform.py +++ b/litex/build/efinix/platform.py @@ -9,6 +9,7 @@ import os from litex.build.generic_platform import * from litex.build.efinix import common, efinity +from litex.build.efinix import EfinixDbParser # EfinixPlatform ----------------------------------------------------------------------------------- @@ -32,6 +33,8 @@ class EfinixPlatform(GenericPlatform): else: raise ValueError("Unknown toolchain") + self.parser = EfinixDbParser(self.efinity_path, self.device) + def get_verilog(self, *args, special_overrides=dict(), **kwargs): so = dict(common.efinix_special_overrides) so.update(special_overrides) @@ -117,4 +120,4 @@ class EfinixPlatform(GenericPlatform): print('Pll pll_available: ' + str(self.pll_available)) def get_free_pll_resource(self): - return self.pll_available[0] \ No newline at end of file + return self.pll_available[0] From a08c5201ade491643940466419c94457849f624b Mon Sep 17 00:00:00 2001 From: Franck Jullien Date: Tue, 28 Sep 2021 18:06:57 +0200 Subject: [PATCH 21/22] Efinix: improve ifacewriter + misc --- litex/build/efinix/efinity.py | 5 +- litex/build/efinix/ifacewriter.py | 100 +++++++++++++++++++++----- litex/soc/cores/clock/efinix_trion.py | 11 +-- 3 files changed, 93 insertions(+), 23 deletions(-) diff --git a/litex/build/efinix/efinity.py b/litex/build/efinix/efinity.py index c473f031c..ddeeec020 100644 --- a/litex/build/efinix/efinity.py +++ b/litex/build/efinix/efinity.py @@ -287,6 +287,8 @@ class EfinityToolchain(): run = True, **kwargs): + self.ifacewriter.set_build_params(platform, build_name) + # Create build directory cwd = os.getcwd() os.makedirs(build_dir, exist_ok=True) @@ -342,7 +344,8 @@ class EfinityToolchain(): # DDR doesn't have Python API so we need to configure it # directly in the peri.xml file - # self.ifacewriter.add_ddr_xml(build_name) + if self.ifacewriter.xml_blocks: + self.ifacewriter.generate_xml_blocks() # Run if run: diff --git a/litex/build/efinix/ifacewriter.py b/litex/build/efinix/ifacewriter.py index 670e4aac2..ccf8acf2d 100644 --- a/litex/build/efinix/ifacewriter.py +++ b/litex/build/efinix/ifacewriter.py @@ -16,11 +16,72 @@ class InterfaceWriter(): def __init__(self, efinity_path): self.efinity_path = efinity_path self.blocks = [] + self.xml_blocks = [] + self.filename = '' + self.platform = None - def add_ddr_xml(self, filename): + def set_build_params(self, platform, build_name): + self.filename = build_name + self.platform = platform + + def generate_xml_blocks(self): et.register_namespace('efxpt', "http://www.efinixinc.com/peri_design_db") - tree = et.parse(filename + '.peri.xml') + tree = et.parse(self.filename + '.peri.xml') root = tree.getroot() + + for block in self.xml_blocks: + if block['type'] == 'DDR': + self.add_ddr_xml(root, block) + if block['type'] == 'LVDS': + self.add_ddr_lvds(root, block) + + xml_string = et.tostring(root, 'utf-8') + reparsed = expatbuilder.parseString(xml_string, False) + print_string = reparsed.toprettyxml(indent=" ") + + # Remove lines with only whitespaces. Not sure why they are here + print_string = os.linesep.join([s for s in print_string.splitlines() if s.strip()]) + + tools.write_to_file("{}.peri.xml".format(self.filename), print_string) + + def add_ddr_lvds(self, root, params): + lvds_info = root.find('efxpt:lvds_info', namespaces) + if params['mode'] == 'OUTPUT': + dir = 'tx' + mode = 'out' + else: + dir = 'rx' + mode = 'in' + + pad = self.platform.parser.get_gpio_instance_from_pin(params['location'][0]) + pad = pad.replace('TXP', 'TX') + pad = pad.replace('TXN', 'TX') + pad = pad.replace('RXP', 'RX') + pad = pad.replace('RXN', 'RX') + # Sometimes there is an extra identifier at the end + # TODO: do a better parser + if pad.count('_') == 2: + pad = pad.rsplit('_', 1)[0] + + lvds = et.SubElement(lvds_info, 'efxpt:lvds', + name = params['name'], + lvds_def = pad, + ops_type = dir) + + et.SubElement(lvds, 'efxpt:ltx_info', pll_instance = '', + fast_clock_name = '{}'.format(params['fast_clk']), + slow_clock_name = '{}'.format(params['slow_clk']), + reset_name = '', + out_bname = '{}'.format(params['name']), + oe_name = '', + clock_div = '1', + mode = '{}'.format(mode), + serialization = '{}'.format(params['serialisation']), + reduced_swing = 'false', + load = '3') + + + def add_ddr_xml(self, root, params): ddr_info = root.find('efxpt:ddr_info', namespaces) ddr = et.SubElement(ddr_info, 'efxpt:ddr', @@ -135,15 +196,6 @@ class InterfaceWriter(): et.SubElement(cs_gate_delay, 'efxpt:param', name='GATE_C_DLY', value= '3', value_type='int') et.SubElement(cs_gate_delay, 'efxpt:param', name='GATE_F_DLY', value= '0', value_type='int') - xml_string = et.tostring(root, 'utf-8') - reparsed = expatbuilder.parseString(xml_string, False) - print_string = reparsed.toprettyxml(indent=" ") - - # Remove lines with only whitespaces. Not sure why they are here - print_string = os.linesep.join([s for s in print_string.splitlines() if s.strip()]) - - tools.write_to_file("{}.peri.xml".format(filename), print_string) - def header(self, build_name, partnumber): header = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() header += """ @@ -185,8 +237,16 @@ design.create('{2}', '{3}', './../gateware', overwrite=True) mode = block['mode'] cmd = '' - -# TODO: {'type': 'GPIO', 'mode': 'OUTPUT', 'location': 'U16', 'size': 4, 'in_reg': 'DDIO_RESYNC', 'out_clk_pin': '', 'is_inclk_inverted': False, 'name': 'auto_ethtx_tx_data_d1', 'name_d2': 'auto_ethtx_tx_data_d2'} + if mode == 'INOUT': + if len(block['location']) == 1: + cmd += 'design.create_inout_gpio("{}")\n'.format(name) + cmd += 'design.assign_pkg_pin("{}","{}")\n'.format(name, block['location'][0]) + else: + cmd += 'design.create_inout_gpio("{}",{},0)\n'.format(name, block['size']-1) + for i, pad in enumerate(block['location']): + cmd += 'design.assign_pkg_pin("{}[{}]","{}")\n'.format(name, i, pad) + cmd += '\n' + return cmd if mode == 'INPUT': if len(block['location']) == 1: @@ -198,7 +258,7 @@ design.create('{2}', '{3}', './../gateware', overwrite=True) cmd += 'design.assign_pkg_pin("{}[{}]","{}")\n'.format(name, i, pad) if 'in_reg' in block: cmd += 'design.set_property("{}","IN_REG","{}")\n'.format(name, block['in_reg']) - cmd += 'design.set_property("{}","IN_CLK_PIN","{}")\n\n'.format(name, block['in_clk_pin']) + cmd += 'design.set_property("{}","IN_CLK_PIN","{}")\n'.format(name, block['in_clk_pin']) return cmd if mode == 'OUTPUT': @@ -262,20 +322,26 @@ design.create('{2}', '{3}', './../gateware', overwrite=True) cmd += 'design.set_property("{}", pll_config, block_type="PLL")\n\n'.format(name) + for i, clock in enumerate(block['clk_out']): + cmd += 'design.set_property("{}","CLKOUT{}_PHASE","{}","PLL")\n'.format(name, i, clock[2]) + cmd += 'target_freq = {\n' for i, clock in enumerate(block['clk_out']): cmd += ' "CLKOUT{}_FREQ": "{}",\n'.format(i, clock[1] / 1e6) - cmd += ' "CLKOUT{}_PHASE": "{}",\n'.format(i, clock[2]) cmd += '}\n' - cmd += 'calc_result = design.auto_calc_pll_clock("{}", target_freq)\n\n'.format(name) + cmd += 'calc_result = design.auto_calc_pll_clock("{}", target_freq)\n'.format(name) + if 'extra' in block: + cmd += block['extra'] + cmd += '\n' if verbose: cmd += 'print("#### {} ####")\n'.format(name) cmd += 'clksrc_info = design.trace_ref_clock("{}", block_type="PLL")\n'.format(name) cmd += 'pprint.pprint(clksrc_info)\n' - cmd += 'clock_source_prop = ["REFCLK_SOURCE", "CORE_CLK_PIN", "EXT_CLK", "CLKOUT0_EN", "CLKOUT1_EN","REFCLK_FREQ", "RESOURCE"]\n' + cmd += 'clock_source_prop = ["REFCLK_SOURCE", "CORE_CLK_PIN", "EXT_CLK", "CLKOUT1_EN", "CLKOUT2_EN","REFCLK_FREQ", "RESOURCE"]\n' cmd += 'clock_source_prop += ["CLKOUT0_FREQ", "CLKOUT1_FREQ", "CLKOUT2_FREQ"]\n' + cmd += 'clock_source_prop += ["CLKOUT0_PHASE", "CLKOUT1_PHASE", "CLKOUT2_PHASE"]\n' cmd += 'prop_map = design.get_property("{}", clock_source_prop, block_type="PLL")\n'.format(name) cmd += 'pprint.pprint(prop_map)\n' diff --git a/litex/soc/cores/clock/efinix_trion.py b/litex/soc/cores/clock/efinix_trion.py index 3811b7d63..205b23c89 100644 --- a/litex/soc/cores/clock/efinix_trion.py +++ b/litex/soc/cores/clock/efinix_trion.py @@ -11,8 +11,6 @@ from migen.genlib.resetsync import AsyncResetSynchronizer from litex.build.generic_platform import * from litex.soc.cores.clock.common import * -from litex.build.efinix import EfinixDbParser - class Open(Signal): pass #TODO: do somthing else @@ -61,16 +59,15 @@ class TRIONPLL(Module): # If clkin has a pin number, PLL clock input is EXTERNAL if self.platform.get_pin_location(clkin): - pad_name = self.platform.get_pin_location(clkin) + pad_name = self.platform.get_pin_location(clkin)[0] self.platform.delete(clkin) #tpl = "create_clock -name {clk} -period {period} [get_ports {{{clk}}}]" #sdc = self.platform.toolchain.additional_sdc_commands #sdc.append(tpl.format(clk=block['input_clock_name'], period=1/freq)) - parser = EfinixDbParser(self.platform.efinity_path, self.platform.device) try: - (pll_res, clock_no) = parser.get_pll_inst_from_pin(pad_name) + (pll_res, clock_no) = self.platform.parser.get_pll_inst_from_pin(pad_name) except: self.logger.error("Cannot find a pll with {} as input".format(pad_name)) quit() @@ -115,6 +112,10 @@ class TRIONPLL(Module): block = self.platform.toolchain.ifacewriter.get_block(self.pll_name) block['clk_out'].append([clk_out_name, freq, phase, margin]) + def extra(self, extra): + block = self.platform.toolchain.ifacewriter.get_block(self.pll_name) + block['extra'] = extra + def compute_config(self): pass From 93c470aecbdc2fcaf3c15b9b5081f83a0c252d2f Mon Sep 17 00:00:00 2001 From: Franck Jullien Date: Tue, 28 Sep 2021 18:08:03 +0200 Subject: [PATCH 22/22] Efinix: add a local video.py with VideoLVDSPHY for testing --- litex/build/efinix/video.py | 448 ++++++++++++++++++++++++++++++++++++ 1 file changed, 448 insertions(+) create mode 100644 litex/build/efinix/video.py diff --git a/litex/build/efinix/video.py b/litex/build/efinix/video.py new file mode 100644 index 000000000..31c6defbe --- /dev/null +++ b/litex/build/efinix/video.py @@ -0,0 +1,448 @@ +# +# This file is part of LiteX. +# +# Copyright (c) 2021 Florent Kermarrec +# SPDX-License-Identifier: BSD-2-Clause + +import os +import math + +from migen import * +from migen.genlib.cdc import MultiReg + +from litex.soc.interconnect.csr import * +from litex.soc.interconnect import stream +from litex.soc.cores.code_tmds import TMDSEncoder + +from litex.build.io import SDROutput, DDROutput + +# Video Constants ---------------------------------------------------------------------------------- + +hbits = 12 +vbits = 12 + +# Video Timings ------------------------------------------------------------------------------------ + +video_timings = { + "640x480@60Hz" : { + "pix_clk" : 25.175e6, + "h_active" : 640, + "h_blanking" : 160, + "h_sync_offset" : 16, + "h_sync_width" : 96, + "v_active" : 480, + "v_blanking" : 45, + "v_sync_offset" : 10, + "v_sync_width" : 2, + }, + "640x480@75Hz" : { + "pix_clk" : 31.5e6, + "h_active" : 640, + "h_blanking" : 200, + "h_sync_offset" : 16, + "h_sync_width" : 64, + "v_active" : 480, + "v_blanking" : 20, + "v_sync_offset" : 1, + "v_sync_width" : 3, + }, + "800x600@60Hz" : { + "pix_clk" : 40e6, + "h_active" : 800, + "h_blanking" : 256, + "h_sync_offset" : 40, + "h_sync_width" : 128, + "v_active" : 600, + "v_blanking" : 28, + "v_sync_offset" : 1, + "v_sync_width" : 4, + }, + "800x600@75Hz": { + "pix_clk" : 49.5e6, + "h_active" : 800, + "h_blanking" : 256, + "h_sync_offset" : 16, + "h_sync_width" : 80, + "v_active" : 600, + "v_blanking" : 25, + "v_sync_offset" : 1, + "v_sync_width" : 3, + }, + "1024x768@60Hz": { + "pix_clk" : 65e6, + "h_active" : 1024, + "h_blanking" : 320, + "h_sync_offset" : 24, + "h_sync_width" : 136, + "v_active" : 768, + "v_blanking" : 38, + "v_sync_offset" : 3, + "v_sync_width" : 6, + }, + "1024x768@75Hz": { + "pix_clk" : 78.8e6, + "h_active" : 1024, + "h_blanking" : 288, + "h_sync_offset" : 16, + "h_sync_width" : 96, + "v_active" : 768, + "v_blanking" : 32, + "v_sync_offset" : 1, + "v_sync_width" : 3, + }, + "1280x720@60Hz": { + "pix_clk" : 74.25e6, + "h_active" : 1280, + "h_blanking" : 370, + "h_sync_offset" : 220, + "h_sync_width" : 40, + "v_active" : 720, + "v_blanking" : 30, + "v_sync_offset" : 5, + "v_sync_width" : 5, + }, + "1920x1080@30Hz": { + "pix_clk" : 89.01e6, + "h_active" : 1920, + "h_blanking" : 720, + "h_sync_offset" : 528, + "h_sync_width" : 44, + "v_active" : 1080, + "v_blanking" : 45, + "v_sync_offset" : 4, + "v_sync_width" : 5, + }, + "1920x1080@60Hz": { + "pix_clk" : 148.35e6, + "h_active" : 1920, + "h_blanking" : 280, + "h_sync_offset" : 88, + "h_sync_width" : 44, + "v_active" : 1080, + "v_blanking" : 45, + "v_sync_offset" : 4, + "v_sync_width" : 5, + }, + "1920x1080@50Hz": { + "pix_clk" : 148.5e6, + "h_active" : 1920, + "h_blanking" : 720, + "h_sync_offset" : 528, + "h_sync_width" : 44, + "v_active" : 1080, + "v_blanking" : 45, + "v_sync_offset" : 4, + "v_sync_width" : 5, + }, + "1920x1200@60Hz": { + "pix_clk" : 148.2e6, + "h_active" : 1920, + "h_blanking" : 80, + "h_sync_offset" : 8, + "h_sync_width" : 32, + "v_active" : 1200, + "v_blanking" : 35, + "v_sync_offset" : 21, + "v_sync_width" : 8, + }, +} + +# Video Timing Generator --------------------------------------------------------------------------- + +video_timing_layout = [ + # Synchronization signals. + ("hsync", 1), + ("vsync", 1), + ("de", 1), + # Extended/Optional synchronization signals. + ("hres", hbits), + ("vres", vbits), + ("hcount", hbits), + ("vcount", vbits), +] + +video_data_layout = [ + # Synchronization signals. + ("hsync", 1), + ("vsync", 1), + ("de", 1), + # Data signals. + ("r", 8), + ("g", 8), + ("b", 8), +] + +video_data_layout_dual = [ + # Synchronization signals. + ("hsync", 2), + ("vsync", 2), + ("de", 2), + # Data signals. + ("r", 16), + ("g", 16), + ("b", 16), +] + +class VideoTimingGenerator(Module, AutoCSR): + def __init__(self, default_video_timings="800x600@60Hz"): + # Check / Get Video Timings (can be str or dict) + if isinstance(default_video_timings, str): + try: + self.video_timings = vt = video_timings[default_video_timings] + except KeyError: + msg = [f"Video Timings {default_video_timings} not supported, availables:"] + for video_timing in video_timings.keys(): + msg.append(f" - {video_timing} / {video_timings[video_timing]['pix_clk']/1e6:3.2f}MHz.") + raise ValueError("\n".join(msg)) + else: + self.video_timings = vt = default_video_timings + + # MMAP Control/Status Registers. + self._enable = CSRStorage(reset=1) + + self._hres = CSRStorage(hbits, vt["h_active"]) + self._hsync_start = CSRStorage(hbits, vt["h_active"] + vt["h_sync_offset"]) + self._hsync_end = CSRStorage(hbits, vt["h_active"] + vt["h_sync_offset"] + vt["h_sync_width"]) + self._hscan = CSRStorage(hbits, vt["h_active"] + vt["h_blanking"]) + + self._vres = CSRStorage(vbits, vt["v_active"]) + self._vsync_start = CSRStorage(vbits, vt["v_active"] + vt["v_sync_offset"]) + self._vsync_end = CSRStorage(vbits, vt["v_active"] + vt["v_sync_offset"] + vt["v_sync_width"]) + self._vscan = CSRStorage(vbits, vt["v_active"] + vt["v_blanking"]) + + # Video Timing Source + self.source = source = stream.Endpoint(video_timing_layout) + + # # # + + # Resynchronize Enable to Video clock domain. + self.enable = enable = Signal() + self.specials += MultiReg(self._enable.storage, enable) + + # Resynchronize Horizontal Timings to Video clock domain. + self.hres = hres = Signal(hbits) + self.hsync_start = hsync_start = Signal(hbits) + self.hsync_end = hsync_end = Signal(hbits) + self.hscan = hscan = Signal(hbits) + self.specials += MultiReg(self._hres.storage, hres) + self.specials += MultiReg(self._hsync_start.storage, hsync_start) + self.specials += MultiReg(self._hsync_end.storage, hsync_end) + self.specials += MultiReg(self._hscan.storage, hscan) + + # Resynchronize Vertical Timings to Video clock domain. + self.vres = vres = Signal(vbits) + self.vsync_start = vsync_start = Signal(vbits) + self.vsync_end = vsync_end = Signal(vbits) + self.vscan = vscan = Signal(vbits) + self.specials += MultiReg(self._vres.storage, vres) + self.specials += MultiReg(self._vsync_start.storage, vsync_start) + self.specials += MultiReg(self._vsync_end.storage, vsync_end) + self.specials += MultiReg(self._vscan.storage, vscan) + + # Generate timings. + hactive = Signal() + vactive = Signal() + fsm = FSM(reset_state="IDLE") + fsm = ResetInserter()(fsm) + self.submodules.fsm = fsm + self.comb += fsm.reset.eq(~enable) + fsm.act("IDLE", + NextValue(hactive, 0), + NextValue(vactive, 0), + NextValue(source.hres, hres), + NextValue(source.vres, vres), + NextValue(source.hcount, 0), + NextValue(source.vcount, 0), + NextState("RUN") + ) + self.comb += source.de.eq(hactive & vactive) # DE when both HActive and VActive. + self.sync += source.first.eq((source.hcount == 0) & (source.vcount == 0)), + self.sync += source.last.eq( (source.hcount == hscan) & (source.vcount == vscan)), + fsm.act("RUN", + source.valid.eq(1), + If(source.ready, + # Increment HCount. + NextValue(source.hcount, source.hcount + 1), + # Generate HActive / HSync. + If(source.hcount == 0, NextValue(hactive, 1)), # Start of HActive. + If(source.hcount == hres, NextValue(hactive, 0)), # End of HActive. + If(source.hcount == hsync_start, NextValue(source.hsync, 1)), # Start of HSync. + If(source.hcount == hsync_end, NextValue(source.hsync, 0)), # End of HSync. + # End of HScan. + If(source.hcount == hscan, + # Reset HCount. + NextValue(source.hcount, 0), + # Increment VCount. + NextValue(source.vcount, source.vcount + 1), + # Generate VActive / VSync. + If(source.vcount == 0, NextValue(vactive, 1)), # Start of VActive. + If(source.vcount == vres, NextValue(vactive, 0)), # End of HActive. + If(source.vcount == vsync_start, NextValue(source.vsync, 1)), # Start of VSync. + If(source.vcount == vsync_end, NextValue(source.vsync, 0)), # End of VSync. + # End of VScan. + If(source.vcount == vscan, + # Reset VCount. + NextValue(source.vcount, 0), + ) + ) + ) + ) + +# Video Patterns ----------------------------------------------------------------------------------- + +class ColorBarsPattern(Module): + """Color Bars Pattern""" + def __init__(self): + self.enable = Signal(reset=1) + self.vtg_sink = vtg_sink = stream.Endpoint(video_timing_layout) + self.source = source = stream.Endpoint(video_data_layout) + + # # # + + enable = Signal() + self.specials += MultiReg(self.enable, enable) + + # Control Path. + pix = Signal(hbits) + bar = Signal(3) + + fsm = FSM(reset_state="IDLE") + fsm = ResetInserter()(fsm) + self.submodules.fsm = fsm + self.comb += fsm.reset.eq(~self.enable) + fsm.act("IDLE", + NextValue(pix, 0), + NextValue(bar, 0), + vtg_sink.ready.eq(1), + If(vtg_sink.valid & vtg_sink.first & (vtg_sink.hcount == 0) & (vtg_sink.vcount == 0), + vtg_sink.ready.eq(0), + NextState("RUN") + ) + ) + fsm.act("RUN", + vtg_sink.connect(source, keep={"valid", "ready", "last", "de", "hsync", "vsync"}), + If(source.valid & source.ready & source.de, + NextValue(pix, pix + 1), + If(pix == (vtg_sink.hres[3:] -1), # 8 Color Bars. + NextValue(pix, 0), + NextValue(bar, bar + 1) + ) + ) + ) + + # Data Path. + color_bar = [ + # R G B + [0xff, 0xff, 0xff], # White + [0xff, 0xff, 0x00], # Yellow + [0x00, 0xff, 0xff], # Cyan + [0x00, 0xff, 0x00], # Green + [0xff, 0x00, 0xff], # Purple + [0xff, 0x00, 0x00], # Red + [0x00, 0x00, 0xff], # Blue + [0x00, 0x00, 0x00], # Black + ] + cases = {} + for i in range(8): + cases[i] = [ + source.r.eq(color_bar[i][0]), + source.g.eq(color_bar[i][1]), + source.b.eq(color_bar[i][2]) + ] + self.comb += Case(bar, cases) + +class VideoLVDSPHY(Module): + def __init__(self, platform, pads, pixel_clk='px_clk', px_clk_x3_5='px_clk_x3_5'): + self.sink = sink = stream.Endpoint(video_data_layout_dual) + + # # # + + channel1 = Record(video_data_layout) + channel2 = Record(video_data_layout) + + self.comb += [ channel1.r.eq(sink.r[0:8]), + channel1.g.eq(sink.g[0:8]), + channel1.b.eq(sink.b[0:8]), + channel1.de.eq(sink.de[0]), + channel1.vsync.eq(sink.vsync[0]), + channel1.hsync.eq(sink.hsync[0]), + + channel2.r.eq(sink.r[7:16]), + channel2.g.eq(sink.g[7:16]), + channel2.b.eq(sink.b[7:16]), + channel2.de.eq(sink.de[1]), + channel2.vsync.eq(sink.vsync[1]), + channel2.hsync.eq(sink.hsync[1]), + ] + + # Always ack Sink, no backpressure. + self.comb += sink.ready.eq(1) + + # -------- LVDS CLOCK ----------- + + name = platform.get_pin_name(pads.clk) + pad = platform.get_pin_location(pads.clk) + + clk = platform.add_iface_io('hdmi_clk', 7) + + block = {'type':'LVDS', + 'mode':'OUTPUT', + 'name':name, + 'location':[pad[0]], + 'serialisation':7, + 'fast_clk':px_clk_x3_5, + 'slow_clk':pixel_clk, + + } + platform.toolchain.ifacewriter.xml_blocks.append(block) + platform.delete(pads.clk) + + # -------- LVDS DATA ----------- + + lvds = [] + + lvds.append((pads.rxpa1, platform.get_pin_name(pads.rxpa1), platform.get_pin_location(pads.rxpa1))) + lvds.append((pads.rxpa2, platform.get_pin_name(pads.rxpa2), platform.get_pin_location(pads.rxpa2))) + rxpa1 = platform.add_iface_io('hdmi_rxpa1', 7) + rxpa2 = platform.add_iface_io('hdmi_rxpa2', 7) + + lvds.append((pads.rxpb1, platform.get_pin_name(pads.rxpb1), platform.get_pin_location(pads.rxpb1))) + lvds.append((pads.rxpb2, platform.get_pin_name(pads.rxpb2), platform.get_pin_location(pads.rxpb2))) + rxpb1 = platform.add_iface_io('hdmi_rxpb1', 7) + rxpb2 = platform.add_iface_io('hdmi_rxpb2', 7) + + lvds.append((pads.rxpc1, platform.get_pin_name(pads.rxpc1), platform.get_pin_location(pads.rxpc1))) + lvds.append((pads.rxpc2, platform.get_pin_name(pads.rxpc2), platform.get_pin_location(pads.rxpc2))) + rxpc1 = platform.add_iface_io('hdmi_rxpc1', 7) + rxpc2 = platform.add_iface_io('hdmi_rxpc2', 7) + + lvds.append((pads.rxpd1, platform.get_pin_name(pads.rxpd1), platform.get_pin_location(pads.rxpd1))) + lvds.append((pads.rxpd2, platform.get_pin_name(pads.rxpd2), platform.get_pin_location(pads.rxpd2))) + rxpd1 = platform.add_iface_io('hdmi_rxpd1', 7) + rxpd2 = platform.add_iface_io('hdmi_rxpd2', 7) + + for signal, name, pad in lvds: + block = {'type':'LVDS', + 'mode':'OUTPUT', + 'name':name, + 'location':[pad[0]], + 'serialisation':7, + 'fast_clk':px_clk_x3_5, + 'slow_clk':pixel_clk, + + } + platform.toolchain.ifacewriter.xml_blocks.append(block) + platform.delete(signal) + + self.comb += [ rxpa1.eq(Cat(channel1.g[2], channel1.r[7], channel1.r[6], channel1.r[5], channel1.r[4], channel1.r[3], channel1.r[2])), + rxpb1.eq(Cat(channel1.b[3], channel1.b[2], channel1.g[7], channel1.g[6], channel1.g[5], channel1.g[4], channel1.g[3])), + rxpc1.eq(Cat(channel1.de, channel1.vsync, channel1.hsync, channel1.b[7], channel1.b[6], channel1.b[5], channel1.b[4])), + rxpd1.eq(Cat(0, channel1.b[7], channel1.b[7], channel1.g[7], channel1.g[7], channel1.r[7], channel1.r[7])), + + rxpa2.eq(Cat(channel2.g[2], channel2.r[7], channel2.r[6], channel2.r[5], channel2.r[4], channel2.r[3], channel2.r[2])), + rxpb2.eq(Cat(channel2.b[3], channel2.b[2], channel2.g[7], channel2.g[6], channel2.g[5], channel2.g[4], channel2.g[3])), + rxpc2.eq(Cat(channel2.de, channel2.vsync, channel2.hsync, channel2.b[7], channel2.b[6], channel2.b[5], channel2.b[4])), + rxpd2.eq(Cat(0, channel2.b[7], channel2.b[7], channel2.g[7], channel2.g[7], channel2.r[7], channel2.r[7])), + + clk.eq(0b1100011), + ]