build: efinix/efinity lattice/diamond osfpga xilinx/f4pga xilinx/ise xilinx/yosys_nextpnr -> GenericToolchain

This commit is contained in:
Gwenhael Goavec-Merou 2022-06-28 22:21:24 +02:00
parent a4bb65655c
commit 382ebbf661
6 changed files with 607 additions and 847 deletions

View File

@ -23,57 +23,88 @@ from migen.fhdl.simplify import FullMemoryWE
from litex.build import tools from litex.build import tools
from litex.build.generic_platform import * from litex.build.generic_platform import *
from litex.build.generic_platform import Pins, IOStandard, Misc 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 common
from litex.build.efinix import InterfaceWriter from litex.build.efinix import InterfaceWriter
def get_pin_direction(fragment, platform, pinname):
pins = platform.constraint_manager.get_io_signals() # Efinity Toolchain --------------------------------------------------------------------------------
class EfinityToolchain(GenericToolchain):
attr_translate = {}
def __init__(self, efinity_path):
super().__init__()
self.options = {}
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 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) + "}"
def build(self, platform, fragment, **kwargs):
# Apply FullMemoryWE on Design (Efiniy does not infer memories correctly otherwise).
FullMemoryWE()(fragment)
return self._build(platform, fragment, **kwargs)
# Timing Constraints (.sdc) ------------------------------------------------------------------------
def build_timing_constraints(self, vns):
sdc = []
# 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)))
# 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)))
# Add additional commands
sdc += self.additional_sdc_commands
# 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): for pin in sorted(pins, key=lambda x: x.duid):
# Better idea ??? # Better idea ???
if (pinname.split("[")[0] == pin.name): if (pinname.split("[")[0] == pin.name):
return pin.direction return pin.direction
return "Unknown" return "Unknown"
# Timing Constraints (.sdc) ------------------------------------------------------------------------ def _create_gpio_instance(self, sig, pins):
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 = "" l = ""
if len(pins) > 1: if len(pins) > 1:
l = ",{},0".format(len(pins) - 1) l = ",{},0".format(len(pins) - 1)
d = get_pin_direction(fragment, platform, sig) d = self.get_pin_direction(sig)
return 'design.create_{d}_gpio("{name}"{len})'.format(d=d, name=sig, len=l) return 'design.create_{d}_gpio("{name}"{len})'.format(d=d, name=sig, len=l)
def _format_constraint(c, signame, fmt_r, fragment, platform): def _format_constraint(self, c, signame, fmt_r):
# IO location constraints # IO location constraints
if isinstance(c, Pins): if isinstance(c, Pins):
tpl = 'design.assign_pkg_pin("{signame}","{pin}")\n' tpl = 'design.assign_pkg_pin("{signame}","{pin}")\n'
@ -125,21 +156,21 @@ def _format_constraint(c, signame, fmt_r, fragment, platform):
tpl = 'design.set_property( "{signame}","{prop}","{val}")\n' tpl = 'design.set_property( "{signame}","{prop}","{val}")\n'
return tpl.format(signame=signame, prop=prop, val=val) return tpl.format(signame=signame, prop=prop, val=val)
def _format_conf_constraint(signame, pin, others, resname, fragment, platform): def _format_conf_constraint(self, signame, pin, others, resname):
fmt_r = "{}:{}".format(*resname[:2]) fmt_r = "{}:{}".format(*resname[:2])
if resname[2] is not None: if resname[2] is not None:
fmt_r += "." + resname[2] fmt_r += "." + resname[2]
fmt_c = [_format_constraint(c, signame, fmt_r, fragment, platform) for c in ([Pins(pin)] + others)] fmt_c = [self._format_constraint(c, signame, fmt_r) for c in ([Pins(pin)] + others)]
return "".join(fmt_c) return "".join(fmt_c)
def _build_iface_gpio(named_sc, named_pc, fragment, platform, excluded_ios): def _build_iface_gpio(self):
conf = [] conf = []
inst = [] inst = []
# GPIO # GPIO
for sig, pins, others, resname in named_sc: for sig, pins, others, resname in self.named_sc:
excluded = False excluded = False
for excluded_io in excluded_ios: for excluded_io in self.excluded_ios:
if isinstance(excluded_io, str): if isinstance(excluded_io, str):
if sig == excluded_io: if sig == excluded_io:
excluded = True excluded = True
@ -148,60 +179,71 @@ def _build_iface_gpio(named_sc, named_pc, fragment, platform, excluded_ios):
excluded = True excluded = True
if excluded: if excluded:
continue continue
inst.append(_create_gpio_instance(fragment, platform, sig, pins)) inst.append(self._create_gpio_instance(sig, pins))
if len(pins) > 1: if len(pins) > 1:
for i, p in enumerate(pins): for i, p in enumerate(pins):
conf.append(_format_conf_constraint("{}[{}]".format(sig, i), p, others, resname, fragment, platform)) conf.append(_format_conf_constraint("{}[{}]".format(sig, i), p, others, resname))
else: else:
conf.append(_format_conf_constraint(sig, pins[0], others, resname, fragment, platform)) conf.append(self._format_conf_constraint(sig, pins[0], others, resname))
if named_pc: if self.named_pc:
conf.append("\n\n".join(named_pc)) conf.append("\n\n".join(self.named_pc))
conf = inst + conf conf = inst + conf
return "\n".join(conf) return "\n".join(conf)
def _build_peri(efinity_path, build_name, device, named_sc, named_pc, fragment, platform, additional_iface_commands, excluded_ios): def build_io_constraints(self):
pythonpath = "" pythonpath = ""
header = platform.toolchain.ifacewriter.header(build_name, device) header = self.ifacewriter.header(self._build_name, self.platform.device)
gen = platform.toolchain.ifacewriter.generate(device) gen = self.ifacewriter.generate(self.platform.device)
#TODO : move this to ifacewriter #TODO : move this to ifacewriter
gpio = _build_iface_gpio(named_sc, named_pc, fragment, platform, excluded_ios) gpio = self._build_iface_gpio()
add = "\n".join(additional_iface_commands) add = "\n".join(self.additional_iface_commands)
footer = platform.toolchain.ifacewriter.footer() footer = self.ifacewriter.footer()
tools.write_to_file("iface.py", header + gen + gpio + add + 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: if tools.subprocess_call_filtered([self.efinity_path + "/bin/python3", "iface.py"], common.colors) != 0:
raise OSError("Error occurred during Efinity peri script execution.") 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 self.platform.iobank_info:
self.ifacewriter.generate_xml_blocks()
# Project configuration (.xml) --------------------------------------------------------------------- # Because the Python API is sometimes bugged, we need to tweak the generated xml
if self.ifacewriter.fix_xml:
self.ifacewriter.fix_xml_values()
def _build_xml(family, device, timing_model, build_name, sources):
# Project configuration (.xml) ---------------------------------------------------------------------
def build_project(self):
now = datetime.datetime.now() now = datetime.datetime.now()
# Create Project. # Create Project.
root = et.Element("efx:project") root = et.Element("efx:project")
root.attrib["xmlns:efx"] = "http://www.efinixinc.com/enf_proj" root.attrib["xmlns:efx"] = "http://www.efinixinc.com/enf_proj"
root.attrib["name"] = build_name root.attrib["name"] = self._build_name
root.attrib["location"] = str(pathlib.Path().resolve()) 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["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')}" root.attrib["last_change_date"] = f"Date : {now.strftime('%Y-%m-%d %H:%M')}"
# Add Device. # Add Device.
device_info = et.SubElement(root, "efx:device_info") device_info = et.SubElement(root, "efx:device_info")
et.SubElement(device_info, "efx:family", name=family) et.SubElement(device_info, "efx:family", name=self.platform.family)
et.SubElement(device_info, "efx:device", name=device) et.SubElement(device_info, "efx:device", name=self.platform.device)
et.SubElement(device_info, "efx:timing_model", name=timing_model) et.SubElement(device_info, "efx:timing_model", name=self.platform.timing_model)
# Add Design Info. # Add Design Info.
design_info = et.SubElement(root, "efx:design_info") design_info = et.SubElement(root, "efx:design_info")
et.SubElement(design_info, "efx:top_module", name=build_name) et.SubElement(design_info, "efx:top_module", name=self._build_name)
# Add Design Sources. # Add Design Sources.
for filename, language, library, *copy in sources: for filename, language, library, *copy in self.platform.sources:
if language is None: if language is None:
continue continue
et.SubElement(design_info, "efx:design_file", { et.SubElement(design_info, "efx:design_file", {
@ -212,7 +254,7 @@ def _build_xml(family, device, timing_model, build_name, sources):
# Add Timing Constraints. # Add Timing Constraints.
constraint_info = et.SubElement(root, "efx:constraint_info") constraint_info = et.SubElement(root, "efx:constraint_info")
et.SubElement(constraint_info, "efx:sdc_file", name=f"{build_name}.sdc") et.SubElement(constraint_info, "efx:sdc_file", name=f"{self._build_name}.sdc")
# Add Misc Info. # Add Misc Info.
misc_info = et.SubElement(root, "efx:misc_info") misc_info = et.SubElement(root, "efx:misc_info")
@ -224,107 +266,19 @@ def _build_xml(family, device, timing_model, build_name, sources):
xml_str = et.tostring(root, "utf-8") xml_str = et.tostring(root, "utf-8")
xml_str = expatbuilder.parseString(xml_str, False) xml_str = expatbuilder.parseString(xml_str, False)
xml_str = xml_str.toprettyxml(indent=" ") xml_str = xml_str.toprettyxml(indent=" ")
tools.write_to_file("{}.xml".format(build_name), xml_str) tools.write_to_file("{}.xml".format(self._build_name), xml_str)
# Efinity Toolchain -------------------------------------------------------------------------------- def build_script(self):
return "" # not used
class EfinityToolchain: def run_script(self, script):
attr_translate = {}
def __init__(self, efinity_path):
self.options = {}
self.clocks = dict()
self.false_paths = set()
self.efinity_path = efinity_path
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):
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)
# 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)
# 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")
# Add Include Paths.
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)
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,
)
# Generate project file (.xml)
_build_xml(
family = platform.family,
device = platform.device,
timing_model = platform.timing_model,
build_name = build_name,
sources = platform.sources
)
# 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
)
# 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:
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. # Synthesis/Mapping.
r = tools.subprocess_call_filtered([self.efinity_path + "/bin/efx_map", r = tools.subprocess_call_filtered([self.efinity_path + "/bin/efx_map",
"--project", f"{build_name}", "--project", f"{self._build_name}",
"--root", f"{build_name}", "--root", f"{self._build_name}",
"--write-efx-verilog", f"outflow/{build_name}.map.v", "--write-efx-verilog", f"outflow/{self._build_name}.map.v",
"--write-premap-module", f"outflow/{build_name}.elab.vdb", "--write-premap-module", f"outflow/{self._build_name}.elab.vdb",
"--binary-db", f"{build_name}.vdb", "--binary-db", f"{self._build_name}.vdb",
"--family", platform.family, "--family", platform.family,
"--device", platform.device, "--device", platform.device,
"--mode", "speed", "--mode", "speed",
@ -342,7 +296,7 @@ class EfinityToolchain:
"--veri_option", "verilog_mode=verilog_2k,vhdl_mode=vhdl_2008", "--veri_option", "verilog_mode=verilog_2k,vhdl_mode=vhdl_2008",
"--work-dir", "work_syn", "--work-dir", "work_syn",
"--output-dir", "outflow", "--output-dir", "outflow",
"--project-xml", f"{build_name}.xml", "--project-xml", f"{self._build_name}.xml",
"--I", "./" "--I", "./"
], common.colors) ], common.colors)
if r != 0: if r != 0:
@ -351,7 +305,7 @@ class EfinityToolchain:
# Place and Route. # Place and Route.
r = tools.subprocess_call_filtered([self.efinity_path + "/bin/python3", r = tools.subprocess_call_filtered([self.efinity_path + "/bin/python3",
self.efinity_path + "/scripts/efx_run_pt.py", self.efinity_path + "/scripts/efx_run_pt.py",
f"{build_name}", f"{self._build_name}",
platform.family, platform.family,
platform.device platform.device
], common.colors) ], common.colors)
@ -359,19 +313,19 @@ class EfinityToolchain:
raise OSError("Error occurred during efx_run_pt execution.") raise OSError("Error occurred during efx_run_pt execution.")
r = tools.subprocess_call_filtered([self.efinity_path + "/bin/efx_pnr", r = tools.subprocess_call_filtered([self.efinity_path + "/bin/efx_pnr",
"--circuit", f"{build_name}", "--circuit", f"{self._build_name}",
"--family", platform.family, "--family", platform.family,
"--device", platform.device, "--device", platform.device,
"--operating_conditions", platform.timing_model, "--operating_conditions", platform.timing_model,
"--pack", "--pack",
"--place", "--place",
"--route", "--route",
"--vdb_file", f"work_syn/{build_name}.vdb", "--vdb_file", f"work_syn/{self._build_name}.vdb",
"--use_vdb_file", "on", "--use_vdb_file", "on",
"--place_file", f"outflow/{build_name}.place", "--place_file", f"outflow/{self._build_name}.place",
"--route_file", f"outflow/{build_name}.route", "--route_file", f"outflow/{self._build_name}.route",
"--sdc_file", f"{build_name}.sdc", "--sdc_file", f"{self._build_name}.sdc",
"--sync_file", f"outflow/{build_name}.interface.csv", "--sync_file", f"outflow/{self._build_name}.interface.csv",
"--seed", "1", "--seed", "1",
"--work_dir", "work_pnr", "--work_dir", "work_pnr",
"--output_dir", "outflow", "--output_dir", "outflow",
@ -383,11 +337,11 @@ class EfinityToolchain:
# Bitstream. # Bitstream.
r = tools.subprocess_call_filtered([self.efinity_path + "/bin/efx_pgm", r = tools.subprocess_call_filtered([self.efinity_path + "/bin/efx_pgm",
"--source", f"work_pnr/{build_name}.lbf", "--source", f"work_pnr/{self._build_name}.lbf",
"--dest", f"{build_name}.hex", "--dest", f"{self._build_name}.hex",
"--device", platform.device, "--device", platform.device,
"--family", platform.family, "--family", platform.family,
"--periph", f"outflow/{build_name}.lpf", "--periph", f"outflow/{self._build_name}.lpf",
"--oscillator_clock_divider", "DIV8", "--oscillator_clock_divider", "DIV8",
"--spi_low_power_mode", "off", "--spi_low_power_mode", "off",
"--io_weak_pullup", "on", "--io_weak_pullup", "on",
@ -398,22 +352,3 @@ class EfinityToolchain:
], common.colors) ], common.colors)
if r != 0: if r != 0:
raise OSError("Error occurred during efx_pgm execution.") raise OSError("Error occurred during efx_pgm execution.")
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))

View File

@ -45,42 +45,6 @@ class LatticeDiamondToolchain(GenericToolchain):
return self._build(platform, fragment, **kwargs) 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 -------------------------------------------------------------------------------------- # Helpers --------------------------------------------------------------------------------------
@classmethod @classmethod

View File

@ -12,43 +12,62 @@ from shutil import which, copyfile
from migen.fhdl.structure import _Fragment from migen.fhdl.structure import _Fragment
from litex.build.generic_toolchain import GenericToolchain
from litex.build.generic_platform import * from litex.build.generic_platform import *
from litex.build import tools from litex.build import tools
# Timing Constraints (.sdc) ------------------------------------------------------------------------ # OSFPGAToolchain -----------------------------------------------------------------------------------
def _build_sdc(clocks, vns, build_name): class OSFPGAToolchain(GenericToolchain):
attr_translate = {}
def __init__(self, toolchain):
self.toolchain = toolchain
self.clocks = dict()
def build(self, platform, fragment, **kwargs):
return self._build(platform, fragment, **kwargs)
# Constraints ----------------------------------------------------------------------------------
def build_io_constraints(self):
return ("", "") # TODO
# Timing Constraints (.sdc) --------------------------------------------------------------------
def build_timing_constraints(self, vns):
sdc = [] sdc = []
for clk, period in sorted(clocks.items(), key=lambda x: x[0].duid): 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)}}}]") 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: with open(f"{build_name}.sdc", "w") as f:
f.write("\n".join(sdc)) f.write("\n".join(sdc))
return (self._build_name + ".sdc", "SDC")
# Script ------------------------------------------------------------------------------------------- # Project --------------------------------------------------------------------------------------
def _build_tcl(name, device, files, build_name, include_paths): def build_project(self):
tcl = [] tcl = []
# Create Design. # Create Design.
tcl.append(f"create_design {build_name}") tcl.append(f"create_design {self._build_name}")
# Set Device. # Set Device.
tcl.append(f"target_device {device.upper()}") tcl.append(f"target_device {self.platform.device.upper()}")
# Add Include Path. # Add Include Path.
tcl.append("add_include_path ./") tcl.append("add_include_path ./")
for include_path in include_paths: for include_path in platform.verilog_include_paths:
tcl.append(f"add_include_path {include_path}") tcl.append(f"add_include_path {include_path}")
# Add Sources. # Add Sources.
for f, typ, lib in files: for f, typ, lib in self.platform.sources:
tcl.append(f"add_design_file {f}") tcl.append(f"add_design_file {f}")
# Set Top Module. # Set Top Module.
tcl.append(f"set_top_module {build_name}") tcl.append(f"set_top_module {self._build_name}")
# Add Timings Constraints. # Add Timings Constraints.
tcl.append(f"add_constraint_file {build_name}.sdc") tcl.append(f"add_constraint_file {self._build_name}.sdc")
# Run. # Run.
tcl.append("synth") tcl.append("synth")
@ -63,60 +82,12 @@ def _build_tcl(name, device, files, build_name, include_paths):
with open("build.tcl", "w") as f: with open("build.tcl", "w") as f:
f.write("\n".join(tcl)) f.write("\n".join(tcl))
# OSFPGAToolchain ----------------------------------------------------------------------------------- # Script ---------------------------------------------------------------------------------------
class OSFPGAToolchain: def build_script(self):
attr_translate = {} return "" # unused
def __init__(self, toolchain): def run_script(self, script):
self.toolchain = toolchain
self.clocks = dict()
def build(self, platform, fragment,
build_dir = "build",
build_name = "top",
run = True,
**kwargs):
# 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)
# Generate constraints file.
# IOs.
# TODO.
# Timings (.sdc)
_build_sdc(
clocks = self.clocks,
vns = v_output.ns,
build_name = build_name,
)
# 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,
)
# Run
if run:
toolchain_sh = self.toolchain toolchain_sh = self.toolchain
if which(toolchain_sh) is None: if which(toolchain_sh) is None:
msg = f"Unable to find {toolchain_sh.upper()} toolchain, please:\n" msg = f"Unable to find {toolchain_sh.upper()} toolchain, please:\n"
@ -125,16 +96,3 @@ class OSFPGAToolchain:
if subprocess.call([toolchain_sh, "--batch", "--script", "build.tcl"]) != 0: if subprocess.call([toolchain_sh, "--batch", "--script", "build.tcl"]) != 0:
raise OSError(f"Error occured during {toolchain_sh.upper()}'s script execution.") raise OSError(f"Error occured during {toolchain_sh.upper()}'s script execution.")
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

