├── src ├── cmd │ ├── run.rs │ ├── config.rs │ ├── upgrade.rs │ ├── load.rs │ ├── update.rs │ ├── mod.rs │ ├── list.rs │ ├── remove.rs │ ├── synth.rs │ ├── docs.rs │ ├── cmd.rs │ ├── dotf.rs │ ├── sim.rs │ ├── install.rs │ └── include.rs ├── error.rs ├── main.rs ├── toml.rs └── config_man.rs ├── .gitignore ├── LICENSE.md ├── Cargo.toml ├── CONTRIBUTING.md ├── .github └── workflows │ └── release.yml ├── installer.iss ├── README.md └── install.sh /src/cmd/run.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | 3 | use crate::cmd::{Execute, Run}; 4 | 5 | impl Execute for Run { 6 | async fn execute(&self) -> Result<()> { 7 | Ok(()) 8 | } 9 | } -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::Display; 2 | use std::fmt::{self, Formatter}; 3 | 4 | /// Custom error type for early exit 5 | #[derive(Debug)] 6 | pub struct SilentExit { 7 | pub code: u8, 8 | } 9 | 10 | impl Display for SilentExit { 11 | fn fmt(&self, _: &mut Formatter<'_>) -> fmt::Result { 12 | Ok(()) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/cmd/config.rs: -------------------------------------------------------------------------------- 1 | use crate::cmd::{Execute, Config}; 2 | use crate::config_man::set_analytics; 3 | use anyhow::Result; 4 | 5 | impl Execute for Config { 6 | async fn execute(&self) -> Result<()> { 7 | if self.analytics.is_some() { 8 | set_analytics(self.analytics.unwrap())?; 9 | println!("Analytics set to: {}", self.analytics.unwrap()); 10 | } 11 | Ok(()) 12 | } 13 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | 16 | # RustRover 17 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 18 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 19 | # and can be added to the global gitignore or merged into this file. For a more nuclear 20 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 21 | #.idea/ 22 | 23 | vpm_modules/ 24 | Vpm.toml 25 | vpm.toml 26 | vpm.lock 27 | .svlangserver/ 28 | .env 29 | 30 | .DS_Store 31 | .vscode/launch.json 32 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2024 Instachip 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. 10 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | description = "A powerful package manager for Verilog projects, streamlining IP core management and accelerating hardware design workflows" 3 | documentation = "https://github.com/getinstachip/vpm#readme" 4 | homepage = "https://getinstachip.com" 5 | repository = "https://github.com/getinstachip/vpm" 6 | name = "vpm" 7 | version = "0.2.18" 8 | edition = "2021" 9 | license = "MIT" 10 | copyright = "Copyright (c) 2024 Instachip" 11 | authors = ["Instachip "] 12 | 13 | [dependencies] 14 | clap = { version = "4.5.13", features = ["derive"] } 15 | tokio = { version = "1.39.2", features = ["full"] } 16 | openssl = { version = "0.10", features = ["vendored"] } 17 | reqwest = { version = "0.12.5", features = ["json", "blocking"] } 18 | serde = { version = "1.0.208", features = ["derive"] } 19 | tree-sitter-verilog = { git = "https://github.com/tree-sitter/tree-sitter-verilog" } 20 | tree-sitter = "0.20.6" 21 | anyhow = "1.0.86" 22 | serde_json = "1.0.125" 23 | walkdir = "2.5.0" 24 | once_cell = "1.19.0" 25 | tempfile = "3.12.0" 26 | cargo-lock = "9.0.0" 27 | fastrand = "2.1.1" 28 | fancy-regex = "0.13.0" 29 | dialoguer = "0.11.0" 30 | fuzzy-matcher = "0.3.7" 31 | indicatif = "0.17.8" 32 | which = "6.0.3" 33 | regex = "1.10.6" 34 | toml_edit = "0.22.20" 35 | imara-diff = "0.1.7" 36 | uuid = { version = "1.10.0", features = ["v7"] } 37 | directories = "5.0.1" 38 | ring = "0.17.8" 39 | base64 = "0.22.1" 40 | hex = "0.4.3" 41 | rand = "0.8.5" 42 | sha2 = "0.10.8" 43 | sys-info = "0.9.1" 44 | 45 | [build-dependencies] 46 | cc="*" 47 | -------------------------------------------------------------------------------- /src/cmd/upgrade.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use std::process::Command; 3 | 4 | use crate::cmd::{Execute, Upgrade}; 5 | use crate::config_man::set_version; 6 | 7 | impl Execute for Upgrade { 8 | async fn execute(&self) -> Result<()> { 9 | println!("Upgrading VPM..."); 10 | upgrade_vpm()?; 11 | let version = get_latest_version()?; 12 | if !version.is_empty() { 13 | set_version(&version)?; 14 | } 15 | 16 | println!("VPM upgrade completed successfully."); 17 | Ok(()) 18 | } 19 | } 20 | 21 | fn upgrade_vpm() -> Result<()> { 22 | if cfg!(unix) { 23 | let output = Command::new("sh") 24 | .arg("-c") 25 | .arg("curl -sSfL https://raw.githubusercontent.com/getinstachip/vpm-internal/main/install.sh | sh") 26 | .output()?; 27 | 28 | if !output.status.success() { 29 | return Err(anyhow::anyhow!("Upgrade command failed")); 30 | } 31 | } else if cfg!(windows) { 32 | println!("To upgrade VPM on Windows, please follow these steps:"); 33 | println!("1. Visit https://github.com/getinstachip/vpm/releases/latest"); 34 | println!("2. Download the appropriate .exe file for your system"); 35 | println!("3. Run the downloaded .exe file to complete the upgrade"); 36 | return Ok(()); 37 | } else { 38 | return Err(anyhow::anyhow!("Unsupported operating system")); 39 | } 40 | 41 | Ok(()) 42 | } 43 | 44 | fn get_latest_version() -> Result { 45 | let output = Command::new("git") 46 | .arg("describe ") 47 | .arg("--tags") 48 | .arg("--abbrev=0") 49 | .output()?; 50 | Ok(String::from_utf8(output.stdout)?) 51 | } -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod cmd; 2 | mod error; 3 | mod toml; 4 | mod config_man; 5 | 6 | use std::env; 7 | use std::io::{self, Write}; 8 | use std::process::ExitCode; 9 | use std::fs; 10 | 11 | use clap::Parser; 12 | 13 | use crate::cmd::{Cmd, Execute}; 14 | use crate::error::SilentExit; 15 | use crate::config_man::{get_config_path, create_config}; 16 | 17 | #[tokio::main] 18 | pub async fn main() -> ExitCode { 19 | // Forcibly disable backtraces. 20 | env::remove_var("RUST_LIB_BACKTRACE"); 21 | env::remove_var("RUST_BACKTRACE"); 22 | 23 | let flag_file = get_config_path().unwrap().with_file_name(".vpm_welcome_shown"); 24 | if !flag_file.exists() { 25 | create_config().unwrap(); 26 | 27 | println!("Welcome to vpm!"); 28 | println!("We collect anonymous usage data to improve the tool."); 29 | println!("The following information will be collected:"); 30 | println!(" - The version of vpm you are using"); 31 | println!(" - Which commands you run and when (not including arguments, input, or output)"); 32 | println!("No personal information will be collected."); 33 | println!("To opt-out, run `vpm config --analytics false`. You may change this at any time.\n"); 34 | println!("Rerun your command to accept and continue."); 35 | 36 | fs::write(flag_file, "").unwrap(); 37 | return ExitCode::SUCCESS; 38 | } 39 | 40 | match Cmd::parse().execute().await { 41 | Ok(()) => ExitCode::SUCCESS, 42 | Err(e) => match e.downcast::() { 43 | Ok(SilentExit { code }) => code.into(), 44 | Err(e) => { 45 | _ = writeln!(io::stderr(), "vpm: {e:?}"); 46 | ExitCode::FAILURE 47 | } 48 | }, 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/cmd/load.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use std::path::Path; 3 | use std::process::Command; 4 | 5 | use crate::cmd::{Execute, Load}; 6 | 7 | impl Execute for Load { 8 | async fn execute(&self) -> Result<()> { 9 | let top_module_path = Path::new(&self.top_module_path); 10 | let constraints_path = Path::new(&self.constraints_path); 11 | if self.riscv { 12 | load_xilinx(top_module_path, constraints_path)?; 13 | } else { 14 | unimplemented!("Non RISC-V loading not yet implemented"); 15 | } 16 | Ok(()) 17 | } 18 | } 19 | 20 | fn load_xilinx(edif_path: &Path, constraints_path: &Path) -> Result<()> { 21 | let edif_path_str = edif_path.to_str().unwrap(); 22 | let constraints_path_str = constraints_path.to_str().unwrap(); 23 | 24 | Command::new("yosys") 25 | .args(&["-p", &format!("read_edif {}; write_json design.json", edif_path_str)]) 26 | .status()?; 27 | 28 | Command::new("nextpnr-xilinx") 29 | .args(&[ 30 | "--chipdb", "vpm_modules/chipdb-xc7a35t.bin", 31 | "--xdc", constraints_path_str, 32 | "--json", "design.json", 33 | "--write", "output.fasm", 34 | "--device", "xc7a35tcsg324-1" 35 | ]) 36 | .status()?; 37 | 38 | let fasm_output = Command::new("fasm2frames") 39 | .args(&["--part", "xc7a35tcsg324-1", "output.fasm"]) 40 | .output()?; 41 | std::fs::write("output.frames", fasm_output.stdout)?; 42 | 43 | Command::new("xc7frames2bit") 44 | .args(&[ 45 | "--part_file", "vpm_modules/xc7a35tcsg324-1.yaml", 46 | "--part_name", "xc7a35tcsg324-1", 47 | "--frm_file", "output.frames", 48 | "--output_file", "output.bit" 49 | ]) 50 | .status()?; 51 | 52 | println!("Bitstream generated successfully: output.bit"); 53 | Ok(()) 54 | } -------------------------------------------------------------------------------- /src/cmd/update.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | 3 | use crate::cmd::{Execute, Update}; 4 | use crate::cmd::include::get_head_commit_hash; 5 | use crate::toml::{get_repo_links, add_top_module, remove_top_module}; 6 | use imara_diff::intern::InternedInput; 7 | use imara_diff::{diff, Algorithm, UnifiedDiffBuilder}; 8 | 9 | impl Execute for Update { 10 | async fn execute(&self) -> Result<()> { 11 | let module_path = &self.module_path; 12 | println!("Updating module '{}'", module_path); 13 | update_module(module_path, self.commit.as_deref()) 14 | } 15 | } 16 | 17 | fn update_module(module_path: &str, commit: Option<&str>) -> Result<()> { 18 | let repo_links = get_repo_links(module_path); 19 | if repo_links.is_empty() { 20 | return Err(anyhow::anyhow!("No repositories found for module '{}'", module_path)); 21 | } 22 | 23 | let chosen_repo = if repo_links.len() == 1 { 24 | repo_links.into_iter().next().unwrap() 25 | } else { 26 | println!("Multiple repositories found for module '{}'. Please choose one:", module_path); 27 | for (index, link) in repo_links.iter().enumerate() { 28 | println!("{}. {}", index + 1, link); 29 | } 30 | let mut choice = String::new(); 31 | std::io::stdin().read_line(&mut choice)?; 32 | let index: usize = choice.trim().parse()?; 33 | repo_links.into_iter().nth(index - 1) 34 | .ok_or_else(|| anyhow::anyhow!("Invalid choice"))? 35 | }; 36 | 37 | let head_commit_hash = get_head_commit_hash(&chosen_repo).unwrap(); 38 | let commit_hash = commit.unwrap_or(&head_commit_hash); 39 | 40 | println!("Updating module '{}' to commit '{}'", module_path, commit_hash); 41 | let old_contents = std::fs::read_to_string(module_path)?; 42 | remove_top_module(&chosen_repo, module_path)?; 43 | add_top_module(&chosen_repo, module_path, commit_hash)?; 44 | let new_contents = std::fs::read_to_string(module_path)?; 45 | println!("Module '{}' updated to commit '{}'", module_path, commit_hash); 46 | 47 | display_diff(&old_contents, &new_contents); 48 | 49 | Ok(()) 50 | } 51 | 52 | fn display_diff(old_contents: &str, new_contents: &str) { 53 | let input = InternedInput::new(old_contents, new_contents); 54 | let diff_output = diff( 55 | Algorithm::Histogram, 56 | &input, 57 | UnifiedDiffBuilder::new(&input) 58 | ); 59 | 60 | println!("Diff:\n{}", diff_output); 61 | } -------------------------------------------------------------------------------- /src/cmd/mod.rs: -------------------------------------------------------------------------------- 1 | mod cmd; 2 | mod upgrade; 3 | mod include; 4 | mod update; 5 | mod remove; 6 | mod dotf; 7 | mod list; 8 | mod install; 9 | mod sim; 10 | mod docs; 11 | mod synth; 12 | mod run; 13 | mod load; 14 | mod config; 15 | 16 | use anyhow::Result; 17 | 18 | pub use crate::cmd::cmd::*; 19 | 20 | use crate::config_man::send_event; 21 | 22 | pub trait Execute { 23 | async fn execute(&self) -> Result<()>; 24 | } 25 | 26 | 27 | impl Execute for Cmd { 28 | async fn execute(&self) -> Result<()> { 29 | match self { 30 | Cmd::Upgrade(cmd) => { 31 | cmd.execute().await?; 32 | send_event("upgrade".to_string()).await?; 33 | Ok(()) 34 | }, 35 | Cmd::Include(cmd) => { 36 | cmd.execute().await?; 37 | send_event("include".to_string()).await?; 38 | Ok(()) 39 | }, 40 | Cmd::Update(cmd) => { 41 | cmd.execute().await?; 42 | send_event("update".to_string()).await?; 43 | Ok(()) 44 | }, 45 | Cmd::Remove(cmd) => { 46 | cmd.execute().await?; 47 | send_event("remove".to_string()).await?; 48 | Ok(()) 49 | }, 50 | Cmd::Dotf(cmd) => { 51 | cmd.execute().await?; 52 | send_event("dotf".to_string()).await?; 53 | Ok(()) 54 | }, 55 | Cmd::Install(cmd) => { 56 | cmd.execute().await?; 57 | send_event("install".to_string()).await?; 58 | Ok(()) 59 | }, 60 | Cmd::List(cmd) => { 61 | cmd.execute().await?; 62 | send_event("list".to_string()).await?; 63 | Ok(()) 64 | }, 65 | Cmd::Sim(cmd) => { 66 | cmd.execute().await?; 67 | send_event("sim".to_string()).await?; 68 | Ok(()) 69 | }, 70 | Cmd::Docs(cmd) => { 71 | cmd.execute().await?; 72 | send_event("docs".to_string()).await?; 73 | Ok(()) 74 | }, 75 | Cmd::Synth(cmd) => { 76 | cmd.execute().await?; 77 | send_event("synth".to_string()).await?; 78 | Ok(()) 79 | }, 80 | Cmd::Load(cmd) => { 81 | cmd.execute().await?; 82 | send_event("load".to_string()).await?; 83 | Ok(()) 84 | }, 85 | Cmd::Run(cmd) => { 86 | cmd.execute().await?; 87 | send_event("run".to_string()).await?; 88 | Ok(()) 89 | }, 90 | Cmd::Config(cmd) => { 91 | cmd.execute().await?; 92 | send_event("config".to_string()).await?; 93 | Ok(()) 94 | }, 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/cmd/list.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Result, Context, anyhow}; 2 | use std::collections::HashSet; 3 | use std::process::Command; 4 | use crate::cmd::{Execute, List}; 5 | use tempfile::tempdir; 6 | 7 | const STD_LIB_URL: &str = "https://github.com/getinstachip/openchips"; 8 | 9 | impl Execute for List { 10 | async fn execute(&self) -> Result<()> { 11 | match list_verilog_files() { 12 | Ok(verilog_files) => { 13 | println!("Available Verilog modules:"); 14 | for file in verilog_files { 15 | println!(" {}", file); 16 | } 17 | Ok(()) 18 | } 19 | Err(e) => { 20 | eprintln!("Error: Failed to list Verilog files. {}", e); 21 | eprintln!("Debug steps:"); 22 | eprintln!("1. Check your internet connection"); 23 | eprintln!("2. Ensure git is installed and accessible from the command line"); 24 | eprintln!("3. Verify you have read permissions for the temporary directory"); 25 | Err(e) 26 | } 27 | } 28 | } 29 | } 30 | 31 | fn list_verilog_files() -> Result> { 32 | let temp_dir = tempdir().context("Failed to create temporary directory. Ensure you have write permissions in the system temp directory.")?; 33 | let repo_path = temp_dir.path(); 34 | 35 | // Clone the repository 36 | let output = Command::new("git") 37 | .args([ 38 | "clone", 39 | "--depth", 40 | "1", 41 | "--single-branch", 42 | "--jobs", 43 | "4", 44 | STD_LIB_URL, 45 | repo_path.to_str().unwrap_or_default(), 46 | ]) 47 | .output() 48 | .context("Failed to execute git command. Ensure git is installed and accessible from the command line.")?; 49 | 50 | if !output.status.success() { 51 | return Err(anyhow!( 52 | "Git clone failed. Error: {}. This could be due to network issues, incorrect repository URL, or git configuration problems.", 53 | String::from_utf8_lossy(&output.stderr) 54 | )); 55 | } 56 | 57 | let mut verilog_files = HashSet::new(); 58 | 59 | for entry in walkdir::WalkDir::new(repo_path) 60 | .into_iter() 61 | .filter_map(|e| e.ok()) 62 | { 63 | if let Some(extension) = entry.path().extension() { 64 | match extension.to_str() { 65 | Some("v") | Some("sv") => { 66 | if let Some(file_name) = entry.path().file_stem() { 67 | verilog_files.insert(file_name.to_string_lossy().into_owned()); 68 | } 69 | } 70 | _ => {} 71 | } 72 | } 73 | } 74 | 75 | if verilog_files.is_empty() { 76 | Err(anyhow!("No Verilog files found in the repository. This could indicate an issue with the repository structure or content.")) 77 | } else { 78 | Ok(verilog_files.into_iter().collect()) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/cmd/remove.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::path::PathBuf; 3 | use std::io::{self, Write}; 4 | 5 | use anyhow::{Result, anyhow}; 6 | 7 | use crate::cmd::{Execute, Remove}; 8 | use crate::toml::{remove_top_module, get_repo_links}; 9 | 10 | impl Execute for Remove { 11 | async fn execute(&self) -> Result<()> { 12 | remove_module(&self.package_path)?; 13 | Ok(()) 14 | } 15 | } 16 | 17 | fn remove_module(module_path: &str) -> Result<()> { 18 | let module_path = PathBuf::from(module_path); 19 | if !module_path.exists() { 20 | return Err(anyhow!("Module not found: {}", module_path.display())); 21 | } 22 | 23 | let module_name = module_path.file_name().unwrap().to_str().unwrap(); 24 | 25 | // Ask for y/n confirmation 26 | print!("Are you sure you want to remove the module {}? (y/n): ", module_name); 27 | io::stdout().flush()?; 28 | let mut confirmation = String::new(); 29 | io::stdin().read_line(&mut confirmation)?; 30 | if confirmation.trim().to_lowercase() != "y" { 31 | return Ok(()); 32 | } 33 | 34 | let repo_links = get_repo_links(module_name); 35 | 36 | let repo_link = match repo_links.len() { 37 | 0 => return Err(anyhow!("No repository links found for module: {}", module_name)), 38 | 1 => repo_links.into_iter().next().unwrap(), 39 | _ => { 40 | println!("Multiple repository links found for module: {}. Please choose the correct repository link.", module_name); 41 | for (i, link) in repo_links.iter().enumerate() { 42 | println!("{}. {}", i + 1, link); 43 | } 44 | 45 | let mut choice = String::new(); 46 | print!("Enter your choice (1-{}): ", repo_links.len()); 47 | io::stdout().flush()?; 48 | io::stdin().read_line(&mut choice)?; 49 | let index: usize = choice.trim().parse().map_err(|_| anyhow!("Invalid choice"))?; 50 | 51 | if index < 1 || index > repo_links.len() { 52 | return Err(anyhow!("Invalid choice")); 53 | } 54 | repo_links.iter().nth(index - 1) 55 | .ok_or_else(|| anyhow!("Invalid choice"))? 56 | .to_string() 57 | } 58 | }; 59 | 60 | // Ask to enter the name of the module to confirm 61 | print!("To confirm removal, please re-type \"{}\" (without the quotes): ", module_name); 62 | io::stdout().flush()?; 63 | let mut confirmation_name = String::new(); 64 | io::stdin().read_line(&mut confirmation_name)?; 65 | if confirmation_name.trim() != module_name { 66 | return Err(anyhow!("Module name does not match. Removal cancelled.")); 67 | } 68 | 69 | fs::remove_file(&module_path)?; 70 | // Remove the corresponding header file if it exists 71 | let header_path = module_path.with_extension("vh"); 72 | if header_path.exists() { 73 | fs::remove_file(&header_path)?; 74 | println!("Removed header file: {}", header_path.display()); 75 | } 76 | remove_top_module(&repo_link, module_name)?; 77 | println!("Removed module: {}", module_path.display()); 78 | 79 | Ok(()) 80 | } 81 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | 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. 4 | 5 | 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. 6 | 7 | ## Getting started 8 | 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. 9 | 10 | _For something that is bigger than a one or two line fix:_ 11 | 1. Create your own fork of the code 12 | 2. Do the changes in your fork (be sure to follow the code style of the project) 13 | 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) 14 | 15 | _For small or "obvious" fixes..._ 16 | * 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 17 | 18 | **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: 19 | - Spelling / grammar fixes 20 | - Typo correction, white space and formatting changes 21 | - Comment clean up 22 | - Bug fixes that change default return values or error codes stored in constants 23 | - Adding logging messages or debugging output 24 | - Changes to ‘metadata’ files like Gemfile, .gitignore, build scripts, etc. 25 | - Moving source files from one directory or package to another 26 | 27 | ## How to report a bug 28 | **Security Disclosure** 29 | 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. 30 | In order to determine whether you are dealing with a security issue, ask yourself these two questions: 31 | * Can I access something that's not mine, or something I shouldn't have access to? 32 | * Can I disable something for other people? 33 | 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. 34 | 35 | ## Code review process 36 | 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. 37 | 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). 38 | 39 | ## Philosophy 40 | Our philosophy is to provide robust tooling to make chip design as intuitive as possible. 41 | 42 | 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. 43 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: 3 | push: 4 | workflow_dispatch: 5 | env: 6 | CARGO_INCREMENTAL: 0 7 | permissions: 8 | contents: write 9 | jobs: 10 | release: 11 | name: ${{ matrix.target }} 12 | runs-on: ${{ matrix.os }} 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | include: 17 | - os: ubuntu-latest 18 | target: x86_64-unknown-linux-musl 19 | deb: true 20 | - os: ubuntu-latest 21 | target: arm-unknown-linux-musleabihf 22 | - os: ubuntu-latest 23 | target: armv7-unknown-linux-musleabihf 24 | deb: true 25 | - os: ubuntu-latest 26 | target: aarch64-unknown-linux-musl 27 | deb: true 28 | - os: ubuntu-latest 29 | target: i686-unknown-linux-musl 30 | deb: true 31 | - os: ubuntu-latest 32 | target: aarch64-linux-android 33 | - os: macos-latest 34 | target: x86_64-apple-darwin 35 | - os: macos-latest 36 | target: aarch64-apple-darwin 37 | - os: windows-latest 38 | target: x86_64-pc-windows-msvc 39 | - os: windows-latest 40 | target: i686-pc-windows-msvc 41 | - os: windows-latest 42 | target: aarch64-pc-windows-msvc 43 | steps: 44 | - name: Checkout repository 45 | uses: actions/checkout@v4 46 | with: 47 | fetch-depth: 0 48 | 49 | - name: Get version 50 | id: get_version 51 | uses: SebRollen/toml-action@v1.2.0 52 | with: 53 | file: Cargo.toml 54 | field: package.version 55 | 56 | - name: Install Rust 57 | uses: actions-rs/toolchain@v1 58 | with: 59 | toolchain: stable 60 | profile: minimal 61 | override: true 62 | target: ${{ matrix.target }} 63 | 64 | - name: Setup cache 65 | uses: Swatinem/rust-cache@v2.7.3 66 | with: 67 | key: ${{ matrix.target }} 68 | 69 | - name: Install cross 70 | if: ${{ runner.os == 'Linux' }} 71 | uses: actions-rs/cargo@v1 72 | with: 73 | command: install 74 | args: --color=always --git=https://github.com/cross-rs/cross.git --locked --rev=02bf930e0cb0c6f1beffece0788f3932ecb2c7eb --verbose cross 75 | 76 | - name: Build binary 77 | uses: actions-rs/cargo@v1 78 | env: 79 | POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }} 80 | DOCS_KEY: ${{ secrets.DOCS_KEY }} 81 | with: 82 | command: build 83 | args: --release --target=${{ matrix.target }} --color=always --verbose 84 | use-cross: ${{ runner.os == 'Linux' }} 85 | 86 | - name: Install cargo-deb 87 | if: ${{ matrix.deb == true }} 88 | uses: actions-rs/install@v0.1 89 | with: 90 | crate: cargo-deb 91 | 92 | - name: Build deb 93 | if: ${{ matrix.deb == true }} 94 | uses: actions-rs/cargo@v1 95 | with: 96 | command: deb 97 | args: --no-build --no-strip --output=. --target=${{ matrix.target }} 98 | 99 | - name: Package (*nix) 100 | if: ${{ runner.os != 'Windows' }} 101 | run: | 102 | tar -cv LICENSE README.md \ 103 | -C target/${{ matrix.target }}/release/ vpm | 104 | gzip --best > \ 105 | vpm-${{ steps.get_version.outputs.value }}-${{ matrix.target }}.tar.gz 106 | 107 | - name: Create variable files 108 | if: ${{ runner.os == 'Windows' }} 109 | run: | 110 | echo "${{ steps.get_version.outputs.value }}" > version.txt 111 | echo "${{ matrix.target }}" >> target.txt 112 | 113 | - name: Package (Windows) 114 | if: ${{ runner.os == 'Windows' }} 115 | uses: Minionguyjpro/Inno-Setup-Action@v1.2.4 116 | with: 117 | path: installer.iss 118 | options: /O+ 119 | 120 | - name: Upload artifact 121 | uses: actions/upload-artifact@v4 122 | with: 123 | name: ${{ matrix.target }} 124 | path: | 125 | *.deb 126 | *.tar.gz 127 | *.zip 128 | *.exe 129 | 130 | - name: Create release 131 | if: | 132 | github.ref == 'refs/heads/main' && startsWith(github.event.head_commit.message, 'chore(release)') 133 | uses: softprops/action-gh-release@v2 134 | with: 135 | draft: true 136 | files: | 137 | *.deb 138 | *.tar.gz 139 | *.zip 140 | *.exe 141 | name: ${{ steps.get_version.outputs.value }} 142 | tag_name: '' 143 | -------------------------------------------------------------------------------- /src/cmd/synth.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Result, Context}; 2 | use std::path::PathBuf; 3 | use std::process::Command; 4 | use std::fs::File; 5 | use std::io::Write; 6 | 7 | use crate::cmd::{Execute, Synth}; 8 | 9 | impl Execute for Synth { 10 | async fn execute(&self) -> Result<()> { 11 | synthesize_design( 12 | &self.top_module_path, 13 | self.riscv, 14 | self.core_path.as_ref(), 15 | &self.board, 16 | self.gen_yosys_script 17 | ) 18 | } 19 | } 20 | 21 | fn synthesize_design( 22 | top_module_path: &str, 23 | riscv: bool, 24 | core_path: Option<&String>, 25 | board: &Option, 26 | gen_yosys_script: bool 27 | ) -> Result<()> { 28 | let top_module_path = PathBuf::from(top_module_path); 29 | let (input_file, module_name, parent_dir, _) = extract_path_info(&top_module_path); 30 | 31 | let script_content = match board { 32 | Some(board) if board.to_lowercase() == "xilinx" => { 33 | let board_name = "artix7"; 34 | let output_file = format!("{}/{}_{}_{}_synth.v", parent_dir, module_name, board_name, "xilinx"); 35 | generate_xilinx_script_content(&input_file, riscv, core_path.cloned(), &module_name, &output_file)? 36 | }, 37 | None => { 38 | let output_file = format!("{}/{}_synth.v", parent_dir, module_name); 39 | generate_yosys_script_content(&input_file, &module_name, &output_file) 40 | }, 41 | Some(other) => { 42 | return Err(anyhow::anyhow!("Unsupported board: {}", other)); 43 | } 44 | }; 45 | 46 | if gen_yosys_script { 47 | let script_file = PathBuf::from(&parent_dir).join(format!("{}_synth_script.ys", module_name)); 48 | write_script_to_file(&script_file, &script_content)?; 49 | println!("Yosys script generated at: {:?}", script_file); 50 | } 51 | 52 | run_yosys_with_script_content(&script_content)?; 53 | println!("Synthesis completed successfully."); 54 | Ok(()) 55 | } 56 | 57 | fn extract_path_info(top_module_path: &PathBuf) -> (String, String, String, String) { 58 | let input_file = top_module_path.to_str().unwrap().to_string(); 59 | let top_module = top_module_path.file_stem().unwrap().to_str().unwrap().to_string(); 60 | let parent_dir = top_module_path.parent().unwrap().to_string_lossy().to_string(); 61 | let output_file = format!("{}/{}_synth.v", parent_dir, top_module); 62 | (input_file, top_module, parent_dir, output_file) 63 | } 64 | 65 | fn generate_yosys_script_content(input_file: &str, top_module: &str, output_file: &str) -> String { 66 | format!( 67 | r#" 68 | # Read the Verilog file 69 | read_verilog {} 70 | 71 | # Synthesize the design 72 | synth -top {} 73 | 74 | # Optimize the design 75 | opt 76 | 77 | # Write the synthesized design 78 | write_verilog {} 79 | "#, 80 | input_file, 81 | top_module, 82 | output_file 83 | ) 84 | } 85 | 86 | fn generate_xilinx_script_content(top_module_path_str: &str, riscv: bool, core_path: Option, module_name: &str, output_file: &str) -> Result { 87 | let mut script_content = format!( 88 | r#" 89 | # Read the SystemVerilog file 90 | read_verilog -sv {top_module_path_str} 91 | "# 92 | ); 93 | 94 | if riscv { 95 | if let Some(core_path) = core_path { 96 | script_content.push_str(&format!( 97 | r#" 98 | # Read the RISC-V core 99 | read_verilog -sv {core_path} 100 | "# 101 | )); 102 | } else { 103 | return Err(anyhow::anyhow!("RISC-V core path is required when riscv flag is set")); 104 | } 105 | } 106 | 107 | script_content.push_str(&format!( 108 | r#" 109 | # Synthesize for Xilinx 7 series (Artix-7) 110 | synth_xilinx -top {module_name} -family xc7 111 | 112 | # Optimize the design 113 | opt 114 | 115 | # Map to Xilinx 7 series cells 116 | abc -lut 6 117 | 118 | # Clean up 119 | clean 120 | 121 | # Write the synthesized design to a Verilog file 122 | write_verilog {output_file} 123 | 124 | write_edif {output_file}.edif 125 | 126 | # Print statistics 127 | stat 128 | "# 129 | )); 130 | 131 | Ok(script_content) 132 | } 133 | 134 | fn write_script_to_file(script_file: &PathBuf, script_content: &str) -> Result<()> { 135 | let mut file = File::create(script_file)?; 136 | file.write_all(script_content.as_bytes())?; 137 | Ok(()) 138 | } 139 | 140 | fn run_yosys_with_script_content(script_content: &str) -> Result<()> { 141 | let output = Command::new("yosys") 142 | .arg("-p") 143 | .arg(script_content) 144 | .output() 145 | .context("Failed to execute Yosys")?; 146 | 147 | println!("Yosys output:"); 148 | println!("{}", String::from_utf8_lossy(&output.stdout)); 149 | 150 | if !output.status.success() { 151 | let error_message = String::from_utf8_lossy(&output.stderr); 152 | return Err(anyhow::anyhow!("Yosys synthesis failed: {}", error_message)); 153 | } 154 | 155 | Ok(()) 156 | } -------------------------------------------------------------------------------- /installer.iss: -------------------------------------------------------------------------------- 1 | #define VerFile = FileOpen("version.txt") 2 | #define MyAppVersion = FileRead(VerFile) 3 | #expr FileClose(VerFile) 4 | #undef VerFile 5 | 6 | #define TarFile FileOpen("target.txt") 7 | #define MyTarget FileRead(TarFile) 8 | #expr FileClose(TarFile) 9 | #undef TarFile 10 | 11 | #define MyAppName "VPM" 12 | #define MyAppPublisher "Instachip" 13 | #define MyAppURL "https://getinstachip.com/" 14 | 15 | [Setup] 16 | AppId={{E3D813B5-C9DB-4FC0-957C-9D06371B378E} 17 | AppName={#MyAppName} 18 | AppVersion={#MyAppVersion} 19 | AppPublisher={#MyAppPublisher} 20 | AppPublisherURL={#MyAppURL} 21 | AppSupportURL={#MyAppURL} 22 | AppUpdatesURL={#MyAppURL} 23 | CreateAppDir=yes 24 | PrivilegesRequired=lowest 25 | PrivilegesRequiredOverridesAllowed=dialog 26 | Compression=lzma 27 | SolidCompression=yes 28 | WizardStyle=modern 29 | DefaultDirName={autopf}\{#MyAppName} 30 | DisableDirPage=no 31 | DirExistsWarning=no 32 | OutputBaseFilename=vpm-installer-{#MyAppVersion}-{#MyTarget} 33 | OutputDir=. 34 | 35 | [Languages] 36 | Name: "english"; MessagesFile: "compiler:Default.isl" 37 | Name: "armenian"; MessagesFile: "compiler:Languages\Armenian.isl" 38 | Name: "brazilianportuguese"; MessagesFile: "compiler:Languages\BrazilianPortuguese.isl" 39 | Name: "bulgarian"; MessagesFile: "compiler:Languages\Bulgarian.isl" 40 | Name: "catalan"; MessagesFile: "compiler:Languages\Catalan.isl" 41 | Name: "corsican"; MessagesFile: "compiler:Languages\Corsican.isl" 42 | Name: "czech"; MessagesFile: "compiler:Languages\Czech.isl" 43 | Name: "danish"; MessagesFile: "compiler:Languages\Danish.isl" 44 | Name: "dutch"; MessagesFile: "compiler:Languages\Dutch.isl" 45 | Name: "finnish"; MessagesFile: "compiler:Languages\Finnish.isl" 46 | Name: "french"; MessagesFile: "compiler:Languages\French.isl" 47 | Name: "german"; MessagesFile: "compiler:Languages\German.isl" 48 | Name: "hebrew"; MessagesFile: "compiler:Languages\Hebrew.isl" 49 | Name: "hungarian"; MessagesFile: "compiler:Languages\Hungarian.isl" 50 | Name: "icelandic"; MessagesFile: "compiler:Languages\Icelandic.isl" 51 | Name: "italian"; MessagesFile: "compiler:Languages\Italian.isl" 52 | Name: "japanese"; MessagesFile: "compiler:Languages\Japanese.isl" 53 | Name: "korean"; MessagesFile: "compiler:Languages\Korean.isl" 54 | Name: "norwegian"; MessagesFile: "compiler:Languages\Norwegian.isl" 55 | Name: "polish"; MessagesFile: "compiler:Languages\Polish.isl" 56 | Name: "portuguese"; MessagesFile: "compiler:Languages\Portuguese.isl" 57 | Name: "russian"; MessagesFile: "compiler:Languages\Russian.isl" 58 | Name: "slovak"; MessagesFile: "compiler:Languages\Slovak.isl" 59 | Name: "slovenian"; MessagesFile: "compiler:Languages\Slovenian.isl" 60 | Name: "spanish"; MessagesFile: "compiler:Languages\Spanish.isl" 61 | Name: "turkish"; MessagesFile: "compiler:Languages\Turkish.isl" 62 | Name: "ukrainian"; MessagesFile: "compiler:Languages\Ukrainian.isl" 63 | 64 | [Files] 65 | Source: "target\{#MyTarget}\release\vpm.exe"; DestDir: "{app}"; Flags: ignoreversion 66 | Source: "README.md"; DestDir: "{app}"; Flags: ignoreversion 67 | 68 | [Tasks] 69 | Name: addtopath; Description: "Add application directory to PATH"; Flags: checkedonce 70 | 71 | [Code] 72 | const 73 | ModPathName = 'modifypath'; 74 | ModPathType = 'user'; 75 | 76 | function ModPathDir(): TArrayOfString; 77 | begin 78 | SetArrayLength(Result, 1); 79 | Result[0] := ExpandConstant('{app}'); 80 | end; 81 | 82 | procedure ModPath(); 83 | var 84 | oldpath: string; 85 | newpath: string; 86 | updatepath: boolean; 87 | begin 88 | if not RegQueryStringValue(HKEY_CURRENT_USER, 'Environment', 'Path', oldpath) then 89 | oldpath := ''; 90 | 91 | updatepath := true; 92 | 93 | if (Pos(';' + UpperCase(ExpandConstant('{app}')) + ';', ';' + UpperCase(oldpath) + ';') > 0) then 94 | updatepath := false 95 | else if (Pos(';' + UpperCase(ExpandConstant('{app}')) + '\;', ';' + UpperCase(oldpath) + ';') > 0) then 96 | updatepath := false; 97 | 98 | if (updatepath) then begin 99 | newpath := oldpath; 100 | if (Pos(';', oldpath) > 0) then 101 | newpath := newpath + ';' + ExpandConstant('{app}') 102 | else 103 | newpath := ExpandConstant('{app}'); 104 | 105 | if RegWriteStringValue(HKEY_CURRENT_USER, 'Environment', 'Path', newpath) then 106 | begin 107 | StringChangeEx(oldpath, ';', #13#10, True); 108 | StringChangeEx(newpath, ';', #13#10, True); 109 | Log('Old PATH:' + #13#10 + oldpath); 110 | Log('New PATH:' + #13#10 + newpath); 111 | RegWriteStringValue(HKEY_CURRENT_USER, 'Environment', 'Path', newpath); 112 | end else 113 | Log('Error: Failed to modify PATH'); 114 | end; 115 | end; 116 | 117 | procedure CurStepChanged(CurStep: TSetupStep); 118 | begin 119 | if (CurStep = ssPostInstall) and WizardIsTaskSelected('addtopath') then 120 | ModPath(); 121 | end; 122 | 123 | procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); 124 | var 125 | oldpath: string; 126 | newpath: string; 127 | pathArr: TArrayOfString; 128 | i: Integer; 129 | begin 130 | if (CurUninstallStep = usUninstall) then begin 131 | if RegQueryStringValue(HKEY_CURRENT_USER, 'Environment', 'Path', oldpath) then begin 132 | oldpath := oldpath + ';'; 133 | i := 0; 134 | while (Pos(';', oldpath) > 0) do begin 135 | SetArrayLength(pathArr, i + 1); 136 | pathArr[i] := Copy(oldpath, 0, Pos(';', oldpath) - 1); 137 | oldpath := Copy(oldpath, Pos(';', oldpath) + 1, Length(oldpath)); 138 | i := i + 1; 139 | 140 | if (pathArr[i - 1] <> ExpandConstant('{app}')) then begin 141 | if (newpath = '') then 142 | newpath := pathArr[i - 1] 143 | else 144 | newpath := newpath + ';' + pathArr[i - 1]; 145 | end; 146 | end; 147 | 148 | RegWriteStringValue(HKEY_CURRENT_USER, 'Environment', 'Path', newpath); 149 | end; 150 | end; 151 | end; 152 | 153 | [UninstallDelete] 154 | Type: filesandordirs; Name: "{app}" -------------------------------------------------------------------------------- /src/toml.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::fs::{OpenOptions, read_to_string}; 3 | use std::io::Write; 4 | use std::path::Path; 5 | use std::collections::HashSet; 6 | use anyhow::Result; 7 | use toml_edit::{Array, DocumentMut, InlineTable, Item, Table, Value}; 8 | 9 | 10 | #[derive(Serialize, Deserialize, Debug)] 11 | struct Package { 12 | name: String, 13 | version: String, 14 | authors: Vec, 15 | description: String, 16 | license: String, 17 | } 18 | 19 | #[derive(Debug)] 20 | struct VpmToml { 21 | toml_doc: DocumentMut, 22 | } 23 | 24 | impl Default for Package { 25 | fn default() -> Self { 26 | Package { 27 | name: "my-vpm-package".to_string(), 28 | version: "0.1.0".to_string(), 29 | authors: vec![" ".to_string()], 30 | description: "A vpm package".to_string(), 31 | license: "LicenseRef-LICENSE".to_string(), 32 | } 33 | } 34 | } 35 | 36 | impl VpmToml { 37 | pub fn from(filepath: &str) -> Self { 38 | if !Path::new(filepath).exists() { 39 | let mut initial_doc = DocumentMut::new(); 40 | initial_doc["package"] = Item::Table(Table::new()); 41 | initial_doc["package"]["name"] = Item::Value(Value::from(Package::default().name)); 42 | initial_doc["package"]["version"] = Item::Value(Value::from(Package::default().version)); 43 | initial_doc["package"]["authors"] = Item::Value(Value::from(Array::from(Package::default().authors.iter().map(|s| Value::from(s.to_string())).collect()))); 44 | initial_doc["package"]["description"] = Item::Value(Value::from(Package::default().description)); 45 | initial_doc["package"]["license"] = Item::Value(Value::from(Package::default().license)); 46 | 47 | initial_doc["dependencies"] = Item::Table(Table::new()); 48 | 49 | let mut file = OpenOptions::new() 50 | .write(true) 51 | .create(true) 52 | .truncate(true) 53 | .open(filepath) 54 | .expect("Failed to create vpm.toml"); 55 | file.write_all(initial_doc.to_string().as_bytes()).expect("Failed to write to vpm.toml"); 56 | } 57 | 58 | let toml_content = read_to_string(filepath).expect("Failed to read vpm.toml"); 59 | Self { 60 | toml_doc: toml_content.parse::().expect("Failed to parse vpm.toml") 61 | } 62 | } 63 | 64 | pub fn get_dependencies(&self) -> Option<&Table> { 65 | self.toml_doc["dependencies"].as_table() 66 | } 67 | 68 | pub fn add_dependency(&mut self, git: &str) { 69 | self.toml_doc["dependencies"][git] = Item::Value(Value::Array(Array::new())); 70 | } 71 | 72 | pub fn add_top_module(&mut self, repo_link: &str, module_name: &str, commit: &str) { 73 | let array = self.toml_doc["dependencies"][repo_link].as_array_mut().unwrap(); 74 | if !array.iter().any(|m| m.as_inline_table().unwrap().get("top_module").unwrap().as_str().unwrap() == module_name) { 75 | let new_entry = Value::InlineTable({ 76 | let mut table = InlineTable::new(); 77 | table.insert("top_module".to_string(), Value::from(module_name)); 78 | table.insert("commit_hash".to_string(), Value::from(commit.to_string())); 79 | table 80 | }); 81 | array.push(new_entry); 82 | } 83 | } 84 | 85 | pub fn remove_dependency(&mut self, git: &str) { 86 | if let Some(dependencies) = self.toml_doc["dependencies"].as_table_mut() { 87 | dependencies.remove(git); 88 | } 89 | } 90 | 91 | pub fn remove_top_module(&mut self, repo_link: &str, module_name: &str) { 92 | if let Some(dependencies) = self.toml_doc["dependencies"].as_table_mut() { 93 | if let Some(modules) = dependencies.get_mut(repo_link).and_then(|v| v.as_array_mut()) { 94 | modules.retain(|m| { 95 | if let Some(table) = m.as_inline_table() { 96 | if let Some(top_module) = table.get("top_module").and_then(|v| v.as_str()) { 97 | return top_module != module_name; 98 | } 99 | } 100 | true 101 | }); 102 | 103 | // If the array is empty after removal, remove the entire dependency 104 | if modules.is_empty() { 105 | dependencies.remove(repo_link); 106 | } 107 | } 108 | } 109 | } 110 | 111 | pub fn write_to_file(&self, filepath: &str) -> Result<()> { 112 | let toml_content = self.toml_doc.to_string(); 113 | let mut formatted_content = String::new(); 114 | for line in toml_content.lines() { 115 | if !line.trim().contains("}, ") { 116 | formatted_content.push_str(line); 117 | } else { 118 | let indent_level = line.chars().take_while(|&c| c != '{').count(); 119 | formatted_content.push_str(&line.replace("}, ", &format!("}},\n{}", " ".repeat(indent_level)))); 120 | } 121 | formatted_content.push('\n'); 122 | } 123 | 124 | let mut file = OpenOptions::new() 125 | .write(true) 126 | .create(true) 127 | .truncate(true) 128 | .open(filepath) 129 | .expect("Failed to open vpm.toml"); 130 | file.write_all(formatted_content.as_bytes()).expect("Failed to write to vpm.toml"); 131 | Ok(()) 132 | } 133 | 134 | pub fn get_repo_links(&self, module_name: &str) -> HashSet { 135 | let mut repo_links = HashSet::new(); 136 | if let Some(dependencies) = self.toml_doc["dependencies"].as_table() { 137 | for (repo_link, dependency) in dependencies.iter() { 138 | if let Some(top_modules) = dependency.as_array() { 139 | if top_modules.iter().any(|m| m.as_inline_table().unwrap().get("top_module").unwrap().as_str().unwrap() == module_name) { 140 | repo_links.insert(repo_link.to_string()); 141 | } 142 | } 143 | } 144 | } 145 | repo_links 146 | } 147 | } 148 | 149 | pub fn add_dependency(git: &str) -> Result<()> { 150 | let mut vpm_toml = VpmToml::from("vpm.toml"); 151 | if !vpm_toml.get_dependencies().unwrap().contains_key(git) { 152 | vpm_toml.add_dependency(git); 153 | vpm_toml.write_to_file("vpm.toml")?; 154 | } 155 | Ok(()) 156 | } 157 | 158 | pub fn add_top_module(repo_link: &str, module_path: &str, commit: &str) -> Result<()> { 159 | let mut vpm_toml = VpmToml::from("vpm.toml"); 160 | vpm_toml.add_top_module(repo_link, module_path, commit); 161 | vpm_toml.write_to_file("vpm.toml")?; 162 | Ok(()) 163 | } 164 | 165 | fn remove_dependency(git: &str) -> Result<()> { 166 | let mut vpm_toml = VpmToml::from("vpm.toml"); 167 | vpm_toml.remove_dependency(git); 168 | vpm_toml.write_to_file("vpm.toml")?; 169 | Ok(()) 170 | } 171 | 172 | pub fn remove_top_module(repo_link: &str, module_name: &str) -> Result<()> { 173 | let mut vpm_toml = VpmToml::from("vpm.toml"); 174 | vpm_toml.remove_top_module(repo_link, module_name); 175 | if let Some(dependencies) = vpm_toml.toml_doc["dependencies"].as_table() { 176 | if let Some(modules) = dependencies.get(repo_link).and_then(|v| v.as_array()) { 177 | if modules.is_empty() { 178 | remove_dependency(repo_link)?; 179 | } 180 | } 181 | } 182 | vpm_toml.write_to_file("vpm.toml")?; 183 | Ok(()) 184 | } 185 | 186 | pub fn get_repo_links(module_name: &str) -> HashSet { 187 | let vpm_toml = VpmToml::from("vpm.toml"); 188 | vpm_toml.get_repo_links(module_name) 189 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Verilog Package Manager (VPM) 2 | 3 | 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. 4 | 5 | ## Features 6 | 7 | - **Module Management**: Easily include, update, and remove modules in your project. 8 | - **Documentation Generation**: Automatically create comprehensive documentation for your Verilog modules. 9 | - **Dependency Handling**: Manage project dependencies with ease. 10 | - **Simulation Support**: Simulate your Verilog files directly through VPM. 11 | - **Tool Integration**: Seamlessly install and set up open-source tools for your project. 12 | - **File Generation**: Automatically generate necessary files like .f, .svh, .xcd, and .tcl. 13 | 14 | ## Installation 15 | 16 | VPM is designed for easy installation with no additional dependencies. 17 | 18 | ### Default Installation (Linux/MacOS): 19 | ```bash 20 | curl -f https://getinstachip.com/install.sh | sh 21 | ``` 22 | 23 | ### Default Installation (Windows): 24 | 1. Download the `.zip` file matching your Windows architecture from the [latest release page](https://github.com/getinstachip/vpm/releases/latest) 25 | 2. Extract and run the `.exe` file 26 | 27 | If installation doesn't work, try the following: 28 | 29 | ### Linux alternative: 30 | We support Snap 31 | 32 | ```bash 33 | snap download instachip-vpm 34 | alias vpm='instachip-vpm.vpm' 35 | ``` 36 | 37 | ### MacOS alternative: 38 | ```bash 39 | brew tap getinstachip/vpm 40 | brew install vpm 41 | ``` 42 | 43 | After installation, the vpm command will be available in any terminal. 44 | 45 | ## Commands 46 | 47 | - `vpm include `: Include any module from a repo (and all its submodules). 48 | - `vpm docs `: Generate documentation for any module (highlighting bugs and edge cases) 49 | - `vpm install `: Auto-integrate an open-source tool without manual setup 50 | - `vpm update `: Update module to the latest version 51 | - `vpm remove `: Remove a module from your project 52 | - `vpm list`: List all modules in our standard library 53 | - `vpm dotf `: Generate a `.f` filelist when exporting your project 54 | - `vpm sim `: Simulate Verilog module using iVerilog 55 | 56 | ### vpm include 57 | Include a module or repository in your project. 58 | 59 | This command: 60 | - Downloads the specified module or repository 61 | - Analyzes the module hierarchy 62 | - Includes all necessary submodules and generates appropriate header files 63 | - Updates the vpm.toml file with new module details 64 | 65 | This command comes in two forms: 66 | 1. Include a module and all its submodules: 67 | ```bash 68 | vpm include 69 | ``` 70 | `URL_TO_TOP_MODULE`: Full GitHub URL to the top module to include. The URL should come in the format of `https://github.com///blob/branch/`. 71 | 72 | Example: 73 | ```bash 74 | vpm include https://github.com/ZipCPU/zipcpu/blob/master/rtl/core/prefetch.v 75 | ``` 76 | 77 | ![](https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExY3Jmbmw0NWlva3F2bHdyY2h0NGZwNGlvNXRjZTY2bXB4ODRzOXd6eiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/KwHCr2ifmIZzSkpfjv/giphy.gif) 78 | 79 | 2. Include a repository: 80 | ```bash 81 | vpm include --repo 82 | ``` 83 | 84 | Press tab to select multiple modules and press ENTER to install. If no modules are selected, all modules in the repository will be installed. 85 | 86 | Example: 87 | ```bash 88 | vpm include --repo ZipCPU/zipcpu 89 | ``` 90 | ![](https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExMG5uaHJ1N2twd2JiY2pucjlwbjNjNm02NjRycDlocDF5bnB2eHNvYiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/QJ2sIDYIftEgu5uNAg/giphy.gif) 91 | ### vpm docs 92 | Generate comprehensive documentation for a module. 93 | 94 | This command generates a Markdown README file containing: 95 | - Overview and module description 96 | - Pinout diagram 97 | - Table of ports 98 | - Table of parameters 99 | - Important implementation details 100 | - Simulation output and GTKWave waveform details (Coming soon!) 101 | - List of any major bugs or caveats if they exist 102 | 103 | ```bash 104 | vpm docs 105 | ``` 106 | 107 | ``: Name of the module to generate documentation for. Include the file extension. 108 | 109 | `[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. 110 | 111 | Examples: 112 | ```bash 113 | vpm docs pfcache.v 114 | vpm docs pfcache.v https://github.com/ZipCPU/zipcpu 115 | ``` 116 | ![](https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExOXc5NWpmYnV5eGxtYzRud2tid3poYTZyYXEwdmpqaGF3MjZwdW5leiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/C8nFHwNq0qBpXRF9pP/giphy.gif) 117 | 118 | ### vpm update 119 | Update a package to the latest version. 120 | 121 | This command: 122 | - Checks for the latest version of the specified module 123 | - Downloads and replaces the current version with the latest 124 | - Updates all dependencies and submodules 125 | - Modifies the vpm.toml file to reflect the changes 126 | 127 | ```bash 128 | vpm update 129 | ``` 130 | 131 | ``: Full module path of the package to update 132 | 133 | Example: 134 | ```bash 135 | vpm update my_project/modules/counter 136 | ``` 137 | 138 | ### vpm remove 139 | Remove a package from your project. 140 | 141 | This command: 142 | - Removes the specified module from your project 143 | - Updates the vpm.toml file to remove the module entry 144 | - Cleans up any orphaned dependencies 145 | 146 | ```bash 147 | vpm remove 148 | ``` 149 | 150 | ``: Full module path of the package to remove 151 | 152 | Example: 153 | ```bash 154 | vpm remove my_project/modules/unused_module 155 | ``` 156 | 157 | ### vpm dotf 158 | Generate a .f file list for a Verilog or SystemVerilog module. 159 | 160 | ```bash 161 | vpm dotf 162 | ``` 163 | 164 | ``: Path to the top module to generate the file list for. File should be local. 165 | 166 | Example: 167 | ```bash 168 | vpm dotf ./vpm_modules/pfcache/fwb_master.v 169 | ``` 170 | ![](https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExMHhkdjQ1bnl0cTA3cW1lOHVuNjkxaW1ydzFndXNnaDZlMHFiMWRpNSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/mafBT4PURloV52oLFP/giphy.gif) 171 | 172 | This command: 173 | - Analyzes the specified top module 174 | - Identifies all submodules and dependencies 175 | - Generates a .f file containing all necessary file paths 176 | - Includes all locally scoped defines for submodules 177 | 178 | ### vpm install 179 | Install and set up an open-source tool for integration into your project. 180 | 181 | This command: 182 | - Downloads the specified tool 183 | - Configures the tool for your system 184 | - Integrates it with your VPM project setup 185 | 186 | ```bash 187 | vpm install 188 | ``` 189 | ``: Name of the tool to install 190 | 191 | Example: 192 | ```bash 193 | vpm install verilator 194 | ``` 195 | ![](https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExNjFhc2t1ZTBwM29xdm10dThubWN3ZGhvOWhjeXJjNnQ0dWVqd2szdSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/737P65RSHVlu2dxXVu/giphy.gif) 196 | 197 | Currently supported tools: 198 | - Verilator 199 | - Chipyard 200 | - OpenROAD 201 | - Edalize 202 | - Icarus Verilog 203 | 204 | Coming soon: 205 | - Yosys (with support for ABC) 206 | - RISC-V GNU Toolchain 207 | 208 | ### vpm sim 209 | Simulate Verilog files. 210 | 211 | This command: 212 | - Compiles the specified Verilog files 213 | - Runs the simulation 214 | - Provides output and analysis of the simulation results 215 | 216 | ```bash 217 | vpm sim ... 218 | ``` 219 | ``: List of Verilog files to simulate using Icarus Verilog. 220 | 221 | Example: 222 | ```bash 223 | vpm sim testbench.v module1.v module2.v 224 | ``` 225 | ![](https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExcnhiaDNwZmRhazVlODAxanlqaW1yaXdpazVmNTVwanJ4c2V3a3RscSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/6ImXOh4OVsjrWYrikf/giphy.gif) 226 | 227 | ### vpm list 228 | List all modules in VPM's standard library. 229 | 230 | This command displays all available modules in the standard Verilog library, including: 231 | - Common modules 232 | - RISC-V modules 233 | 234 | ```bash 235 | vpm list 236 | ``` 237 | 238 | ### vpm config 239 | Configure VPM settings. 240 | 241 | This command allows you to enable or disable anonymous usage data collection. 242 | 243 | ```bash 244 | vpm config