Full Code of getinstachip/vpm for AI

main 03763cc40a23 cached
27 files
167.5 KB
42.2k tokens
142 symbols
1 requests
Download .txt
Repository: getinstachip/vpm
Branch: main
Commit: 03763cc40a23
Files: 27
Total size: 167.5 KB

Directory structure:
gitextract_f5njyyrd/

├── .github/
│   └── workflows/
│       └── release.yml
├── .gitignore
├── CONTRIBUTING.md
├── Cargo.toml
├── LICENSE.md
├── README.md
├── install.sh
├── installer.iss
└── src/
    ├── cmd/
    │   ├── cmd.rs
    │   ├── config.rs
    │   ├── docs.rs
    │   ├── dotf.rs
    │   ├── include.rs
    │   ├── install.rs
    │   ├── list.rs
    │   ├── load.rs
    │   ├── mod.rs
    │   ├── remove.rs
    │   ├── run.rs
    │   ├── sim.rs
    │   ├── synth.rs
    │   ├── update.rs
    │   └── upgrade.rs
    ├── config_man.rs
    ├── error.rs
    ├── main.rs
    └── toml.rs

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/release.yml
================================================
name: release
on:
  push:
  workflow_dispatch:
env:
  CARGO_INCREMENTAL: 0
permissions:
  contents: write
jobs:
  release:
    name: ${{ matrix.target }}
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        include:
          - os: ubuntu-latest
            target: x86_64-unknown-linux-musl
            deb: true
          - os: ubuntu-latest
            target: arm-unknown-linux-musleabihf
          - os: ubuntu-latest
            target: armv7-unknown-linux-musleabihf
            deb: true
          - os: ubuntu-latest
            target: aarch64-unknown-linux-musl
            deb: true
          - os: ubuntu-latest
            target: i686-unknown-linux-musl
            deb: true
          - os: ubuntu-latest
            target: aarch64-linux-android
          - os: macos-latest
            target: x86_64-apple-darwin
          - os: macos-latest
            target: aarch64-apple-darwin
          - os: windows-latest
            target: x86_64-pc-windows-msvc
          - os: windows-latest
            target: i686-pc-windows-msvc
          - os: windows-latest
            target: aarch64-pc-windows-msvc
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Get version
        id: get_version
        uses: SebRollen/toml-action@v1.2.0
        with:
          file: Cargo.toml
          field: package.version

      - name: Install Rust
        uses: actions-rs/toolchain@v1
        with:
          toolchain: stable
          profile: minimal
          override: true
          target: ${{ matrix.target }}

      - name: Setup cache
        uses: Swatinem/rust-cache@v2.7.3
        with:
          key: ${{ matrix.target }}

      - name: Install cross
        if: ${{ runner.os == 'Linux' }}
        uses: actions-rs/cargo@v1
        with:
          command: install
          args: --color=always --git=https://github.com/cross-rs/cross.git --locked --rev=02bf930e0cb0c6f1beffece0788f3932ecb2c7eb --verbose cross

      - name: Build binary
        uses: actions-rs/cargo@v1
        env:
          POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }}
          DOCS_KEY: ${{ secrets.DOCS_KEY }}
        with:
          command: build
          args: --release --target=${{ matrix.target }} --color=always --verbose
          use-cross: ${{ runner.os == 'Linux' }}

      - name: Install cargo-deb
        if: ${{ matrix.deb == true }}
        uses: actions-rs/install@v0.1
        with:
          crate: cargo-deb

      - name: Build deb
        if: ${{ matrix.deb == true }}
        uses: actions-rs/cargo@v1
        with:
          command: deb
          args: --no-build --no-strip --output=. --target=${{ matrix.target }}

      - name: Package (*nix)
        if: ${{ runner.os != 'Windows' }}
        run: |
          tar -cv LICENSE README.md \
            -C target/${{ matrix.target }}/release/ vpm |
            gzip --best > \
            vpm-${{ steps.get_version.outputs.value }}-${{ matrix.target }}.tar.gz

      - name: Create variable files
        if: ${{ runner.os == 'Windows' }}
        run: |
          echo "${{ steps.get_version.outputs.value }}" > version.txt
          echo "${{ matrix.target }}" >> target.txt

      - name: Package (Windows)
        if: ${{ runner.os == 'Windows' }}
        uses: Minionguyjpro/Inno-Setup-Action@v1.2.4
        with:
          path: installer.iss
          options: /O+

      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: ${{ matrix.target }}
          path: |
            *.deb
            *.tar.gz
            *.zip
            *.exe

      - name: Create release
        if: |
          github.ref == 'refs/heads/main' && startsWith(github.event.head_commit.message, 'chore(release)')
        uses: softprops/action-gh-release@v2
        with:
          draft: true
          files: |
            *.deb
            *.tar.gz
            *.zip
            *.exe
          name: ${{ steps.get_version.outputs.value }}
          tag_name: ''


================================================
FILE: .gitignore
================================================
# Generated by Cargo
# will have compiled files and executables
debug/
target/

# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock

