From 382ebbf661ee4b9ad744f79502ee68a5409d2c79 Mon Sep 17 00:00:00 2001 From: Gwenhael Goavec-Merou Date: Tue, 28 Jun 2022 22:21:24 +0200 Subject: [PATCH] build: efinix/efinity lattice/diamond osfpga xilinx/f4pga xilinx/ise xilinx/yosys_nextpnr -> GenericToolchain --- litex/build/efinix/efinity.py | 633 +++++++++++++--------------- litex/build/lattice/diamond.py | 36 -- litex/build/osfpga/osfpga.py | 166 +++----- litex/build/xilinx/f4pga.py | 151 +++---- litex/build/xilinx/ise.py | 373 ++++++++-------- litex/build/xilinx/yosys_nextpnr.py | 95 ++--- 6 files changed, 607 insertions(+), 847 deletions(-) diff --git a/litex/build/efinix/efinity.py b/litex/build/efinix/efinity.py index 24d6b3db1..9f5af5a4d 100644 --- a/litex/build/efinix/efinity.py +++ b/litex/build/efinix/efinity.py @@ -23,397 +23,332 @@ from migen.fhdl.simplify import FullMemoryWE from litex.build import tools from litex.build.generic_platform import * from litex.build.generic_platform import Pins, IOStandard, Misc +from litex.build.generic_toolchain import GenericToolchain from litex.build.efinix import common from litex.build.efinix import InterfaceWriter -def get_pin_direction(fragment, platform, pinname): - pins = platform.constraint_manager.get_io_signals() - for pin in sorted(pins, key=lambda x: x.duid): - # Better idea ??? - if (pinname.split("[")[0] == pin.name): - return pin.direction - return "Unknown" - -# 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 (.xml) ------------------------------------------------------------------ - -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", - "1.2_V_Differential_HSTL", "1.2_V_Differential_SSTL", - "1.2_V_HSTL", "1.2_V_LVCMOS", "1.2_V_SSTL", "1.5_V_Differential_HSTL", - "1.5_V_Differential_SSTL", "1.5_V_HSTL", "1.5_V_LVCMOS", "1.5_V_SSTL", - "1.8_V_Differential_HSTL", "1.8_V_Differential_SSTL", "1.8_V_HSTL", - "1.8_V_LVCMOS", "1.8_V_SSTL", "2.5_V_LVCMOS", "3.0_V_LVCMOS", - "3.0_V_LVTTL", "3.3_V_LVCMOS", "3.3_V_LVTTL" - ] - - 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 "SLEWRATE" in c.misc: - prop = "SLEW_RATE" - val = "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, excluded_ios): - conf = [] - inst = [] - - # GPIO - for sig, pins, others, resname in named_sc: - excluded = False - for excluded_io in excluded_ios: - if isinstance(excluded_io, str): - if sig == excluded_io: - excluded = True - elif isinstance(excluded_io, Signal): - if sig == excluded_io.name: - excluded = True - if excluded: - continue - inst.append(_create_gpio_instance(fragment, platform, sig, pins)) - if len(pins) > 1: - for i, p in enumerate(pins): - conf.append(_format_conf_constraint("{}[{}]".format(sig, i), p, others, resname, fragment, platform)) - else: - conf.append(_format_conf_constraint(sig, pins[0], others, resname, fragment, platform)) - if named_pc: - conf.append("\n\n".join(named_pc)) - - conf = inst + conf - - return "\n".join(conf) - -def _build_peri(efinity_path, build_name, device, named_sc, named_pc, fragment, platform, additional_iface_commands, excluded_ios): - pythonpath = "" - - header = platform.toolchain.ifacewriter.header(build_name, device) - gen = platform.toolchain.ifacewriter.generate(device) - #TODO : move this to ifacewriter - gpio = _build_iface_gpio(named_sc, named_pc, fragment, platform, excluded_ios) - add = "\n".join(additional_iface_commands) - footer = platform.toolchain.ifacewriter.footer() - - tools.write_to_file("iface.py", header + gen + gpio + add + footer) - - if tools.subprocess_call_filtered([efinity_path + "/bin/python3", "iface.py"], common.colors) != 0: - raise OSError("Error occurred during Efinity peri script execution.") - - -# Project configuration (.xml) --------------------------------------------------------------------- - -def _build_xml(family, device, timing_model, build_name, sources): - now = datetime.datetime.now() - - # Create Project. - root = et.Element("efx:project") - root.attrib["xmlns:efx"] = "http://www.efinixinc.com/enf_proj" - root.attrib["name"] = build_name - 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_change_date"] = f"Date : {now.strftime('%Y-%m-%d %H:%M')}" - - # Add Device. - device_info = et.SubElement(root, "efx:device_info") - et.SubElement(device_info, "efx:family", name=family) - et.SubElement(device_info, "efx:device", name=device) - et.SubElement(device_info, "efx:timing_model", name=timing_model) - - # Add Design Info. - design_info = et.SubElement(root, "efx:design_info") - et.SubElement(design_info, "efx:top_module", name=build_name) - - # Add Design Sources. - for filename, language, library, *copy in sources: - if language is None: - continue - et.SubElement(design_info, "efx:design_file", { - "name" : filename, - "version" : "default", - "library" : "default" if ".vh" not in filename else library, - }) - - # Add Timing Constraints. - constraint_info = et.SubElement(root, "efx:constraint_info") - et.SubElement(constraint_info, "efx:sdc_file", name=f"{build_name}.sdc") - - # Add Misc Info. - misc_info = et.SubElement(root, "efx:misc_info") - - # Add IP Info. - ip_info = et.SubElement(root, "efx:ip_info") - - # Generate .xml - xml_str = et.tostring(root, "utf-8") - xml_str = expatbuilder.parseString(xml_str, False) - xml_str = xml_str.toprettyxml(indent=" ") - tools.write_to_file("{}.xml".format(build_name), xml_str) # Efinity Toolchain -------------------------------------------------------------------------------- -class EfinityToolchain: +class EfinityToolchain(GenericToolchain): attr_translate = {} def __init__(self, efinity_path): + super().__init__() self.options = {} - self.clocks = dict() - self.false_paths = set() self.efinity_path = efinity_path + os.environ["EFXPT_HOME"] = self.efinity_path + "/pt" self.ifacewriter = InterfaceWriter(efinity_path) self.excluded_ios = [] self.additional_sdc_commands = [] self.additional_iface_commands = [] - def build(self, platform, fragment, - build_dir = "build", - build_name = "top", - run = True, - **kwargs): + def finalize(self): + self.ifacewriter.set_build_params(self.platform, self._build_name) + # Add Include Paths. + if self.platform.verilog_include_paths: + self.options["includ_path"] = "{" + ";".join(self.platform.verilog_include_paths) + "}" - 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) + def build(self, platform, fragment, **kwargs): # Apply FullMemoryWE on Design (Efiniy does not infer memories correctly otherwise). FullMemoryWE()(fragment) - # Finalize Design. - if not isinstance(fragment, _Fragment): - fragment = fragment.get_fragment() - platform.finalize(fragment) + return self._build(platform, fragment, **kwargs) - # Generate Design. - v_output = platform.get_verilog(fragment, name=build_name, **kwargs) - v_output.write(f"{build_name}.v") - platform.add_source(f"{build_name}.v") + # Timing Constraints (.sdc) ------------------------------------------------------------------------ - # Add Include Paths. - if platform.verilog_include_paths: - self.options["includ_path"] = "{" + ";".join(platform.verilog_include_paths) + "}" + def build_timing_constraints(self, vns): + sdc = [] - os.environ["EFXPT_HOME"] = self.efinity_path + "/pt" + # Clock constraints + for clk, period in sorted(self.clocks.items(), key=lambda x: x[0].duid): + is_port = False + for sig, pins, others, resname in self.named_sc: + if sig == self._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=self._vns.get_name(clk), period=str(period))) + else: + tpl = "create_clock -name {clk} -period {period} [get_nets {{{clk}}}]" + sdc.append(tpl.format(clk=self._vns.get_name(clk), period=str(period))) - # Generate Design Timing Constraints file (.sdc) - named_sc, named_pc = platform.resolve_signals(v_output.ns) - _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, - ) + # False path constraints + for from_, to in sorted(self.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_=self._vns.get_name(from_), to=self._vns.get_name(to))) - # Generate project file (.xml) - _build_xml( - family = platform.family, - device = platform.device, - timing_model = platform.timing_model, - build_name = build_name, - sources = platform.sources - ) + # Add additional commands + sdc += self.additional_sdc_commands - # Generate peripheral file (.peri.xml) - _build_peri( - efinity_path = self.efinity_path, - build_name = build_name, - device = platform.device, - named_sc = named_sc, - named_pc = named_pc, - fragment = fragment, - platform = platform, - additional_iface_commands = self.additional_iface_commands, - excluded_ios = self.excluded_ios - ) + # Generate .sdc + tools.write_to_file("{}.sdc".format(self._build_name), "\n".join(sdc)) + return (self._build_name + ".sdc", "SDC") + + # Peripheral configuration (.xml) ------------------------------------------------------------------ + + def get_pin_direction(self, pinname): + pins = self.platform.constraint_manager.get_io_signals() + for pin in sorted(pins, key=lambda x: x.duid): + # Better idea ??? + if (pinname.split("[")[0] == pin.name): + return pin.direction + return "Unknown" + + def _create_gpio_instance(self, sig, pins): + l = "" + if len(pins) > 1: + l = ",{},0".format(len(pins) - 1) + d = self.get_pin_direction(sig) + return 'design.create_{d}_gpio("{name}"{len})'.format(d=d, name=sig, len=l) + + def _format_constraint(self, c, signame, fmt_r): + # 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", + "1.2_V_Differential_HSTL", "1.2_V_Differential_SSTL", + "1.2_V_HSTL", "1.2_V_LVCMOS", "1.2_V_SSTL", "1.5_V_Differential_HSTL", + "1.5_V_Differential_SSTL", "1.5_V_HSTL", "1.5_V_LVCMOS", "1.5_V_SSTL", + "1.8_V_Differential_HSTL", "1.8_V_Differential_SSTL", "1.8_V_HSTL", + "1.8_V_LVCMOS", "1.8_V_SSTL", "2.5_V_LVCMOS", "3.0_V_LVCMOS", + "3.0_V_LVTTL", "3.3_V_LVCMOS", "3.3_V_LVTTL" + ] + + 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 "SLEWRATE" in c.misc: + prop = "SLEW_RATE" + val = "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(self, signame, pin, others, resname): + fmt_r = "{}:{}".format(*resname[:2]) + if resname[2] is not None: + fmt_r += "." + resname[2] + fmt_c = [self._format_constraint(c, signame, fmt_r) for c in ([Pins(pin)] + others)] + return "".join(fmt_c) + + def _build_iface_gpio(self): + conf = [] + inst = [] + + # GPIO + for sig, pins, others, resname in self.named_sc: + excluded = False + for excluded_io in self.excluded_ios: + if isinstance(excluded_io, str): + if sig == excluded_io: + excluded = True + elif isinstance(excluded_io, Signal): + if sig == excluded_io.name: + excluded = True + if excluded: + continue + inst.append(self._create_gpio_instance(sig, pins)) + if len(pins) > 1: + for i, p in enumerate(pins): + conf.append(_format_conf_constraint("{}[{}]".format(sig, i), p, others, resname)) + else: + conf.append(self._format_conf_constraint(sig, pins[0], others, resname)) + if self.named_pc: + conf.append("\n\n".join(self.named_pc)) + + conf = inst + conf + + return "\n".join(conf) + + def build_io_constraints(self): + pythonpath = "" + + header = self.ifacewriter.header(self._build_name, self.platform.device) + gen = self.ifacewriter.generate(self.platform.device) + #TODO : move this to ifacewriter + gpio = self._build_iface_gpio() + add = "\n".join(self.additional_iface_commands) + footer = self.ifacewriter.footer() + + tools.write_to_file("iface.py", header + gen + gpio + add + footer) + + if tools.subprocess_call_filtered([self.efinity_path + "/bin/python3", "iface.py"], common.colors) != 0: + raise OSError("Error occurred during Efinity peri script execution.") # Some IO blocks don't have Python API so we need to configure them # directly in the peri.xml file # We also need to configure the bank voltage here - if self.ifacewriter.xml_blocks or platform.iobank_info: + if self.ifacewriter.xml_blocks or self.platform.iobank_info: self.ifacewriter.generate_xml_blocks() # Because the Python API is sometimes bugged, we need to tweak the generated xml if self.ifacewriter.fix_xml: self.ifacewriter.fix_xml_values() - # Run - if run: - # Synthesis/Mapping. - r = tools.subprocess_call_filtered([self.efinity_path + "/bin/efx_map", - "--project", f"{build_name}", - "--root", f"{build_name}", - "--write-efx-verilog", f"outflow/{build_name}.map.v", - "--write-premap-module", f"outflow/{build_name}.elab.vdb", - "--binary-db", f"{build_name}.vdb", - "--family", platform.family, - "--device", platform.device, - "--mode", "speed", - "--max_ram", "-1", - "--max_mult", "-1", - "--infer-clk-enable", "3", - "--infer-sync-set-reset", "1", - "--fanout-limit", "0", - "--bram_output_regs_packing", "1", - "--retiming", "1", - "--seq_opt", "1", - "--blast_const_operand_adders", "1", - "--mult_input_regs_packing", "1", - "--mult_output_regs_packing", "1", - "--veri_option", "verilog_mode=verilog_2k,vhdl_mode=vhdl_2008", - "--work-dir", "work_syn", - "--output-dir", "outflow", - "--project-xml", f"{build_name}.xml", - "--I", "./" - ], common.colors) - if r != 0: - raise OSError("Error occurred during efx_map execution.") - # Place and Route. - r = tools.subprocess_call_filtered([self.efinity_path + "/bin/python3", - self.efinity_path + "/scripts/efx_run_pt.py", - f"{build_name}", - platform.family, - platform.device - ], common.colors) - if r != 0: - raise OSError("Error occurred during efx_run_pt execution.") - r = tools.subprocess_call_filtered([self.efinity_path + "/bin/efx_pnr", - "--circuit", f"{build_name}", - "--family", platform.family, - "--device", platform.device, - "--operating_conditions", platform.timing_model, - "--pack", - "--place", - "--route", - "--vdb_file", f"work_syn/{build_name}.vdb", - "--use_vdb_file", "on", - "--place_file", f"outflow/{build_name}.place", - "--route_file", f"outflow/{build_name}.route", - "--sdc_file", f"{build_name}.sdc", - "--sync_file", f"outflow/{build_name}.interface.csv", - "--seed", "1", - "--work_dir", "work_pnr", - "--output_dir", "outflow", - "--timing_analysis", "on", - "--load_delay_matrix" - ], common.colors) - if r != 0: - raise OSError("Error occurred during efx_pnr execution.") + # Project configuration (.xml) --------------------------------------------------------------------- - # Bitstream. - r = tools.subprocess_call_filtered([self.efinity_path + "/bin/efx_pgm", - "--source", f"work_pnr/{build_name}.lbf", - "--dest", f"{build_name}.hex", - "--device", platform.device, - "--family", platform.family, - "--periph", f"outflow/{build_name}.lpf", - "--oscillator_clock_divider", "DIV8", - "--spi_low_power_mode", "off", - "--io_weak_pullup", "on", - "--enable_roms", "on", - "--mode", "active", - "--width", "1", - "--enable_crc_check", "on" - ], common.colors) - if r != 0: - raise OSError("Error occurred during efx_pgm execution.") + def build_project(self): + now = datetime.datetime.now() - os.chdir(cwd) + # Create Project. + root = et.Element("efx:project") + root.attrib["xmlns:efx"] = "http://www.efinixinc.com/enf_proj" + root.attrib["name"] = self._build_name + 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_change_date"] = f"Date : {now.strftime('%Y-%m-%d %H:%M')}" - return v_output.ns + # Add Device. + device_info = et.SubElement(root, "efx:device_info") + et.SubElement(device_info, "efx:family", name=self.platform.family) + et.SubElement(device_info, "efx:device", name=self.platform.device) + et.SubElement(device_info, "efx:timing_model", name=self.platform.timing_model) - 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 + # Add Design Info. + design_info = et.SubElement(root, "efx:design_info") + et.SubElement(design_info, "efx:top_module", name=self._build_name) - 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)) + # Add Design Sources. + for filename, language, library, *copy in self.platform.sources: + if language is None: + continue + et.SubElement(design_info, "efx:design_file", { + "name" : filename, + "version" : "default", + "library" : "default" if ".vh" not in filename else library, + }) + + # Add Timing Constraints. + constraint_info = et.SubElement(root, "efx:constraint_info") + et.SubElement(constraint_info, "efx:sdc_file", name=f"{self._build_name}.sdc") + + # Add Misc Info. + misc_info = et.SubElement(root, "efx:misc_info") + + # Add IP Info. + ip_info = et.SubElement(root, "efx:ip_info") + + # Generate .xml + xml_str = et.tostring(root, "utf-8") + xml_str = expatbuilder.parseString(xml_str, False) + xml_str = xml_str.toprettyxml(indent=" ") + tools.write_to_file("{}.xml".format(self._build_name), xml_str) + + def build_script(self): + return "" # not used + + def run_script(self, script): + # Synthesis/Mapping. + r = tools.subprocess_call_filtered([self.efinity_path + "/bin/efx_map", + "--project", f"{self._build_name}", + "--root", f"{self._build_name}", + "--write-efx-verilog", f"outflow/{self._build_name}.map.v", + "--write-premap-module", f"outflow/{self._build_name}.elab.vdb", + "--binary-db", f"{self._build_name}.vdb", + "--family", platform.family, + "--device", platform.device, + "--mode", "speed", + "--max_ram", "-1", + "--max_mult", "-1", + "--infer-clk-enable", "3", + "--infer-sync-set-reset", "1", + "--fanout-limit", "0", + "--bram_output_regs_packing", "1", + "--retiming", "1", + "--seq_opt", "1", + "--blast_const_operand_adders", "1", + "--mult_input_regs_packing", "1", + "--mult_output_regs_packing", "1", + "--veri_option", "verilog_mode=verilog_2k,vhdl_mode=vhdl_2008", + "--work-dir", "work_syn", + "--output-dir", "outflow", + "--project-xml", f"{self._build_name}.xml", + "--I", "./" + ], common.colors) + if r != 0: + raise OSError("Error occurred during efx_map execution.") + + # Place and Route. + r = tools.subprocess_call_filtered([self.efinity_path + "/bin/python3", + self.efinity_path + "/scripts/efx_run_pt.py", + f"{self._build_name}", + platform.family, + platform.device + ], common.colors) + if r != 0: + raise OSError("Error occurred during efx_run_pt execution.") + + r = tools.subprocess_call_filtered([self.efinity_path + "/bin/efx_pnr", + "--circuit", f"{self._build_name}", + "--family", platform.family, + "--device", platform.device, + "--operating_conditions", platform.timing_model, + "--pack", + "--place", + "--route", + "--vdb_file", f"work_syn/{self._build_name}.vdb", + "--use_vdb_file", "on", + "--place_file", f"outflow/{self._build_name}.place", + "--route_file", f"outflow/{self._build_name}.route", + "--sdc_file", f"{self._build_name}.sdc", + "--sync_file", f"outflow/{self._build_name}.interface.csv", + "--seed", "1", + "--work_dir", "work_pnr", + "--output_dir", "outflow", + "--timing_analysis", "on", + "--load_delay_matrix" + ], common.colors) + if r != 0: + raise OSError("Error occurred during efx_pnr execution.") + + # Bitstream. + r = tools.subprocess_call_filtered([self.efinity_path + "/bin/efx_pgm", + "--source", f"work_pnr/{self._build_name}.lbf", + "--dest", f"{self._build_name}.hex", + "--device", platform.device, + "--family", platform.family, + "--periph", f"outflow/{self._build_name}.lpf", + "--oscillator_clock_divider", "DIV8", + "--spi_low_power_mode", "off", + "--io_weak_pullup", "on", + "--enable_roms", "on", + "--mode", "active", + "--width", "1", + "--enable_crc_check", "on" + ], common.colors) + if r != 0: + raise OSError("Error occurred during efx_pgm execution.") diff --git a/litex/build/lattice/diamond.py b/litex/build/lattice/diamond.py index 649ce72b2..e21b42560 100644 --- a/litex/build/lattice/diamond.py +++ b/litex/build/lattice/diamond.py @@ -45,42 +45,6 @@ class LatticeDiamondToolchain(GenericToolchain): return self._build(platform, fragment, **kwargs) - ## Create build directory - #os.makedirs(build_dir, exist_ok=True) - #cwd = os.getcwd() - #os.chdir(build_dir) - - ## Finalize design - #if not isinstance(fragment, _Fragment): - # fragment = fragment.get_fragment() - #platform.finalize(fragment) - - ## Generate verilog - #v_output = platform.get_verilog(fragment, name=build_name, **kwargs) - #named_sc, named_pc = platform.resolve_signals(v_output.ns) - #v_file = build_name + ".v" - #v_output.write(v_file) - #platform.add_source(v_file) - - ## Generate design constraints file (.lpf) - #_build_lpf(named_sc, named_pc, self.clocks, v_output.ns, build_name) - - ## Generate design script file (.tcl) - #_build_tcl(platform.device, platform.sources, platform.verilog_include_paths, build_name) - - ## Generate build script - #script = _build_script(build_name, platform.device) - - ## Run - #if run: - # _run_script(script) - # if timingstrict: - # _check_timing(build_name) - - #os.chdir(cwd) - - #return v_output.ns - # Helpers -------------------------------------------------------------------------------------- @classmethod diff --git a/litex/build/osfpga/osfpga.py b/litex/build/osfpga/osfpga.py index 7700ecce5..36dd8b4bb 100644 --- a/litex/build/osfpga/osfpga.py +++ b/litex/build/osfpga/osfpga.py @@ -12,129 +12,87 @@ from shutil import which, copyfile from migen.fhdl.structure import _Fragment +from litex.build.generic_toolchain import GenericToolchain from litex.build.generic_platform import * from litex.build import tools -# Timing Constraints (.sdc) ------------------------------------------------------------------------ - -def _build_sdc(clocks, vns, build_name): - sdc = [] - for clk, period in sorted(clocks.items(), key=lambda x: x[0].duid): - sdc.append(f"create_clock -name {vns.get_name(clk)} -period {str(period)} [get_ports {{{vns.get_name(clk)}}}]") - with open(f"{build_name}.sdc", "w") as f: - f.write("\n".join(sdc)) - -# Script ------------------------------------------------------------------------------------------- - -def _build_tcl(name, device, files, build_name, include_paths): - tcl = [] - - # Create Design. - tcl.append(f"create_design {build_name}") - - # Set Device. - tcl.append(f"target_device {device.upper()}") - - # Add Include Path. - tcl.append("add_include_path ./") - for include_path in include_paths: - tcl.append(f"add_include_path {include_path}") - - # Add Sources. - for f, typ, lib in files: - tcl.append(f"add_design_file {f}") - - # Set Top Module. - tcl.append(f"set_top_module {build_name}") - - # Add Timings Constraints. - tcl.append(f"add_constraint_file {build_name}.sdc") - - # Run. - tcl.append("synth") - tcl.append("packing") - tcl.append("place") - tcl.append("route") - tcl.append("sta") - tcl.append("power") - tcl.append("bitstream") - - # Generate .tcl. - with open("build.tcl", "w") as f: - f.write("\n".join(tcl)) - # OSFPGAToolchain ----------------------------------------------------------------------------------- -class OSFPGAToolchain: +class OSFPGAToolchain(GenericToolchain): attr_translate = {} def __init__(self, toolchain): self.toolchain = toolchain self.clocks = dict() - def build(self, platform, fragment, - build_dir = "build", - build_name = "top", - run = True, - **kwargs): + def build(self, platform, fragment, **kwargs): + return self._build(platform, fragment, **kwargs) - # Create build directory. - cwd = os.getcwd() - os.makedirs(build_dir, exist_ok=True) - os.chdir(build_dir) + # Constraints ---------------------------------------------------------------------------------- - # Finalize design - if not isinstance(fragment, _Fragment): - fragment = fragment.get_fragment() - platform.finalize(fragment) + def build_io_constraints(self): + return ("", "") # TODO - # 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) + # Timing Constraints (.sdc) -------------------------------------------------------------------- - # Generate constraints file. - # IOs. - # TODO. + def build_timing_constraints(self, vns): + sdc = [] + for clk, period in sorted(self.clocks.items(), key=lambda x: x[0].duid): + sdc.append(f"create_clock -name {vns.get_name(clk)} -period {str(period)} [get_ports {{{vns.get_name(clk)}}}]") + with open(f"{build_name}.sdc", "w") as f: + f.write("\n".join(sdc)) + return (self._build_name + ".sdc", "SDC") - # Timings (.sdc) - _build_sdc( - clocks = self.clocks, - vns = v_output.ns, - build_name = build_name, - ) + # Project -------------------------------------------------------------------------------------- - # Generate build script (.tcl) - script = _build_tcl( - name = platform.devicename, - device = platform.device, - files = platform.sources, - build_name = build_name, - include_paths = platform.verilog_include_paths, - ) + def build_project(self): + tcl = [] - # Run - if run: - toolchain_sh = self.toolchain - if which(toolchain_sh) is None: - msg = f"Unable to find {toolchain_sh.upper()} toolchain, please:\n" - msg += f"- Add {toolchain_sh.upper()} toolchain to your $PATH." - raise OSError(msg) + # Create Design. + tcl.append(f"create_design {self._build_name}") - if subprocess.call([toolchain_sh, "--batch", "--script", "build.tcl"]) != 0: - raise OSError(f"Error occured during {toolchain_sh.upper()}'s script execution.") + # Set Device. + tcl.append(f"target_device {self.platform.device.upper()}") - os.chdir(cwd) + # Add Include Path. + tcl.append("add_include_path ./") + for include_path in platform.verilog_include_paths: + tcl.append(f"add_include_path {include_path}") - return v_output.ns + # Add Sources. + for f, typ, lib in self.platform.sources: + tcl.append(f"add_design_file {f}") - 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 + # Set Top Module. + tcl.append(f"set_top_module {self._build_name}") + + # Add Timings Constraints. + tcl.append(f"add_constraint_file {self._build_name}.sdc") + + # Run. + tcl.append("synth") + tcl.append("packing") + tcl.append("place") + tcl.append("route") + tcl.append("sta") + tcl.append("power") + tcl.append("bitstream") + + # Generate .tcl. + with open("build.tcl", "w") as f: + f.write("\n".join(tcl)) + + # Script --------------------------------------------------------------------------------------- + + def build_script(self): + return "" # unused + + def run_script(self, script): + toolchain_sh = self.toolchain + if which(toolchain_sh) is None: + msg = f"Unable to find {toolchain_sh.upper()} toolchain, please:\n" + msg += f"- Add {toolchain_sh.upper()} toolchain to your $PATH." + raise OSError(msg) + + if subprocess.call([toolchain_sh, "--batch", "--script", "build.tcl"]) != 0: + raise OSError(f"Error occured during {toolchain_sh.upper()}'s script execution.") diff --git a/litex/build/xilinx/f4pga.py b/litex/build/xilinx/f4pga.py index f2c5d6b9b..d6acb308d 100644 --- a/litex/build/xilinx/f4pga.py +++ b/litex/build/xilinx/f4pga.py @@ -10,6 +10,7 @@ import math from migen.fhdl.structure import _Fragment +from litex.build.generic_toolchain import GenericToolchain from litex.build.generic_platform import * from litex.build.xilinx.vivado import _xdc_separator, _build_xdc from litex.build import tools @@ -28,7 +29,7 @@ F4CACHEPATH = '.f4cache' # F4PGAToolchain ------------------------------------------------------------------------------- # Formerly SymbiflowToolchain, Symbiflow has been renamed to F4PGA ----------------------------- -class F4PGAToolchain: +class F4PGAToolchain(GenericToolchain): attr_translate = { "keep": ("dont_touch", "true"), "no_retiming": ("dont_touch", "true"), @@ -40,59 +41,11 @@ class F4PGAToolchain: } def __init__(self): - self.clocks = dict() - self.false_paths = set() + super().__init__() self._partname = None - - def _generate_prj_flow(self, platform, build_name): - target = "bitstream" - - prj_flow_cfg_dict = {} - prj_flow_cfg_dict["dependencies"] = {} - prj_flow_cfg_dict["values"] = {} - prj_flow_cfg_dict[self._partname] = {} - - deps_cfg = prj_flow_cfg_dict["dependencies"] - deps_cfg["sources"] = [f for f,language,_ in platform.sources if language in ["verilog", "system_verilog"]] - deps_cfg["xdc"] = f"{build_name}.xdc" - deps_cfg["sdc"] = f"{build_name}.sdc" - deps_cfg["build_dir"] = os.getcwd() - deps_cfg["synth_log"] = f"{build_name}_synth.log" - deps_cfg["pack_log"] = f"{build_name}_pack.log" - deps_cfg["json"] = f"{build_name}.json" - - values_cfg = prj_flow_cfg_dict["values"] - values_cfg["top"] = build_name - values_cfg["part_name"] = self._partname - - prj_flow_cfg = ProjectFlowConfig("") - prj_flow_cfg.flow_cfg = prj_flow_cfg_dict - - flow_cfg = make_flow_config(prj_flow_cfg, self._partname) - - flow = Flow( - target=target, - cfg=flow_cfg, - f4cache=F4Cache(F4CACHEPATH) - ) - - print("\nProject status:") - flow.print_resolved_dependencies(0) - print("") - - return flow - - def _build_clock_constraints(self, platform): - platform.add_platform_command(_xdc_separator("Clock constraints")) - for clk, period in sorted(self.clocks.items(), key=lambda x: x[0].duid): - platform.add_platform_command( - "create_clock -period " + str(period) + - " {clk}", clk=clk) + self._flow = None def build(self, platform, fragment, - build_dir = "build", - build_name = "top", - run = True, enable_xpm = False, **kwargs): @@ -103,57 +56,67 @@ class F4PGAToolchain: "xc7a200t-sbg484-1" : "xc7a200tsbg484-1", }.get(platform.device, platform.device) - # Create build directory - os.makedirs(build_dir, exist_ok=True) - cwd = os.getcwd() - os.chdir(build_dir) - - # Finalize design - if not isinstance(fragment, _Fragment): - fragment = fragment.get_fragment() - platform.finalize(fragment) - - # Generate timing constraints - self._build_clock_constraints(platform) - - # 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) - set_verbosity_level(2) - # Generate design constraints - tools.write_to_file(build_name + ".xdc", _build_xdc(named_sc, named_pc)) + return self._build(platform, fragment, **kwargs) - flow = self._generate_prj_flow( - platform = platform, - build_name = build_name + def build_io_contraints(self): + # Generate design constraints + tools.write_to_file(self._build_name + ".xdc", _build_xdc(self.named_sc, self.named_pc)) + return (self._build_name + ".xdc", "XDC") + + def build_timing_constraints(self): + self.platform.add_platform_command(_xdc_separator("Clock constraints")) + for clk, period in sorted(self.clocks.items(), key=lambda x: x[0].duid): + self.platform.add_platform_command( + "create_clock -period " + str(period) + + " {clk}", clk=clk) + return ("", "") + + def build_project(self): + target = "bitstream" + + prj_flow_cfg_dict = {} + prj_flow_cfg_dict["dependencies"] = {} + prj_flow_cfg_dict["values"] = {} + prj_flow_cfg_dict[self._partname] = {} + + deps_cfg = prj_flow_cfg_dict["dependencies"] + deps_cfg["sources"] = [f for f,language,_ in self.platform.sources if language in ["verilog", "system_verilog"]] + deps_cfg["xdc"] = f"{self._build_name}.xdc" + deps_cfg["sdc"] = f"{self._build_name}.sdc" + deps_cfg["build_dir"] = os.getcwd() + deps_cfg["synth_log"] = f"{self._build_name}_synth.log" + deps_cfg["pack_log"] = f"{self._build_name}_pack.log" + deps_cfg["json"] = f"{self._build_name}.json" + + values_cfg = prj_flow_cfg_dict["values"] + values_cfg["top"] = self._build_name + values_cfg["part_name"] = self._partname + + prj_flow_cfg = ProjectFlowConfig("") + prj_flow_cfg.flow_cfg = prj_flow_cfg_dict + + flow_cfg = make_flow_config(prj_flow_cfg, self._partname) + + self._flow = Flow( + target=target, + cfg=flow_cfg, + f4cache=F4Cache(F4CACHEPATH) ) - if run: - try: - flow.execute() - except Exception as e: - print(e) + print("\nProject status:") + self._flow.print_resolved_dependencies(0) + print("") + + def run_script(self, script): + try: + flow.execute() + except Exception as e: + print(e) flow.f4cache.save() - 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): # FIXME: false path constraints are currently not supported by the F4PGA toolchain return diff --git a/litex/build/xilinx/ise.py b/litex/build/xilinx/ise.py index 5c6b01190..d25ac4578 100644 --- a/litex/build/xilinx/ise.py +++ b/litex/build/xilinx/ise.py @@ -17,163 +17,14 @@ from shutil import which from migen.fhdl.structure import _Fragment +from litex.build.generic_toolchain import GenericToolchain from litex.build.generic_platform import * from litex.build import tools from litex.build.xilinx import common -# Constraints (.ucf) ------------------------------------------------------------------------------- - -def _format_constraint(c): - if isinstance(c, Pins): - return "LOC=" + c.identifiers[0] - elif isinstance(c, IOStandard): - return "IOSTANDARD=" + c.name - elif isinstance(c, Drive): - return "DRIVE=" + str(c.strength) - elif isinstance(c, Misc): - return c.misc - -def _format_ucf(signame, pin, others, resname): - fmt_c = [] - for c in [Pins(pin)] + others: - fc = _format_constraint(c) - if fc is not None: - fmt_c.append(fc) - fmt_r = resname[0] + ":" + str(resname[1]) - if resname[2] is not None: - fmt_r += "." + resname[2] - return "NET \"" + signame + "\" " + " | ".join(fmt_c) + "; # " + fmt_r + "\n" - -def _build_ucf(named_sc, named_pc): - r = "" - for sig, pins, others, resname in named_sc: - if len(pins) > 1: - for i, p in enumerate(pins): - r += _format_ucf(sig + "(" + str(i) + ")", p, others, resname) - else: - r += _format_ucf(sig, pins[0], others, resname) - if named_pc: - r += "\n" + "\n\n".join(named_pc) - return r - -# Project (.xst) ----------------------------------------------------------------------------------- - -def _build_xst(device, sources, vincpaths, build_name, xst_opt): - prj_contents = "" - for filename, language, library, *copy in sources: - prj_contents += language + " " + library + " " + tools.cygpath(filename) + "\n" - tools.write_to_file(build_name + ".prj", prj_contents) - - xst_contents = """run --ifn {build_name}.prj --top {build_name} -{xst_opt} --ofn {build_name}.ngc --p {device} -""".format(build_name=build_name, xst_opt=xst_opt, device=device) - if vincpaths: - xst_contents += "-vlgincdir {" - for path in vincpaths: - xst_contents += tools.cygpath(path) + " " - xst_contents += "}" - tools.write_to_file(build_name + ".xst", xst_contents) - -# Yosys Run ---------------------------------------------------------------------------------------- - -def _run_yosys(device, sources, vincpaths, build_name): - ys_contents = "" - incflags = "" - for path in vincpaths: - incflags += " -I" + path - for filename, language, library, *copy in sources: - ys_contents += "read_{}{} {}\n".format(language, incflags, filename) - - family = "" - if (device.startswith("xc7") or device.startswith("xa7") or device.startswith("xq7")): - family = "xc7" - elif (device.startswith("xc6s") or device.startswith("xa6s") or device.startswith("xq6s")): - family = "xc6s" - else: - raise OSError("Unsupported device") - - ys_contents += """hierarchy -top top -synth_xilinx -top top -family {family} -ise -write_edif -pvector bra {build_name}.edif""".format(build_name=build_name, family=family) - - ys_name = build_name + ".ys" - tools.write_to_file(ys_name, ys_contents) - r = subprocess.call(["yosys", ys_name]) - if r != 0: - raise OSError("Subprocess failed") - -# ISE Run ------------------------------------------------------------------------------------------ - -def _run_ise(build_name, mode, ngdbuild_opt, toolchain, platform): - if sys.platform == "win32" or sys.platform == "cygwin": - script_ext = ".bat" - shell = ["cmd", "/c"] - build_script_contents = "@echo off\nrem Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n" - fail_stmt = " || exit /b" - else: - script_ext = ".sh" - shell = ["bash"] - build_script_contents = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\nset -e\n" - if os.getenv("LITEX_ENV_ISE", False): - build_script_contents += "source " + os.path.join(os.getenv("LITEX_ENV_ISE"), "settings64.sh\n") - fail_stmt = "" - if mode == "edif": - ext = "ngo" - build_script_contents += """ -edif2ngd {build_name}.edif {build_name}.{ext}{fail_stmt} -""" - else: - ext = "ngc" - build_script_contents += """ -xst -ifn {build_name}.xst{fail_stmt} -""" - - # This generates a .v file for post synthesis simulation - build_script_contents += """ -netgen -ofmt verilog -w -sim {build_name}.{ext} {build_name}_synth.v -""" - - build_script_contents += """ -ngdbuild {ngdbuild_opt} -uc {build_name}.ucf {build_name}.{ext} {build_name}.ngd{fail_stmt} -""" - if mode == "cpld": - build_script_contents += """ -cpldfit -ofmt verilog {par_opt} -p {device} {build_name}.ngd{fail_stmt} -taengine -f {build_name}.vm6 -detail -iopath -l {build_name}.tim{fail_stmt} -hprep6 -s IEEE1532 -i {build_name}.vm6{fail_stmt} -""" - else: - build_script_contents += """ -map {map_opt} -o {build_name}_map.ncd {build_name}.ngd {build_name}.pcf{fail_stmt} -par {par_opt} {build_name}_map.ncd {build_name}.ncd {build_name}.pcf{fail_stmt} -bitgen {bitgen_opt} {build_name}.ncd {build_name}.bit{fail_stmt} -""" - build_script_contents = build_script_contents.format(build_name=build_name, - ngdbuild_opt=ngdbuild_opt, bitgen_opt=toolchain.bitgen_opt, ext=ext, - par_opt=toolchain.par_opt, map_opt=toolchain.map_opt, - device=platform.device, fail_stmt=fail_stmt) - build_script_contents += toolchain.ise_commands.format(build_name=build_name) - build_script_file = "build_" + build_name + script_ext - tools.write_to_file(build_script_file, build_script_contents, force_unix=False) - command = shell + [build_script_file] - - if which("ise") is None and os.getenv("LITEX_ENV_ISE", False) == False: - msg = "Unable to find or source ISE toolchain, please either:\n" - msg += "- Source ISE's settings manually.\n" - msg += "- Or set LITEX_ENV_ISE environment variant to ISE's settings path.\n" - msg += "- Or add ISE toolchain to your $PATH." - raise OSError(msg) - - if tools.subprocess_call_filtered(command, common.colors) != 0: - raise OSError("Error occured during ISE's script execution.") - # XilinxISEToolchain -------------------------------------------------------------------------------- -class XilinxISEToolchain: +class XilinxISEToolchain(GenericToolchain): attr_translate = { "keep": ("keep", "true"), "no_retiming": ("register_balancing", "no"), @@ -185,71 +36,197 @@ class XilinxISEToolchain: } def __init__(self): + super().__init__() self.xst_opt = "-ifmt MIXED\n-use_new_parser yes\n-opt_mode SPEED\n-register_balancing yes" self.map_opt = "-ol high -w" self.par_opt = "-ol high -w" self.ngdbuild_opt = "" self.bitgen_opt = "-g Binary:Yes -w" self.ise_commands = "" + self.mode = "xst" + self.isemode = "xst" def build(self, platform, fragment, - build_dir = "build", - build_name = "top", - run = True, mode = "xst", **kwargs): - # Create build directory - os.makedirs(build_dir, exist_ok=True) - cwd = os.getcwd() - os.chdir(build_dir) + self._mode = mode + self._isemode = mode if mode in ["xst", "cpld"] else "edif" - # Finalize design - if not isinstance(fragment, _Fragment): - fragment = fragment.get_fragment() - platform.finalize(fragment) + return self._build(platform, fragment, **kwargs) - vns = None - try: - if mode in ["xst", "yosys", "cpld"]: - # Generate verilog - v_output = platform.get_verilog(fragment, name=build_name, **kwargs) - vns = v_output.ns - named_sc, named_pc = platform.resolve_signals(vns) - v_file = build_name + ".v" - v_output.write(v_file) - platform.add_source(v_file) + # Constraints (.ucf) ------------------------------------------------------------------------------- - # Generate design project (.xst) - if mode in ["xst", "cpld"]: - _build_xst(platform.device, platform.sources, platform.verilog_include_paths, build_name, self.xst_opt) - isemode = mode - else: - # Run Yosys - if run: - _run_yosys(platform.device, platform.sources, platform.verilog_include_paths, build_name) - isemode = "edif" - self.ngdbuild_opt += "-p " + platform.device + @classmethod + def _format_constraint(cls, c): + if isinstance(c, Pins): + return "LOC=" + c.identifiers[0] + elif isinstance(c, IOStandard): + return "IOSTANDARD=" + c.name + elif isinstance(c, Drive): + return "DRIVE=" + str(c.strength) + elif isinstance(c, Misc): + return c.misc - if mode in ["edif"]: - # Generate edif - e_output = platform.get_edif(fragment) - vns = e_output.ns - named_sc, named_pc = platform.resolve_signals(vns) - e_file = build_name + ".edif" - e_output.write(e_file) - isemode = "edif" + def _format_ucf(self, signame, pin, others, resname): + fmt_c = [] + for c in [Pins(pin)] + others: + fc = self._format_constraint(c) + if fc is not None: + fmt_c.append(fc) + fmt_r = resname[0] + ":" + str(resname[1]) + if resname[2] is not None: + fmt_r += "." + resname[2] + return "NET \"" + signame + "\" " + " | ".join(fmt_c) + "; # " + fmt_r + "\n" - # Generate design constraints (.ucf) - tools.write_to_file(build_name + ".ucf", _build_ucf(named_sc, named_pc)) + def build_io_constraints(self): + r = "" + for sig, pins, others, resname in self.named_sc: + if len(pins) > 1: + for i, p in enumerate(pins): + r += self._format_ucf(sig + "(" + str(i) + ")", p, others, resname) + else: + r += self._format_ucf(sig, pins[0], others, resname) + if self.named_pc: + r += "\n" + "\n\n".join(self.named_pc) + tools.write_to_file(self._build_name + ".ucf", r) + return (self._build_name + ".ucf", "UCF") - # Run ISE - if run: - _run_ise(build_name, isemode, self.ngdbuild_opt, self, platform) - finally: - os.chdir(cwd) + # Project (.xst) ------------------------------------------------------------------------------- - return vns + def build_project(self): + if self.mode not in ["xst", "cpld"]: + return ("", "") + prj_contents = "" + for filename, language, library, *copy in self.platform.sources: + prj_contents += language + " " + library + " " + tools.cygpath(filename) + "\n" + tools.write_to_file(self._build_name + ".prj", prj_contents) + + xst_contents = """run +-ifn {build_name}.prj +-top {build_name} +{xst_opt} +-ofn {build_name}.ngc +-p {device} +""".format(build_name=self._build_name, xst_opt=self.xst_opt, device=self.platform.device) + if self.platform.verilog_include_paths: + xst_contents += "-vlgincdir {" + for path in self.platform.verilog_include_paths: + xst_contents += tools.cygpath(path) + " " + xst_contents += "}" + tools.write_to_file(self._build_name + ".xst", xst_contents) + + # Yosys Run ---------------------------------------------------------------------------------------- + + def _run_yosys(build_name): + device = self.platform.device + ys_contents = "" + incflags = "" + for path in platform.verilog_include_paths: + incflags += " -I" + path + for filename, language, library, *copy in self.platform.sources: + ys_contents += "read_{}{} {}\n".format(language, incflags, filename) + + family = "" + if (device.startswith("xc7") or device.startswith("xa7") or device.startswith("xq7")): + family = "xc7" + elif (device.startswith("xc6s") or device.startswith("xa6s") or device.startswith("xq6s")): + family = "xc6s" + else: + raise OSError("Unsupported device") + + ys_contents += """hierarchy -top top + synth_xilinx -top top -family {family} -ise + write_edif -pvector bra {build_name}.edif""".format(build_name=self._build_name, family=family) + + ys_name = self._build_name + ".ys" + tools.write_to_file(ys_name, ys_contents) + r = subprocess.call(["yosys", ys_name]) + if r != 0: + raise OSError("Subprocess failed") + + # ISE Run ------------------------------------------------------------------------------------------ + + def build_script(self): + if sys.platform == "win32" or sys.platform == "cygwin": + script_ext = ".bat" + shell = ["cmd", "/c"] + build_script_contents = "@echo off\nrem Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n" + fail_stmt = " || exit /b" + else: + script_ext = ".sh" + shell = ["bash"] + build_script_contents = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\nset -e\n" + if os.getenv("LITEX_ENV_ISE", False): + build_script_contents += "source " + os.path.join(os.getenv("LITEX_ENV_ISE"), "settings64.sh\n") + fail_stmt = "" + if self._isemode == "edif": + ext = "ngo" + build_script_contents += """ +edif2ngd {build_name}.edif {build_name}.{ext}{fail_stmt} +""" + else: + ext = "ngc" + build_script_contents += """ +xst -ifn {build_name}.xst{fail_stmt} +""" + + # This generates a .v file for post synthesis simulation + build_script_contents += """ +netgen -ofmt verilog -w -sim {build_name}.{ext} {build_name}_synth.v +""" + + build_script_contents += """ +ngdbuild {ngdbuild_opt} -uc {build_name}.ucf {build_name}.{ext} {build_name}.ngd{fail_stmt} +""" + if self._isemode == "cpld": + build_script_contents += """ +cpldfit -ofmt verilog {par_opt} -p {device} {build_name}.ngd{fail_stmt} +taengine -f {build_name}.vm6 -detail -iopath -l {build_name}.tim{fail_stmt} +hprep6 -s IEEE1532 -i {build_name}.vm6{fail_stmt} +""" + else: + build_script_contents += """ +map {map_opt} -o {build_name}_map.ncd {build_name}.ngd {build_name}.pcf{fail_stmt} +par {par_opt} {build_name}_map.ncd {build_name}.ncd {build_name}.pcf{fail_stmt} +bitgen {bitgen_opt} {build_name}.ncd {build_name}.bit{fail_stmt} +""" + build_script_contents = build_script_contents.format(build_name=self._build_name, + ngdbuild_opt=self.ngdbuild_opt, bitgen_opt=self.bitgen_opt, ext=ext, + par_opt=self.par_opt, map_opt=self.map_opt, + device=self.platform.device, fail_stmt=fail_stmt) + build_script_contents += self.ise_commands.format(build_name=self._build_name) + build_script_file = "build_" + self._build_name + script_ext + tools.write_to_file(build_script_file, build_script_contents, force_unix=False) + return build_script_file + + def run_script(self, script): + + if self.mode == "yosys": + self._run_yosys() + self.ngdbuild_opt += "-p " + self.platform.device + + if self.mode == "edif": + # Generate edif + e_output = self.platform.get_edif(self._fragment) + self._vns = e_output.ns + self.named_sc, self.named_pc = self.platform.resolve_signals(self._vns) + e_file = build_name + ".edif" + e_output.write(e_file) + self.build_io_constraints() + + + command = shell + [build_script_file] + + if which("ise") is None and os.getenv("LITEX_ENV_ISE", False) == False: + msg = "Unable to find or source ISE toolchain, please either:\n" + msg += "- Source ISE's settings manually.\n" + msg += "- Or set LITEX_ENV_ISE environment variant to ISE's settings path.\n" + msg += "- Or add ISE toolchain to your $PATH." + raise OSError(msg) + + if tools.subprocess_call_filtered(command, common.colors) != 0: + raise OSError("Error occured during ISE's script execution.") # ISE is broken and you must use *separate* TNM_NET objects for period # constraints and other constraints otherwise it will be unable to trace diff --git a/litex/build/xilinx/yosys_nextpnr.py b/litex/build/xilinx/yosys_nextpnr.py index ddbce69d1..7473fe041 100644 --- a/litex/build/xilinx/yosys_nextpnr.py +++ b/litex/build/xilinx/yosys_nextpnr.py @@ -17,6 +17,7 @@ from shutil import which from migen.fhdl.structure import _Fragment, wrap, Constant from migen.fhdl.specials import Instance +from litex.build.generic_toolchain import GenericToolchain from litex.build.generic_platform import * from litex.build.xilinx.vivado import _xdc_separator, _format_xdc, _build_xdc from litex.build import tools @@ -86,7 +87,7 @@ def _run_make(): # YosysNextpnrToolchain ------------------------------------------------------------------------------- -class YosysNextpnrToolchain: +class YosysNextpnrToolchain(GenericToolchain): attr_translate = { #"keep": ("dont_touch", "true"), #"no_retiming": ("dont_touch", "true"), @@ -98,13 +99,12 @@ class YosysNextpnrToolchain: } def __init__(self): - self.clocks = dict() - self.false_paths = set() + super().__init__() self.f4pga_device = None self.bitstream_device = None self._partname = None - def _check_properties(self, platform): + def _check_properties(self): if not self.f4pga_device: try: self.f4pga_device = { @@ -113,7 +113,7 @@ class YosysNextpnrToolchain: "xc7a100tcsg324-1" : "xc7a35t", "xc7z010clg400-1" : "xc7z010", "xc7z020clg400-1" : "xc7z020", - }[platform.device] + }[self.platform.device] except KeyError: raise ValueError(f"f4pga_device is not specified") if not self.bitstream_device: @@ -123,9 +123,9 @@ class YosysNextpnrToolchain: self.bitstream_device = { "xc7a": "artix7", # xc7a35t, xc7a50t, xc7a100t, xc7a200t "xc7z": "zynq7", # xc7z010, xc7z020 - }[platform.device[:4]] + }[self.platform.device[:4]] except KeyError: - raise ValueError(f"Unsupported device: {platform.device}") + raise ValueError(f"Unsupported device: {self.platform.device}") # FIXME: prjxray-db doesn't have xc7a35ticsg324-1L - use closest replacement self._partname = { "xc7a35ticsg324-1L" : "xc7a35tcsg324-1", @@ -133,15 +133,15 @@ class YosysNextpnrToolchain: "xc7a200t-sbg484-1" : "xc7a200tsbg484-1", "xc7z010clg400-1" : "xc7z010clg400-1", "xc7z020clg400-1" : "xc7z020clg400-1", - }.get(platform.device, platform.device) + }.get(self.platform.device, self.platform.device) - def _generate_makefile(self, platform, build_name): + def build_script(self): Var = _MakefileGenerator.Var Rule = _MakefileGenerator.Rule makefile = _MakefileGenerator([ "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n", - Var("TOP", build_name), + Var("TOP", self._build_name), Var("PARTNAME", self._partname), Var("DEVICE", self.f4pga_device), Var("BITSTREAM_DEVICE", self.bitstream_device), @@ -149,10 +149,10 @@ class YosysNextpnrToolchain: Var("DB_DIR", "/usr/share/nextpnr/prjxray-db"), #FIXME: resolve path Var("CHIPDB_DIR", "/usr/share/nextpnr/xilinx-chipdb"), #FIXME: resolve path "", - Var("VERILOG", [f for f,language,_ in platform.sources if language in ["verilog", "system_verilog"]]), + Var("VERILOG", [f for f,language,_ in self.platform.sources if language in ["verilog", "system_verilog"]]), Var("MEM_INIT", [f"{name}" for name in os.listdir() if name.endswith(".init")]), - Var("SDC", f"{build_name}.sdc"), - Var("XDC", f"{build_name}.xdc"), + Var("SDC", f"{self._build_name}.sdc"), + Var("XDC", f"{self._build_name}.xdc"), Var("ARTIFACTS", [ "$(TOP).fasm", "$(TOP).frames", "*.bit", "*.fasm", "*.json", "*.log", "*.rpt", @@ -182,75 +182,38 @@ class YosysNextpnrToolchain: ]) tools.write_to_file("Makefile", makefile.generate()) + return "Makefile" - def _build_clock_constraints(self, platform): - platform.add_platform_command(_xdc_separator("Clock constraints")) + def build_timing_constraints(self, vns): + self.platform.add_platform_command(_xdc_separator("Clock constraints")) #for clk, period in sorted(self.clocks.items(), key=lambda x: x[0].duid): # platform.add_platform_command( # "create_clock -period " + str(period) + # " {clk}", clk=clk) pass #clock constraints not supported + def build_io_constraints(self): + tools.write_to_file(self._build_name + ".xdc", _build_xdc(self.named_sc, self.named_pc)) + return (self._build_name + ".xdc", "XDC") + def _fix_instance(self, instance): pass - def build(self, platform, fragment, - build_dir = "build", - build_name = "top", - run = True, - enable_xpm = False, - **kwargs): - - self._check_properties(platform) - - # Create build directory - os.makedirs(build_dir, exist_ok=True) - cwd = os.getcwd() - os.chdir(build_dir) - - # Finalize design - if not isinstance(fragment, _Fragment): - fragment = fragment.get_fragment() - platform.finalize(fragment) - + def finalize(self): # toolchain-specific fixes - for instance in fragment.specials: + for instance in self.fragment.specials: if isinstance(instance, Instance): self._fix_instance(instance) - # Generate timing constraints - self._build_clock_constraints(platform) + def build(self, platform, fragment, + enable_xpm = False, + **kwargs): - # 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) + #FIXME + self.platform = platform + self._check_properties() - self._generate_makefile( - platform = platform, - build_name = build_name - ) - - # Generate design constraints - tools.write_to_file(build_name + ".xdc", _build_xdc(named_sc, named_pc)) - - if run: - _run_make() - - 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 + return self._build(platform, fragment, **kwargs) def add_false_path_constraint(self, platform, from_, to): # FIXME: false path constraints are currently not supported by the F4PGA toolchain