Compare commits

..

27 Commits

Author SHA1 Message Date
5d8d995a04 chore(changelog): update changelog for v0.6.1
All checks were successful
Build and Publish nightly package / build-and-publish (release) Successful in 2m11s
2025-07-16 09:50:07 +00:00
36f6e7314a chore(version): bump to 0.6.1
All checks were successful
Auto Changelog & (Release) / release (push) Successful in 1m8s
Build and Publish nightly package / build-and-publish (push) Successful in 2m24s
2025-07-16 11:48:54 +02:00
bc8ae46b4a chore(changelog): update unreleased changelog 2025-07-16 09:47:41 +00:00
dde2363ad7 chore(config): add git-cliff configuration file
All checks were successful
Auto Changelog & (Release) / release (push) Successful in 1m15s
Build and Publish nightly package / build-and-publish (push) Successful in 2m35s
2025-07-16 11:46:06 +02:00
32e3b51a77 chore(version): add version file for tracking 2025-07-16 11:41:54 +02:00
da387f2ee6 feat(ci): add workflows for nightly builds and releases
- Introduce separate workflows for nightly and release builds
- Add scripts for version management, asset uploads, and cleanup
- Improve Python environment setup and dependency caching
2025-07-16 11:41:35 +02:00
08679c2680 style(pyproject): simplify include array formatting 2025-07-16 11:41:18 +02:00
7b6f9ef224 chore: Updates project version to 0.6.0
All checks were successful
Build and Publish / build-and-publish (push) Successful in 1m55s
- Updates the project version in pyproject.toml
- Reflects changes for a new release
2025-07-16 11:30:18 +02:00
6ca389d5cb refactor: use typer for CLI argument parsing
- Replaces `argparse` with `typer` for command-line argument
  parsing to improve CLI interface and maintainability.
- Converts commands to subcommands using `typer.Typer()`.
- Streamlines CLI structure and enhances user experience.
2025-07-16 11:29:31 +02:00
1d7bc19965 style: Updates VS Code editor color scheme
- Updates the VS Code settings to customize the color scheme for
  the activity bar and activity bar badge.
- Enhances the visual appearance and user experience of the editor.
2025-07-16 11:04:58 +02:00
175bf4882a refactor: improves project configuration
- Removes unnecessary fields and configures the project.
- Streamlines tool options for clarity.
- Simplifies dependencies.
2025-07-16 11:04:44 +02:00
0d26c42f8a chore: remove build-deb.yml workflow file
Signed-off-by: Max P. <Mail@MPassarello.de>
2025-05-01 17:44:36 +02:00
fa7e738b7e docs(readme): expand README with detailed usage and setup
- Add comprehensive overview of HDLBuild features and functionality
- Include installation prerequisites and step-by-step instructions
- Document CLI commands with examples for common tasks
- Provide example `project.yml` configuration for customization
- Add sections on contributing, license, and GitHub Actions
- Enhance clarity and usability for new users and contributors

Signed-off-by: Max P. <Mail@MPassarello.de>
2025-05-01 17:43:12 +02:00
60c4ca19b1 Remove push trigger for main branch in build-deb.yml 2025-04-28 10:37:37 +02:00
38d4c7d5f3 Add twine to development dependencies in pyproject.toml
Some checks failed
Build Wheels / build (push) Has been cancelled
Build and Publish / build-and-publish (push) Successful in 30s
2025-04-28 10:36:30 +02:00
9d33b6e8be Update workflow to use Ubuntu 22.04 for build and publish job 2025-04-28 10:35:13 +02:00
dde3b5dc5d Add GitHub Actions workflow for building and publishing to Gitea 2025-04-28 10:34:33 +02:00
6d6b731033 Bump version from 0.5.2 to 0.5.3 in pyproject.toml
All checks were successful
Build Wheels / build (push) Successful in 3m36s
2025-04-27 18:56:40 +00:00
8196eb3929 Update gitignore to include .locale and vhdl_ls.toml 2025-04-27 18:56:34 +00:00
5083852c29 Bump version from 0.5.1 to 0.5.2 in pyproject.toml
All checks were successful
Build Wheels / build (push) Successful in 4m8s
2025-04-27 18:52:00 +00:00
215e4aa545 Refactors project initialization logic
Simplifies file handling by consolidating template copying logic
into a single loop. Corrects the resolution of the templates
directory path for improved reliability.

Improves maintainability and reduces redundancy in code.
2025-04-27 18:51:46 +00:00
d78bfcc408 Bump version from 0.5.0 to 0.5.1 in pyproject.toml
All checks were successful
Build Wheels / build (push) Successful in 3m38s
2025-04-27 18:40:23 +00:00
8ed550f451 Defers project loading to execution phase
Removes project loading from command initialization and shifts it to the execution phase for better resource management and initialization performance.

Improves flexibility by ensuring the project is only loaded when needed.
2025-04-27 18:39:58 +00:00
37b684fd2d Bump version from 0.4.0 to 0.5.0 in pyproject.toml
All checks were successful
Build Wheels / build (push) Successful in 4m33s
2025-04-27 18:03:57 +00:00
7cf69aa16f Adds project initialization command
All checks were successful
Build Wheels / build (push) Successful in 4m44s
Introduces an `init` command to initialize new HDLBuild projects.
Includes creation of default `.gitignore` and `project.yml` files
from templates if they do not already exist. Updates command
registration to include the new `init` command.
2025-04-27 17:58:11 +00:00
9274461d7a Updates package inclusion and dependency list
Adds template files to the package inclusion list to ensure they are distributed with the build.

Removes an unused dependency to streamline the dependency list.
2025-04-27 17:57:52 +00:00
b79b7559f2 Add template files for gitignore and project configuration 2025-04-27 17:57:40 +00:00
27 changed files with 1060 additions and 238 deletions

View File

