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