View File

@ -10,6 +10,7 @@ import math
from migen.fhdl.structure import _Fragment from migen.fhdl.structure import _Fragment
from litex.build.generic_toolchain import GenericToolchain
from litex.build.generic_platform import * from litex.build.generic_platform import *
from litex.build.xilinx.vivado import _xdc_separator, _build_xdc from litex.build.xilinx.vivado import _xdc_separator, _build_xdc
from litex.build import tools from litex.build import tools
@ -28,7 +29,7 @@ F4CACHEPATH = '.f4cache'
# F4PGAToolchain ------------------------------------------------------------------------------- # F4PGAToolchain -------------------------------------------------------------------------------
# Formerly SymbiflowToolchain, Symbiflow has been renamed to F4PGA ----------------------------- # Formerly SymbiflowToolchain, Symbiflow has been renamed to F4PGA -----------------------------
class F4PGAToolchain: class F4PGAToolchain(GenericToolchain):
attr_translate = { attr_translate = {
"keep": ("dont_touch", "true"), "keep": ("dont_touch", "true"),
"no_retiming": ("dont_touch", "true"), "no_retiming": ("dont_touch", "true"),
@ -40,59 +41,11 @@ class F4PGAToolchain:
} }
def __init__(self): def __init__(self):
self.clocks = dict() super().__init__()
self.false_paths = set()
self._partname = None self._partname = None
self._flow = 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)
def build(self, platform, fragment, def build(self, platform, fragment,
build_dir = "build",
build_name = "top",
run = True,
enable_xpm = False, enable_xpm = False,
**kwargs): **kwargs):
@ -103,37 +56,60 @@ class F4PGAToolchain:
"xc7a200t-sbg484-1" : "xc7a200tsbg484-1", "xc7a200t-sbg484-1" : "xc7a200tsbg484-1",
}.get(platform.device, platform.device) }.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) set_verbosity_level(2)
# Generate design constraints return self._build(platform, fragment, **kwargs)
tools.write_to_file(build_name + ".xdc", _build_xdc(named_sc, named_pc))
flow = self._generate_prj_flow( def build_io_contraints(self):
platform = platform, # Generate design constraints
build_name = build_name 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: print("\nProject status:")
self._flow.print_resolved_dependencies(0)
print("")
def run_script(self, script):
try: try:
flow.execute() flow.execute()
except Exception as e: except Exception as e:
@ -141,19 +117,6 @@ class F4PGAToolchain:
flow.f4cache.save() 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): def add_false_path_constraint(self, platform, from_, to):
# FIXME: false path constraints are currently not supported by the F4PGA toolchain # FIXME: false path constraints are currently not supported by the F4PGA toolchain
return return

