diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 89535c1937..6e36bedef3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,11 +3,19 @@ stages: - trigger - test +# Pipeline mode: +# - default (CI_PIPELINE_MODE unset): normal pytest CI +# - implementation (CI_PIPELINE_MODE=implementation): dedicated implementation pipeline + generator: stage: generate image: python:3.8-alpine variables: N_TESTS_PER_YAML: 4 + rules: + - if: '$CI_PIPELINE_MODE == "implementation"' + when: never + - when: on_success tags: - k8s-default before_script: @@ -21,9 +29,25 @@ generator: pytests: stage: trigger + rules: + - if: '$CI_PIPELINE_MODE == "implementation"' + when: never + - when: on_success trigger: include: - local: test/pytest/ci-template.yml - artifact: test/pytest/pytests.yml job: generator strategy: depend + +implementation-pytests: + stage: trigger + rules: + - if: '$CI_PIPELINE_MODE == "implementation"' + when: on_success + - when: never + trigger: + include: + - local: test/pytest/implementation/ci-template.yml + - local: test/pytest/implementation/pytests.yml + strategy: depend diff --git a/test/pytest/conftest.py b/test/pytest/conftest.py index 7841a1a989..654cd3d586 100644 --- a/test/pytest/conftest.py +++ b/test/pytest/conftest.py @@ -56,6 +56,7 @@ def synthesis_config(): 'run_synthesis': str_to_bool(os.getenv('RUN_SYNTHESIS', 'false')), 'tools_version': { 'Vivado': os.getenv('VIVADO_VERSION', '2020.1'), + 'VivadoAccelerator': os.getenv('VIVADO_VERSION', '2020.1'), 'Vitis': os.getenv('VITIS_VERSION', '2024.1'), 'Quartus': os.getenv('QUARTUS_VERSION', 'latest'), 'oneAPI': os.getenv('ONEAPI_VERSION', '2025.0.1'), @@ -66,4 +67,18 @@ def synthesis_config(): 'Quartus': {'synth': True, 'fpgasynth': False}, 'oneAPI': {'build_type': 'report', 'run': False}, }, + 'implementation_build_args': { + # Full accelerator flow for implementation dataset collection: + # run HLS synth, downstream Vivado synth, and bitfile generation. + 'VivadoAccelerator': { + 'reset': True, + 'csim': False, + 'synth': True, + 'cosim': True, + 'validation': False, + 'export': True, + 'vsynth': True, + 'bitfile': True, + } + }, } diff --git a/test/pytest/generate_ci_yaml.py b/test/pytest/generate_ci_yaml.py index 684abc0511..bf9fed81e2 100644 --- a/test/pytest/generate_ci_yaml.py +++ b/test/pytest/generate_ci_yaml.py @@ -25,6 +25,7 @@ # Blacklisted tests will be skipped BLACKLIST = {'test_reduction'} +EXCLUDED_DIRS = {'implementation'} # Long-running tests will not be bundled with other tests LONGLIST = {'test_hgq_layers', 'test_hgq_players', 'test_qkeras', 'test_pytorch_api'} @@ -44,6 +45,11 @@ } +def include_in_default_ci(path: Path) -> bool: + """Exclude dedicated suites (e.g. implementation-only jobs) from the default CI matrix.""" + return not any(part in EXCLUDED_DIRS for part in path.parts) + + def collect_test_functions_from_ast(test_file): """Collect all test function names using AST parsing (no imports).""" with open(test_file, encoding='utf-8') as f: @@ -79,7 +85,8 @@ def generate_test_yaml(test_root='.'): test_paths = [ path for path in test_root.glob('**/test_*.py') - if path.stem not in (BLACKLIST | LONGLIST | set(SPLIT_BY_TEST_CASE.keys()) | KERAS3_LIST) + if include_in_default_ci(path) + and path.stem not in (BLACKLIST | LONGLIST | set(SPLIT_BY_TEST_CASE.keys()) | KERAS3_LIST) ] need_example_models = [uses_example_model(path) for path in test_paths] @@ -99,7 +106,7 @@ def generate_test_yaml(test_root='.'): else: yml.update(diff_yml) - test_paths = [path for path in test_root.glob('**/test_*.py') if path.stem in LONGLIST] + test_paths = [path for path in test_root.glob('**/test_*.py') if include_in_default_ci(path) and path.stem in LONGLIST] for path in test_paths: name = path.stem.replace('test_', '') test_file = str(path.relative_to(test_root)) @@ -107,7 +114,9 @@ def generate_test_yaml(test_root='.'): diff_yml = yaml.safe_load(template.format(name, '.pytest', test_file, int(needs_examples))) yml.update(diff_yml) - test_paths = [path for path in test_root.glob('**/test_*.py') if path.stem in SPLIT_BY_TEST_CASE] + test_paths = [ + path for path in test_root.glob('**/test_*.py') if include_in_default_ci(path) and path.stem in SPLIT_BY_TEST_CASE + ] for path in test_paths: stem = path.stem name_base = stem.replace('test_', '') @@ -125,7 +134,9 @@ def generate_test_yaml(test_root='.'): else: yml.update(diff_yml) - keras3_paths = [path for path in test_root.glob('**/test_*.py') if path.stem in KERAS3_LIST] + keras3_paths = [ + path for path in test_root.glob('**/test_*.py') if include_in_default_ci(path) and path.stem in KERAS3_LIST + ] keras3_need_examples = [uses_example_model(path) for path in keras3_paths] k3_idxs = list(range(len(keras3_need_examples))) diff --git a/test/pytest/implementation/README.md b/test/pytest/implementation/README.md new file mode 100644 index 0000000000..5e2a0dc724 --- /dev/null +++ b/test/pytest/implementation/README.md @@ -0,0 +1,67 @@ +# Implementation CI Suite + +This directory contains manually listed implementation tests. These jobs run full backend implementation flows and collect dataset artifacts with actual tool reports and generated project files. + +The normal pytest CI should stay separate from this suite. Implementation tests are intended for manually triggered dataset collection, not for the regular fast test matrix. + +## Pipeline Layout + +Implementation CI uses: + +- `test/pytest/implementation/pytests.yml`: static job list +- `test/pytest/implementation/ci-template.yml`: implementation-specific GitLab templates +- `test/pytest/implementation/implementation_helpers.py`: dataset artifact helpers used by implementation tests + +Jobs in `pytests.yml` extend backend-specific runtime templates from `ci-template.yml`, for example: + +- `.pytest-implementation-vivadoaccelerator` +- `.pytest-implementation-vivado-runtime` + +Each concrete job sets `PYTESTFILE` to one specific test function, so each model/backend run becomes a separate CI job and artifact set. + +## Dataset Artifacts + +Implementation tests should use `run_implementation_collection_test()` from `implementation_helpers.py`. The helper: + +- runs `hls_model.build(...)` with backend-specific implementation build args +- captures full terminal output to `*_build.log` +- writes the hls4ml report returned by the backend to `*_hls4ml_report.json` +- writes one compact dataset record to `*_dataset.json` +- compresses the generated project directory to `*_project.zip` +- records the project archive path, size, and SHA256 in the dataset record + +The dataset record includes: + +- hls4ml source commit and repository URL +- example-models source commit and model file names +- backend, project name, board/part metadata, and build args +- backend-specific toolchain versions +- CI metadata for finding the run later +- build timing and the hls4ml report +- pointers to the log, hls4ml report JSON, bitstream files, and project archive + +`IMPLEMENTATION_DATASET_DIR` controls where artifacts are written. In CI it is set to: + +```bash +test/pytest/implementation +``` + +## Test Policy + +Implementation tests should load models from the `example-models` submodule instead of defining models inline. This keeps dataset records tied to a model name, model file, and exact `example-models` commit. + +Keep test code focused on: + +- loading the example model +- converting it with the backend under test +- passing clear metadata to `run_implementation_collection_test()` + +Avoid duplicating dataset/report parsing in individual tests. Add backend-specific dataset behavior in `implementation_helpers.py` when needed. + +## Adding A Test + +1. Add or update a `test_*.py` file in this directory. +2. Load a model from `example-models`. +3. Add model metadata including name, source, source commit, and model file paths. +4. Add a static job in `pytests.yml` with `PYTESTFILE` pointing to the specific test function. +5. Set tool versions and backend runtime template through the job/template variables. diff --git a/test/pytest/implementation/ci-template.yml b/test/pytest/implementation/ci-template.yml new file mode 100644 index 0000000000..4f481ccaed --- /dev/null +++ b/test/pytest/implementation/ci-template.yml @@ -0,0 +1,64 @@ +.pytest-implementation: + stage: test + image: gitlab-registry.cern.ch/fastmachinelearning/hls4ml-testing:0.6.3.base + tags: + - k8s-default + variables: + CONDA_ENV: "hls4ml-testing" + EXTRA_DEPS: "[da,testing,testing-keras2,sr,optimization]" + IMPLEMENTATION_DATASET_DIR: "test/pytest/implementation" + before_script: + - eval "$(conda shell.bash hook)" + - conda activate "$CONDA_ENV" + - git config --global --add safe.directory /builds/fastmachinelearning/hls4ml + - git submodule update --init --recursive hls4ml/templates/catapult/ + - if [ $EXAMPLEMODEL == 1 ]; then git submodule update --init example-models; fi + - pip install .${EXTRA_DEPS} + artifacts: + when: always + reports: + junit: + - test/pytest/report.xml + paths: + - test/pytest/*.tar.gz + - test/pytest/implementation/*_dataset.json + - test/pytest/implementation/*_hls4ml_report.json + - test/pytest/implementation/*_build.log + - test/pytest/implementation/*_project.zip + +.pytest-implementation-vivado-runtime: + extends: .pytest-implementation + script: + - mkdir -p cmd_vivado_${VIVADO_VERSION} + - echo '#!/bin/bash' > cmd_vivado_${VIVADO_VERSION}/vivado_hls + - echo "apptainer exec --cleanenv --env LANG=C,LC_ALL=C /cvmfs/projects.cern.ch/hls4ml/vivado/${VIVADO_VERSION} vivado_hls \"\$@\"" >> cmd_vivado_${VIVADO_VERSION}/vivado_hls + - chmod +x cmd_vivado_${VIVADO_VERSION}/vivado_hls + - echo '#!/bin/bash' > cmd_vivado_${VIVADO_VERSION}/vivado + - echo "apptainer exec --cleanenv --env LANG=C,LC_ALL=C /cvmfs/projects.cern.ch/hls4ml/vivado/${VIVADO_VERSION} vivado \"\$@\"" >> cmd_vivado_${VIVADO_VERSION}/vivado + - chmod +x cmd_vivado_${VIVADO_VERSION}/vivado + - export PATH=$PWD/cmd_vivado_${VIVADO_VERSION}:$PATH + - export IMPLEMENTATION_DATASET_DIR=$PWD/test/pytest/implementation + - cd test/pytest + - pytest $PYTESTFILE -s -rA --junitxml=report.xml --randomly-seed=42 --randomly-dont-reorganize --randomly-dont-reset-seed + +.pytest-implementation-vitis-runtime: + extends: .pytest-implementation + script: + - mkdir -p cmd_vivado_${VIVADO_VERSION} + - echo '#!/bin/bash' > cmd_vivado_${VIVADO_VERSION}/vivado + - echo "apptainer exec --cleanenv --env LANG=C,LC_ALL=C /cvmfs/projects.cern.ch/hls4ml/vitis/${VITIS_VERSION} vivado \"\$@\"" >> cmd_vivado_${VIVADO_VERSION}/vivado + - chmod +x cmd_vivado_${VIVADO_VERSION}/vivado + - mkdir -p cmd_vitis_${VITIS_VERSION} + - echo '#!/bin/bash' > cmd_vitis_${VITIS_VERSION}/vitis-run + - echo "apptainer exec /cvmfs/projects.cern.ch/hls4ml/vitis/${VITIS_VERSION} vitis-run \"\$@\"" >> cmd_vitis_${VITIS_VERSION}/vitis-run + - chmod +x cmd_vitis_${VITIS_VERSION}/vitis-run + - echo '#!/bin/bash' > cmd_vitis_${VITIS_VERSION}/v++ + - echo "apptainer exec /cvmfs/projects.cern.ch/hls4ml/vitis/${VITIS_VERSION} v++ \"\$@\"" >> cmd_vitis_${VITIS_VERSION}/v++ + - chmod +x cmd_vitis_${VITIS_VERSION}/v++ + - echo '#!/bin/bash' > cmd_vitis_${VITIS_VERSION}/vitis + - echo "apptainer exec /cvmfs/projects.cern.ch/hls4ml/vitis/${VITIS_VERSION} vitis \"\$@\"" >> cmd_vitis_${VITIS_VERSION}/vitis + - chmod +x cmd_vitis_${VITIS_VERSION}/vitis + - export PATH=$PWD/cmd_vivado_${VIVADO_VERSION}:$PWD/cmd_vitis_${VITIS_VERSION}:$PATH + - export IMPLEMENTATION_DATASET_DIR=$PWD/test/pytest/implementation + - cd test/pytest + - pytest $PYTESTFILE -s -rA --junitxml=report.xml --randomly-seed=42 --randomly-dont-reorganize --randomly-dont-reset-seed diff --git a/test/pytest/implementation/implementation_helpers.py b/test/pytest/implementation/implementation_helpers.py new file mode 100644 index 0000000000..c4090be81c --- /dev/null +++ b/test/pytest/implementation/implementation_helpers.py @@ -0,0 +1,277 @@ +import json +import os +import subprocess +import sys +import time +from contextlib import contextmanager +from datetime import datetime, timezone +from hashlib import sha256 +from pathlib import Path +from zipfile import ZIP_DEFLATED, ZipFile + +import pytest + +DATASET_SCHEMA_VERSION = 'implementation-dataset/v1' +DATASET_DIR_ENV = 'IMPLEMENTATION_DATASET_DIR' +DEFAULT_DATASET_DIR = Path(__file__).parent + +EXPECTED_REPORT_KEYS = { + 'VivadoAccelerator': { + 'CSynthesisReport', + 'CosimReport', + 'CosimResults', + 'ImplementationReport', + 'TimingReport', + 'VivadoSynthReport', + }, +} + +REQUIRED_METADATA_FIELDS = { + 'VivadoAccelerator': {'board', 'part'}, +} + +BITFILE_REQUIRED_BACKENDS = {'VivadoAccelerator'} + +TOOLCHAINS_BY_BACKEND = { + 'VivadoAccelerator': {'vivado': ('VivadoAccelerator', 'Vivado')}, + 'VitisUnified': { + 'vivado': ('Vivado',), + 'vitis': ('VitisUnified', 'Vitis'), + }, +} + + +def _utc_now(): + return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace('+00:00', 'Z') + + +def _project_root(): + return Path(__file__).parents[3] + + +def _git_commit(path): + try: + return subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=path, text=True).strip() + except (FileNotFoundError, subprocess.CalledProcessError): + return None + + +def _portable_path(path): + return os.path.relpath(path, _project_root()) + + +def _collect_files(output_dir, suffixes=None): + output_path = Path(output_dir) + files = [] + if not output_path.exists(): + return files + + for path in sorted(output_path.rglob('*')): + if not path.is_file() or (suffixes is not None and path.suffix not in suffixes): + continue + entry = { + 'path': str(path.relative_to(output_path)), + 'size_bytes': path.stat().st_size, + } + files.append(entry) + return files + + +def _file_sha256(path): + digest = sha256() + with open(path, 'rb') as fp: + for chunk in iter(lambda: fp.read(1024 * 1024), b''): + digest.update(chunk) + return digest.hexdigest() + + +def _zip_directory(directory, zip_path): + directory = Path(directory) + zip_path = Path(zip_path) + zip_path.parent.mkdir(parents=True, exist_ok=True) + with ZipFile(zip_path, 'w', ZIP_DEFLATED) as archive: + for path in sorted(directory.rglob('*')): + if path.is_file(): + archive.write(path, path.relative_to(directory.parent)) + return zip_path + + +def _dataset_dir(): + return Path(os.getenv(DATASET_DIR_ENV, str(DEFAULT_DATASET_DIR))) + + +def _artifact_path(filename): + dataset_dir = _dataset_dir() + dataset_dir.mkdir(parents=True, exist_ok=True) + return dataset_dir / filename + + +def _write_json(data, filename): + out_path = _artifact_path(filename) + with open(out_path, 'w') as fp: + json.dump(data, fp, indent=4, sort_keys=True) + return out_path + + +@contextmanager +def _capture_terminal_output(path): + path = Path(path) + path.parent.mkdir(parents=True, exist_ok=True) + sys.stdout.flush() + sys.stderr.flush() + saved_stdout = os.dup(1) + saved_stderr = os.dup(2) + tee = subprocess.Popen(['tee', str(path)], stdin=subprocess.PIPE) + os.dup2(tee.stdin.fileno(), 1) + os.dup2(tee.stdin.fileno(), 2) + + try: + yield path + finally: + sys.stdout.flush() + sys.stderr.flush() + try: + os.dup2(saved_stdout, 1) + os.dup2(saved_stderr, 2) + finally: + tee.stdin.close() + tee.wait() + os.close(saved_stdout) + os.close(saved_stderr) + + +def _validate_metadata(backend, metadata): + if metadata is None: + metadata = {} + if not isinstance(metadata, dict): + raise TypeError('Implementation metadata must be a dictionary.') + + missing = REQUIRED_METADATA_FIELDS.get(backend, set()) - metadata.keys() + if missing: + raise AssertionError(f'Missing implementation metadata for {backend}: {sorted(missing)}') + return metadata + + +def _toolchain_versions(config, backend): + versions = config.get('tools_version', {}) + toolchains = TOOLCHAINS_BY_BACKEND.get(backend) + if toolchains is None: + return {backend.lower(): versions.get(backend, 'unknown')} + + resolved = {} + for tool_name, version_keys in toolchains.items(): + resolved[tool_name] = next((versions[key] for key in version_keys if versions.get(key)), 'unknown') + return resolved + + +def _build_dataset( + config, + hls_model, + test_case_id, + backend, + metadata, + report, + build_args, + build_started_at, + build_finished_at, + build_duration_seconds, + hls4ml_report_path, + build_output_path, + project_archive_path, +): + output_dir = hls_model.config.get_output_dir() + model = metadata.get('model', {}) + run_metadata = {key: value for key, value in metadata.items() if key not in {'artifact_id', 'model'}} + + return { + 'schema_version': DATASET_SCHEMA_VERSION, + 'created_at_utc': _utc_now(), + 'test_id': test_case_id, + 'source': { + 'hls4ml_commit': _git_commit(_project_root()), + 'repository_url': 'https://github.com/fastmachinelearning/hls4ml', + }, + 'model': model, + 'hls_config': { + 'backend': backend, + 'project_name': hls_model.config.get_project_name(), + 'build_args': build_args, + }, + 'metadata': run_metadata, + 'toolchain': _toolchain_versions(config, backend), + 'ci': { + 'pipeline_id': os.getenv('CI_PIPELINE_ID'), + 'job_id': os.getenv('CI_JOB_ID'), + 'project_url': os.getenv('CI_PROJECT_URL'), + 'job_url': os.getenv('CI_JOB_URL'), + 'runner_description': os.getenv('CI_RUNNER_DESCRIPTION'), + 'runner_tags': os.getenv('CI_RUNNER_TAGS'), + }, + 'build': { + 'status': 'success', + 'started_at_utc': build_started_at, + 'finished_at_utc': build_finished_at, + 'duration_seconds': build_duration_seconds, + }, + 'hls4ml_report': report, + 'artifacts': { + 'output_dir': _portable_path(output_dir), + 'hls4ml_report_json': _portable_path(hls4ml_report_path), + 'build_output_log': _portable_path(build_output_path), + 'bitstreams': _collect_files(output_dir, {'.bit'}), + 'project_archive': { + 'path': _portable_path(project_archive_path), + 'size_bytes': Path(project_archive_path).stat().st_size, + 'sha256': _file_sha256(project_archive_path), + }, + }, + } + + +def run_implementation_collection_test(config, hls_model, test_case_id, backend, metadata=None): + """ + Build an implementation target and emit a dataset record plus raw backend reports. + """ + metadata = _validate_metadata(backend, metadata) + artifact_id = metadata.get('artifact_id', test_case_id) + build_args = config.get('implementation_build_args', {}).get(backend, config.get('build_args', {}).get(backend, {})) + build_output_path = _artifact_path(f'{artifact_id}_build.log') + + started_at = _utc_now() + started = time.monotonic() + try: + with _capture_terminal_output(build_output_path): + report = hls_model.build(**build_args) + except Exception as e: + pytest.fail(f'hls_model.build failed: {e}') + finished_at = _utc_now() + duration = round(time.monotonic() - started, 3) + + expected_keys = EXPECTED_REPORT_KEYS.get(backend, set()) + assert report and expected_keys.issubset(report.keys()), ( + f'Implementation failed: missing expected report keys: expected {expected_keys}, got {set(report.keys())}' + ) + + output_dir = hls_model.config.get_output_dir() + bitfiles = _collect_files(output_dir, {'.bit'}) + if backend in BITFILE_REQUIRED_BACKENDS: + assert bitfiles, f'Implementation failed: no bitstream was generated in {output_dir}' + + hls4ml_report_path = _write_json(report, f'{artifact_id}_hls4ml_report.json') + project_archive_path = _zip_directory(output_dir, _artifact_path(f'{artifact_id}_project.zip')) + dataset = _build_dataset( + config=config, + hls_model=hls_model, + test_case_id=test_case_id, + backend=backend, + metadata=metadata, + report=report, + build_args=build_args, + build_started_at=started_at, + build_finished_at=finished_at, + build_duration_seconds=duration, + hls4ml_report_path=hls4ml_report_path, + build_output_path=build_output_path, + project_archive_path=project_archive_path, + ) + _write_json(dataset, f'{artifact_id}_dataset.json') diff --git a/test/pytest/implementation/pytests.yml b/test/pytest/implementation/pytests.yml new file mode 100644 index 0000000000..004fabd091 --- /dev/null +++ b/test/pytest/implementation/pytests.yml @@ -0,0 +1,18 @@ +.pytest-implementation-vivadoaccelerator: + extends: + - .pytest-implementation-vivado-runtime + variables: + VIVADO_VERSION: "2020.1" + EXAMPLEMODEL: 1 + +pytest.implementation.keras_1layer_vivadoacc: + extends: + - .pytest-implementation-vivadoaccelerator + variables: + PYTESTFILE: implementation/test_vivadoaccelerator_implementation.py::test_keras_1layer + +pytest.implementation.keras_conv1d_small_vivadoacc: + extends: + - .pytest-implementation-vivadoaccelerator + variables: + PYTESTFILE: implementation/test_vivadoaccelerator_implementation.py::test_keras_conv1d_small diff --git a/test/pytest/implementation/test_vivadoaccelerator_implementation.py b/test/pytest/implementation/test_vivadoaccelerator_implementation.py new file mode 100644 index 0000000000..e9b589d0d4 --- /dev/null +++ b/test/pytest/implementation/test_vivadoaccelerator_implementation.py @@ -0,0 +1,92 @@ +import subprocess +from pathlib import Path + +from implementation_helpers import run_implementation_collection_test +from tensorflow.keras.models import model_from_json + +import hls4ml + +test_root_path = Path(__file__).parent +example_model_path = (test_root_path / '../../../example-models').resolve() + +BACKEND = 'VivadoAccelerator' +IO_TYPE = 'io_parallel' +VIVADOACC_BOARD = 'zcu102' +VIVADOACC_PART = 'xczu9eg-ffvb1156-2-e' + + +def _load_keras_example_model(model_json, weights_h5): + model_path = example_model_path / model_json + with model_path.open('r') as f: + model = model_from_json(f.read()) + model.load_weights(example_model_path / weights_h5) + return model + + +def _example_models_commit(): + try: + return subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=example_model_path, text=True).strip() + except (FileNotFoundError, subprocess.CalledProcessError): + return None + + +def _run_example_model_implementation( + *, + model_name, + model_json, + weights_h5, + test_case_id, + synthesis_config, +): + model = _load_keras_example_model(model_json, weights_h5) + + hls_config = hls4ml.utils.config_from_keras_model(model, granularity='name', backend=BACKEND) + output_dir = str(test_root_path / test_case_id) + hls_model = hls4ml.converters.convert_from_keras_model( + model, + hls_config=hls_config, + output_dir=output_dir, + backend=BACKEND, + io_type=IO_TYPE, + board=VIVADOACC_BOARD, + part=VIVADOACC_PART, + ) + + run_implementation_collection_test( + config=synthesis_config, + hls_model=hls_model, + test_case_id=test_case_id, + backend=BACKEND, + metadata={ + 'artifact_id': f'{model_name}_vivadoacc_{VIVADOACC_BOARD}', + 'model': { + 'name': model_name, + 'source': 'example-models', + 'source_commit': _example_models_commit(), + 'model_json': str(Path(model_json)), + 'weights_h5': str(Path(weights_h5)), + }, + 'board': VIVADOACC_BOARD, + 'part': VIVADOACC_PART, + }, + ) + + +def test_keras_1layer(test_case_id, synthesis_config): + _run_example_model_implementation( + model_name='keras_1layer', + model_json='keras/KERAS_1layer.json', + weights_h5='keras/KERAS_1layer_weights.h5', + test_case_id=test_case_id, + synthesis_config=synthesis_config, + ) + + +def test_keras_conv1d_small(test_case_id, synthesis_config): + _run_example_model_implementation( + model_name='keras_conv1d_small', + model_json='keras/KERAS_conv1d_small.json', + weights_h5='keras/KERAS_conv1d_small_weights.h5', + test_case_id=test_case_id, + synthesis_config=synthesis_config, + )