├── .github └── workflows │ ├── docs.yml │ └── push.yml ├── .gitignore ├── README.md ├── cosim ├── __init__.py ├── utils.py └── vhpidirect │ ├── .gitignore │ ├── __init__.py │ └── c │ ├── grt.ver │ ├── stubs.c │ └── vhpidirect_user.h ├── docs ├── _static │ ├── VUnit_cosim_logo_420x420.png │ ├── vunit_cosim.ico │ └── vunit_cosim.svg ├── _templates │ └── .gitkeep ├── bridges │ ├── index.rst │ └── vhpidirect.rst ├── conf.py ├── examples │ ├── buffer.rst │ ├── copy.rst │ └── index.rst ├── genindex.rst ├── index.rst ├── py-modindex.rst └── utils.rst ├── examples ├── buffer │ ├── corun.py │ ├── run.py │ └── src │ │ ├── main.c │ │ ├── tb_ext_byte_vector.vhd │ │ ├── tb_ext_integer_vector.vhd │ │ └── tb_ext_string.vhd └── copy │ ├── run.py │ └── src │ ├── cp.c │ ├── tb_extcp_byte_vector.vhd │ └── tb_extcp_string.vhd ├── requirements.txt ├── setup.py ├── tests ├── acceptance │ ├── __init__.py │ └── test_examples.py └── lint │ ├── __init__.py │ ├── pylintrc │ └── test_lint.py └── tox.ini /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: 'docs' 2 | 3 | on: [ push, pull_request ] 4 | 5 | env: 6 | # https://github.com/tox-dev/tox/issues/1468 7 | PY_COLORS: 1 8 | CLONE_VUNIT: git clone --recurse-submodules -j4 https://github.com/VUnit/vunit ../vunit 9 | 10 | jobs: 11 | 12 | docs: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v1 16 | with: 17 | submodules: recursive 18 | - uses: actions/setup-python@v1 19 | with: 20 | python-version: 3.8 21 | - name: install dependencies 22 | run: | 23 | pip install -U pip --progress-bar off 24 | pip install -U virtualenv tox --progress-bar off 25 | - name: get VUnit 26 | run: $CLONE_VUNIT 27 | - name: build docs 28 | run: tox -e py38-docs -- --color 29 | - uses: actions/upload-artifact@master 30 | with: 31 | name: VUnit-site 32 | path: .tox/py38-docs/tmp/docsbuild/ 33 | - name: 'publish site to gh-pages' 34 | if: github.event_name != 'pull_request' && github.repository == 'VUnit/cosim' 35 | env: 36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 37 | run: | 38 | cd .tox/py38-docs/tmp/docsbuild/ 39 | touch .nojekyll 40 | 41 | git init 42 | git add . 43 | git config --local user.email "push@gha" 44 | git config --local user.name "GHA" 45 | git commit -a -m "update ${{ github.sha }}" 46 | 47 | git remote add origin https://x-access-token:$GITHUB_TOKEN@github.com/$GITHUB_REPOSITORY 48 | git push -u origin +HEAD:gh-pages 49 | -------------------------------------------------------------------------------- /.github/workflows/push.yml: -------------------------------------------------------------------------------- 1 | name: 'push' 2 | 3 | on: [ push, pull_request ] 4 | 5 | env: 6 | DOCKER_REGISTRY: docker.pkg.github.com 7 | # https://github.com/tox-dev/tox/issues/1468 8 | PY_COLORS: 1 9 | CLONE_VUNIT: git clone --recurse-submodules -j4 https://github.com/VUnit/vunit ../vunit 10 | 11 | jobs: 12 | 13 | lin: 14 | strategy: 15 | fail-fast: false 16 | max-parallel: 2 17 | matrix: 18 | task: [ 19 | "38-fmt -- --check", 20 | "38-lint -- --color=yes", 21 | ] 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v1 25 | - uses: actions/setup-python@v1 26 | with: 27 | python-version: 3.8 28 | - name: install VUnit 29 | run: $CLONE_VUNIT 30 | - name: install dependencies 31 | run: | 32 | pip install -U pip --progress-bar off 33 | pip install -U virtualenv tox --progress-bar off 34 | - name: run job 35 | run: tox -e py${{ matrix.task }} 36 | 37 | docker: 38 | strategy: 39 | fail-fast: false 40 | max-parallel: 2 41 | matrix: 42 | task: [ 43 | { py: 38, img: "docker.pkg.github.com/vunit/vunit/dev:llvm" }, 44 | { py: 38, img: "docker.pkg.github.com/vunit/vunit/dev:mcode" }, 45 | ] 46 | runs-on: ubuntu-latest 47 | steps: 48 | - uses: actions/checkout@v1 49 | with: 50 | submodules: recursive 51 | - name: docker login 52 | run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login -u vunit-gha --password-stdin "$DOCKER_REGISTRY" 53 | - name: run job 54 | run: | 55 | $CLONE_VUNIT 56 | docker run --rm -tv $(pwd)/..:/src -w /src/cosim ${{ matrix.task.img }} tox -e py${{ matrix.task.py }}-acceptance-ghdl 57 | - name: docker logout 58 | run: docker logout "$DOCKER_REGISTRY" 59 | if: always() 60 | 61 | win: 62 | strategy: 63 | fail-fast: false 64 | max-parallel: 2 65 | matrix: 66 | task: [ 67 | 38-acceptance-ghdl, 68 | 38-lint, 69 | ] 70 | runs-on: windows-latest 71 | steps: 72 | - uses: actions/checkout@v1 73 | - name: git submodule update 74 | run: git submodule update --init --recursive 75 | if: (endsWith( matrix.task, '-lint' ) || endsWith( matrix.task, '-unit' )) == false 76 | - uses: actions/setup-python@v1 77 | with: 78 | python-version: 3.8 79 | - name: install dependencies 80 | run: | 81 | pip install -U pip --progress-bar off 82 | pip install -U virtualenv tox --progress-bar off 83 | - name: install VUnit 84 | shell: bash 85 | run: $CLONE_VUNIT 86 | - name: install GHDL 87 | if: endsWith( matrix.task, '-ghdl' ) 88 | shell: bash 89 | run: | 90 | curl -fsSL -o ghdl.zip https://github.com/ghdl/ghdl/releases/download/v0.36/ghdl-0.36-mingw32-mcode.zip 91 | 7z x ghdl.zip "-o../ghdl" -y 92 | mv ../ghdl/GHDL/0.36-mingw32-mcode/ ../ghdl-v0.36 93 | rm -rf ../ghdl ghdl.zip 94 | - name: run job 95 | shell: bash 96 | run: | 97 | export PATH=$PATH:$(pwd)/../ghdl-v0.36/bin 98 | tox -e py${{ matrix.task }} -- --color=yes 99 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/vunit_out 2 | *.pyc 3 | *.bak 4 | *.o 5 | *.so 6 | *.egg-info 7 | .devcontainer 8 | .tox 9 | .vscode 10 | env/ 11 | venv/ 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > **This repository is in a very early planning phase. Although the VHPIDIRECT bridge for GHDL is functional, breaking changes are being discussed in [VUnit/vunit#603](https://github.com/VUnit/vunit/issues/603)**. 2 | 3 |

4 | 9 | 14 | 'docs' workflow Status 19 | 'push' workflow Status 23 |