@@ -0,0 +1,5 @@
chore(pr): ${PullRequestTitle} ${PullRequestReference}
${PullRequestDescription}
Merged from ${HeadBranch} into ${BaseBranch}

View File

@@ -0,0 +1,45 @@
#!/usr/bin/env bash
set -euo pipefail
# cleanup_dev_versions.sh - Delete old PyPI dev versions from Gitea package registry
# Required environment variables
USERNAME="${TWINE_USERNAME}"
TOKEN="${TWINE_PASSWORD}"
REPO="${GITHUB_REPOSITORY}" # e.g., maxp/repocat
API_BASE="${GITHUB_API_URL%/}" # Strip trailing slash if present
OWNER="${REPO%%/*}"
PACKAGE_NAME="${REPO##*/}"
API_URL="${API_BASE}/packages/${OWNER}/pypi/${PACKAGE_NAME}"
# Fetch the list of versions
response=$(curl -s -u "$USERNAME:$TOKEN" "$API_URL")
# Extract all .dev versions, sort by creation time
mapfile -t versions_to_delete < <(echo "$response" | jq -r '
map(select(.version | test("\\.dev"))) |
sort_by(.created_at) |
.[0:-1][] |
.version')
# Determine latest version to keep
latest_version=$(echo "$response" | jq -r '
map(select(.version | test("\\.dev"))) |
sort_by(.created_at) |
last.version')
if [[ -z "$latest_version" || ${#versions_to_delete[@]} -eq 0 ]]; then
echo "No old .dev versions to delete."
exit 0
fi
echo "Keeping latest .dev version: $latest_version"
# Delete old .dev versions
for version in "${versions_to_delete[@]}"; do
echo "Deleting old .dev version: $version"
curl -s -X DELETE -u "$USERNAME:$TOKEN" "$API_URL/$version"
done
echo "Cleanup complete."

View File

@@ -0,0 +1,21 @@
#!/usr/bin/env bash
set -euo pipefail
# Eingaben
TAG="$1"
TOKEN="${ACTIONS_RUNTIME_TOKEN:-<fallback_token>}"
REPO="${GITHUB_REPOSITORY:-owner/example}"
API="${GITHUB_API_URL:-https://gitea.example.tld/api/v1}"
OWNER=$(echo "$REPO" | cut -d/ -f1)
NAME=$(echo "$REPO" | cut -d/ -f2)
RESPONSE=$(curl -sf \
-H "Authorization: token $TOKEN" \
"$API/repos/$OWNER/$NAME/releases/tags/$TAG")
RELEASE_ID=$(echo "$RESPONSE" | jq -r '.id')
echo "Release-ID für $TAG ist: $RELEASE_ID"
# Für GitHub Actions als Umgebungsvariable
echo "GT_RELEASE_ID=$RELEASE_ID" >> "$GITHUB_ENV"

View File

@@ -0,0 +1,14 @@
#!/bin/bash
BASE_VERSION=$(cat VERSION)
NIGHTLY_SUFFIX=""
if [[ "$1" == "nightly" ]]; then
# Beispiel: 20240511.1358 → 11. Mai, 13:58 Uhr
NIGHTLY_SUFFIX=".dev$(date +%Y%m%d%H%M)"
fi
FULL_VERSION="${BASE_VERSION}${NIGHTLY_SUFFIX}"
echo "Using version: $FULL_VERSION"
poetry version "$FULL_VERSION"

View File

@@ -0,0 +1,21 @@
#!/usr/bin/env bash
set -euo pipefail
# Stelle sicher, dass wir im Projektverzeichnis sind
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$ROOT_DIR"
PYPROJECT="pyproject.toml"
VERSION_FILE="VERSION"
# Extrahiere die Version mit grep + sed (keine externen Abhängigkeiten nötig)
VERSION=$(grep -E '^version\s*=' "$PYPROJECT" | head -n1 | sed -E 's/.*=\s*"([^"]+)".*/\1/')
if [[ -z "$VERSION" ]]; then
echo "❌ Version konnte nicht aus $PYPROJECT gelesen werden."
exit 1
fi
printf "%s" "$VERSION" > "$VERSION_FILE"
echo "✅ Version synchronisiert: $VERSION$VERSION_FILE"

40
.gitea/scripts/upload-asset.sh Executable file
View File

@@ -0,0 +1,40 @@
#!/usr/bin/env bash
set -euo pipefail
# Eingabeparameter
FILE_PATH="$1" # z. B. ./dist/build.zip
CUSTOM_NAME="${2:-}" # optional: anderer Name unter dem das Asset gespeichert werden soll
RELEASE_ID="${GT_RELEASE_ID:-}" # aus Umgebung
# Validierung
if [[ -z "$RELEASE_ID" ]]; then
echo "❌ RELEASE_ID ist nicht gesetzt. Abbruch."
exit 1
fi
if [[ ! -f "$FILE_PATH" ]]; then
echo "❌ Datei '$FILE_PATH' existiert nicht. Abbruch."
exit 1
fi
# Default-Konfiguration
TOKEN="${ACTIONS_RUNTIME_TOKEN:-<fallback_token>}"
REPO="${GITHUB_REPOSITORY:-owner/example}"
API="${GITHUB_API_URL:-https://gitea.example.tld/api/v1}"
# Owner/Repo auflösen
OWNER=$(echo "$REPO" | cut -d/ -f1)
NAME=$(echo "$REPO" | cut -d/ -f2)
# Dateiname setzen
FILENAME="${CUSTOM_NAME:-$(basename "$FILE_PATH")}"
echo "🔼 Uploading '$FILE_PATH' as '$FILENAME' to release ID $RELEASE_ID"
# Upload
curl -sf -X POST \
-H "Authorization: token $TOKEN" \
-F "attachment=@$FILE_PATH" \
"$API/repos/$OWNER/$NAME/releases/$RELEASE_ID/assets?name=$FILENAME"
echo "✅ Upload abgeschlossen: $FILENAME"

View File

@@ -0,0 +1,62 @@
name: Build and Publish nightly package
on:
workflow_dispatch:
push:
branches:
- main
paths-ignore:
- 'CHANGELOG.md'
jobs:
build-and-publish:
runs-on: ubuntu-22.04
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: 🐍 Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: 🔄 Restore cache
uses: https://git.0xmax42.io/actions/cache@v1
with:
key: poetry-v1-${{ runner.os }}-${{ hashFiles('poetry.lock') }}
paths: |
~/.cache/pypoetry
~/.cache/pip
- name: Install Poetry
run: |
pip install poetry
- name: Install Project Dependencies
working-directory: .
run: |
poetry install
- name: Set version from VERSION file (with nightly suffix)
run: ./.gitea/scripts/set_poetry_version.sh nightly
- name: Build Package
working-directory: .
run: |
poetry build
- name: Publish to Gitea Package Registry
working-directory: .
env:
TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }}
TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }}
run: |
poetry run twine upload --repository-url ${{ secrets.TWINE_URL }} dist/*
- name: Cleanup old dev versions
run: |
.gitea/scripts/cleanup_versions.sh '\.dev'
env:
TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }}
TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }}

View File

@@ -0,0 +1,64 @@
name: Build and Publish nightly package
on:
release:
types: [published]
jobs:
build-and-publish:
runs-on: ubuntu-22.04
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: ${{ github.event.release.tag_name }}
- name: 🐍 Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: 🔄 Restore cache
uses: https://git.0xmax42.io/actions/cache@v1
with:
key: poetry-v1-${{ runner.os }}-${{ hashFiles('poetry.lock') }}
paths: |
~/.cache/pypoetry
~/.cache/pip
- name: Install Poetry
run: |
pip install poetry
- name: Install Project Dependencies
working-directory: .
run: |
poetry install
- name: Build Package
working-directory: .
run: |
poetry build
- name: Get built wheel filename
id: get_whl
run: |
echo "whl_file=$(basename dist/*.whl)" >> $GITHUB_OUTPUT
echo "sdist_file=$(basename dist/*.tar.gz)" >> $GITHUB_OUTPUT
- name: Publish to Gitea Package Registry
working-directory: .
env:
TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }}
TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }}
run: |
poetry run twine upload --repository-url ${{ secrets.TWINE_URL }} dist/*
- name: Get Release ID from tag
run: .gitea/scripts/get-release-id.sh "${{ github.event.release.tag_name }}"
- name: Upload assets
run: |
.gitea/scripts/upload-asset.sh ./dist/${{ steps.get_whl.outputs.whl_file }}
.gitea/scripts/upload-asset.sh ./dist/${{ steps.get_whl.outputs.sdist_file }}

View File

@@ -0,0 +1,18 @@
name: Auto Changelog & (Release)
on:
push:
branches:
- main
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Release
uses: https://git.0xmax42.io/actions/auto-changelog-release-action@v0
with:
token: ${{ secrets.RELEASE_PUBLISH_TOKEN }}

View File

@@ -1,97 +0,0 @@
name: Build Wheels
on:
workflow_dispatch:
push:
branches:
- main
paths:
- "pyproject.toml"
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: ⚙️ Prepare Environment Variables
run: |
echo "AGENT_TOOLSDIRECTORY=/home/runner/toolcache" >> $GITHUB_ENV
mkdir -p /home/runner/toolcache
- name: 🛒 Checkout Repository
uses: actions/checkout@v4
- name: 🐍 Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: 📦 Install Poetry
run: |
curl -sSL https://install.python-poetry.org | python3 -
echo "$HOME/.local/bin" >> $GITHUB_PATH
poetry self add poetry-plugin-export
- name: 🔧 Install Project Dependencies
run: |
poetry install
- name: 📝 Export requirements.txt
run: |
poetry export --without-hashes --format=requirements.txt > requirements.txt
- name: 📥 Download Wheels for Dependencies
run: |
mkdir -p dist/wheels
poetry run pip install wheel
poetry run pip download --only-binary=:all: --dest dist -r requirements.txt
- name: 🛠 Build Project Wheel
run: |
poetry build -f wheel
- name: 🔌 Install pipx and wheel2deb
run: |
python3 -m pip install --user pipx
python3 -m pipx ensurepath
pipx install wheel2deb
- name: 📦 Install wheel2deb Dependencys
run: |
sudo apt update
sudo apt install -y apt-file dpkg-dev fakeroot build-essential devscripts debhelper
sudo apt-file update
- name: 🛠 Convert Wheel to DEB
run: |
mkdir -p dist/debs
wheel2deb default -v -x dist -o dist/debs
- name: 📦 Clone `packages`-Branch in ./packages
run: |
REPO_URL_BASE="$(echo $GITHUB_SERVER_URL/${{ secrets.TEA_USER }}/debrepo.git | sed 's/^https\?:\/\///')"
REPO_URL="https://${{ secrets.TEA_USER }}:${{ secrets.TEA_PAT }}@$REPO_URL_BASE"
echo "::add-mask::$REPO_URL"
echo "📡 Klone Repository von: $REPO_URL"
git clone --depth 1 --branch packages "$REPO_URL" packages
- name: 📂 Ensure `./packages/hdlbuild` Exists and Copy DEBs
run: |
mkdir -p ./packages/hdlbuild
find dist/debs -type f -name "*.deb" -exec cp {} ./packages/hdlbuild/ \;
- name: 📝 Commit & Push immer
run: |
cd packages
git config user.name "Gitea CI"
git config user.email "ci@yourdomain"
git add hdlbuild/
git commit -m "🔄 Update hdlbuild $(date -u +'%Y-%m-%d %H:%M UTC')" || echo "🧊 Keine Änderungen – leerer Commit"
REPO_URL_BASE="$(echo $GITHUB_SERVER_URL/${{ secrets.TEA_USER }}/debrepo.git | sed 's/^https\?:\/\///')"
REPO_URL="https://${{ secrets.TEA_USER }}:${{ secrets.TEA_PAT }}@$REPO_URL_BASE"
echo "::add-mask::$REPO_URL"
git remote set-url origin "$REPO_URL"
git push origin packages

11
.vscode/settings.json vendored
View File

@@ -2,5 +2,14 @@
"python.envFile": "${workspaceFolder}/.env", "python.envFile": "${workspaceFolder}/.env",
"python.analysis.extraPaths": [ "python.analysis.extraPaths": [
"src" "src"
] ],
"workbench.colorCustomizations": {
"activityBar.activeBackground": "#8dc4ff",
"activityBar.background": "#8dc4ff",
"activityBar.foreground": "#15202b",
"activityBar.inactiveForeground": "#15202b99",
"activityBarBadge.background": "#ff007b",
"activityBarBadge.foreground": "#e7e7e7"
},
"peacock.color": "#5aaaff"
} }

31
CHANGELOG.md Normal file
View File

@@ -0,0 +1,31 @@
# Changelog
All notable changes to this project will be documented in this file.
## [0.6.1] - 2025-07-16
### 🚀 Features
- *(ci)* Add workflows for nightly builds and releases - ([da387f2](https://git.0xmax42.io/maxp/hdlbuild/commit/da387f2ee602390d616c79bf4057ccf941e21462))
### 🚜 Refactor
- Use typer for CLI argument parsing - ([6ca389d](https://git.0xmax42.io/maxp/hdlbuild/commit/6ca389d5cbbeff53faab9d61376a8c77ed097b6c))
- Improves project configuration - ([175bf48](https://git.0xmax42.io/maxp/hdlbuild/commit/175bf4882a8f172ee536d726b31136690572be36))
### 📚 Documentation
- *(readme)* Expand README with detailed usage and setup - ([fa7e738](https://git.0xmax42.io/maxp/hdlbuild/commit/fa7e738b7eade5a627218741a6fb4bd1617f7801))
### 🎨 Styling
- *(pyproject)* Simplify include array formatting - ([08679c2](https://git.0xmax42.io/maxp/hdlbuild/commit/08679c2680b49119e0414688a80e8dc2659236b4))
- Updates VS Code editor color scheme - ([1d7bc19](https://git.0xmax42.io/maxp/hdlbuild/commit/1d7bc1996522ab54970348b5118ad319849a6a1f))
### ⚙️ Miscellaneous Tasks
- *(config)* Add git-cliff configuration file - ([dde2363](https://git.0xmax42.io/maxp/hdlbuild/commit/dde2363ad7dd2fd2d48c6154e3b88c1c4a6867fd))
- Updates project version to 0.6.0 - ([7b6f9ef](https://git.0xmax42.io/maxp/hdlbuild/commit/7b6f9ef2240864b103903e79c895a76db59c14fa))
- Remove build-deb.yml workflow file - ([0d26c42](https://git.0xmax42.io/maxp/hdlbuild/commit/0d26c42f8ae419d509aa47d3f7a23bfdd08cf79b))

168
README.md
View File

@@ -1,2 +1,168 @@
# hdlbuild # HDLBuild
HDLBuild is a flexible build management tool for FPGA projects. It simplifies the process of managing dependencies, building, testing, and deploying FPGA designs using Xilinx ISE tools.
## Features
- **Dependency Management**: Automatically resolves and manages project dependencies from Git repositories.
- **Build Automation**: Supports synthesis, implementation, and bitstream generation for FPGA designs.
- **Testbench Execution**: Automates the process of building and running testbenches.
- **Customizable Tool Options**: Provides extensive configuration options for Xilinx ISE tools.
- **Project Initialization**: Quickly set up new projects with predefined templates.
- **Rich Console Output**: Provides detailed and interactive console feedback using `rich`.
---
## Installation
### Prerequisites
- Python 3.10 or higher
- Poetry (for dependency management)
- Xilinx ISE (14.7) installed and configured
### Steps
1. Clone the repository:
```bash
git clone https://github.com/your-repo/hdlbuild.git
cd hdlbuild
```
2. Install dependencies:
```bash
poetry install
```
3. Add the `hdlbuild` CLI to your PATH:
```bash
poetry shell
```
---
## Usage
### CLI Commands
HDLBuild provides a command-line interface (CLI) for managing FPGA projects. Below are the available commands:
#### 1. **Initialize a New Project**
```bash
hdlbuild init
```
- Creates a new project with a project.yml configuration file and a .gitignore file.
#### 2. **Resolve Dependencies**
```bash
hdlbuild dep
```
- Clones and resolves all project dependencies defined in project.yml.
#### 3. **Build the Project**
```bash
hdlbuild build
```
- Runs the full build process, including synthesis, implementation, and bitstream generation.
- To only synthesize the design:
```bash
hdlbuild build synth
```
#### 4. **Run Testbenches**
```bash
hdlbuild test <testbench_name>
```
- Builds and runs the specified testbench.
#### 5. **Clean Build Artifacts**
```bash
hdlbuild clean
```
- Removes build artifacts.
- To clean all generated files:
```bash
hdlbuild clean all
```
---
## Configuration
The project is configured using a project.yml file. Below is an example configuration:
```yml
name: MyFPGAProject
topmodule: top_module
target_device: xc3s1200e-4-fg320
xilinx_path: /opt/Xilinx/14.7/ISE_DS/ISE
constraints: constraints.ucf
sources:
vhdl:
- path: src/*.vhd
library: work
testbenches:
vhdl:
- path: tests/*.vhd
library: work
dependencies:
- git: "https://github.com/example/dependency.git"
rev: "main"
build:
build_dir: working
report_dir: reports
copy_target_dir: output
tool_options:
xst:
- "-opt_mode Speed"
- "-opt_level 2"
map:
- "-detail"
- "-timing"
par: []
bitgen:
- "-g StartupClk:JtagClk"
```
---
## Development
### Building the Package
To build the Python package:
```bash
poetry build
```
---
## GitHub Actions
The project includes GitHub workflows for building and deploying the package:
1. **Build and Publish**: build-and-deploy.yml
---
## License
This project is licensed under the MIT License.
---
## Contributing
Contributions are welcome! Please follow these steps:
1. Fork the repository.
2. Create a new branch for your feature or bugfix.
3. Submit a pull request.

1
VERSION Normal file
View File

@@ -0,0 +1 @@
0.6.1

104
cliff.toml Normal file
View File

@@ -0,0 +1,104 @@
# CLIFF_VERSION=2.8.0
# git-cliff ~ default configuration file
# https://git-cliff.org/docs/configuration
#
# Lines starting with "#" are comments.
# Configuration options are organized into tables and keys.
# See documentation for more information on available options.
[remote.gitea]
owner = "maxp"
repo = "hdlbuild"
[changelog]
# postprocessors
postprocessors = [
{ pattern = '<GITEA_URL>', replace = "https://git.0xmax42.io" }, # replace gitea url
]
# template for the changelog header
header = """
# Changelog\n
All notable changes to this project will be documented in this file.\n
"""
# template for the changelog body
# https://keats.github.io/tera/docs/#introduction
body = """
{%- macro remote_url() -%}
<GITEA_URL>/{{ remote.gitea.owner }}/{{ remote.gitea.repo }}
{%- endmacro -%}
{% if version %}\
{% if previous.version %}\
## [{{ version | trim_start_matches(pat="v") }}]\
({{ self::remote_url() }}/compare/{{ previous.version }}..{{ version }}) - {{ timestamp | date(format="%Y-%m-%d") }}
{% else %}\
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
{% endif %}\
{% else %}\
## [unreleased]
{% endif %}\
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | striptags | trim | upper_first }}
{% for commit in commits %}
- {% if commit.scope %}*({{ commit.scope }})* {% endif %}\
{% if commit.breaking %}[**breaking**] {% endif %}\
{{ commit.message | upper_first }} - \
([{{ commit.id | truncate(length=7, end="") }}]({{ self::remote_url() }}/commit/{{ commit.id }}))\
{% endfor %}
{% endfor %}\n
"""
# template for the changelog footer
footer = """
"""
# remove the leading and trailing s
trim = true
# render body even when there are no releases to process
# render_always = true
# output file path
# output = "test.md"
[git]
# parse the commits based on https://www.conventionalcommits.org
conventional_commits = true
# filter out the commits that are not conventional
filter_unconventional = true
# process each line of a commit as an individual commit
split_commits = false
# regex for preprocessing the commit messages
commit_preprocessors = [
# Replace issue numbers
#{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](<REPO>/issues/${2}))"},
# Check spelling of the commit with https://github.com/crate-ci/typos
# If the spelling is incorrect, it will be automatically fixed.
#{ pattern = '.*', replace_command = 'typos --write-changes -' },
]
# regex for parsing and grouping commits
commit_parsers = [
{ message = "^feat", group = "<!-- 0 -->🚀 Features" },
{ message = "^fix", group = "<!-- 1 -->🐛 Bug Fixes" },
{ message = "^doc", group = "<!-- 3 -->📚 Documentation" },
{ message = "^perf", group = "<!-- 4 -->⚡ Performance" },
{ message = "^refactor", group = "<!-- 2 -->🚜 Refactor" },
{ message = "^style", group = "<!-- 5 -->🎨 Styling" },
{ message = "^test", group = "<!-- 6 -->🧪 Testing" },
{ message = "^chore\\(changelog\\)", skip = true },
{ message = "^chore\\(version\\)", skip = true },
{ message = "^chore\\(release\\): prepare for", skip = true },
{ message = "^chore\\(deps.*\\)", skip = true },
{ message = "^chore\\(pr\\)", skip = true },
{ message = "^chore\\(pull\\)", skip = true },
{ message = "^chore|^ci", group = "<!-- 7 -->⚙️ Miscellaneous Tasks" },
{ body = ".*security", group = "<!-- 8 -->🛡️ Security" },
{ message = "^revert", group = "<!-- 9 -->◀️ Revert" },
{ message = ".*", group = "<!-- 10 -->💼 Other" },
]
# Regex to select git tags that represent releases.
tag_pattern = "v[0-9]+\\.[0-9]+\\.[0-9]+"
# filter out the commits that are not matched by commit parsers
filter_commits = false
# sort the tags topologically
topo_order = false
# sort the commits inside sections by oldest/newest order
sort_commits = "newest"

View File

@@ -1,27 +1,23 @@
name: VGA name:
topmodule: VGA_Test topmodule:
target_device: xc3s1200e-4-fg320 target_device: xc3s1200e-4-fg320
xilinx_path: /opt/Xilinx/14.7/ISE_DS/ISE xilinx_path: /opt/Xilinx/14.7/ISE_DS/ISE
constraints:
sources: sources:
vhdl: vhdl:
- path: src/*.vhd - path: src/*.vhd
library: work library: work
verilog: []
dependencies:
- git: "https://git.0xmax42.io/maxp/Asynchronous-FIFO-AXI-Handshake.git"
rev: "hdlbuild"
testbenches: testbenches:
vhdl: vhdl:
- path: tests/*.vhd - path: tests/*.vhd
library: work library: work
verilog: [] dependencies:
# - git: "https://git.0xmax42.io/maxp/Asynchronous-FIFO-AXI-Handshake.git"
constraints: src/VGA_test.ucf # rev: "hdlbuild"
build: build:
build_dir: working build_dir: working
@@ -39,7 +35,8 @@ tool_options:
map: map:
- "-detail" - "-detail"
- "-timing" - "-timing"
- "-ol high" - "-ol"
- "high"
par: [] par: []

View File

@@ -1,11 +1,12 @@
[tool.poetry] [tool.poetry]
name = "hdlbuild" name = "hdlbuild"
version = "0.4.0" version = "0.6.1"
description = "Flexible FPGA Build System" description = "Flexible FPGA Build System"
authors = ["0xMax42 <Mail@0xMax42.io>"] authors = ["0xMax42 <Mail@0xMax42.io>"]
license = "MIT" license = "MIT"
readme = "README.md" readme = "README.md"
packages = [{ include = "hdlbuild", from = "src" }] packages = [{ include = "hdlbuild", from = "src" }]
include = ["src/hdlbuild/templates/*"]
[tool.poetry.scripts] [tool.poetry.scripts]
hdlbuild = "hdlbuild.cli:main" hdlbuild = "hdlbuild.cli:main"
@@ -13,10 +14,13 @@ hdlbuild = "hdlbuild.cli:main"
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.10" python = "^3.10"
pyyaml = "^6.0.2" pyyaml = "^6.0.2"
doit = "^0.36.0"
pydantic = "^2.11.3" pydantic = "^2.11.3"
rich = "^14.0.0" rich = "^14.0.0"
gitpython = "^3.1.44" gitpython = "^3.1.44"
typer = "^0.16.0"
[tool.poetry.group.dev.dependencies]
twine = "^6.1.0"
[build-system] [build-system]
requires = ["poetry-core"] requires = ["poetry-core"]

View File

@@ -1,32 +1,32 @@
import argparse import typer
from importlib.metadata import version, PackageNotFoundError from importlib.metadata import version, PackageNotFoundError
from hdlbuild.commands import register_commands
def get_version(): from hdlbuild.commands.build import cli as build_cli
from hdlbuild.commands.clean import cli as clean_cli
from hdlbuild.commands.dep import cli as dep_cli
from hdlbuild.commands.test import cli as test_cli
from hdlbuild.commands.init import cli as init_cli
def get_version() -> str:
try: try:
return version("hdlbuild") # Paketname aus pyproject.toml return version("hdlbuild")
except PackageNotFoundError: except PackageNotFoundError:
return "unknown" return "unknown"
app = typer.Typer(
rich_help_panel="ℹ️ HDLBuild – FPGA‑Build‑Tool",
help=f"hdlbuild v{get_version()} – Build‑Management for FPGA projects"
)
# Unter‑Kommandos registrieren (entspricht add_subparsers)
app.add_typer(build_cli, name="build", help="Build the project")
app.add_typer(clean_cli, name="clean", help="Clean build artifacts")
app.add_typer(dep_cli, name="dep", help="Resolve dependencies")
app.add_typer(test_cli, name="test", help="Run simulations/testbenches")
app.add_typer(init_cli, name="init", help="Initialize project")
def main(): def main():
version_str = get_version() app()
parser = argparse.ArgumentParser(
description=f"hdlbuild v{version_str} - Build management tool for FPGA projects",
formatter_class=argparse.RawTextHelpFormatter
)
subparsers = parser.add_subparsers(
title="Commands",
description="Available commands",
dest="command",
required=True
)
# Register all commands
register_commands(subparsers)
args = parser.parse_args()
args.func(args)
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@@ -1 +0,0 @@
from .commands import register_commands

View File

@@ -1,30 +1,34 @@
from hdlbuild.tools.xilinx_ise.main import xilinx_ise_all, xilinx_ise_synth import typer
from hdlbuild.utils.console_utils import ConsoleUtils
from hdlbuild.utils.directory_manager import ensure_directories_exist
from hdlbuild.utils.project_loader import load_project_config
class BuildCommand: from hdlbuild.tools.xilinx_ise.main import xilinx_ise_all, xilinx_ise_synth
def __init__(self): from hdlbuild.utils.console_utils import ConsoleUtils
self.console_utils = ConsoleUtils("hdlbuild") from hdlbuild.utils.directory_manager import ensure_directories_exist
self.project = load_project_config() from hdlbuild.utils.project_loader import load_project_config
def register(self, subparsers): cli = typer.Typer(rich_help_panel="🔨 Build Commands")
parser = subparsers.add_parser("build", help="Start the build process")
parser.add_argument(
"target",
nargs="?",
choices=["synth"],
help="Specify 'synth' to only synthesize the design (optional)"
)
parser.set_defaults(func=self.execute)
def execute(self, args): @cli.callback(invoke_without_command=True)
"""Starts the build process.""" def build(
if args.target == "synth": target: str = typer.Argument(
self.console_utils.print("Starting synth process...") None,
ensure_directories_exist(True) help="Optional: 'synth' to run synthesis only",
xilinx_ise_synth(self.project) show_default=False,
else: rich_help_panel="🔨 Build Commands",
self.console_utils.print("Starting build process...") )
ensure_directories_exist(True) ) -> None:
xilinx_ise_all(self.project) """
Run the full build flow or synthesis only.
* `hdlbuild build` → full flow
* `hdlbuild build synth` → synthesis only
"""
console = ConsoleUtils("hdlbuild")
project = load_project_config()
ensure_directories_exist(True)
if target == "synth":
console.print("Starting synthesis …")
xilinx_ise_synth(project)
else:
console.print("Starting full build …")
xilinx_ise_all(project)

View File

@@ -1,27 +1,35 @@
from hdlbuild.utils.console_utils import ConsoleUtils import typer
from hdlbuild.utils.directory_manager import clear_build_directories, clear_directories
class CleanCommand: from hdlbuild.utils.console_utils import ConsoleUtils
def __init__(self): from hdlbuild.utils.directory_manager import clear_build_directories, clear_directories
self.console_utils = ConsoleUtils("hdlbuild")
def register(self, subparsers): cli = typer.Typer(rich_help_panel="🧹 Clean Commands")
parser = subparsers.add_parser("clean", help="Clean build artifacts")
parser.add_argument(
"target",
nargs="?",
choices=["all"],
help="Specify 'all' to clean everything (optional)"
)
parser.set_defaults(func=self.execute)
def execute(self, args): @cli.callback(invoke_without_command=True)
"""Cleans the build artifacts.""" def clean(
if args.target == "all": target: str = typer.Argument(
self.console_utils.print("Starting clean all process...") None,
clear_directories() help="Optional: 'all' → wipe *all* artefacts, otherwise only the build directory",
self.console_utils.print("All cleaned.") show_default=False,
else: )
self.console_utils.print("Clearing build artifacts...") ) -> None:
clear_build_directories() """
self.console_utils.print("Build artifacts cleaned.") Remove build artefacts (`build/*`) or *everything* (`all`).
Examples
--------
```bash
hdlbuild clean # build/* and temporary files only
hdlbuild clean all # also caches, logs, etc.
```
"""
console = ConsoleUtils("hdlbuild")
if target == "all":
console.print("Starting clean‑all …")
clear_directories()
console.print("All artefacts removed.")
else:
console.print("Removing build artefacts …")
clear_build_directories()
console.print("Build artefacts removed.")

View File

@@ -1,17 +0,0 @@
from hdlbuild.commands.build import BuildCommand
from hdlbuild.commands.clean import CleanCommand
from hdlbuild.commands.dep import DepCommand
from hdlbuild.commands.test import TestCommand
def register_commands(subparsers):
"""Registers all available commands."""
commands = [
CleanCommand(),
BuildCommand(),
DepCommand(),
TestCommand()
]
for command in commands:
command.register(subparsers)

View File

@@ -1,17 +1,23 @@
from hdlbuild.dependencies.resolver import DependencyResolver import typer
from hdlbuild.utils.console_utils import ConsoleUtils
from hdlbuild.utils.project_loader import load_project_config
class DepCommand: from hdlbuild.dependencies.resolver import DependencyResolver
def __init__(self): from hdlbuild.utils.console_utils import ConsoleUtils
self.console_utils = ConsoleUtils("hdlbuild") from hdlbuild.utils.project_loader import load_project_config
self.project = load_project_config()
def register(self, subparsers): cli = typer.Typer(rich_help_panel="🔗 Dependency Commands")
parser = subparsers.add_parser("dep", help="Start the dependencies process")
parser.set_defaults(func=self.execute)
def execute(self, args): @cli.callback(invoke_without_command=True)
"""Starts the dependencies process.""" def dep() -> None:
self.console_utils.print("Starting dependencies process...") """
DependencyResolver(self.project).resolve_all() Resolve all project dependencies.
```bash
hdlbuild dep
```
"""
console = ConsoleUtils("hdlbuild")
project = load_project_config()
console.print("Resolving dependencies …")
DependencyResolver(project).resolve_all()
console.print("Dependencies resolved.")

View File

@@ -0,0 +1,35 @@
import shutil
from pathlib import Path
import typer
from hdlbuild.utils.console_utils import ConsoleUtils
cli = typer.Typer(rich_help_panel="🆕 Init Commands")
@cli.callback(invoke_without_command=True)
def init() -> None:
"""
Initialise a new HDLBuild project in the current directory.
Copies `.gitignore` and `project.yml` from the template folder.
"""
console = ConsoleUtils("hdlbuild")
project_dir = Path.cwd()
script_dir = Path(__file__).parent.resolve()
template_dir = (script_dir / ".." / "templates").resolve()
files = [
("gitignore.template", ".gitignore"),
("project.yml.template", "project.yml"),
]
for template_name, target_name in files:
template_path = template_dir / template_name
target_path = project_dir / target_name
if not target_path.exists():
shutil.copy(template_path, target_path)
console.print(f"Created {target_name}")
else:
console.print(f"{target_name} already exists – skipping.")

View File

@@ -1,23 +1,31 @@
from hdlbuild.tools.xilinx_ise.isim import build_testbench, run_testbench import typer
from hdlbuild.utils.console_utils import ConsoleUtils
from hdlbuild.utils.project_loader import load_project_config
class TestCommand: from hdlbuild.tools.xilinx_ise.isim import build_testbench, run_testbench
def __init__(self): from hdlbuild.utils.console_utils import ConsoleUtils
self.console_utils = ConsoleUtils("hdlbuild") from hdlbuild.utils.project_loader import load_project_config
self.project = load_project_config()
def register(self, subparsers): cli = typer.Typer(rich_help_panel="🧪 Test Commands")
parser = subparsers.add_parser("test", help="Start the Tests process")
parser.add_argument(
"target",
nargs="?",
help="Select the target to test"
)
parser.set_defaults(func=self.execute)
def execute(self, args): @cli.callback(invoke_without_command=True)
"""Starts the test process.""" def test(
self.console_utils.print("Starting test process...") target: str = typer.Argument(
build_testbench(self.project, args.target) None,
run_testbench(self.project, args.target) help="Name of the test target (leave empty to run all)",
show_default=False,
)
) -> None:
"""
Build and run testbenches.
```bash
hdlbuild test # run all TBs
hdlbuild test alu # run TB 'alu' only
```
"""
console = ConsoleUtils("hdlbuild")
project = load_project_config()
console.print("Starting test flow …")
build_testbench(project, target)
run_testbench(project, target)
console.print("Tests finished.")

View File

@@ -0,0 +1,6 @@
.hdlbuild_deps/
.working/
reports/
output/
.locale/
vhdl_ls.toml

View File

@@ -0,0 +1,268 @@
name:
topmodule:
target_device: xc3s1200e-4-fg320
xilinx_path: /opt/Xilinx/14.7/ISE_DS/ISE
constraints:
sources:
vhdl:
- path: src/*.vhd
library: work
testbenches:
vhdl:
- path: tests/*.vhd
library: work
dependencies:
# - git: "https://git.0xmax42.io/maxp/Asynchronous-FIFO-AXI-Handshake.git"
# rev: "hdlbuild"
build:
build_dir: working
report_dir: reports
copy_target_dir: output
# Tool Optionen
tool_options:
common:
- "-intstyle"
- "xflow"
ngdbuild: []
map:
- "-detail"
- "-timing"
- "-ol"
- "high"
par: []
bitgen:
- "-g"
- "StartupClk:JtagClk"
trace:
- "-v"
- "3"
- "-n"
- "3"
fuse:
- "-incremental"
isim:
- "-gui"
xst:
# Optimization goal: prioritize speed or area.
# Values: Speed | Area
- "-opt_mode Speed"
# Optimization level: more aggressive optimizations at level 2.
# Values: 1 | 2
- "-opt_level 2"
# Use the new XST parser (recommended for modern designs).
# Values: yes | no
- "-use_new_parser yes"
# Preserve design hierarchy or allow flattening for optimization.
# Values: Yes | No | Soft
- "-keep_hierarchy No"
# Determines how hierarchy is preserved in the netlist.
# Values: As_Optimized | Rebuilt
- "-netlist_hierarchy As_Optimized"
# Global optimization strategy for nets.
# Values: AllClockNets | Offset_In_Before | Offset_Out_After | Inpad_To_Outpad | Max_Delay
- "-glob_opt AllClockNets"
## Misc ##
# Enable reading of IP cores.
# Values: YES | NO
- "-read_cores YES"
# Do not write timing constraints into synthesis report.
# Values: YES | NO
- "-write_timing_constraints NO"
# Analyze paths across different clock domains.
# Values: YES | NO
- "-cross_clock_analysis NO"
# Character used to separate hierarchy levels in instance names.
# Default: /
- "-hierarchy_separator /"
# Delimiters used for bus signals.
# Values: <> | [] | () | {}
- "-bus_delimiter <>"
# Maintain original case of identifiers.
# Values: Maintain | Upper | Lower
- "-case Maintain"
# Target maximum utilization ratio for slices.
# Values: 1–100
- "-slice_utilization_ratio 100"
# Target maximum utilization ratio for BRAMs.
# Values: 1–100
- "-bram_utilization_ratio 100"
# Use Verilog 2001 syntax features.
# Values: YES | NO
- "-verilog2001 YES"
#### HDL Options ####
## FSM ##
# Extract FSMs (Finite State Machines) from HDL code.
# Values: YES | NO
- "-fsm_extract YES"
# Encoding strategy for FSMs.
# Values: Auto | Gray | One-Hot | Johnson | Compact | Sequential | Speed1 | User
- "-fsm_encoding Auto"
# Add safe logic for undefined FSM states.
# Values: Yes | No
- "-safe_implementation No"
# Structure used to implement FSMs.
# Values: LUT | BRAM
- "-fsm_style LUT"
## RAM/ROM ##
# Extract RAM inference from HDL.
# Values: Yes | No
- "-ram_extract Yes"
# Style used to implement RAM.
# Values: Auto | Block | Distributed
- "-ram_style Auto"
# Extract ROM inference from HDL.
# Values: Yes | No
- "-rom_extract Yes"
# Style used for implementing ROM.
# Values: Auto | Distributed | Block
- "-rom_style Auto"
# Enable or disable automatic BRAM packing.
# Values: YES | NO
- "-auto_bram_packing NO"
## MUX/Decoder/Shift Register ##
# Extract multiplexers where possible.
# Values: Yes | No | Force
- "-mux_extract Yes"
# Style used for implementing MUX logic.
# Values: Auto | MUXCY | MUXF
- "-mux_style Auto"
# Extract decoder logic from behavioral code.
# Values: YES | NO
- "-decoder_extract YES"
# Extract and optimize priority encoder structures.
# Values: Yes | No | Force
- "-priority_extract Yes"
# Extract shift register logic.
# Values: YES | NO
- "-shreg_extract YES"
# Extract simple shift operations into dedicated hardware.
# Values: YES | NO
- "-shift_extract YES"
## Multiplier ##
# Style for implementing multipliers.
# Values: Auto | LUT | Pipe_LUT | Pipe_Block | Block
- "-mult_style Auto"
## Misc ##
# Collapse XOR trees where beneficial.
# Values: YES | NO
- "-xor_collapse YES"
# Share resources like adders or multipliers between logic blocks.
# Values: YES | NO | Force
- "-resource_sharing YES"
# Convert asynchronous resets to synchronous where possible.
# Values: YES | NO
- "-async_to_sync NO"
#### Xilinx Specific Options ####
## Optimization ##
# Enable removal of logically equivalent registers.
# Values: YES | NO
- "-equivalent_register_removal YES"
# Duplicate registers to reduce fanout or improve timing.
# Values: YES | NO
- "-register_duplication YES"
# Move registers across logic to balance timing.
# Values: Yes | No | Forward | Backward
- "-register_balancing No"
# Use clock enable signals where possible.
# Values: Auto | Yes | No
- "-use_clock_enable Yes"
# Use synchronous set (preset) signals when available.
# Values: Auto | Yes | No
- "-use_sync_set Yes"
# Use synchronous reset signals where possible.
# Values: Auto | Yes | No
- "-use_sync_reset Yes"
## I/O ##
# Insert IO buffers for top-level ports.
# Values: YES | NO
- "-iobuf YES"
# Placement strategy for IOB registers (Auto = let tools decide).
# Values: Auto | YES | NO
- "-iob Auto"
## Misc ##
# Maximum allowed fanout for a net.
# Values: integer (e.g., 500)
- "-max_fanout 500"
# Maximum number of BUFGs (global buffers) to use.
# Values: 0–32 (device-dependent)
- "-bufg 24"
# Enable logic packing into slices.
# Values: YES | NO
- "-slice_packing YES"
# Try to reduce the number of primitive instances used.
# Values: YES | NO
- "-optimize_primitives NO"
# Margin in percent beyond the target slice utilization.
# Values: 0–100
- "-slice_utilization_ratio_maxmargin 5"