View File

@ -17,13 +17,48 @@ from shutil import which
from migen.fhdl.structure import _Fragment from migen.fhdl.structure import _Fragment
from litex.build.generic_toolchain import GenericToolchain
from litex.build.generic_platform import * from litex.build.generic_platform import *
from litex.build import tools from litex.build import tools
from litex.build.xilinx import common from litex.build.xilinx import common
# Constraints (.ucf) ------------------------------------------------------------------------------- # XilinxISEToolchain --------------------------------------------------------------------------------
def _format_constraint(c): class XilinxISEToolchain(GenericToolchain):
attr_translate = {
"keep": ("keep", "true"),
"no_retiming": ("register_balancing", "no"),
"async_reg": None,
"mr_ff": None,
"ars_ff1": None,
"ars_ff2": None,
"no_shreg_extract": ("shreg_extract", "no")
}
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,
mode = "xst",
**kwargs):
self._mode = mode
self._isemode = mode if mode in ["xst", "cpld"] else "edif"
return self._build(platform, fragment, **kwargs)
# Constraints (.ucf) -------------------------------------------------------------------------------
@classmethod
def _format_constraint(cls, c):
if isinstance(c, Pins): if isinstance(c, Pins):
return "LOC=" + c.identifiers[0] return "LOC=" + c.identifiers[0]
elif isinstance(c, IOStandard): elif isinstance(c, IOStandard):
@ -33,10 +68,10 @@ def _format_constraint(c):
elif isinstance(c, Misc): elif isinstance(c, Misc):
return c.misc return c.misc
def _format_ucf(signame, pin, others, resname): def _format_ucf(self, signame, pin, others, resname):
fmt_c = [] fmt_c = []
for c in [Pins(pin)] + others: for c in [Pins(pin)] + others:
fc = _format_constraint(c) fc = self._format_constraint(c)
if fc is not None: if fc is not None:
fmt_c.append(fc) fmt_c.append(fc)
fmt_r = resname[0] + ":" + str(resname[1]) fmt_r = resname[0] + ":" + str(resname[1])
@ -44,25 +79,28 @@ def _format_ucf(signame, pin, others, resname):
fmt_r += "." + resname[2] fmt_r += "." + resname[2]
return "NET \"" + signame + "\" " + " | ".join(fmt_c) + "; # " + fmt_r + "\n" return "NET \"" + signame + "\" " + " | ".join(fmt_c) + "; # " + fmt_r + "\n"
def _build_ucf(named_sc, named_pc): def build_io_constraints(self):
r = "" r = ""
for sig, pins, others, resname in named_sc: for sig, pins, others, resname in self.named_sc:
if len(pins) > 1: if len(pins) > 1:
for i, p in enumerate(pins): for i, p in enumerate(pins):
r += _format_ucf(sig + "(" + str(i) + ")", p, others, resname) r += self._format_ucf(sig + "(" + str(i) + ")", p, others, resname)
else: else:
r += _format_ucf(sig, pins[0], others, resname) r += self._format_ucf(sig, pins[0], others, resname)
if named_pc: if self.named_pc:
r += "\n" + "\n\n".join(named_pc) r += "\n" + "\n\n".join(self.named_pc)
return r tools.write_to_file(self._build_name + ".ucf", r)
return (self._build_name + ".ucf", "UCF")
# Project (.xst) ----------------------------------------------------------------------------------- # Project (.xst) -------------------------------------------------------------------------------
def _build_xst(device, sources, vincpaths, build_name, xst_opt): def build_project(self):
if self.mode not in ["xst", "cpld"]:
return ("", "")
prj_contents = "" prj_contents = ""
for filename, language, library, *copy in sources: for filename, language, library, *copy in self.platform.sources:
prj_contents += language + " " + library + " " + tools.cygpath(filename) + "\n" prj_contents += language + " " + library + " " + tools.cygpath(filename) + "\n"
tools.write_to_file(build_name + ".prj", prj_contents) tools.write_to_file(self._build_name + ".prj", prj_contents)
xst_contents = """run xst_contents = """run
-ifn {build_name}.prj -ifn {build_name}.prj
@ -70,22 +108,23 @@ def _build_xst(device, sources, vincpaths, build_name, xst_opt):
{xst_opt} {xst_opt}
-ofn {build_name}.ngc -ofn {build_name}.ngc
-p {device} -p {device}
""".format(build_name=build_name, xst_opt=xst_opt, device=device) """.format(build_name=self._build_name, xst_opt=self.xst_opt, device=self.platform.device)
if vincpaths: if self.platform.verilog_include_paths:
xst_contents += "-vlgincdir {" xst_contents += "-vlgincdir {"
for path in vincpaths: for path in self.platform.verilog_include_paths:
xst_contents += tools.cygpath(path) + " " xst_contents += tools.cygpath(path) + " "
xst_contents += "}" xst_contents += "}"
tools.write_to_file(build_name + ".xst", xst_contents) tools.write_to_file(self._build_name + ".xst", xst_contents)
# Yosys Run ---------------------------------------------------------------------------------------- # Yosys Run ----------------------------------------------------------------------------------------
def _run_yosys(device, sources, vincpaths, build_name): def _run_yosys(build_name):
device = self.platform.device
ys_contents = "" ys_contents = ""
incflags = "" incflags = ""
for path in vincpaths: for path in platform.verilog_include_paths:
incflags += " -I" + path incflags += " -I" + path
for filename, language, library, *copy in sources: for filename, language, library, *copy in self.platform.sources:
ys_contents += "read_{}{} {}\n".format(language, incflags, filename) ys_contents += "read_{}{} {}\n".format(language, incflags, filename)
family = "" family = ""
@ -97,18 +136,18 @@ def _run_yosys(device, sources, vincpaths, build_name):
raise OSError("Unsupported device") raise OSError("Unsupported device")
ys_contents += """hierarchy -top top ys_contents += """hierarchy -top top
synth_xilinx -top top -family {family} -ise synth_xilinx -top top -family {family} -ise
write_edif -pvector bra {build_name}.edif""".format(build_name=build_name, family=family) write_edif -pvector bra {build_name}.edif""".format(build_name=self._build_name, family=family)
ys_name = build_name + ".ys" ys_name = self._build_name + ".ys"
tools.write_to_file(ys_name, ys_contents) tools.write_to_file(ys_name, ys_contents)
r = subprocess.call(["yosys", ys_name]) r = subprocess.call(["yosys", ys_name])
if r != 0: if r != 0:
raise OSError("Subprocess failed") raise OSError("Subprocess failed")
# ISE Run ------------------------------------------------------------------------------------------ # ISE Run ------------------------------------------------------------------------------------------
def _run_ise(build_name, mode, ngdbuild_opt, toolchain, platform): def build_script(self):
if sys.platform == "win32" or sys.platform == "cygwin": if sys.platform == "win32" or sys.platform == "cygwin":
script_ext = ".bat" script_ext = ".bat"
shell = ["cmd", "/c"] shell = ["cmd", "/c"]
@ -121,7 +160,7 @@ def _run_ise(build_name, mode, ngdbuild_opt, toolchain, platform):
if os.getenv("LITEX_ENV_ISE", False): if os.getenv("LITEX_ENV_ISE", False):
build_script_contents += "source " + os.path.join(os.getenv("LITEX_ENV_ISE"), "settings64.sh\n") build_script_contents += "source " + os.path.join(os.getenv("LITEX_ENV_ISE"), "settings64.sh\n")
fail_stmt = "" fail_stmt = ""
if mode == "edif": if self._isemode == "edif":
ext = "ngo" ext = "ngo"
build_script_contents += """ build_script_contents += """
edif2ngd {build_name}.edif {build_name}.{ext}{fail_stmt} edif2ngd {build_name}.edif {build_name}.{ext}{fail_stmt}
@ -140,7 +179,7 @@ netgen -ofmt verilog -w -sim {build_name}.{ext} {build_name}_synth.v
build_script_contents += """ build_script_contents += """
ngdbuild {ngdbuild_opt} -uc {build_name}.ucf {build_name}.{ext} {build_name}.ngd{fail_stmt} ngdbuild {ngdbuild_opt} -uc {build_name}.ucf {build_name}.{ext} {build_name}.ngd{fail_stmt}
""" """
if mode == "cpld": if self._isemode == "cpld":
build_script_contents += """ build_script_contents += """
cpldfit -ofmt verilog {par_opt} -p {device} {build_name}.ngd{fail_stmt} 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} taengine -f {build_name}.vm6 -detail -iopath -l {build_name}.tim{fail_stmt}
@ -152,13 +191,31 @@ map {map_opt} -o {build_name}_map.ncd {build_name}.ngd {build_name}.pcf{fail_stm
par {par_opt} {build_name}_map.ncd {build_name}.ncd {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} bitgen {bitgen_opt} {build_name}.ncd {build_name}.bit{fail_stmt}
""" """
build_script_contents = build_script_contents.format(build_name=build_name, build_script_contents = build_script_contents.format(build_name=self._build_name,
ngdbuild_opt=ngdbuild_opt, bitgen_opt=toolchain.bitgen_opt, ext=ext, ngdbuild_opt=self.ngdbuild_opt, bitgen_opt=self.bitgen_opt, ext=ext,
par_opt=toolchain.par_opt, map_opt=toolchain.map_opt, par_opt=self.par_opt, map_opt=self.map_opt,
device=platform.device, fail_stmt=fail_stmt) device=self.platform.device, fail_stmt=fail_stmt)
build_script_contents += toolchain.ise_commands.format(build_name=build_name) build_script_contents += self.ise_commands.format(build_name=self._build_name)
build_script_file = "build_" + build_name + script_ext build_script_file = "build_" + self._build_name + script_ext
tools.write_to_file(build_script_file, build_script_contents, force_unix=False) 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] command = shell + [build_script_file]
if which("ise") is None and os.getenv("LITEX_ENV_ISE", False) == False: if which("ise") is None and os.getenv("LITEX_ENV_ISE", False) == False:
@ -171,86 +228,6 @@ bitgen {bitgen_opt} {build_name}.ncd {build_name}.bit{fail_stmt}
if tools.subprocess_call_filtered(command, common.colors) != 0: if tools.subprocess_call_filtered(command, common.colors) != 0:
raise OSError("Error occured during ISE's script execution.") raise OSError("Error occured during ISE's script execution.")
# XilinxISEToolchain --------------------------------------------------------------------------------
class XilinxISEToolchain:
attr_translate = {
"keep": ("keep", "true"),
"no_retiming": ("register_balancing", "no"),
"async_reg": None,
"mr_ff": None,
"ars_ff1": None,
"ars_ff2": None,
"no_shreg_extract": ("shreg_extract", "no")
}
def __init__(self):
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 = ""
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)
# Finalize design
if not isinstance(fragment, _Fragment):
fragment = fragment.get_fragment()
platform.finalize(fragment)
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)
# 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
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"
# Generate design constraints (.ucf)
tools.write_to_file(build_name + ".ucf", _build_ucf(named_sc, named_pc))
# Run ISE
if run:
_run_ise(build_name, isemode, self.ngdbuild_opt, self, platform)
finally:
os.chdir(cwd)
return vns
# ISE is broken and you must use *separate* TNM_NET objects for period # ISE is broken and you must use *separate* TNM_NET objects for period
# constraints and other constraints otherwise it will be unable to trace # constraints and other constraints otherwise it will be unable to trace
# them through clock objects like DCM and PLL objects. # them through clock objects like DCM and PLL objects.

