diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index bd44b99..6068590 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -27,4 +27,6 @@ jobs:
pylint -s n openflow
git diff --check --cached
- name: Test
- run: pytest test
+ run: |
+ pytest test
+ make -C examples/configure
diff --git a/Makefile b/Makefile
index 846cb1b..b1ea078 100644
--- a/Makefile
+++ b/Makefile
@@ -1,10 +1,11 @@
#!/usr/bin/make
check:
- pycodestyle openflow
- pylint -s n openflow
+ pycodestyle openflow examples
+ pylint -s n openflow examples
git diff --check --cached
pytest test
+ make -C examples/configure
clean:
py3clean .
diff --git a/README.md b/README.md
index 5244613..336a85a 100644
--- a/README.md
+++ b/README.md
@@ -13,3 +13,33 @@ Currently, it is based on `GHDL`, `Yosys`, `ghdl-yosys-plugin`, `nextpnr`,
> **NOTE:** it started as part of [PyFPGA](https://github.com/PyFPGA/pyfpga)
> and will be used to solves the `openflow` **tool**.
+
+## Installation
+
+Openflow requires Python `>=3.6`. For now, it's only available as a git repository
+hosted on GitHub. It can be installed with pip:
+
+```
+pip install 'git+https://github.com/PyFPGA/pyfpga#egg=pyfpga'
+```
+
+> On GNU/Linux, installing pip packages on the system requires `sudo`.
+> Alternatively, use `--local` for installing PyFPGA in your HOME.
+
+You can get a copy of the repository either through git clone or downloading a
+tarball/zipfile:
+
+```
+git clone https://github.com/PyFPGA/openflow.git
+cd openflow
+```
+
+Then, use pip from the root of the repo:
+
+```
+pip install -e .
+```
+
+> With `-e` (`--editable`) your application is installed into site-packages via
+> a kind of symlink. That allows pulling changes through git or changing the
+> branch, without the need to reinstall the package.
diff --git a/examples/configure/Makefile b/examples/configure/Makefile
new file mode 100644
index 0000000..8ede04d
--- /dev/null
+++ b/examples/configure/Makefile
@@ -0,0 +1,8 @@
+EXAMPLES=$(wildcard *.py)
+
+.PHONY: $(EXAMPLES)
+
+all: $(EXAMPLES)
+
+$(EXAMPLES):
+ python3 $@
diff --git a/examples/configure/defaults.py b/examples/configure/defaults.py
new file mode 100644
index 0000000..b2d5ce4
--- /dev/null
+++ b/examples/configure/defaults.py
@@ -0,0 +1,8 @@
+"""Example about default values."""
+
+from openflow.configure import ConfigureTools
+
+cfg = ConfigureTools()
+
+for tool in cfg.get_tools():
+ print(cfg.get_command(tool))
diff --git a/examples/configure/file.py b/examples/configure/file.py
new file mode 100644
index 0000000..5eae3ac
--- /dev/null
+++ b/examples/configure/file.py
@@ -0,0 +1,8 @@
+"""Example about to specify a file."""
+
+from openflow.configure import ConfigureTools
+
+cfg = ConfigureTools('file.yml')
+
+for tool in cfg.get_tools():
+ print(cfg.get_command(tool))
diff --git a/examples/configure/file.yml b/examples/configure/file.yml
new file mode 100644
index 0000000..0452b32
--- /dev/null
+++ b/examples/configure/file.yml
@@ -0,0 +1,15 @@
+engine:
+ name: example
+ volumes:
+ - "path1:path2"
+ - "path3:path4"
+ work: .
+ options: global-option
+tools:
+ example1:
+ name: alt-example1
+ container: hdlc/example1
+ example2:
+ name: alt-example2
+ container: hdlc/example2
+ options: local-option
diff --git a/examples/configure/methods.py b/examples/configure/methods.py
new file mode 100644
index 0000000..a359144
--- /dev/null
+++ b/examples/configure/methods.py
@@ -0,0 +1,36 @@
+"""Example about to use the different methods."""
+
+from openflow.configure import ConfigureTools
+
+cfg = ConfigureTools()
+
+print('* Defaults for GHDL:')
+print(cfg.get_command('ghdl'))
+
+print('* Setting a different engine:')
+cfg.set_engine('podman')
+print(cfg.get_command('ghdl'))
+
+print('* Setting different volumes:')
+cfg.set_volumes(['v1:v1', 'v2:v2'])
+print(cfg.get_command('ghdl'))
+
+print('* Setting a different work:')
+cfg.set_work('/tmp')
+print(cfg.get_command('ghdl'))
+
+print('* Setting a global options:')
+cfg.set_global_options('--global option')
+print(cfg.get_command('ghdl'))
+
+print('* Setting a new container:')
+cfg.set_container('ghdl', 'alt-ghdl-container')
+print(cfg.get_command('ghdl'))
+
+print('* Setting a new tool name:')
+cfg.set_name('ghdl', 'alt-ghdl-name')
+print(cfg.get_command('ghdl'))
+
+print('* Setting a local options:')
+cfg.set_local_options('ghdl', '--local option')
+print(cfg.get_command('ghdl'))
diff --git a/openflow/__init__.py b/openflow/__init__.py
index 9ba9573..7111003 100644
--- a/openflow/__init__.py
+++ b/openflow/__init__.py
@@ -3,3 +3,4 @@
__version__ = '0.1.0'
from openflow.openflow import Openflow
+from openflow.configure import ConfigureTools
diff --git a/openflow/configure.py b/openflow/configure.py
new file mode 100644
index 0000000..4ebea8f
--- /dev/null
+++ b/openflow/configure.py
@@ -0,0 +1,105 @@
+#
+# Copyright (C) 2020-2021 Rodrigo A. Melo
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+
+"""openflow.configure
+
+A Class to configure the OCI engine, the containers and the name of the
+underlying FOSS tools.
+"""
+
+
+import os
+from yaml import safe_load, dump
+
+
+class ConfigureTools:
+ """Configure Tools."""
+
+ def __init__(self, filename='.openflow.yml'):
+ """Class constructor."""
+ userfile = os.getenv('OPENFLOW_FILE')
+ homefile = os.path.join(os.path.expanduser('~'), filename)
+ projfile = os.path.join(os.path.dirname(__file__), 'configure.yml')
+ if userfile is not None and os.path.exists(userfile):
+ filepath = userfile
+ elif os.path.exists(filename):
+ filepath = filename
+ elif os.path.exists(homefile):
+ filepath = homefile
+ else:
+ filepath = projfile
+ self.configs = {}
+ with open(filepath, 'r') as file:
+ self.configs = safe_load(file)
+
+ def get_command(self, tool):
+ """Get the command-line needed to run a tool."""
+ engine = self.configs['engine']['name']
+ name = self.configs['tools'][tool]['name']
+ if engine is not None and os.getenv('OPENFLOW_OFF') is None:
+ oci = [
+ engine,
+ 'run --rm',
+ '-v ' + (' -v ').join(self.configs['engine']['volumes']),
+ '-w ' + self.configs['engine']['work'],
+ self.configs['engine'].get('options', None),
+ self.configs['tools'][tool].get('options', None),
+ self.configs['tools'][tool]['container'],
+ name
+ ]
+ return ' '.join(list(filter(None, oci)))
+ return name
+
+ def get_tools(self):
+ """Returns the list of configured tools."""
+ return sorted(list(self.configs['tools'].keys()))
+
+ def dump(self):
+ """Dumps the configuration in YAML format (debug purpouses)."""
+ return dump(self.configs)
+
+ def set_engine(self, engine):
+ """Set the OCI engine."""
+ self.configs['engine']['name'] = engine
+
+ def unset_engine(self):
+ """Unset the OCI engine. """
+ self.configs['engine']['name'] = None
+
+ def set_volumes(self, volumes):
+ """Set the volumes of the OCI engine."""
+ self.configs['engine']['volumes'] = volumes
+
+ def set_work(self, work):
+ """Set the working directory inside the container."""
+ self.configs['engine']['work'] = work
+
+ def set_global_options(self, options):
+ """Set options shared by all the containers."""
+ self.configs['engine']['options'] = options
+
+ def set_container(self, tool, container):
+ """Set the container of the specified tool."""
+ self.configs['tools'][tool]['container'] = container
+
+ def set_name(self, tool, name):
+ """Set the name of the specified tool."""
+ self.configs['tools'][tool]['name'] = name
+
+ def set_local_options(self, tool, options):
+ """Set options for a particular container."""
+ self.configs['tools'][tool]['options'] = options
diff --git a/openflow/configure.yml b/openflow/configure.yml
new file mode 100644
index 0000000..6ac1f96
--- /dev/null
+++ b/openflow/configure.yml
@@ -0,0 +1,35 @@
+engine:
+ name: docker
+ volumes:
+ - "$HOME:$HOME"
+ work: $PWD
+tools:
+ ecppack:
+ name: ecppack
+ container: hdlc/prjtrellis
+ ghdl:
+ name: ghdl
+ container: hdlc/ghdl:yosys
+ icepack:
+ name: icepack
+ container: hdlc/icestorm
+ iceprog:
+ name: iceprog
+ container: hdlc/icestorm
+ icetime:
+ name: icetime
+ container: hdlc/icestorm
+ nextpnr-ecp5:
+ name: nextpnr-ecp5
+ container: hdlc/nextpnr:ecp5
+ nextpnr-ice40:
+ name: nextpnr-ice40
+ container: hdlc/nextpnr:ice40
+ openocd:
+ name: openocd
+ container: hdlc/prog
+ options: "--device /dev/bus/usb"
+ yosys:
+ name: yosys
+ container: hdlc/ghdl:yosys
+ options: "--device /dev/bus/usb"
diff --git a/openflow/helpers/configure.py b/openflow/helpers/configure.py
new file mode 100644
index 0000000..c3a941c
--- /dev/null
+++ b/openflow/helpers/configure.py
@@ -0,0 +1,62 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 Rodrigo A. Melo
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+
+"""
+A CLI to get the full OCI command for the specified tool.
+"""
+
+import argparse
+
+from openflow import __version__ as version
+from openflow.configure import ConfigureTools
+
+
+def main():
+ """Solves the main functionality of this helper."""
+
+ cfg = ConfigureTools()
+
+ # Parsing the command-line.
+
+ parser = argparse.ArgumentParser(
+ description=__doc__,
+ formatter_class=argparse.RawDescriptionHelpFormatter
+ )
+
+ parser.add_argument(
+ '-v', '--version',
+ action='version',
+ version='v{}'.format(version)
+ )
+
+ parser.add_argument(
+ 'tool',
+ metavar='TOOL',
+ choices=cfg.get_tools(),
+ help=', '.join(cfg.get_tools())
+ )
+
+ args = parser.parse_args()
+
+ # Solving the functionality
+
+ print(cfg.get_command(args.tool))
+
+
+if __name__ == "__main__":
+ main()
diff --git a/openflow/openflow.py b/openflow/openflow.py
index 5ebd616..a9f5316 100644
--- a/openflow/openflow.py
+++ b/openflow/openflow.py
@@ -28,6 +28,51 @@ def __init__(self, name='openflow', part='hx8k-ct256'):
"""Class constructor."""
self.name = name
self.part = get_part(part)
+ self.set_oci()
+ self.set_tools()
+
+ def set_oci(self):
+ """Set the OCI engine."""
+ self.oci = {
+ 'engine': 'docker',
+ 'volumes': ['$HOME:$HOME'],
+ 'work': '$PWD',
+ 'containers': {
+ 'ghdl': 'hdlc/ghdl:yosys',
+ 'yosys': 'hdlc/ghdl:yosys',
+ 'nextpnr-ice40': 'hdlc/nextpnr:ice40',
+ 'icetime': 'hdlc/icestorm',
+ 'icepack': 'hdlc/icestorm',
+ 'iceprog': 'hdlc/icestorm',
+ 'nextpnr-ecp5': 'hdlc/nextpnr:ecp5',
+ 'ecppack': 'hdlc/prjtrellis',
+ 'openocd': 'hdlc/prog'
+ },
+ 'tools': {
+ 'ghdl': 'ghdl',
+ 'yosys': 'yosys',
+ 'nextpnr-ice40': 'nextpnr-ice40',
+ 'icetime': 'icetime',
+ 'icepack': 'icepack',
+ 'iceprog': 'iceprog',
+ 'nextpnr-ecp5': 'nextpnr-ecp5',
+ 'ecppack': 'ecppack',
+ 'openocd': 'openocd'
+ }
+ }
+
+ def set_tools(self):
+ """Set the underlying tools."""
+ # Check if oci['engine'] is available
+ engine = self.oci['engine']
+ volumes = '-v ' + ('-v ').join(self.oci['volumes'])
+ work = '-w ' + self.oci['work']
+ command = '{} run --rm {} {}'.format(engine, volumes, work)
+ self.tools = {}
+ for tool in self.oci['tools']:
+ self.tools[tool] = '{} {} {}'.format(
+ command, self.oci['containers'][tool], self.oci['tools'][tool]
+ )
# pylint: disable=too-many-arguments
def synthesis(
diff --git a/setup.py b/setup.py
index 397d563..de8313e 100644
--- a/setup.py
+++ b/setup.py
@@ -1,13 +1,15 @@
+import re
from setuptools import setup, find_packages
-import openflow
+with open('openflow/__init__.py', 'r') as f:
+ version = re.search(r"__version__ = '([\d\.]*)'", f.read()).group(1)
-with open('README.md', 'r') as fh:
- long_description = fh.read()
+with open('README.md', 'r') as f:
+ long_description = f.read()
setup(
name='fpgaopenflow',
- version=openflow.__version__,
+ version=version,
description='a Python library, and CLI utilities, which solves HDL-to-bitstream based on FOSS',
long_description=long_description,
long_description_content_type='text/markdown',
@@ -16,11 +18,13 @@
license='GPLv3',
url='https://github.com/PyFPGA/openflow',
packages=find_packages(),
+ package_data={'': ['configure.yml']},
entry_points={
'console_scripts': [
'openflow_syn = openflow.helpers.synthesis:main',
'openflow_imp = openflow.helpers.implementation:main',
- 'openflow_bit = openflow.helpers.bitstream:main'
+ 'openflow_bit = openflow.helpers.bitstream:main',
+ 'openflow_cfg = openflow.helpers.configure:main'
],
},
classifiers=[
@@ -36,5 +40,7 @@
'Topic :: Utilities',
'Topic :: Software Development :: Build Tools',
"Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)"
- ]
+ ],
+ install_requires=['pyyaml'],
+ python_requires=">=3.5, <4"
)