efinix: support PLL, add dbparser and ifacewriter

This commit is contained in:
Franck Jullien 2021-09-20 07:56:53 +02:00
parent 0278d3eee8
commit 9b6ae2ff03
7 changed files with 338 additions and 56 deletions

View File

@ -1 +1,3 @@
from litex.build.efinix.programmer import EfinixProgrammer
from litex.build.efinix.programmer import EfinixProgrammer
from litex.build.efinix.dbparser import EfinixDbParser
from litex.build.efinix.ifacewriter import InterfaceWriter

View File

@ -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)

View File

@ -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))
self.false_paths.add((from_, to))

View File

@ -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()"""

View File

@ -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)
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

View File

@ -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

View File

@ -0,0 +1,92 @@
#
# This file is part of LiteX.
#
# Copyright (c) 2021 Franck Jullien <franck.jullien@collshade.fr>
# Copyright (c) 2021 Florent Kermarrec <florent@enjoy-digital.fr>
# 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