diff --git a/README.md b/README.md index d848dde..bd76bc2 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,31 @@ # Vivado HLS Command Line Tool (hlsclt) + A Vivado HLS Command Line Helper Tool. -Current functionality includes flexibly executing the main Vivado HLS build stages and cleaning up generated files. Supports a command line driven development process. +Supports a command line driven development process, which increases the performance of the HLS tool and aids compatibility with source control tools, in order achieve an increase in productivity. + +## Features +- Flexibly execute any of the Vivado HLS build stages +- Open build reports +- Clean up generated files +- View complete project status +- Open Vivado HLS GUI with project loaded ## Requirements -Python 3 - tested with python 3.6.1 +- Python 2 or 3 + - Tested with and 2.7.5 and 3.6.1 +- Vivado HLS + - Tested with Vivado HLS 2017.1 ## Install -Easy installation: ```Shell pip install hlsclt ``` - -Manual installation: -```Shell -git clone https://github.com/benjmarshall/hlsclt.git -sudo cp ./hlsclt/hlsclt/hlsclt.py /usr/local/bin/hlsclt -sudo chmod +x /usr/local.bin/hlsclt -``` +Depends on [Click](https://pypi.python.org/pypi/click) which will be installed automatically by pip. ## Usage -This tool is intended to aid command line driven development process for Vivado HLS. Whilst the tool is designed to be flexible, certain guidelines should be followed. A top level project folder should contain your HLS source files (or folders) and a 'hls_config.py' file which specifies some of the required configuration for a HLS project (device, clock speed etc). +### Quickstart +This tool is intended to aid a command line driven development process for Vivado HLS. Whilst the tool is designed to be flexible, certain guidelines should be followed. A top level project folder should contain your HLS source files (or folders) and a 'hls_config.py' file which specifies some of the required configuration for a HLS project (device, clock speed etc). A recommended directory structure is as follows: @@ -32,42 +37,118 @@ A recommended directory structure is as follows: - testbench.cpp - hls_config.py -An example project structure and hls_config.py can be found in the [examples](hlsclt/examples) directory. +An example project structure and hls_config.py can be found in the [examples](hlsclt/examples) directory. A full guide for setting a config.py can be seen in the [Project Config](#project-configuration) section. -The tool should be invoked from within the project folder, i.e.: +The tool should be invoked from within the project folder, i.e. : ```Shell cd my_project_name -hlsclt -csim +hlsclt build csim ``` The tool will read in the configuration from your 'hls_config.py' file and invoke Vivado HLS to perform the chosen build stages. -All of the tool options can be seen my using the '--help' argument: +All of the tools commands and options can be seen by using the '--help' argument: ``` [ben@localhost]$ hlsclt --help -usage: hlsclt [-h] [-clean] [-keep] [-csim] [-syn] [-cosim | -cosim_debug] [-export_ip | -evaluate_ip] - [-export_dsp | -evaluate_dsp] - -Helper tool for using Vivado HLS through the command line. If no arguments are specified then a default -run is executed which includes C simulation, C synthesis, Cosimulation and export for both Vivado IP -Catalog and System Generator. If any of the run options are specified then only those specified are -performed. - -optional arguments: - -h, --help show this help message and exit - -clean remove all Vivado HLS generated files - -keep keep all previous solution and generate a new one - -csim perform C simulation stage - -syn perform C synthesis stage - -cosim perform cosimulation - -cosim_debug perform cosimulation with debug logging - -export_ip perform export for Vivado IP Catalog - -evaluate_ip perform export for Vivado IP Catalog with build to place and route - -export_dsp perform export for System Generator - -evaluate_dsp perform export for System Generator with build to place and route +Usage: hlsclt [OPTIONS] COMMAND [ARGS]... + + Helper tool for using Vivado HLS through the command line. If no arguments + are specified then a default run is executed which includes C simulation, + C synthesis, Cosimulation and export for both Vivado IP Catalog and System + Generator. If any of the run options are specified then only those + specified are performed. + +Options: + --version Show the version and exit. + --help Show this message and exit. + +Commands: + build Run HLS build stages. + clean Remove generated files. + open_gui Open the Vivado HLS GUI and load the project. + report Open reports. + status Print out the current project status. +``` + +### Nested Commands +The tool is built using the concept of 'nested' commands (like git for example), where the main command 'hlsclt' has a group of subcommands, some of which in turn have subcommands. The 'status' command is a simple example of single level of nesting: + +``` +[ben@localhost]$ hlsclt status +Project Details + Project Name: proj_simple_adder + Number of solutions generated: 1 + Latest Solution Folder: 'proj_simple_adder/solution1' + Language Choice: vhdl +Build Status + C Simulation: Pass + C Synthesis: Not Run + Cosimulation: Not Run + Export: + IP Catalog: Not Run + System Generator: Not Run + Export Evaluation: Not Run +``` + +The build subcommand is slightly more complex than the other top-level commands. Nested subcommands under the build command can be chained in order to perform multiple HLS build stages, each with their own options: + +``` +[ben@localhost]$ hlsclt build csim syn cosim -d +``` + +In this example the tool will launch the HLS process to run a C simulation, followed by C Synthesis, and finally a cosimulation with debugging enabled so that we can view the waveforms of the cosimulation at a later point. + +Each command or subcommand has it's own help option which gives specific information about the command and how to use it. For example the export subcomand: +``` +[ben@localhost]$ hlsclt build export --help +Usage: hlsclt build export [OPTIONS] + + Runs the Vivado HLS export stage. + +Options: + -t, --type [ip|sysgen] Specify an export type, Vivado IP Catalog or System + Generator. Accepts multiple occurrences. [required] + -e, --evaluate Runs Vivado synthesis and place and route for the + generated export. + --help Show this message and exit. +``` + +### Project Configuration +Each Vivado HLS project requires a 'config.py' file in order to use hlsclt. This file contains all of the information required by Vivado HLS and hlsclt to perform build operations for your project. The file uses basic python syntax to specify the configuration in a parsable format. The full list of available configuration options is shown below: + +|Configuration Item | Variable Name | Valid Options | Required | +|-------------------|-----------------------|--------------------------------|----------| +|Project Name |project_name |Any valid directory name |No (Default is name of the containing project folder prepended with 'proj_') | +|Function Name |top_level_function_name|String which match function name|Yes | +|Source Files Dir |src_dir_name |Name of directory where source files are located, relative to the project folder|No (Default is 'src')| +|Testbench Files Dir|tb_dir_name |Name of directory where testbench files are located, relative to the project folder|No (Default is 'tb')| +|Source Files |src_files |A list of source files required, located within the Source Files directory|Yes| +|Testbench Files |tb_files |A list of testbench files required, located within the Testbench Files directory|Yes| +|Device String |part_name |A device string as used by Vivado HLS (see examples)|Yes| +|Clock Period |clock_period |A value in nanoseconds input as a string, e.g. "10"|Yes| +|HDL Language |language |Either "vhdl" or "verilog" |No (Default is "vhdl")| + + +Here is an example file taken from the [simple_adder](hlsclt/examples/simple_adder) example shipped with the tool (note that some of the optional items have been commented out in order to use the defaults): + +```python +# Config file for Simple Adder Vivado HLS project + +#project_name = "optional_project_name_here" +top_level_function_name = "simple_adder" +#src_dir_name = "src" +#tb_dir_name = "tb" +src_files = ["dut.h","dut.cpp"] +tb_files = ["testbench.cpp"] +part_name = "xc7z020clg484-1" +clock_period = "10" +language = "vhdl" ``` ## License See [LICENSE](LICENSE) + +## Bugs/Issues +If you have any issues or find a bug please first search the [open issues](https://github.com/benjmarshall/hlsclt/issues) on github and then submit a new issue ticket. diff --git a/hlsclt/__init__.py b/hlsclt/__init__.py index 9df94e5..143f486 100644 --- a/hlsclt/__init__.py +++ b/hlsclt/__init__.py @@ -1,2 +1 @@ # __init__.py -__version__ = '1.0.0.dev2' diff --git a/hlsclt/_version.py b/hlsclt/_version.py new file mode 100644 index 0000000..56d49e0 --- /dev/null +++ b/hlsclt/_version.py @@ -0,0 +1 @@ +__version__ = '1.0.0.a1' diff --git a/hlsclt/build_commands/__init__.py b/hlsclt/build_commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hlsclt/build_commands/build_commands.py b/hlsclt/build_commands/build_commands.py new file mode 100644 index 0000000..a14150f --- /dev/null +++ b/hlsclt/build_commands/build_commands.py @@ -0,0 +1,183 @@ +# -*- coding: utf-8 -*- +""" Build related subcommands for HLSCLT. + +Copyright (c) 2017 Ben Marshall +""" + +### Imports ### +import click +import os +import subprocess +from hlsclt.helper_funcs import find_solution_num +from hlsclt.report_commands.report_commands import open_report + +### Supporting Functions ### +# Function to generate the 'pre-amble' within the HLS Tcl build script. +def do_start_build_stuff(ctx): + config = ctx.obj.config + solution_num = ctx.obj.solution_num + try: + file = click.open_file("run_hls.tcl","w") + file.write("open_project " + config["project_name"] + "\n") + file.write("set_top " + config["top_level_function_name"] + "\n") + for src_file in config["src_files"]: + file.write("add_files " + config["src_dir_name"] + "/" + src_file + "\n") + for tb_file in config["tb_files"]: + file.write("add_files -tb " + config["tb_dir_name"] + "/" + tb_file + "\n") + if ctx.params['keep']: + file.write("open_solution -reset \"solution" + str(solution_num) + "\"" + "\n") + else: + file.write("open_solution \"solution" + str(solution_num) + "\"" + "\n") + file.write("set_part \{" + config["part_name"] + "\}" + "\n") + file.write("create_clock -period " + config["clock_period"] + " -name default" + "\n") + return file + except OSError: + click.echo("Woah! Couldn't create a Tcl run file in the current folder!") + raise click.Abort() + +# Function to write a default build into the HLS Tcl build script. +def do_default_build(ctx): + config = ctx.obj.config + file = ctx.obj.file + file.write("csim_design -clean" + "\n") + file.write("csynth_design" + "\n") + file.write("cosim_design -O -rtl " + config["language"] + "\n") + file.write("export_design -format ip_catalog" + "\n") + file.write("export_design -format sysgen" + "\n") + +# Function which defines the main actions of the 'csim' command. +def do_csim_stuff(ctx): + file = ctx.obj.file + file.write("csim_design -clean" + "\n") + +# Function which defines the main actions of the 'syn' command. +def do_syn_stuff(ctx): + file = ctx.obj.file + file.write("csynth_design" + "\n") + +# Function to perform a search for existing c synthesis results in a specified hls project and solution. +def check_for_syn_results(proj_name, solution_num, top_level_function_name): + return_val = False + try: + with click.open_file(proj_name + "/solution" + str(solution_num) + "/syn/report/" + top_level_function_name + "_csynth.rpt"): + return_val = True + except OSError: + pass + return return_val + +# Function to check is C synthesis is going to be required but may have been forgorgotten by the user. +def syn_lookahead_check(ctx): + config = ctx.obj.config + solution_num = ctx.obj.solution_num + file = ctx.obj.file + if (not ctx.obj.syn_command_present) and (not check_for_syn_results(config["project_name"], solution_num, config["top_level_function_name"])): + if click.confirm("C Synthesis has not yet been run but is required for the process(es) you have selected.\nWould you like to add it to this run?", default=True): + click.echo("Adding csynth option.") + file.write("csynth_design" + "\n") + else: + click.echo("Ok, watch out for missing synthesis errors!") + +# Function which defines the main actions of the 'cosim' command. +def do_cosim_stuff(ctx,debug): + config = ctx.obj.config + file = ctx.obj.file + if debug: + file.write("cosim_design -rtl " + config["language"] + " -trace_level all" + "\n") + else: + file.write("cosim_design -O -rtl " + config["language"] + "\n") + +# Function which defines the main actions of the 'export' command. +def do_export_stuff(ctx,type,evaluate): + config = ctx.obj.config + file = ctx.obj.file + if evaluate: + if "ip" in type: + file.write("export_design -format ip_catalog -evaluate " + config["language"] + "\n") + if "sysgen" in type: + file.write("export_design -format sysgen -evaluate " + config["language"] + "\n") + else: + if "ip" in type: + file.write("export_design -format ip_catalog" + "\n") + if "sysgen" in type: + file.write("export_design -format sysgen" + "\n") + +# Function which defines the actions that occur after a HLS build. +def do_end_build_stuff(ctx,sub_command_returns,report): + # Check for reporting flag + if report: + if not sub_command_returns: + # Must be on the default run, add all stages manually + sub_command_returns = ['csim','syn','cosim','export'] + for report in sub_command_returns: + open_report(ctx,report) + +### Click Command Definitions ### +# Build group entry point +@click.group(chain=True, invoke_without_command=True, short_help='Run HLS build stages.') +@click.option('-k','--keep', is_flag=True, help='Preserves existing solutions and creates a new one.') +@click.option('-r','--report', is_flag=True, help='Open build reports when finished.') +@click.pass_context +def build(ctx,keep,report): + """Runs the Vivado HLS tool and executes the specified build stages.""" + ctx.obj.solution_num = find_solution_num(ctx) + ctx.obj.file = do_start_build_stuff(ctx) + pass + +# Callback which executes when all specified build subcommands have been finished. +@build.resultcallback() +@click.pass_context +def build_end_callback(ctx,sub_command_returns,keep,report): + # Catch the case where no subcommands have been issued and offer a default build + if not sub_command_returns: + if click.confirm("No build stages specified, would you like to run a default sequence using all the build stages?", abort=True): + do_default_build(ctx) + ctx.obj.file.write("exit" + "\n") + ctx.obj.file.close() + # Call the Vivado HLS process + hls_processs = subprocess.run(["vivado_hls", "-f", "run_hls.tcl"]) + # Check return status of the HLS process. + if hls_processs.returncode < 0: + raise click.Abort() + elif hls_processs.returncode > 0: + click.echo("Warning: HLS Process returned an error, skipping report opening!") + raise click.Abort() + else: + do_end_build_stuff(ctx,sub_command_returns,report) + +# csim subcommand +@build.command('csim') +@click.pass_context +def csim(ctx): + """Runs the Vivado HLS C simulation stage.""" + do_csim_stuff(ctx) + return 'csim' + +# syn subcommand +@build.command('syn') +@click.pass_context +def syn(ctx): + """Runs the Vivado HLS C synthesis stage.""" + do_syn_stuff(ctx) + ctx.obj.syn_command_present = True + return 'syn' + +# cosim subcommand +@build.command('cosim') +@click.option('-d', '--debug', is_flag=True, help='Turns off compile optimisations and enables logging for cosim.') +@click.pass_context +def cosim(ctx,debug): + """Runs the Vivado HLS cosimulation stage.""" + syn_lookahead_check(ctx) + do_cosim_stuff(ctx,debug) + return 'cosim' + +# export subcommand +@build.command('export') +@click.option('-t', '--type', required=True, multiple=True, type=click.Choice(['ip','sysgen']), help='Specify an export type, Vivado IP Catalog or System Generator. Accepts multiple occurences.') +@click.option('-e', '--evaluate', is_flag=True, help='Runs Vivado synthesis and place and route for the generated export.') +@click.pass_context +def export(ctx, type, evaluate): + """Runs the Vivado HLS export stage.""" + syn_lookahead_check(ctx) + do_export_stuff(ctx,type,evaluate) + return 'export' diff --git a/hlsclt/classes.py b/hlsclt/classes.py new file mode 100644 index 0000000..14f8a6f --- /dev/null +++ b/hlsclt/classes.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +""" Class definitions for the HLSCLT Command Line Tool. + +Copyright (c) 2017 Ben Marshall +""" + +# Generic error class +class Error(Exception): + """Base class for exceptions in this module.""" + pass + +# Specific error class for local config file errors +class ConfigError(Error): + """Exception raised for options not defined in config. + + Attributes: + message -- explanation of the error + """ + + def __init__(self, message): + self.message = message + +# Class to hold application specific info within the Click context. +class hlsclt_internal_object(object): + def __init__(self, config={}, solution_num=1, file=None, syn_command_present=False): + self.config = config + self.solution_num = solution_num + self.file=file + self.syn_command_present = syn_command_present diff --git a/hlsclt/clean_commands/__init__.py b/hlsclt/clean_commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hlsclt/clean_commands/clean_commands.py b/hlsclt/clean_commands/clean_commands.py new file mode 100644 index 0000000..e584687 --- /dev/null +++ b/hlsclt/clean_commands/clean_commands.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +""" Clean up subcommands for HLSCLT. + +Copyright (c) 2017 Ben Marshall +""" + +### Imports ### +import click +import shutil +import os + +### Supporting Functions### +# Callback function used to exit the program on a negative user prompt response +def abort_if_false(ctx, param, value): + if not value: + ctx.abort() + +# Function to safely handle file deletions and return status +def try_delete(item): + try: + shutil.rmtree(item) + except OSError: + try: + os.remove(item) + except OSError: + return 1 + else: + return 0 + else: + return 0 + +# Funtion to remove generated files +def clean_up_generated_files(obj): + config = obj.config + if try_delete(config["project_name"]) + try_delete("run_hls.tcl") + try_delete("vivado_hls.log") == 3: + click.echo("Warning: Nothing to remove!") + else: + click.echo("Cleaned up generated files.") + +### Click Command Definitions ### +# Clean Command +@click.command('clean',short_help='Remove generated files.') +@click.option('--yes', is_flag=True, callback=abort_if_false, + expose_value=False, + prompt='Are you sure you want to remove all generated files?', + help='Force quiet removal.') +@click.pass_obj +def clean(obj): + """Removes all Vivado HLS generated files and the generated Tcl build script.""" + clean_up_generated_files(obj) diff --git a/hlsclt/examples/simple_adder/hls_config.py b/hlsclt/examples/simple_adder/hls_config.py index 864e4dc..25bb97b 100644 --- a/hlsclt/examples/simple_adder/hls_config.py +++ b/hlsclt/examples/simple_adder/hls_config.py @@ -8,4 +8,4 @@ tb_files = ["testbench.cpp"] part_name = "xc7z020clg484-1" clock_period = "10" -#language = ["vhdl", "verilog"] +language = "vhdl" diff --git a/hlsclt/examples/simple_adder/src/dut.cpp b/hlsclt/examples/simple_adder/src/dut.cpp index 9db18ae..676a387 100755 --- a/hlsclt/examples/simple_adder/src/dut.cpp +++ b/hlsclt/examples/simple_adder/src/dut.cpp @@ -1,4 +1,5 @@ int simple_adder(int a, int b) { + #pragma HLS PIPELINE int c; c = a + b; return c; diff --git a/hlsclt/helper_funcs.py b/hlsclt/helper_funcs.py new file mode 100644 index 0000000..2a98e4d --- /dev/null +++ b/hlsclt/helper_funcs.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +""" Helper functions for the HLSCLT Command Line Tool. + +Copyright (c) 2017 Ben Marshall +""" + +### Imports ### +import click +import os +import imp +from glob import glob + +### Function Definitions ### +# Function to generate the default config dicttionary +def generate_default_config(): + config = { + "project_name" : "proj_" + os.path.relpath(".",".."), + "top_level_function_name" : "", + "src_dir_name" : "src", + "tb_dir_name" : "tb", + "src_files" : "", + "tb_files" : "", + "part_name" : "", + "clock_period" : "", + "language" : "vhdl", + } + return config + +# Function to read in the config from a local file and generate a config structure. +def get_vars_from_file(filename): + try: + with click.open_file(filename) as f: + config = imp.load_source('config', '', f) + return config + except OSError: + click.echo("Error: No hls_config.py found, please create a config file for your project. For an example config file please see the 'examples' folder within the hlsclt install directory.") + raise click.Abort() + +# Funtion to parse a loaded config structure and overwrite the config dictionary defaults. +def parse_config_vars(config_loaded, config, errors): + config_loaded_dict = dict((name, getattr(config_loaded, name)) for name in dir(config_loaded) if not name.startswith('__')) + config_loaded_set = set(config_loaded_dict) + config_set = set(config) + options_defined = config_loaded_set.intersection(config_set) + for name in config: + if str(name) in options_defined: + config[name] = config_loaded_dict[name] + try: + if not config[name]: + raise ConfigError("Error: " + name + " is not defined in config file. No default exists, please define a value in the config file.") + except ConfigError as err: + errors.append(err) + continue + +# Function to find the highest solution number within a HLS project. +def find_solution_num(ctx): + config = ctx.obj.config + # Seach for solution folders + paths = glob(config["project_name"] + "/solution*/") + solution_num = len(paths) + # First solution is always 1. + if solution_num == 0: + solution_num = 1; + # If keep argument is specified we are starting a new solution. + try: + if ctx.params["keep"]: + solution_num = solution_num + 1 + except KeyError: + pass + return solution_num diff --git a/hlsclt/hlsclt.py b/hlsclt/hlsclt.py index f0896cb..e619290 100755 --- a/hlsclt/hlsclt.py +++ b/hlsclt/hlsclt.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ A Vivado HLS Command Line Helper tool @@ -6,129 +5,23 @@ """ ### Imports ### +import click +from ._version import __version__ import os -import sys -import shutil -import argparse -from glob import glob -import contextlib -from distutils.util import strtobool - -### Class definitions ### -class Error(Exception): - """Base class for exceptions in this module.""" - pass - -class ConfigError(Error): - """Exception raised for options not defined in config. - - Attributes: - message -- explanation of the error - """ - - def __init__(self, message): - self.message = message - -### Support Functions ### -def try_delete(item): - try: - shutil.rmtree(item) - except OSError: - try: - os.remove(item) - except OSError: - return 1 - else: - return 0 - else: - return 0 - -def get_vars_from_file(filename): - import imp - try: - with open(filename) as f: - config = imp.load_source('config', '', f) - return config - except OSError: - print("Error: No hls_config.py found, please create a config file for your project. For an example config file please see the 'examples' folder within the hlsclt install directory.") - sys.exit() - -def parse_config_vars(config_loaded, config, errors): - config_loaded_dict = dict((name, getattr(config_loaded, name)) for name in dir(config_loaded) if not name.startswith('__')) - config_loaded_set = set(config_loaded_dict) - config_set = set(config) - options_defined = config_loaded_set.intersection(config_set) - for name in config: - if str(name) in options_defined: - config[name] = config_loaded_dict[name] - try: - if not config[name]: - raise ConfigError("Error: " + name + " is not defined in config file. No default exists, please define a value in the config file.") - except ConfigError as err: - errors.append(err) - continue - -def just_loop_on(input): - if isinstance(input, str): - yield input - else: - try: - for item in input: - yield item - except TypeError: - yield input - -def check_for_syn_results(proj_name, solution_num, top_level_function_name): - return_val = False - try: - with open(proj_name + "/solution" + str(solution_num) + "/syn/report/" + top_level_function_name + "_csynth.rpt"): - return_val = True - except OSError: - pass - return return_val - -def prompt(query): - sys.stdout.write('%s [y/n]: ' % query) - val = input() - try: - ret = strtobool(val) - except ValueError: - sys.stdout.write('Please answer with a y/n\n') - return prompt(query) - return ret - -def main(): - # Set up default config dictionary - config = { - "project_name" : "proj_" + os.path.relpath(".",".."), - "top_level_function_name" : "", - "src_dir_name" : "src", - "tb_dir_name" : "tb", - "src_files" : "", - "tb_files" : "", - "part_name" : "", - "clock_period" : "", - "language" : "vhdl", - } - - # Parse command line arguments - parser = argparse.ArgumentParser(description="Helper tool for using Vivado HLS through the command line. If no arguments are specified then a default run is executed which includes C simulation, C synthesis, Cosimulation and export for both Vivado IP Catalog and System Generator. If any of the run options are specified then only those specified are performed.") - parser.add_argument("-clean", help="remove all Vivado HLS generated files", action="store_true") - parser.add_argument("-keep", help="keep all previous solution and generate a new one", action="store_true") - parser.add_argument("-csim", help="perform C simulation stage", action="store_true") - parser.add_argument("-syn", help="perform C synthesis stage", action="store_true") - cosim_group = parser.add_mutually_exclusive_group() - cosim_group.add_argument("-cosim", help="perform cosimulation", action="store_true") - cosim_group.add_argument("-cosim_debug", help="perform cosimulation with debug logging", action="store_true") - export_ip_group = parser.add_mutually_exclusive_group() - export_ip_group.add_argument("-export_ip", help="perform export for Vivado IP Catalog", action="store_true") - export_ip_group.add_argument("-evaluate_ip", help="perform export for Vivado IP Catalog with build to place and route", action="store_true") - export_dsp_group = parser.add_mutually_exclusive_group() - export_dsp_group.add_argument("-export_dsp", help="perform export for System Generator", action="store_true") - export_dsp_group.add_argument("-evaluate_dsp", help="perform export for System Generator with build to place and route", action="store_true") - args = parser.parse_args() - - # Load project specifics from local config file and add to config dict +from .classes import * +from .helper_funcs import * +from .clean_commands import clean_commands +from .build_commands import build_commands +from .report_commands import report_commands + +### Main Click Entry Point ### +@click.group() +@click.version_option(version=__version__) +@click.pass_context +def cli(ctx): + """Helper tool for using Vivado HLS through the command line. If no arguments are specified then a default run is executed which includes C simulation, C synthesis, Cosimulation and export for both Vivado IP Catalog and System Generator. If any of the run options are specified then only those specified are performed.""" + # Generate a default config dict and then load in the local config file. + config = generate_default_config(); config_loaded = get_vars_from_file('hls_config.py') errors = [] parse_config_vars(config_loaded, config, errors) @@ -136,81 +29,15 @@ def main(): for err in errors: print(err) print("Config Errors, exiting...") - sys.exit() - - # Check for clean argument - if args.clean: - if len(sys.argv) > 2: - print("Warning: The 'Clean' option is exclusive. All other arguments will be ignored.") - if try_delete(config["project_name"]) + try_delete("run_hls.tcl") + try_delete("vivado_hls.log") == 3: - print("Warning: Nothing to remove!") - else: - print("Cleaned up generated files.") - sys.exit() - - # Find solution_num - paths = glob(config["project_name"] + "/solution*/") - solution_num = len(paths) - if solution_num == 0: - solution_num = 1; - elif args.keep: - solution_num = solution_num + 1 - - # Write out TCL file - file = open("run_hls.tcl","w") - file.write("open_project " + config["project_name"] + "\n") - file.write("set_top " + config["top_level_function_name"] + "\n") - for src_file in config["src_files"]: - file.write("add_files " + config["src_dir_name"] + "/" + src_file + "\n") - for tb_file in config["tb_files"]: - file.write("add_files -tb " + config["tb_dir_name"] + "/" + tb_file + "\n") - if args.keep: - file.write("open_solution -reset \"solution" + str(solution_num) + "\"" + "\n") - else: - file.write("open_solution \"solution" + str(solution_num) + "\"" + "\n") - file.write("set_part \{" + config["part_name"] + "\}" + "\n") - file.write("create_clock -period " + config["clock_period"] + " -name default" + "\n") - - if not(args.csim or args.syn or args.cosim or args.cosim_debug or args.export_ip or args.export_dsp or args.evaluate_ip or args.evaluate_dsp): - file.write("csim_design -clean" + "\n") - file.write("csynth_design" + "\n") - file.write("cosim_design -O -rtl " + config["language"] + "\n") - file.write("export_design -format ip_catalog" + "\n") - file.write("export_design -format sysgen" + "\n") - file.write("exit" + "\n") - else: - if args.csim: - file.write("csim_design -clean" + "\n") - if args.syn: - file.write("csynth_design" + "\n") - # Check for arguments which will require csynth where syn has not been passed as an argument - if (not args.syn) and (args.cosim or args.cosim_debug or args.export_ip or args.export_dsp or args.evaluate_ip or args.evaluate_dsp): - if not check_for_syn_results(config["project_name"], solution_num, config["top_level_function_name"]): - if prompt("C Synthesis has not yet been run but is required for the process(es) you have selected.\nWould you like to add it to this run?"): - print("Adding csynth option.") - file.write("csynth_design" + "\n") - else: - print("Ok, watch out for missing synthesis errors!") - if args.cosim: - for language in just_loop_on(config["language"]): - file.write("cosim_design -O -rtl " + language + "\n") - if args.cosim_debug: - for language in just_loop_on(config["language"]): - file.write("cosim_design -rtl " + language + " -trace_level all" + "\n") - if args.export_dsp: - file.write("export_design -format ip_catalog" + "\n") - if args.export_ip: - file.write("export_design -format sysgen" + "\n") - if args.evaluate_dsp: - for language in just_loop_on(config["language"]): - file.write("export_design -format ip_catalog -evaluate " + language + "\n") - if args.evaluate_ip: - for language in just_loop_on(config["language"]): - file.write("export_design -format sysgen -evaluate " + language + "\n") - file.write("exit") - file.close() - - # Call the Vivado HLS process - os.system("vivado_hls -f run_hls.tcl") + raise click.Abort() + # Store the loaded config in an object within the Click context so it is available to all commands. + obj = hlsclt_internal_object(config) + ctx.obj = obj + pass -if __name__ == "__main__": main() +# Add Click Sub Commands +cli.add_command(clean_commands.clean) +cli.add_command(build_commands.build) +cli.add_command(report_commands.report) +cli.add_command(report_commands.open_gui) +cli.add_command(report_commands.status) diff --git a/hlsclt/report_commands/__init__.py b/hlsclt/report_commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hlsclt/report_commands/report_commands.py b/hlsclt/report_commands/report_commands.py new file mode 100644 index 0000000..54bc652 --- /dev/null +++ b/hlsclt/report_commands/report_commands.py @@ -0,0 +1,141 @@ +# -*- coding: utf-8 -*- +""" Report subcommands for HLSCLT. + +Copyright (c) 2017 Ben Marshall +""" + +### Imports ### +import click +import os +import subprocess +from glob import glob +from hlsclt.helper_funcs import find_solution_num + +### Supporting Functions ### +# Function to check if project exists +def check_for_project(ctx): + config = ctx.obj.config + if not glob(config["project_name"]): + click.echo("Error: Can't find a project folder have you run a build process yet?") + raise click.Abort() + +# Function for opening reports. +def open_report(ctx,report): + config = ctx.obj.config + solution_num = ctx.obj.solution_num + report_files = [] + if report == 'csim': + report_files.append(config["project_name"] + "/solution" + str(solution_num) + "/csim/report/" + config["top_level_function_name"] + "_csim.log") + elif report == 'syn': + report_files.append(config["project_name"] + "/solution" + str(solution_num) + "/syn/report/" + config["top_level_function_name"] + "_csynth.rpt") + elif report == 'cosim': + report_files.append(config["project_name"] + "/solution" + str(solution_num) + "/sim/report/" + config["top_level_function_name"] + "_cosim.rpt") + report_files.append(config["project_name"] + "/solution" + str(solution_num) + "/sim/report/" + config["language"] + "/" + config["top_level_function_name"] + ".log") + elif report == 'export': + report_files.append(config["project_name"] + "/solution" + str(solution_num) + "/impl/report/" + config["language"] + "/" + config["top_level_function_name"] + "_export.rpt") + for file in report_files: + return_val = os.system('xdg-open ' + file + ' >/dev/null 2>&1') + if return_val != 0: + click.echo("Error: Looks like the " + report + " report doesn't exist for project: " + config["project_name"] + ", solution number: " + str(solution_num) + ". Make sure you have run that build stage.") + +# Function for opening the HLS GUI +def open_project_in_gui(ctx): + config = ctx.obj.config + hls_process = subprocess.Popen(["vivado_hls", "-p", config["project_name"]]) + +# Function for gathering the project status +def gather_project_status(ctx): + config = ctx.obj.config + solution_num = ctx.obj.solution_num + project_status = [] + # Pull details from csim report + try: + with click.open_file(config["project_name"] + "/solution" + str(solution_num) + "/csim/report/" + config["top_level_function_name"] + "_csim.log","r") as f: + # Pass/Fail info is always in the second last line of the csim report + status_line = f.readlines()[-2] + if "0 errors" in status_line.lower(): + project_status.append("csim_pass") + elif "fail" in status_line.lower(): + project_status.append("csim_fail") + else: + project_status.append("csim_done") + f.close() + except OSError: + pass + # Pull setails from csynth report + if os.path.isfile(config["project_name"] + "/solution" + str(solution_num) + "/syn/report/" + config["top_level_function_name"] + "_csynth.rpt"): + project_status.append('syn_done') + # Pull details from cosim report + try: + with click.open_file(config["project_name"] + "/solution" + str(solution_num) + "/sim/report/" + config["top_level_function_name"] + "_cosim.rpt","r") as f: + # search through cosim report to find out pass/fail status for each language + for line in f: + if config["language"] in line.lower(): + if "pass" in line.lower(): + project_status.append('cosim_pass') + elif "fail" in line.lower(): + project_status.append('cosim_fail') + project_status.append('cosim_done') + f.close() + except OSError: + pass + # Pull details from implementation directory, first the presence of an export... + if os.path.isdir(config["project_name"] + "/solution" + str(solution_num) + "/impl/ip"): + project_status.append('export_ip_done') + if os.path.isdir(config["project_name"] + "/solution" + str(solution_num) + "/impl/sysgen"): + project_status.append('export_sysgen_done') + # ... then the presence of a Vivado evaluate run + if os.path.isfile(config["project_name"] + "/solution" + str(solution_num) + "/impl/report/" + config["language"] + "/" + config["top_level_function_name"] + "_export.rpt"): + project_status.append('evaluate_done') + return project_status + +# Function for printing out the project status +def print_project_status(ctx): + config = ctx.obj.config + solution_num = ctx.obj.solution_num + project_status = gather_project_status(ctx) + # Print out a 'pretty' message showing project status, first up some project details + click.secho("Project Details", bold=True) + click.echo(" Project Name: " + config["project_name"]) + click.echo(" Number of solutions generated: " + str(solution_num)) + click.echo(" Latest Solution Folder: '" + config["project_name"] + "/solution" + str(solution_num) + "'") + click.echo(" Language Choice: " + config["language"]) + # And now details about what builds have been run/are passing. + # This section uses lots (too many!) 'conditional expressions' to embed formatting into the output. + click.secho("Build Status", bold=True) + click.echo(" C Simulation: " + (click.style("Pass", fg='green') if "csim_pass" in project_status else (click.style("Fail", fg='red') if "csim_fail" in project_status else (click.style("Run (Can't get status)", fg='yellow') if "csim_done" in project_status else click.style("Not Run", fg='yellow'))))) + click.echo(" C Synthesis: " + (click.style("Run", fg='green') if "syn_done" in project_status else click.style("Not Run", fg='yellow'))) + click.echo(" Cosimulation: " + (click.style("Pass", fg='green') if "cosim_pass" in project_status else (click.style("Fail", fg='red') if "cosim_fail" in project_status else (click.style("Run (Can't get status)", fg='yellow') if "cosim_done" in project_status else click.style("Not Run", fg='yellow'))))) + click.echo(" Export:" ) + click.echo(" IP Catalog: " + (click.style("Run", fg='green') if "export_ip_done" in project_status else click.style("Not Run", fg='yellow'))) + click.echo(" System Generator: " + (click.style("Run", fg='green') if "export_sysgen_done" in project_status else click.style("Not Run", fg='yellow'))) + click.echo(" Export Evaluation: " + (click.style("Run", fg='green') if "evaluate_done" in project_status else click.style("Not Run", fg='yellow'))) + +### Click Command Definitions ### +# Report Command +@click.command('report', short_help='Open reports.') +@click.option('-s', '--stage', required=True, multiple=True, + type=click.Choice(['csim','syn','cosim','export']), + help='Which build stage to open the report for. Multiple occurences accepted') +@click.pass_context +def report(ctx,stage): + """Opens the Vivado HLS report for the chosen build stages.""" + check_for_project(ctx) + ctx.obj.solution_num = find_solution_num(ctx) + for report in stage: + open_report(ctx,report) + +@click.command('open_gui', short_help='Open the Vivado HLS GUI and load the project.') +@click.pass_context +def open_gui(ctx): + """Opens the Vivado HLS GUI and loads the project.""" + check_for_project(ctx) + open_project_in_gui(ctx) + +@click.command('status', short_help='Print out the current project status.') +@click.pass_context +def status(ctx): + """Prints out a message detailing the current project status.""" + check_for_project(ctx) + ctx.obj.solution_num = find_solution_num(ctx) + print_project_status(ctx) diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..3c6e79c --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[bdist_wheel] +universal=1 diff --git a/setup.py b/setup.py index 5095365..0b0e64d 100644 --- a/setup.py +++ b/setup.py @@ -11,10 +11,15 @@ with open(path.join(here, 'README.md'), encoding='utf-8') as f: long_description = f.read() +# Get the version number +version = {} +with open("hlsclt/_version.py") as fp: + exec(fp.read(), version) + setup( name='hlsclt', - version='1.0.0.dev2', + version=version['__version__'], description='A Vivado HLS Command Line Helper Tool', long_description=long_description, @@ -34,17 +39,18 @@ 'Intended Audience :: Developers', 'Topic :: Software Development :: Build Tools', 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python :: 3.6' + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 2.7' ], keywords='xilinx vivado development', packages=find_packages(), - install_requires=[], + install_requires=['Click'], entry_points = { - 'console_scripts': ['hlsclt=hlsclt.hlsclt:main'] + 'console_scripts': ['hlsclt=hlsclt.hlsclt:cli'] }, include_package_data=True,