diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 89f89ff..00b8f35 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -1,6 +1,10 @@ name: documentation -on: [push, pull_request, workflow_dispatch] +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] permissions: contents: write diff --git a/.gitignore b/.gitignore index f6cf477..530f4b9 100644 --- a/.gitignore +++ b/.gitignore @@ -71,3 +71,5 @@ obj_dir/ # Built documentation /target/doc/ +# Environment variables +.env diff --git a/Cargo.lock b/Cargo.lock index 9dd5015..0b1ea4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -994,7 +994,7 @@ dependencies = [ [[package]] name = "stg" -version = "0.3.2" +version = "0.3.3" dependencies = [ "anyhow", "clap", diff --git a/Cargo.toml b/Cargo.toml index ff40b01..b2ed810 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "stg" -version = "0.3.2" +version = "0.3.3" edition = "2024" authors = ["En-Ming Huang (samuel21119)"] description = "Structured Testbench Generation - Automated SystemVerilog testbench generator" diff --git a/USAGE.md b/USAGE.md index bddf872..664f449 100644 --- a/USAGE.md +++ b/USAGE.md @@ -32,9 +32,28 @@ STG supports two main testbench generation modes SV and CC/SC mode: In SV mode, both your DUT and golden reference are Verilog/SystemVerilog modules. +**Note:** Verilog files can be specified as positional arguments or with `--verilog` flag. Both styles are equivalent: +```bash +# Using positional arguments (shorter) +stg generate path/to/dut.v --golden path/to/golden.v --type --out testbench.sv + +# Using --verilog flag (explicit) +stg generate --verilog path/to/dut.v --golden path/to/golden.v --type --out testbench.sv +``` + #### Generate Testbench Only ```bash +# Using positional arguments +stg generate \ + path/to/dut.v \ + --module dut_module_name \ + --golden path/to/golden.v \ + --golden-module golden_module_name \ + --type \ + --out testbench.sv + +# Or using --verilog flag stg generate \ --verilog path/to/dut.v \ --module dut_module_name \ @@ -313,7 +332,7 @@ Generate a testbench and optionally compile it. | Argument | Description | Example | |----------|-------------|---------| -| `--verilog ` | Path to DUT Verilog file(s) - supports multiple DUTs | `--verilog dut.v` or `--verilog dut1.v --verilog dut2.v` | +| `...` or `--verilog ` | Path to DUT Verilog file(s) - can be positional args or with flag | `dut.v` or `--verilog dut.v` or `dut1.v dut2.v` | | `--type ` | Design type | `--type combinational` | | `--out ` | Output testbench path | `--out tb.sv` or `--out tb.cpp` | @@ -383,6 +402,14 @@ Generate a testbench and optionally compile it. Identify and classify signals in a Verilog module. ```bash +# Using positional argument +stg identify \ + path/to/module.v \ + --module module_name \ + --type \ + --out signals.yaml + +# Or using --verilog flag stg identify \ --verilog path/to/module.v \ --module module_name \ @@ -401,12 +428,87 @@ outputs: [result, valid] reset_active_high: false ``` +### `stg parse` + +Parse Verilog/SystemVerilog files and generate a module priority list. This command analyzes the module instantiation hierarchy and outputs modules sorted by priority (least dependencies first, then by submodule complexity). + +**Note:** Multiple Verilog files can be specified and will be automatically concatenated before parsing. + +```bash +# Single file (using positional argument) +stg parse \ + path/to/module.v \ + --out modules.yaml + +# Multiple files (positional arguments - will be concatenated) +stg parse \ + file1.v file2.v file3.v \ + --out modules.yaml + +# Or using --verilog flag +stg parse \ + --verilog path/to/module.v \ + --out modules.yaml + +# Multiple files with flag +stg parse \ + --verilog file1.v,file2.v,file3.v \ + --out modules.yaml +``` + +**What this does:** +- Parses all modules in the Verilog file +- Analyzes the module instantiation graph +- Calculates the total number of submodules for each module (including nested submodules) +- Sorts modules by priority: modules with fewer dependencies come first, followed by those with more submodules +- Outputs detailed module information in YAML format + +**Output format (YAML):** +```yaml +- name: submodule_a + submodule_count: 0 + ports: + - name: in + direction: input + width: 8 + - name: out + direction: output + width: 8 +- name: top_module + submodule_count: 5 + ports: + - name: clk + direction: input + width: 1 + - name: data_in + direction: input + width: 32 + - name: data_out + direction: output + width: 32 +``` + +**Use cases:** +- Understanding the module hierarchy in complex Verilog files +- Identifying the top-level module when `--module` is not specified +- Analyzing module complexity based on submodule count +- Debugging module dependencies + ### `stg compile` Compile user-provided testbench with DUT and golden model. **SystemVerilog:** ```bash +# Using positional arguments +stg compile \ + dut.v \ + --golden golden.v \ + --testbench tb.sv \ + --out-exe tb_exe \ + [--verilator] + +# Or using --verilog flag stg compile \ --verilog dut.v \ --golden golden.v \ @@ -414,7 +516,15 @@ stg compile \ --out-exe tb_exe \ [--verilator] -# Multi-DUT SystemVerilog +# Multi-DUT with positional arguments +stg compile \ + dut1.v dut2.v \ + --golden golden.v \ + --testbench tb.sv \ + --out-exe tb_exe \ + [--verilator] + +# Multi-DUT with flag stg compile \ --verilog dut1.v --verilog dut2.v \ --golden golden.v \ @@ -426,7 +536,7 @@ stg compile \ **C++/SystemC:** ```bash stg compile \ - --verilog dut.v \ + dut.v \ --module dut_module \ --golden golden_model.h \ --testbench tb.cpp \ diff --git a/docs/source/cli_reference.md b/docs/source/cli_reference.md index 60eab1e..b95447e 100644 --- a/docs/source/cli_reference.md +++ b/docs/source/cli_reference.md @@ -14,12 +14,17 @@ These options can be used with any `stg` command. The `generate` command is used to create a new testbench. ```bash -stg generate [OPTIONS] --verilog --out --type +stg generate [OPTIONS] [VERILOG_FILES...] --out --type ``` +**Note:** Verilog files can be specified either as positional arguments or using the `--verilog` flag. Both approaches are equivalent. + ### File and Module Options -- `--verilog `: (Required) Path to the DUT (Design Under Test) Verilog/SystemVerilog file. You can specify this option multiple times for multi-DUT testing. +- `[VERILOG_FILES...]` or `--verilog `: (Required) Path to the DUT (Design Under Test) Verilog/SystemVerilog file(s). + - Positional: `stg generate dut.v --type combinational --out tb.sv` + - With flag: `stg generate --verilog dut.v --type combinational --out tb.sv` + - Multiple files: `stg generate dut1.v dut2.v ...` or `--verilog dut1.v --verilog dut2.v` - `--module `: The name of the DUT (Device Under Test) module to be tested. This is strongly recommended if the Verilog file contains more than one module. If no `--module` is specified, we use a smart algorithm to decide the best module by parsing the module instantiation graph and selecting the one with the least in-degree. For multi-DUT testing, you can provide a comma-separated list (e.g., `--module dut0,dut1`) or use the option multiple times (e.g., `--module dut0 --module dut1`). If there are multiple DUTs but only one `--module` is specified, STG will reference all DUTs using the same module name. **Note**: in SystemVerilog-mode, `--emplace-module` should be given so that there is no naming conflicts if the modules are having same name. However, in C++/System-C mode, this renaming mechanism is handled by Verilator automatically. - `--golden `: Path to the golden reference model. This can be a Verilog/SystemVerilog file or a C++/SystemC header file (when using `--cc` or `--sc`). @@ -74,10 +79,12 @@ stg generate [OPTIONS] --verilog --out --type The `identify` command classifies the signals of a module. ```bash -stg identify [OPTIONS] --verilog --type --out +stg identify [OPTIONS] [VERILOG_FILE] --type --out ``` -- `--verilog `: (Required) Path to the Verilog/SystemVerilog file. +- `[VERILOG_FILE]` or `--verilog `: (Required) Path to the Verilog/SystemVerilog file. + - Positional: `stg identify module.v --type combinational --out signals.yaml` + - With flag: `stg identify --verilog module.v --type combinational --out signals.yaml` - `--module `: The name of the module to identify signals from. - `--type `: (Required) The design type (`combinational`, `seq_clocked`, `seq_done`). - `--out `: (Required) The output path for the signal list in YAML format. @@ -85,15 +92,61 @@ stg identify [OPTIONS] --verilog --type --out - `--control-signals [...]`: Explicitly define control signals. - `--data-signals [...]`: Explicitly define data signals. +## `stg parse` + +The `parse` command parses Verilog/SystemVerilog files and generates a module priority list. + +```bash +stg parse [VERILOG_FILES...] --out +``` + +- `[VERILOG_FILES...]` or `--verilog `: (Required) Path to the Verilog/SystemVerilog file(s) to parse. + - Single file: `stg parse module.v --out modules.yaml` + - Multiple files (concatenated): `stg parse file1.v file2.v file3.v --out modules.yaml` + - With flag: `stg parse --verilog module.v --out modules.yaml` +- `--out `: (Required) Output path for the module priority list in YAML format. + +**Description:** + +The `parse` command analyzes Verilog/SystemVerilog files to extract module information and their dependencies. When multiple files are provided, they are automatically concatenated before parsing. It performs the following: + +1. Parses all modules in the specified file +2. Builds a module instantiation graph to understand dependencies +3. Calculates the total submodule count for each module (including nested submodules) +4. Sorts modules by priority using a smart algorithm: + - Modules with fewer dependencies (lower in-degree) come first + - Among modules with the same in-degree, those with more submodules come first +5. Outputs detailed information in YAML format, including module names, submodule counts, and port information + +**Output Format:** + +```yaml +- name: module_name + submodule_count: + ports: + - name: port_name + direction: input/output + width: +``` + +**Use Cases:** +- Understanding module hierarchy in complex designs +- Identifying the top-level module when `--module` is not specified in other commands +- Analyzing design complexity based on submodule counts +- Debugging module dependencies and instantiation relationships + ## `stg compile` The `compile` command compiles a user-provided testbench. ```bash -stg compile [OPTIONS] --verilog --golden --testbench --out-exe +stg compile [OPTIONS] [VERILOG_FILES...] --golden --testbench --out-exe ``` -- `--verilog `: (Required) Path to the DUT Verilog/SystemVerilog file(s). +- `[VERILOG_FILES...]` or `--verilog `: (Required) Path to the DUT Verilog/SystemVerilog file(s). + - Single file: `stg compile dut.v --golden golden.v --testbench tb.sv --out-exe exe` + - Multiple files: `stg compile dut1.v dut2.v --golden golden.v --testbench tb.sv --out-exe exe` + - With flag: `stg compile --verilog dut.v --golden golden.v --testbench tb.sv --out-exe exe` - `--module `: The name of the DUT module. - `--golden `: (Required) Path to the golden reference file. - `--testbench `: (Required) Path to the testbench file (`.sv` or `.cpp`). diff --git a/docs/source/usage.md b/docs/source/usage.md index bddf872..5d0e5bc 100644 --- a/docs/source/usage.md +++ b/docs/source/usage.md @@ -32,9 +32,28 @@ STG supports two main testbench generation modes SV and CC/SC mode: In SV mode, both your DUT and golden reference are Verilog/SystemVerilog modules. +**Note:** Verilog files can be specified as positional arguments or with `--verilog` flag. Both styles are equivalent: +```bash +# Using positional arguments (shorter) +stg generate path/to/dut.v --golden path/to/golden.v --type --out testbench.sv + +# Using --verilog flag (explicit) +stg generate --verilog path/to/dut.v --golden path/to/golden.v --type --out testbench.sv +``` + #### Generate Testbench Only ```bash +# Using positional arguments +stg generate \ + path/to/dut.v \ + --module dut_module_name \ + --golden path/to/golden.v \ + --golden-module golden_module_name \ + --type \ + --out testbench.sv + +# Or using --verilog flag stg generate \ --verilog path/to/dut.v \ --module dut_module_name \ @@ -401,6 +420,54 @@ outputs: [result, valid] reset_active_high: false ``` +### `stg parse` + +Parse Verilog/SystemVerilog files and generate a module priority list. This command analyzes the module instantiation hierarchy and outputs modules sorted by priority (least dependencies first, then by submodule complexity). + +```bash +stg parse \ + --verilog path/to/module.v \ + --out modules.yaml +``` + +**What this does:** +- Parses all modules in the Verilog file +- Analyzes the module instantiation graph +- Calculates the total number of submodules for each module (including nested submodules) +- Sorts modules by priority: modules with fewer dependencies come first, followed by those with more submodules +- Outputs detailed module information in YAML format + +**Output format (YAML):** +```yaml +- name: submodule_a + submodule_count: 0 + ports: + - name: in + direction: input + width: 8 + - name: out + direction: output + width: 8 +- name: top_module + submodule_count: 5 + ports: + - name: clk + direction: input + width: 1 + - name: data_in + direction: input + width: 32 + - name: data_out + direction: output + width: 32 +``` + +**Use cases:** +- Understanding the module hierarchy in complex Verilog files +- Identifying the top-level module when `--module` is not specified +- Analyzing module complexity based on submodule count +- Debugging module dependencies + ### `stg compile` Compile user-provided testbench with DUT and golden model. diff --git a/src/cli.rs b/src/cli.rs index 72efc1f..7112542 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -22,13 +22,19 @@ pub enum Commands { Identify(Box), /// Compile user-provided testbench with DUT(s) and golden model Compile(Box), + /// Parse Verilog modules and print the module priority list + Parse(Box), } #[derive(Args, Debug)] pub struct GenerateArgs { - /// Path to DUT Verilog/SystemVerilog file - #[arg(long, required = true, value_delimiter(','))] - pub verilog: Vec, + /// Path to DUT Verilog/SystemVerilog file (can be specified as positional arguments or with --verilog) + #[arg(long, value_delimiter(','))] + pub verilog: Option>, + + /// Positional Verilog files (if --verilog not specified) + #[arg(value_name = "VERILOG_FILES")] + pub verilog_files: Vec, /// DUT module name (if file has multiple) #[arg(long, value_delimiter(','))] @@ -144,11 +150,30 @@ pub struct GenerateArgs { pub out_header: Option, } +impl GenerateArgs { + /// Get the Verilog files from either --verilog flag or positional arguments + pub fn get_verilog_files(&self) -> Result> { + if let Some(ref verilog) = self.verilog { + Ok(verilog.clone()) + } else if !self.verilog_files.is_empty() { + Ok(self.verilog_files.clone()) + } else { + anyhow::bail!( + "No Verilog files specified. Use --verilog flag or provide files as positional arguments" + ) + } + } +} + #[derive(Args, Debug)] pub struct IdentifyArgs { - /// Path to Verilog/SystemVerilog file - #[arg(long, required = true)] - pub verilog: PathBuf, + /// Path to Verilog/SystemVerilog file (can be specified as positional argument or with --verilog) + #[arg(long)] + pub verilog: Option, + + /// Positional Verilog file (if --verilog not specified) + #[arg(value_name = "VERILOG_FILE")] + pub verilog_file: Option, /// DUT module name (if file has multiple) #[arg(long)] @@ -175,11 +200,30 @@ pub struct IdentifyArgs { pub data_signals: Option>, } +impl IdentifyArgs { + /// Get the Verilog file from either --verilog flag or positional argument + pub fn get_verilog_file(&self) -> Result { + if let Some(ref verilog) = self.verilog { + Ok(verilog.clone()) + } else if let Some(ref verilog_file) = self.verilog_file { + Ok(verilog_file.clone()) + } else { + anyhow::bail!( + "No Verilog file specified. Use --verilog flag or provide file as positional argument" + ) + } + } +} + #[derive(Args, Debug)] pub struct CompileCommandArgs { - /// Path to DUT Verilog/SystemVerilog file - #[arg(long, required = true, value_delimiter(','))] - pub verilog: Vec, + /// Path to DUT Verilog/SystemVerilog file (can be specified as positional arguments or with --verilog) + #[arg(long, value_delimiter(','))] + pub verilog: Option>, + + /// Positional Verilog files (if --verilog not specified) + #[arg(value_name = "VERILOG_FILES")] + pub verilog_files: Vec, /// DUT module name (will auto-detect from Verilog if not specified) #[arg(long, value_delimiter(','))] @@ -234,6 +278,51 @@ pub struct CompileCommandArgs { pub sc: bool, } +impl CompileCommandArgs { + /// Get the Verilog files from either --verilog flag or positional arguments + pub fn get_verilog_files(&self) -> Result> { + if let Some(ref verilog) = self.verilog { + Ok(verilog.clone()) + } else if !self.verilog_files.is_empty() { + Ok(self.verilog_files.clone()) + } else { + anyhow::bail!( + "No Verilog files specified. Use --verilog flag or provide files as positional arguments" + ) + } + } +} + +#[derive(Args, Debug)] +pub struct ParseArgs { + /// Path to Verilog/SystemVerilog file(s) (can be specified as positional arguments or with --verilog) + #[arg(long, value_delimiter(','))] + pub verilog: Option>, + + /// Positional Verilog files (if --verilog not specified) + #[arg(value_name = "VERILOG_FILES")] + pub verilog_files: Vec, + + /// Output path for the module priority list (YAML), if not specified, will be printed to stdout + #[arg(long)] + pub out: Option, +} + +impl ParseArgs { + /// Get the Verilog files from either --verilog flag or positional arguments + pub fn get_verilog_files(&self) -> Result> { + if let Some(ref verilog) = self.verilog { + Ok(verilog.clone()) + } else if !self.verilog_files.is_empty() { + Ok(self.verilog_files.clone()) + } else { + anyhow::bail!( + "No Verilog files specified. Use --verilog flag or provide files as positional arguments" + ) + } + } +} + pub fn string_or_vec<'de, D>(deserializer: D) -> Result>, D::Error> where D: serde::Deserializer<'de>, diff --git a/src/commands/compile.rs b/src/commands/compile.rs index 216cdb7..63cd246 100644 --- a/src/commands/compile.rs +++ b/src/commands/compile.rs @@ -40,8 +40,8 @@ pub fn run_compile(args: CompileCommandArgs) -> Result<()> { } // Canonicalize paths - let verilog_paths: Vec = args - .verilog + let verilog_files = args.get_verilog_files()?; + let verilog_paths: Vec = verilog_files .iter() .map(|path| { path.canonicalize() diff --git a/src/commands/generate.rs b/src/commands/generate.rs index 101bc29..6d5fdd1 100644 --- a/src/commands/generate.rs +++ b/src/commands/generate.rs @@ -94,8 +94,8 @@ pub fn run_generate(args: GenerateArgs) -> Result<()> { ); } - let verilog_paths = args - .verilog + let verilog_files = args.get_verilog_files()?; + let verilog_paths = verilog_files .iter() .map(|path| { path.canonicalize() diff --git a/src/commands/identify.rs b/src/commands/identify.rs index 188988d..81eb910 100644 --- a/src/commands/identify.rs +++ b/src/commands/identify.rs @@ -8,10 +8,11 @@ use std::fs; use crate::cli::{IdentifyArgs, YamlConfig, load_config}; pub fn run_identify(args: IdentifyArgs) -> Result<()> { - let verilog_path = args.verilog.canonicalize().with_context(|| { + let verilog_file = args.get_verilog_file()?; + let verilog_path = verilog_file.canonicalize().with_context(|| { format!( "Failed to canonicalize DUT file: {}", - args.verilog.display() + verilog_file.display() ) })?; let out_path = args.out.clone(); diff --git a/src/commands/parse.rs b/src/commands/parse.rs new file mode 100644 index 0000000..0fee5a3 --- /dev/null +++ b/src/commands/parse.rs @@ -0,0 +1,42 @@ +use crate::tools::file_utils::concatenate_verilog_files; +use crate::tools::verilog_parser::parse_verilog_modules; +use anyhow::{Context, Result}; +use std::fs; + +use crate::cli::ParseArgs; + +pub fn run_parse(args: ParseArgs) -> Result<()> { + let verilog_files = args.get_verilog_files()?; + + // Concatenate multiple files if needed + let verilog_path = concatenate_verilog_files(&verilog_files)?; + let verilog_path = verilog_path.canonicalize().with_context(|| { + format!( + "Failed to canonicalize Verilog file: {}", + verilog_path.display() + ) + })?; + + let modules = parse_verilog_modules(&verilog_path) + .with_context(|| format!("Failed to parse Verilog file: {}", verilog_path.display()))?; + + let mut file_content = String::new(); + for module in modules { + file_content.push_str(&format!("- name: {}\n", module.name)); + file_content.push_str(&format!(" submodule_count: {}\n", module.submodule_count)); + file_content.push_str(&format!(" ports:\n")); + for port in module.ports { + file_content.push_str(&format!(" - name: {}\n", port.name)); + file_content.push_str(&format!(" direction: {}\n", port.direction)); + file_content.push_str(&format!(" width: {}\n", port.width)); + } + } + if let Some(out_path) = args.out { + fs::write(&out_path, file_content) + .with_context(|| format!("Failed to write output file: {}", out_path.display()))?; + log::info!("Wrote module priority list: {}", out_path.display()); + } else { + println!("{}", file_content); + } + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index 97bfd44..b4a1ce0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ pub mod cli; pub mod tools { pub mod compiler; pub mod emplace_verilog; + pub mod file_utils; pub mod generator; pub mod signal_classification; pub mod verilog_parser; @@ -15,4 +16,5 @@ pub mod commands { pub mod compile; pub mod generate; pub mod identify; + pub mod parse; } diff --git a/src/main.rs b/src/main.rs index c359363..89091ea 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ use stg::cli::{Cli, Commands}; use stg::commands::compile::run_compile; use stg::commands::generate::run_generate; use stg::commands::identify::run_identify; +use stg::commands::parse::run_parse; fn main() { env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); @@ -14,6 +15,7 @@ fn main() { Commands::Generate(args) => run_generate(*args), Commands::Identify(args) => run_identify(*args), Commands::Compile(args) => run_compile(*args), + Commands::Parse(args) => run_parse(*args), }; if let Err(e) = result { diff --git a/src/tools/file_utils.rs b/src/tools/file_utils.rs new file mode 100644 index 0000000..f87d195 --- /dev/null +++ b/src/tools/file_utils.rs @@ -0,0 +1,109 @@ +use anyhow::{Context, Result}; +use std::fs; +use std::path::PathBuf; +use tempfile::NamedTempFile; + +/// Concatenate multiple Verilog files into a single temporary file +/// +/// # Arguments +/// * `verilog_paths` - Vector of paths to Verilog files to concatenate +/// +/// # Returns +/// * `Result` - Path to the temporary file containing concatenated content +/// +/// # Note +/// The temporary file will be automatically cleaned up when it goes out of scope +/// unless the path is saved. The caller should keep the returned PathBuf alive +/// for as long as the file is needed. +pub fn concatenate_verilog_files(verilog_paths: &[PathBuf]) -> Result { + if verilog_paths.is_empty() { + anyhow::bail!("No Verilog files provided for concatenation"); + } + + // If only one file, return it directly + if verilog_paths.len() == 1 { + return Ok(verilog_paths[0].clone()); + } + + // Create a temporary file + let temp_file = NamedTempFile::new() + .context("Failed to create temporary file for Verilog concatenation")?; + let temp_path = temp_file.path().with_extension("v"); + + // Keep the temp file from being deleted + let (_, path) = temp_file.keep() + .context("Failed to persist temporary file")?; + + // Rename to have .v extension + fs::rename(&path, &temp_path) + .context("Failed to rename temporary file")?; + + let mut concatenated_content = String::new(); + + // Add header comment + concatenated_content.push_str(&format!( + "// Automatically concatenated from {} Verilog files\n", + verilog_paths.len() + )); + + for (idx, verilog_path) in verilog_paths.iter().enumerate() { + concatenated_content.push_str(&format!( + "\n// ========== File {}: {} ==========\n\n", + idx + 1, + verilog_path.display() + )); + + let content = fs::read_to_string(verilog_path) + .with_context(|| format!("Failed to read Verilog file: {}", verilog_path.display()))?; + + concatenated_content.push_str(&content); + concatenated_content.push_str("\n"); + } + + // Write concatenated content to temporary file + fs::write(&temp_path, concatenated_content) + .context("Failed to write concatenated Verilog to temporary file")?; + + log::info!( + "Concatenated {} Verilog files into temporary file: {}", + verilog_paths.len(), + temp_path.display() + ); + + Ok(temp_path) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::Write; + + #[test] + fn test_concatenate_single_file() { + let temp1 = NamedTempFile::new().unwrap(); + let path1 = temp1.path().to_path_buf(); + + let result = concatenate_verilog_files(&[path1.clone()]).unwrap(); + assert_eq!(result, path1); + } + + #[test] + fn test_concatenate_multiple_files() { + let mut temp1 = NamedTempFile::new().unwrap(); + let mut temp2 = NamedTempFile::new().unwrap(); + + writeln!(temp1, "module test1; endmodule").unwrap(); + writeln!(temp2, "module test2; endmodule").unwrap(); + temp1.flush().unwrap(); + temp2.flush().unwrap(); + + let path1 = temp1.path().to_path_buf(); + let path2 = temp2.path().to_path_buf(); + + let result = concatenate_verilog_files(&[path1, path2]).unwrap(); + + let content = fs::read_to_string(&result).unwrap(); + assert!(content.contains("module test1")); + assert!(content.contains("module test2")); + } +} diff --git a/src/tools/verilog_parser.rs b/src/tools/verilog_parser.rs index 4118ceb..b2fbaa6 100644 --- a/src/tools/verilog_parser.rs +++ b/src/tools/verilog_parser.rs @@ -19,6 +19,7 @@ pub struct PortInfo { pub struct ModuleInfo { pub name: String, pub ports: Vec, + pub submodule_count: usize, } // Data structure for the module graph @@ -28,6 +29,8 @@ struct ModuleGraph { edges: HashMap>, // module_name -> set of parent module names (reverse edges) reverse_edges: HashMap>, + // module_name -> count of each submodule type instantiated + instantiation_counts: HashMap>, // All module names modules: HashSet, } @@ -37,6 +40,7 @@ impl ModuleGraph { Self { edges: HashMap::new(), reverse_edges: HashMap::new(), + instantiation_counts: HashMap::new(), modules: HashSet::new(), } } @@ -52,6 +56,13 @@ impl ModuleGraph { .entry(child.to_string()) .or_default() .insert(parent.to_string()); + // Count the instantiation + *self + .instantiation_counts + .entry(parent.to_string()) + .or_default() + .entry(child.to_string()) + .or_default() += 1; self.modules.insert(parent.to_string()); self.modules.insert(child.to_string()); } @@ -173,6 +184,7 @@ fn try_parse_with_sv_parser(verilog_text: &str) -> Result> { ModuleInfo { name: mod_name, ports, + submodule_count: 0, }, ); } @@ -204,6 +216,7 @@ fn try_parse_with_sv_parser(verilog_text: &str) -> Result> { ModuleInfo { name: mod_name, ports, + submodule_count: 0, }, ); } @@ -214,6 +227,7 @@ fn try_parse_with_sv_parser(verilog_text: &str) -> Result> { // Second pass: collect instantiations for (mod_name, mod_node) in &module_bodies { for n in mod_node.clone() { + // Handle user-defined module instantiations if let RefNode::ModuleInstantiation(x) = n && let Some(inst_id) = unwrap_node!(x, ModuleIdentifier) && let Some(inst_id) = get_identifier(inst_id) @@ -238,6 +252,8 @@ fn try_parse_with_sv_parser(verilog_text: &str) -> Result> { let mut scc_submodule_count = vec![0; sccs.len()]; // Build SCC adjacency (edges between SCCs) let mut scc_edges = vec![Vec::new(); sccs.len()]; + // Track how many instances of each child SCC each parent SCC has + let mut scc_instance_counts = vec![HashMap::new(); sccs.len()]; for (parent, children) in &graph.edges { let scc_parent = *module_to_scc.get(parent).unwrap(); for child in children { @@ -245,15 +261,28 @@ fn try_parse_with_sv_parser(verilog_text: &str) -> Result> { if scc_parent != scc_child { scc_in_degree[scc_child] += 1; scc_edges[scc_parent].push(scc_child); + // Count how many instances of this child SCC the parent has + if let Some(counts) = graph.instantiation_counts.get(parent) { + if let Some(&child_instances) = counts.get(child) { + *scc_instance_counts[scc_parent] + .entry(scc_child) + .or_insert(0) += child_instances; + } + } } } - scc_submodule_count[scc_parent] += children.len(); + // Count actual instantiations, not just unique types + if let Some(counts) = graph.instantiation_counts.get(parent) { + let total_instances: usize = counts.values().sum(); + scc_submodule_count[scc_parent] += total_instances; + } } // Propagate submodule counts up the SCC graph fn propagate_submodules( idx: usize, scc_edges: &Vec>, scc_submodule_count: &mut Vec, + scc_instance_counts: &Vec>, visited: &mut Vec, ) -> usize { if visited[idx] { @@ -262,14 +291,29 @@ fn try_parse_with_sv_parser(verilog_text: &str) -> Result> { visited[idx] = true; let mut total = scc_submodule_count[idx]; for &child in &scc_edges[idx] { - total += propagate_submodules(child, scc_edges, scc_submodule_count, visited); + let child_total = propagate_submodules( + child, + scc_edges, + scc_submodule_count, + scc_instance_counts, + visited, + ); + // Multiply by the number of instances of this child SCC + let instances = scc_instance_counts[idx].get(&child).unwrap_or(&1); + total += child_total * instances; } scc_submodule_count[idx] = total; total } let mut visited = vec![false; sccs.len()]; for i in 0..sccs.len() { - propagate_submodules(i, &scc_edges, &mut scc_submodule_count, &mut visited); + propagate_submodules( + i, + &scc_edges, + &mut scc_submodule_count, + &scc_instance_counts, + &mut visited, + ); } // Sort SCCs: least in-degree, then most submodules let mut scc_indices: Vec = (0..sccs.len()).collect(); @@ -279,7 +323,11 @@ fn try_parse_with_sv_parser(verilog_text: &str) -> Result> { for &scc_idx in &scc_indices { for m in &sccs[scc_idx] { if let Some(info) = module_info_map.get(m) { - sorted_modules.push(info.clone()); + sorted_modules.push(ModuleInfo { + name: info.name.clone(), + ports: info.ports.clone(), + submodule_count: scc_submodule_count[scc_idx], + }); } } } @@ -532,6 +580,7 @@ fn parse_iverilog_output(compiled_text: &str) -> Result> { modules.push(ModuleInfo { name, ports: std::mem::take(&mut current_ports), + submodule_count: 0, }); } @@ -560,6 +609,7 @@ fn parse_iverilog_output(compiled_text: &str) -> Result> { modules.push(ModuleInfo { name, ports: current_ports, + submodule_count: 0, }); } @@ -609,7 +659,11 @@ fn fallback_simple_parse(verilog_text: &str) -> Result> { }); } - modules.push(ModuleInfo { name, ports }); + modules.push(ModuleInfo { + name, + ports, + submodule_count: 0, + }); } Ok(modules)