24 | 25 | # Interfacing VHDL and foreign languages with [VUnit](https://github.com/VUnit/vunit) 26 | 27 | Three main approaches are used to co-simulate (co-execute) VHDL sources along with software applications written in a different language (typically C/C++): 28 | 29 | - Verilog Procedural Interface (VPI), also known as Program Language Interface (PLI) 2.0. 30 | - VHDL Procedural Interface (VHPI), or specific implementations, such as Foreign Language Interface (FLI). 31 | - Generation of C/C++ models/sources through a transpiler. 32 | 33 | This repository aims to gather resources to use these techniques with VUnit. The content is organised in **bridges**, **examples** and **utils**: 34 | 35 | - Bridges contain VHDL packages, (optionally) matching C headers and Python classes. Each bridge provides the glue logic between a VHDL API (or another bridge) and some other API in VHDL and/or C (bindings). 36 | - Examples are working demo projects that use some utils and/or bridges. Dockerfiles are included in the examples that require additional FOSS tools. 37 | - Utils contains helper functions in Python (based on `ctypes`, `base64`, `numpy` and `Pillow`) which are useful for interacting with C-alike executables. 38 | -------------------------------------------------------------------------------- /cosim/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | HDL co-simulation with VUnit 3 | """ 4 | -------------------------------------------------------------------------------- /cosim/utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | C/Python co-simulation utilities 3 | """ 4 | 5 | # pylint:disable=wrong-import-order 6 | 7 | import sys 8 | from sys import platform 9 | from pathlib import Path 10 | from base64 import b64encode 11 | from io import BytesIO 12 | import ctypes 13 | import _ctypes # type: ignore 14 | import numpy # type: ignore 15 | from PIL import Image # type: ignore 16 | 17 | 18 | def dlopen(path): 19 | """ 20 | Open/load a PIE binary or a shared library. 21 | """ 22 | if not Path(path).is_file(): 23 | print("Executable binary not found: " + path) 24 | sys.exit(1) 25 | try: 26 | return ctypes.CDLL(path) 27 | except OSError: 28 | print( 29 | "Loading executables dynamically seems not to be supported on this platform" 30 | ) 31 | sys.exit(1) 32 | 33 | 34 | def dlclose(obj): 35 | """ 36 | Close/unload a PIE binary or a shared library. 37 | 38 | :param obj: object returned by ctypes.CDLL when the resource was loaded 39 | """ 40 | if platform == "win32": 41 | _ctypes.FreeLibrary(obj._handle) # pylint:disable=protected-access,no-member 42 | else: 43 | _ctypes.dlclose(obj._handle) # pylint:disable=protected-access,no-member 44 | 45 | 46 | def enc_args(args): 47 | """ 48 | Convert args to a suitable format for a foreign C function. 49 | 50 | :param args: list of strings 51 | """ 52 | xargs = (ctypes.POINTER(ctypes.c_char) * len(args))() 53 | for idx, arg in enumerate(args): 54 | xargs[idx] = ctypes.create_string_buffer(arg.encode("utf-8")) 55 | return xargs 56 | 57 | 58 | def byte_buf(lst): 59 | """ 60 | Convert array to a string buffer (uint8_t* in C). 61 | 62 | :param lst: list of naturals range [0,255] 63 | """ 64 | return ctypes.create_string_buffer(bytes(lst), len(lst)) 65 | 66 | 67 | def int_buf(lst, bpw=4, signed=True): 68 | """ 69 | Convert array to a string buffer (uint8_t* in C). 70 | 71 | :param lst: list of integers 72 | :param bpw: number of bytes per word/integer 73 | :param signed: whether to encode the numbers as signed 74 | """ 75 | out = [None] * 4 * len(lst) 76 | for idx, val in enumerate(lst): 77 | out[idx * 4 : idx * 4 + 4] = (val).to_bytes( 78 | bpw, byteorder="little", signed=signed 79 | ) 80 | return byte_buf(out) 81 | 82 | 83 | def read_byte_buf(buf): 84 | """ 85 | Read byte/string buffer (uint8_t* in C) as a list of numbers. 86 | """ 87 | return read_int_buf(buf, bpw=1, signed=False) 88 | 89 | 90 | def read_int_buf(buf, bpw=4, signed=True): 91 | """ 92 | Read byte/string buffer as a list of numbers. 93 | 94 | :param buf: byte/string buffer (uint8_t* in C) to read from 95 | :param bpw: number of bytes per word/integer 96 | :param signed: whether to decode the numbers as signed 97 | """ 98 | out = [None] * int(len(buf) / bpw) 99 | if bpw == 1: 100 | for idx, val in enumerate(buf): 101 | out[idx] = int.from_bytes(val, byteorder="little", signed=signed) 102 | else: 103 | for idx, _ in enumerate(out): 104 | out[idx] = int.from_bytes( 105 | buf[idx * bpw : idx * bpw + bpw], byteorder="little", signed=signed 106 | ) 107 | return out 108 | 109 | 110 | def b64enc_int_list(lst, width, height): 111 | """ 112 | Encode list of numbers as a base64 encoded PNG image. 113 | 114 | :param lst: list of pixel values (len = height x width) in row-major order 115 | :param width: spatial width 116 | :param height: spatial height 117 | """ 118 | buf = BytesIO() 119 | Image.fromarray( 120 | numpy.array(numpy.reshape(lst, (height, width)), dtype=numpy.uint16) 121 | ).save(buf, format="PNG") 122 | return b64encode(buf.getvalue()).decode("utf8") 123 | 124 | 125 | def b64enc_list_of_int_lists(lst, width, height): 126 | """ 127 | Convert list of lists of numbers to list of base64 encoded PNG images. 128 | 129 | :param lst: list of lists of pixel values (len = height x width) in row-major order 130 | :param width: spatial width 131 | :param height: spatial height 132 | """ 133 | b64 = [[] for idx in range(len(lst))] 134 | for idx, val in enumerate(lst): 135 | b64[idx] = b64enc_int_list(val, width, height) 136 | return b64 137 | -------------------------------------------------------------------------------- /cosim/vhpidirect/.gitignore: -------------------------------------------------------------------------------- 1 | /vhdl/* -------------------------------------------------------------------------------- /cosim/vhpidirect/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | VHPIDIRECT VUnit co-simulation bridge 3 | """ 4 | 5 | import re 6 | from os import linesep, makedirs 7 | from pathlib import Path 8 | from shutil import copyfile 9 | from vunit import ROOT as SROOT # type: ignore 10 | 11 | ROOT = Path(SROOT) 12 | 13 | 14 | class VHPIDIRECT: 15 | """ 16 | VHPIDIRECT VUnit co-simulation bridge class 17 | 18 | :param srcs: optional alternative location of the sources 19 | """ 20 | 21 | def __init__(self, srcs=None): 22 | self._srcs = Path(srcs) if srcs is not None else Path(__file__).parent 23 | 24 | def bridge(self, features=None): 25 | """ 26 | Get lists of files to register the bridge with ``vu.add_builtins`` 27 | 28 | :param features: struct,``{'string': , 'integer': }``, to provide 29 | bridges for the external VHDL API. Accepted values are 30 | ``True``, ``False``, ``None`` or ``['path/to/custom/file']``. 31 | """ 32 | funcs = { 33 | "string": { 34 | "write_char : procedure": "write_char", 35 | "read_char : function": "read_char", 36 | "get_ptr : function": "get_string_ptr", 37 | }, 38 | "integer_vector": { 39 | "write_integer : procedure": "write_integer", 40 | "read_integer : function": "read_integer", 41 | "get_ptr : function": "get_intvec_ptr", 42 | }, 43 | } 44 | 45 | api = {"string": True, "integer_vector": True} 46 | for key in api: 47 | if key in features: 48 | val = features[key] 49 | if val is not True: 50 | api[key] = val 51 | else: 52 | dname = self._srcs / "vhdl" / ("%s.vhd" % key) 53 | api[key] = [str(dname)] 54 | 55 | try: 56 | makedirs(str(dname.parent), 0o755) 57 | except FileExistsError: 58 | pass 59 | 60 | attrs = linesep 61 | for fkey, fval in funcs[key].items(): 62 | attrs += ' attribute foreign of %s is "VHPIDIRECT %s";%s' % ( 63 | fkey, 64 | fval, 65 | linesep, 66 | ) 67 | 68 | # Instead of providing separate VHDL sources, in this bridge we patch 69 | # the reference API definition sources from VUnit/vunit 70 | with ( 71 | ROOT 72 | / "vunit" 73 | / "vhdl" 74 | / "data_types" 75 | / "src" 76 | / "api" 77 | / ("external_%s_pkg.vhd" % key) 78 | ).open() as sfptr: 79 | with dname.open("w") as dfptr: 80 | dfptr.write( 81 | re.sub("(end package;)", attrs + r"\1", sfptr.read()) 82 | ) 83 | return api 84 | 85 | @property 86 | def include(self): 87 | """ 88 | Get path to include directories containing C sources/headers 89 | """ 90 | return [str(self._srcs / "c")] 91 | 92 | def verscript(self, file=None): 93 | """ 94 | Get argument to add a custom version-script file to GHDL 95 | 96 | :param file: provide a custom file instead of the one provided with the bridge 97 | """ 98 | return "-Wl,-Wl,--version-script=%s" % ( 99 | file if file is not None else str(self._srcs / "c" / "grt.ver") 100 | ) 101 | 102 | @staticmethod 103 | def post_func(results): 104 | """ 105 | Optional post-func to copy runtime args for each test/executable to output subdir 'cosim' 106 | """ 107 | report = results.get_report() 108 | cosim_args_dir = Path(report.output_path) / "cosim" 109 | 110 | try: 111 | makedirs(str(cosim_args_dir)) 112 | except FileExistsError: 113 | pass 114 | 115 | for key, item in report.tests.items(): 116 | copyfile( 117 | str(Path(item.path) / "ghdl" / "args.json"), 118 | str( 119 | cosim_args_dir / ("%s.json" % re.search(r"lib\.(.+)\.all", key)[1]) 120 | ), 121 | ) 122 | -------------------------------------------------------------------------------- /cosim/vhpidirect/c/grt.ver: -------------------------------------------------------------------------------- 1 | VHPIDIRECT { 2 | global: 3 | main; 4 | ghdl_main; 5 | read_char; 6 | write_char; 7 | read_integer; 8 | write_integer; 9 | set_string_ptr; 10 | get_string_ptr; 11 | set_intvec_ptr; 12 | get_intvec_ptr; 13 | local: 14 | *; 15 | }; 16 | -------------------------------------------------------------------------------- /cosim/vhpidirect/c/stubs.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | void set_string_ptr(uint8_t id, uint8_t *p) { 6 | printf("ERROR set_string_ptr: THIS IS A STUB\n"); 7 | exit(1); 8 | return; 9 | } 10 | 11 | uintptr_t get_string_ptr(uint8_t id) { 12 | printf("ERROR get_string_ptr: THIS IS A STUB\n"); 13 | exit(1); 14 | return NULL; 15 | } 16 | 17 | void write_char( uint8_t id, uint32_t i, uint8_t v ) { 18 | printf("ERROR write_char: THIS IS A STUB\n"); 19 | exit(1); 20 | return; 21 | } 22 | 23 | uint8_t read_char( uint8_t id, uint32_t i ) { 24 | printf("ERROR read_char: THIS IS A STUB\n"); 25 | exit(1); 26 | return 0; 27 | } 28 | 29 | //--- 30 | 31 | void set_intvec_ptr(uint8_t id, uint8_t *p) { 32 | printf("ERROR set_intvec_ptr: THIS IS A STUB\n"); 33 | exit(1); 34 | return; 35 | } 36 | 37 | uintptr_t get_intvec_ptr(uint8_t id) { 38 | printf("ERROR get_intvec_ptr: THIS IS A STUB\n"); 39 | exit(1); 40 | return NULL; 41 | } 42 | 43 | void write_integer(uint8_t id, uint32_t i, int32_t v) { 44 | printf("ERROR write_integer: THIS IS A STUB\n"); 45 | exit(1); 46 | return; 47 | } 48 | 49 | int32_t read_integer(uint8_t id, uint32_t i) { 50 | printf("ERROR read_integer: THIS IS A STUB\n"); 51 | exit(1); 52 | return 0; 53 | } 54 | -------------------------------------------------------------------------------- /cosim/vhpidirect/c/vhpidirect_user.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | extern int ghdl_main (int argc, char **argv); 4 | 5 | uint8_t *D[256]; 6 | 7 | //--- 8 | 9 | // External string/byte_vector through access (mode = extacc) 10 | 11 | void set_string_ptr(uint8_t id, uintptr_t p) { 12 | D[id] = (uint8_t*)p; 13 | } 14 | 15 | uintptr_t get_string_ptr(uint8_t id) { 16 | return (uintptr_t)D[id]; 17 | } 18 | 19 | // External string/byte_vector through functions (mode = extfnc) 20 | 21 | void write_char(uint8_t id, uint32_t i, uint8_t v) { 22 | ((uint8_t*)D[id])[i] = v; 23 | } 24 | 25 | uint8_t read_char(uint8_t id, uint32_t i) { 26 | return ((uint8_t*)D[id])[i]; 27 | } 28 | 29 | //--- 30 | 31 | // External integer_vector through access (mode = extacc) 32 | 33 | void set_intvec_ptr(uint8_t id, uintptr_t p) { 34 | D[id] = (uint8_t*)p; 35 | } 36 | 37 | uintptr_t get_intvec_ptr(uint8_t id) { 38 | return (uintptr_t)D[id]; 39 | } 40 | 41 | // External integer_vector through functions (mode = extfnc) 42 | 43 | void write_integer(uint8_t id, uint32_t i, int32_t v) { 44 | ((int32_t*)D[id])[i] = v; 45 | } 46 | 47 | int32_t read_integer(uint8_t id, uint32_t i) { 48 | return ((int32_t*)D[id])[i]; 49 | } 50 | -------------------------------------------------------------------------------- /docs/_static/VUnit_cosim_logo_420x420.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VUnit/cosim/9ad3dfa20e34a269b2a4df8e2b18b934ab84dd72/docs/_static/VUnit_cosim_logo_420x420.png -------------------------------------------------------------------------------- /docs/_static/vunit_cosim.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VUnit/cosim/9ad3dfa20e34a269b2a4df8e2b18b934ab84dd72/docs/_static/vunit_cosim.ico -------------------------------------------------------------------------------- /docs/_static/vunit_cosim.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 38 | image/svg+xml 40 | 42 | image/svg+xml 43 | 45 | 46 | 47 | 48 | 49 | 56 | 63 | 69 | 70 | -------------------------------------------------------------------------------- /docs/_templates/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VUnit/cosim/9ad3dfa20e34a269b2a4df8e2b18b934ab84dd72/docs/_templates/.gitkeep -------------------------------------------------------------------------------- /docs/bridges/index.rst: -------------------------------------------------------------------------------- 1 | .. _bridges: 2 | 3 | Bridges 4 | ####### 5 | 6 | Bridges contain VHDL packages, matching C headers and/or Python classes. Each bridge provides the glue logic between a VHDL API and some other API in VHDL and/or C (bindings). 7 | 8 | .. NOTE:: `GHDL `_, the only free and open source simulator for VHDL supported by VUnit, features VPI and a limited subset of VHPI named VHPIDIRECT. Since co-simulation of VHDL and Python through VPI is already supported and documented by `cocotb `_, this project is mostly focused on VHPIDIRECT. 9 | 10 | Available modules: 11 | 12 | :ref:`bridges:vhpidirect` 13 | C bindings for VUnit's external VHDL API (see :ref:`vunit:data_types_library`). 14 | 15 | .. toctree:: 16 | :maxdepth: 2 17 | :hidden: 18 | 19 | vhpidirect 20 | -------------------------------------------------------------------------------- /docs/bridges/vhpidirect.rst: -------------------------------------------------------------------------------- 1 | .. _bridges:vhpidirect: 2 | 3 | VHPIDIRECT 4 | ########## 5 | 6 | This is a bridge between VUnit's external VHDL API and C, through GHDL's VHPIDIRECT 7 | features (see :ref:`ghdl:USING:Foreign`). A Python class (named ``VHPIDIRECT``) provides 8 | helper functions to (re)use a C API (``vhpidirect_user.h``, ``grt.ver`` and/or 9 | ``stubs.c``) with VUnit's ``add_builtins`` and ``set_sim_option("ghdl.elab_flags", ...)``. 10 | Examples of how to use this bridge are shown in :ref:`examples:vhpidirect`. 11 | 12 | .. NOTE:: In the latest stable release of GHDL (v0.36), mcode backend does not support 13 | VHPIDIRECT. Moreover, using LLVM or GCC backends is suggested, as these generate 14 | executable files and are supported in a wider range of platforms. 15 | 16 | .. NOTE:: For GHDL to generate PIE executable files, it needs to be configured with option 17 | ``--default-pic``. Moreover, loading ELF binaries dynamically is a *hackish* procedure 18 | that takes advantage of PIE ELF binaries and shared libraries having a common structure 19 | on GNU/Linux. However, this does not work on Windows (see `ghdl/ghdl#803 `_). 20 | As a result: 21 | 22 | * GHDL needs to be enhanced to allow building designs as shared libraries instead of 23 | executable binaries (see `ghdl/ghdl#800 `_). 24 | * or, build features in VUnit need to be extended to use ``ghdl --bind``, 25 | ``ghdl --list-link`` and gcc/clang in order to generate a ``*.dll`` on Windows. 26 | * or, a helper method need to be added to this class to use gcc/clang. 27 | 28 | Implementation details 29 | ====================== 30 | 31 | ``vhpidirect_user.h`` is the C implementation of the interface. It uses an array of 32 | pointers (``uint8_t *D[256];``) to keep a reference to all the buffers that are shared 33 | between C and VHDL. 34 | 35 | .. NOTE:: Users that need to share more than 256 pointers, can and should include their 36 | own copy of this header file. Actually, in example :ref:`examples:copy` the provided 37 | header file is not used. Instead, functions are implemented in ``main.c``. 38 | 39 | ``stubs.c`` is to be used when multiple tests are handled in a single ``run.py`` file 40 | (i.e. by the same VUnit object), but some of them do NOT use external modes. Since 41 | builtins are compiled once only, every test (binary) needs to have placeholders for the C 42 | functions that VHDL expects to use. ``stubs.c`` provides *dummy* definitions of the C API. 43 | Note that these will produce errors if executed at runtime. 44 | 45 | ``grt.ver`` is to be used when the ELF file built with GHDL is to be loaded dynamically. 46 | By default, GHDL hides most of the global symbols. Providing this file as an elaboration 47 | option ensures that the functions of the C API are visible. Script ``corun.py`` in example 48 | :ref:`examples:buffer` shows how to dynamically load and execute the simulation from Python. 49 | 50 | extfnc mode 51 | ----------- 52 | 53 | As explained in :ref:`vunit:data_types_library:external`, mode *extfnc* is expected to be 54 | used when data is not available in the same memory space as the VHDL simulation. For 55 | example, when libraries such as `gRPC `_, `ZeroMQ `_ 56 | or `netpp `_ are used to co-execute simulations in 57 | remote targets. Hence, ``write_*/read_*`` function bodies provided in ``vhpidirect_user.h`` 58 | are for testing purposes. In practice, developers willing to use this mode are expected 59 | to provide their own custom header file. 60 | 61 | Note that this is the difference between *extfnc* and *extacc*: with *extacc*, there is 62 | no explicit callback when VHDL modifies a value in a shared buffer. Conversely, with 63 | *extfnc*, VHDL cannot modify a value without a callback. 64 | 65 | Python interface 66 | ================ 67 | 68 | .. autoclass:: cosim.vhpidirect.VHPIDIRECT() 69 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | # -- Path setup -------------------------------------------------------------- 4 | 5 | # If extensions (or modules to document with autodoc) are in another directory, 6 | # add these directories to sys.path here. If the directory is relative to the 7 | # documentation root, use os.path.abspath to make it absolute, like shown here. 8 | # 9 | # import os 10 | # import sys 11 | # sys.path.insert(0, os.path.abspath('.')) 12 | 13 | 14 | # -- Project information ----------------------------------------------------- 15 | 16 | project = "VUnit cosim" 17 | copyright = "2019-2020, author" 18 | author = "author" 19 | 20 | version = "" 21 | release = "" 22 | 23 | 24 | # -- General configuration --------------------------------------------------- 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be 27 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 28 | # ones. 29 | extensions = [ 30 | "sphinx.ext.autodoc", 31 | # "sphinx.ext.extlinks", 32 | "sphinx.ext.intersphinx", 33 | # "sphinx.ext.todo", 34 | # "sphinxarg.ext", # Automatic argparse command line argument documentation 35 | ] 36 | 37 | autodoc_default_flags = ["members"] 38 | 39 | # Add any paths that contain templates here, relative to this directory. 40 | templates_path = ["_templates"] 41 | 42 | # List of patterns, relative to source directory, that match files and 43 | # directories to ignore when looking for source files. 44 | # This pattern also affects html_static_path and html_extra_path. 45 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] 46 | 47 | 48 | # -- Options for HTML output ------------------------------------------------- 49 | 50 | # The theme to use for HTML and HTML Help pages. See the documentation for 51 | # a list of builtin themes. 52 | # 53 | html_theme = "alabaster" 54 | 55 | # Add any paths that contain custom static files (such as style sheets) here, 56 | # relative to this directory. They are copied after the builtin static files, 57 | # so a file named "default.css" will overwrite the builtin "default.css". 58 | html_static_path = ["_static"] 59 | 60 | html_logo = str(Path(html_static_path[0]) / "VUnit_cosim_logo_420x420.png") 61 | 62 | html_favicon = str(Path(html_static_path[0]) / "vunit_cosim.ico") 63 | 64 | 65 | # -- Sphinx.Ext.InterSphinx -------------------------------------------------- 66 | 67 | intersphinx_mapping = { 68 | "vunit": ("https://vunit.github.io", None), 69 | "ghdl": ("https://ghdl.rtfd.io/en/latest", None), 70 | } 71 | -------------------------------------------------------------------------------- /docs/examples/buffer.rst: -------------------------------------------------------------------------------- 1 | .. _examples:buffer: 2 | 3 | Buffer 4 | ###### 5 | 6 | This example shows how to use :class:`cosim.vhpidirect.VHPIDIRECT` (see :ref:`bridges:vhpidirect` to interact with VUnit's internal ``string_ptr``/``byte_vector_ptr`` and/or ``integer_vector_ptr``, from C and/or Python. 7 | 8 | * ``run.py``: supports a custom CLI option, ``--build``, that will set ``VU.set_sim_option("ghdl.elab_e", True)``. 9 | 10 | * :func:`cosim.vhpidirect.VHPIDIRECT.bridge` is used to enable external mode support for ``string_ptr`` and `integer_vector_ptr` (see :ref:`vunit:data_types_library`). 11 | * :func:`cosim.vhpidirect.VHPIDIRECT.include` is used to build C sources with the provided C API/headers. 12 | * :func:`cosim.vhpidirect.VHPIDIRECT.verscript` is used to provide a version script that matches the functions in the C API. 13 | * The ``post_func`` provided by :ref:`bridges:vhpidirect` (see :func:`cosim.vhpidirect.VHPIDIRECT.post_func`) is used to save in subdir `vunit_out/cosim` the CLI args that correspond to each testbench. 14 | 15 | * ``corun.py``: allows to dynamically load and run any of the simulation binaries built with ``run.py``. Given a testbench name, it finds the corresponding binary and arguments JSON file. Then, it is loaded dynamically and data buffers allocated in Python are shared with the simulation. 16 | 17 | Example session: 18 | 19 | .. CODE:: bash 20 | 21 | # python3 run.py --build -v 22 | ... 23 | 24 | # ls vunit_out/cosim/ 25 | tb_external_byte_vector.json tb_external_integer_vector.json tb_external_string.json 26 | 27 | # python3 corun.py tb_external_byte_vector 28 | ... 29 | 30 | .. NOTE:: GHDL with mcode backend does NOT generate executable binaries. In order to use the two-step workflow, eithe LLVM or GCC backends need to be used. 31 | 32 | .. NOTE:: ``corun.py`` depends on :mod:`cosim.utils`, which requires some additional dependencies. File ``requirements.txt`` can be used to install all of them at once. 33 | -------------------------------------------------------------------------------- /docs/examples/copy.rst: -------------------------------------------------------------------------------- 1 | .. _examples:copy: 2 | 3 | Copy 4 | ###### 5 | 6 | This example shows the most basic usage of :class:`cosim.vhpidirect.VHPIDIRECT` (see :ref:`bridges:vhpidirect`) to interact with VUnit's internal ``string_ptr``/``byte_vector_ptr`` (see :ref:`vunit:data_types_library`) from C. :func:`cosim.vhpidirect.VHPIDIRECT.bridge` is used enable external mode support for ``string_ptr``. Two equivalent testbenches are provided: one uses ``string_ptr`` and the other one ``byte_vector_ptr``. 7 | -------------------------------------------------------------------------------- /docs/examples/index.rst: -------------------------------------------------------------------------------- 1 | .. _examples: 2 | 3 | Examples 4 | ######## 5 | 6 | .. NOTE:: Since this project is not distributed through PyPI yet, and because it depends 7 | on a specific branch of VUnit, it is suggested to clone both repositories as siblings, 8 | and to use PYTHONPATH. For example: 9 | 10 | .. CODE:: bash 11 | 12 | mkdir VUnit 13 | cd VUnit 14 | git clone -b cosim --recurse-submodules -j4 https://github.com/dbhi/vunit 15 | git clone https://github.com/vunit/cosim 16 | export PYTHONPATH="$(pwd)/vunit":"$(pwd)/cosim" 17 | 18 | .. _examples:vhpidirect: 19 | 20 | VHPIDIRECT 21 | ---------- 22 | 23 | .. toctree:: 24 | :maxdepth: 2 25 | 26 | copy 27 | buffer 28 | -------------------------------------------------------------------------------- /docs/genindex.rst: -------------------------------------------------------------------------------- 1 | .. # This file is a placeholder and will be replaced 2 | 3 | Index 4 | ##### 5 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. centered:: |shieldVunit|_ |shieldCosim|_ |shieldGitter|_ |shieldTwitter|_ 2 | 3 | .. |shieldVunit| image:: https://img.shields.io/badge/VUnit/vunit-0c479d.svg?longCache=true&style=flat-square&logo=github 4 | .. _shieldVunit: https://github.com/VUnit/vunit 5 | 6 | .. |shieldCosim| image:: https://img.shields.io/badge/VUnit/cosim-000000.svg?longCache=true&style=flat-square&logo=github&logoColor=fdbe00 7 | .. _shieldCosim: https://github.com/VUnit/cosim 8 | 9 | .. |shieldGitter| image:: https://img.shields.io/gitter/room/VUnit/vunit.svg?longCache=true&style=flat-square&logo=gitter&logoColor=4db797&color=4db797 10 | .. _shieldGitter: https://gitter.im/VUnit/vunit 11 | 12 | .. |shieldTwitter| image:: https://img.shields.io/twitter/follow/VUnitFramework.svg?longCache=true&style=flat-square&color=1DA1F2&label=%40VUnitFramework&logo=twitter&logoColor=fff 13 | .. _shieldTwitter: https://www.twitter.com/VUnitFramework 14 | 15 | VUnit cosim 16 | =========== 17 | 18 | **VUnit** is an open source unit testing framework for VHDL/SystemVerilog, which features the 19 | functionality needed to realise continuous and automated testing of HDL code. 20 | VUnit doesn't replace but rather complements traditional testing methodologies by 21 | supporting a *"test early and often"* approach through automation. :ref:`Read more ` 22 | 23 | **VUnit cosim** follows the same philosophy to support co-simulation (co-execution) of VHDL along with 24 | software applications written in a different language (typically C/C++ or Python). The content of this module is organised in bridges, examples and utils: 25 | 26 | * :ref:`bridges`: glue logic between a :ref:`external VHDL API ` (or another bridge) and some other API in VHDL and/or C (bindings). 27 | 28 | * :ref:`examples`: working demo projects that use some utils and/or bridges. 29 | 30 | * :ref:`utils`: helper functions in Python (based on ctypes, base64, numpy and Pillow) which are useful for interacting with C-alike executables. 31 | 32 | .. toctree:: 33 | :maxdepth: 3 34 | :hidden: 35 | 36 | bridges/index 37 | examples/index 38 | utils 39 | genindex 40 | py-modindex 41 | -------------------------------------------------------------------------------- /docs/py-modindex.rst: -------------------------------------------------------------------------------- 1 | .. # This file is a placeholder and will be replaced 2 | 3 | Python Module Index 4 | ################### 5 | -------------------------------------------------------------------------------- /docs/utils.rst: -------------------------------------------------------------------------------- 1 | .. _utils: 2 | 3 | Utils 4 | ##### 5 | 6 | .. automodule:: cosim.utils 7 | -------------------------------------------------------------------------------- /examples/buffer/corun.py: -------------------------------------------------------------------------------- 1 | """ 2 | Buffer: dynamically loading a simulation from Python 3 | ---------------------------------------------------- 4 | 5 | Given a PIE executable and a set of CLI args in a JSON file, execute a simulation twice: 6 | 7 | * First, load it dynamically and execute the simulation without shared data buffers. 8 | * Then, load it again, allocate data buffers from Python, execute the simulation, 9 | and check the modified buffers from Python. 10 | """ 11 | 12 | import argparse 13 | import sys 14 | from pathlib import Path 15 | from json import load 16 | from cosim.utils import ( 17 | enc_args, 18 | dlopen, 19 | dlclose, 20 | byte_buf, 21 | int_buf, 22 | read_byte_buf, 23 | read_int_buf, 24 | ) 25 | 26 | PARSER = argparse.ArgumentParser() 27 | PARSER.add_argument( 28 | "testbench", type=str, nargs="+", help="name of a pre-built testbench" 29 | ) 30 | PARSER.add_argument( 31 | "--output-path", 32 | type=str, 33 | dest="output_path", 34 | default=str(Path(__file__).parent / "vunit_out"), 35 | help="output path (default: 'vunit_out')", 36 | ) 37 | 38 | PARGS = PARSER.parse_args() 39 | 40 | with ( 41 | Path(PARGS.output_path) / "cosim" / ("%s.json" % PARGS.testbench[0]) 42 | ).open() as json_file: 43 | ARGS = load(json_file) 44 | if "integer" not in PARGS.testbench[0]: 45 | NEW_BUF = byte_buf 46 | READ_BUF = read_byte_buf 47 | else: 48 | NEW_BUF = int_buf 49 | READ_BUF = read_int_buf 50 | 51 | XARGS = enc_args([ARGS["bin"]] + ARGS["sim"]) 52 | 53 | print("\nREGULAR EXECUTION") 54 | GHDL = dlopen(ARGS["bin"]) 55 | try: 56 | GHDL.main(len(XARGS) - 1, XARGS) 57 | # TOFIX With VHDL 93, the execution is Aborted and Python exits here 58 | except SystemExit as exc: 59 | if exc.code != 0: 60 | sys.exit(exc.code) 61 | dlclose(GHDL) 62 | 63 | print("\nPYTHON ALLOCATION") 64 | GHDL = dlopen(ARGS["bin"]) 65 | 66 | DATA = [111, 122, 133, 144, 155] 67 | 68 | # Two pointers/buffers are to be allocated 69 | BUF = [[] for c in range(2)] 70 | 71 | # Allocate and initialize shared data buffer 72 | BUF[1] = NEW_BUF(DATA + [0 for x in range(2 * len(DATA))]) 73 | 74 | # Fill 'params' vector 75 | BUF[0] = int_buf( 76 | [-(2 ** 31) + 10, -(2 ** 31), 3, 0, len(DATA)] # clk_step # update # block_length 77 | ) 78 | 79 | for x, v in enumerate(BUF): 80 | GHDL.set_string_ptr(x, v) 81 | 82 | for i, v in enumerate(READ_BUF(BUF[1])): 83 | print("py " + str(i) + ": " + str(v)) 84 | 85 | GHDL.ghdl_main(len(XARGS) - 1, XARGS) 86 | 87 | for i, v in enumerate(READ_BUF(BUF[1])): 88 | print("py " + str(i) + ": " + str(v)) 89 | 90 | dlclose(GHDL) 91 | -------------------------------------------------------------------------------- /examples/buffer/run.py: -------------------------------------------------------------------------------- 1 | """ 2 | Buffer 3 | ------ 4 | 5 | An array of type ``uint8_t`` is allocated in a C application and some values 6 | are written to the first ``1/3`` positions. Then, the VHDL simulation is 7 | executed, where the (external) array/buffer is used. 8 | 9 | In the VHDL testbenches, two vector pointers are created, each of them using 10 | a different access mechanism (``extfunc`` or ``extacc``). One of them is used to copy 11 | the first ``1/3`` elements to positions ``[1/3, 2/3)``, while incrementing each value 12 | by one. The second one is used to copy elements from ``[1/3, 2/3)`` to ``[2/3, 3/3)``, 13 | while incrementing each value by two. 14 | 15 | When the simulation is finished, the C application checks whether data was successfully 16 | copied/modified. The content of the buffer is printed both before and after the 17 | simulation. 18 | """ 19 | 20 | from sys import argv 21 | from pathlib import Path 22 | from subprocess import check_call 23 | from vunit import VUnit 24 | from cosim.vhpidirect import VHPIDIRECT 25 | 26 | COSIM = VHPIDIRECT() 27 | 28 | SRC = Path(__file__).parent / "src" 29 | 30 | BUILD_ONLY = False 31 | if "--build" in argv: 32 | argv.remove("--build") 33 | BUILD_ONLY = True 34 | 35 | # Compile C applications to objects 36 | C_IOBJ = SRC / "imain.o" 37 | C_BOBJ = SRC / "bmain.o" 38 | 39 | for val in [["int32_t", C_IOBJ], ["uint8_t", C_BOBJ]]: 40 | check_call( 41 | ["gcc", "-fPIC", "-DTYPE=%s" % val[0]] 42 | + ["-I%s" % inc for inc in COSIM.include] 43 | + ["-c", str(SRC / "main.c"), "-o", val[1]] 44 | ) 45 | 46 | # Enable the external feature for strings/byte_vectors and integer_vectors 47 | VU = VUnit.from_argv(vhdl_standard="2008", compile_builtins=False) 48 | VU.add_builtins(COSIM.bridge({"string": True, "integer_vector": True})) 49 | 50 | LIB = VU.add_library("lib") 51 | LIB.add_source_files(SRC / "tb_ext_*.vhd") 52 | 53 | # Add the C object to the elaboration of GHDL 54 | for tb in LIB.get_test_benches(pattern="*tb_ext*", allow_empty=False): 55 | tb.set_sim_option("ghdl.elab_flags", ["-Wl," + str(C_BOBJ)]) 56 | for tb in LIB.get_test_benches(pattern="*tb_ext*_integer*", allow_empty=False): 57 | tb.set_sim_option("ghdl.elab_flags", ["-Wl," + str(C_IOBJ)], overwrite=True) 58 | 59 | if BUILD_ONLY: 60 | VU.set_sim_option("ghdl.elab_flags", [COSIM.verscript()], overwrite=False) 61 | VU.set_sim_option("ghdl.elab_e", True) 62 | VU._args.elaborate = True # pylint: disable=protected-access 63 | VU.main(post_run=COSIM.post_func) 64 | else: 65 | VU.main() 66 | -------------------------------------------------------------------------------- /examples/buffer/src/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | Buffer 3 | 4 | An array of type uint8_t is allocated and some values are written to the first 1/3 5 | positions. Then, the VHDL simulation is executed, where the (external) array/buffer 6 | is used. When the simulation is finished, the results are checked. The content of 7 | the buffer is printed both before and after the simulation. 8 | 9 | This source file is used for both string/byte_vector and integer_vector. 10 | Accordingly, TYPE must be defined as uint8_t or int32_t during compilation. 11 | Keep in mind that the buffer (D) is of type uint8_t*, independently of TYPE, i.e. 12 | accesses are casted. 13 | 14 | NOTE: This file is expected to be used along with tb_ext_string.vhd, tb_ext_byte_vector.vhd 15 | or tb_ext_integer_vector.vhd 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include "vhpidirect_user.h" 22 | 23 | const uint32_t length = 5; 24 | 25 | /* 26 | Check procedure, to be executed when GHDL exits. 27 | The simulation is expected to copy the first 1/3 elements to positions [1/3, 2/3), 28 | while incrementing each value by one, and then copy elements from [1/3, 2/3) to 29 | [2/3, 3/3), while incrementing each value by two. 30 | */ 31 | static void exit_handler(void) { 32 | unsigned i, j, z, k; 33 | TYPE expected, got; 34 | k = 0; 35 | for (j=0; j<3; j++) { 36 | k += j; 37 | for(i=0; i 13 | #include 14 | #include 15 | 16 | extern int ghdl_main (int argc, char **argv); 17 | 18 | uint8_t *D[1]; 19 | const uint32_t length = 10; 20 | 21 | // Check procedure, to be executed when GHDL exits. 22 | // The simulation is expected to copy the first 1/3 elements to positions [1/3, 2/3), 23 | // while incrementing each value by one, and then copy elements from [1/3, 2/3) to 24 | // [2/3, 3/3), while incrementing each value by two. 25 | static void exit_handler(void) { 26 | int i, j, z, k; 27 | uint8_t expected, got; 28 | k = 0; 29 | 30 | for(i=0; i4.4.0"], 57 | description=( 58 | "Resources for interfacing VHDL and foreign languages with VUnit," 59 | "an open source unit testing framework" 60 | ), 61 | ) 62 | -------------------------------------------------------------------------------- /tests/acceptance/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VUnit/cosim/9ad3dfa20e34a269b2a4df8e2b18b934ab84dd72/tests/acceptance/__init__.py -------------------------------------------------------------------------------- /tests/acceptance/test_examples.py: -------------------------------------------------------------------------------- 1 | """ 2 | Verify that all example run scripts work correctly 3 | """ 4 | 5 | import sys 6 | from os import environ 7 | from pathlib import Path 8 | from subprocess import call 9 | import unittest 10 | import pytest 11 | from vunit.sim_if.common import has_simulator, simulator_check 12 | 13 | ROOT = Path(__file__).parent.parent.parent 14 | 15 | 16 | @unittest.skipIf( 17 | not has_simulator() 18 | or simulator_check(lambda simclass: not simclass.supports_vhpi()), 19 | "A simulator/backend that supports interfacing with external C code is required", 20 | ) 21 | class TestExamples(unittest.TestCase): 22 | """ 23 | Verify that example projects run correctly 24 | """ 25 | 26 | def setUp(self): 27 | self.output_path = str(Path(__file__).parent / "examples_run_out") 28 | self.report_file = str(Path(self.output_path) / "xunit.xml") 29 | 30 | def check(self, run_file, args=None, vhdl_standard="2008", exit_code=0): 31 | """ 32 | Run external run file and verify exit code 33 | """ 34 | args = args if args is not None else [] 35 | new_env = environ.copy() 36 | new_env["VUNIT_VHDL_STANDARD"] = vhdl_standard 37 | retcode = call( 38 | [sys.executable, run_file, "--output-path=%s" % self.output_path] 39 | + (args if args else []), 40 | env=new_env, 41 | ) 42 | self.assertEqual(retcode, exit_code) 43 | 44 | def test_copy(self): 45 | self.check( 46 | str(ROOT / "examples" / "copy" / "run.py"), 47 | args=["--clean", "--xunit-xml=%s" % self.report_file], 48 | ) 49 | 50 | def test_buffer(self): 51 | self.check( 52 | str(ROOT / "examples" / "buffer" / "run.py"), 53 | args=["--clean", "--xunit-xml=%s" % self.report_file], 54 | ) 55 | 56 | @pytest.mark.xfail 57 | def test_buffer_dyn(self): 58 | self.check( 59 | str(ROOT / "examples" / "buffer" / "run.py"), 60 | args=["--build", "--xunit-xml=%s" % self.report_file], 61 | ) 62 | self.check( 63 | str(ROOT / "examples" / "buffer" / "corun.py"), args=["tb_external_string"], 64 | ) 65 | self.check( 66 | str(ROOT / "examples" / "buffer" / "corun.py"), 67 | args=["tb_external_byte_vector"], 68 | ) 69 | self.check( 70 | str(ROOT / "examples" / "buffer" / "corun.py"), 71 | args=["tb_external_integer_vector"], 72 | ) 73 | -------------------------------------------------------------------------------- /tests/lint/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VUnit/cosim/9ad3dfa20e34a269b2a4df8e2b18b934ab84dd72/tests/lint/__init__.py -------------------------------------------------------------------------------- /tests/lint/pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # Specify a configuration file. 4 | #rcfile= 5 | 6 | # Python code to execute, usually for sys.path manipulation such as 7 | # pygtk.require(). 8 | #init-hook= 9 | 10 | # Add files or directories to the blacklist. They should be base names, not 11 | # paths. 12 | ignore=CVS 13 | 14 | # Pickle collected data for later comparisons. 15 | persistent=no 16 | 17 | # List of plugins (as comma separated values of python modules names) to load, 18 | # usually to register additional checkers. 19 | load-plugins= 20 | 21 | 22 | [MESSAGES CONTROL] 23 | 24 | # Enable the message, report, category or checker with the given id(s). You can 25 | # either give multiple identifier separated by comma (,) or put this option 26 | # multiple time. See also the "--disable" option for examples. 27 | #enable= 28 | 29 | # Disable the message, report, category or checker with the given id(s). You 30 | # can either give multiple identifiers separated by comma (,) or put this 31 | # option multiple times (only on the command line, not in the configuration 32 | # file where it should appear only once).You can also use "--disable=all" to 33 | # disable everything first and then reenable specific checks. For example, if 34 | # you want to run only the similarities checker, you can use "--disable=all 35 | # --enable=similarities". If you want to run only the classes checker, but have 36 | # no Warning level messages displayed, use"--disable=all --enable=classes 37 | # --disable=W" 38 | #disable= 39 | disable=too-few-public-methods, locally-disabled, redefined-variable-type, duplicate-code, file-ignored, 40 | useless-object-inheritance, bad-continuation, 41 | # Failed to suppress this locally in ostools.py 42 | subprocess-popen-preexec-fn 43 | 44 | [REPORTS] 45 | 46 | # Set the output format. Available formats are text, parseable, colorized, msvs 47 | # (visual studio) and html. You can also give a reporter class, eg 48 | # mypackage.mymodule.MyReporterClass. 49 | output-format=text 50 | 51 | # Put messages in a separate file for each module / package specified on the 52 | # command line instead of printing them on stdout. Reports (if any) will be 53 | # written in a file name "pylint_global.[txt|html]". 54 | files-output=no 55 | 56 | # Tells whether to display a full report or only the messages 57 | reports=no 58 | 59 | # Python expression which should return a note less than 10 (10 is the highest 60 | # note). You have access to the variables errors warning, statement which 61 | # respectively contain the number of errors / warnings messages and the total 62 | # number of statements analyzed. This is used by the global evaluation report 63 | # (RP0004). 64 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 65 | 66 | # Template used to display messages. This is a python new-style format string 67 | # used to format the message information. See doc for all details 68 | #msg-template= 69 | 70 | 71 | [VARIABLES] 72 | 73 | # Tells whether we should check for unused import in __init__ files. 74 | init-import=no 75 | 76 | # A regular expression matching the beginning of the name of dummy variables 77 | # (i.e. not used). 78 | dummy-variables-rgx=_$|dummy|args|kwargs 79 | 80 | # List of additional names supposed to be defined in builtins. Remember that 81 | # you should avoid to define new builtins when possible. 82 | additional-builtins= 83 | 84 | 85 | [BASIC] 86 | 87 | # List of builtins function names that should not be used, separated by a comma 88 | bad-functions=map,filter,apply,input 89 | 90 | # Regular expression which should only match correct module names 91 | module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ 92 | 93 | # Regular expression which should only match correct module level names 94 | const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ 95 | 96 | # Regular expression which should only match correct class names 97 | class-rgx=[A-Z_][a-zA-Z0-9]+$|T 98 | 99 | # Regular expression which should only match correct function names 100 | function-rgx=[a-z_][a-z0-9_]{2,}$ 101 | 102 | # Regular expression which should only match correct method names 103 | method-rgx=[a-z_][a-z0-9_]{2,}$ 104 | 105 | # Regular expression which should only match correct instance attribute names 106 | attr-rgx=[a-z_][a-z0-9_]{2,30}$ 107 | 108 | # Regular expression which should only match correct argument names 109 | argument-rgx=[a-z_][a-z0-9_]{2,30}$ 110 | 111 | # Regular expression which should only match correct variable names 112 | variable-rgx=[a-z_][a-z0-9_]{2,}$ 113 | 114 | # Regular expression which should only match correct attribute names in class 115 | # bodies 116 | class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,}|(__.*__))$ 117 | 118 | # Regular expression which should only match correct list comprehension / 119 | # generator expression variable names 120 | inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ 121 | 122 | # Good variable names which should always be accepted, separated by a comma 123 | good-names=i,j,k,ex,Run,_,ui,vu,fg,bg 124 | 125 | # Bad variable names which should always be refused, separated by a comma 126 | bad-names=foo,bar,baz,toto,tutu,tata 127 | 128 | # Regular expression which should only match function or class names that do 129 | # not require a docstring. 130 | # TODO: Do not require docstring on test_ methods 131 | no-docstring-rgx=__.*__|test_.* 132 | 133 | # Minimum line length for functions/classes that require docstrings, shorter 134 | # ones are exempt. 135 | docstring-min-length=4 136 | 137 | 138 | [TYPECHECK] 139 | 140 | # Tells whether missing members accessed in mixin class should be ignored. A 141 | # mixin class is detected if its name ends with "mixin" (case insensitive). 142 | ignore-mixin-members=yes 143 | 144 | # List of classes names for which member attributes should not be checked 145 | # (useful for classes with attributes dynamically set). 146 | ignored-classes=SQLObject 147 | 148 | # List of members which are set dynamically and missed by pylint inference 149 | # system, and so shouldn't trigger E0201 when accessed. Python regular 150 | # expressions are accepted. 151 | generated-members=REQUEST,acl_users,aq_parent 152 | 153 | 154 | [FORMAT] 155 | 156 | # Maximum number of characters on a single line. 157 | max-line-length=120 158 | 159 | # Regexp for a line that is allowed to be longer than the limit. 160 | ignore-long-lines=^\s*(# )??$ 161 | 162 | # Allow the body of an if to be on the same line as the test if there is no 163 | # else. 164 | single-line-if-stmt=no 165 | 166 | # List of optional constructs for which whitespace checking is disabled 167 | no-space-check=trailing-comma,dict-separator 168 | 169 | # Maximum number of lines in a module 170 | max-module-lines=1000 171 | 172 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 173 | # tab). 174 | indent-string=' ' 175 | 176 | 177 | [MISCELLANEOUS] 178 | 179 | # List of note tags to take in consideration, separated by a comma. 180 | notes=FIXME,XXX,TODO 181 | 182 | 183 | [SIMILARITIES] 184 | 185 | # Minimum lines number of a similarity. 186 | min-similarity-lines=4 187 | 188 | # Ignore comments when computing similarities. 189 | ignore-comments=yes 190 | 191 | # Ignore docstrings when computing similarities. 192 | ignore-docstrings=yes 193 | 194 | # Ignore imports when computing similarities. 195 | ignore-imports=yes 196 | 197 | 198 | [IMPORTS] 199 | 200 | # Deprecated modules which should not be used, separated by a comma 201 | deprecated-modules=regsub,TERMIOS,Bastion,rexec 202 | 203 | # Create a graph of every (i.e. internal and external) dependencies in the 204 | # given file (report RP0402 must not be disabled) 205 | import-graph= 206 | 207 | # Create a graph of external dependencies in the given file (report RP0402 must 208 | # not be disabled) 209 | ext-import-graph= 210 | 211 | # Create a graph of internal dependencies in the given file (report RP0402 must 212 | # not be disabled) 213 | int-import-graph= 214 | 215 | 216 | [CLASSES] 217 | # List of method names used to declare (i.e. assign) instance attributes. 218 | defining-attr-methods=__init__,__new__,setUp 219 | 220 | # List of valid names for the first argument in a class method. 221 | valid-classmethod-first-arg=cls 222 | 223 | # List of valid names for the first argument in a metaclass class method. 224 | valid-metaclass-classmethod-first-arg=mcs 225 | 226 | 227 | [DESIGN] 228 | 229 | # Maximum number of arguments for function / method 230 | max-args=6 231 | 232 | # Argument names that match this expression will be ignored. Default to name 233 | # with leading underscore 234 | ignored-argument-names=_.*|kwargs|args 235 | 236 | # Maximum number of locals for function / method body 237 | max-locals=15 238 | 239 | # Maximum number of return / yield for function / method body 240 | max-returns=6 241 | 242 | # Maximum number of branch for function / method body 243 | max-branches=12 244 | 245 | # Maximum number of statements in function / method body 246 | max-statements=50 247 | 248 | # Maximum number of parents for a class (see R0901). 249 | max-parents=7 250 | 251 | # Maximum number of attributes for a class (see R0902). 252 | max-attributes=7 253 | 254 | # Minimum number of public methods for a class (see R0903). 255 | min-public-methods=2 256 | 257 | # Maximum number of public methods for a class (see R0904). 258 | max-public-methods=20 259 | 260 | 261 | [EXCEPTIONS] 262 | 263 | # Exceptions that will emit a warning when being caught. Defaults to 264 | # "Exception" 265 | overgeneral-exceptions=Exception 266 | -------------------------------------------------------------------------------- /tests/lint/test_lint.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | # You can obtain one at http://mozilla.org/MPL/2.0/. 4 | # 5 | # Copyright (c) 2014-2019, Lars Asplund lars.anders.asplund@gmail.com 6 | 7 | """ 8 | PEP8 check 9 | """ 10 | 11 | import unittest 12 | from subprocess import check_call 13 | from sys import executable 14 | from glob import glob 15 | from pathlib import Path 16 | 17 | 18 | def get_files_and_folders(): 19 | """ 20 | Return all files and folders which shall be arguments to pycodestyle and pylint 21 | """ 22 | root = Path(__file__).parent.parent.parent 23 | ret = list(glob(str(root / "**" / "*.py"), recursive=True)) 24 | ret.remove(str(root / "docs" / "conf.py")) 25 | return ret 26 | 27 | 28 | class TestMyPy(unittest.TestCase): 29 | """ 30 | Run MyPy static type analysis 31 | """ 32 | 33 | @staticmethod 34 | def test_mypy(): 35 | check_call([executable, "-m", "mypy", "cosim"]) 36 | 37 | 38 | class TestPycodestyle(unittest.TestCase): 39 | """ 40 | Test that all python code follows PEP8 Python coding standard 41 | """ 42 | 43 | @staticmethod 44 | def test_pycodestyle(): 45 | check_call( 46 | [ 47 | executable, 48 | "-m", 49 | "pycodestyle", 50 | "--show-source", 51 | "--show-pep8", 52 | "--max-line-length=120", 53 | # W503 mutually exclusive with W504 54 | # E722 bare except checked by pylint 55 | "--ignore=E402,W503,E722,E501,E203", 56 | ] 57 | + get_files_and_folders() 58 | ) 59 | 60 | 61 | class TestPylint(unittest.TestCase): 62 | """ 63 | Check that there are no pylint errors or warnings 64 | """ 65 | 66 | @staticmethod 67 | def test_pylint(): 68 | check_call( 69 | [ 70 | executable, 71 | "-m", 72 | "pylint", 73 | "--rcfile=" + str(Path(__file__).parent / "pylintrc"), 74 | ] 75 | + get_files_and_folders() 76 | ) 77 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py{36,37,38}-{fmt,lint,docs,-acceptance-ghdl} 3 | skip_missing_interpreters = True 4 | 5 | [testenv] 6 | recreate=True 7 | 8 | deps= 9 | fmt: black 10 | pytest 11 | {toxinidir}/../vunit 12 | lint: pycodestyle 13 | lint: pylint 14 | lint: mypy 15 | lint: -Ur{toxinidir}/requirements.txt 16 | docs: -Ur{toxinidir}/requirements.txt 17 | docs: docutils 18 | docs: sphinx 19 | 20 | setenv= 21 | acceptance-ghdl: VUNIT_SIMULATOR=ghdl 22 | 23 | commands= 24 | fmt: {envpython} -m black ./ {posargs} 25 | lint: {envpython} -m pytest -v -ra tests/lint {posargs} 26 | acceptance: {envpython} -m pytest -v -ra tests/acceptance {posargs} 27 | docs: {envpython} -m sphinx -TEWanb html docs {envtmpdir}/docsbuild {posargs} 28 | --------------------------------------------------------------------------------