# These are backup files generated by rustfmt
**/*.rs.bk

# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb

# RustRover
#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
#  and can be added to the global gitignore or merged into this file.  For a more nuclear
#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

vpm_modules/
Vpm.toml
vpm.toml
vpm.lock
.svlangserver/
.env

.DS_Store
.vscode/launch.json


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing Guidelines

First off, thank you for considering contributing to the Verilog Package Manager (VPM). It's people like you that make VPM such a great tool.

Following these guidelines helps to communicate that you respect the time of the developers managing and developing this open source project. In return, they should reciprocate that respect in addressing your issue, assessing changes, and helping you finalize your pull requests.

## Getting started
Anybody is welcome to contribute—we encourage anybody who is interested in this project to join the VPM discord. We'll discuss upcoming changes, user suggesions, and roadmaps.

_For something that is bigger than a one or two line fix:_
1. Create your own fork of the code
2. Do the changes in your fork (be sure to follow the code style of the project)
3. If you like the change and think the project could use it, send a pull request indicating that you have a CLA on file (for these larger fixes, try to include an update on the discord as well)

_For small or "obvious" fixes..._
* Small contributions such as fixing spelling errors, where the content is small enough to not be considered intellectual property, can be submitted by a contributor as a patch

**As a rule of thumb, changes are obvious fixes if they do not introduce any new functionality or creative thinking.** As long as the change does not affect functionality, some likely examples include the following:
- Spelling / grammar fixes
- Typo correction, white space and formatting changes
- Comment clean up
- Bug fixes that change default return values or error codes stored in constants
- Adding logging messages or debugging output
- Changes to ‘metadata’ files like Gemfile, .gitignore, build scripts, etc.
- Moving source files from one directory or package to another

## How to report a bug
**Security Disclosure**
If you find a security vulnerability, do **NOT** open an issue. Email jag.maddipatla@gmail.com or sathvik.redrouthu@gmail.com instead. Any security issues should be submitted here directly.
In order to determine whether you are dealing with a security issue, ask yourself these two questions:
* Can I access something that's not mine, or something I shouldn't have access to?
* Can I disable something for other people?
If the answer to either of those two questions are "yes", then you're probably dealing with a security issue. Note that even if you answer "no" to both questions, you may still be dealing with a security issue, so if you're unsure, just email us.

## Code review process
Once you submit a contribution, it will be signed off by either @Jag-M or @sathvikr prior to being implemented. Interested contributors should join our discord to get commit access.
We also hold weekly triage meetings in a public google meet that all contributors/interested persons may join. Any community feedback will be implemented as soon as possible (usually within a couple of hours).

## Philosophy
Our philosophy is to provide robust tooling to make chip design as intuitive as possible.

If you find yourself wishing for a feature that doesn't exist in VPM, you are probably not alone. There are bound to be others out there with similar needs. Many of the features that VPM has today have been added because our users saw the need. Open an issue on our issues list on GitHub which describes the feature you would like to see, why you need it, and how it should work.


================================================
FILE: Cargo.toml
================================================
[package]
description = "A powerful package manager for Verilog projects, streamlining IP core management and accelerating hardware design workflows"
documentation = "https://github.com/getinstachip/vpm#readme"
homepage = "https://getinstachip.com"
repository = "https://github.com/getinstachip/vpm"
name = "vpm"
version = "0.2.18"
edition = "2021"
license = "MIT"
copyright = "Copyright (c) 2024 Instachip"
authors = ["Instachip <team@getinstachip.com>"]

[dependencies]
clap = { version = "4.5.13", features = ["derive"] }
tokio = { version = "1.39.2", features = ["full"] }
openssl = { version = "0.10", features = ["vendored"] }
reqwest = { version = "0.12.5", features = ["json", "blocking"] }
serde = { version = "1.0.208", features = ["derive"] }
tree-sitter-verilog = { git = "https://github.com/tree-sitter/tree-sitter-verilog" }
tree-sitter = "0.20.6"
anyhow = "1.0.86"
serde_json = "1.0.125"
walkdir = "2.5.0"
once_cell = "1.19.0"
tempfile = "3.12.0"
cargo-lock = "9.0.0"
fastrand = "2.1.1"
fancy-regex = "0.13.0"
dialoguer = "0.11.0"
fuzzy-matcher = "0.3.7"
indicatif = "0.17.8"
which = "6.0.3"
regex = "1.10.6"
toml_edit = "0.22.20"
imara-diff = "0.1.7"
uuid = { version = "1.10.0", features = ["v7"] }
directories = "5.0.1"
ring = "0.17.8"
base64 = "0.22.1"
hex = "0.4.3"
rand = "0.8.5"
sha2 = "0.10.8"
sys-info = "0.9.1"

[build-dependencies]
cc="*"


================================================
FILE: LICENSE.md
================================================
# MIT License

Copyright (c) 2024 Instachip

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


================================================
FILE: README.md
================================================
# Verilog Package Manager (VPM)

VPM is a powerful package manager for Verilog projects, currently being piloted at Stanford and UC Berkeley. It's designed to streamline the management, reuse, and communication of IP cores and dependencies in hardware design workflows, significantly accelerating your design process.

## Features

- **Module Management**: Easily include, update, and remove modules in your project.
- **Documentation Generation**: Automatically create comprehensive documentation for your Verilog modules.
- **Dependency Handling**: Manage project dependencies with ease.
- **Simulation Support**: Simulate your Verilog files directly through VPM.
- **Tool Integration**: Seamlessly install and set up open-source tools for your project.
- **File Generation**: Automatically generate necessary files like .f, .svh, .xcd, and .tcl.

## Installation

VPM is designed for easy installation with no additional dependencies. 

### Default Installation (Linux/MacOS):
```bash
curl -f https://getinstachip.com/install.sh | sh
```

### Default Installation (Windows):
1. Download the `.zip` file matching your Windows architecture from the [latest release page](https://github.com/getinstachip/vpm/releases/latest)
2. Extract and run the `.exe` file

If installation doesn't work, try the following:

### Linux alternative:
We support Snap

```bash
snap download instachip-vpm
alias vpm='instachip-vpm.vpm'
```

### MacOS alternative:
```bash
brew tap getinstachip/vpm
brew install vpm
```

After installation, the vpm command will be available in any terminal.

## Commands

- `vpm include <path_to_module.sv>`: Include any module from a repo (and all its submodules).
- `vpm docs <module.sv>`: Generate documentation for any module (highlighting bugs and edge cases)
- `vpm install <tool>`: Auto-integrate an open-source tool without manual setup
- `vpm update <module.sv>`: Update module to the latest version
- `vpm remove <module.sv>`: Remove a module from your project
- `vpm list`: List all modules in our standard library
- `vpm dotf <module.sv>`:  Generate a `.f` filelist when exporting your project
- `vpm sim <module.sv> <testbench.sv>`: Simulate Verilog module using iVerilog
  
### vpm include
Include a module or repository in your project.

This command:
- Downloads the specified module or repository
- Analyzes the module hierarchy
- Includes all necessary submodules and generates appropriate header files
- Updates the vpm.toml file with new module details

This command comes in two forms:
1. Include a module and all its submodules:
```bash
vpm include <URL_TO_TOP_MODULE.sv>
```
`URL_TO_TOP_MODULE`: Full GitHub URL to the top module to include. The URL should come in the format of `https://github.com/<AUTHOR_NAME>/<REPO_NAME>/blob/branch/<PATH_TO_MODULE.sv>`.

Example:
```bash
vpm include https://github.com/ZipCPU/zipcpu/blob/master/rtl/core/prefetch.v
```

![](https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExY3Jmbmw0NWlva3F2bHdyY2h0NGZwNGlvNXRjZTY2bXB4ODRzOXd6eiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/KwHCr2ifmIZzSkpfjv/giphy.gif)

2. Include a repository:
```bash
vpm include --repo <AUTHOR_NAME/REPO_NAME>
```

Press tab to select multiple modules and press ENTER to install. If no modules are selected, all modules in the repository will be installed.

Example:
```bash
vpm include --repo ZipCPU/zipcpu
```
![](https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExMG5uaHJ1N2twd2JiY2pucjlwbjNjNm02NjRycDlocDF5bnB2eHNvYiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/QJ2sIDYIftEgu5uNAg/giphy.gif)
### vpm docs
Generate comprehensive documentation for a module.

This command generates a Markdown README file containing:
- Overview and module description
- Pinout diagram
- Table of ports
- Table of parameters
- Important implementation details
- Simulation output and GTKWave waveform details (Coming soon!)
- List of any major bugs or caveats if they exist

```bash
vpm docs <MODULE.sv>
```

`<MODULE>`: Name of the module to generate documentation for. Include the file extension.

`[URL]`: Optional URL of the repository to generate documentation for. If not specified, VPM will assume the module is local, and will search for the module in the vpm_modules directory.

Examples:
```bash
vpm docs pfcache.v
vpm docs pfcache.v https://github.com/ZipCPU/zipcpu
```
![](https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExOXc5NWpmYnV5eGxtYzRud2tid3poYTZyYXEwdmpqaGF3MjZwdW5leiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/C8nFHwNq0qBpXRF9pP/giphy.gif)

### vpm update
Update a package to the latest version.

This command:
- Checks for the latest version of the specified module
- Downloads and replaces the current version with the latest
- Updates all dependencies and submodules
- Modifies the vpm.toml file to reflect the changes

```bash
vpm update <PACKAGE_PATH>
```

`<PACKAGE_PATH>`: Full module path of the package to update

Example:
```bash
vpm update my_project/modules/counter
```

### vpm remove
Remove a package from your project.

This command:
- Removes the specified module from your project
- Updates the vpm.toml file to remove the module entry
- Cleans up any orphaned dependencies

```bash
vpm remove <PACKAGE_PATH>
```

`<PACKAGE_PATH>`: Full module path of the package to remove

Example:
```bash
vpm remove my_project/modules/unused_module
```

### vpm dotf
Generate a .f file list for a Verilog or SystemVerilog module.

```bash
vpm dotf <PATH_TO_TOP_MODULE>
```

`<PATH_TO_TOP_MODULE>`: Path to the top module to generate the file list for. File should be local.

Example:
```bash
vpm dotf ./vpm_modules/pfcache/fwb_master.v
```
![](https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExMHhkdjQ1bnl0cTA3cW1lOHVuNjkxaW1ydzFndXNnaDZlMHFiMWRpNSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/mafBT4PURloV52oLFP/giphy.gif)

This command:
- Analyzes the specified top module
- Identifies all submodules and dependencies
- Generates a .f file containing all necessary file paths
- Includes all locally scoped defines for submodules

### vpm install
Install and set up an open-source tool for integration into your project.

This command:
- Downloads the specified tool
- Configures the tool for your system
- Integrates it with your VPM project setup

```bash
vpm install <TOOL_NAME>
```
`<TOOL_NAME>`: Name of the tool to install

Example:
```bash
vpm install verilator
```
![](https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExNjFhc2t1ZTBwM29xdm10dThubWN3ZGhvOWhjeXJjNnQ0dWVqd2szdSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/737P65RSHVlu2dxXVu/giphy.gif)

Currently supported tools:
- Verilator
- Chipyard
- OpenROAD
- Edalize
- Icarus Verilog

Coming soon:
- Yosys (with support for ABC)
- RISC-V GNU Toolchain

### vpm sim
Simulate Verilog files.

This command:
- Compiles the specified Verilog files
- Runs the simulation
- Provides output and analysis of the simulation results

```bash
vpm sim <VERILOG_FILES>...
```
`<VERILOG_FILES>`: List of Verilog files to simulate using Icarus Verilog.

Example:
```bash
vpm sim testbench.v module1.v module2.v
```
![](https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExcnhiaDNwZmRhazVlODAxanlqaW1yaXdpazVmNTVwanJ4c2V3a3RscSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/6ImXOh4OVsjrWYrikf/giphy.gif)

### vpm list
List all modules in VPM's standard library.

This command displays all available modules in the standard Verilog library, including:
- Common modules
- RISC-V modules

```bash
vpm list
```

### vpm config
Configure VPM settings.

This command allows you to enable or disable anonymous usage data collection.

```bash
vpm config <OPTION> <VALUE>
```

OPTIONS:
- analytics (true/false): Enable or disable anonymous usage data collection.

Example:
```bash
vpm config --analytics true
```

## Configuration

VPM uses a `vpm.toml` file for project configuration. This file allows you to specify project properties, dependencies, and custom settings.

Example vpm.toml file:
```toml
[library]
name = "my_cpu"
version = "0.3.5"
description = "A basic CPU."

[dependencies]
"https://github.com/ZipCPU/zipcpu" = { modules = ["alu", "register_file"], commit = "1234567890abcdef" }
```

### Support and Contribution
For issues, feature requests, or contributions, please email sathvikr@getinstachip.com or create a GitHub Issue. Please read our CONTRIBUTING.md file for guidelines on how to contribute to VPM.

### License
VPM is released under the MIT License.

### Acknowledgements

We'd like to thank our early adopters for their valuable feedback and support in developing VPM:

- [Kasun Buddhi](https://www.linkedin.com/in/kasunbuddhi/)
- [Krishna](https://www.linkedin.com/in/krishna-verilog/)
- [Yash](https://www.linkedin.com/in/yash-verilog/)
- [Max Korbel](https://www.linkedin.com/in/maxkorbel/)
- [Samuel](https://www.linkedin.com/in/samuel-verilog/)
- [Biplab Das](https://www.linkedin.com/in/biplab-das-7b9870165/)
- [Nikhil Raju](https://www.linkedin.com/in/nikhil-raju-verilog/)

And many more contributors who have helped shape VPM.


================================================
FILE: install.sh
================================================
#!/bin/sh
# shellcheck shell=dash
# shellcheck disable=SC3043 # Assume `local` extension

# The official vpm installer.
#
# It runs on Unix shells like {a,ba,da,k,z}sh. It uses the common `local`
# extension. Note: Most shells limit `local` to 1 var per line, contra bash.

main() {
    # The version of ksh93 that ships with many illumos systems does not support the "local"
    # extension. Print a message rather than fail in subtle ways later on:
    if [ "${KSH_VERSION-}" = 'Version JM 93t+ 2010-03-05' ]; then
        err 'the installer does not work with this ksh93 version; please try bash'
    fi

    set -u

    parse_args "$@"

    local _arch
    _arch="${ARCH:-$(ensure get_architecture)}"
    assert_nz "${_arch}" "arch"
    echo "Detected architecture: ${_arch}"

    local _bin_name
    case "${_arch}" in
    *windows*) _bin_name="vpm.exe" ;;
    *) _bin_name="vpm" ;;
    esac

    # Create and enter a temporary directory.
    local _tmp_dir
    _tmp_dir="$(mktemp -d)" || err "mktemp: could not create temporary directory"
    cd "${_tmp_dir}" || err "cd: failed to enter directory: ${_tmp_dir}"

    # Download and extract vpm.
    local _package
    _package="$(ensure download_vpm "${_arch}")"
    assert_nz "${_package}" "package"
    echo "Downloaded package: ${_package}"
    case "${_package}" in
    *.tar.gz)
        need_cmd tar
        ensure tar -xf "${_package}"
        ;;
    *.zip)
        need_cmd unzip
        ensure unzip -oq "${_package}"
        ;;
    *)
        err "unsupported package format: ${_package}"
        ;;
    esac

    # Install binary.
    ensure try_sudo mkdir -p -- "${BIN_DIR}"
    ensure try_sudo cp -- "${_bin_name}" "${BIN_DIR}/${_bin_name}"
    ensure try_sudo chmod +x "${BIN_DIR}/${_bin_name}"
    echo "Installed vpm to ${BIN_DIR}"

    # Print success message and check $PATH.
    echo ""
    echo "vpm is installed!"
    if ! echo ":${PATH}:" | grep -Fq ":${BIN_DIR}:"; then
        echo "Note: ${BIN_DIR} is not on your \$PATH. vpm will not work unless it is added to \$PATH."
    fi
}

# Parse the arguments passed and set variables accordingly.
parse_args() {
    # BIN_DIR_DEFAULT="${HOME}/.local/bin"
    BIN_DIR_DEFAULT=/usr/local/bin
    # MAN_DIR_DEFAULT="${HOME}/.local/share/man"
    SUDO_DEFAULT="sudo"

    BIN_DIR="${BIN_DIR_DEFAULT}"
    # MAN_DIR="${MAN_DIR_DEFAULT}"
    SUDO="${SUDO_DEFAULT}"

    while [ "$#" -gt 0 ]; do
        case "$1" in
        --arch) ARCH="$2" && shift 2 ;;
        --arch=*) ARCH="${1#*=}" && shift 1 ;;
        --bin-dir) BIN_DIR="$2" && shift 2 ;;
        --bin-dir=*) BIN_DIR="${1#*=}" && shift 1 ;;
        # --man-dir) MAN_DIR="$2" && shift 2 ;;
        # --man-dir=*) MAN_DIR="${1#*=}" && shift 1 ;;
        --sudo) SUDO="$2" && shift 2 ;;
        --sudo=*) SUDO="${1#*=}" && shift 1 ;;
        -h | --help) usage && exit 0 ;;
        *) err "Unknown option: $1" ;;
        esac
    done
}

usage() {
    # heredocs are not defined in POSIX.
    local _text_heading _text_reset
    _text_heading="$(tput bold || true 2>/dev/null)$(tput smul || true 2>/dev/null)"
    _text_reset="$(tput sgr0 || true 2>/dev/null)"

    local _arch
    _arch="$(get_architecture || true)"

    echo "\
${_text_heading}vpm installer${_text_reset}
Instachip <team@getinstachip.com>
https://github.com/getinstachip/vpm

Fetches and installs vpm. If vpm is already installed, it will be updated to the latest version.

${_text_heading}Usage:${_text_reset}
  install.sh [OPTIONS]

${_text_heading}Options:${_text_reset}
      --arch     Override the architecture identified by the installer [current: ${_arch}]
      --bin-dir  Override the installation directory [default: ${BIN_DIR_DEFAULT}]
      # --man-dir  Override the manpage installation directory [default: ${MAN_DIR_DEFAULT}]
      --sudo     Override the command used to elevate to root privileges [default: ${SUDO_DEFAULT}]
  -h, --help     Print help"
}

download_vpm() {
    local _arch="$1"

    if check_cmd curl; then
        _dld=curl
    elif check_cmd wget; then
        _dld=wget
    else
        need_cmd 'curl or wget'
    fi
    need_cmd grep

    local _releases_url="https://api.github.com/repos/getinstachip/vpm/releases/latest"
    local _releases
    case "${_dld}" in
    curl) _releases="$(curl -sL "${_releases_url}")" ||
        err "curl: failed to download ${_releases_url}" ;;
    wget) _releases="$(wget -qO- "${_releases_url}")" ||
        err "wget: failed to download ${_releases_url}" ;;
    *) err "unsupported downloader: ${_dld}" ;;
    esac
    (echo "${_releases}" | grep -q 'API rate limit exceeded') &&
        err "you have exceeded GitHub's API rate limit. Please try again later, or use a different installation method: https://github.com/getinstachip/vpm/#installation"

    local _package_url
    _package_url="$(echo "${_releases}" | grep "browser_download_url" | cut -d '"' -f 4 | grep -- "${_arch}")" ||
        err "vpm has not yet been packaged for your architecture (${_arch}), please file an issue: https://github.com/getinstachip/vpm/issues"

    local _ext
    case "${_package_url}" in
    *.tar.gz) _ext="tar.gz" ;;
    *.zip) _ext="zip" ;;
    *) err "unsupported package format: ${_package_url}" ;;
    esac

    local _package="vpm.${_ext}"
    case "${_dld}" in
    curl) _releases="$(curl -sLo "${_package}" "${_package_url}")" || err "curl: failed to download ${_package_url}" ;;
    wget) _releases="$(wget -qO "${_package}" "${_package_url}")" || err "wget: failed to download ${_package_url}" ;;
    *) err "unsupported downloader: ${_dld}" ;;
    esac

    echo "${_package}"
}

try_sudo() {
    if "$@" >/dev/null 2>&1; then
        return 0
    fi

    need_sudo
    "${SUDO}" "$@"
}

need_sudo() {
    if ! check_cmd "${SUDO}"; then
        err "\
could not find the command \`${SUDO}\` needed to get permissions for install.

If you are on Windows, please run your shell as an administrator, then rerun this script.
Otherwise, please run this script as root, or install \`sudo\`."
    fi

    if ! "${SUDO}" -v; then
        err "sudo permissions not granted, aborting installation"
    fi
}

# The below functions have been extracted with minor modifications from the
# Rustup install script:
#
#   https://github.com/rust-lang/rustup/blob/4c1289b2c3f3702783900934a38d7c5f912af787/rustup-init.sh

get_architecture() {
    local _ostype _cputype _bitness _arch _clibtype
    _ostype="$(uname -s)"
    _cputype="$(uname -m)"
    _clibtype="musl"

    if [ "${_ostype}" = Linux ]; then
        if [ "$(uname -o || true)" = Android ]; then
            _ostype=Android
        fi
    fi

    if [ "${_ostype}" = Darwin ] && [ "${_cputype}" = i386 ]; then
        # Darwin `uname -m` lies
        if sysctl hw.optional.x86_64 | grep -q ': 1'; then
            _cputype=x86_64
        fi
    fi

    if [ "${_ostype}" = SunOS ]; then
        # Both Solaris and illumos presently announce as "SunOS" in "uname -s"
        # so use "uname -o" to disambiguate.  We use the full path to the
        # system uname in case the user has coreutils uname first in PATH,
        # which has historically sometimes printed the wrong value here.
        if [ "$(/usr/bin/uname -o || true)" = illumos ]; then
            _ostype=illumos
        fi

        # illumos systems have multi-arch userlands, and "uname -m" reports the
        # machine hardware name; e.g., "i86pc" on both 32- and 64-bit x86
        # systems.  Check for the native (widest) instruction set on the
        # running kernel:
        if [ "${_cputype}" = i86pc ]; then
            _cputype="$(isainfo -n)"
        fi
    fi

    case "${_ostype}" in
    Android)
        _ostype=linux-android
        ;;
    Linux)
        check_proc
        _ostype=unknown-linux-${_clibtype}
        _bitness=$(get_bitness)
        ;;
    FreeBSD)
        _ostype=unknown-freebsd
        ;;
    NetBSD)
        _ostype=unknown-netbsd
        ;;
    DragonFly)
        _ostype=unknown-dragonfly
        ;;
    Darwin)
        _ostype=apple-darwin
        ;;
    illumos)
        _ostype=unknown-illumos
        ;;
    MINGW* | MSYS* | CYGWIN* | Windows_NT)
        _ostype=pc-windows-msvc
        ;;
    *)
        err "unrecognized OS type: ${_ostype}"
        ;;
    esac

    case "${_cputype}" in
    i386 | i486 | i686 | i786 | x86)
        _cputype=i686
        ;;
    xscale | arm)
        _cputype=arm
        if [ "${_ostype}" = "linux-android" ]; then
            _ostype=linux-androideabi
        fi
        ;;
    armv6l)
        _cputype=arm
        if [ "${_ostype}" = "linux-android" ]; then
            _ostype=linux-androideabi
        else
            _ostype="${_ostype}eabihf"
        fi
        ;;
    armv7l | armv8l)
        _cputype=armv7
        if [ "${_ostype}" = "linux-android" ]; then
            _ostype=linux-androideabi
        else
            _ostype="${_ostype}eabihf"
        fi
        ;;
    aarch64 | arm64)
        _cputype=aarch64
        ;;
    x86_64 | x86-64 | x64 | amd64)
        _cputype=x86_64
        ;;
    mips)
        _cputype=$(get_endianness mips '' el)
        ;;
    mips64)
        if [ "${_bitness}" -eq 64 ]; then
            # only n64 ABI is supported for now
            _ostype="${_ostype}abi64"
            _cputype=$(get_endianness mips64 '' el)
        fi
        ;;
    ppc)
        _cputype=powerpc
        ;;
    ppc64)
        _cputype=powerpc64
        ;;
    ppc64le)
        _cputype=powerpc64le
        ;;
    s390x)
        _cputype=s390x
        ;;
    riscv64)
        _cputype=riscv64gc
        ;;
    *)
        err "unknown CPU type: ${_cputype}"
        ;;
    esac

    # Detect 64-bit linux with 32-bit userland
    if [ "${_ostype}" = unknown-linux-musl ] && [ "${_bitness}" -eq 32 ]; then
        case ${_cputype} in
        x86_64)
            # 32-bit executable for amd64 = x32
            if is_host_amd64_elf; then {
                err "x32 userland is unsupported"
            }; else
                _cputype=i686
            fi
            ;;
        mips64)
            _cputype=$(get_endianness mips '' el)
            ;;
        powerpc64)
            _cputype=powerpc
            ;;
        aarch64)
            _cputype=armv7
            if [ "${_ostype}" = "linux-android" ]; then
                _ostype=linux-androideabi
            else
                _ostype="${_ostype}eabihf"
            fi
            ;;
        riscv64gc)
            err "riscv64 with 32-bit userland unsupported"
            ;;
        *) ;;
        esac
    fi

    # Detect armv7 but without the CPU features Rust needs in that build,
    # and fall back to arm.
    # See https://github.com/rust-lang/rustup.rs/issues/587.
    if [ "${_ostype}" = "unknown-linux-musleabihf" ] && [ "${_cputype}" = armv7 ]; then
        if ensure grep '^Features' /proc/cpuinfo | grep -q -v neon; then
            # At least one processor does not have NEON.
            _cputype=arm
        fi
    fi

    _arch="${_cputype}-${_ostype}"
    echo "${_arch}"
}

get_bitness() {
    need_cmd head
    # Architecture detection without dependencies beyond coreutils.
    # ELF files start out "\x7fELF", and the following byte is
    #   0x01 for 32-bit and
    #   0x02 for 64-bit.
    # The printf builtin on some shells like dash only supports octal
    # escape sequences, so we use those.
    local _current_exe_head
    _current_exe_head=$(head -c 5 /proc/self/exe)
    if [ "${_current_exe_head}" = "$(printf '\177ELF\001')" ]; then
        echo 32
    elif [ "${_current_exe_head}" = "$(printf '\177ELF\002')" ]; then
        echo 64
    else
        err "unknown platform bitness"
    fi
}

get_endianness() {
    local cputype="$1"
    local suffix_eb="$2"
    local suffix_el="$3"

    # detect endianness without od/hexdump, like get_bitness() does.
    need_cmd head
    need_cmd tail

    local _current_exe_endianness
    _current_exe_endianness="$(head -c 6 /proc/self/exe | tail -c 1)"
    if [ "${_current_exe_endianness}" = "$(printf '\001')" ]; then
        echo "${cputype}${suffix_el}"
    elif [ "${_current_exe_endianness}" = "$(printf '\002')" ]; then
        echo "${cputype}${suffix_eb}"
    else
        err "unknown platform endianness"
    fi
}

is_host_amd64_elf() {
    need_cmd head
    need_cmd tail
    # ELF e_machine detection without dependencies beyond coreutils.
    # Two-byte field at offset 0x12 indicates the CPU,
    # but we're interested in it being 0x3E to indicate amd64, or not that.
    local _current_exe_machine
    _current_exe_machine=$(head -c 19 /proc/self/exe | tail -c 1)
    [ "${_current_exe_machine}" = "$(printf '\076')" ]
}

check_proc() {
    # Check for /proc by looking for the /proc/self/exe link.
    # This is only run on Linux.
    if ! test -L /proc/self/exe; then
        err "unable to find /proc/self/exe. Is /proc mounted? Installation cannot proceed without /proc."
    fi
}

need_cmd() {
    if ! check_cmd "$1"; then
        err "need '$1' (command not found)"
    fi
}

check_cmd() {
    command -v -- "$1" >/dev/null 2>&1
}

# Run a command that should never fail. If the command fails execution
# will immediately terminate with an error showing the failing
# command.
ensure() {
    if ! "$@"; then err "command failed: $*"; fi
}

assert_nz() {
    if [ -z "$1" ]; then err "found empty string: $2"; fi
}

err() {
    echo "Error: $1" >&2
    exit 1
}

# This is put in braces to ensure that the script does not run until it is
# downloaded completely.
{
    main "$@" || exit 1
}


================================================
FILE: installer.iss
================================================
#define VerFile = FileOpen("version.txt")
#define MyAppVersion = FileRead(VerFile)
#expr FileClose(VerFile)
#undef VerFile

#define TarFile FileOpen("target.txt")
#define MyTarget FileRead(TarFile)
#expr FileClose(TarFile)
#undef TarFile

#define MyAppName "VPM"
#define MyAppPublisher "Instachip"
#define MyAppURL "https://getinstachip.com/"

[Setup]
AppId={{E3D813B5-C9DB-4FC0-957C-9D06371B378E}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
CreateAppDir=yes
PrivilegesRequired=lowest
PrivilegesRequiredOverridesAllowed=dialog
Compression=lzma
SolidCompression=yes
WizardStyle=modern
DefaultDirName={autopf}\{#MyAppName}
DisableDirPage=no
DirExistsWarning=no
OutputBaseFilename=vpm-installer-{#MyAppVersion}-{#MyTarget}
OutputDir=.

[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
Name: "armenian"; MessagesFile: "compiler:Languages\Armenian.isl"
Name: "brazilianportuguese"; MessagesFile: "compiler:Languages\BrazilianPortuguese.isl"
Name: "bulgarian"; MessagesFile: "compiler:Languages\Bulgarian.isl"
Name: "catalan"; MessagesFile: "compiler:Languages\Catalan.isl"
Name: "corsican"; MessagesFile: "compiler:Languages\Corsican.isl"
Name: "czech"; MessagesFile: "compiler:Languages\Czech.isl"
Name: "danish"; MessagesFile: "compiler:Languages\Danish.isl"
Name: "dutch"; MessagesFile: "compiler:Languages\Dutch.isl"
Name: "finnish"; MessagesFile: "compiler:Languages\Finnish.isl"
Name: "french"; MessagesFile: "compiler:Languages\French.isl"
Name: "german"; MessagesFile: "compiler:Languages\German.isl"
Name: "hebrew"; MessagesFile: "compiler:Languages\Hebrew.isl"
Name: "hungarian"; MessagesFile: "compiler:Languages\Hungarian.isl"
Name: "icelandic"; MessagesFile: "compiler:Languages\Icelandic.isl"
Name: "italian"; MessagesFile: "compiler:Languages\Italian.isl"
Name: "japanese"; MessagesFile: "compiler:Languages\Japanese.isl"
Name: "korean"; MessagesFile: "compiler:Languages\Korean.isl"
Name: "norwegian"; MessagesFile: "compiler:Languages\Norwegian.isl"
Name: "polish"; MessagesFile: "compiler:Languages\Polish.isl"
Name: "portuguese"; MessagesFile: "compiler:Languages\Portuguese.isl"
Name: "russian"; MessagesFile: "compiler:Languages\Russian.isl"
Name: "slovak"; MessagesFile: "compiler:Languages\Slovak.isl"
Name: "slovenian"; MessagesFile: "compiler:Languages\Slovenian.isl"
Name: "spanish"; MessagesFile: "compiler:Languages\Spanish.isl"
Name: "turkish"; MessagesFile: "compiler:Languages\Turkish.isl"
Name: "ukrainian"; MessagesFile: "compiler:Languages\Ukrainian.isl"

[Files]
Source: "target\{#MyTarget}\release\vpm.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "README.md"; DestDir: "{app}"; Flags: ignoreversion

[Tasks]
Name: addtopath; Description: "Add application directory to PATH"; Flags: checkedonce

[Code]
const
    ModPathName = 'modifypath';
    ModPathType = 'user';

function ModPathDir(): TArrayOfString;
begin
    SetArrayLength(Result, 1);
    Result[0] := ExpandConstant('{app}');
end;

procedure ModPath();
var
    oldpath: string;
    newpath: string;
    updatepath: boolean;
begin
    if not RegQueryStringValue(HKEY_CURRENT_USER, 'Environment', 'Path', oldpath) then
        oldpath := '';

    updatepath := true;

    if (Pos(';' + UpperCase(ExpandConstant('{app}')) + ';', ';' + UpperCase(oldpath) + ';') > 0) then
        updatepath := false
    else if (Pos(';' + UpperCase(ExpandConstant('{app}')) + '\;', ';' + UpperCase(oldpath) + ';') > 0) then
        updatepath := false;

    if (updatepath) then begin
        newpath := oldpath;
        if (Pos(';', oldpath) > 0) then
            newpath := newpath + ';' + ExpandConstant('{app}')
        else
            newpath := ExpandConstant('{app}');

        if RegWriteStringValue(HKEY_CURRENT_USER, 'Environment', 'Path', newpath) then
        begin
            StringChangeEx(oldpath, ';', #13#10, True);
            StringChangeEx(newpath, ';', #13#10, True);
            Log('Old PATH:' + #13#10 + oldpath);
            Log('New PATH:' + #13#10 + newpath);
            RegWriteStringValue(HKEY_CURRENT_USER, 'Environment', 'Path', newpath);
        end else
            Log('Error: Failed to modify PATH');
    end;
end;

procedure CurStepChanged(CurStep: TSetupStep);
begin
    if (CurStep = ssPostInstall) and WizardIsTaskSelected('addtopath') then
        ModPath();
end;

procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
var
    oldpath: string;
    newpath: string;
    pathArr: TArrayOfString;
    i: Integer;
begin
    if (CurUninstallStep = usUninstall) then begin
        if RegQueryStringValue(HKEY_CURRENT_USER, 'Environment', 'Path', oldpath) then begin
            oldpath := oldpath + ';';
            i := 0;
            while (Pos(';', oldpath) > 0) do begin
                SetArrayLength(pathArr, i + 1);
                pathArr[i] := Copy(oldpath, 0, Pos(';', oldpath) - 1);
                oldpath := Copy(oldpath, Pos(';', oldpath) + 1, Length(oldpath));
                i := i + 1;

                if (pathArr[i - 1] <> ExpandConstant('{app}')) then begin
                    if (newpath = '') then
                        newpath := pathArr[i - 1]
                    else
                        newpath := newpath + ';' + pathArr[i - 1];
                end;
            end;

            RegWriteStringValue(HKEY_CURRENT_USER, 'Environment', 'Path', newpath);
        end;
    end;
end;

[UninstallDelete]
Type: filesandordirs; Name: "{app}"

================================================
FILE: src/cmd/cmd.rs
================================================
use clap::Parser;

#[derive(Debug, Parser)]
#[clap(
    about = "VPM - Verilog Package Manager",
    author,
    version,
    propagate_version = true,
    disable_help_subcommand = true,
    after_help = "Run 'vpm <COMMAND> --help' for more information on a specific command."
)]
pub enum Cmd {
    #[command(
        about = "vpm include <MODULE_URL> [--repo] [--riscv] [--commit <HASH>] // Add a module or repository to your project",
        long_about = "Include a module with one command. VPM's internal parser will identify and configure any subdependencies."
    )]
    Include(Include),

    #[command(
        about = "vpm update <MODULE_PATH> // Update a module to its latest version",
        long_about = "Update a specific module to its latest version. This command checks for updates to the specified module and applies them if available."
    )]
    Update(Update),

    #[command(
        about = "vpm remove <PACKAGE_PATH> // Remove a package from your project",
        long_about = "Remove a package from your project. This command uninstalls the specified package and removes it from your project's dependencies, helping you maintain a clean and efficient project structure."
    )]
    Remove(Remove),

    #[command(
        about = "vpm dotf <TOP_MODULE_PATH> // Generate a .f filelist for a module",
        long_about = "Generate a filelist (.f file) for a top module and all its submodules."
    )]
    Dotf(Dotf),

    #[command(
        about = "vpm docs <MODULE_PATH> [--url <URL>] // Generate documentation for a module",
        long_about = "Generate documentation for a module. This command creates comprehensive documentation for the specified module, including descriptions of inputs, outputs, and functionality. It supports both local modules and those hosted on remote repositories."
    )]
    Docs(Docs),

    #[command(
        about = "vpm install <TOOL_NAME> // Install a specified tool",
        long_about = "Install a specified tool. VPM automates the build process and installs missing subdependencies. Support for powerful linters, Icarus Verilog, Verilator, Yosys, GTKWave, the RISC-V toolchain, and more."
    )]
    Install(Install),

    #[command(
        about = "vpm list // List all available modules in the project",
        long_about = "List all available modules in the current project. This command provides an overview of all modules currently included in your project, helping you keep track of your dependencies and project structure."
    )]
    List(List),

    #[command(
        about = "vpm sim <FILE_PATHS>... // Simulate Verilog files",
        long_about = "Simulate one or more Verilog files. This command runs simulations on the specified Verilog files, allowing you to test and verify the behavior of your designs before synthesis or implementation."
    )]
    Sim(Sim),

    #[command(
        about = "vpm synth <TOP_MODULE_PATH> // Synthesize a top module",
        long_about = "Synthesize a top module. This command performs synthesis on the specified top module, converting your RTL design into a gate-level netlist. Supports synthesis for:
    • Board-agnostic (default)
    • Xilinx FPGAs
    • Altera FPGAs (coming soon)
    • Custom board files (coming soon)
    "
    )]
    Synth(Synth),

    #[command(
        about = "vpm load <TOP_MODULE_PATH> // Load a top module onto a target device",
        long_about = "Load a top module onto a target device. This command programs the synthesized design onto the specified hardware, allowing you to test your design on actual FPGA or ASIC hardware."
    )]
    Load(Load),

    #[command(
        about = "vpm run <PROGRAM> // Execute a specified program",
        long_about = "Run a specified program. This command executes the given program, which can be useful for running custom scripts, tools, or compiled designs as part of your Verilog development workflow."
    )]
    Run(Run),

    #[command(
        about = "vpm upgrade // Upgrade VPM to the latest version",
        long_about = "Upgrade VPM to the latest version available."
    )]
    Upgrade(Upgrade),

    #[command(
        about = "vpm config <KEY> <VALUE> // Configure VPM settings",
        long_about = "Configure VPM settings. This command allows you to set various options and preferences for VPM, such as enabling or disabling analytics."
    )]
    Config(Config),
}

#[derive(Debug, Parser)]
pub struct Upgrade {}

#[derive(Debug, Parser)]
pub struct Include {
    #[arg(long, short, help = "If this flag is set, the URL will be treated as a full repository. If not set, the URL will be treated as a single module.")]
    pub repo: bool,
    #[arg(help = "GitHub URL of the module to include. This should point to a single .v or .sv file in GitHub. If --repo is set, <URL> should not be a full repository URL, but rather 'AUTHOR_NAME/REPO_NAME'")]
    pub url: String,
    #[arg(long, help = "Include RISC-V specific modules. Use this flag when including modules designed specifically for RISC-V architectures.")]
    pub riscv: bool,
    #[arg(long, help = "Commit hash of the module to include. This should be a valid commit hash from the module's repository.")]
    pub commit: Option<String>,
}

#[derive(Debug, Parser)]
pub struct Update {
    #[arg(help = "Full module path of the module to update. This should be the complete path to the module file within your project structure.")]
    pub module_path: String,
    #[arg(long, help = "Update to the given commit hash. If not set, the latest commit hash will be used.")]
    pub commit: Option<String>,
}

#[derive(Debug, Parser)]
pub struct Remove {
    #[arg(help = "Full module path of the package to remove. This should be the complete path to the package directory within your project structure.")]
    pub package_path: String,
}

#[derive(Debug, Parser)]
pub struct Dotf {
    #[arg(help = "Path to the top module to generate a filelist for. This should be the complete path to the top module file within your project structure.")]
    pub path_to_top_module: String,
}

#[derive(Debug, Parser)]
pub struct Docs {
    #[arg(help = "Path of the module to generate documentation for. This should be the path to the module file within your project structure, starting with 'vpm_modules/'.")]
    pub module_path: String,
    #[arg(long, help = "If this flag is set, the module path will be treated as a link to a .v or .sv file in a GitHub repository. If not set, the path will be treated as a local file path.")]
    pub from_repo: bool,
    #[arg(long, help = "Generate documentation in offline mode for code security.")]
    pub offline: bool,
}

#[derive(Debug, Parser)]
pub struct Install {
    #[arg(help = "Name of the tool to install. This should be a valid tool name recognized by VPM. Available options:
    • verilator: A fast Verilog/SystemVerilog simulator
    • iverilog: Icarus Verilog, a Verilog simulation and synthesis tool
    • yosys: Open-source Verilog synthesis suite
    • gtkwave: Waveform viewer for simulation results
    • verible: SystemVerilog parser, style linter, and formatter
    • edalize: One-stop library for interfacing EDA tools
    • riscv-gnu-toolchain: GNU toolchain for RISC-V, including GCC compiler and associated tools")]
    pub tool_name: String,
}

#[derive(Debug, Parser)]
pub struct Sim {
    #[arg(help = "List of paths to .v or .sv files you want to simulate. Include '_tb' in the testbench file name; otherwise, a base testbench and waveform will be generated.")]
    pub verilog_files: Vec<String>,
    #[arg(long, help = "Generate waveform output. If set, the simulation will produce waveform data and open it in GTKWave.")]
    pub waveform: bool,
    pub folder: Option<String>,
}

#[derive(Debug, Parser)]
pub struct List {}

#[derive(Debug, Parser)]
pub struct Synth {
    #[arg(help = "Top module path to synthesize. This should be the path to the main module of your design that you want to synthesize.")]
    pub top_module_path: String,
    #[arg(long, help = "Set this flag if you're working with a RISC-V based design.")]
    pub riscv: bool,
    #[arg(long, help = "Path to RISC-V core. Required if --riscv is set. This should be the path to your RISC-V core implementation.")]
    pub core_path: Option<String>,
    #[arg(long, help = "Specify target board. Use this to optimize the synthesis for a specific FPGA board. Current options:
    • xilinx: Optimize for Xilinx FPGA boards
    • altera: Optimize for Altera FPGA boards (coming soon)
    • custom: Use a custom board file (coming soon)")]
    pub board: Option<String>,
    #[arg(long, help = "Generate synthesis script. If set, the command will produce a Yosys synthesis script instead of running the synthesis directly.")]
    pub gen_yosys_script: bool,
}

#[derive(Debug, Parser)]
pub struct Load {
    #[arg(help = "Path to the top module to load. This should be the path to the synthesized netlist or bitstream file.")]
    pub top_module_path: String,
    #[arg(help = "Path to the .xcd constraint file. This file should contain timing and placement constraints for your design.")]
    pub constraints_path: String,
    #[arg(long, help = "Use RISC-V toolchain. Set this flag if you're working with a RISC-V based design and need to use RISC-V specific tools for loading.")]
    pub riscv: bool,
}

#[derive(Debug, Parser)]
pub struct Run {
    #[arg(help = "Path to the program to run. This can be a compiled binary, a script, or any executable file.")]
    pub program_path: String,
    #[arg(long, help = "Use RISC-V toolchain. Set this flag if you're running a program compiled for RISC-V architecture and need to use RISC-V specific tools or emulators.")]
    pub riscv: bool,
}

#[derive(Debug, Parser)]
pub struct Config {
    #[arg(long, help = "Enable or disable anonymous usage data collection. Set to false to opt-out of data collection.")]
    pub analytics: Option<bool>,
}


================================================
FILE: src/cmd/config.rs
================================================
use crate::cmd::{Execute, Config};
use crate::config_man::set_analytics;
use anyhow::Result;

impl Execute for Config {
    async fn execute(&self) -> Result<()> {
        if self.analytics.is_some() {
            set_analytics(self.analytics.unwrap())?;
            println!("Analytics set to: {}", self.analytics.unwrap());
        }
        Ok(())
    }
}

================================================
FILE: src/cmd/docs.rs
================================================
use anyhow::{Result, Context, anyhow};
use reqwest::Client;
use std::path::PathBuf;
use serde_json::json;
use std::fs;
use indicatif::{ProgressBar, ProgressStyle};
use std::process::{Command, Stdio};

use crate::cmd::{Execute, Docs};
use crate::config_man::{decrypt_docs_count, encrypt_docs_count};

impl Execute for Docs {
    async fn execute(&self) -> Result<()> {
        let docs_count = decrypt_docs_count()?;
        if docs_count >= 10 {
            println!("You have used all your documentation generation credits. Consider upgrading to VPM Pro for unlimited and betterdocumentation generation.");
            return Ok(());
        }

        if self.from_repo {
            let content = fetch_module_content(&self.module_path).await
                .context("Failed to fetch module content. Please check your internet connection and ensure the provided URL is correct.")?;
            let file_name = self.module_path.split('/').last().unwrap_or(&self.module_path);
            let folder_name = file_name.split('.').next().unwrap_or(file_name);
            let destination = PathBuf::from("./vpm_modules").join(folder_name);
            fs::create_dir_all(&destination)
                .context("Failed to create destination directory. Please check if you have write permissions in the current directory.")?;
            if self.offline {
                generate_docs_offline(&self.module_path, &content, Some(destination.join(format!("{}_README.md", folder_name)))).await
                    .context("Failed to generate documentation offline. Please check the module content and try again.")?;
            } else {
                generate_docs(&self.module_path, &content, Some(destination.join(format!("{}_README.md", folder_name)))).await
                    .context("Failed to generate documentation. Please check the module content and try again.")?;
            }
        } else {
            let full_module_path = PathBuf::from(&self.module_path);
            
            if full_module_path.exists() {
                let content = fs::read_to_string(&full_module_path)
                    .with_context(|| format!("Failed to read module file: {}. Please ensure you have read permissions for this file.", full_module_path.display()))?;
                println!("Generating documentation for local module '{}'", self.module_path);
                let readme_path = full_module_path.with_file_name(format!("{}_README.md", full_module_path.file_stem().unwrap().to_str().unwrap()));
                if self.offline {
                    generate_docs_offline(&self.module_path, &content, Some(readme_path)).await
                        .context("Failed to generate documentation offline for the local module. Please check the module content and try again.")?;
                } else {
                    generate_docs(&self.module_path, &content, Some(readme_path)).await
                        .context("Failed to generate documentation for the local module. Please check the module content and try again.")?;
                }
            } else {
                return Err(anyhow!("Module '{}' not found in vpm_modules. Please provide a URL to a repository containing the module, or ensure the module exists in the correct location.", self.module_path));
            }
        }
        encrypt_docs_count(docs_count + 1)?;
        println!("Documentation generated successfully. You have used {} of your 10 credits.", docs_count + 1);
        Ok(())
    }
}

async fn fetch_module_content(url: &str) -> Result<String> {
    let client = reqwest::Client::new();

    // Extract the raw content URL
    let raw_url = url.replace("github.com", "raw.githubusercontent.com")
                     .replace("/blob/", "/");

    println!("Fetching content from URL: {}", raw_url);

    // Fetch the content
    let response = client.get(&raw_url).send().await?;

    if !response.status().is_success() {
        return Err(anyhow::anyhow!("Failed to fetch module content: HTTP {}", response.status()));
    }

    let content = response.text().await?;

    Ok(content)
}

fn format_text(text: &str) -> String {
    text.replace("\\n", "\n")
        .replace("\\'", "'")
        .replace("\\\"", "\"")
        .replace("\\\\", "\\")
}

async fn generate_docs(module_path: &str, content: &str, full_module_path: Option<PathBuf>) -> Result<()> {
    let pb = ProgressBar::new(100);
    pb.set_style(ProgressStyle::default_bar()
        .template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta}) {msg}")
        .unwrap_or_else(|_| ProgressStyle::default_bar())
        .progress_chars("#>-"));
    
    pb.set_position(33);
    pb.set_message("Generating documentation...");

    let client = Client::new();
    let api_url = "https://bmniatl2bh.execute-api.us-east-1.amazonaws.com/dev/getApiKey";
    let response = client.post(api_url)
        .header("Content-Type", "application/json")
        .json(&json!({ "code": content }))
        .send().await
        .context("Failed to send request to documentation generation API. Please check your internet connection and try again.")?;

    let documentation = format_text(&response.text().await
        .context("Failed to read response from documentation generation API. The API might be experiencing issues. Please try again later.")?);

    pb.set_position(66);
    pb.set_message("Writing documentation to file...");

    let readme_path = if let Some(path) = full_module_path {
        path
    } else {
        let module_name = module_path.rsplit('/').next().unwrap_or(module_path);
        let dir = PathBuf::from("./vpm_modules").join(module_name).parent().unwrap().to_path_buf();
        fs::create_dir_all(&dir)
            .with_context(|| format!("Failed to create directory: {}. Please ensure you have write permissions in this location.", dir.display()))?;
        dir.join(format!("{}_README.md", module_name))
    };
    tokio::fs::write(&readme_path, documentation).await
        .with_context(|| format!("Failed to write documentation to file: {}. Please ensure you have write permissions in this location.", readme_path.display()))?;
    
    pb.set_position(100);
    pb.finish_with_message(format!("Documentation for {} written to {}", module_path, readme_path.display()));

    Ok(())
}

async fn generate_docs_offline(module_path: &str, content: &str, full_module_path: Option<PathBuf>) -> Result<()> {
    let pb = ProgressBar::new(100);
    pb.set_style(ProgressStyle::default_bar()
        .template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta}) {msg}")
        .unwrap_or_else(|_| ProgressStyle::default_bar())
        .progress_chars("#>-"));
    
    pb.set_position(33);
    pb.set_message("Generating documentation offline...");

    // Check if Ollama is installed
    if !Command::new("ollama").arg("--version").output().is_ok() {
        pb.set_message("Ollama not found. Installing...");
        
        // Install Ollama
        let install_status = if cfg!(target_os = "macos") {
            Command::new("brew").args(&["install", "ollama"]).status()
        } else if cfg!(target_os = "linux") {
            Command::new("curl").args(&["-fsSL", "https://ollama.ai/install.sh", "|", "sh"]).status()
        } else {
            return Err(anyhow::anyhow!("Unsupported operating system for Ollama installation"));
        };

        if let Err(e) = install_status {
            return Err(anyhow::anyhow!("Failed to install Ollama: {}", e));
        }

        pb.set_message("Ollama installed successfully");
    }

    // Start Ollama server in the background
    let mut ollama_serve = Command::new("ollama")
        .arg("serve")
        .stdout(Stdio::null())
        .stderr(Stdio::null())
        .spawn()
        .context("Failed to start Ollama server. Make sure it's installed and in your PATH.")?;

    // Give the server a moment to start up
    tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;

    // Prepare the Ollama command
    let ollama_output = Command::new("ollama")
        .arg("run")
        .arg("codellama")
        .arg("Generate documentation for the following Verilog module:")
        .arg(content)
        .output()
        .context("Failed to execute Ollama. Make sure it's installed and in your PATH.")?;

    // Stop the Ollama server
    ollama_serve.kill().context("Failed to stop Ollama server")?;

    if !ollama_output.status.success() {
        return Err(anyhow::anyhow!("Ollama command failed: {}", String::from_utf8_lossy(&ollama_output.stderr)));
    }

    let documentation = String::from_utf8(ollama_output.stdout)
        .context("Failed to parse Ollama output as UTF-8")?;

    pb.set_position(66);
    pb.set_message("Writing documentation to file...");

    let readme_path = if let Some(path) = full_module_path {
        path
    } else {
        let module_name = module_path.rsplit('/').next().unwrap_or(module_path);
        let dir = PathBuf::from("./vpm_modules").join(module_name).parent().unwrap().to_path_buf();
        fs::create_dir_all(&dir)
            .with_context(|| format!("Failed to create directory: {}. Please ensure you have write permissions in this location.", dir.display()))?;
        dir.join(format!("{}_README.md", module_name))
    };
    tokio::fs::write(&readme_path, documentation).await
        .with_context(|| format!("Failed to write documentation to file: {}. Please ensure you have write permissions in this location.", readme_path.display()))?;
    
    pb.set_position(100);
    pb.finish_with_message(format!("Documentation for {} written to {}", module_path, readme_path.display()));

    Ok(())
}


================================================
FILE: src/cmd/dotf.rs
================================================
use anyhow::Result;
use std::fs;
use std::path::{Path, PathBuf};
use std::io::Write;

use crate::cmd::{Execute, Dotf};

impl Execute for Dotf {
    async fn execute(&self) -> Result<()> {
        // Clear the .f file if it already exists
        let top_module_file = Path::new(&self.path_to_top_module).file_name().and_then(|f| f.to_str()).unwrap_or("");
        let top_module_dir = Path::new(&self.path_to_top_module).with_extension("").to_str().unwrap_or("").to_string();
        let filelist_name = format!("{}.f", top_module_file.trim_end_matches(".sv").trim_end_matches(".v"));
        let filelist_path = PathBuf::from("vpm_modules").join(&top_module_dir).join(&filelist_name);

        if filelist_path.exists() {
            fs::write(&filelist_path, "")?;
        }
        let _ = append_modules_to_filelist(&self.path_to_top_module, true);
        Ok(())
    }
}

pub fn append_modules_to_filelist(top_module_path: &str, sub: bool) -> Result<()> {
    let vpm_modules_dir = PathBuf::from("./vpm_modules");
    let mut visited_modules: Vec<String> = Vec::new();

    let top_module_file = Path::new(top_module_path).file_name().and_then(|f| f.to_str()).unwrap_or("");
    let top_module_dir = Path::new(top_module_path).with_extension("").to_str().unwrap_or("").to_string();
    let filelist_name = format!("{}.f", top_module_file.trim_end_matches(".sv").trim_end_matches(".v"));
    let filelist_path = PathBuf::from("vpm_modules").join(&top_module_dir).join(&filelist_name);

    let mut filepaths = Vec::new();
    let mut f_statements = Vec::new();
    let mut define_statements = Vec::new();

    append_module(&vpm_modules_dir, top_module_file, top_module_file, &mut visited_modules, sub, &filelist_path, &mut filepaths, &mut f_statements, &mut define_statements)?;

    // Write all filepaths together
    let mut file = fs::OpenOptions::new()
        .append(true)
        .create(true)
        .open(&filelist_path)?;

    // Add +incdir+ statement
    file.write_all(format!("+incdir+{}\n\n", vpm_modules_dir.join(&top_module_dir).display()).as_bytes())?;

    for filepath in filepaths {
        file.write_all(format!("{}\n", filepath).as_bytes())?;
    }

    file.write_all(b"\n")?;

    // Write all unique define statements together
    let mut unique_defines: std::collections::HashSet<String> = std::collections::HashSet::new();
    for define in define_statements {
        unique_defines.insert(define);
    }
    for define in unique_defines {
        file.write_all(format!("{}\n", define).as_bytes())?;
    }

    file.write_all(b"\n")?;

    // Write all -f statements together
    for f_statement in f_statements {
        file.write_all(format!("{}\n", f_statement).as_bytes())?;
    }

    Ok(())
}

fn append_module(
    dir: &Path,
    module: &str,
    top_module: &str,
    visited_modules: &mut Vec<String>,
    sub: bool,
    filelist_path: &Path,
    filepaths: &mut Vec<String>,
    f_statements: &mut Vec<String>,
    define_statements: &mut Vec<String>,
) -> Result<()> {
    for entry in fs::read_dir(dir)? {
        let entry = entry?;
        let path = entry.path();
        if path.is_file() && path.file_name().map_or(false, |name| name == module) {
            let module_path = path.to_str().unwrap_or_default();
            let contents = fs::read_to_string(&path)?;

            filepaths.push(module_path.to_string());

            // Check for `define macros and module boundaries
            let mut in_ifdef_block = false;
            let mut in_module = false;
            let mut module_defines = Vec::new();
            let mut current_module_name = String::new();

            for line in contents.lines() {
                let trimmed_line = line.trim();
                if trimmed_line.starts_with("module") {
                    in_module = true;
                    current_module_name = trimmed_line.split_whitespace().nth(1).unwrap_or("").to_string();
                } else if trimmed_line.starts_with("endmodule") {
                    in_module = false;
                    if !module_defines.is_empty() {
                        if current_module_name == top_module.trim_end_matches(".v") {
                            // Add defines to the top module's .f file
                            define_statements.extend(module_defines.clone());
                        } else {
                            let submodule_filelist_name = format!("{}.f", current_module_name);
                            let submodule_filelist_path = dir.join(&submodule_filelist_name);
                            let mut submodule_file = fs::OpenOptions::new()
                                .append(true)
                                .create(true)
                                .open(&submodule_filelist_path)?;
                            for define in &module_defines {
                                submodule_file.write_all(format!("{}\n", define).as_bytes())?;
                            }
                            f_statements.push(format!("-f {}", submodule_filelist_path.to_str().unwrap_or_default()));
                        }
                    }
                    module_defines.clear();
                } else if trimmed_line.starts_with("`ifdef") {
                    in_ifdef_block = true;
                    let parts: Vec<&str> = trimmed_line.split_whitespace().collect();
                    if parts.len() >= 2 {
                        let macro_name = parts[1];
                        let define = format!("+define+{}", macro_name);
                        if !in_module {
                            if !define_statements.contains(&define) {
                                define_statements.push(define.clone());
                            }
                        } else {
                            module_defines.push(define);
                        }
                    }
                } else if trimmed_line.starts_with("`endif") {
                    in_ifdef_block = false;
                } else if !in_ifdef_block && trimmed_line.starts_with("`define") {
                    let parts: Vec<&str> = trimmed_line.split_whitespace().collect();
                    let define = if parts.len() >= 3 {
                        let macro_name = parts[1];
                        let macro_value = parts[2..].join(" ");
                        format!("+define+{}={}", macro_name, macro_value)
                    } else if parts.len() == 2 {
                        let macro_name = parts[1];
                        format!("+define+{}", macro_name)
                    } else {
                        continue;
                    };
                    if !in_module {
                        if !define_statements.contains(&define) {
                            define_statements.push(define.clone());
                        }
                    } else {
                        module_defines.push(define);
                    }
                }
            }

            if sub {
                let mut parser = tree_sitter::Parser::new();
                parser
                    .set_language(tree_sitter_verilog::language())
                    .expect("Error loading Verilog grammar");

                if let Some(tree) = parser.parse(&contents, None) {
                    let root_node = tree.root_node();
                    find_module_instantiations(
                        root_node,
                        top_module,
                        &contents,
                        visited_modules,
                        sub,
                        filelist_path,
                        filepaths,
                        f_statements,
                        define_statements)?;
                }
            }

            return Ok(());
        } else if path.is_dir() {
            append_module(
                &path,
                module,
                top_module,
                visited_modules,
                sub,
                filelist_path,
                filepaths,
                f_statements,
                define_statements)?;
        }
    }

    Ok(())
}

fn find_module_instantiations(
    root_node: tree_sitter::Node,
    top_module: &str,
    contents: &str,
    visited_modules: &mut Vec<String>,
    sub: bool,
    filelist_path: &Path,
    filepaths: &mut Vec<String>,
    f_statements: &mut Vec<String>,
    define_statements: &mut Vec<String>,
) -> Result<()> {
    let mut cursor = root_node.walk();
    for child in root_node.children(&mut cursor) {
        if child.kind().contains("instantiation") {
            if let Some(first_child) = child.child(0) {
                if let Ok(module) = first_child.utf8_text(contents.as_bytes()) {
                    let module_name_v = format!("{}.v", module);
                    let module_name_sv = format!("{}.sv", module);
                    if !visited_modules.contains(&module_name_v) && !visited_modules.contains(&module_name_sv) {
                        visited_modules.push(module_name_v.clone());
                        visited_modules.push(module_name_sv.clone());
                        append_module(
                            &PathBuf::from("./vpm_modules"),
                            &module_name_v,
                            top_module,
                            visited_modules,
                            sub,
                            filelist_path,
                            filepaths,
                            f_statements,
                            define_statements)?;
                        append_module(
                            &PathBuf::from("./vpm_modules"),
                            &module_name_sv,
                            top_module,
                            visited_modules,
                            sub,
                            filelist_path,
                            filepaths,
                            f_statements,
                            define_statements)?;
                    }
                }
            }
        }

        find_module_instantiations(
            child,
            top_module,
            contents,
            visited_modules,
            sub,
            filelist_path,
            filepaths,
            f_statements,
            define_statements)?;
    }
    
    Ok(())
}


================================================
FILE: src/cmd/include.rs
================================================
use std::collections::HashSet;
use std::env::current_dir;
use std::path::{Path, PathBuf};
use std::{fs, process::Command};
use anyhow::{Context, Result};
use once_cell::sync::Lazy;
use tree_sitter::{Node, Parser, Query, QueryCursor};
use crate::cmd::{Execute, Include};
use crate::toml::{add_dependency, add_top_module};
use walkdir::{DirEntry, WalkDir};
use fancy_regex::Regex;

use dialoguer::{theme::ColorfulTheme, MultiSelect};
use fuzzy_matcher::FuzzyMatcher;
use fuzzy_matcher::skim::SkimMatcherV2;
use std::io::{self, Write};
use indicatif::{ProgressBar, ProgressStyle};

impl Execute for Include {
    async fn execute(&self) -> Result<()> {
        println!("Including from: '{}'", self.url);
        let repo_name = name_from_url(&self.url);
        let tmp_path = PathBuf::from("/tmp").join(repo_name);
        let commit = if self.commit.is_none() {
            Some(get_head_commit_hash(&self.url)?)
        } else {
            self.commit.clone()
        };
        if self.repo {
            include_entire_repo(&self.url, &tmp_path, self.riscv, commit.as_deref())?
        } else {
            include_single_module(&self.url, self.riscv, commit.as_deref())?
        }
        Ok(())
    }
}

pub fn get_head_commit_hash(url: &str) -> Result<String> {
    let github_url = if url.starts_with("https://github.com/") {
        url.to_string()
    } else {
        format!("https://github.com/{}", url)
    };

    let (repo_url, _) = github_url.rsplit_once("/blob/").unwrap_or((&github_url, ""));

    let output = Command::new("git")
        .args(["ls-remote", repo_url, "HEAD"])
        .output()?;

    if output.status.success() {
        let stdout = String::from_utf8(output.stdout)?;
        let hash = stdout.split_whitespace().next().unwrap_or("").to_string();
        if !hash.is_empty() {
            Ok(hash[..7].to_string())  // Return only the first 7 characters (short hash)
        } else {
            Err(anyhow::anyhow!("Failed to get HEAD commit hash: Empty hash returned"))
        }
    } else {
        let stderr = String::from_utf8_lossy(&output.stderr);
        Err(anyhow::anyhow!("Failed to get HEAD commit hash: {}", stderr))
    }
}

fn include_entire_repo(url: &str, tmp_path: &PathBuf, riscv: bool, commit_hash: Option<&str>) -> Result<()> {
    let url = format!("https://github.com/{}", url);
    println!("Full GitHub URL: {}@{}", url, commit_hash.unwrap_or("HEAD"));
    include_repo_from_url(&url, "/tmp/", commit_hash)?;
    add_dependency(&url)?;

    let files = get_files(&tmp_path.to_str().unwrap_or_default());
    let items = get_relative_paths(&files, tmp_path);

    let selected_items = select_modules(&items)?;

    process_selected_modules(&url, tmp_path, &selected_items, riscv, commit_hash)?;

    fs::remove_dir_all(tmp_path)?;
    print_success_message(&url, &selected_items);
    Ok(())
}

fn include_single_module(url: &str, riscv: bool, commit_hash: Option<&str>) -> Result<()> {
    let repo_url = get_github_repo_url(url).unwrap();
    include_repo_from_url(&repo_url, "/tmp/", commit_hash)?;
    add_dependency(&repo_url)?;
    println!("Repo URL: {}@{}", repo_url, commit_hash.unwrap_or("HEAD"));
    let module_path = get_component_path_from_github_url(url).unwrap_or_default();
    println!("Including module: {}", module_path);
    include_module_from_url(&module_path, &repo_url, riscv, commit_hash)?;
    println!("Successfully installed module: {}", module_path);
    Ok(())
}

fn get_files(directory: &str) -> Vec<String> {
    WalkDir::new(directory)
        .into_iter()
        .filter_map(|entry| {
            entry.ok().and_then(|e| {
                if e.file_type().is_file() {
                    Some(e.path().to_string_lossy().into_owned())
                } else {
                    None
                }
            })
        })
        .collect()
}

fn get_relative_paths(files: &[String], tmp_path: &PathBuf) -> Vec<String> {
    files.iter()
        .map(|file| file.strip_prefix(&tmp_path.to_string_lossy().as_ref())
            .unwrap_or(file)
            .trim_start_matches('/')
            .to_string())
        .collect()
}

fn select_modules(items: &[String]) -> Result<HashSet<String>> {
    let matcher = SkimMatcherV2::default();
    let mut selected_items: HashSet<String> = HashSet::new();

    loop {
        print!("Enter module name (or press Enter to finish): ");
        io::stdout().flush()?;

        let mut query = String::new();
        io::stdin().read_line(&mut query)?;
        query = query.trim().to_string();

        if query.is_empty() {
            break;
        }

        let filtered_items: Vec<String> = items
            .iter()
            .filter(|&item| matcher.fuzzy_match(item, &query).is_some())
            .cloned()
            .collect();

        let selection = MultiSelect::with_theme(&ColorfulTheme::default())
            .with_prompt("Toggle items to include with the space bar. Hit enter to start a new search")
            .items(&filtered_items)
            .interact()?;

        for i in &selected_items {
            println!("- {}", i);
        }

        selected_items.extend(selection.iter().map(|&i| filtered_items[i].clone()));
    }

    print!("\x1B[2J\x1B[1;1H");
    Ok(selected_items)
}

fn process_selected_modules(url: &str, tmp_path: &PathBuf, selected_items: &HashSet<String>, riscv: bool, commit_hash: Option<&str>) -> Result<()> {
    for item in selected_items {
        let displayed_path = item.strip_prefix(tmp_path.to_string_lossy().as_ref()).unwrap_or(item).trim_start_matches('/');
        println!("Including module: {}", displayed_path);
        
        let full_path = tmp_path.join(displayed_path);
        let module_path = full_path.strip_prefix(tmp_path).unwrap_or(&full_path).to_str().unwrap().trim_start_matches('/');
        println!("Module path: {}", module_path);

        include_module_from_url(module_path, url, riscv, commit_hash)?;
    }

    if selected_items.is_empty() {
        println!("No modules selected. Including entire repository.");
        include_repo_from_url(url, "./", commit_hash)?;
    }

    Ok(())
}

fn print_success_message(url: &str, selected_items: &HashSet<String>) {
    if !selected_items.is_empty() {
        let installed_modules = selected_items.iter()
            .map(|item| item.to_string())
            .collect::<Vec<String>>()
            .join(", ");
        println!("Successfully installed module(s): {}", installed_modules);
    } else {
        println!("Successfully installed repository '{}'.", name_from_url(url));
    }
}

fn name_from_url(url: &str) -> &str {
    url.rsplit('/').find(|&s| !s.is_empty()).unwrap_or_default()
}

fn get_component_path_from_github_url(url: &str) -> Option<String> {
    let parts: Vec<&str> = url.split("/").collect();
    if parts.len() < 8 || !url.starts_with("https://github.com/") {
        return None;
    }

    Some(parts[7..].join("/"))
}

fn get_github_repo_url(url: &str) -> Option<String> {
    let parts: Vec<&str> = url.split('/').collect();
    if parts.len() < 5 || !url.starts_with("https://github.com/") {
        return None;
    }

    Some(format!("https://github.com/{}/{}", parts[3], parts[4]))
}

fn is_full_filepath(path: &str) -> bool {
    path.contains('/') || path.contains('\\')
}

fn filepath_to_dir_entry(filepath: PathBuf) -> Result<DirEntry> {
    WalkDir::new(filepath)
        .min_depth(0)
        .max_depth(0)
        .into_iter()
        .next()
        .ok_or_else(|| anyhow::anyhow!("Failed to create DirEntry"))?
        .context("Failed to create DirEntry")
}

fn generate_top_v_content(module_path: &str) -> Result<String> {
    println!("Generating top.v file for RISC-V in {}", module_path);
    let module_content = fs::read_to_string(module_path)?;

    let mut top_content = String::new();
    top_content.push_str("// Auto-generated top.v file for RISC-V\n\n");

    // Use regex to find module declaration
    let module_re = regex::Regex::new(r"module\s+(\w+)\s*(?:#\s*\(([\s\S]*?)\))?\s*\(([\s\S]*?)\);").unwrap();
    if let Some(captures) = module_re.captures(&module_content) {
        let module_name = captures.get(1).unwrap().as_str();
        println!("Module name: {}", module_name);

        // Extract parameters
        let params = captures.get(2).map_or(Vec::new(), |m| {
            m.as_str().lines()
                .map(|line| line.trim())
                .filter(|line| !line.is_empty())
                .collect()
        });

        // Extract ports
        let ports: Vec<&str> = captures.get(3).unwrap().as_str()
            .lines()
            .map(|line| line.trim())
            .filter(|line| !line.is_empty())
            .collect();

        // Generate top module ports
        top_content.push_str("module top (\n");
        for port in &ports {
            top_content.push_str(&format!("    {}\n", port));
        }
        top_content.push_str(");\n\n");

        // Instantiate the module
        top_content.push_str(&format!("{} #(\n", module_name));
        for param in params.iter() {
            if let Some((name, value)) = param.split_once('=') {
                let name = name.trim().trim_start_matches("parameter").trim();
                let name = name.split_whitespace().last().unwrap_or(name);
                let value = value.trim().trim_end_matches(',');
                top_content.push_str(&format!("    .{}({}),\n", name, value));
            }
        }
        top_content.push_str(") cpu (\n");

        // Connect ports
        let port_re = regex::Regex::new(r"(input|output|inout)\s+(?:wire|reg)?\s*(?:\[.*?\])?\s*(\w+)").unwrap();
        for (i, port) in ports.iter().enumerate() {
            if let Some(port_captures) = port_re.captures(port) {
                let port_name = port_captures.get(2).unwrap().as_str();
                top_content.push_str(&format!("    .{}({}){}\n", port_name, port_name, if i < ports.len() - 1 { "," } else { "" }));
            }
        }
        top_content.push_str(");\n\n");

        top_content.push_str("endmodule\n");
        return Ok(top_content);
    }

    Err(anyhow::anyhow!("No module declaration found in the file"))
}

fn generate_xdc_content(module_path: &str) -> Result<String> {
    println!("Generating constraints.xdc file for Xilinx Artix-7 board in {}", module_path);
    let module_content = fs::read_to_string(module_path)?;

    let mut xdc_content = String::new();
    xdc_content.push_str("## Auto-generated constraints.xdc file for Xilinx Artix-7 board\n\n");

    // Use regex to find all ports
    let port_re = regex::Regex::new(r"(?m)^\s*(input|output|inout)\s+(?:wire|reg)?\s*(?:\[.*?\])?\s*(\w+)").unwrap();
    let mut ports = Vec::new();

    for captures in port_re.captures_iter(&module_content) {
        let port_type = captures.get(1).unwrap().as_str();
        let port_name = captures.get(2).unwrap().as_str();
        ports.push((port_type, port_name));
    }

    // Define pin mappings (you may need to adjust these based on your specific board)
    let pin_mappings = [
        ("clk", "E3"),
        ("resetn", "C12"),
        ("trap", "D10"),
        ("mem_valid", "C11"),
        ("mem_instr", "C10"),
        ("mem_ready", "A10"),
        ("mem_addr[0]", "A8"),
        ("mem_wdata[0]", "C5"),
        ("mem_wstrb[0]", "C6"),
        ("mem_rdata[0]", "D5"),
    ];

    // Generate constraints for each port
    for (_port_type, port_name) in ports {
        if let Some((_, pin)) = pin_mappings.iter().find(|&&(p, _)| p == port_name) {
            let iostandard = if port_name == "clk" { "LVCMOS33" } else { "LVCMOS33" };
            xdc_content.push_str(&format!("set_property -dict {{ PACKAGE_PIN {} IOSTANDARD {} }} [get_ports {{ {} }}]\n", pin, iostandard, port_name));
        } else {
            println!("Warning: No pin mapping found for port: {}", port_name);
        }
    }

    // Add clock constraint
    if let Some((_, _clk_pin)) = pin_mappings.iter().find(|&&(p, _)| p == "clk") {
        xdc_content.push_str(&format!("\n## Clock signal\n"));
        xdc_content.push_str(&format!("create_clock -period 10.000 -name sys_clk_pin -waveform {{0.000 5.000}} -add [get_ports {{ clk }}]\n"));
    } else {
        println!("Warning: No clock signal found. XDC file may be incomplete.");
        xdc_content.push_str("\n## Warning: No clock signal found. Please add clock constraints manually.\n");
    }

    Ok(xdc_content)
}

pub fn include_module_from_url(module_path: &str, url: &str, riscv: bool, commit_hash: Option<&str>) -> Result<()> {
    let package_name = name_from_url(url);

    include_repo_from_url(url, "/tmp/", commit_hash)?;
    let destination = "./";
    process_module(package_name, module_path, destination.to_owned(), &mut HashSet::new(), url, true, commit_hash)?;

    let module_path = Path::new(&destination).join(Path::new(module_path).file_name().unwrap());
    anyhow::ensure!(module_path.exists(), "Module file not found in the destination folder");

    if riscv {
        let top_v_content = generate_top_v_content(&module_path.to_str().unwrap())?;
        fs::write(format!("{}/top.v", destination), top_v_content)?;
        println!("Created top.v file for RISC-V in {}", destination);
        // Generate .xdc file for Xilinx Artix-7 board
        let xdc_content = generate_xdc_content(&format!("{}/top.v", destination))?;
        fs::write(format!("{}/constraints.xdc", destination), xdc_content)?;
        println!("Created constraints.xdc file for Xilinx Artix-7 board in {}", destination);
    }
    add_top_module(url, current_dir()?.join(module_path.file_name().unwrap()).to_str().unwrap(), commit_hash.unwrap_or(""))?;
    
    Ok(())
}

pub fn process_module(package_name: &str, module: &str, destination: String, visited: &mut HashSet<String>, url: &str, is_top_module: bool, commit_hash: Option<&str>) -> Result<HashSet<String>> {
    // println!("Processing module: {}", module);
    let module_name = module.strip_suffix(".v").or_else(|| module.strip_suffix(".sv")).unwrap_or(module);
    let module_with_ext = if module.ends_with(".v") || module.ends_with(".sv") {
        module.to_string()
    } else {
        format!("{}.v", module_name)
    };
    if !visited.insert(module_with_ext.clone()) {
        return Ok(HashSet::new());
    }

    let tmp_path = PathBuf::from("/tmp").join(package_name);
    let file_path = tmp_path.join(&module_with_ext);

    let target_path = PathBuf::from(&destination);

    println!("Including submodule '{}'", module_with_ext);

    let mut processed_modules = HashSet::new();

    if is_full_filepath(&module_with_ext) {
        // println!("Full filepath detected for module '{}'", module_with_ext);
        let dir_entry = filepath_to_dir_entry(file_path)?;
        process_file(&dir_entry, &target_path.to_str().unwrap(), module, url, visited, is_top_module)?;
        processed_modules.insert(module_with_ext.clone());
    } else {
        // println!("Full filepath not detected for module '{}'", module_with_ext);
        process_non_full_filepath(module_name, &tmp_path, &target_path, url, visited, is_top_module, &mut processed_modules)?;
    }

    let submodules = download_and_process_submodules(package_name, module, &destination, url, visited, is_top_module, commit_hash)?;
    processed_modules.extend(submodules);

    Ok(processed_modules)
}

fn process_non_full_filepath(module_name: &str, tmp_path: &PathBuf, target_path: &PathBuf, url: &str, visited: &mut HashSet<String>, is_top_module: bool, processed_modules: &mut HashSet<String>) -> Result<()> {
    let matching_entries = find_matching_entries(module_name, tmp_path);
    println!("Found {} matching entries for module '{}'", matching_entries.len(), module_name);
    if matching_entries.is_empty() {
        println!("No matching files found for module '{}'. Skipping...", module_name);
    } else if matching_entries.len() == 1 {
        let dir_entry = filepath_to_dir_entry(matching_entries[0].clone())?;
        process_file(&dir_entry, target_path.to_str().unwrap(), module_name, url, visited, is_top_module)?;
        processed_modules.insert(format!("{}.v", module_name));
    } else {
        process_multiple_matches(matching_entries, target_path, module_name, url, visited, is_top_module, processed_modules)?;
    }

    Ok(())
}

fn find_matching_entries(module_name: &str, tmp_path: &PathBuf) -> Vec<PathBuf> {
    WalkDir::new(tmp_path)
        .into_iter()
        .filter_map(Result::ok)
        .filter(|entry| {
            entry.file_name().to_str() == Some(&format!("{}.sv", module_name)) || 
            entry.file_name().to_str() == Some(&format!("{}.v", module_name))
        })
        .map(|entry| entry.path().to_path_buf())
        .collect()
}

fn process_multiple_matches(matching_entries: Vec<PathBuf>, target_path: &PathBuf, module_name: &str, url: &str, visited: &mut HashSet<String>, is_top_module: bool, processed_modules: &mut HashSet<String>) -> Result<()> {
    println!("Multiple modules found for '{}'. Please choose:", module_name);
    for (i, entry) in matching_entries.iter().enumerate() {
        println!("{}: {}", i + 1, entry.display());
    }

    let mut choice = String::new();
    std::io::stdin().read_line(&mut choice)?;
    let index: usize = choice.trim().parse()?;

    if index > 0 && index <= matching_entries.len() {
        let dir_entry = filepath_to_dir_entry(matching_entries[index - 1].clone())?;
        process_file(&dir_entry, target_path.to_str().unwrap(), module_name, url, visited, is_top_module)?;
        processed_modules.insert(format!("{}.v", module_name));
    } else {
        anyhow::bail!("Invalid choice");
    }

    Ok(())
}

fn process_file(entry: &DirEntry, destination: &str, module_path: &str, url: &str, visited: &mut HashSet<String>, is_top_module: bool) -> Result<()> {
    let target_path = PathBuf::from(destination);
    let extension = entry.path().extension().and_then(|s| s.to_str()).unwrap_or("v");
    fs::copy(entry.path(), &target_path.join(entry.file_name()))?;

    let contents = fs::read_to_string(entry.path())?;
    let mut parser = Parser::new();
    parser.set_language(tree_sitter_verilog::language())?;
    let tree = parser.parse(&contents, None).context("Failed to parse file")?;

    let header_content = generate_headers(tree.root_node(), &contents)?;
    let module_name = Path::new(module_path)
        .file_stem()
        .and_then(|s| s.to_str())
        .unwrap_or(module_path);
    let module_name_with_ext = if !module_name.ends_with(".v") && !module_name.ends_with(".sv") {
        format!("{}.{}", module_name, extension)
    } else {
        module_name.to_string()
    };
    let header_filename = format!("{}.{}", module_name.strip_suffix(".v").unwrap_or(module_name), if extension == "sv" { "svh" } else { "vh" });
    let headers_dir = target_path.join("headers");
    fs::create_dir_all(&headers_dir)?;
    fs::write(headers_dir.join(&header_filename), header_content)?;
    println!("Generating header file: {}", target_path.join(&header_filename).to_str().unwrap());

    let full_module_path = target_path.join(&module_name_with_ext);
    update_lockfile(&full_module_path, url, &contents, visited, is_top_module)?;

    Ok(())
}

fn download_and_process_submodules(package_name: &str, module_path: &str, destination: &str, url: &str, visited: &mut HashSet<String>, _is_top_module: bool, commit_hash: Option<&str>) -> Result<HashSet<String>> {
    let module_name = Path::new(module_path)
        .file_stem()
        .and_then(|s| s.to_str())
        .unwrap_or(module_path);
    // println!("Processing submodule: {}", module_path);
    let module_name_with_ext = if module_path.ends_with(".sv") {
        format!("{}.sv", module_name)
    } else if module_path.ends_with(".v") {
        format!("{}.v", module_name)
    } else {
        module_path.to_string()
    };

    let full_module_path = PathBuf::from(destination).join(&module_name_with_ext);
    // println!("Full module path: {}", full_module_path.display());
    let contents = match fs::read_to_string(&full_module_path) {
        Ok(c) => c,
        Err(e) => {
            println!("Warning: Failed to read file {}: {}. Skipping this module.", full_module_path.display(), e);
            return Ok(HashSet::new());
        }
    };
    
    let mut parser = Parser::new();
    if let Err(e) = parser.set_language(tree_sitter_verilog::language()) {
        eprintln!("Warning: Failed to set parser language: {}. Skipping submodule processing.", e);
        return Ok(HashSet::new());
    }

    let submodules = match get_submodules(&contents) {
        Ok(s) => s,
        Err(e) => {
            eprintln!("Warning: Failed to get submodules from {}: {}. Continuing without submodules.", full_module_path.display(), e);
            HashSet::new()
        }
    };

    let mut all_submodules = HashSet::new();

    for submodule in submodules {
        let submodule_with_ext = if submodule.ends_with(".v") || submodule.ends_with(".sv") {
            submodule.to_string()
        } else {
            let parent_extension = Path::new(&module_name_with_ext)
                .extension()
                .and_then(|ext| ext.to_str())
                .unwrap_or("v");
            format!("{}.{}", &submodule, parent_extension)
        };
        if !visited.contains(&submodule_with_ext) {
            let submodule_destination = PathBuf::from(destination);
            if let Err(e) = fs::create_dir_all(&submodule_destination) {
                eprintln!("Warning: Failed to create directory {}: {}. Skipping this submodule.", submodule_destination.display(), e);
                continue;
            }
            
            match process_module(
                package_name,
                &submodule_with_ext,
                submodule_destination.to_str().unwrap().to_string(),
                visited,
                &url,
                false,
                commit_hash.clone()
            ) {
                Ok(processed_submodules) => {
                    all_submodules.insert(submodule_with_ext.clone());
                    all_submodules.extend(processed_submodules);
                },
                Err(e) => {
                    eprintln!("Warning: Failed to process submodule {}: {}. Skipping this submodule.", submodule_with_ext, e);
                    continue;
                }
            }

            let full_submodule_path = submodule_destination.join(&submodule_with_ext);
            if let Err(e) = update_lockfile(&full_submodule_path, &url, &contents, visited, false) {
                eprintln!("Warning: Failed to update lockfile for {}: {}. Continuing without updating lockfile.", full_submodule_path.display(), e);
            }
        }
    }

    Ok(all_submodules)
}

fn update_lockfile(full_path: &PathBuf, url: &str, contents: &str, visited: &HashSet<String>, is_top_module: bool) -> Result<()> {
    let mut lockfile = fs::read_to_string("vpm.lock").unwrap_or_default();
    let module_entry = if is_top_module {
        format!("[[package]]\nfull_path = \"{}\"\nsource = \"{}\"\nparents = []\n", full_path.display(), url)
    } else {
        format!("[[package]]\nfull_path = \"{}\"\nsource = \"{}\"\n", full_path.display(), url)
    };

    let mut parser = Parser::new();
    parser.set_language(tree_sitter_verilog::language())?;
    let submodules = get_submodules(contents)?;
    let submodules_vec: Vec<String> = submodules.into_iter().collect();

    if !lockfile.contains(&format!("full_path = \"{}\"", full_path.display())) {
        let formatted_submodules = submodules_vec.iter()
            .map(|s| format!("  \"{}\",", s))
            .collect::<Vec<_>>()
            .join("\n");
        lockfile.push_str(&format!("\n{}\nsubmodules = [\n{}\n]\n", module_entry, formatted_submodules));
    } else {
        update_submodules(&mut lockfile, &module_entry, &submodules_vec);
    }

    for submodule in &submodules_vec {
        if !visited.contains(submodule) {
            let submodule_path = full_path.parent().unwrap().join(submodule);
            if let Some(existing_entry) = lockfile.find(&format!("\n[[package]]\nfull_path = \"{}\"", submodule_path.display())) {
                let parent_start = lockfile[existing_entry..].find("parents = [").map(|i| existing_entry + i);
                if let Some(start) = parent_start {
                    let end = lockfile[start..].find(']').map(|i| start + i + 1).unwrap_or(lockfile.len());
                    let current_parents = lockfile[start..end].to_string();
                    let new_parents = if current_parents.contains(&full_path.display().to_string()) {
                        current_parents
                    } else {
                        format!("{}  \"{}\",\n]", &current_parents[..current_parents.len() - 1], full_path.display())
                    };
                    lockfile.replace_range(start..end, &new_parents);
                }
            } else {
                let submodule_entry = format!("\n[[package]]\nfull_path = \"{}\"\nsource = \"{}\"\nparents = [\n  \"{}\",\n]\nsubmodules = []\n", submodule_path.display(), url, full_path.display());
                lockfile.push_str(&submodule_entry);
            }
        }
    }

    fs::write("vpm.lock", lockfile)?;
    Ok(())
}

fn update_submodules(lockfile: &mut String, module_entry: &str, submodules: &[String]) {
    if let Some(start) = lockfile.find(module_entry).and_then(|pos| lockfile[pos..].find("submodules = [").map(|offset| pos + offset)) {
        let end = lockfile[start..].find(']').map(|pos| start + pos + 1).unwrap_or(lockfile.len());
        let new_modules = format!("submodules = [\n{}\n]", submodules.iter().map(|m| format!("  \"{}\",", m)).collect::<Vec<_>>().join("\n"));
        lockfile.replace_range(start..end, &new_modules);
    }
}

pub fn generate_headers(root_node: Node, contents: &str) -> Result<String> {
    static QUERY: Lazy<Query> = Lazy::new(|| {
        Query::new(
            tree_sitter_verilog::language(),
            "(module_declaration
                (module_header
                    (module_keyword)
                    (simple_identifier) @module_name)
                (module_nonansi_header
                    (parameter_port_list)? @params
                    (list_of_ports) @ports)
            )
            (module_declaration
                (module_header
                    (module_keyword)
                    (simple_identifier) @module_name)
                (module_ansi_header
                    (parameter_port_list)? @params
                    (list_of_port_declarations)? @ports)
            )",
        )
        .expect("Failed to create query")
    });

    let mut query_cursor = QueryCursor::new();
    let matches = query_cursor.matches(&QUERY, root_node, contents.as_bytes());

    let mut header_content = String::new();

    for match_ in matches {
        let mut module_name = "";
        let mut params = "";
        let mut ports = "";

        for capture in match_.captures {
            let capture_text = &contents[capture.node.byte_range()];
            match capture.index {
                0 => module_name = capture_text,
                1 => params = capture_text,
                2 => ports = capture_text,
                _ => {}
            }
        }
        
        header_content.push_str(&format!(
            "`ifndef {}_H\n`define {}_H\n\n",
            module_name.to_uppercase(),
            module_name.to_uppercase()
        ));

        if !params.is_empty() {
            header_content.push_str(&format!(
                "// Parameters\n{}\n\n",
                params.trim()
            ));
        }

        if !ports.is_empty() {
            header_content.push_str(&format!(
                "// Ports\n{}\n\n",
                ports.trim()
            ));
        }

        header_content.push_str(&format!(
            "// Module: {}\n// TODO: Add module description\n\n`endif // {}_H\n\n",
            module_name,
            module_name.to_uppercase()
        ));
    }

    Ok(header_content)
}


pub fn get_submodules(contents: &str) -> Result<HashSet<String>> {
    static REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(
        r"(?mi)^\s*(?!(always(_comb|_ff|_latch)?|assert|assign|assume|begin|case|cover|else|end(case|function|generate|module|primitive|table|task)?|enum|for|forever|function|generate|if|initial|input|int|localparam|logic|module|negedge|output|param(eter)?|posedge|primitive|real|reg|repeat|table|task|time|timescale|typedef|while|wire))(\w+)\s*(?:#\([\s.\w(\[\-:\]\),{'}`/+!~@#$%^&*=<>?]+\))?\s*[\w\[:\]]+\s*(?:\([\s.\w(\[\-:\]\),{'}`/+!~@#$%^&*=<>?|]+\));"
    ).unwrap());
    let submodules: HashSet<String> = REGEX
        .captures_iter(contents) // Iterate over captures
        .map(|caps| caps.unwrap().get(0).unwrap().as_str()) // Extract the matched string
        .map(|s| s.split_whitespace().next().unwrap().to_string()) // Split and get submodule name
        .collect(); // Collect into a HashSet
    // for submodule in &submodules {
        // println!("Found submodule: {}", submodule);
    // }
    Ok(submodules)
}

pub fn include_repo_from_url(url: &str, location: &str, commit_hash: Option<&str>) -> Result<()> {
    let repo_path = Path::new(location).join(name_from_url(url));
    let pb = ProgressBar::new_spinner();
    pb.set_style(ProgressStyle::default_spinner().template("{spinner} {msg}").unwrap());
    pb.set_message("Reading repository...");
    pb.enable_steady_tick(std::time::Duration::from_millis(100));
    clone_repo(url, &repo_path, commit_hash)?;
    pb.finish_with_message("Reading repository complete");
    Ok(())
}

pub fn clone_repo(url: &str, repo_path: &Path, commit_hash: Option<&str>) -> Result<()> {
    if repo_path.exists() {
        fs::remove_dir_all(repo_path)?;
    }
    Command::new("git")
        .args([ "clone", "--depth", "1", "--single-branch", "--jobs", "4",
            url, repo_path.to_str().unwrap_or_default(),
        ])
        .stdout(std::process::Stdio::null())
        .stderr(std::process::Stdio::null())
        .status()
        .with_context(|| format!("Failed to clone repository from URL: '{}'", url))?;
    if let Some(hash) = commit_hash {
        Command::new("git")
            .args([ "-C", repo_path.to_str().unwrap_or_default(), "checkout", hash ])
            .stdout(std::process::Stdio::null())
            .stderr(std::process::Stdio::null())
            .status()
            .with_context(|| format!("Failed to checkout commit hash: '{}'", hash))?;
    }
    Ok(())
}

================================================
FILE: src/cmd/install.rs
================================================
use anyhow::{Context, Result};
use std::process::Command;
use std::path::Path;
use std::env;
use std::fs::OpenOptions;
use std::io::Write;

use crate::cmd::{Execute, Install};

impl Execute for Install {
    async fn execute(&self) -> Result<()> {
        match self.tool_name.as_str() {
            "verilator" => {
                println!("Installing Verilator...");
                install_verilator()?;
            },
            "icarus-verilog" => {
                println!("Installing Icarus Verilog...");
                install_icarus_verilog()?;
            },
            "chipyard" => {
                println!("Installing Chipyard...");
                install_chipyard()?;
            },
            "openroad" => {
                println!("Installing OpenROAD...");
                install_openroad()?;
            },
            "edalize" => {
                println!("Installing Edalize...");
                install_edalize()?;
            },
            "yosys" => {
                println!("Installing Yosys...");
                install_yosys()?;
            },
            "riscv" => {
                println!("Installing RISC-V toolchain...");
                install_riscv()?;
            },
            "nextpnr" => {
                println!("Installing NextPNR...");
                install_nextpnr()?;
            },
            "project-xray" => {
                println!("Installing Project XRay...");
                install_xray()?;
            },
            _ => {
                println!("Tool '{}' is not recognized for installation.", self.tool_name);
            }
        }

        Ok(())
    }
}

fn has_sudo_access() -> bool {
    let output = Command::new("sudo")
        .arg("-n")
        .arg("true")
        .output()
        .expect("Failed to execute sudo command");
    output.status.success()
}

fn install_verilator() -> Result<()> {
    println!("Installing Verilator...");

    #[cfg(target_os = "macos")]
    {
        println!("Running on macOS...");
        // Install Verilator using Homebrew on macOS
        let status = Command::new("brew")
            .arg("install")
            .arg("verilator")
            .status()
            .context("Failed to install Verilator using Homebrew")?;

        if !status.success() {
            println!("Failed to install Verilator on macOS.");
            return Ok(());
        }
    }

    #[cfg(target_os = "linux")]
    {
        println!("Running on Linux...");
        
        if has_sudo_access() {
            // Install Verilator using package manager
            if !is_arch_distro() {
                // Install Verilator using apt-get on non-Arch Linux
                let status = Command::new("sudo")
                    .args(&["apt-get", "update"])
                    .status()
                    .context("Failed to update package lists")?;

                if !status.success() {
                    println!("Failed to update package lists on Linux.");
                    return Ok(());
                }

                let status = Command::new("sudo")
                    .args(&["apt-get", "install", "-y", "verilator"])
                    .status()
                    .context("Failed to install Verilator using apt-get")?;

                if !status.success() {
                    println!("Failed to install Verilator on Linux.");
                    return Ok(());
                }
            } else {
                // Install Verilator using pacman on Arch Linux
                let status = Command::new("sudo")
                    .args(&["pacman", "-Syu", "--noconfirm", "verilator"])
                    .status()
                    .context("Failed to install Verilator using pacman")?;

                if !status.success() {
                    println!("Failed to install Verilator on Arch Linux.");
                    return Ok(());
                }
            }
        } else {
            println!("No sudo access. Installing Verilator from source...");
            install_verilator_from_source()?;
        }
    }

    #[cfg(not(any(target_os = "macos", target_os = "linux")))]
    {
        println!("Unsupported operating system. Please install Verilator manually.");
        return Ok(());
    }

    println!("Verilator installed successfully.");
    Ok(())
}

fn install_verilator_from_source() -> Result<()> {
    // Create a directory for the installation
    let install_dir = Path::new(&std::env::var("HOME")?).join("verilator");
    std::fs::create_dir_all(&install_dir)?;

    // Clone the repository
    Command::new("git")
        .args(&["clone", "https://github.com/verilator/verilator"])
        .current_dir(&install_dir)
        .status()
        .context("Failed to clone Verilator repository")?;

    let source_dir = install_dir.join("verilator");

    // Configure with custom prefix
    Command::new("autoconf")
        .current_dir(&source_dir)
        .status()
        .context("Failed to run autoconf for Verilator")?;

    Command::new("./configure")
        .arg(format!("--prefix={}", install_dir.display()))
        .current_dir(&source_dir)
        .status()
        .context("Failed to configure Verilator")?;

    // Build
    Command::new("make")
        .current_dir(&source_dir)
        .status()
        .context("Failed to build Verilator")?;

    // Install
    Command::new("make")
        .arg("install")
        .current_dir(&source_dir)
        .status()
        .context("Failed to install Verilator")?;

    // Add installation directory to PATH
    println!("Verilator installed successfully in {}.", install_dir.display());
    println!("Please add the following line to your shell configuration file (e.g., .bashrc or .zshrc):");
    println!("export PATH=$PATH:{}/bin", install_dir.display());

    Ok(())
}

fn install_icarus_verilog() -> Result<()> {
    println!("Installing Icarus Verilog...");

    #[cfg(target_os = "macos")]
    {
        println!("Running on macOS...");
        // Install Icarus Verilog using Homebrew on macOS
        let status = Command::new("brew")
            .arg("install")
            .arg("icarus-verilog")
            .status()
            .context("Failed to install Icarus Verilog using Homebrew")?;

        if !status.success() {
            println!("Failed to install Icarus Verilog on macOS.");
            return Ok(());
        }
    }

    #[cfg(target_os = "linux")]
    {
        if !is_arch_distro() {
            println!("Running on Linux...");
            // Install Icarus Verilog using apt-get on Linux
            let status = Command::new("sudo")
                .arg("apt-get")
                .arg("update")
                .status()
                .context("Failed to update package lists")?;

            if !status.success() {
                println!("Failed to update package lists on Linux.");
                return Ok(());
            }

            let status = Command::new("sudo")
                .arg("apt-get")
                .arg("install")
                .arg("-y")
                .arg("iverilog")
                .status()
                .context("Failed to install Icarus Verilog using apt-get")?;

            if !status.success() {
                println!("Failed to install Icarus Verilog on Linux using Apt-Get.");
                let install_dir = Path::new(&std::env::var("HOME")?).join("icarus_verilog");
    std::fs::create_dir_all(&install_dir)?;


    Command::new("git")
        .args(&["clone", "https://github.com/steveicarus/iverilog.git"])
        .current_dir(&install_dir)
        .status()
        .context("Failed to clone Icarus Verilog repository")?;

    let source_dir = install_dir.join("iverilog");


    Command::new("sh")
        .arg("autoconf.sh")
        .current_dir(&source_dir)
        .status()
        .context("Failed to generate configure script")?;


    Command::new("./configure")
        .arg(format!("--prefix={}", install_dir.display()))
        .current_dir(&source_dir)
        .status()
        .context("Failed to configure Icarus Verilog")?;


    Command::new("make")
        .current_dir(&source_dir)
        .status()
        .context("Failed to build Icarus Verilog")?;


    Command::new("make")
        .arg("install")
        .current_dir(&source_dir)
        .status()
        .context("Failed to install Icarus Verilog")?;


    println!("Icarus Verilog installed successfully.");
    println!("Please add the following line to your shell configuration file (e.g., .bashrc or .zshrc):");
    println!("export PATH=$PATH:{}/bin", install_dir.display());

                return Ok(());
            } else {
                
        } else {
            println!("Running on Arch Linux...");
            // Install Icarus Verilog using pacman on Arch Linux
            let status = Command::new("sudo")
                .arg("pacman")
                .arg("-Syu")
                .arg("--noconfirm")
                .arg("iverilog")
                .status()
                .context("Failed to install Icarus Verilog using pacman")?;

            if !status.success() {
                println!("Failed to install Icarus Verilog on Arch Linux.");
                return Ok(());
            }
        }
    }

    #[cfg(not(any(target_os = "macos", target_os = "linux")))]
    {
        println!("Unsupported operating system. Please install Icarus Verilog manually.");
        return Ok(());
    }

    println!("Icarus Verilog installed successfully.");
    Ok(())
}

fn install_chipyard() -> Result<()> {
    println!("Installing Chipyard...");

    // Define the installation directory
    let install_dir = Path::new("/usr/local/bin");

    // Download Chipyard binary
    let status = Command::new("curl")
        .args(&["-L", "https://github.com/ucb-bar/chipyard/releases/latest/download/chipyard", "-o", install_dir.join("chipyard").to_str().unwrap()])
        .status()
        .context("Failed to download Chipyard binary")?;

    if !status.success() {
        println!("Failed to download Chipyard binary.");
        return Ok(());
    }

    // Make the binary executable
    let status = Command::new("chmod")
        .args(&["+x", install_dir.join("chipyard").to_str().unwrap()])
        .status()
        .context("Failed to make Chipyard binary executable")?;

    if !status.success() {
        println!("Failed to make Chipyard binary executable.");
        return Ok(());
    }

    println!("Chipyard installed successfully.");
    Ok(())
}

fn install_edalize() -> Result<()> {
    println!("Installing Edalize...");

    let (_python_cmd, pip_cmd) = if check_command("python3") {
        ("python3", "pip3")
    } else if check_command("python") {
        ("python", "pip")
    } else {
        println!("Neither Python 3 nor Python 2 is installed. Please install Python before proceeding.");
        return Ok(());
    };

    if !check_command(pip_cmd) {
        println!("{} is not installed. Please install pip before proceeding.", pip_cmd);
        return Ok(());
    }

    // Install Edalize
    let status = Command::new(pip_cmd)
        .arg("install")
        .arg("--user")
        .arg("edalize")
        .status()
        .context("Failed to install Edalize using pip")?;

    if !status.success() {
        println!("Failed to install Edalize.");
        return Ok(());
    }

    // Install FuseSoC
    let status = Command::new(pip_cmd)
        .arg("install")
        .arg("--user")
        .arg("fusesoc")
        .status()
        .context("Failed to install FuseSoC using pip")?;

    if !status.success() {
        println!("Failed to install FuseSoC.");
        return Ok(());
    }

    println!("Edalize installed successfully.");
    Ok(())
}

fn check_command(cmd: &str) -> bool {
    Command::new(cmd)
        .arg("--version")
        .output()
        .is_ok()
}

fn install_openroad() -> Result<()> {
    println!("Installing OpenROAD...");

    #[cfg(target_os = "linux")]
    {
        println!("Running on Linux...");
        // Install OpenROAD using apt on Linux
        let status = Command::new("sudo")
            .arg("apt")
            .arg("update")
            .status()
            .context("Failed to update package lists")?;

        if !status.success() {
            println!("Failed to update package lists on Linux.");
            return Ok(());
        }

        let status = Command::new("sudo")
            .arg("apt")
            .arg("install")
            .arg("-y")
            .arg("openroad")
            .status()
            .context("Failed to install OpenROAD using apt")?;

        if !status.success() {
            println!("Failed to install OpenROAD on Linux.");
            return Ok(());
        }
    }

    #[cfg(target_os = "macos")]
    {
        println!("Running on macOS...");
        // Install OpenROAD using Homebrew on macOS
        let status = Command::new("brew")
            .arg("install")
            .arg("openroad/openroad/openroad")
            .status()
            .context("Failed to install OpenROAD using Homebrew")?;

        if !status.success() {
            println!("Failed to install OpenROAD on macOS.");
            return Ok(());
        }
    }

    #[cfg(not(any(target_os = "macos", target_os = "linux")))]
    {
        println!("Unsupported operating system. Please install OpenROAD manually.");
        return Ok(());
    }

    println!("OpenROAD installed successfully.");
    Ok(())
}

fn install_yosys() -> Result<()> {
    println!("Installing Yosys and ABC...");

    #[cfg(target_os = "macos")]
    {
        println!("Running on macOS...");
        // Install Yosys using Homebrew on macOS
        let status = Command::new("brew")
            .arg("install")
            .arg("yosys")
            .status()
            .context("Failed to install Yosys using Homebrew")?;

        if !status.success() {
            println!("Failed to install Yosys on macOS.");
            return Ok(());
        }

        // Install ABC by git cloning and making
        if !Path::new("/usr/local/bin/abc").exists() {
            println!("Installing ABC...");
            let status = Command::new("git")
                .args(&["clone", "https://github.com/berkeley-abc/abc.git"])
                .status()
                .context("Failed to clone ABC repository")?;

            if !status.success() {
                println!("Failed to clone ABC repository.");
                return Ok(());
            }

            let status = Command::new("make")
                .current_dir("abc")
                .status()
                .context("Failed to make ABC")?;

            if !status.success() {
                println!("Failed to make ABC.");
                return Ok(());
            }

            let status = Command::new("sudo")
                .args(&["mv", "abc/abc", "/usr/local/bin/"])
                .status()
                .context("Failed to move ABC to /usr/local/bin/")?;

            if !status.success() {
                println!("Failed to move ABC to /usr/local/bin/.");
                return Ok(());
            }

            println!("ABC installed successfully.");
        } else {
            println!("ABC is already installed.");
        }
    }
    println!("Yosys and ABC installed successfully.");
    Ok(())
}

fn install_riscv() -> Result<()> {
    println!("Installing RISC-V toolchain...");
    Command::new("git")
        .args(&["clone", "--recursive", "https://github.com/riscv/riscv-gnu-toolchain.git"])
        .status()?;

    // Change to the cloned directory
    env::set_current_dir("riscv-gnu-toolchain")?;

    // Step 2: Install prerequisites (for Ubuntu/Debian)
    Command::new("sudo")
        .args(&["apt-get", "install", "autoconf", "automake", "autotools-dev", "curl", "python3", "libmpc-dev", "libmpfr-dev", "libgmp-dev", "gawk", "build-essential", "bison", "flex", "texinfo", "gperf", "libtool", "patchutils", "bc", "zlib1g-dev", "libexpat-dev"])
        .status()?;

    // Step 3: Create install directory
    Command::new("sudo")
        .args(&["mkdir", "-p", "/opt/riscv"])
        .status()?;

    // Step 4: Configure and build the toolchain
    Command::new("./configure")
        .arg("--prefix=/opt/riscv")
        .status()?;

    Command::new("sudo")
        .arg("make")
        .status()?;

    // Step 5: Add the toolchain to PATH
    let home = env::var("HOME")?;
    let bashrc_path = Path::new(&home).join(".bashrc");
    let mut bashrc = OpenOptions::new()
        .append(true)
        .open(bashrc_path)?;

    writeln!(bashrc, "\nexport PATH=$PATH:/opt/riscv/bin")?;

    // Step 6: Verify installation
    Command::new("/opt/riscv/bin/riscv64-unknown-elf-gcc")
        .arg("--version")
        .status()?;

    println!("RISC-V GNU toolchain installed successfully!");
    println!("Please restart your terminal or run 'source ~/.bashrc' to update your PATH.");
    Ok(())
}

fn install_nextpnr() -> Result<()> {
    println!("Installing NextPNR...");

    // Install NextPNR using Homebrew on macOS
    let status = Command::new("brew")
        .arg("install")
        .arg("nextpnr")
        .status()
        .context("Failed to install NextPNR using Homebrew")?;

    if !status.success() {
        println!("Failed to install NextPNR on macOS.");
        return Ok(());
    }

    println!("NextPNR installed successfully.");
    Ok(())
}

fn install_xray() -> Result<()> {
    println!("Installing Project XRay...");

    // Install Project XRay using Homebrew on macOS
    let status = Command::new("brew")
        .arg("install")
        .arg("xray")
        .status()
        .context("Failed to install Project XRay using Homebrew")?;

    if !status.success() {
        println!("Failed to install Project XRay on macOS.");
        return Ok(());
    }

    println!("Project XRay installed successfully.");
    Ok(())
}

fn is_arch_distro() -> bool {
    Command::new("pacman")
        .arg("--version")
        .output()
        .is_ok()
}


================================================
FILE: src/cmd/list.rs
================================================
use anyhow::{Result, Context, anyhow};
use std::collections::HashSet;
use std::process::Command;
use crate::cmd::{Execute, List};
use tempfile::tempdir;

const STD_LIB_URL: &str = "https://github.com/getinstachip/openchips";

impl Execute for List {
    async fn execute(&self) -> Result<()> {
        match list_verilog_files() {
            Ok(verilog_files) => {
                println!("Available Verilog modules:");
                for file in verilog_files {
                    println!("  {}", file);
                }
                Ok(())
            }
            Err(e) => {
                eprintln!("Error: Failed to list Verilog files. {}", e);
                eprintln!("Debug steps:");
                eprintln!("1. Check your internet connection");
                eprintln!("2. Ensure git is installed and accessible from the command line");
                eprintln!("3. Verify you have read permissions for the temporary directory");
                Err(e)
            }
        }
    }
}

fn list_verilog_files() -> Result<Vec<String>> {
    let temp_dir = tempdir().context("Failed to create temporary directory. Ensure you have write permissions in the system temp directory.")?;
    let repo_path = temp_dir.path();

    // Clone the repository
    let output = Command::new("git")
        .args([
            "clone",
            "--depth",
            "1",
            "--single-branch",
            "--jobs",
            "4",
            STD_LIB_URL,
            repo_path.to_str().unwrap_or_default(),
        ])
        .output()
        .context("Failed to execute git command. Ensure git is installed and accessible from the command line.")?;

    if !output.status.success() {
        return Err(anyhow!(
            "Git clone failed. Error: {}. This could be due to network issues, incorrect repository URL, or git configuration problems.",
            String::from_utf8_lossy(&output.stderr)
        ));
    }

    let mut verilog_files = HashSet::new();

    for entry in walkdir::WalkDir::new(repo_path)
        .into_iter()
        .filter_map(|e| e.ok())
    {
        if let Some(extension) = entry.path().extension() {
            match extension.to_str() {
                Some("v") | Some("sv") => {
                    if let Some(file_name) = entry.path().file_stem() {
                        verilog_files.insert(file_name.to_string_lossy().into_owned());
                    }
                }
                _ => {}
            }
        }
    }

    if verilog_files.is_empty() {
        Err(anyhow!("No Verilog files found in the repository. This could indicate an issue with the repository structure or content."))
    } else {
        Ok(verilog_files.into_iter().collect())
    }
}


================================================
FILE: src/cmd/load.rs
================================================
use anyhow::Result;
use std::path::Path;
use std::process::Command;

use crate::cmd::{Execute, Load};

impl Execute for Load {
    async fn execute(&self) -> Result<()> {
        let top_module_path = Path::new(&self.top_module_path);
        let constraints_path = Path::new(&self.constraints_path);
        if self.riscv {
            load_xilinx(top_module_path, constraints_path)?;
        } else {
            unimplemented!("Non RISC-V loading not yet implemented");
        }
        Ok(())
    }
}

fn load_xilinx(edif_path: &Path, constraints_path: &Path) -> Result<()> {
    let edif_path_str = edif_path.to_str().unwrap();
    let constraints_path_str = constraints_path.to_str().unwrap();
    
    Command::new("yosys")
        .args(&["-p", &format!("read_edif {}; write_json design.json", edif_path_str)])
        .status()?;

    Command::new("nextpnr-xilinx")
        .args(&[
            "--chipdb", "vpm_modules/chipdb-xc7a35t.bin",
            "--xdc", constraints_path_str,
            "--json", "design.json",
            "--write", "output.fasm",
            "--device", "xc7a35tcsg324-1"
        ])
        .status()?;

    let fasm_output = Command::new("fasm2frames")
        .args(&["--part", "xc7a35tcsg324-1", "output.fasm"])
        .output()?;
    std::fs::write("output.frames", fasm_output.stdout)?;

    Command::new("xc7frames2bit")
        .args(&[
            "--part_file", "vpm_modules/xc7a35tcsg324-1.yaml",
            "--part_name", "xc7a35tcsg324-1",
            "--frm_file", "output.frames",
            "--output_file", "output.bit"
        ])
        .status()?;

    println!("Bitstream generated successfully: output.bit");
    Ok(())
}

================================================
FILE: src/cmd/mod.rs
================================================
mod cmd;
mod upgrade;
mod include;
mod update;
mod remove;
mod dotf;
mod list;
mod install;
mod sim;
mod docs;
mod synth;
mod run;
mod load;
mod config;

use anyhow::Result;

pub use crate::cmd::cmd::*;

use crate::config_man::send_event;

pub trait Execute {
    async fn execute(&self) -> Result<()>;
}


impl Execute for Cmd {
    async fn execute(&self) -> Result<()> {
        match self {
            Cmd::Upgrade(cmd) => {
                cmd.execute().await?;
                send_event("upgrade".to_string()).await?;
                Ok(())
            },
            Cmd::Include(cmd) => {
                cmd.execute().await?;
                send_event("include".to_string()).await?;
                Ok(())
            },
            Cmd::Update(cmd) => {
                cmd.execute().await?;
                send_event("update".to_string()).await?;
                Ok(())
            },
            Cmd::Remove(cmd) => {
                cmd.execute().await?;
                send_event("remove".to_string()).await?;
                Ok(())
            },
            Cmd::Dotf(cmd) => {
                cmd.execute().await?;
                send_event("dotf".to_string()).await?;
                Ok(())
            },
            Cmd::Install(cmd) => {
                cmd.execute().await?;
                send_event("install".to_string()).await?;
                Ok(())
            },
            Cmd::List(cmd) => {
                cmd.execute().await?;
                send_event("list".to_string()).await?;
                Ok(())
            },
            Cmd::Sim(cmd) => {
                cmd.execute().await?;
                send_event("sim".to_string()).await?;
                Ok(())
            },
            Cmd::Docs(cmd) => {
                cmd.execute().await?;
                send_event("docs".to_string()).await?;
                Ok(())
            },
            Cmd::Synth(cmd) => {
                cmd.execute().await?;
                send_event("synth".to_string()).await?;
                Ok(())
            },
            Cmd::Load(cmd) => {
                cmd.execute().await?;
                send_event("load".to_string()).await?;
                Ok(())
            },
            Cmd::Run(cmd) => {
                cmd.execute().await?;
                send_event("run".to_string()).await?;
                Ok(())
            },
            Cmd::Config(cmd) => {
                cmd.execute().await?;
                send_event("config".to_string()).await?;
                Ok(())
            },
        }
    }
}


================================================
FILE: src/cmd/remove.rs
================================================
use std::fs;
use std::path::PathBuf;
use std::io::{self, Write};

use anyhow::{Result, anyhow};

use crate::cmd::{Execute, Remove};
use crate::toml::{remove_top_module, get_repo_links};

impl Execute for Remove {
    async fn execute(&self) -> Result<()> {
        remove_module(&self.package_path)?;
        Ok(())
    }
}

fn remove_module(module_path: &str) -> Result<()> {
    let module_path = PathBuf::from(module_path);
    if !module_path.exists() {
        return Err(anyhow!("Module not found: {}", module_path.display()));
    }

    let module_name = module_path.file_name().unwrap().to_str().unwrap();
    
    // Ask for y/n confirmation
    print!("Are you sure you want to remove the module {}? (y/n): ", module_name);
    io::stdout().flush()?;
    let mut confirmation = String::new();
    io::stdin().read_line(&mut confirmation)?;
    if confirmation.trim().to_lowercase() != "y" {
        return Ok(());
    }

    let repo_links = get_repo_links(module_name);

    let repo_link = match repo_links.len() {
        0 => return Err(anyhow!("No repository links found for module: {}", module_name)),
        1 => repo_links.into_iter().next().unwrap(),
        _ => {
            println!("Multiple repository links found for module: {}. Please choose the correct repository link.", module_name);
            for (i, link) in repo_links.iter().enumerate() {
                println!("{}. {}", i + 1, link);
            }
            
            let mut choice = String::new();
            print!("Enter your choice (1-{}): ", repo_links.len());
            io::stdout().flush()?;
            io::stdin().read_line(&mut choice)?;
            let index: usize = choice.trim().parse().map_err(|_| anyhow!("Invalid choice"))?;
            
            if index < 1 || index > repo_links.len() {
                return Err(anyhow!("Invalid choice"));
            }
            repo_links.iter().nth(index - 1)
                .ok_or_else(|| anyhow!("Invalid choice"))?
                .to_string()
        }
    };

    // Ask to enter the name of the module to confirm
    print!("To confirm removal, please re-type \"{}\" (without the quotes): ", module_name);
    io::stdout().flush()?;
    let mut confirmation_name = String::new();
    io::stdin().read_line(&mut confirmation_name)?;
    if confirmation_name.trim() != module_name {
        return Err(anyhow!("Module name does not match. Removal cancelled."));
    }

    fs::remove_file(&module_path)?;
    // Remove the corresponding header file if it exists
    let header_path = module_path.with_extension("vh");
    if header_path.exists() {
        fs::remove_file(&header_path)?;
        println!("Removed header file: {}", header_path.display());
    }
    remove_top_module(&repo_link, module_name)?;    
    println!("Removed module: {}", module_path.display());

    Ok(())
}


================================================
FILE: src/cmd/run.rs
================================================
use anyhow::Result;

use crate::cmd::{Execute, Run};

impl Execute for Run {
    async fn execute(&self) -> Result<()> {
        Ok(())
    }
}

================================================
FILE: src/cmd/sim.rs
================================================
use anyhow::{Context, Result};
use std::process::Command;
use std::env;
use std::path::{Path, PathBuf};
use std::fs;
use fastrand;
use crate::cmd::{Execute, Sim};
use std::fs::File;
use std::io::{BufRead, BufReader};
use fancy_regex::Regex;
use walkdir::WalkDir;

impl Execute for Sim {
    async fn execute(&self) -> Result<()> {
        let output_path = if let Some(folder) = &self.folder {
            compile_verilog_from_folder(folder)?
        } else {
            let mut verilog_files = self.verilog_files.clone();
            if !testbench_exists(&verilog_files) {
                generate_and_add_testbench(&mut verilog_files)?;
            }
            compile_verilog(&verilog_files)?
        };

        if self.waveform {
            run_simulation_with_waveform(&output_path)?;
        } else {
            run_simulation(&output_path)?;
        }

        Ok(())
    }
}

fn testbench_exists(verilog_files: &[String]) -> bool {
    verilog_files.iter().any(|file| file.to_lowercase().contains("_tb.v"))
}

fn generate_and_add_testbench(verilog_files: &mut Vec<String>) -> Result<()> {
    if let Some(first_file) = verilog_files.first() {
        let is_systemverilog = first_file.ends_with(".sv");
        let testbench_content = generate_testbench(first_file, is_systemverilog)
            .context("Failed to generate testbench. Please check if the Verilog file is valid.")?;
        let testbench_path = format!("{}_tb.{}", first_file.trim_end_matches(if is_systemverilog { ".sv" } else { ".v" }), if is_systemverilog { "sv" } else { "v" });
        fs::write(&testbench_path, testbench_content)
            .context("Failed to write testbench file. Please check if you have write permissions.")?;
        // Remove comments from the original Verilog file
        remove_comments_from_file(first_file)?;

        verilog_files.push(testbench_path.clone());
        println!("Generated testbench: {}", testbench_path);
        Ok(())
    } else {
        Err(anyhow::anyhow!("No Verilog files provided. Please specify at least one Verilog file."))
    }
}

pub fn compile_verilog_from_folder(folder: &str) -> Result<PathBuf> {
    println!("Compiling Verilog files from folder: {}", folder);
    let verilog_files = collect_verilog_files(folder)?;

    if verilog_files.is_empty() {
        return Err(anyhow::anyhow!("No Verilog files found in the specified folder."));
    }

    let output_dir = Path::new(folder);
    let random_output_name = generate_random_output_name();
    let output_path = output_dir.join(&random_output_name);

    let command_status = run_iverilog_command(output_path.to_str().unwrap(), &verilog_files)?;

    if !command_status.success() {
        return Err(anyhow::anyhow!("Compilation failed. Please check the error messages above."));
    }

    println!("Compilation successful. Output file: {:?}", output_path);
    Ok(output_path)
}

fn collect_verilog_files(folder: &str) -> Result<Vec<String>> {
    let mut verilog_files = Vec::new();

    for entry in WalkDir::new(folder).into_iter().filter_map(|e| e.ok()) {
        let path = entry.path();
        if path.is_file() && (path.extension() == Some("v".as_ref()) || path.extension() == Some("sv".as_ref())) {
            verilog_files.push(path.to_string_lossy().into_owned());
        }
    }

    Ok(verilog_files)
}

fn run_simulation_with_waveform(output_path: &Path) -> Result<()> {
    println!("Running simulation with waveform...");
    println!("Output path: {:?}", output_path);
    let testbench_dir = output_path.parent().unwrap();
    let vcd_path = testbench_dir.join("waveform.vcd");
    let current_dir = std::env::current_dir()
        .context("Failed to get current directory. Please check your file system permissions.")?;
    std::env::set_current_dir(testbench_dir)
        .context("Failed to change directory to testbench location. Please ensure the directory exists and you have necessary permissions.")?;
    
    let mut cmd = Command::new("vvp");
    cmd.arg(output_path.file_name().unwrap());
    cmd.arg(format!("-vcd={}", vcd_path.file_name().unwrap().to_str().unwrap()));
    
    let output = cmd.output()
        .context("Failed to run simulation with VCD output. Debug steps:\n1. Ensure 'vvp' is installed: Run 'vvp --version' in terminal.\n2. Check if 'vvp' is in your PATH: Run 'which vvp' (Unix) or 'where vvp' (Windows).\n3. If not found, install Icarus Verilog or add its bin directory to your PATH.")?;
    
    // Call gtkwave on the generated waveform file
    println!("Opening waveform in GTKWave...");
    let _gtkwave_status = Command::new("gtkwave")
        .arg("waveform.vcd")
        .spawn()
        .context("Failed to open GTKWave. Debug steps:\n1. Ensure GTKWave is installed: Run 'gtkwave --version' in terminal.\n2. Check if 'gtkwave' is in your PATH: Run 'which gtkwave' (Unix) or 'where gtkwave' (Windows).\n3. If not found, install GTKWave or add its installation directory to your PATH.")?;

    // We don't wait for GTKWave to exit, as it's a GUI application
    println!("GTKWave opened successfully. You can now view the waveform.");
    
    std::env::set_current_dir(current_dir)
        .context("Failed to change back to the original directory. This is unexpected, please check your file system.")?;
    
    if !output.status.success() {
        let error_message = String::from_utf8_lossy(&output.stderr);
        return Err(anyhow::anyhow!("Simulation failed. Error details:\n{}\n\nDebugging steps:\n1. Check your Verilog code for syntax errors.\n2. Ensure all module dependencies are correctly included.\n3. Verify testbench inputs and timing.\n4. Run the simulation without waveform generation to isolate the issue.", error_message));
    }
    
    println!("Generated waveform file: {}", vcd_path.display());
    println!("If GTKWave didn't open automatically, you can manually open the waveform file using GTKWave.");
    Ok(())
}

pub fn generate_testbench(module_path: &str, is_systemverilog: bool) -> Result<String> {
    println!("Generating testbench for module: {}", module_path);

    let (module_name, ports, parameters) = extract_module_info(module_path)?;

    println!("Module name: {}", module_name);
    println!("Ports: {:?}", ports);
    println!("Parameters: {:?}", parameters);

    let mut testbench = String::new();
    testbench.push_str(&generate_testbench_header(&module_name));
    testbench.push_str(&declare_parameters(&parameters));
    testbench.push_str(&declare_wires_for_ports(&ports, is_systemverilog));
    testbench.push_str(&instantiate_module(&module_name, &ports, &parameters));
    testbench.push_str(&generate_clock(&ports));
    testbench.push_str(&generate_initial_block(&ports, is_systemverilog));
    testbench.push_str(&generate_check_outputs_task(&ports, is_systemverilog));
    testbench.push_str("endmodule\n");

    Ok(testbench)
}

fn extract_module_info(module_path: &str) -> Result<(String, Vec<(String, Option<String>, String)>, Vec<(String, String)>)> {
    let file = File::open(module_path)
        .context(format!("Failed to open module file: {}. Please check if the file exists and you have read permissions.", module_path))?;
    let reader = BufReader::new(file);

    let mut module_name = String::new();
    let mut ports = Vec::new();
    let mut parameters = Vec::new();
    let mut in_module = false;
    let module_regex = Regex::new(r"module\s+(\w+)\s*(?:\(|#)").unwrap();
    let port_regex = Regex::new(r"(input|output|inout)\s+(?:reg|wire|logic)?\s*(\[.*?\])?\s*(\w+)").unwrap();
    let parameter_regex = Regex::new(r"parameter\s+(\w+)\s*=\s*([^,\)]+)").unwrap();
    let inline_parameter_regex = Regex::new(r"(\w+)\s*=\s*([^,\)]+)").unwrap();

    for line in reader.lines() {
        let line = line?;
        if !in_module {
            if let Ok(Some(captures)) = module_regex.captures(&line) {
                module_name = captures[1].to_string();
                in_module = true;
            }
        } else {
            for capture_result in port_regex.captures_iter(&line) {
                if let Ok(capture) = capture_result {
                    let direction = capture[1].to_string();
                    let bus_width = capture.get(2).map(|m| m.as_str().to_string());
                    let name = capture[3].to_string();
                    ports.push((direction, bus_width, name));
                }
            }
            for capture_result in parameter_regex.captures_iter(&line) {
                if let Ok(capture) = capture_result {
                    let name = capture[1].to_string();
                    let value = capture[2].to_string();
                    if let Some(existing) = parameters.iter_mut().find(|(n, _)| n == &name) {
                        existing.1 = value;
                    } else {
                        parameters.push((name, value));
                    }
                }
            }
            for capture_result in inline_parameter_regex.captures_iter(&line) {
                if let Ok(capture) = capture_result {
                    let name = capture[1].to_string();
                    let value = capture[2].to_string();
                    if !parameters.iter().any(|(n, _)| n == &name) {
                        parameters.push((name, value));
                    }
                }
            }
            if line.contains(");") {
                break;
            }
        }
    }

    if module_name.is_empty() {
        return Err(anyhow::anyhow!("Could not find module declaration in {}. Please ensure the file contains a valid Verilog or SystemVerilog module.", module_path));
    }

    Ok((module_name, ports, parameters))
}

fn generate_testbench_header(module_name: &str) -> String {
    format!("`timescale 1ns / 1ps\n\nmodule {}_tb;\n\n", module_name)
}

fn declare_parameters(parameters: &[(String, String)]) -> String {
    let mut declarations = String::new();
    for (name, value) in parameters {
        let mut line = format!("    parameter {} = {}", name, value);
        if !line.ends_with(')') && line.contains('(') {
            line.push(')');
        }
        declarations.push_str(&line);
        declarations.push_str(";\n");
    }
    declarations.push_str("\n");
    declarations
}

fn declare_wires_for_ports(ports: &[(String, Option<String>, String)], is_systemverilog: bool) -> String {
    let mut declarations = String::new();
    for (direction, bus_width, name) in ports {
        let wire_type = if is_systemverilog { "logic" } else { "reg" };
        let declaration = match bus_width {
            Some(width) => format!("    {} {} {};\n", if direction == "input" { wire_type } else { "wire" }, width, name),
            None => format!("    {} {};\n", if direction == "input" { wire_type } else { "wire" }, name),
        };
        declarations.push_str(&declaration);
    }
    declarations.push_str("\n");
    declarations
}

fn instantiate_module(module_name: &str, ports: &[(String, Option<String>, String)], parameters: &[(String, String)]) -> String {
    let mut instantiation = String::new();
    if !parameters.is_empty() {
        instantiation.push_str(&format!("    {} #(\n", module_name));
        for (i, (name, _)) in parameters.iter().enumerate() {
            instantiation.push_str(&format!("        .{}({}){}\n", name, name, if i < parameters.len() - 1 { "," } else { "" }));
        }
        instantiation.push_str("    ) uut (\n");
    } else {
        instantiation.push_str(&format!("    {} uut (\n", module_name));
    }
    for (i, (_, _, name)) in ports.iter().enumerate() {
        instantiation.push_str(&format!("        .{}({}){}\n", name, name, if i < ports.len() - 1 { "," } else { "" }));
    }
    instantiation.push_str("    );\n\n");
    instantiation
}

fn generate_clock(ports: &[(String, Option<String>, String)]) -> String {
    if let Some((_, _, clock_name)) = ports.iter().find(|(_, _, name)| name.to_lowercase().contains("clk")) {
        format!("    localparam CLOCK_PERIOD = 10;\n    initial begin\n        {} = 0;\n        forever #(CLOCK_PERIOD/2) {} = ~{};\n    end\n\n", clock_name, clock_name, clock_name)
    } else {
        String::new()
    }
}

fn generate_initial_block(ports: &[(String, Option<String>, String)], is_systemverilog: bool) -> String {
    let mut initial_block = String::from("    initial begin\n        $dumpfile(\"waveform.vcd\");\n        $dumpvars(0, uut);\n\n");
    for (direction, bus_width, name) in ports {
        if direction == "input" && !name.to_lowercase().contains("clk") {
            match bus_width {
                Some(width) => {
                    let width_value = width.trim_matches(|c| c == '[' || c == ']').split(':').next().unwrap_or("0");
                    if is_systemverilog {
                        initial_block.push_str(&format!("        {} = '{{{} {{1'b0}}}};\n", name, width_value));
                    } else {
                        initial_block.push_str(&format!("        {} = {{'b0}};\n", name));
                    }
                },
                None => initial_block.push_str(&format!("        {} = 1'b0;\n", name)),
            }
        }
    }
    initial_block.push_str("\n        #100;\n\n        #1000;\n        $finish;\n    end\n\n");
    initial_block
}

fn generate_check_outputs_task(ports: &[(String, Option<String>, String)], is_systemverilog: bool) -> String {
    let mut task = String::from("    task check_outputs;\n");
    if is_systemverilog {
        task.push_str("        input string test_case;\n");
    } else {
        task.push_str("        input [8*32-1:0] test_case;\n");
    }
    task.push_str("        begin\n            $display(\"Checking outputs for %s\", test_case);\n");
    for (direction, bus_width, name) in ports {
        if direction == "output" {
            let format_specifier = match bus_width {
                Some(_) => "%h",
                None => "%b",
            };
            task.push_str(&format!("            $display(\"  {} = {}\", {});\n", name, format_specifier, name));
        }
    }
    task.push_str("        end\n    endtask\n\n");
    task
}

fn remove_comments_from_file(file_path: &str) -> Result<()> {
    let file = File::open(file_path)
        .context(format!("Failed to open file: {}", file_path))?;
    let reader = BufReader::new(file);
    let mut content = String::new();

    for line in reader.lines() {
        let line = line?;
        let parts: Vec<&str> = line.split("//").collect();
        if !parts.is_empty() {
            let code_part = parts[0].trim_end();
            if !code_part.is_empty() {
                content.push_str(code_part);
                content.push('\n');
            }
        }
    }

    fs::write(file_path, content)
        .context(format!("Failed to write updated content to file: {}", file_path))?;

    Ok(())
}

pub fn compile_verilog(verilog_files: &Vec<String>) -> Result<PathBuf> {
    println!("Compiling Verilog files...");

    let first_file = &verilog_files[0];
    let output_dir = Path::new(first_file).parent().unwrap();
    let random_output_name = generate_random_output_name();
    let output_path = output_dir.join(&random_output_name);
    let command_status = run_iverilog_command(output_path.to_str().unwrap(), verilog_files)?;

    if !command_status.success() {
        return Err(anyhow::anyhow!("Failed to compile Verilog files. Please check your Verilog code for syntax errors."));
    }

    if !output_path.exists() {
        return Err(anyhow::anyhow!("Output binary not found: {:?}. Compilation may have failed silently.", output_path));
    }
    println!("Compiled output: {:?}", output_path);
    Ok(output_path)
}

fn generate_random_output_name() -> String {
    std::iter::repeat_with(fastrand::alphanumeric)
        .take(10)
        .collect()
}

fn run_iverilog_command(output_name: &str, verilog_files: &[String]) -> Result<std::process::ExitStatus> {
    let mut command = Command::new("iverilog");
    command.arg("-o").arg(output_name);
    for file in verilog_files {
        command.arg(file);
    }
    command.status()
        .context("Failed to execute Icarus Verilog compilation. Please ensure Icarus Verilog is installed and accessible.")
}

pub fn run_simulation(output_path: &PathBuf) -> Result<()> {
    println!("Running simulation...");

    let current_dir = env::current_dir()
        .context("Failed to get current directory. Please check your file system permissions.")?;
    
    let binary_path: PathBuf = current_dir.join(output_path);

    let status = Command::new(&binary_path)
        .status()
        .context(format!("Failed to execute simulation. Please ensure the binary at {:?} is executable.", binary_path))?;

    if !status.success() {
        eprintln!("Warning: Simulation completed with non-zero exit status. This may indicate errors in your Verilog code.");
    } else {
        println!("Simulation completed successfully.");
    }

    if let Err(e) = fs::remove_file(&binary_path) {
        eprintln!("Warning: Failed to remove temporary binary file: {}. You may want to delete it manually.", e);
    }

    Ok(())
}


================================================
FILE: src/cmd/synth.rs
================================================
use anyhow::{Result, Context};
use std::path::PathBuf;
use std::process::Command;
use std::fs::File;
use std::io::Write;

use crate::cmd::{Execute, Synth};

impl Execute for Synth {
    async fn execute(&self) -> Result<()> {
        synthesize_design(
            &self.top_module_path,
            self.riscv,
            self.core_path.as_ref(),
            &self.board,
            self.gen_yosys_script
        )
    }
}

fn synthesize_design(
    top_module_path: &str,
    riscv: bool,
    core_path: Option<&String>,
    board: &Option<String>,
    gen_yosys_script: bool
) -> Result<()> {
    let top_module_path = PathBuf::from(top_module_path);
    let (input_file, module_name, parent_dir, _) = extract_path_info(&top_module_path);
    
    let script_content = match board {
        Some(board) if board.to_lowercase() == "xilinx" => {
            let board_name = "artix7";
            let output_file = format!("{}/{}_{}_{}_synth.v", parent_dir, module_name, board_name, "xilinx");
            generate_xilinx_script_content(&input_file, riscv, core_path.cloned(), &module_name, &output_file)?
        },
        None => {
            let output_file = format!("{}/{}_synth.v", parent_dir, module_name);
            generate_yosys_script_content(&input_file, &module_name, &output_file)
        },
        Some(other) => {
            return Err(anyhow::anyhow!("Unsupported board: {}", other));
        }
    };

    if gen_yosys_script {
        let script_file = PathBuf::from(&parent_dir).join(format!("{}_synth_script.ys", module_name));
        write_script_to_file(&script_file, &script_content)?;
        println!("Yosys script generated at: {:?}", script_file);
    }

    run_yosys_with_script_content(&script_content)?;
    println!("Synthesis completed successfully.");
    Ok(())
}

fn extract_path_info(top_module_path: &PathBuf) -> (String, String, String, String) {
    let input_file = top_module_path.to_str().unwrap().to_string();
    let top_module = top_module_path.file_stem().unwrap().to_str().unwrap().to_string();
    let parent_dir = top_module_path.parent().unwrap().to_string_lossy().to_string();
    let output_file = format!("{}/{}_synth.v", parent_dir, top_module);
    (input_file, top_module, parent_dir, output_file)
}

fn generate_yosys_script_content(input_file: &str, top_module: &str, output_file: &str) -> String {
    format!(
        r#"
# Read the Verilog file
read_verilog {}

# Synthesize the design
synth -top {}

# Optimize the design
opt

# Write the synthesized design
write_verilog {}
        "#,
        input_file,
        top_module,
        output_file
    )
}

fn generate_xilinx_script_content(top_module_path_str: &str, riscv: bool, core_path: Option<String>, module_name: &str, output_file: &str) -> Result<String> {
    let mut script_content = format!(
        r#"
# Read the SystemVerilog file
read_verilog -sv {top_module_path_str}
"#
    );

    if riscv {
        if let Some(core_path) = core_path {
            script_content.push_str(&format!(
                r#"
# Read the RISC-V core
read_verilog -sv {core_path}
"#
            ));
        } else {
            return Err(anyhow::anyhow!("RISC-V core path is required when riscv flag is set"));
        }
    }

    script_content.push_str(&format!(
        r#"
# Synthesize for Xilinx 7 series (Artix-7)
synth_xilinx -top {module_name} -family xc7

# Optimize the design
opt

# Map to Xilinx 7 series cells
abc -lut 6

# Clean up
clean

# Write the synthesized design to a Verilog file
write_verilog {output_file}

write_edif {output_file}.edif

# Print statistics
stat
"#
    ));

    Ok(script_content)
}

fn write_script_to_file(script_file: &PathBuf, script_content: &str) -> Result<()> {
    let mut file = File::create(script_file)?;
    file.write_all(script_content.as_bytes())?;
    Ok(())
}

fn run_yosys_with_script_content(script_content: &str) -> Result<()> {
    let output = Command::new("yosys")
        .arg("-p")
        .arg(script_content)
        .output()
        .context("Failed to execute Yosys")?;

    println!("Yosys output:");
    println!("{}", String::from_utf8_lossy(&output.stdout));

    if !output.status.success() {
        let error_message = String::from_utf8_lossy(&output.stderr);
        return Err(anyhow::anyhow!("Yosys synthesis failed: {}", error_message));
    }

    Ok(())
}

================================================
FILE: src/cmd/update.rs
================================================
use anyhow::Result;

use crate::cmd::{Execute, Update};
use crate::cmd::include::get_head_commit_hash;
use crate::toml::{get_repo_links, add_top_module, remove_top_module};
use imara_diff::intern::InternedInput;
use imara_diff::{diff, Algorithm, UnifiedDiffBuilder};

impl Execute for Update {
    async fn execute(&self) -> Result<()> {
        let module_path = &self.module_path;
        println!("Updating module '{}'", module_path);
        update_module(module_path, self.commit.as_deref())
    }
}

fn update_module(module_path: &str, commit: Option<&str>) -> Result<()> {
    let repo_links = get_repo_links(module_path);
    if repo_links.is_empty() {
        return Err(anyhow::anyhow!("No repositories found for module '{}'", module_path));
    }

    let chosen_repo = if repo_links.len() == 1 {
        repo_links.into_iter().next().unwrap()
    } else {
        println!("Multiple repositories found for module '{}'. Please choose one:", module_path);
        for (index, link) in repo_links.iter().enumerate() {
            println!("{}. {}", index + 1, link);
        }
        let mut choice = String::new();
        std::io::stdin().read_line(&mut choice)?;
        let index: usize = choice.trim().parse()?;
        repo_links.into_iter().nth(index - 1)
            .ok_or_else(|| anyhow::anyhow!("Invalid choice"))?
    };

    let head_commit_hash = get_head_commit_hash(&chosen_repo).unwrap();
    let commit_hash = commit.unwrap_or(&head_commit_hash);

    println!("Updating module '{}' to commit '{}'", module_path, commit_hash);
    let old_contents = std::fs::read_to_string(module_path)?;
    remove_top_module(&chosen_repo, module_path)?;
    add_top_module(&chosen_repo, module_path, commit_hash)?;
    let new_contents = std::fs::read_to_string(module_path)?;
    println!("Module '{}' updated to commit '{}'", module_path, commit_hash);

    display_diff(&old_contents, &new_contents);

    Ok(())
}

fn display_diff(old_contents: &str, new_contents: &str) {
    let input = InternedInput::new(old_contents, new_contents);
    let diff_output = diff(
        Algorithm::Histogram,
        &input,
        UnifiedDiffBuilder::new(&input)
    );

    println!("Diff:\n{}", diff_output);
}

================================================
FILE: src/cmd/upgrade.rs
================================================
use anyhow::Result;
use std::process::Command;

use crate::cmd::{Execute, Upgrade};
use crate::config_man::set_version;

impl Execute for Upgrade {
    async fn execute(&self) -> Result<()> {
        println!("Upgrading VPM...");
        upgrade_vpm()?;
        let version = get_latest_version()?;
        if !version.is_empty() {
            set_version(&version)?;
        }

        println!("VPM upgrade completed successfully.");
        Ok(())
    }
}

fn upgrade_vpm() -> Result<()> {
    if cfg!(unix) {
        let output = Command::new("sh")
            .arg("-c")
            .arg("curl -sSfL https://raw.githubusercontent.com/getinstachip/vpm-internal/main/install.sh | sh")
            .output()?;

        if !output.status.success() {
            return Err(anyhow::anyhow!("Upgrade command failed"));
        }
    } else if cfg!(windows) {
        println!("To upgrade VPM on Windows, please follow these steps:");
        println!("1. Visit https://github.com/getinstachip/vpm/releases/latest");
        println!("2. Download the appropriate .exe file for your system");
        println!("3. Run the downloaded .exe file to complete the upgrade");
        return Ok(());
    } else {
        return Err(anyhow::anyhow!("Unsupported operating system"));
    }

    Ok(())
}

fn get_latest_version() -> Result<String> {
    let output = Command::new("git")
        .arg("describe ")
        .arg("--tags")
        .arg("--abbrev=0")
        .output()?;
    Ok(String::from_utf8(output.stdout)?)
}

================================================
FILE: src/config_man.rs
================================================
use anyhow::Result;
use directories::ProjectDirs;
use rand::RngCore;
use reqwest::Client;
use serde_json::json;
use std::fs;
use std::path::PathBuf;
use toml_edit::{DocumentMut, Item, Value, Table};
use uuid::Uuid;

use ring::aead::{self, Aad, LessSafeKey, Nonce};
use base64::{Engine as _, engine::general_purpose};
use ring::aead::UnboundKey;
use sha2::{Digest, Sha256};
use sys_info;

const POSTHOG_API_KEY: Option<&str> = option_env!("POSTHOG_API_KEY");
const DOCS_KEY: Option<&str> = option_env!("DOCS_KEY");

pub async fn send_event(command: String) -> Result<()> {
    if get_analytics()? {
        let uuid = get_uuid()?;
        let version = env!("CARGO_PKG_VERSION").to_string();
        let api_key = POSTHOG_API_KEY.expect("POSTHOG_API_KEY environment variable not set").to_string();
        
        let client = Client::new();
        let payload = json!({
            "api_key": api_key,
            "event": "user_action",
            "distinct_id": uuid,
            "properties": {
                "command": command,
                "version": version
            }
        });

        let _response = client.post("https://us.i.posthog.com/capture/")
            .json(&payload)
            .send()
            .await?;

        // if !response.status().is_success() {
        //     eprintln!("Failed to send event to PostHog: {}", response.status());
        // }
    }
    Ok(())
}

pub fn get_config_path() -> Option<PathBuf> {
    ProjectDirs::from("com", "Instachip", "vpm")
        .map(|proj_dirs| proj_dirs.config_dir().to_path_buf())
        .map(|mut path| {
            path.push("config.toml");
            path
        })
}

pub fn create_config() -> Result<()> {
    let config_path = get_config_path().unwrap();
    if !config_path.exists() {
        if let Some(parent) = config_path.parent() {
            fs::create_dir_all(parent)?;
        }
        fs::File::create(&config_path)?;
    }
    fs::write(config_path.clone(), "").expect("Failed to create config.toml");
    let contents = fs::read_to_string(config_path.clone())?;
    let mut config_doc = contents.parse::<DocumentMut>().expect("Failed to parse config.toml");

    config_doc.insert("user", Item::Table(Table::new()));
    let user_table = config_doc["user"].as_table_mut().unwrap();
    user_table.insert("uuid", Item::Value(Value::from(create_uuid()?)));
    user_table.insert("os", Item::Value(Value::from(std::env::consts::OS)));
    user_table.insert("arch", Item::Value(Value::from(std::env::consts::ARCH)));

    config_doc.insert("tool", Item::Table(Table::new()));
    let tool_table = config_doc["tool"].as_table_mut().unwrap();
    tool_table.insert("version", Item::Value(Value::from(env!("CARGO_PKG_VERSION"))));

    config_doc.insert("options", Item::Table(Table::new()));
    let options_table = config_doc["options"].as_table_mut().unwrap();
    options_table.insert("analytics", Item::Value(Value::from(true)));

    config_doc.insert("metrics", Item::Table(Table::new()));
    let metrics_table = config_doc["metrics"].as_table_mut().unwrap();
    metrics_table.insert("docs_count", Item::Value(Value::from(0)));
    encrypt_docs_count(0)?;

    fs::write(config_path, config_doc.to_string()).expect("Failed to write config.toml");
    Ok(())
}

fn create_uuid() -> Result<String> {
    let uuid = Uuid::now_v7().to_string();
    let os = sys_info::os_type().unwrap_or_default();
    let release = sys_info::os_release().unwrap_or_default();
    let arch = std::env::consts::ARCH.to_string();
    let cpu_num = sys_info::cpu_num().unwrap_or_default().to_string();
    let cpu_speed = sys_info::cpu_speed().unwrap_or_default().to_string();
    let mem_total = sys_info::mem_info().unwrap_or(sys_info::MemInfo { total: 0, free: 0, buffers: 0, cached: 0, swap_total: 0, swap_free: 0, avail: 0 }).total.to_string();
    let hostname = sys_info::hostname().unwrap_or_default();
    let timezone = std::env::var("TZ").unwrap_or_else(|_| "Unknown".to_string());

    let mut hasher = Sha256::new();
    hasher.update(uuid);
    hasher.update(os);
    hasher.update(release);
    hasher.update(arch);
    hasher.update(cpu_num);
    hasher.update(cpu_speed);
    hasher.update(mem_total);
    hasher.update(hostname);
    hasher.update(timezone);
    let hash = hasher.finalize();
    Ok(format!("{:x}", hash))
}

fn get_uuid() -> Result<String> {
    let config_path = get_config_path().unwrap();
    if !config_path.exists() {
        create_config()?;
    }
    let contents = fs::read_to_string(config_path)?;
    let config = contents.parse::<DocumentMut>().expect("Failed to parse config.toml");
    Ok(config["user"]["uuid"].as_str().unwrap().to_string())
}

pub fn set_analytics(value: bool) -> Result<()> {
    let config_path = get_config_path().unwrap();
    if !config_path.exists() {
        create_config()?;
    }
    let config = fs::read_to_string(config_path.clone())?;
    let mut config_doc = config.parse::<DocumentMut>().expect("Failed to parse config.toml");
    config_doc["options"]["analytics"] = Item::Value(Value::from(value));
    fs::write(config_path, config_doc.to_string()).expect("Failed to write config.toml");
    Ok(())
}

fn get_analytics() -> Result<bool> {
    let config_path = get_config_path().unwrap();
    if !config_path.exists() {
        create_config()?;
    }
    let config = fs::read_to_string(config_path.clone())?;
    let config_doc = config.parse::<DocumentMut>().expect("Failed to parse config.toml");
    Ok(config_doc["options"]["analytics"].as_bool().unwrap())
}

pub fn set_version(version: &str) -> Result<()> {
    let config_path = get_config_path().unwrap();
    if !config_path.exists() {
        create_config()?;
    }
    let config = fs::read_to_string(config_path.clone())?;
    let mut config_doc = config.parse::<DocumentMut>().expect("Failed to parse config.toml");
    config_doc["tool"]["version"] = Item::Value(Value::from(version));
    fs::write(config_path, config_doc.to_string()).expect("Failed to write config.toml");
    Ok(())
}   

pub fn decrypt_docs_count() -> Result<u8> {
    let config_path = get_config_path().ok_or(anyhow::anyhow!("Failed to get config path"))?;
    if !config_path.exists() {
        create_config()?;
    }

    let config = fs::read_to_string(config_path)?;
    let config_doc = config.parse::<DocumentMut>().expect("Failed to parse config.toml");
    let encrypted_docs_base64 = config_doc["metrics"]["docs_count"]
        .as_str()
        .ok_or_else(|| anyhow::anyhow!("docs_count not found in config"))?;
    let encrypted_docs = general_purpose::STANDARD
        .decode(encrypted_docs_base64)
        .map_err(|e| anyhow::anyhow!("Failed to decode docs count: {}", e))?;
    // Get the key from environment variable
    let docs_key_str = DOCS_KEY.ok_or_else(|| anyhow::anyhow!("DOCS_KEY is not set"))?;
    let key_bytes = hex::decode(docs_key_str).map_err(|e| anyhow::anyhow!("Invalid DOCS_KEY format: {}", e))?;

    // Create an AEAD key
    let unbound_key =
        UnboundKey::new(&aead::AES_256_GCM, &key_bytes).map_err(|_| anyhow::anyhow!("Invalid key"))?;
    let key = LessSafeKey::new(unbound_key);

    // Extract nonce and ciphertext
    if encrypted_docs.len() < 12 {
        return Err(anyhow::anyhow!("Ciphertext too short"));
    }
    let (nonce_bytes, ciphertext_and_tag) = encrypted_docs.split_at(12);
    let nonce = Nonce::try_assume_unique_for_key(nonce_bytes).map_err(|_| anyhow::anyhow!("Invalid nonce"))?;

    // Prepare mutable buffer for decryption
    let mut in_out = ciphertext_and_tag.to_vec();

    // Decrypt the data
    key.open_in_place(nonce, Aad::empty(), &mut in_out)
        .map_err(|_| anyhow::anyhow!("Decryption failed"))?;

    // Convert decrypted data to string
    let decrypted_str =
        std::str::from_utf8(&in_out).map_err(|_| anyhow::anyhow!("Invalid UTF-8 in decrypted data"))?;
    let docs_count: u8 = decrypted_str.parse().map_err(|_| anyhow::anyhow!("Failed to parse decrypted data"))?;

    Ok(docs_count)
}

pub fn encrypt_docs_count(docs_count: u8) -> Result<()> {
    // Convert the docs_count to a string and then to bytes
    let docs_count_bytes = docs_count.to_string().into_bytes();
    // Get the key from the environment variable
    let docs_key_str = DOCS_KEY.ok_or_else(|| anyhow::anyhow!("DOCS_KEY is not set"))?;
    let key_bytes = hex::decode(docs_key_str).map_err(|e| anyhow::anyhow!("Invalid DOCS_KEY format: {}", e))?;

    // Create an AEAD key
    let unbound_key =
        UnboundKey::new(&aead::AES_256_GCM, &key_bytes).map_err(|_| anyhow::anyhow!("Invalid key"))?;
    let key = LessSafeKey::new(unbound_key);

    // Generate a random nonce
    let mut nonce_bytes = [0u8; 12];
    rand::rngs::OsRng.fill_bytes(&mut nonce_bytes);
    let nonce = Nonce::assume_unique_for_key(nonce_bytes);

    // Prepare buffer for encryption (data + space for the tag)
    let mut in_out = docs_count_bytes;
    in_out.extend_from_slice(&[0u8; 16]);

    // Encrypt the data
    key.seal_in_place_append_tag(nonce, Aad::empty(), &mut in_out)
        .map_err(|_| anyhow::anyhow!("Encryption failed"))?;

    // Prepend nonce to the ciphertext
    let mut encrypted_data = nonce_bytes.to_vec();
    encrypted_data.extend_from_slice(&in_out);

    // Encode the encrypted data to base64
    let encrypted_base64 = general_purpose::STANDARD.encode(encrypted_data);

    let config_path = get_config_path().unwrap();
    let config = fs::read_to_string(config_path.clone())?;
    let mut config_doc = config.parse::<DocumentMut>().expect("Failed to parse config.toml");
    config_doc["metrics"]["docs_count"] = Item::Value(Value::from(encrypted_base64));
    fs::write(config_path, config_doc.to_string()).expect("Failed to write config.toml");

    Ok(())
}

================================================
FILE: src/error.rs
================================================
use core::fmt::Display;
use std::fmt::{self, Formatter};

/// Custom error type for early exit
#[derive(Debug)]
pub struct SilentExit {
    pub code: u8,
}

impl Display for SilentExit {
    fn fmt(&self, _: &mut Formatter<'_>) -> fmt::Result {
        Ok(())
    }
}


================================================
FILE: src/main.rs
================================================
mod cmd;
mod error;
mod toml;
mod config_man;

use std::env;
use std::io::{self, Write};
use std::process::ExitCode;
use std::fs;

use clap::Parser;

use crate::cmd::{Cmd, Execute};
use crate::error::SilentExit;
use crate::config_man::{get_config_path, create_config};

#[tokio::main]
pub async fn main() -> ExitCode {
    // Forcibly disable backtraces.
    env::remove_var("RUST_LIB_BACKTRACE");
    env::remove_var("RUST_BACKTRACE");

    let flag_file = get_config_path().unwrap().with_file_name(".vpm_welcome_shown");
    if !flag_file.exists() {
        create_config().unwrap();

        println!("Welcome to vpm!");
        println!("We collect anonymous usage data to improve the tool.");
        println!("The following information will be collected:");
        println!(" - The version of vpm you are using");
        println!(" - Which commands you run and when (not including arguments, input, or output)");
        println!("No personal information will be collected.");
        println!("To opt-out, run `vpm config --analytics false`. You may change this at any time.\n");
        println!("Rerun your command to accept and continue.");

        fs::write(flag_file, "").unwrap();
        return ExitCode::SUCCESS;
    }

    match Cmd::parse().execute().await {
        Ok(()) => ExitCode::SUCCESS,
        Err(e) => match e.downcast::<SilentExit>() {
            Ok(SilentExit { code }) => code.into(),
            Err(e) => {
                _ = writeln!(io::stderr(), "vpm: {e:?}");
                ExitCode::FAILURE
            }
        },
    }
}


================================================
FILE: src/toml.rs
================================================
use serde::{Deserialize, Serialize};
use std::fs::{OpenOptions, read_to_string};
use std::io::Write;
use std::path::Path;
use std::collections::HashSet;
use anyhow::Result;
use toml_edit::{Array, DocumentMut, InlineTable, Item, Table, Value};


#[derive(Serialize, Deserialize, Debug)]
struct Package {
    name: String,
    version: String,
    authors: Vec<String>,
    description: String,
    license: String,
}

#[derive(Debug)]
struct VpmToml {
    toml_doc: DocumentMut,
}

impl Default for Package {
    fn default() -> Self {
        Package {
            name: "my-vpm-package".to_string(),
            version: "0.1.0".to_string(),
            authors: vec!["<author-name> <author-email>".to_string()],
            description: "A vpm package".to_string(),
            license: "LicenseRef-LICENSE".to_string(),
        }
    }
}

impl VpmToml {    
    pub fn from(filepath: &str) -> Self {
        if !Path::new(filepath).exists() {
            let mut initial_doc = DocumentMut::new();
            initial_doc["package"] = Item::Table(Table::new());
            initial_doc["package"]["name"] = Item::Value(Value::from(Package::default().name));
            initial_doc["package"]["version"] = Item::Value(Value::from(Package::default().version));
            initial_doc["package"]["authors"] = Item::Value(Value::from(Array::from(Package::default().authors.iter().map(|s| Value::from(s.to_string())).collect())));
            initial_doc["package"]["description"] = Item::Value(Value::from(Package::default().description));
            initial_doc["package"]["license"] = Item::Value(Value::from(Package::default().license));

            initial_doc["dependencies"] = Item::Table(Table::new());

            let mut file = OpenOptions::new()
                .write(true)
                .create(true)
                .truncate(true)
                .open(filepath)
                .expect("Failed to create vpm.toml");
            file.write_all(initial_doc.to_string().as_bytes()).expect("Failed to write to vpm.toml");
        }

        let toml_content = read_to_string(filepath).expect("Failed to read vpm.toml");
        Self {
            toml_doc: toml_content.parse::<DocumentMut>().expect("Failed to parse vpm.toml")
        }
    }

    pub fn get_dependencies(&self) -> Option<&Table> {
        self.toml_doc["dependencies"].as_table()
    }

    pub fn add_dependency(&mut self, git: &str) {
        self.toml_doc["dependencies"][git] = Item::Value(Value::Array(Array::new()));
    }

    pub fn add_top_module(&mut self, repo_link: &str, module_name: &str, commit: &str) {
        let array = self.toml_doc["dependencies"][repo_link].as_array_mut().unwrap();
        if !array.iter().any(|m| m.as_inline_table().unwrap().get("top_module").unwrap().as_str().unwrap() == module_name) {
            let new_entry = Value::InlineTable({
                let mut table = InlineTable::new();
                table.insert("top_module".to_string(), Value::from(module_name));
                table.insert("commit_hash".to_string(), Value::from(commit.to_string()));
                table
            });
            array.push(new_entry);
        }
    }

    pub fn remove_dependency(&mut self, git: &str) {
        if let Some(dependencies) = self.toml_doc["dependencies"].as_table_mut() {
            dependencies.remove(git);
        }
    }

    pub fn remove_top_module(&mut self, repo_link: &str, module_name: &str) {
    if let Some(dependencies) = self.toml_doc["dependencies"].as_table_mut() {
        if let Some(modules) = dependencies.get_mut(repo_link).and_then(|v| v.as_array_mut()) {
            modules.retain(|m| {
                if let Some(table) = m.as_inline_table() {
                    if let Some(top_module) = table.get("top_module").and_then(|v| v.as_str()) {
                        return top_module != module_name;
                    }
                }
                true
            });

            // If the array is empty after removal, remove the entire dependency
            if modules.is_empty() {
                dependencies.remove(repo_link);
            }
        }
    }
    }

    pub fn write_to_file(&self, filepath: &str) -> Result<()> {
        let toml_content = self.toml_doc.to_string();
        let mut formatted_content = String::new();
        for line in toml_content.lines() {
            if !line.trim().contains("}, ") {
                formatted_content.push_str(line);
            } else {
                let indent_level = line.chars().take_while(|&c| c != '{').count();
                formatted_content.push_str(&line.replace("}, ", &format!("}},\n{}", " ".repeat(indent_level))));
            }
            formatted_content.push('\n');
        }

        let mut file = OpenOptions::new()
            .write(true)
            .create(true)
            .truncate(true)
            .open(filepath)
            .expect("Failed to open vpm.toml");
        file.write_all(formatted_content.as_bytes()).expect("Failed to write to vpm.toml");
        Ok(())
    }

    pub fn get_repo_links(&self, module_name: &str) -> HashSet<String> {
        let mut repo_links = HashSet::new();
        if let Some(dependencies) = self.toml_doc["dependencies"].as_table() {
            for (repo_link, dependency) in dependencies.iter() {
                if let Some(top_modules) = dependency.as_array() {
                    if top_modules.iter().any(|m| m.as_inline_table().unwrap().get("top_module").unwrap().as_str().unwrap() == module_name) {
                        repo_links.insert(repo_link.to_string());
                    }
                }
            }
        }
        repo_links
    }
}

pub fn add_dependency(git: &str) -> Result<()> {
    let mut vpm_toml = VpmToml::from("vpm.toml");
    if !vpm_toml.get_dependencies().unwrap().contains_key(git) {
        vpm_toml.add_dependency(git);
        vpm_toml.write_to_file("vpm.toml")?;
    }
    Ok(())
}

pub fn add_top_module(repo_link: &str, module_path: &str, commit: &str) -> Result<()> {
    let mut vpm_toml = VpmToml::from("vpm.toml");
    vpm_toml.add_top_module(repo_link, module_path, commit);
    vpm_toml.write_to_file("vpm.toml")?;
    Ok(())
}

fn remove_dependency(git: &str) -> Result<()> {
    let mut vpm_toml = VpmToml::from("vpm.toml");
    vpm_toml.remove_dependency(git);
    vpm_toml.write_to_file("vpm.toml")?;
    Ok(())
}

pub fn remove_top_module(repo_link: &str, module_name: &str) -> Result<()> {
    let mut vpm_toml = VpmToml::from("vpm.toml");
    vpm_toml.remove_top_module(repo_link, module_name);
    if let Some(dependencies) = vpm_toml.toml_doc["dependencies"].as_table() {
        if let Some(modules) = dependencies.get(repo_link).and_then(|v| v.as_array()) {
            if modules.is_empty() {
                remove_dependency(repo_link)?;
            }
        }
    }
    vpm_toml.write_to_file("vpm.toml")?;
    Ok(())
}

pub fn get_repo_links(module_name: &str) -> HashSet<String> {
    let vpm_toml = VpmToml::from("vpm.toml");
    vpm_toml.get_repo_links(module_name)
}
Download .txt
gitextract_f5njyyrd/

├── .github/
│   └── workflows/
│       └── release.yml
├── .gitignore
├── CONTRIBUTING.md
├── Cargo.toml
├── LICENSE.md
├── README.md
├── install.sh
├── installer.iss
└── src/
    ├── cmd/
    │   ├── cmd.rs
    │   ├── config.rs
    │   ├── docs.rs
    │   ├── dotf.rs
    │   ├── include.rs
    │   ├── install.rs
    │   ├── list.rs
    │   ├── load.rs
    │   ├── mod.rs
    │   ├── remove.rs
    │   ├── run.rs
    │   ├── sim.rs
    │   ├── synth.rs
    │   ├── update.rs
    │   └── upgrade.rs
    ├── config_man.rs
    ├── error.rs
    ├── main.rs
    └── toml.rs
Download .txt
SYMBOL INDEX (142 symbols across 19 files)

FILE: src/cmd/cmd.rs
  type Cmd (line 12) | pub enum Cmd {
  type Upgrade (line 98) | pub struct Upgrade {}
  type Include (line 101) | pub struct Include {
  type Update (line 113) | pub struct Update {
  type Remove (line 121) | pub struct Remove {
  type Dotf (line 127) | pub struct Dotf {
  type Docs (line 133) | pub struct Docs {
  type Install (line 143) | pub struct Install {
  type Sim (line 156) | pub struct Sim {
  type List (line 165) | pub struct List {}
  type Synth (line 168) | pub struct Synth {
  type Load (line 185) | pub struct Load {
  type Run (line 195) | pub struct Run {
  type Config (line 203) | pub struct Config {

FILE: src/cmd/config.rs
  method execute (line 6) | async fn execute(&self) -> Result<()> {

FILE: src/cmd/docs.rs
  method execute (line 13) | async fn execute(&self) -> Result<()> {
  function fetch_module_content (line 60) | async fn fetch_module_content(url: &str) -> Result<String> {
  function format_text (line 81) | fn format_text(text: &str) -> String {
  function generate_docs (line 88) | async fn generate_docs(module_path: &str, content: &str, full_module_pat...
  function generate_docs_offline (line 130) | async fn generate_docs_offline(module_path: &str, content: &str, full_mo...

FILE: src/cmd/dotf.rs
  method execute (line 9) | async fn execute(&self) -> Result<()> {
  function append_modules_to_filelist (line 24) | pub fn append_modules_to_filelist(top_module_path: &str, sub: bool) -> R...
  function append_module (line 73) | fn append_module(
  function find_module_instantiations (line 201) | fn find_module_instantiations(

FILE: src/cmd/include.rs
  method execute (line 20) | async fn execute(&self) -> Result<()> {
  function get_head_commit_hash (line 38) | pub fn get_head_commit_hash(url: &str) -> Result<String> {
  function include_entire_repo (line 65) | fn include_entire_repo(url: &str, tmp_path: &PathBuf, riscv: bool, commi...
  function include_single_module (line 83) | fn include_single_module(url: &str, riscv: bool, commit_hash: Option<&st...
  function get_files (line 95) | fn get_files(directory: &str) -> Vec<String> {
  function get_relative_paths (line 110) | fn get_relative_paths(files: &[String], tmp_path: &PathBuf) -> Vec<Strin...
  function select_modules (line 119) | fn select_modules(items: &[String]) -> Result<HashSet<String>> {
  function process_selected_modules (line 157) | fn process_selected_modules(url: &str, tmp_path: &PathBuf, selected_item...
  function print_success_message (line 177) | fn print_success_message(url: &str, selected_items: &HashSet<String>) {
  function name_from_url (line 189) | fn name_from_url(url: &str) -> &str {
  function get_component_path_from_github_url (line 193) | fn get_component_path_from_github_url(url: &str) -> Option<String> {
  function get_github_repo_url (line 202) | fn get_github_repo_url(url: &str) -> Option<String> {
  function is_full_filepath (line 211) | fn is_full_filepath(path: &str) -> bool {
  function filepath_to_dir_entry (line 215) | fn filepath_to_dir_entry(filepath: PathBuf) -> Result<DirEntry> {
  function generate_top_v_content (line 225) | fn generate_top_v_content(module_path: &str) -> Result<String> {
  function generate_xdc_content (line 289) | fn generate_xdc_content(module_path: &str) -> Result<String> {
  function include_module_from_url (line 342) | pub fn include_module_from_url(module_path: &str, url: &str, riscv: bool...
  function process_module (line 366) | pub fn process_module(package_name: &str, module: &str, destination: Str...
  function process_non_full_filepath (line 403) | fn process_non_full_filepath(module_name: &str, tmp_path: &PathBuf, targ...
  function find_matching_entries (line 419) | fn find_matching_entries(module_name: &str, tmp_path: &PathBuf) -> Vec<P...
  function process_multiple_matches (line 431) | fn process_multiple_matches(matching_entries: Vec<PathBuf>, target_path:...
  function process_file (line 452) | fn process_file(entry: &DirEntry, destination: &str, module_path: &str, ...
  function download_and_process_submodules (line 484) | fn download_and_process_submodules(package_name: &str, module_path: &str...
  function update_lockfile (line 570) | fn update_lockfile(full_path: &PathBuf, url: &str, contents: &str, visit...
  function update_submodules (line 619) | fn update_submodules(lockfile: &mut String, module_entry: &str, submodul...
  function generate_headers (line 627) | pub fn generate_headers(root_node: Node, contents: &str) -> Result<Strin...
  function get_submodules (line 702) | pub fn get_submodules(contents: &str) -> Result<HashSet<String>> {
  function include_repo_from_url (line 717) | pub fn include_repo_from_url(url: &str, location: &str, commit_hash: Opt...
  function clone_repo (line 728) | pub fn clone_repo(url: &str, repo_path: &Path, commit_hash: Option<&str>...

FILE: src/cmd/install.rs
  method execute (line 11) | async fn execute(&self) -> Result<()> {
  function has_sudo_access (line 58) | fn has_sudo_access() -> bool {
  function install_verilator (line 67) | fn install_verilator() -> Result<()> {
  function install_verilator_from_source (line 141) | fn install_verilator_from_source() -> Result<()> {
  function install_icarus_verilog (line 188) | fn install_icarus_verilog() -> Result<()> {
  function install_chipyard (line 308) | fn install_chipyard() -> Result<()> {
  function install_edalize (line 340) | fn install_edalize() -> Result<()> {
  function check_command (line 387) | fn check_command(cmd: &str) -> bool {
  function install_openroad (line 394) | fn install_openroad() -> Result<()> {
  function install_yosys (line 452) | fn install_yosys() -> Result<()> {
  function install_riscv (line 512) | fn install_riscv() -> Result<()> {
  function install_nextpnr (line 559) | fn install_nextpnr() -> Result<()> {
  function install_xray (line 578) | fn install_xray() -> Result<()> {
  function is_arch_distro (line 597) | fn is_arch_distro() -> bool {

FILE: src/cmd/list.rs
  constant STD_LIB_URL (line 7) | const STD_LIB_URL: &str = "https://github.com/getinstachip/openchips";
  method execute (line 10) | async fn execute(&self) -> Result<()> {
  function list_verilog_files (line 31) | fn list_verilog_files() -> Result<Vec<String>> {

FILE: src/cmd/load.rs
  method execute (line 8) | async fn execute(&self) -> Result<()> {
  function load_xilinx (line 20) | fn load_xilinx(edif_path: &Path, constraints_path: &Path) -> Result<()> {

FILE: src/cmd/mod.rs
  type Execute (line 22) | pub trait Execute {
    method execute (line 23) | async fn execute(&self) -> Result<()>;
    method execute (line 28) | async fn execute(&self) -> Result<()> {

FILE: src/cmd/remove.rs
  method execute (line 11) | async fn execute(&self) -> Result<()> {
  function remove_module (line 17) | fn remove_module(module_path: &str) -> Result<()> {

FILE: src/cmd/run.rs
  method execute (line 6) | async fn execute(&self) -> Result<()> {

FILE: src/cmd/sim.rs
  method execute (line 14) | async fn execute(&self) -> Result<()> {
  function testbench_exists (line 35) | fn testbench_exists(verilog_files: &[String]) -> bool {
  function generate_and_add_testbench (line 39) | fn generate_and_add_testbench(verilog_files: &mut Vec<String>) -> Result...
  function compile_verilog_from_folder (line 58) | pub fn compile_verilog_from_folder(folder: &str) -> Result<PathBuf> {
  function collect_verilog_files (line 80) | fn collect_verilog_files(folder: &str) -> Result<Vec<String>> {
  function run_simulation_with_waveform (line 93) | fn run_simulation_with_waveform(output_path: &Path) -> Result<()> {
  function generate_testbench (line 133) | pub fn generate_testbench(module_path: &str, is_systemverilog: bool) -> ...
  function extract_module_info (line 155) | fn extract_module_info(module_path: &str) -> Result<(String, Vec<(String...
  function generate_testbench_header (line 218) | fn generate_testbench_header(module_name: &str) -> String {
  function declare_parameters (line 222) | fn declare_parameters(parameters: &[(String, String)]) -> String {
  function declare_wires_for_ports (line 236) | fn declare_wires_for_ports(ports: &[(String, Option<String>, String)], i...
  function instantiate_module (line 250) | fn instantiate_module(module_name: &str, ports: &[(String, Option<String...
  function generate_clock (line 268) | fn generate_clock(ports: &[(String, Option<String>, String)]) -> String {
  function generate_initial_block (line 276) | fn generate_initial_block(ports: &[(String, Option<String>, String)], is...
  function generate_check_outputs_task (line 297) | fn generate_check_outputs_task(ports: &[(String, Option<String>, String)...
  function remove_comments_from_file (line 318) | fn remove_comments_from_file(file_path: &str) -> Result<()> {
  function compile_verilog (line 342) | pub fn compile_verilog(verilog_files: &Vec<String>) -> Result<PathBuf> {
  function generate_random_output_name (line 362) | fn generate_random_output_name() -> String {
  function run_iverilog_command (line 368) | fn run_iverilog_command(output_name: &str, verilog_files: &[String]) -> ...
  function run_simulation (line 378) | pub fn run_simulation(output_path: &PathBuf) -> Result<()> {

FILE: src/cmd/synth.rs
  method execute (line 10) | async fn execute(&self) -> Result<()> {
  function synthesize_design (line 21) | fn synthesize_design(
  function extract_path_info (line 57) | fn extract_path_info(top_module_path: &PathBuf) -> (String, String, Stri...
  function generate_yosys_script_content (line 65) | fn generate_yosys_script_content(input_file: &str, top_module: &str, out...
  function generate_xilinx_script_content (line 86) | fn generate_xilinx_script_content(top_module_path_str: &str, riscv: bool...
  function write_script_to_file (line 134) | fn write_script_to_file(script_file: &PathBuf, script_content: &str) -> ...
  function run_yosys_with_script_content (line 140) | fn run_yosys_with_script_content(script_content: &str) -> Result<()> {

FILE: src/cmd/update.rs
  method execute (line 10) | async fn execute(&self) -> Result<()> {
  function update_module (line 17) | fn update_module(module_path: &str, commit: Option<&str>) -> Result<()> {
  function display_diff (line 52) | fn display_diff(old_contents: &str, new_contents: &str) {

FILE: src/cmd/upgrade.rs
  method execute (line 8) | async fn execute(&self) -> Result<()> {
  function upgrade_vpm (line 21) | fn upgrade_vpm() -> Result<()> {
  function get_latest_version (line 44) | fn get_latest_version() -> Result<String> {

FILE: src/config_man.rs
  constant POSTHOG_API_KEY (line 17) | const POSTHOG_API_KEY: Option<&str> = option_env!("POSTHOG_API_KEY");
  constant DOCS_KEY (line 18) | const DOCS_KEY: Option<&str> = option_env!("DOCS_KEY");
  function send_event (line 20) | pub async fn send_event(command: String) -> Result<()> {
  function get_config_path (line 49) | pub fn get_config_path() -> Option<PathBuf> {
  function create_config (line 58) | pub fn create_config() -> Result<()> {
  function create_uuid (line 93) | fn create_uuid() -> Result<String> {
  function get_uuid (line 118) | fn get_uuid() -> Result<String> {
  function set_analytics (line 128) | pub fn set_analytics(value: bool) -> Result<()> {
  function get_analytics (line 140) | fn get_analytics() -> Result<bool> {
  function set_version (line 150) | pub fn set_version(version: &str) -> Result<()> {
  function decrypt_docs_count (line 162) | pub fn decrypt_docs_count() -> Result<u8> {
  function encrypt_docs_count (line 207) | pub fn encrypt_docs_count(docs_count: u8) -> Result<()> {

FILE: src/error.rs
  type SilentExit (line 6) | pub struct SilentExit {
  method fmt (line 11) | fn fmt(&self, _: &mut Formatter<'_>) -> fmt::Result {

FILE: src/main.rs
  function main (line 18) | pub async fn main() -> ExitCode {

FILE: src/toml.rs
  type Package (line 11) | struct Package {
  type VpmToml (line 20) | struct VpmToml {
    method from (line 37) | pub fn from(filepath: &str) -> Self {
    method get_dependencies (line 64) | pub fn get_dependencies(&self) -> Option<&Table> {
    method add_dependency (line 68) | pub fn add_dependency(&mut self, git: &str) {
    method add_top_module (line 72) | pub fn add_top_module(&mut self, repo_link: &str, module_name: &str, c...
    method remove_dependency (line 85) | pub fn remove_dependency(&mut self, git: &str) {
    method remove_top_module (line 91) | pub fn remove_top_module(&mut self, repo_link: &str, module_name: &str) {
    method write_to_file (line 111) | pub fn write_to_file(&self, filepath: &str) -> Result<()> {
    method get_repo_links (line 134) | pub fn get_repo_links(&self, module_name: &str) -> HashSet<String> {
  method default (line 25) | fn default() -> Self {
  function add_dependency (line 149) | pub fn add_dependency(git: &str) -> Result<()> {
  function add_top_module (line 158) | pub fn add_top_module(repo_link: &str, module_path: &str, commit: &str) ...
  function remove_dependency (line 165) | fn remove_dependency(git: &str) -> Result<()> {
  function remove_top_module (line 172) | pub fn remove_top_module(repo_link: &str, module_name: &str) -> Result<(...
  function get_repo_links (line 186) | pub fn get_repo_links(module_name: &str) -> HashSet<String> {
Condensed preview — 27 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (180K chars).
[
  {
    "path": ".github/workflows/release.yml",
    "chars": 4088,
    "preview": "name: release\non:\n  push:\n  workflow_dispatch:\nenv:\n  CARGO_INCREMENTAL: 0\npermissions:\n  contents: write\njobs:\n  releas"
  },
  {
    "path": ".gitignore",
    "chars": 887,
    "preview": "# Generated by Cargo\n# will have compiled files and executables\ndebug/\ntarget/\n\n# Remove Cargo.lock from gitignore if cr"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 3430,
    "preview": "# Contributing Guidelines\n\nFirst off, thank you for considering contributing to the Verilog Package Manager (VPM). It's "
  },
  {
    "path": "Cargo.toml",
    "chars": 1365,
    "preview": "[package]\ndescription = \"A powerful package manager for Verilog projects, streamlining IP core management and accelerati"
  },
  {
    "path": "LICENSE.md",
    "chars": 1068,
    "preview": "# MIT License\n\nCopyright (c) 2024 Instachip\n\nPermission is hereby granted, free of charge, to any person obtaining a cop"
  },
  {
    "path": "README.md",
    "chars": 8900,
    "preview": "# Verilog Package Manager (VPM)\n\nVPM is a powerful package manager for Verilog projects, currently being piloted at Stan"
  },
  {
    "path": "install.sh",
    "chars": 13496,
    "preview": "#!/bin/sh\n# shellcheck shell=dash\n# shellcheck disable=SC3043 # Assume `local` extension\n\n# The official vpm installer.\n"
  },
  {
    "path": "installer.iss",
    "chars": 5530,
    "preview": "#define VerFile = FileOpen(\"version.txt\")\n#define MyAppVersion = FileRead(VerFile)\n#expr FileClose(VerFile)\n#undef VerFi"
  },
  {
    "path": "src/cmd/cmd.rs",
    "chars": 9894,
    "preview": "use clap::Parser;\n\n#[derive(Debug, Parser)]\n#[clap(\n    about = \"VPM - Verilog Package Manager\",\n    author,\n    version"
  },
  {
    "path": "src/cmd/config.rs",
    "chars": 358,
    "preview": "use crate::cmd::{Execute, Config};\nuse crate::config_man::set_analytics;\nuse anyhow::Result;\n\nimpl Execute for Config {\n"
  },
  {
    "path": "src/cmd/docs.rs",
    "chars": 9683,
    "preview": "use anyhow::{Result, Context, anyhow};\nuse reqwest::Client;\nuse std::path::PathBuf;\nuse serde_json::json;\nuse std::fs;\nu"
  },
  {
    "path": "src/cmd/dotf.rs",
    "chars": 10312,
    "preview": "use anyhow::Result;\nuse std::fs;\nuse std::path::{Path, PathBuf};\nuse std::io::Write;\n\nuse crate::cmd::{Execute, Dotf};\n\n"
  },
  {
    "path": "src/cmd/include.rs",
    "chars": 30601,
    "preview": "use std::collections::HashSet;\nuse std::env::current_dir;\nuse std::path::{Path, PathBuf};\nuse std::{fs, process::Command"
  },
  {
    "path": "src/cmd/install.rs",
    "chars": 18032,
    "preview": "use anyhow::{Context, Result};\nuse std::process::Command;\nuse std::path::Path;\nuse std::env;\nuse std::fs::OpenOptions;\nu"
  },
  {
    "path": "src/cmd/list.rs",
    "chars": 2741,
    "preview": "use anyhow::{Result, Context, anyhow};\nuse std::collections::HashSet;\nuse std::process::Command;\nuse crate::cmd::{Execut"
  },
  {
    "path": "src/cmd/load.rs",
    "chars": 1684,
    "preview": "use anyhow::Result;\nuse std::path::Path;\nuse std::process::Command;\n\nuse crate::cmd::{Execute, Load};\n\nimpl Execute for "
  },
  {
    "path": "src/cmd/mod.rs",
    "chars": 2560,
    "preview": "mod cmd;\nmod upgrade;\nmod include;\nmod update;\nmod remove;\nmod dotf;\nmod list;\nmod install;\nmod sim;\nmod docs;\nmod synth"
  },
  {
    "path": "src/cmd/remove.rs",
    "chars": 2858,
    "preview": "use std::fs;\nuse std::path::PathBuf;\nuse std::io::{self, Write};\n\nuse anyhow::{Result, anyhow};\n\nuse crate::cmd::{Execut"
  },
  {
    "path": "src/cmd/run.rs",
    "chars": 143,
    "preview": "use anyhow::Result;\n\nuse crate::cmd::{Execute, Run};\n\nimpl Execute for Run {\n    async fn execute(&self) -> Result<()> {"
  },
  {
    "path": "src/cmd/sim.rs",
    "chars": 17136,
    "preview": "use anyhow::{Context, Result};\nuse std::process::Command;\nuse std::env;\nuse std::path::{Path, PathBuf};\nuse std::fs;\nuse"
  },
  {
    "path": "src/cmd/synth.rs",
    "chars": 4357,
    "preview": "use anyhow::{Result, Context};\nuse std::path::PathBuf;\nuse std::process::Command;\nuse std::fs::File;\nuse std::io::Write;"
  },
  {
    "path": "src/cmd/update.rs",
    "chars": 2218,
    "preview": "use anyhow::Result;\n\nuse crate::cmd::{Execute, Update};\nuse crate::cmd::include::get_head_commit_hash;\nuse crate::toml::"
  },
  {
    "path": "src/cmd/upgrade.rs",
    "chars": 1513,
    "preview": "use anyhow::Result;\nuse std::process::Command;\n\nuse crate::cmd::{Execute, Upgrade};\nuse crate::config_man::set_version;\n"
  },
  {
    "path": "src/config_man.rs",
    "chars": 9769,
    "preview": "use anyhow::Result;\nuse directories::ProjectDirs;\nuse rand::RngCore;\nuse reqwest::Client;\nuse serde_json::json;\nuse std:"
  },
  {
    "path": "src/error.rs",
    "chars": 268,
    "preview": "use core::fmt::Display;\nuse std::fmt::{self, Formatter};\n\n/// Custom error type for early exit\n#[derive(Debug)]\npub stru"
  },
  {
    "path": "src/main.rs",
    "chars": 1570,
    "preview": "mod cmd;\nmod error;\nmod toml;\nmod config_man;\n\nuse std::env;\nuse std::io::{self, Write};\nuse std::process::ExitCode;\nuse"
  },
  {
    "path": "src/toml.rs",
    "chars": 7060,
    "preview": "use serde::{Deserialize, Serialize};\nuse std::fs::{OpenOptions, read_to_string};\nuse std::io::Write;\nuse std::path::Path"
  }
]

About this extraction

This page contains the full source code of the getinstachip/vpm GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 27 files (167.5 KB), approximately 42.2k tokens, and a symbol index with 142 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!