View File

@ -17,6 +17,7 @@ from shutil import which
from migen.fhdl.structure import _Fragment, wrap, Constant from migen.fhdl.structure import _Fragment, wrap, Constant
from migen.fhdl.specials import Instance from migen.fhdl.specials import Instance
from litex.build.generic_toolchain import GenericToolchain
from litex.build.generic_platform import * from litex.build.generic_platform import *
from litex.build.xilinx.vivado import _xdc_separator, _format_xdc, _build_xdc from litex.build.xilinx.vivado import _xdc_separator, _format_xdc, _build_xdc
from litex.build import tools from litex.build import tools
@ -86,7 +87,7 @@ def _run_make():
# YosysNextpnrToolchain ------------------------------------------------------------------------------- # YosysNextpnrToolchain -------------------------------------------------------------------------------
class YosysNextpnrToolchain: class YosysNextpnrToolchain(GenericToolchain):
attr_translate = { attr_translate = {
#"keep": ("dont_touch", "true"), #"keep": ("dont_touch", "true"),
#"no_retiming": ("dont_touch", "true"), #"no_retiming": ("dont_touch", "true"),
@ -98,13 +99,12 @@ class YosysNextpnrToolchain:
} }
def __init__(self): def __init__(self):
self.clocks = dict() super().__init__()
self.false_paths = set()
self.f4pga_device = None self.f4pga_device = None
self.bitstream_device = None self.bitstream_device = None
self._partname = None self._partname = None
def _check_properties(self, platform): def _check_properties(self):
if not self.f4pga_device: if not self.f4pga_device:
try: try:
self.f4pga_device = { self.f4pga_device = {
@ -113,7 +113,7 @@ class YosysNextpnrToolchain:
"xc7a100tcsg324-1" : "xc7a35t", "xc7a100tcsg324-1" : "xc7a35t",
"xc7z010clg400-1" : "xc7z010", "xc7z010clg400-1" : "xc7z010",
"xc7z020clg400-1" : "xc7z020", "xc7z020clg400-1" : "xc7z020",
}[platform.device] }[self.platform.device]
except KeyError: except KeyError:
raise ValueError(f"f4pga_device is not specified") raise ValueError(f"f4pga_device is not specified")
if not self.bitstream_device: if not self.bitstream_device:
@ -123,9 +123,9 @@ class YosysNextpnrToolchain:
self.bitstream_device = { self.bitstream_device = {
"xc7a": "artix7", # xc7a35t, xc7a50t, xc7a100t, xc7a200t "xc7a": "artix7", # xc7a35t, xc7a50t, xc7a100t, xc7a200t
"xc7z": "zynq7", # xc7z010, xc7z020 "xc7z": "zynq7", # xc7z010, xc7z020
}[platform.device[:4]] }[self.platform.device[:4]]
except KeyError: 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 # FIXME: prjxray-db doesn't have xc7a35ticsg324-1L - use closest replacement
self._partname = { self._partname = {
"xc7a35ticsg324-1L" : "xc7a35tcsg324-1", "xc7a35ticsg324-1L" : "xc7a35tcsg324-1",
@ -133,15 +133,15 @@ class YosysNextpnrToolchain:
"xc7a200t-sbg484-1" : "xc7a200tsbg484-1", "xc7a200t-sbg484-1" : "xc7a200tsbg484-1",
"xc7z010clg400-1" : "xc7z010clg400-1", "xc7z010clg400-1" : "xc7z010clg400-1",
"xc7z020clg400-1" : "xc7z020clg400-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 Var = _MakefileGenerator.Var
Rule = _MakefileGenerator.Rule Rule = _MakefileGenerator.Rule
makefile = _MakefileGenerator([ makefile = _MakefileGenerator([
"# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n", "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n",
Var("TOP", build_name), Var("TOP", self._build_name),
Var("PARTNAME", self._partname), Var("PARTNAME", self._partname),
Var("DEVICE", self.f4pga_device), Var("DEVICE", self.f4pga_device),
Var("BITSTREAM_DEVICE", self.bitstream_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("DB_DIR", "/usr/share/nextpnr/prjxray-db"), #FIXME: resolve path
Var("CHIPDB_DIR", "/usr/share/nextpnr/xilinx-chipdb"), #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("MEM_INIT", [f"{name}" for name in os.listdir() if name.endswith(".init")]),
Var("SDC", f"{build_name}.sdc"), Var("SDC", f"{self._build_name}.sdc"),
Var("XDC", f"{build_name}.xdc"), Var("XDC", f"{self._build_name}.xdc"),
Var("ARTIFACTS", [ Var("ARTIFACTS", [
"$(TOP).fasm", "$(TOP).frames", "$(TOP).fasm", "$(TOP).frames",
"*.bit", "*.fasm", "*.json", "*.log", "*.rpt", "*.bit", "*.fasm", "*.json", "*.log", "*.rpt",
@ -182,75 +182,38 @@ class YosysNextpnrToolchain:
]) ])
tools.write_to_file("Makefile", makefile.generate()) tools.write_to_file("Makefile", makefile.generate())
return "Makefile"
def _build_clock_constraints(self, platform): def build_timing_constraints(self, vns):
platform.add_platform_command(_xdc_separator("Clock constraints")) self.platform.add_platform_command(_xdc_separator("Clock constraints"))
#for clk, period in sorted(self.clocks.items(), key=lambda x: x[0].duid): #for clk, period in sorted(self.clocks.items(), key=lambda x: x[0].duid):
# platform.add_platform_command( # platform.add_platform_command(
# "create_clock -period " + str(period) + # "create_clock -period " + str(period) +
# " {clk}", clk=clk) # " {clk}", clk=clk)
pass #clock constraints not supported 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): def _fix_instance(self, instance):
pass pass
def build(self, platform, fragment, def finalize(self):
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)
# toolchain-specific fixes # toolchain-specific fixes
for instance in fragment.specials: for instance in self.fragment.specials:
if isinstance(instance, Instance): if isinstance(instance, Instance):
self._fix_instance(instance) self._fix_instance(instance)
# Generate timing constraints def build(self, platform, fragment,
self._build_clock_constraints(platform) enable_xpm = False,
**kwargs):
# Generate verilog #FIXME
v_output = platform.get_verilog(fragment, name=build_name, **kwargs) self.platform = platform
named_sc, named_pc = platform.resolve_signals(v_output.ns) self._check_properties()
v_file = build_name + ".v"
v_output.write(v_file)
platform.add_source(v_file)
self._generate_makefile( return self._build(platform, fragment, **kwargs)
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
def add_false_path_constraint(self, platform, from_, to): def add_false_path_constraint(self, platform, from_, to):
# FIXME: false path constraints are currently not supported by the F4PGA toolchain # FIXME: false path constraints are currently not supported by the F4PGA toolchain