From 50fdc5ce4122bbae2e4a26ef7f61178f3ec467af Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Fri, 6 Dec 2019 09:29:48 +0100 Subject: [PATCH] build/altera: cleanup/simplify (no functional change) Altera build backend was a bit messy and needed some cleanup to ease future maintenance and new features. --- litex/build/altera/common.py | 42 +++--- litex/build/altera/platform.py | 6 +- litex/build/altera/programmer.py | 11 +- litex/build/altera/quartus.py | 230 ++++++++++++++++--------------- 4 files changed, 157 insertions(+), 132 deletions(-) diff --git a/litex/build/altera/common.py b/litex/build/altera/common.py index d2d56d88..45770984 100644 --- a/litex/build/altera/common.py +++ b/litex/build/altera/common.py @@ -9,15 +9,16 @@ from migen.genlib.resetsync import AsyncResetSynchronizer from migen.fhdl.structure import * +# DifferentialInput -------------------------------------------------------------------------------- class AlteraDifferentialInputImpl(Module): def __init__(self, i_p, i_n, o): self.specials += [ Instance("ALT_INBUF_DIFF", - name="ibuf_diff", - i_i=i_p, - i_ibar=i_n, - o_o=o + name = "ibuf_diff", + i_i = i_p, + i_ibar = i_n, + o_o = o ) ] @@ -27,15 +28,16 @@ class AlteraDifferentialInput: def lower(dr): return AlteraDifferentialInputImpl(dr.i_p, dr.i_n, dr.o) +# DifferentialOutput ------------------------------------------------------------------------------- class AlteraDifferentialOutputImpl(Module): def __init__(self, i, o_p, o_n): self.specials += [ Instance("ALT_OUTBUF_DIFF", - name="obuf_diff", - i_i=i, - o_o=o_p, - o_obar=o_n + name = "obuf_diff", + i_i = i, + o_o = o_p, + o_obar = o_n ) ] @@ -45,20 +47,25 @@ class AlteraDifferentialOutput: def lower(dr): return AlteraDifferentialOutputImpl(dr.i, dr.o_p, dr.o_n) +# AsyncResetSynchronizer --------------------------------------------------------------------------- class AlteraAsyncResetSynchronizerImpl(Module): def __init__(self, cd, async_reset): rst_meta = Signal() self.specials += [ Instance("DFF", - i_d=0, i_clk=cd.clk, - i_clrn=1, i_prn=~async_reset, - o_q=rst_meta + i_d = 0, + i_clk = cd.clk, + i_clrn = 1, + i_prn = ~async_reset, + o_q = rst_meta ), Instance("DFF", - i_d=rst_meta, i_clk=cd.clk, - i_clrn=1, i_prn=~async_reset, - o_q=cd.rst + i_d = rst_meta, + i_clk = cd.clk, + i_clrn = 1, + i_prn = ~async_reset, + o_q = cd.rst ) ] @@ -68,9 +75,10 @@ class AlteraAsyncResetSynchronizer: def lower(dr): return AlteraAsyncResetSynchronizerImpl(dr.cd, dr.async_reset) +# Special Overrides -------------------------------------------------------------------------------- altera_special_overrides = { - DifferentialInput: AlteraDifferentialInput, - DifferentialOutput: AlteraDifferentialOutput, - AsyncResetSynchronizer: AlteraAsyncResetSynchronizer + DifferentialInput: AlteraDifferentialInput, + DifferentialOutput: AlteraDifferentialOutput, + AsyncResetSynchronizer: AlteraAsyncResetSynchronizer, } diff --git a/litex/build/altera/platform.py b/litex/build/altera/platform.py index ef85f47a..a95dcf4b 100644 --- a/litex/build/altera/platform.py +++ b/litex/build/altera/platform.py @@ -7,10 +7,11 @@ import os from litex.build.generic_platform import GenericPlatform from litex.build.altera import common, quartus +# AlteraPlatform ----------------------------------------------------------------------------------- class AlteraPlatform(GenericPlatform): bitstream_ext = ".sof" - create_rbf = True + create_rbf = True def __init__(self, *args, toolchain="quartus", **kwargs): GenericPlatform.__init__(self, *args, **kwargs) @@ -26,8 +27,7 @@ class AlteraPlatform(GenericPlatform): def get_verilog(self, *args, special_overrides=dict(), **kwargs): so = dict(common.altera_special_overrides) so.update(special_overrides) - return GenericPlatform.get_verilog(self, *args, special_overrides=so, - **kwargs) + return GenericPlatform.get_verilog(self, *args, special_overrides=so, **kwargs) def build(self, *args, **kwargs): return self.toolchain.build(self, *args, **kwargs) diff --git a/litex/build/altera/programmer.py b/litex/build/altera/programmer.py index 6ced8848..c389635b 100644 --- a/litex/build/altera/programmer.py +++ b/litex/build/altera/programmer.py @@ -5,15 +5,18 @@ import subprocess from litex.build.generic_programmer import GenericProgrammer +# USBBlaster --------------------------------------------------------------------------------------- class USBBlaster(GenericProgrammer): needs_bitreverse = False def __init__(self, cable_name="USB-Blaster", device_id=1): self.cable_name = cable_name - self.device_id = device_id + self.device_id = device_id def load_bitstream(self, bitstream_file, cable_suffix=""): - subprocess.call(["quartus_pgm", "-m", "jtag", "-c", - "{}{}".format(self.cable_name, cable_suffix), "-o", - "p;{}@{}".format(bitstream_file, self.device_id)]) + subprocess.call(["quartus_pgm", + "-m", "jtag", + "-c", "{}{}".format(self.cable_name, cable_suffix), + "-o", "p;{}@{}".format(bitstream_file, self.device_id) + ]) diff --git a/litex/build/altera/quartus.py b/litex/build/altera/quartus.py index 40f0681a..48a061e2 100644 --- a/litex/build/altera/quartus.py +++ b/litex/build/altera/quartus.py @@ -13,175 +13,189 @@ from migen.fhdl.structure import _Fragment from litex.build.generic_platform import Pins, IOStandard, Misc from litex.build import tools +# IO/Placement Constraints (.qsf) ------------------------------------------------------------------ def _format_constraint(c, signame, fmt_r): + # IO location constraints if isinstance(c, Pins): - return "set_location_assignment -comment \"{name}\" " \ - "-to {signame} Pin_{pin}".format( - signame=signame, - name=fmt_r, - pin=c.identifiers[0]) + tpl = "set_location_assignment -comment \"{name}\" -to {signame} Pin_{pin}" + return tpl.format(signame=signame, name=fmt_r, pin=c.identifiers[0]) + + # IO standard constraints elif isinstance(c, IOStandard): - return "set_instance_assignment -name io_standard " \ - "-comment \"{name}\" \"{std}\" -to {signame}".format( - signame=signame, - name=fmt_r, - std=c.name) + tpl = "set_instance_assignment -name io_standard -comment \"{name}\" \"{std}\" -to {signame}" + return tpl.format(signame=signame, name=fmt_r, std=c.name) + + # Others constraints elif isinstance(c, Misc): if not isinstance(c.misc, str) and len(c.misc) == 2: - return "set_instance_assignment -comment \"{name}\" " \ - "-name {misc[0]} \"{misc[1]}\" -to {signame}".format( - signame=signame, - name=fmt_r, - misc=c.misc) + tpl = "set_instance_assignment -comment \"{name}\" -name {misc[0]} \"{misc[1]}\" -to {signame}" + return tpl.format(signame=signame, name=fmt_r, misc=c.misc) else: - return "set_instance_assignment -comment \"{name}\" " \ - "-name {misc} " \ - "-to {signame}".format( - signame=signame, - name=fmt_r, - misc=c.misc) + tpl = "set_instance_assignment -comment \"{name}\" -name {misc} -to {signame}" + return tpl.format(signame=signame, name=fmt.r, misc=c.misc) - -def _format_qsf(signame, pin, others, resname): +def _format_qsf_constraint(signame, pin, others, resname): fmt_r = "{}:{}".format(*resname[:2]) if resname[2] is not None: fmt_r += "." + resname[2] - - fmt_c = [_format_constraint(c, signame, fmt_r) for c in - ([Pins(pin)] + others)] - + fmt_c = [_format_constraint(c, signame, fmt_r) for c in ([Pins(pin)] + others)] return '\n'.join(fmt_c) - -def _build_qsf(named_sc, named_pc, build_name): - lines = [] +def _build_qsf_constraints(named_sc, named_pc): + qsf = [] for sig, pins, others, resname in named_sc: if len(pins) > 1: for i, p in enumerate(pins): - lines.append( - _format_qsf("{}[{}]".format(sig, i), p, others, resname)) + qsf.append(_format_qsf_constraint("{}[{}]".format(sig, i), p, others, resname)) else: - lines.append(_format_qsf(sig, pins[0], others, resname)) - + qsf.append(_format_qsf_constraint(sig, pins[0], others, resname)) if named_pc: - lines.append("") - lines.append("\n\n".join(named_pc)) - - # Set top level name to "build_name" in .qsf file instead always use "top" name - lines.append("set_global_assignment -name top_level_entity " + build_name) - return "\n".join(lines) + qsf.append("\n\n".join(named_pc)) + return "\n".join(qsf) +# Timing Constraints (.sdc) ------------------------------------------------------------------------ def _build_sdc(clocks, false_paths, vns, build_name): - lines = [] + sdc = [] + # Clock constraints for clk, period in sorted(clocks.items(), key=lambda x: x[0].duid): - lines.append( - "create_clock -name {clk} -period ".format(clk=vns.get_name(clk)) + str(period) + - " [get_ports {{{clk}}}]".format(clk=vns.get_name(clk))) - for from_, to in sorted(false_paths, - key=lambda x: (x[0].duid, x[1].duid)): - lines.append( - "set_false_path " - "-from [get_clocks {{{from_}}}] " - "-to [get_clocks {{{to}}}]".format( - from_=vns.get_name(from_), to=vns.get_name(to))) - tools.write_to_file("{}.sdc".format(build_name), "\n".join(lines)) - - -def _build_files(device, ips, sources, vincpaths, named_sc, named_pc, build_name): - lines = [] - for filename in ips: - lines.append("set_global_assignment -name QSYS_FILE {path} ".format( - path=filename.replace("\\", "/"))) + tpl = "create_clock -name {clk} -period {period} [get_ports {{{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))) + tools.write_to_file("{}.sdc".format(build_name), "\n".join(sdc)) + +# Project (.qsf) ----------------------------------------------------------------------------------- + +def _build_qsf(device, ips, sources, vincpaths, named_sc, named_pc, build_name): + qsf = [] + + # Set device + qsf.append("set_global_assignment -name DEVICE {}".format(device)) + + # Add sources for filename, language, library in sources: - # Enforce use of SystemVerilog since Quartus does not support global parameters in Verilog - if language == "verilog": - language = "systemverilog" - lines.append( - "set_global_assignment -name {lang}_FILE {path} " - "-library {lib}".format( - lang=language.upper(), - path=filename.replace("\\", "/"), - lib=library)) - lines.append("set_global_assignment -name SDC_FILE {}.sdc".format(build_name)) + if language == "verilog": language = "systemverilog" # Enforce use of SystemVerilog + tpl = "set_global_assignment -name {lang}_FILE {path} -library {lib}" + qsf.append(tpl.format(lang=language.upper(), path=filename.replace("\\", "/"), lib=library)) + # Add ips + for filename in ips: + tpl = "set_global_assignment -name QSYS_FILE {filename}" + qsf.append(tpl.replace(filename=filename.replace("\\", "/"))) + + # Add include paths for path in vincpaths: - lines.append("set_global_assignment -name SEARCH_PATH {}".format( - path.replace("\\", "/"))) + qsf.append("set_global_assignment -name SEARCH_PATH {}".format(path.replace("\\", "/"))) - lines.append(_build_qsf(named_sc, named_pc, build_name)) - lines.append("set_global_assignment -name DEVICE {}".format(device)) - tools.write_to_file("{}.qsf".format(build_name), "\n".join(lines)) + # Set top level + qsf.append("set_global_assignment -name top_level_entity " + build_name) + # Add io, placement constraints + qsf.append(_build_qsf_constraints(named_sc, named_pc)) -def _run_quartus(build_name, quartus_path, create_rbf): - if sys.platform == "win32" or sys.platform == "cygwin": - build_script_contents = "REM Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n" - build_script_file = "build_" + build_name + ".bat" - command = build_script_file - else: - build_script_contents = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\nset -e\n" - build_script_file = "build_" + build_name + ".sh" - command = ["bash", build_script_file] - build_script_contents += """ + # Set timing constraints + qsf.append("set_global_assignment -name SDC_FILE {}.sdc".format(build_name)) -set -e + # Generate qsf + tools.write_to_file("{}.qsf".format(build_name), "\n".join(qsf)) -quartus_map --read_settings_files=on --write_settings_files=off {build_name} -c {build_name} +# Script ------------------------------------------------------------------------------------------- + +def _build_script(build_name, quartus_path, create_rbf): + if sys.platform in ["win32", "cygwin"]: + script_contents = "REM Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + script_file = "build_" + build_name + ".bat" + else: + script_contents = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + script_file = "build_" + build_name + ".sh" + script_contents += """ +quartus_map --read_settings_files=on --write_settings_files=off {build_name} -c {build_name} quartus_fit --read_settings_files=off --write_settings_files=off {build_name} -c {build_name} quartus_asm --read_settings_files=off --write_settings_files=off {build_name} -c {build_name} quartus_sta {build_name} -c {build_name}""" if create_rbf: - build_script_contents +=""" + script_contents += """ if [ -f "{build_name}.sof" ] then quartus_cpf -c {build_name}.sof {build_name}.rbf fi - """ - build_script_contents = build_script_contents.format(build_name=build_name) # noqa - tools.write_to_file(build_script_file, - build_script_contents, - force_unix=True) + script_contents = script_contents.format(build_name=build_name) + tools.write_to_file(script_file, script_contents, force_unix=True) - if subprocess.call(command): + return script_file + +def _run_script(script): + if sys.platform in ["win32", "cygwin"]: + shell = ["cmd", "/c"] + else: + shell = ["bash"] + + if subprocess.call(shell + [script]) != 0: raise OSError("Subprocess failed") +# AlteraQuartusToolchain --------------------------------------------------------------------------- class AlteraQuartusToolchain: def __init__(self): - self.clocks = dict() + self.clocks = dict() self.false_paths = set() - def build(self, platform, fragment, build_dir="build", build_name="top", - toolchain_path=None, run=True, **kwargs): + def build(self, platform, fragment, + build_dir = "build", + build_name = "top", + toolchain_path = None, + run = True, + **kwargs): + + # Get default toolchain path (if not specified) if toolchain_path is None: - toolchain_path="/opt/Altera" + toolchain_path = "/opt/Altera" + + # 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) - _build_files(platform.device, - platform.ips, - platform.sources, - platform.verilog_include_paths, - named_sc, - named_pc, - build_name) - - _build_sdc(self.clocks, self.false_paths, v_output.ns, build_name) + + # Generate design timing constraints file (.sdc) + _build_sdc( + clocks = self.clocks, + false_paths = self.false_paths, + vns = v_output.ns, + build_name = build_name) + + # Generate design project and location constraints file (.qsf) + _build_qsf( + device = platform.device, + ips = platform.ips, + sources = platform.sources, + vincpaths = platform.verilog_include_paths, + named_sc = named_sc, + named_pc = named_pc, + build_name = build_name) + + # Generate build script + script = _build_script(build_name, toolchain_path, platform.create_rbf) + + # Run if run: - _run_quartus(build_name, toolchain_path, platform.create_rbf) + _run_script(script) os.chdir(cwd) @@ -190,7 +204,7 @@ class AlteraQuartusToolchain: def add_period_constraint(self, platform, clk, period): if clk in self.clocks: raise ValueError("A period constraint already exists") - period = math.floor(period*1e3)/1e3 # round to lowest picosecond + period = math.floor(period*1e3)/1e3 # Round to lowest picosecond self.clocks[clk] = period def add_false_path_constraint(self, platform, from_, to): -- 2.30.2