diff --git a/litex/build/efinix/__init__.py b/litex/build/efinix/__init__.py new file mode 100644 index 000000000..eda581876 --- /dev/null +++ b/litex/build/efinix/__init__.py @@ -0,0 +1,4 @@ +from litex.build.efinix.programmer import EfinixProgrammer +from litex.build.efinix.dbparser import EfinixDbParser +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/common.py b/litex/build/efinix/common.py new file mode 100644 index 000000000..9a4ef0c2b --- /dev/null +++ b/litex/build/efinix/common.py @@ -0,0 +1,43 @@ +# +# This file is part of LiteX. +# +# Copyright (c) 2021 Franck Jullien +# Copyright (c) 2015-2018 Florent Kermarrec +# SPDX-License-Identifier: BSD-2-Clause + +from migen.fhdl.module import Module +from migen.genlib.resetsync import AsyncResetSynchronizer + +from litex.build.io import * + +# 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 +} diff --git a/litex/build/efinix/dbparser.py b/litex/build/efinix/dbparser.py new file mode 100644 index 000000000..d8426dc7d --- /dev/null +++ b/litex/build/efinix/dbparser.py @@ -0,0 +1,99 @@ +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: + 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) + + def get_gpio_instance_from_pin(self, pin): + dmap = self.get_device_map(self.device) + return self.get_pad_name_xml(dmap, pin) diff --git a/litex/build/efinix/ddr.py b/litex/build/efinix/ddr.py new file mode 100644 index 000000000..97452b05a --- /dev/null +++ b/litex/build/efinix/ddr.py @@ -0,0 +1,93 @@ +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, cd): + self.blocks = [] + self.platform = platform + self.config = config + self.nb_ports = 1 + + if config['ports'] != None: + self.nb_ports = self.config['ports'] + + # 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) + + 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 == 1: + 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 new file mode 100644 index 000000000..ddeeec020 --- /dev/null +++ b/litex/build/efinix/efinity.py @@ -0,0 +1,371 @@ +# +# 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 +import datetime + +from xml.dom import expatbuilder +import xml.etree.ElementTree as et + +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 + +from litex.build.efinix import InterfaceWriter + +_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_gpio(named_sc, named_pc, fragment, platform, specials_gpios): + conf = [] + inst = [] + + # GPIO + for sig, pins, others, resname in named_sc: + 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)) + else: + conf.append(_format_conf_constraint(sig, pins[0], others, resname, fragment, platform)) + if named_pc: + conf.append("\n\n".join(named_pc)) + + conf = inst + conf + + return "\n".join(conf) + +def _build_peri(efinity_path, build_name, partnumber, named_sc, named_pc, fragment, platform, additional_iface_commands, specials_gpios): + pythonpath = "" + + 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() + + tools.write_to_file("iface.py", header + gen + gpio + add + footer) + + subprocess.call([efinity_path + '/bin/python3', 'iface.py']) + +# Project configuration ------------------------------------------------------------------------ + +def _build_xml(partnumber, build_name, sources, additional_xml_commands): + + root = et.Element('efx:project') + + now = datetime.datetime.now() + date_str = " Date: " + now.strftime("%Y-%m-%d %H:%M") + " " + + # 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'] = build_name + root.attrib['description'] = '' + root.attrib['last_change_date'] = date_str + root.attrib['location'] = str(pathlib.Path().resolve()) + root.attrib['sw_version'] = '2021.1.165.2.19' # TODO: read it from sw_version.txt + root.attrib['last_run_state'] = '' + root.attrib['last_run_tool'] = '' + root.attrib['last_run_flow'] = '' + root.attrib['config_result_in_sync'] = 'sync' + root.attrib['design_ood'] = 'sync' + root.attrib['place_ood'] = 'sync' + root.attrib['route_ood'] = 'sync' + root.attrib['xsi:schemaLocation'] = 'http://www.efinixinc.com/enf_proj enf_proj.xsd' + + device_info = et.SubElement(root, 'efx:device_info') + et.SubElement(device_info, 'efx:family', name = 'Trion') + et.SubElement(device_info, 'efx:device', name = partnumber) + et.SubElement(device_info, 'efx:timing_model', name = 'C4') + + design_info = et.SubElement(root, 'efx:design_info') + et.SubElement(design_info, "efx:top_module", name = build_name) + for filename, language, library in sources: + 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") + et.SubElement(constraint_info, "efx:sdc_file", name = "{}.sdc".format(build_name)) + + misc_info = et.SubElement(root, "efx:misc_info") + ip_info = et.SubElement(root, "efx:ip_info") + + synthesis = et.SubElement(root, "efx:synthesis", tool_name="efx_map") + for l in additional_xml_commands: + if l[0] == 'efx_map': + val = {'name':l[1], 'value':l[2], 'value_type':l[3]} + et.SubElement(synthesis, "efx:param", val) + + place_and_route = et.SubElement(root, "efx:place_and_route", tool_name="efx_pnr") + for l in additional_xml_commands: + if l[0] == 'efx_pnr': + val = {'name':l[1], 'value':l[2], 'value_type':l[3]} + et.SubElement(place_and_route, "efx:param", val) + + bitstream_generation = et.SubElement(root, "efx:bitstream_generation", tool_name="efx_pgm") + for l in additional_xml_commands: + if l[0] == 'efx_pgm': + val = {'name':l[1], 'value':l[2], 'value_type':l[3]} + et.SubElement(bitstream_generation, "efx:param", val) + + xml_string = et.tostring(root, 'utf-8') + reparsed = expatbuilder.parseString(xml_string, False) + print_string = reparsed.toprettyxml(indent=" ") + + # Generate .xml + tools.write_to_file("{}.xml".format(build_name), print_string) + +class EfinityToolchain(): + attr_translate = {} + + def __init__(self, efinity_path): + self.options = {} + self.clocks = dict() + self.false_paths = set() + self.efinity_path = efinity_path + self.additional_sdc_commands = [] + self.additional_xml_commands = [] + self.ifacewriter = InterfaceWriter(efinity_path) + self.specials_gpios = [] + self.additional_iface_commands = [] + + def build(self, platform, fragment, + build_dir = "build", + build_name = "top", + run = True, + **kwargs): + + self.ifacewriter.set_build_params(platform, build_name) + + # Create build directory + cwd = os.getcwd() + os.makedirs(build_dir, exist_ok=True) + os.chdir(build_dir) + + # Finalize design + if not isinstance(fragment, _Fragment): + fragment = fragment.get_fragment() + platform.finalize(fragment) + + # Generate verilog + v_output = platform.get_verilog(fragment, name=build_name, **kwargs) + named_sc, named_pc = platform.resolve_signals(v_output.ns) + v_file = build_name + ".v" + v_output.write(v_file) + platform.add_source(v_file) + + 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) + '}' + + os.environ['EFXPT_HOME'] = self.efinity_path + '/pt' + + # Generate design timing constraints file (.sdc) + _build_sdc( + clocks = self.clocks, + false_paths = self.false_paths, + vns = v_output.ns, + named_sc = named_sc, + build_name = build_name, + additional_sdc_commands = self.additional_sdc_commands) + + # Generate project file (.xml) + _build_xml( + partnumber = platform.device, + build_name = build_name, + sources = platform.sources, + additional_xml_commands = self.additional_xml_commands) + + # 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, + 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 + if self.ifacewriter.xml_blocks: + self.ifacewriter.generate_xml_blocks() + + # Run + if run: + subprocess.call([self.efinity_path + '/scripts/efx_run.py', build_name + '.xml', '-f', 'compile']) + + os.chdir(cwd) + + return v_output.ns + + def add_period_constraint(self, platform, clk, period): + clk.attr.add("keep") + period = math.floor(period*1e3)/1e3 # round to lowest picosecond + if clk in self.clocks: + if period != self.clocks[clk]: + raise ValueError("Clock already constrained to {:.2f}ns, new constraint to {:.2f}ns" + .format(self.clocks[clk], period)) + self.clocks[clk] = period + + def add_false_path_constraint(self, platform, from_, to): + from_.attr.add("keep") + to.attr.add("keep") + if (to, from_) not in self.false_paths: + 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..ccf8acf2d --- /dev/null +++ b/litex/build/efinix/ifacewriter.py @@ -0,0 +1,367 @@ +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, efinity_path): + self.efinity_path = efinity_path + self.blocks = [] + self.xml_blocks = [] + self.filename = '' + self.platform = None + + 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(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', + 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, '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{}'.format(type_suffix), 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_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') + 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, '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, '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, '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, '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, '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') + + 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}', './../gateware', 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_gpio(self, block, verbose=True): + name = block['name'] + mode = block['mode'] + cmd = '' + + 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: + 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'.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'.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': + 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 += '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 += '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: + cmd += 'pll_config = {{ "CLKOUT{}_EN":"1", "CLKOUT{}_PIN":"{}" }}\n'.format(i, i, clock[0]) + else: + cmd += 'pll_config = {{ "CLKOUT{}_PIN":"{}" }}\n'.format(i, clock[0]) + + 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 += '}\n' + 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", "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' + + 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) + if b['type'] == 'GPIO': + output += self.generate_gpio(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 new file mode 100644 index 000000000..5f73823b8 --- /dev/null +++ b/litex/build/efinix/platform.py @@ -0,0 +1,123 @@ +# +# 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 + +from litex.build.generic_platform import * +from litex.build.efinix import common, efinity +from litex.build.efinix import EfinixDbParser + +# EfinixPlatform ----------------------------------------------------------------------------------- + +class EfinixPlatform(GenericPlatform): + bitstream_ext = ".bit" + + 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 + 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") + + 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) + 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) + + # 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): + if sig is None: + return None + sc = self.constraint_manager.get_sig_constraints() + for s, pins, others, resource in sc: + 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: + if resource[2]: + return resource[0] + '_' + resource[2] + else: + return resource[0] + return None + + 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 + if append: + self.toolchain.specials_gpios.append(tmp) + return tmp + + def add_iface_ios(self, io, append=True): + self.add_extension(io) + tmp = self.request(io[0][0]) + 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] diff --git a/litex/build/efinix/programmer.py b/litex/build/efinix/programmer.py new file mode 100644 index 000000000..10a3eaff4 --- /dev/null +++ b/litex/build/efinix/programmer.py @@ -0,0 +1,32 @@ +# +# This file is part of LiteX. +# +# Copyright (c) 2021 Franck Jullien +# SPDX-License-Identifier: BSD-2-Clause + +import os +import sys +import subprocess + +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' + 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) diff --git a/litex/build/efinix/rgmii.py b/litex/build/efinix/rgmii.py new file mode 100644 index 000000000..82ca6f3ac --- /dev/null +++ b/litex/build/efinix/rgmii.py @@ -0,0 +1,220 @@ +# +# This file is part of LiteEth. +# +# Copyright (c) 2021 Franck Jullien +# 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_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 + for i in range(4): + 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), + 'location':[pad[i]], + '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_data) + + 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') + + 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_data = Signal(8) + + # 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_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_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', + '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_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(rx_ctl_d), + source.data.eq(rx_data), + 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() + + # ************************* + # * RX CLOCK * + # ************************* + + # 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) + + 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':'auto_eth_tx_delayed_clk', + 'mode':'OUTPUT_CLK' + } + platform.toolchain.ifacewriter.blocks.append(block) + + # ************************* + # * TX CLOCK * + # ************************* + + self.submodules.pll = pll = TRIONPLL(platform) + pll.register_clkin(None, 125e6, name='auto_eth_rx_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), + ] + + #platform.add_false_path_constraints(ClockSignal('sys'), self.cd_eth_rx.clk, self.cd_eth_tx.clk) + +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/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), + ] diff --git a/litex/build/generic_platform.py b/litex/build/generic_platform.py index 4b4511cd7..6b0d4c66a 100644 --- a/litex/build/generic_platform.py +++ b/litex/build/generic_platform.py @@ -190,6 +190,22 @@ class ConstraintManager: def add_extension(self, io): self.available.extend(io) + def delete(self, signal): + for res, obj in self.matched: + 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 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: @@ -275,12 +291,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) @@ -313,6 +331,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) 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/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..205b23c89 --- /dev/null +++ b/litex/soc/cores/clock/efinix_trion.py @@ -0,0 +1,126 @@ +# +# 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 * + +class Open(Signal): pass + +#TODO: do somthing else +count = 0 + +# Efinix / TRIONPLL ---------------------------------------------------------------------------------- + +class TRIONPLL(Module): + nclkouts_max = 4 + 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 + + block['type'] = 'PLL' + 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, name= ''): + block = self.platform.toolchain.ifacewriter.get_block(self.pll_name) + + block['input_clock_name'] = self.platform.get_pin_name(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)[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)) + + try: + (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() + + 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_freq'] = freq + + self.logger.info("Using {}".format(block['resource'])) + + def create_clkout(self, cd, freq, phase=0, margin=1e-2, name='', with_reset=False): + assert self.nclkouts < self.nclkouts_max + + if name != '': + clk_out_name = name + else: + clk_out_name = '{}_CLKOUT{}'.format(self.pll_name, self.nclkouts) + + if cd != None: + 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) + 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 extra(self, extra): + block = self.platform.toolchain.ifacewriter.get_block(self.pll_name) + block['extra'] = extra + + def compute_config(self): + pass + + def set_configuration(self): + pass + + def do_finalize(self): + pass \ No newline at end of file diff --git a/litex/soc/integration/soc.py b/litex/soc/integration/soc.py index 524f229c7..a63c5d6c9 100644 --- a/litex/soc/integration/soc.py +++ b/litex/soc/integration/soc.py @@ -822,7 +822,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, @@ -832,7 +832,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( @@ -841,8 +841,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