diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml deleted file mode 100644 index 554e0af..0000000 --- a/.github/workflows/clippy.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: Clippy Lint - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - -env: - CARGO_TERM_COLOR: always - -jobs: - clippy: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - components: clippy - override: true - - - name: Cache cargo registry - uses: actions/cache@v4 - with: - path: ~/.cargo/registry - key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - - - name: Cache cargo index - uses: actions/cache@v4 - with: - path: ~/.cargo/git - key: ${{ runner.os }}-cargo-git-${{ hashFiles('**/Cargo.lock') }} - - - name: Cache cargo build - uses: actions/cache@v4 - with: - path: target - key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} - - - name: Run clippy - run: cargo clippy --all-targets --all-features -- -D warnings diff --git a/.gitignore b/.gitignore index b428569..530f4b9 100644 --- a/.gitignore +++ b/.gitignore @@ -73,7 +73,3 @@ obj_dir/ # Environment variables .env - -# Python files -__pycache__ -.venv diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index c4ca9c9..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,467 +0,0 @@ -# AGENTS.md - AI Assistant Notes - -This document provides guidance for AI assistants working on the STG-Rust codebase. - -## Project Overview - -**STG (Structured Testbench Generation)** is a Rust tool that automatically generates comprehensive testbenches for Verilog/SystemVerilog designs. It compares a Design Under Test (DUT) against a golden reference model. - -### Core Functionality -- Parse Verilog/SystemVerilog modules and extract port information -- Classify signals into control vs. data, and inputs vs. outputs -- Generate semi-exhaustive test patterns with intelligent coverage -- Support multiple testbench modes: SystemVerilog, C++, and SystemC -- Compare DUT outputs against golden reference and report statistics - -### Key Design Principles -1. **Type Safety**: Leverage Rust's type system to prevent entire classes of bugs -2. **Performance**: Native binary with no runtime interpreter overhead -3. **Flexibility**: Support multiple modes (SV, CC, SC) and design types -4. **Portability**: Single binary that can be shipped anywhere - ---- - -## Architecture - -### Directory Structure - -``` -stg-rust/ -├── src/ -│ ├── main.rs # Entry point, CLI parsing -│ ├── lib.rs # Library exports -│ ├── cli.rs # CLI argument definitions (using clap) -│ ├── python_runtime.rs # Python environment management for LM features -│ ├── commands/ # Command implementations -│ │ ├── generate.rs # Main testbench generation logic -│ │ ├── generate_fsm.rs # FSM-based coverage-enhanced generation -│ │ ├── compile.rs # Testbench compilation -│ │ ├── identify.rs # Signal classification -│ │ └── parse.rs # Module parsing and priority sorting -│ └── tools/ # Core functionality modules -│ ├── verilog_parser.rs # Verilog parsing (sv-parser + iverilog) -│ ├── signal_classification.rs # Signal type identification -│ ├── generator.rs # Testbench template generation -│ ├── compiler.rs # iverilog/Verilator compilation -│ ├── emplace_verilog.rs # Module emplacement (SV mode) -│ ├── fsm_extractor.rs # FSM extraction for coverage -│ ├── fsm_analysis.rs # FSM analysis and DFS engine -│ └── file_utils.rs # File I/O utilities -├── examples/ # Test cases and examples -│ ├── ALU/ # Combinational logic example -│ ├── ALU_cc/ # C++ testbench example -│ ├── pingpong/ # Sequential clocked example -│ ├── seq_detector/ # FSM coverage example -│ └── traffic_light/ # Traffic light FSM example -├── tests/ # Rust integration tests -└── docs/ # Sphinx documentation -``` - -### Key Components - -#### 1. Verilog Parser (`verilog_parser.rs`) -- **Hybrid approach**: Uses sv-parser for Rust-based parsing, falls back to iverilog for port information -- **Module priority sorting**: Analyzes instantiation hierarchy and submodule counts -- **Port extraction**: Identifies inputs, outputs, widths, and signal types - -#### 2. Signal Classification (`signal_classification.rs`) -- Identifies control signals (opcodes, enables, modes) vs. data signals (values, addresses) -- Detects special signals: clock, reset, done/valid -- Uses heuristics: signal width, naming patterns, module context - -#### 3. Generator (`generator.rs`) -- Generates testbench templates from Tera templating engine -- Three modes: SystemVerilog (SV), C++ (CC), SystemC (SC) -- Test pattern generation strategy: - - **Control signals**: Exhaustive enumeration (up to 2^26 combinations) - - **Data signals**: Random sampling (default: 1024 samples per control vector) - -#### 4. Compiler (`compiler.rs`) -- **iverilog**: Traditional SystemVerilog compilation -- **Verilator**: High-performance C++ conversion with optional MPI support -- Coverage analysis support (generates .dat files) -- Multi-DUT support with unique prefixes (V0_, V1_, V2_...) - -#### 5. FSM Tools (`fsm_extractor.rs`, `fsm_analysis.rs`) -- Extract FSM state machines from Verilog (deterministic or LM-assisted) -- Generate state transition graphs -- DFS-based test generation for complete state coverage -- Hierarchical signal access for internal state monitoring - ---- - -## Design Types - -STG supports three fundamental design types: - -### 1. Combinational (`combinational`) -- Pure combinational logic (no clock, no state) -- Test approach: Enumerate control signals, random data signals -- Examples: ALU, multiplexers, decoders -- Golden model: Implements `eval()` function - -### 2. Sequential Clocked (`seq_clocked`) -- Clocked sequential design with continuous operation -- Requires clock and usually reset signals -- Test approach: Apply random control/data patterns over multiple clock cycles -- Examples: Counters, shift registers, timers -- Golden model: Implements `posedge_clk()` function - -### 3. Sequential Done (`seq_done`) -- Sequential design with transaction-based operation -- Requires clock, reset, and done/valid signal -- Test approach: Start transaction, wait for done signal, check results -- Examples: GCD, dividers, state machines with completion signals -- Golden model: Implements `posedge_clk()` and tracks completion - ---- - -## Testbench Modes - -### SystemVerilog Mode (SV) -- Both DUT and golden are Verilog modules -- Uses iverilog or Verilator for compilation -- Single-stage workflow -- Good for: Simple designs, standard workflow - -### C++ Mode (CC) -- DUT is Verilog, golden is C++ header file -- **Two-stage workflow**: - 1. Generate template with `--out-header golden_model.h` - 2. Implement golden model, then compile with `--golden golden_model.h` -- Uses Verilator for DUT compilation -- Good for: Complex golden models, custom logic, performance - -### SystemC Mode (SC) -- Similar to CC but uses SystemC types (`sc_uint`) -- Two-stage workflow like CC -- Good for: SystemC ecosystem integration, bit-accurate types - ---- - -## Multi-DUT Support - -STG can test multiple DUT implementations simultaneously against one golden reference. - -### Key Features -- Compare multiple implementations in one run -- Individual statistics for each DUT in `test_stats.json` -- Automatic unique prefixes (V0_, V1_, V2_...) -- In SV mode: Use `--emplace-module` to avoid module name conflicts -- In CC/SC mode: Verilator handles renaming automatically - -### Module Specification Methods -1. **Single name**: `--module alu` (used for all DUTs) -2. **Comma-separated**: `--module alu1,alu2,alu3` -3. **Interleaved**: `--verilog dut1.v --module alu1 --verilog dut2.v --module alu2` - ---- - -## Important Conventions - -### Signal Naming -- Clock signals: `clk`, `clock` -- Reset signals: `rst`, `rst_n`, `reset`, `reset_n` -- Done/Valid signals: `done`, `valid`, `ready` -- Control signals: Usually narrow (1-4 bits), named like `op`, `mode`, `cmd` -- Data signals: Usually wider (8+ bits), named like `a`, `b`, `data`, `addr` - -### Coding Patterns -- **Error handling**: Use `Result` for recoverable errors -- **File paths**: Always use `PathBuf` and handle path operations carefully -- **Template rendering**: Use Tera for all code generation -- **External tools**: Shell out to iverilog, verilator using `std::process::Command` - -### Testing -- Examples in `examples/` serve as integration tests -- Each example has a README.md and Makefile -- Run `cargo test` for unit tests -- Test examples with their Makefiles: `make -C examples/ALU` - ---- - -## Common Workflows - -### Adding a New Feature to Testbench Generation - -1. **Update CLI** (`cli.rs`): - ```rust - #[arg(long, help = "Your new feature")] - pub new_feature: bool, - ``` - -2. **Modify Generator** (`tools/generator.rs`): - - Add template variables in the generation context - - Update Tera template rendering - -3. **Update Templates** (embedded in code or separate files): - - Add new template sections - - Use Tera syntax: `{% if new_feature %}...{% endif %}` - -4. **Test**: - - Create example in `examples/new_feature/` - - Add README.md and test files - - Run and verify output - -### Adding Support for a New Signal Type - -1. **Update Classification** (`tools/signal_classification.rs`): - - Add detection logic in signal classification algorithm - - Update `SignalInfo` struct if needed - -2. **Update Generator** (`tools/generator.rs`): - - Add handling for new signal type in testbench templates - - Update test pattern generation - -3. **Update Documentation**: - - Add to USAGE.md with examples - - Update CLI help text - -### Debugging Verilog Parsing Issues - -1. **Enable debug output**: `--debug` flag -2. **Check parser fallback**: sv-parser fails gracefully, iverilog provides backup -3. **Verify module names**: Use `stg parse` to see what modules are detected -4. **Check port extraction**: Use `stg identify` to see classified signals - ---- - -## FSM Coverage Enhancement - -### Overview -New feature for achieving higher coverage in sequential designs by extracting FSM structure. - -**Documentation**: See `docs/source/user_guide/fsm_coverage.md` for the generate-fsm command guide. - -### Two Approaches - -#### Option A: LM-Based Extraction -- Uses language models (OpenAI, Gemini) to identify FSMs -- Python-based, managed through `python_runtime.rs` -- Environment setup with `uv` package manager -- API keys from `.env` file (e.g., `GOOGLE_API_KEY`) - -#### Option B: Deterministic Extraction -- Parser/AST-based approach using iverilog or sv-parser -- Extract state variables, state transitions -- More reliable but less flexible than LM approach - -### DFS Test Generation -- Uses extracted state transition graph -- Performs depth-first search to cover all states -- Generates targeted input sequences -- **Key advantage**: Achieves states that random testing misses - -### Hierarchical Signal Access -- Verilator supports accessing internal signals: `dut->module->signal` -- Condition monitoring: Wait for internal states (e.g., counters reaching values) -- Enables state-aware testing beyond just input/output - -### Examples -- `examples/seq_detector/`: Sequence detector FSM -- `examples/traffic_light/`: Traffic light controller FSM -- Compare coverage: traditional vs. FSM-enhanced - ---- - -## Common Pitfalls and Warnings - -### 1. Module Name Conflicts -**Problem**: Multiple Verilog files with same module names cause compilation errors -**Solution**: -- SV mode: Use `--emplace-module` to add prefixes -- CC/SC mode: Verilator handles automatically with unique prefixes - -### 2. Signal Classification Errors -**Problem**: Important control signals classified as data (or vice versa) -**Solution**: Explicitly specify with `--control-signals` or `--data-signals` - -### 3. Path Handling -**Problem**: Relative paths may break in different contexts -**Solution**: Convert to absolute paths early using `std::fs::canonicalize()` - -### 4. Golden Model Template -**Problem**: Users forget two-stage workflow in CC/SC mode -**Solution**: Clear error messages mentioning stage 1 and stage 2 - -### 5. Verilator Version Compatibility -**Problem**: Older Verilator versions may not support required features -**Solution**: Recommend Verilator v5.020+ in documentation and error messages - -### 6. Coverage File Conflicts -**Problem**: MPI processes write to same coverage.dat file -**Solution**: In CC/SC mode, use rank-specific filenames; SV mode doesn't support MPI coverage - -### 7. Random Seed Consistency -**Problem**: Non-deterministic test results make debugging hard -**Solution**: Use fixed seed for reproducibility (consider adding `--seed` flag) - ---- - -## Testing Strategy - -### Unit Tests -- Located in `tests/` directory -- Test individual components: parser, classifier, generator -- Run with `cargo test` - -### Integration Tests -- Examples serve as integration tests -- Each example has Makefile with test target -- Verify generated testbench compiles and runs without errors - -### Regression Testing -- Keep `test_stats.json` outputs for examples -- Compare statistics after changes to detect regressions -- Golden references should maintain 100% score - -### Coverage Testing -- FSM examples test coverage enhancement features -- Compare deterministic vs. LM-based vs. traditional coverage -- Look for state coverage gaps in `.dat` files - ---- - -## Key Files Reference - -| File | Purpose | When to Modify | -|------|---------|---------------| -| `cli.rs` | Command-line interface | Adding new flags/options | -| `commands/generate.rs` | Main testbench generation | Changing generation logic | -| `tools/verilog_parser.rs` | Verilog parsing | Parser improvements | -| `tools/signal_classification.rs` | Signal type detection | Classification algorithm changes | -| `tools/generator.rs` | Template generation | Testbench template updates | -| `tools/compiler.rs` | Compilation backend | Compiler flag changes | -| `tools/fsm_analysis.rs` | FSM coverage | DFS engine improvements | -| `python_runtime.rs` | Python environment | LM integration changes | - ---- - -## External Dependencies - -### Required Tools -- **iverilog**: SystemVerilog simulation (v11+ recommended) -- **Verilator**: C++ conversion and high-performance simulation (v5.020+ required) -- **Python**: For LM-based FSM extraction (managed through `uv`) - -### Rust Crates -- `clap`: CLI parsing -- `serde`: Serialization (YAML, JSON) -- `tera`: Template engine -- `sv-parser`: Verilog parsing -- `regex`: Pattern matching - ---- - -## Development Guidelines - -### Before Committing -1. Run `cargo fmt` to format code -2. Run `cargo clippy` to check for warnings -3. Run `cargo test` to verify unit tests -4. Test relevant examples: `make -C examples/ALU` -5. Update USAGE.md if adding user-facing features -6. Update AGENTS.md if adding significant architectural changes - -### Adding Examples -1. Create directory under `examples/` -2. Add DUT Verilog file(s) -3. Add golden reference (Verilog or C++ header) -4. Create README.md explaining the example -5. Add Makefile with targets: `all`, `clean`, `test` -6. Document command-line usage in example README - -### Error Messages -- Be specific: Include file names, line numbers, signal names -- Be helpful: Suggest fixes or next steps -- Be consistent: Follow existing error message patterns -- Example: "Module 'alu' not found in dut.v. Available modules: [add, sub]. Use --module to specify." - ---- - -## Future Directions - -### Potential Improvements -- **Coverage analysis**: Integrate Verilator coverage more deeply -- **Smart test generation**: ML-guided test pattern selection -- **Assertion support**: Generate SVA properties from golden model -- **Waveform comparison**: VCD-based debugging for mismatches -- **Parallel DUT compilation**: Speed up multi-DUT builds -- **Interactive mode**: REPL for exploring designs - -### FSM Enhancement -- Automatic state variable identification -- Better LM prompt engineering for complex FSMs -- Support for hierarchical/nested FSMs -- Visualization of state transition graphs - ---- - -## Resources - -### Documentation -- README.md: Quick start and installation -- docs/source/getting_started.md: Installation and quick start guide -- docs/source/user_guide/: Mode guides (SV, CC/SC), multi-DUT, FSM coverage, advanced features -- docs/source/reference/cli.md: Complete CLI reference for all commands -- docs/source/examples.md: Examples by design type -- docs/source/troubleshooting.md: Common issues and solutions -- examples/*/README.md: Specific example guides - -### External Links -- [Original Python STG](https://github.com/AS-SiliconMind/Structured-Testbench-Generation) -- [Verilator Documentation](https://verilator.org/guide/latest/) -- [sv-parser](https://github.com/dalance/sv-parser) -- [Icarus Verilog](http://iverilog.icarus.com/) - ---- - -## Tips for AI Assistants - -1. **Read before modifying**: Always read relevant source files before suggesting changes -2. **Test your changes**: Verify with examples before claiming success -3. **Follow Rust conventions**: Use `Result`, `Option`, proper error handling -4. **Preserve existing behavior**: Don't break working examples -5. **Document decisions**: Update this file when architecture changes -6. **Be explicit**: Don't guess module names or file paths - verify them -7. **Check dependencies**: Ensure external tools (iverilog, verilator) are available -8. **Understand modes**: SV vs. CC/SC have different workflows and constraints -9. **Multi-DUT awareness**: Many features must handle multiple DUTs correctly -10. **FSM features are new**: The FSM coverage enhancement is actively being developed - ---- - -## Quick Reference Commands - -```bash -# Build and install -cargo build --release -cargo install --path . - -# Run tests -cargo test -make -C examples/ALU test - -# Generate testbench (SV mode) -stg generate --verilog dut.v --golden golden.v --type combinational --out tb.sv - -# Generate testbench (CC mode, two-stage) -stg generate --verilog dut.v --type combinational --out tb.cpp --out-header golden.h --cc -# ... implement golden.h ... -stg generate --verilog dut.v --golden golden.h --type combinational --out tb.cpp --out-exe tb_exe --cc - -# Multi-DUT -stg generate --verilog dut1.v --verilog dut2.v --golden golden.h --type combinational --out tb.cpp --out-exe tb_exe --cc - -# Parse and identify -stg parse --verilog design.v --out modules.yaml -stg identify --verilog design.v --module top --type seq_clocked --out signals.yaml - -# FSM-enhanced coverage (future) -stg generate --verilog dut.v --golden golden.v --type seq_clocked --out tb.cpp --fsm-coverage -``` - ---- - -_Last updated: 2026-02-09_ -_Maintained for AI assistants working on STG-Rust_ diff --git a/Cargo.lock b/Cargo.lock index 99e61a2..0b1ea4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -455,25 +455,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "include_dir" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" -dependencies = [ - "include_dir_macros", -] - -[[package]] -name = "include_dir_macros" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" -dependencies = [ - "proc-macro2", - "quote", -] - [[package]] name = "indexmap" version = "2.12.0" @@ -1013,18 +994,15 @@ dependencies = [ [[package]] name = "stg" -version = "0.4.0-beta" +version = "0.3.3" dependencies = [ "anyhow", "clap", "env_logger", "glob", - "include_dir", - "libc", "log", "regex", "serde", - "serde_json", "serde_yaml", "sv-parser", "tempfile", diff --git a/Cargo.toml b/Cargo.toml index c3cbac2..b2ed810 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "stg" -version = "0.4.0-beta" +version = "0.3.3" edition = "2024" authors = ["En-Ming Huang (samuel21119)"] description = "Structured Testbench Generation - Automated SystemVerilog testbench generator" @@ -13,7 +13,6 @@ path = "src/main.rs" [dependencies] clap = { version = "4.5", features = ["derive", "cargo"] } serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" serde_yaml = "0.9" tera = "1.19" regex = "1.10" @@ -23,8 +22,3 @@ glob = "0.3.3" log = "0.4" env_logger = "0.11" sv-parser = "0.13.3" -include_dir = "0.7" -libc = "0.2" - -[build-dependencies] -include_dir = "0.7" diff --git a/build.rs b/build.rs deleted file mode 100644 index 5a034c6..0000000 --- a/build.rs +++ /dev/null @@ -1,4 +0,0 @@ -fn main() { - // Embed the fsm_analyzer Python package - println!("cargo:rerun-if-changed=tools/fsm_analyzer"); -} diff --git a/docs/source/cli_reference.md b/docs/source/cli_reference.md new file mode 100644 index 0000000..b95447e --- /dev/null +++ b/docs/source/cli_reference.md @@ -0,0 +1,162 @@ +# CLI Reference + +This page provides a comprehensive reference for all the command-line flags available in `stg`. + +## Global Options + +These options can be used with any `stg` command. + +- `-h, --help`: Print the help message for a command or subcommand. +- `-V, --version`: Print the version of `stg`. + +## `stg generate` + +The `generate` command is used to create a new testbench. + +```bash +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_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`). +- `--golden-module `: The name of the golden module, if it's a Verilog/SystemVerilog file with multiple modules. +- `--out `: (Required) The path for the generated testbench file (e.g., `tb.sv` or `tb.cpp`). +- `--out-exe `: The path for the compiled executable. If provided, `stg` will automatically compile the generated testbench. +- `--out-header `: In C++/SystemC mode (stage 1), this specifies the output path for the golden model header template. + +### Design Type and Signal Options + +- `--type `: (Required) The type of design being tested. + - `combinational`: For purely combinational logic. + - `seq_clocked`: For sequential designs with a clock. + - `seq_done`: For sequential designs that have a "done" or "valid" signal to indicate completion. +- `--clock `: The name of the clock signal (for `seq_clocked` and `seq_done` types). +- `--reset `: The name of the reset signal (for `seq_clocked` and `seq_done` types). +- `--reset-active `: The active polarity of the reset signal. Can be `high`, `low`, or `unknown`. +- `--done `: The name of the done/valid signal (for `seq_done` type). +- `--control-signals [...]`: A list of input signal names to be treated as control signals, which will be exhaustively tested. +- `--data-signals [...]`: A list of input signal names to be treated as data signals, which will be randomly sampled. + +### Test Generation Options + +- `--random-samples `: The number of random samples to generate for each combination of control signals. Default: `1024`. +- `--max-enumeration `: The maximum width of control signals to enumerate exhaustively (2^N). Default: `26`. +- `--timeout `: The simulation timeout in nanoseconds. Default: `1000000000`. +- `--debug`: Enables debug mode, which prints more detailed information during the test run. +- `--exit-on-error`: If set, the testbench will exit immediately upon the first mismatch. + +### Compilation and Verilator Options + +- `--verilator`: Use Verilator for compilation, which is much faster for large designs. +- `--verilator-mpi`: Enable MPI support for parallel test execution with Verilator. +- `--verilator-jobs `: The number of parallel jobs to use during Verilator compilation. Default: `4`. +- `--verilator-coverage`: Enable coverage analysis with Verilator. +- `--verilator-ignore-warnings`: Ignore common Verilator warnings like `WIDTHTRUNC`, `WIDTHCONCAT`, and `WIDTHEXPAND`. +-g-rust/docs/source/cli_reference.md +- `--compile-flags `: Pass additional flags to the compiler (iverilog or Verilator). +- `--emplace-module`: Embed the DUT and golden modules directly into the testbench file. Useful for SV mode with naming conflicts. + +### C++/SystemC Options + +- `--cc`: Generate a C++ testbench using Verilator. +- `--sc`: Generate a SystemC testbench using Verilator. + +### Other Options + +- `--config `: Path to a YAML configuration file to load options from. + +## `stg identify` + +The `identify` command classifies the signals of a module. + +```bash +stg identify [OPTIONS] [VERILOG_FILE] --type --out +``` + +- `[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. +- `--config `: Path to a YAML configuration file. +- `--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_FILES...] --golden --testbench --out-exe +``` + +- `[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`). +- `--out-exe `: (Required) The path for the compiled executable. +- `--compile-flags `: Additional compiler flags. +- `--emplace-module`: Embed DUT and golden modules in the testbench. +- `--verilator`: Use Verilator for compilation. +- `--verilator-mpi`: Enable MPI support with Verilator. +- `--verilator-ignore-warnings`: Ignore common Verilator warnings. +- `--verilator-jobs `: Number of jobs for Verilator compilation. +- `--verilator-coverage`: Enable coverage analysis. +- `--cc`: For C++ testbenches. +- `--sc`: For SystemC testbenches. diff --git a/docs/source/conf.py b/docs/source/conf.py index 03bcd6b..5513af1 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -7,9 +7,9 @@ # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information project = 'STG-Rust' -copyright = '2025-2026, AS-SiliconMind' +copyright = '2025, AS-SiliconMind' author = 'AS-SiliconMind' -release = '0.4.0-beta' +release = '0.3.2' # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration diff --git a/docs/source/examples.md b/docs/source/examples.md deleted file mode 100644 index 04d95e9..0000000 --- a/docs/source/examples.md +++ /dev/null @@ -1,194 +0,0 @@ -# Examples - -The repository includes several examples demonstrating different design types and testbench modes. Each example is in the `examples/` directory with its own README and Makefile. - -## Combinational Logic — ALU - -### SystemVerilog Mode - -```bash -cd examples/ALU - -stg generate \ - --verilog gate_level.v \ - --module alu_gate_level \ - --golden golden.v \ - --golden-module alu_golden \ - --type combinational \ - --out tb_alu.sv \ - --out-exe tb_alu_exe \ - --control-signals op - -./tb_alu_exe -# Generates test_stats.json with detailed statistics -``` - -### C++ Mode - -```bash -cd examples/ALU_cc - -# Stage 1: Generate template -stg generate \ - --verilog gate_level.v \ - --module alu_gate_level \ - --type combinational \ - --out tb.cpp \ - --out-header my_golden.h \ - --cc \ - --control-signals op - -# Stage 2: Edit my_golden.h, then compile -stg generate \ - --verilog gate_level.v \ - --module alu_gate_level \ - --golden my_golden.h \ - --type combinational \ - --out tb.cpp \ - --out-exe tb_exe \ - --cc \ - --control-signals op - -./tb_exe -``` - -## Sequential Clocked — Counter - -### SystemVerilog Mode - -```bash -cd examples/pingpong - -stg generate \ - --verilog up_only.v \ - --module counter_up_only \ - --golden golden.v \ - --golden-module counter_golden \ - --type seq_clocked \ - --out tb.sv \ - --out-exe tb_exe \ - --clock clk \ - --reset rst_n \ - --reset-active low \ - --random-samples 1000 - -./tb_exe -``` - -### SystemC Golden Model - -```bash -cd examples/pingpong_sc - -stg generate \ - --verilog up_only.v \ - --golden golden_model_sc.h \ - --type seq_clocked \ - --out tb.cpp \ - --out-exe tb_exe \ - --cc \ - --clock clk \ - --reset rst_n \ - --reset-active low \ - --random-samples 100 - -./tb_exe -``` - -## Sequential with Done Signal — GCD - -```bash -cd examples/GCD - -stg generate \ - --verilog gcd_buggy.v \ - --module gcd \ - --golden gcd_golden.v \ - --golden-module gcd_golden \ - --type seq_done \ - --out tb.sv \ - --out-exe tb_exe \ - --clock clk \ - --reset rst \ - --reset-active high \ - --done done \ - --control-signals "" \ - --data-signals a b \ - --random-samples 100 - -./tb_exe -``` - -## Multi-DUT Comparison — ALU Implementations - -Compare multiple ALU implementations against the same golden model: - -```bash -cd examples/multi_dut/ALU - -stg generate \ - --verilog dut1_buggy_add.v --module alu_v1 \ - --verilog dut2_buggy_sub.v --module alu_v2 \ - --verilog dut3_same_name.v --module alu_v1 \ - --golden golden_model.h \ - --type combinational \ - --out tb_multi.cpp \ - --out-exe tb_multi_exe \ - --cc \ - --control-signals op \ - --random-samples 500 - -./tb_multi_exe -cat test_stats.json -``` - -**Example output:** -```json -{ - "dut0": {"out": {"tests": 4000, "success": 3500, "score": 87.50}}, - "dut1": {"out": {"tests": 4000, "success": 3000, "score": 75.00}}, - "dut2": {"out": {"tests": 4000, "success": 4000, "score": 100.00}} -} -``` - -## FSM Coverage — Traffic Light - -```bash -cd examples/traffic_light - -stg generate-fsm traffic_light_controller.sv \ - --golden traffic_light_controller_golden.sv \ - --out tb.cpp \ - --out-exe tb \ - --clock clk \ - --reset rst_n \ - --reset-active low \ - --fsm-method deterministic \ - --verilator-coverage - -./tb -``` - -## FSM Coverage — Sequence Detector - -```bash -cd examples/seq_detector - -# Set API key: export GOOGLE_API_KEY="..." -stg generate-fsm seq_detector.sv \ - --golden seq_detector_golden.sv \ - --out tb.cpp \ - --out-exe tb \ - --fsm-method lm \ - --lm-provider gemini \ - --state-analysis state_analysis.json - -./tb -``` - -## See Also - -- [Getting Started](getting_started.md) — Installation and quick start -- [Modes Overview](user_guide/modes_overview.md) — Understanding modes and design types -- [Multi-DUT Support](user_guide/multi_dut.md) — Multi-DUT details -- [FSM Coverage](user_guide/fsm_coverage.md) — FSM-based testing guide diff --git a/docs/source/getting_started.md b/docs/source/getting_started.md deleted file mode 100644 index 749aae9..0000000 --- a/docs/source/getting_started.md +++ /dev/null @@ -1,172 +0,0 @@ -# Getting Started - -A high-performance Rust implementation of the [Structured Testbench Generation](https://github.com/AS-SiliconMind/Structured-Testbench-Generation) tool for automated Verilog/SystemVerilog testbench creation. - -## What is STG? - -STG automatically generates comprehensive testbenches for digital designs by: - -- Parsing your Verilog/SystemVerilog modules -- Classifying signals (control vs. data, inputs vs. outputs) -- Generating semi-exhaustive test patterns -- Comparing DUT (Design Under Test) against a golden reference -- Supporting both SystemVerilog and C++/SystemC testbenches - -The Rust implementation offers: - -- **Better error messages** with detailed context -- **Type safety** preventing entire classes of bugs -- **Native binary** — no Python interpreter required; the compiled binary can be shipped anywhere - -## Prerequisites - -- Rust toolchain (1.70 or later) -- [iverilog](http://iverilog.icarus.com/) (for SystemVerilog mode) or [Verilator](https://verilator.org/) (for high-performance testing) - -## Installation - -### Install Rust - -```bash -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -# Restart terminal to update environment variables -``` - -### Install STG System-Wide (Recommended) - -```bash -cargo install --path . -``` - -This compiles `stg` and installs it to `$HOME/.cargo/bin/`. By default, after installing Rust's toolchain, `$HOME/.cargo/bin` is added to your `$PATH`, so you can invoke `stg` directly from anywhere. - -### Build from Source - -```bash -cd stg-rust -cargo build --release -``` - -The binary will be available at `target/release/stg`. - -### Install Verilator - -This repository is tested using Verilator v5.020. Older versions shipped with Linux distributions may not be supported. Install from source: - -```bash -VERILATOR_VERSION=v5.020 -PREFIX=$HOME/.usr - -pushd /tmp/ -git clone https://github.com/verilator/verilator.git -cd verilator -git checkout v5.020 -unset VERILATOR_ROOT -autoconf -./configure --prefix=$PREFIX -make -j `nproc` -make install -popd -rm -r /tmp/verilator - -export PATH=${PREFIX}/bin:$PATH -echo export PATH=${PREFIX}/bin:\$PATH >> ~/.bashrc -``` - -### Install Icarus Verilog (iVerilog) - -STG uses iVerilog v11 as a fallback parser when [sv-parser](https://github.com/dalance/sv-parser) cannot parse modules correctly. It is recommended but not strictly required. - -```bash -IVERILOG_VERSION=v11-branch -PREFIX=$HOME/.usr - -pushd /tmp/ -git clone https://github.com/steveicarus/iverilog.git -cd iverilog -git checkout ${IVERILOG_VERSION} - -sh autoconf.sh -./configure --prefix=${PREFIX} -make -j `nproc` -make install -popd -rm -r /tmp/iverilog - -export PATH=${PREFIX}/bin:$PATH -echo export PATH=${PREFIX}/bin:\$PATH >> ~/.bashrc -``` - -### Running Tests - -```bash -cargo test -``` - -See [tests/README.md](https://github.com/AS-SiliconMind/stg-rust/blob/main/tests/README.md) for detailed test information. - -## Quick Start - -### SystemVerilog Mode - -```bash -stg generate \ - --verilog examples/ALU/gate_level.v \ - --module alu_gate_level \ - --golden examples/ALU/golden.v \ - --golden-module alu_golden \ - --type combinational \ - --out tb_alu.sv \ - --out-exe tb_alu_exe \ - --control-signals op - -./tb_alu_exe -``` - -### C++ Mode (Two-Stage Workflow) - -**Stage 1: Generate golden model template** -```bash -stg generate \ - --verilog examples/ALU/gate_level.v \ - --type combinational \ - --out tb.cpp \ - --out-header golden_model.h \ - --cc \ - --control-signals op -``` - -**Stage 2: Implement golden model in `golden_model.h`, then compile** -```bash -stg generate \ - --verilog examples/ALU/gate_level.v \ - --golden golden_model.h \ - --type combinational \ - --out tb.cpp \ - --out-exe tb_exe \ - --cc \ - --control-signals op - -./tb_exe -``` - -STG also supports SystemC — replace `--cc` with `--sc`. - -## Available Commands - -| Command | Description | -|---------|-------------| -| `stg generate` | Generate testbench (and optionally compile) | -| `stg generate-fsm` | Generate FSM-coverage-enhanced testbench | -| `stg identify` | Identify and classify signals in a module | -| `stg parse` | Parse modules and show hierarchy | -| `stg compile` | Compile a user-provided testbench | - -Run `stg --help` or `stg --help` for detailed options. - -## What's Next? - -- [Modes Overview](user_guide/modes_overview.md) — Understand the three testbench modes and design types -- [SystemVerilog Mode](user_guide/sv_mode.md) — Full SV workflow guide -- [C++/SystemC Mode](user_guide/cc_sc_mode.md) — Two-stage CC/SC workflow guide -- [CLI Reference](reference/cli.md) — Complete flag reference for all commands diff --git a/docs/source/index.rst b/docs/source/index.rst index e7fef25..3a15a27 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,37 +1,18 @@ +.. STG-Rust documentation master file, created by + sphinx-quickstart on Wed Jul 31 15:51:53 2024. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + Welcome to STG-Rust's documentation! ====================================== -STG (Structured Testbench Generation) is a high-performance Rust tool that automatically generates comprehensive testbenches for Verilog/SystemVerilog designs. - -.. toctree:: - :maxdepth: 2 - :caption: Getting Started - - getting_started - -.. toctree:: - :maxdepth: 2 - :caption: User Guide - - user_guide/modes_overview - user_guide/sv_mode - user_guide/cc_sc_mode - user_guide/multi_dut - user_guide/fsm_coverage - user_guide/advanced - -.. toctree:: - :maxdepth: 2 - :caption: Reference - - reference/cli - .. toctree:: :maxdepth: 2 - :caption: Additional Resources + :caption: Contents: - examples - troubleshooting + intro.md + usage.md + cli_reference.md Indices and tables diff --git a/docs/source/intro.md b/docs/source/intro.md new file mode 100644 index 0000000..d137b2f --- /dev/null +++ b/docs/source/intro.md @@ -0,0 +1,179 @@ +# STG-Rust - Structured Testbench Generation + +A high-performance Rust implementation of the [Structured Testbench Generation](https://github.com/AS-SiliconMind/Structured-Testbench-Generation) tool for automated Verilog/SystemVerilog testbench creation. + +## What is STG? + +STG automatically generates comprehensive testbenches for digital designs by: +- Parsing your Verilog/SystemVerilog modules +- Classifying signals (control vs. data, inputs vs. outputs) +- Generating semi-exhaustive test patterns +- Comparing DUT (Design Under Test) against a golden reference +- Supporting both SystemVerilog and C++/SystemC testbenches + +## Installation + +### Prerequisites +- Rust toolchain (1.70 or later) +- iverilog (for SystemVerilog mode) or Verilator (for high-performance testing) + +### Install Rust +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +# Restart terminal to update environment variables +``` +### Install System-Wide (Recommended) +```bash +cargo install --path . +``` + +This process compiles `stg` on your computer and installs it to `$HOME/.cargo/bin/`. The compilation process involves compiling third-party dependencies, but it will not take long. +By default, after installing Rust's toolchain, `$HOME/.cargo/bin` will be added to your `$PATH` environment variable. Therefore, you can invoke `stg` directly from anywhere, without setting up a virtual environment like Python. + +#### Install Verilator +This repository is tested using Verilator v5.020, and older version which is shipped with the Linux distribution may not be supported. Use the following script to install a newer Verilator from source. +```bash +VERILATOR_VERSION=v5.020 +PREFIX=$HOME/.usr + +pushd /tmp/ +git clone https://github.com/verilator/verilator.git +cd verilator +git checkout v5.020 +# Every time you need to build: +unset VERILATOR_ROOT # For bash +autoconf # Create ./configure script +./configure --prefix=$PREFIX # Configure and create Makefile +make -j `nproc` +make install +popd +rm -r /tmp/verilator + +# Set the PATH environment variable +export PATH=${PREFIX}/bin:$PATH +echo export PATH=${PREFIX}/bin:\$PATH >> ~/.bashrc +``` + +#### Install Icarus Verilog (iVerilog) +This repository utilizes iVerilog v11 for module's name and port parsing if [sv-parser](https://github.com/dalance/sv-parser) does not parse the modules correctly. +It is recommended to install iVerilog, but it is not nessecerly. +``` +IVERILOG_VERSION=v11-branch +PREFIX=$HOME/.usr + +pushd /tmp/ +git clone https://github.com/steveicarus/iverilog.git +cd iverilog +git checkout ${IVERILOG_VERSION} + +sh autoconf.sh +./configure --prefix=${PREFIX} +make -j `nproc` +make install +popd +rm -r /tmp/iverilog + +# Set the PATH environment variable +export PATH=${PREFIX}/bin:$PATH +echo export PATH=${PREFIX}/bin:\$PATH >> ~/.bashrc +``` + +### Build STG from Source +```bash +cd stg-rust +cargo build --release +``` + +The binary will be available at `target/release/stg`. + +### Tests +A group of tests are provided to verify if the current stg works as expected, but it does not cover all combinations. Check [tests/README.md](tests/README.md) for detailed information. +``` +# Perform all tests +cargo test +``` + +## Quick Start + +### Basic Testbench Generation (SystemVerilog) + +```bash +stg generate \ + --verilog examples/ALU/gate_level.v \ + --module alu_gate_level \ + --golden examples/ALU/golden.v \ + --golden-module alu_golden \ + --type combinational \ + --out tb_alu.sv \ + --out-exe tb_alu_exe \ + --control-signals op + +# Run the testbench +./tb_alu_exe +``` + +### C++ Testbench with Custom Golden Model in C++ +STG also supports System-C, replace `--cc` with `--sc` to use System-C. + +**Stage 1: Generate template** +```bash +stg generate \ + --verilog examples/ALU/gate_level.v \ + --type combinational \ + --out tb.cpp \ + --out-header golden_model.h \ + --cc \ + --control-signals op +``` + +**Stage 2: Implement golden model in `golden_model.h`, then compile** +```bash +stg generate \ + --verilog examples/ALU/gate_level.v \ + --golden golden_model.h \ + --type combinational \ + --out tb.cpp \ + --out-exe tb_exe \ + --cc \ + --control-signals op + +# Run the testbench +./tb_exe +``` + + +## Performance Benefits + +The Rust implementation offers: +- **Better error messages** with detailed context +- **Type safety** preventing entire classes of bugs +- **Native binary** - no Python interpreter required, the compiled binary can be shipped to anywhere + +## Documentation + +- **[USAGE.md](USAGE.md)** - Comprehensive usage guide covering: + - SystemVerilog mode (traditional) + - C++/SystemC mode (two-stage workflow) + - Compilation options and flags + - Advanced features and examples + +## Available Commands + +- `stg generate` - Generate testbench (and optionally compile) +- `stg identify` - Identify and classify signals in a module +- `stg compile` - Compile user-provided testbench files + +Run `stg --help` or `stg --help` for detailed options. + +## Examples + +The repository includes several examples demonstrating different use cases: +- `examples/ALU/` - Combinational logic (ALU with 8 operations) +- `examples/ALU_cc/` - Same ALU with C++ testbench +- `examples/pingpong/` - Sequential clocked design (counter) +- `examples/pingpong_sc/` - Same counter with SystemC golden model +- `examples/GCD/` - seq_done design (GCD algorithm) + +## License + +MIT (same as the Python version) diff --git a/docs/source/reference/cli.md b/docs/source/reference/cli.md deleted file mode 100644 index b925d69..0000000 --- a/docs/source/reference/cli.md +++ /dev/null @@ -1,221 +0,0 @@ -# CLI Reference - -This page provides a complete reference for all command-line options in `stg`. - -## Global Options - -- `-h, --help` — Print the help message for a command or subcommand -- `-V, --version` — Print the version of `stg` - ---- - -## `stg generate` - -Generate a testbench and optionally compile it. - -```bash -stg generate [OPTIONS] [VERILOG_FILES...] --out --type -``` - -Verilog files can be specified as positional arguments or with `--verilog`. Both are equivalent. - -### File and Module Options - -- `[VERILOG_FILES...]` or `--verilog ` — **(Required)** Path to DUT Verilog file(s). - - Single: `stg generate dut.v ...` or `--verilog dut.v` - - Multiple: `stg generate dut1.v dut2.v ...` or `--verilog dut1.v --verilog dut2.v` -- `--module ` — DUT module name(s). Strongly recommended when a file contains multiple modules. For multi-DUT, use comma-separated names (`--module dut0,dut1`) or repeat the flag (`--module dut0 --module dut1`). A single name is used for all DUTs. -- `--golden ` — Path to the golden reference (Verilog file or C++/SystemC header). -- `--golden-module ` — Golden module name (for Verilog golden files with multiple modules). -- `--out ` — **(Required)** Output testbench path (e.g., `tb.sv` or `tb.cpp`). -- `--out-exe ` — Compile and output an executable. If provided, STG compiles automatically. -- `--out-header ` — Output path for golden model header template (CC/SC stage 1). - -### Design Type and Signal Options - -- `--type ` — **(Required)** Design type: - - `combinational` — Pure combinational logic - - `seq_clocked` — Sequential design with clock - - `seq_done` — Sequential design with done/valid signal -- `--clock ` — Clock signal name (for `seq_clocked` and `seq_done`). -- `--reset ` — Reset signal name. -- `--reset-active ` — Reset polarity: `high`, `low`, or `unknown`. -- `--done ` — Done/valid signal name (for `seq_done`). -- `--control-signals [...]` — Signals to enumerate exhaustively. -- `--data-signals [...]` — Signals to sample randomly. - -### Test Generation Options - -- `--random-samples ` — Random samples per control vector. Default: `1024`. -- `--max-enumeration ` — Max control signal width to enumerate (2^N). Default: `26`. -- `--timeout ` — Simulation timeout in nanoseconds. Default: `1000000000`. -- `--debug` — Enable debug output with detailed signal values. -- `--exit-on-error` — Exit immediately on first mismatch. - -### Compilation and Verilator Options - -- `--verilator` — Use Verilator instead of iverilog. -- `--verilator-mpi` — Enable MPI for parallel test execution. -- `--verilator-jobs ` — Parallel Verilator compilation jobs. Default: `4`. -- `--verilator-coverage` — Enable coverage instrumentation. -- `--verilator-ignore-warnings` — Ignore common width warnings. -- `--compile-flags ` — Additional compiler flags. -- `--emplace-module` — Embed DUT and golden modules in the testbench file (SV mode only). - -### C++/SystemC Options - -- `--cc` — Generate a C++ testbench using Verilator. -- `--sc` — Generate a SystemC testbench using Verilator. - -### Other Options - -- `--config ` — Load options from a YAML configuration file. - ---- - -## `stg generate-fsm` - -Generate an FSM-coverage-enhanced C++ testbench using DFS over the state graph. - -```bash -stg generate-fsm [OPTIONS] [VERILOG_FILES...] --golden --out -``` - -### Required Options - -- `[VERILOG_FILES...]` or `--verilog ` — DUT Verilog file(s). -- `--golden ` — Golden reference Verilog file. -- `--out ` — Output testbench path (`.cpp`). - -### Common Options - -- `--module ` — DUT module name. Default: first module in file. -- `--golden-module ` — Golden module name. Default: first module in file. -- `--out-exe ` — Also compile to executable. -- `--clock ` — Clock signal name. Default: `clk`. -- `--reset ` — Reset signal name. Default: `rst`. -- `--reset-active ` — Reset polarity: `high` or `low`. Default: `low`. -- `--fsm-method ` — FSM extraction method: `lm` or `deterministic`. Default: `lm`. -- `--state-analysis ` — Load/save FSM analysis JSON (caching). - -### FSM / LM Options - -- `--lm-provider ` — LLM provider: `gemini`, `openai`, `openrouter`. -- `--lm-name ` — Model name (e.g., `gemini-2.5-flash`). -- `--lm-endpoint ` — Custom API endpoint. -- `--dfs-passes ` — DFS passes with random data. Default: `5`. -- `--transition-timeout ` — Clock cycles to wait for transition conditions. Default: `100`. -- `--random-samples ` — Extra random samples after DFS. Default: `32`. - -### Compilation Options - -- `--verilator-coverage` — Enable coverage instrumentation. -- `--compile-flags ` — Extra flags passed to Verilator. -- `--verilator-jobs ` — Parallel compile jobs. - ---- - -## `stg identify` - -Classify signals in a Verilog module. - -```bash -stg identify [OPTIONS] [VERILOG_FILE] --type --out -``` - -- `[VERILOG_FILE]` or `--verilog ` — **(Required)** Path to Verilog file. -- `--module ` — Module name to identify signals from. -- `--type ` — **(Required)** Design type (`combinational`, `seq_clocked`, `seq_done`). -- `--out ` — **(Required)** Output path for signal list (YAML). -- `--config ` — YAML configuration file. -- `--control-signals [...]` — Explicitly define control signals. -- `--data-signals [...]` — Explicitly define data signals. - -**Output format:** -```yaml -clock_inputs: [clk] -reset_inputs: [rst_n] -done_outputs: [] -control_inputs: [op, mode] -data_inputs: [a, b, data_in] -outputs: [result, valid] -reset_active_high: false -``` - ---- - -## `stg parse` - -Parse Verilog files and generate a module priority list based on the instantiation hierarchy. - -```bash -stg parse [VERILOG_FILES...] --out -``` - -- `[VERILOG_FILES...]` or `--verilog ` — **(Required)** Verilog file(s) to parse. Multiple files are concatenated before parsing. -- `--out ` — **(Required)** Output path for module list (YAML). - -The command: -1. Parses all modules in the file -2. Builds a module instantiation graph -3. Calculates total submodule counts (including nested) -4. Sorts by priority: fewer dependencies first, then by submodule complexity - -**Output format:** -```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_out - direction: output - width: 32 -``` - ---- - -## `stg compile` - -Compile a user-provided testbench with DUT and golden model. - -```bash -stg compile [OPTIONS] [VERILOG_FILES...] --golden --testbench --out-exe -``` - -- `[VERILOG_FILES...]` or `--verilog ` — **(Required)** DUT Verilog file(s). -- `--module ` — DUT module name. -- `--golden ` — **(Required)** Golden reference file. -- `--testbench ` — **(Required)** Testbench file (`.sv` or `.cpp`). -- `--out-exe ` — **(Required)** Output executable path. -- `--compile-flags ` — Additional compiler flags. -- `--emplace-module` — Embed DUT and golden modules in testbench. -- `--verilator` — Use Verilator for compilation. -- `--verilator-mpi` — Enable MPI support. -- `--verilator-ignore-warnings` — Ignore common Verilator warnings. -- `--verilator-jobs ` — Parallel Verilator jobs. -- `--verilator-coverage` — Enable coverage analysis. -- `--cc` — For C++ testbenches. -- `--sc` — For SystemC testbenches. - ---- - -## Runtime Arguments - -These arguments are passed to the compiled testbench executable (not to `stg`): - -- `+STATS_FILE=` — Custom path for the statistics JSON output. Default: `test_stats.json`. - -```bash -./tb_exe +STATS_FILE=my_results.json -``` diff --git a/docs/source/troubleshooting.md b/docs/source/troubleshooting.md deleted file mode 100644 index 90070bb..0000000 --- a/docs/source/troubleshooting.md +++ /dev/null @@ -1,135 +0,0 @@ -# Troubleshooting - -Common issues and solutions when using STG. - -## Verilog Parsing Issues - -### "No modules found in DUT file" - -Make sure your Verilog file contains valid module definitions and iverilog is installed: - -```bash -iverilog -t null -g2009 your_file.v -``` - -You can also use `stg parse` to inspect what modules are detected: - -```bash -stg parse --verilog your_file.v --out modules.yaml -``` - -### "Multiple modules found, please specify --module" - -Your file contains multiple module definitions. Specify which one to test: - -```bash -stg generate --verilog file.v --module my_module ... -``` - -Use `stg parse` to see all available modules and their hierarchy. - -### Parser fallback - -STG uses a hybrid parsing approach: [sv-parser](https://github.com/dalance/sv-parser) for Rust-native parsing, with iverilog as a fallback. If sv-parser fails on your file, ensure iverilog v11+ is installed. Use `--debug` for detailed parser output. - -## C++/SystemC Mode Issues - -### "There are two stages to compile the testbench" - -You're in stage 1 of the CC/SC workflow. First generate the template: - -```bash -stg generate --verilog dut.v --type combinational --out tb.cpp --out-header golden.h --cc -``` - -Then implement the golden model in `golden.h` and run stage 2: - -```bash -stg generate --verilog dut.v --golden golden.h --type combinational --out tb.cpp --out-exe exe --cc -``` - -See the [C++/SystemC Mode Guide](user_guide/cc_sc_mode.md) for the full two-stage workflow. - -## Compilation Issues - -### Verilator compilation errors - -Try ignoring width warnings: - -```bash -stg generate ... --verilator --compile-flags -Wno-WIDTH -``` - -### Verilator version compatibility - -STG requires Verilator v5.020 or later. Older versions shipped with Linux distributions may not support required features. See the [installation guide](getting_started.md#install-verilator) for building from source. - -### Module name conflicts (SV mode) - -When multiple Verilog files contain modules with the same name, compilation will fail. In SV mode, use `--emplace-module` to add prefixes: - -```bash -stg generate --verilog dut1.v --verilog dut2.v --golden golden.v \ - --type combinational --out tb.sv --emplace-module -``` - -In CC/SC mode, Verilator handles renaming automatically. - -## Signal Classification Issues - -### Important signals misclassified - -If control signals are classified as data (or vice versa), specify them explicitly: - -```bash -stg generate ... --control-signals op mode --data-signals a b -``` - -Use `stg identify` to preview the automatic classification: - -```bash -stg identify --verilog dut.v --module my_module --type combinational --out signals.yaml -``` - -## FSM Coverage Issues - -### "No state machines found" - -The design may not contain a detectable FSM, or the coding style is not recognized by the deterministic parser. Try LLM-based extraction: - -```bash -stg generate-fsm design.sv --golden golden.sv --out tb.cpp --fsm-method lm --lm-provider gemini -``` - -### LM method fails - -- Ensure `uv` is installed: -- Check API keys in `.env` or environment variables -- Supported keys: `GOOGLE_API_KEY`, `OPENAI_API_KEY`, `OPENROUTER_API_KEY` - -### Missed states in coverage - -- Increase `--dfs-passes` for more edge coverage -- Increase `--transition-timeout` if internal conditions (counters, timers) take many cycles -- Inspect the extracted FSM with `--state-analysis fsm.json` and verify correctness - -## Runtime Issues - -### Non-deterministic test results - -Random test generation uses different seeds across runs. For reproducibility, consider using a fixed seed (if supported) or saving the `test_stats.json` output for comparison. - -### Coverage file conflicts with MPI - -In SV mode, MPI processes write to the same `coverage.dat` file, which causes conflicts. Use CC/SC mode for MPI coverage — rank-specific filenames are generated automatically. - -### Path issues - -Relative paths may break in different execution contexts. Use absolute paths or ensure you run STG from the correct working directory. - -## Getting Help - -- Run `stg --help` or `stg --help` for command-line usage -- Use `--debug` for detailed diagnostic output -- Check the [CLI Reference](reference/cli.md) for all available options -- See the [examples](examples.md) for working configurations diff --git a/docs/source/usage.md b/docs/source/usage.md new file mode 100644 index 0000000..67d5b7d --- /dev/null +++ b/docs/source/usage.md @@ -0,0 +1,1030 @@ +# STG Usage Guide + +This guide provides comprehensive documentation for using STG (Structured Testbench Generation) in different modes and scenarios. + +## Table of Contents + +- [Modes Overview](#modes-overview) +- [SystemVerilog Mode](#systemverilog-mode-traditional) +- [C++/SystemC Mode](#ccsc-mode) +- [Command Reference](#command-reference) +- [Multi-DUT Support](#multi-dut-support) +- [Advanced Features](#advanced-features) +- [Examples by Design Type](#examples-by-design-type) + +--- + +## Modes Overview + +STG supports two main testbench generation modes SV and CC/SC mode: + +| Mode | Testbench Language | Golden Model | Compiler | Use Case | +|------|-------------------|--------------|----------|----------| +| **SV** (Traditional) | SystemVerilog | Verilog/SystemVerilog | iverilog or Verilator | Standard workflow, easy setup | +| **CC** | C++ | C++ header | Verilator | Custom golden model, SystemC types optional | +| **SC** | C++ with SystemC | SystemC header | Verilator | Full SystemC support with `sc_uint` types | + +--- + +## SystemVerilog Mode (Traditional) + +### Basic Workflow + +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 \ + --golden path/to/golden.v \ + --golden-module golden_module_name \ + --type \ + --out testbench.sv +``` + +#### Generate and Compile (iverilog) + +```bash +stg generate \ + --verilog path/to/dut.v \ + --module dut_module_name \ + --golden path/to/golden.v \ + --golden-module golden_module_name \ + --type \ + --out testbench.sv \ + --out-exe testbench_exe + +# Run the testbench +./testbench_exe +``` + +#### Generate and Compile (Verilator) + +```bash +stg generate \ + --verilog path/to/dut.v \ + --module dut_module_name \ + --golden path/to/golden.v \ + --golden-module golden_module_name \ + --type \ + --out testbench.sv \ + --out-exe testbench_exe \ + --verilator + +# Run the testbench +./testbench_exe +``` + +#### With Verilator MPI (Parallel Execution) + +```bash +stg generate \ + --verilog path/to/dut.v \ + --module dut_module_name \ + --golden path/to/golden.v \ + --golden-module golden_module_name \ + --type \ + --out testbench.sv \ + --out-exe testbench_exe \ + --verilator-mpi + +# Run with 4 MPI processes +mpirun -np 4 ./testbench_exe +``` + +### Recompiling Modified Testbench + +If you modify the generated testbench file manually, use `stg compile`: + +```bash +stg compile \ + --verilog path/to/dut.v \ + --golden path/to/golden.v \ + --testbench testbench.sv \ + --out-exe testbench_exe +``` + +With Verilator: + +```bash +stg compile \ + --verilog path/to/dut.v \ + --golden path/to/golden.v \ + --testbench testbench.sv \ + --out-exe testbench_exe \ + --verilator +``` + +--- + +## CC/SC Mode + +C++/SystemC mode provides a **two-stage workflow** where you implement a golden model in C++ or SystemC, allowing for more flexible and powerful reference implementations. + +### CC Mode (C++ Testbench) + +Use `--cc` for C++ testbenches with optional SystemC data types in the golden model. + +#### Stage 1: Generate Golden Model Template + +```bash +stg generate \ + --verilog path/to/dut.v \ + --module dut_module_name \ + --type \ + --out testbench.cpp \ + --out-header golden_model.h \ + --cc \ + [additional options...] +``` + +**What this does:** +- Generates `golden_model.h` - A C++ header template with signal declarations +- Generates `testbench.cpp` - The C++ testbench that includes your golden model +- You need to implement the golden model logic in the header file + +**Generated golden model structure:** +```cpp +#ifndef GOLDEN_MODEL_H +#define GOLDEN_MODEL_H + +#include + +class GoldenModel { +public: + // Input signals + uint8_t a; + uint8_t b; + uint8_t op; + + // Output signals + uint8_t out; + + // Implement your golden model logic here + void eval() { + // TODO: Implement combinational logic + } + + // For sequential designs + void posedge_clk() { + // TODO: Implement sequential logic + } +}; + +#endif +``` + +#### Stage 2: Implement Golden Model + +Edit `golden_model.h` to implement your golden model. Example for an ALU: + +```cpp +void eval() { + switch (op) { + case 0: out = a + b; break; + case 1: out = a - b; break; + case 2: out = a & b; break; + case 3: out = a | b; break; + case 4: out = a ^ b; break; + case 5: out = a << b; break; + case 6: out = a >> b; break; + case 7: out = (int8_t)a >> b; break; // arithmetic shift + } +} +``` + +#### Stage 3: Compile with Implemented Golden Model + +```bash +stg generate \ + --verilog path/to/dut.v \ + --module dut_module_name \ + --golden golden_model.h \ + --type \ + --out testbench.cpp \ + --out-exe testbench_exe \ + --cc \ + [additional options...] + +# Run the testbench +./testbench_exe +``` + +### SC Mode (SystemC Testbench) + +Use `--sc` for SystemC-style golden models with `sc_uint` data types. + +#### Stage 1: Generate SystemC Golden Model Template + +```bash +stg generate \ + --verilog path/to/dut.v \ + --module dut_module_name \ + --type \ + --out testbench.cpp \ + --out-header golden_model_sc.h \ + --sc \ + [additional options...] +``` + +**Generated SystemC golden model:** +```cpp +#ifndef GOLDEN_MODEL_SC_H +#define GOLDEN_MODEL_SC_H + +#include + +class GoldenModel { +public: + // Using SystemC types for inputs + sc_dt::sc_uint<4> a; + sc_dt::sc_uint<4> b; + sc_dt::sc_uint<3> op; + + // Using SystemC types for outputs + sc_dt::sc_uint<4> out; + + void eval() { + // TODO: Implement combinational logic using sc_uint + } +}; + +#endif +``` + +#### Stage 2: Implement with SystemC Types + +```cpp +void posedge_clk() { + if (rst_n == 0) { + out = 0; + return; + } + + if (en) { + // Use .to_uint() for arithmetic operations + uint32_t val = out.to_uint(); + out = (val + 1) & 0xF; + } +} +``` + +#### Stage 3: Compile with SystemC Golden Model + +```bash +stg generate \ + --verilog path/to/dut.v \ + --module dut_module_name \ + --golden golden_model_sc.h \ + --type \ + --out testbench.cpp \ + --out-exe testbench_exe \ + --sc \ + [additional options...] + +./testbench_exe +``` + +### Recompiling Modified C++/SystemC Testbench + +If you modify the generated C++ testbench, use `stg compile`: + +```bash +stg compile \ + --verilog path/to/dut.v \ + --module dut_module_name \ + --golden golden_model.h \ + --testbench testbench.cpp \ + --out-exe testbench_exe \ + --cc +``` + +--- + +## Command Reference + +### `stg generate` + +Generate a testbench and optionally compile it. + +#### Required Arguments + +| Argument | Description | Example | +|----------|-------------|---------| +| `--verilog ` | Path to DUT Verilog file(s) - supports multiple DUTs | `--verilog dut.v` or `--verilog dut1.v --verilog dut2.v` | +| `--type ` | Design type | `--type combinational` | +| `--out ` | Output testbench path | `--out tb.sv` or `--out tb.cpp` | + +**Design Types:** +- `combinational` - Pure combinational logic +- `seq_clocked` - Sequential design with clock +- `seq_done` - Sequential design with done/valid signal + +#### Optional Arguments + +**Module Selection:** +| Argument | Description | +|----------|-------------| +| `--module ` | DUT module name(s) - single name for all DUTs, comma-separated list, or interleaved. **If multiple names are given, STG matches it with the DUT files in serial.** | `--module alu` or `--module alu1,alu2` or `--module alu1 --module alu2` | +| `--golden ` | Golden reference file (Verilog or C++/SystemC header) | +| `--golden-module ` | Golden module name (for Verilog golden) | + +**CC/SC Mode:** +| Argument | Description | +|----------|-------------| +| `--cc` | Generate C++ testbench with Verilator | +| `--sc` | Generate SystemC golden model template | +| `--out-header ` | Output path for golden model header (stage 1) | + +**Compilation:** +| Argument | Description | +|----------|-------------| +| `--out-exe ` | Compile and output executable | +| `--verilator` | Use Verilator instead of iverilog | +| `--verilator-mpi` | Enable MPI support (parallel execution) | +| `--compile-flags ` | Additional compiler flags | + +**Sequential Design Options:** +| Argument | Description | Example | +|----------|-------------|---------| +| `--clock ` | Clock signal name | `--clock clk` | +| `--reset ` | Reset signal name | `--reset rst_n` | +| `--reset-active ` | Reset polarity | `--reset-active low` | +| `--done ` | Done/valid signal name | `--done valid` | + +**Signal Classification:** +| Argument | Description | +|----------|-------------| +| `--control-signals ` | Explicit control signal names | +| `--data-signals ` | Explicit data signal names | + +**Test Configuration:** +| Argument | Default | Description | +|----------|---------|-------------| +| `--random-samples ` | 1024 | Random samples per control vector | +| `--max-enumeration ` | 26 | Maximum enumeration (2^N combinations) | +| `--timeout ` | 1e11 | Timeout in nanoseconds | +| `--debug` | false | Enable debug output | +| `--exit-on-error` | false | Exit immediately on first error | + +**Runtime Arguments (pass to generated executable):** +| Argument | Default | Description | +|----------|---------|-------------| +| `+STATS_FILE=` | test_stats.json | Custom path for statistics JSON output | + +**Advanced:** +| Argument | Description | +|----------|-------------| +| `--emplace-module` | Embed DUT and golden in testbench file | +| `--config ` | Load options from YAML config | +| `--verilator-jobs ` | Parallel jobs for Verilator (default: 4) | +| `--verilator-coverage` | Enable coverage analysis | +| `--verilator-ignore-warnings` | Ignore width warnings (default: true) | + +### `stg identify` + +Identify and classify signals in a Verilog module. + +```bash +stg identify \ + --verilog path/to/module.v \ + --module module_name \ + --type \ + --out signals.yaml +``` + +**Output format (YAML):** +```yaml +clock_inputs: [clk] +reset_inputs: [rst_n] +done_outputs: [] +control_inputs: [op, mode] +data_inputs: [a, b, data_in] +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. + +**SystemVerilog:** +```bash +stg compile \ + --verilog dut.v \ + --golden golden.v \ + --testbench tb.sv \ + --out-exe tb_exe \ + [--verilator] + +# Multi-DUT SystemVerilog +stg compile \ + --verilog dut1.v --verilog dut2.v \ + --golden golden.v \ + --testbench tb.sv \ + --out-exe tb_exe \ + [--verilator] +``` + +**C++/SystemC:** +```bash +stg compile \ + --verilog dut.v \ + --module dut_module \ + --golden golden_model.h \ + --testbench tb.cpp \ + --out-exe tb_exe \ + --cc + +# Multi-DUT C++/SystemC (comma-separated modules) +stg compile \ + --verilog dut1.v --verilog dut2.v \ + --module dut_module1,dut_module2 \ + --golden golden_model.h \ + --testbench tb.cpp \ + --out-exe tb_exe \ + --cc + +# Multi-DUT C++/SystemC (interleaved specification) +stg compile \ + --verilog dut1.v --module dut_module1 \ + --verilog dut2.v --module dut_module2 \ + --golden golden_model.h \ + --testbench tb.cpp \ + --out-exe tb_exe \ + --cc + +# SystemC support +stg compile \ + --verilog dut.v \ + --module dut_module \ + --golden golden_model.h \ + --testbench tb.cpp \ + --out-exe tb_exe \ + --sc +``` + +--- + +## Multi-DUT Support + +STG supports testing multiple Design Under Test (DUT) files simultaneously, allowing you to compare different implementations against the same golden reference. In SystemVerilog mode, `--emplace-module` should be specified if modules have the same name in multiple DUT files; however, this is not required in C++/System-C mode as this is handled through [Verilator's renaming machanism](https://verilator.org/guide/latest/exe_verilator.html#cmdoption-prefix). + +### Basic Multi-DUT Usage + +**Multiple DUT files:** +```bash +stg generate \ + --verilog dut1.v --verilog dut2.v --verilog dut3.v \ + --golden golden_model.h \ + --type combinational \ + --out testbench.cpp \ + --out-exe testbench_exe \ + --cc +``` + +**Alternative syntax (comma-separated):** +```bash +stg generate \ + --verilog dut1.v,dut2.v,dut3.v \ + --golden golden_model.h \ + --type combinational \ + --out testbench.cpp \ + --out-exe testbench_exe \ + --cc +``` + +### Module Selection for Multi-DUT + +STG provides three ways to specify modules for multiple DUTs: + +**Single module name (used for all DUTs):** +```bash +stg generate \ + --verilog dut1.v --verilog dut2.v \ + --module alu_v1 \ + --golden golden_model.h \ + --type combinational \ + --out testbench.cpp \ + --cc +``` + +**Multiple module names (matched serially with DUTs):** +```bash +stg generate \ + --verilog dut1.v --verilog dut2.v --verilog dut3.v \ + --module alu_v1,alu_v2,alu_v1 \ + --golden golden_model.h \ + --type combinational \ + --out testbench.cpp \ + --cc +``` + +**Interleaved verilog/module specification (explicit pairing):** +```bash +stg generate \ + --verilog dut1.v --module alu_v1 \ + --verilog dut2.v --module alu_v2 \ + --verilog dut3.v --module alu_v1 \ + --golden golden_model.h \ + --type combinational \ + --out testbench.cpp \ + --cc +``` + +**No module specified (uses first module from each DUT file):** +```bash +stg generate \ + --verilog dut1.v --verilog dut2.v \ + --golden golden_model.h \ + --type combinational \ + --out testbench.cpp \ + --cc +``` + +### Multi-DUT in SystemVerilog Mode + +For SystemVerilog mode, use `--emplace-module` when DUT modules have naming conflicts: + +```bash +stg generate \ + --verilog dut1.v --verilog dut2.v \ + --golden golden.v \ + --type combinational \ + --out testbench.sv \ + --out-exe testbench_exe \ + --emplace-module +``` + +This adds prefixes like `V0_`, `V1_` to differentiate DUT modules in the generated testbench. + +### Multi-DUT in C++/SystemC Mode + +In C++/SystemC mode, each DUT is compiled with a unique prefix automatically: + +```bash +stg generate \ + --verilog dut1.v --verilog dut2.v --verilog dut3.v \ + --golden golden_model.h \ + --type combinational \ + --out testbench.cpp \ + --out-exe testbench_exe \ + --cc +``` + +This generates classes like `V0_DUT`, `V1_DUT`, `V2_DUT` for each DUT. + +### Test Statistics and JSON Output + +Multi-DUT testbenches automatically generate detailed statistics for each DUT in JSON format: + +**Generated `test_stats.json`:** +```json +{ + "dut0": { + "out": {"tests": 1024, "success": 1020, "score": 99.61} + }, + "dut1": { + "out": {"tests": 1024, "success": 856, "score": 83.59} + }, + "dut2": { + "out": {"tests": 1024, "success": 1024, "score": 100.00} + } +} +``` + +The JSON file contains: +- **tests**: Total number of test cases for each output signal +- **success**: Number of successful comparisons +- **score**: Success rate as a percentage + +#### Customizing Statistics File Location + +You can specify a custom location for the statistics file using the `+STATS_FILE=` runtime argument: + +**SystemVerilog mode:** +```bash +./tb_exe +STATS_FILE=my_results.json +``` + +**C++/SystemC mode:** +```bash +./tb_exe +STATS_FILE=my_results.json +``` + +This is particularly useful when: +- Running multiple test configurations +- Integrating with automated test frameworks +- Organizing results in specific directory structures + +**Example with custom output:** +```bash +stg generate \ + --verilog dut1.v --verilog dut2.v \ + --golden golden_model.h \ + --type combinational \ + --out tb.cpp \ + --out-exe tb_exe \ + --cc + +./tb_exe +STATS_FILE=results/comparison_$(date +%Y%m%d).json +``` + +### Multi-DUT Sequential Designs + +Multi-DUT works with all design types, including sequential designs: + +```bash +stg generate \ + --verilog gcd_impl1.sv --verilog gcd_impl2.sv --verilog gcd_impl3.sv \ + --golden gcd_golden.h \ + --type seq_done \ + --out testbench.cpp \ + --out-exe testbench_exe \ + --cc \ + --clock clk \ + --reset rst_n \ + --done done \ + --random-samples 50 +``` + +In `seq_done` mode, the testbench waits for all DUTs to complete before comparing results. + +--- + +## Advanced Features + +### Module Emplacement + +Embed DUT and golden modules directly in the testbench file if there are modules with same name in dut and golden (only works in the SystemVerilog mode): + +```bash +stg generate \ + --verilog dut.v \ + --golden golden.v \ + --type combinational \ + --out tb.sv \ + --emplace-module +``` + +This creates a single `.sv` file containing everything, and the module names have either "dut" or "golden" as their prefix. + +### YAML Configuration + +Create a config file to avoid repeating arguments: + +**config.yaml:** +```yaml +module: my_alu +golden_module: alu_golden +clock: clk +reset: rst_n +reset_active: low +control_signals: + - op + - mode +data_signals: + - a + - b +``` + +**Usage:** +```bash +stg generate \ + --verilog dut.v \ + --golden golden.v \ + --type combinational \ + --out tb.sv \ + --config config.yaml +``` + +### Verilator Coverage Analysis +Note: The current traditional SV mode does not support coverage analysis with MPI mode. The reason is that multiple MPI processes will write to the same file. However, in C++/SystemC mode, the DAT filenames have suffixes denoting which rank they belong to. + +Coverage analysis works with both single and multi-DUT configurations: + +**Single DUT:** +```bash +stg generate \ + --verilog dut.v \ + --golden golden.v \ + --type combinational \ + --out tb.sv \ + --out-exe tb_exe \ + --verilator \ + --verilator-coverage + +./tb_exe +# Coverage data written to coverage.dat +verilator_coverage --annotate coverage_report coverage.dat +``` + +**Multi-DUT (C++/SystemC mode):** +```bash +stg generate \ + --verilog dut1.v --verilog dut2.v \ + --golden golden_model.h \ + --type combinational \ + --out tb.cpp \ + --out-exe tb_exe \ + --cc \ + --verilator-coverage + +./tb_exe +# Coverage data written to coverage.dat (covers all DUTs) +verilator_coverage --annotate coverage_report coverage.dat +``` + +### Custom Compile Flags + +Pass additional flags to the compiler: + +```bash +stg generate \ + --verilog dut.v \ + --golden golden.v \ + --type combinational \ + --out tb.sv \ + --out-exe tb_exe \ + --verilator \ + --compile-flags --trace --trace-fst +``` + +--- + +## Examples by Design Type + +### Combinational Logic (ALU) + +```bash +cd examples/ALU + +# SystemVerilog mode +stg generate \ + --verilog gate_level.v \ + --module alu_gate_level \ + --golden golden.v \ + --golden-module alu_golden \ + --type combinational \ + --out tb_alu.sv \ + --out-exe tb_alu_exe \ + --control-signals op + +./tb_alu_exe +# Generates test_stats.json with detailed statistics +``` + +**C++ mode:** +```bash +cd examples/ALU_cc + +# Stage 1: Generate template +stg generate \ + --verilog gate_level.v \ + --module alu_gate_level \ + --type combinational \ + --out tb.cpp \ + --out-header my_golden.h \ + --cc \ + --control-signals op + +# Stage 2: Edit my_golden.h, then compile +stg generate \ + --verilog gate_level.v \ + --module alu_gate_level \ + --golden my_golden.h \ + --type combinational \ + --out tb.cpp \ + --out-exe tb_exe \ + --cc \ + --control-signals op + +./tb_exe +``` + +### Sequential Clocked (Counter) + +```bash +cd examples/pingpong + +stg generate \ + --verilog up_only.v \ + --module counter_up_only \ + --golden golden.v \ + --golden-module counter_golden \ + --type seq_clocked \ + --out tb.sv \ + --out-exe tb_exe \ + --clock clk \ + --reset rst_n \ + --reset-active low \ + --random-samples 1000 + +./tb_exe +``` + +**With SystemC golden model:** +```bash +cd examples/pingpong_sc + +# Using pre-made SystemC golden model +stg generate \ + --verilog up_only.v \ + --golden golden_model_sc.h \ + --type seq_clocked \ + --out tb.cpp \ + --out-exe tb_exe \ + --cc \ + --clock clk \ + --reset rst_n \ + --reset-active low \ + --random-samples 100 + +./tb_exe +``` + +### Sequential with Done Signal (GCD) + +```bash +cd examples/GCD + +stg generate \ + --verilog gcd_buggy.v \ + --module gcd \ + --golden gcd_golden.v \ + --golden-module gcd_golden \ + --type seq_done \ + --out tb.sv \ + --out-exe tb_exe \ + --clock clk \ + --reset rst \ + --reset-active high \ + --done done \ + --control-signals "" \ + --data-signals a b \ + --random-samples 100 + +./tb_exe +``` + +### Multi-DUT Comparison (ALU Implementations) + +Compare multiple ALU implementations against the same golden model: + +```bash +cd examples/multi_dut/ALU + +# Test multiple DUT implementations (using interleaved specification) +stg generate \ + --verilog dut1_buggy_add.v --module alu_v1 \ + --verilog dut2_buggy_sub.v --module alu_v2 \ + --verilog dut3_same_name.v --module alu_v1 \ + --golden golden_model.h \ + --type combinational \ + --out tb_multi.cpp \ + --out-exe tb_multi_exe \ + --cc \ + --control-signals op \ + --random-samples 500 + +./tb_multi_exe + +# Check detailed statistics for each DUT +cat test_stats.json +``` + +**Example output:** +```json +{ + "dut0": {"out": {"tests": 4000, "success": 3500, "score": 87.50}}, + "dut1": {"out": {"tests": 4000, "success": 3000, "score": 75.00}}, + "dut2": {"out": {"tests": 4000, "success": 4000, "score": 100.00}} +} +``` + +--- + +## Troubleshooting + +### "No modules found in DUT file" + +Make sure your Verilog file contains valid module definitions and iverilog is installed: +```bash +iverilog -t null -g2009 your_file.v +``` + +### "Multiple modules found, please specify --module" + +Use `--module` to specify which module to test: +```bash +stg generate --verilog file.v --module my_module ... +``` + +### C++/SC mode: "There are two stages to compile the testbench" + +You're in stage 1. First generate the template: +```bash +stg generate --verilog dut.v --type combinational --out tb.cpp --out-header golden.h --cc +``` + +Then implement the golden model and run stage 2: +```bash +stg generate --verilog dut.v --golden golden.h --type combinational --out tb.cpp --out-exe exe --cc +``` + +### Verilator compilation errors + +Try adding compile flags to ignore width warnings: +```bash +stg generate ... --verilator --compile-flags -Wno-WIDTH +``` + +--- + +## Tips and Best Practices + +1. **Start with SV mode** - It's simpler and good for most cases +2. **Use `--cc` for complex golden models** - C++ gives you more flexibility +3. **Specify control signals explicitly** - Use `--control-signals` for better test coverage +4. **Use Verilator for large designs** - Much faster than iverilog +5. **Enable MPI for very large designs** - Parallel execution can save hours +6. **Use `--debug` to troubleshoot** - Shows detailed signal values +7. **Save configs in YAML** - Reusable and version-controllable +8. **Use `stg compile` for iteration** - Faster when modifying testbenches +9. **Check `test_stats.json`** - Automatically generated with detailed test statistics for each DUT and signal +10. **Use multi-DUT for comparison** - Test multiple implementations simultaneously with `--verilog dut1.v --verilog dut2.v` +11. **Customize statistics output** - Use `+STATS_FILE=custom_name.json` at runtime to specify a different output file for test statistics + +--- + +For more information, see the [README.md](README.md) or run `stg --help`. + diff --git a/docs/source/user_guide/advanced.md b/docs/source/user_guide/advanced.md deleted file mode 100644 index aa4e4cb..0000000 --- a/docs/source/user_guide/advanced.md +++ /dev/null @@ -1,111 +0,0 @@ -# Advanced Features - -This page covers advanced STG features including YAML configuration, coverage analysis, custom compiler flags, and best practices. - -## YAML Configuration - -Create a config file to avoid repeating command-line arguments: - -**config.yaml:** -```yaml -module: my_alu -golden_module: alu_golden -clock: clk -reset: rst_n -reset_active: low -control_signals: - - op - - mode -data_signals: - - a - - b -``` - -**Usage:** -```bash -stg generate \ - --verilog dut.v \ - --golden golden.v \ - --type combinational \ - --out tb.sv \ - --config config.yaml -``` - -Command-line arguments override config file values. - -## Verilator Coverage Analysis - -Enable coverage instrumentation to measure how well your tests exercise the DUT. - -```{note} -SV mode does not support coverage analysis with MPI, as multiple processes write to the same file. In CC/SC mode, coverage filenames include rank suffixes to avoid conflicts. -``` - -### Single DUT - -```bash -stg generate \ - --verilog dut.v \ - --golden golden.v \ - --type combinational \ - --out tb.sv \ - --out-exe tb_exe \ - --verilator \ - --verilator-coverage - -./tb_exe -# Coverage data written to coverage.dat -verilator_coverage --annotate coverage_report coverage.dat -``` - -### Multi-DUT (C++/SystemC Mode) - -```bash -stg generate \ - --verilog dut1.v --verilog dut2.v \ - --golden golden_model.h \ - --type combinational \ - --out tb.cpp \ - --out-exe tb_exe \ - --cc \ - --verilator-coverage - -./tb_exe -verilator_coverage --annotate coverage_report coverage.dat -``` - -## Custom Compiler Flags - -Pass additional flags to iverilog or Verilator: - -```bash -stg generate \ - --verilog dut.v \ - --golden golden.v \ - --type combinational \ - --out tb.sv \ - --out-exe tb_exe \ - --verilator \ - --compile-flags --trace --trace-fst -``` - -## Tips and Best Practices - -1. **Start with SV mode** — It's simpler and works for most cases -2. **Use `--cc` for complex golden models** — C++ gives you more flexibility -3. **Specify control signals explicitly** — `--control-signals` improves test coverage -4. **Use Verilator for large designs** — Much faster than iverilog -5. **Enable MPI for very large designs** — Parallel execution can save hours -6. **Use `--debug` to troubleshoot** — Shows detailed signal values -7. **Save configs in YAML** — Reusable and version-controllable -8. **Use `stg compile` for iteration** — Faster when modifying testbenches manually -9. **Check `test_stats.json`** — Automatically generated with per-DUT, per-signal statistics -10. **Use multi-DUT for comparison** — Test multiple implementations simultaneously -11. **Customize statistics output** — Use `+STATS_FILE=custom_name.json` at runtime - -## See Also - -- [Modes Overview](modes_overview.md) — Compare testbench modes -- [Multi-DUT Support](multi_dut.md) — Multi-DUT testing details -- [CLI Reference](../reference/cli.md) — Complete flag reference -- [Troubleshooting](../troubleshooting.md) — Common issues and solutions diff --git a/docs/source/user_guide/cc_sc_mode.md b/docs/source/user_guide/cc_sc_mode.md deleted file mode 100644 index 824d151..0000000 --- a/docs/source/user_guide/cc_sc_mode.md +++ /dev/null @@ -1,181 +0,0 @@ -# C++ / SystemC Mode - -C++/SystemC mode provides a **two-stage workflow** where you implement a golden model in C++ (or SystemC), giving you maximum flexibility for reference implementations. - -## CC Mode (C++ Testbench) - -Use `--cc` for C++ testbenches. The golden model uses standard C++ integer types. - -### Stage 1: Generate Golden Model Template - -```bash -stg generate \ - --verilog path/to/dut.v \ - --module dut_module_name \ - --type \ - --out testbench.cpp \ - --out-header golden_model.h \ - --cc \ - [additional options...] -``` - -This generates: -- `golden_model.h` — A C++ header template with signal declarations -- `testbench.cpp` — The C++ testbench that includes the golden model - -**Generated golden model structure:** -```cpp -#ifndef GOLDEN_MODEL_H -#define GOLDEN_MODEL_H - -#include - -class GoldenModel { -public: - // Input signals - uint8_t a; - uint8_t b; - uint8_t op; - - // Output signals - uint8_t out; - - // Implement your golden model logic here - void eval() { - // TODO: Implement combinational logic - } - - // For sequential designs - void posedge_clk() { - // TODO: Implement sequential logic - } -}; - -#endif -``` - -### Stage 2: Implement the Golden Model - -Edit `golden_model.h` to implement your logic. Example for an ALU: - -```cpp -void eval() { - switch (op) { - case 0: out = a + b; break; - case 1: out = a - b; break; - case 2: out = a & b; break; - case 3: out = a | b; break; - case 4: out = a ^ b; break; - case 5: out = a << b; break; - case 6: out = a >> b; break; - case 7: out = (int8_t)a >> b; break; - } -} -``` - -### Stage 3: Compile with Implemented Golden Model - -```bash -stg generate \ - --verilog path/to/dut.v \ - --module dut_module_name \ - --golden golden_model.h \ - --type \ - --out testbench.cpp \ - --out-exe testbench_exe \ - --cc \ - [additional options...] - -./testbench_exe -``` - -## SC Mode (SystemC Testbench) - -Use `--sc` for SystemC-style golden models with `sc_uint` data types. - -### Stage 1: Generate SystemC Golden Model Template - -```bash -stg generate \ - --verilog path/to/dut.v \ - --module dut_module_name \ - --type \ - --out testbench.cpp \ - --out-header golden_model_sc.h \ - --sc \ - [additional options...] -``` - -**Generated SystemC golden model:** -```cpp -#ifndef GOLDEN_MODEL_SC_H -#define GOLDEN_MODEL_SC_H - -#include - -class GoldenModel { -public: - sc_dt::sc_uint<4> a; - sc_dt::sc_uint<4> b; - sc_dt::sc_uint<3> op; - sc_dt::sc_uint<4> out; - - void eval() { - // TODO: Implement combinational logic using sc_uint - } -}; - -#endif -``` - -### Stage 2: Implement with SystemC Types - -```cpp -void posedge_clk() { - if (rst_n == 0) { - out = 0; - return; - } - - if (en) { - uint32_t val = out.to_uint(); - out = (val + 1) & 0xF; - } -} -``` - -### Stage 3: Compile with SystemC Golden Model - -```bash -stg generate \ - --verilog path/to/dut.v \ - --module dut_module_name \ - --golden golden_model_sc.h \ - --type \ - --out testbench.cpp \ - --out-exe testbench_exe \ - --sc \ - [additional options...] - -./testbench_exe -``` - -## Recompiling a Modified C++/SystemC Testbench - -If you modify the generated C++ testbench, use `stg compile`: - -```bash -stg compile \ - --verilog path/to/dut.v \ - --module dut_module_name \ - --golden golden_model.h \ - --testbench testbench.cpp \ - --out-exe testbench_exe \ - --cc # or --sc for SystemC -``` - -## See Also - -- [Modes Overview](modes_overview.md) — Compare SV, CC, and SC modes -- [Multi-DUT Support](multi_dut.md) — Test multiple implementations at once -- [CLI Reference](../reference/cli.md) — Full flag reference for `stg generate` diff --git a/docs/source/user_guide/fsm_coverage.md b/docs/source/user_guide/fsm_coverage.md deleted file mode 100644 index 5899321..0000000 --- a/docs/source/user_guide/fsm_coverage.md +++ /dev/null @@ -1,132 +0,0 @@ -# FSM-Based Coverage - -`generate-fsm` creates state-coverage-enhanced C++ testbenches for sequential designs with finite state machines (FSMs). Instead of random input patterns, it uses **depth-first search (DFS)** over the FSM state graph to reach every state and transition. - -## When to Use It - -- **Sequential designs with FSMs** (clocks, resets, internal state) -- **Random testing misses states** because reaching them requires specific input sequences -- **Designs with internal timers/counters** that gate transitions - -Traditional `stg generate` relies on random inputs, which often fails to hit states like `S_PED_WALK` that need multi-cycle sequences. `generate-fsm` discovers the FSM structure and generates targeted tests. - -## Quick Start - -### Without LLM (Deterministic) - -```bash -stg generate-fsm design.sv \ - --golden golden.sv \ - --out tb.cpp \ - --out-exe tb \ - --fsm-method deterministic -``` - -### With LLM (More Flexible) - -Requires an API key (`GOOGLE_API_KEY`, `OPENAI_API_KEY`, or `OPENROUTER_API_KEY` in `.env` or environment): - -```bash -stg generate-fsm design.sv \ - --golden golden.sv \ - --out tb.cpp \ - --out-exe tb \ - --fsm-method lm \ - --lm-provider gemini -``` - -## FSM Extraction Methods - -| Method | Description | Requirements | -|--------|-------------|--------------| -| `deterministic` | Parser-based (iverilog/sv-parser) | None | -| `lm` | LLM identifies FSM from source | API key, `uv` installed | - -- **deterministic**: Fast and reliable. Works for standard FSM coding styles. -- **lm**: Better for complex or unusual FSMs. Uses an LLM to interpret the RTL. - -## State Analysis Caching - -Use `--state-analysis` to save and reuse FSM analysis: - -```bash -# First run: analyze design and save to fsm.json -stg generate-fsm design.sv --golden golden.sv --out tb.cpp \ - --state-analysis fsm.json - -# Later runs: load from fsm.json (skips analysis) -stg generate-fsm design.sv --golden golden.sv --out tb.cpp \ - --state-analysis fsm.json -``` - -- If the file **exists**: load it and skip FSM identification. -- If it **does not exist**: run analysis and save to that path. - -## Examples - -### Traffic Light (Deterministic) - -```bash -cd examples/traffic_light - -stg generate-fsm traffic_light_controller.sv \ - --golden traffic_light_controller_golden.sv \ - --out tb.cpp \ - --out-exe tb \ - --clock clk \ - --reset rst_n \ - --reset-active low \ - --fsm-method deterministic \ - --verilator-coverage - -./tb -``` - -### Sequence Detector (LM-Based) - -```bash -cd examples/seq_detector - -# Set API key: export GOOGLE_API_KEY="..." -stg generate-fsm seq_detector.sv \ - --golden seq_detector_golden.sv \ - --out tb.cpp \ - --out-exe tb \ - --fsm-method lm \ - --lm-provider gemini \ - --state-analysis state_analysis.json - -./tb -``` - -## Output - -- **Testbench**: C++ file using Verilator -- **State analysis JSON**: FSM states, transitions, control/data signals -- **Executable** (with `--out-exe`): Compiled testbench binary - -The testbench traverses the FSM via DFS, applies inputs that satisfy transition conditions, and compares DUT outputs to the golden model. - -## Troubleshooting - -### "No state machines found" - -The design may have no detectable FSM, or the coding style is not recognized. Try `--fsm-method lm` for LLM-based extraction. - -### LM method fails - -- Ensure `uv` is installed: -- Check API keys in `.env` or environment -- Supported keys: `GOOGLE_API_KEY`, `OPENAI_API_KEY`, `OPENROUTER_API_KEY` - -### Missed states in coverage - -- Increase `--dfs-passes` for more edge coverage -- Increase `--transition-timeout` if internal conditions take many cycles -- Use `--state-analysis` to inspect the extracted FSM; fix or adjust if extraction is wrong - -## See Also - -- [Modes Overview](modes_overview.md) — General STG modes and design types -- [CLI Reference](../reference/cli.md) — Full `generate-fsm` flag reference -- [Advanced Features](advanced.md) — Coverage analysis with Verilator diff --git a/docs/source/user_guide/modes_overview.md b/docs/source/user_guide/modes_overview.md deleted file mode 100644 index 66ea109..0000000 --- a/docs/source/user_guide/modes_overview.md +++ /dev/null @@ -1,109 +0,0 @@ -# Modes and Design Types - -This page explains the testbench generation modes and the supported design types in STG. - -## Testbench Modes - -STG supports two main approaches to testbench generation: **SystemVerilog (SV)** mode and **C++/SystemC (CC/SC)** mode. - -| Mode | Testbench Language | Golden Model | Compiler | Use Case | -|------|-------------------|--------------|----------|----------| -| **SV** (default) | SystemVerilog | Verilog/SystemVerilog | iverilog or Verilator | Standard workflow, easy setup | -| **CC** | C++ | C++ header | Verilator | Custom golden model, high performance | -| **SC** | C++ with SystemC | SystemC header | Verilator | SystemC ecosystem, `sc_uint` types | - -### SystemVerilog Mode (SV) - -- Both DUT and golden reference are Verilog/SystemVerilog modules -- Uses iverilog or Verilator for compilation -- **Single-stage workflow**: provide DUT, golden, and generate -- Best for: simple designs, standard verification workflow - -See the [SystemVerilog Mode Guide](sv_mode.md) for full details. - -### C++ Mode (CC) - -- DUT is Verilog, golden model is a C++ header file -- Uses Verilator exclusively for DUT compilation -- **Two-stage workflow**: - 1. Generate a golden model header template with `--out-header` - 2. Implement the golden model, then compile with `--golden` -- Best for: complex golden models, custom C++ logic, maximum performance - -### SystemC Mode (SC) - -- Similar to CC mode but uses SystemC types (`sc_uint`) -- Two-stage workflow like CC mode -- Best for: SystemC ecosystem integration, bit-accurate types - -See the [C++/SystemC Mode Guide](cc_sc_mode.md) for full details on both CC and SC modes. - -## Design Types - -STG supports three fundamental design types, specified with `--type`: - -### `combinational` - -Pure combinational logic with no clock or state elements. - -- **Test approach**: Exhaustively enumerate control signals, randomly sample data signals -- **Examples**: ALUs, multiplexers, decoders, encoders -- **Golden model function**: `eval()` - -```bash -stg generate --verilog dut.v --golden golden.v --type combinational --out tb.sv -``` - -### `seq_clocked` - -Clocked sequential design with continuous operation. - -- **Requires**: clock signal (and usually a reset signal) -- **Test approach**: Apply random control/data patterns over multiple clock cycles -- **Examples**: Counters, shift registers, timers, pipelines -- **Golden model function**: `posedge_clk()` - -```bash -stg generate --verilog dut.v --golden golden.v --type seq_clocked --out tb.sv \ - --clock clk --reset rst_n --reset-active low -``` - -### `seq_done` - -Sequential design with transaction-based operation that signals completion. - -- **Requires**: clock, reset, and a done/valid signal -- **Test approach**: Start a transaction, wait for the done signal, then check results -- **Examples**: GCD calculators, dividers, multi-cycle state machines -- **Golden model function**: `posedge_clk()` (with completion tracking) - -```bash -stg generate --verilog dut.v --golden golden.v --type seq_done --out tb.sv \ - --clock clk --reset rst --reset-active high --done done -``` - -## Signal Classification - -STG automatically classifies input signals into two categories: - -| Category | Behavior | Typical Signals | -|----------|----------|-----------------| -| **Control signals** | Exhaustively enumerated (up to 2^26 combinations) | `op`, `mode`, `cmd`, `sel` (narrow, 1–4 bits) | -| **Data signals** | Randomly sampled (default: 1024 per control vector) | `a`, `b`, `data`, `addr` (wider, 8+ bits) | - -You can override automatic classification with `--control-signals` and `--data-signals`. Use `stg identify` to preview what STG detects: - -```bash -stg identify --verilog dut.v --module my_module --type combinational --out signals.yaml -``` - -## Choosing the Right Mode - -| Scenario | Recommended Mode | -|----------|-----------------| -| Quick verification with Verilog golden | SV mode | -| Complex golden model logic | CC mode | -| Need SystemC types for bit accuracy | SC mode | -| Large design, need speed | CC/SC mode with Verilator | -| Multiple DUT comparison | CC/SC mode (automatic prefix handling) | -| FSM state coverage | `generate-fsm` command (see [FSM Coverage](fsm_coverage.md)) | diff --git a/docs/source/user_guide/multi_dut.md b/docs/source/user_guide/multi_dut.md deleted file mode 100644 index 2a25b24..0000000 --- a/docs/source/user_guide/multi_dut.md +++ /dev/null @@ -1,178 +0,0 @@ -# Multi-DUT Support - -STG can test multiple Design Under Test (DUT) implementations simultaneously against the same golden reference, making it easy to compare different implementations. - -## Basic Multi-DUT Usage - -Specify multiple DUT files with repeated `--verilog` flags: - -```bash -stg generate \ - --verilog dut1.v --verilog dut2.v --verilog dut3.v \ - --golden golden_model.h \ - --type combinational \ - --out testbench.cpp \ - --out-exe testbench_exe \ - --cc -``` - -Or use comma-separated syntax: - -```bash -stg generate \ - --verilog dut1.v,dut2.v,dut3.v \ - --golden golden_model.h \ - --type combinational \ - --out testbench.cpp \ - --out-exe testbench_exe \ - --cc -``` - -## Module Selection - -STG provides three ways to specify modules for multiple DUTs: - -### Single Module Name (Used for All DUTs) - -When all DUT files contain the same module name: - -```bash -stg generate \ - --verilog dut1.v --verilog dut2.v \ - --module alu_v1 \ - --golden golden_model.h \ - --type combinational \ - --out testbench.cpp \ - --cc -``` - -### Comma-Separated Module Names - -Match modules serially with DUT files: - -```bash -stg generate \ - --verilog dut1.v --verilog dut2.v --verilog dut3.v \ - --module alu_v1,alu_v2,alu_v1 \ - --golden golden_model.h \ - --type combinational \ - --out testbench.cpp \ - --cc -``` - -### Interleaved Specification (Explicit Pairing) - -Pair each `--verilog` with a `--module` for clarity: - -```bash -stg generate \ - --verilog dut1.v --module alu_v1 \ - --verilog dut2.v --module alu_v2 \ - --verilog dut3.v --module alu_v1 \ - --golden golden_model.h \ - --type combinational \ - --out testbench.cpp \ - --cc -``` - -### No Module Specified - -STG uses the first module from each DUT file: - -```bash -stg generate \ - --verilog dut1.v --verilog dut2.v \ - --golden golden_model.h \ - --type combinational \ - --out testbench.cpp \ - --cc -``` - -## Multi-DUT in SystemVerilog Mode - -In SV mode, use `--emplace-module` when DUT modules have naming conflicts: - -```bash -stg generate \ - --verilog dut1.v --verilog dut2.v \ - --golden golden.v \ - --type combinational \ - --out testbench.sv \ - --out-exe testbench_exe \ - --emplace-module -``` - -This adds prefixes like `V0_`, `V1_` to differentiate DUT modules. - -## Multi-DUT in C++/SystemC Mode - -In CC/SC mode, each DUT is compiled with a unique prefix automatically (no `--emplace-module` needed): - -```bash -stg generate \ - --verilog dut1.v --verilog dut2.v --verilog dut3.v \ - --golden golden_model.h \ - --type combinational \ - --out testbench.cpp \ - --out-exe testbench_exe \ - --cc -``` - -This generates classes like `V0_DUT`, `V1_DUT`, `V2_DUT` for each DUT. - -## Multi-DUT with Sequential Designs - -Multi-DUT works with all design types: - -```bash -stg generate \ - --verilog gcd_impl1.sv --verilog gcd_impl2.sv --verilog gcd_impl3.sv \ - --golden gcd_golden.h \ - --type seq_done \ - --out testbench.cpp \ - --out-exe testbench_exe \ - --cc \ - --clock clk \ - --reset rst_n \ - --done done \ - --random-samples 50 -``` - -In `seq_done` mode, the testbench waits for all DUTs to complete before comparing results. - -## Test Statistics (JSON Output) - -Multi-DUT testbenches generate per-DUT statistics in `test_stats.json`: - -```json -{ - "dut0": { - "out": {"tests": 1024, "success": 1020, "score": 99.61} - }, - "dut1": { - "out": {"tests": 1024, "success": 856, "score": 83.59} - }, - "dut2": { - "out": {"tests": 1024, "success": 1024, "score": 100.00} - } -} -``` - -Each entry contains: -- **tests**: Total number of test cases per output signal -- **success**: Number of successful comparisons -- **score**: Success rate as a percentage - -### Custom Statistics File Location - -Use the `+STATS_FILE=` runtime argument to specify a custom output path: - -```bash -./tb_exe +STATS_FILE=results/comparison_$(date +%Y%m%d).json -``` - -## See Also - -- [SystemVerilog Mode](sv_mode.md) — SV mode details including module emplacement -- [C++/SystemC Mode](cc_sc_mode.md) — CC/SC mode two-stage workflow -- [Examples](../examples.md) — Multi-DUT example with ALU implementations diff --git a/docs/source/user_guide/sv_mode.md b/docs/source/user_guide/sv_mode.md deleted file mode 100644 index d8ab65d..0000000 --- a/docs/source/user_guide/sv_mode.md +++ /dev/null @@ -1,126 +0,0 @@ -# SystemVerilog Mode - -In SystemVerilog (SV) mode, both your DUT and golden reference are Verilog/SystemVerilog modules. This is the simplest workflow and is the default mode. - -## Basic Workflow - -Verilog files can be specified as positional arguments or with the `--verilog` flag — both are equivalent: - -```bash -# Positional arguments -stg generate path/to/dut.v --golden path/to/golden.v --type --out tb.sv - -# Explicit --verilog flag -stg generate --verilog path/to/dut.v --golden path/to/golden.v --type --out tb.sv -``` - -## Generate Testbench Only - -Generate a testbench file without compiling: - -```bash -stg generate \ - --verilog path/to/dut.v \ - --module dut_module_name \ - --golden path/to/golden.v \ - --golden-module golden_module_name \ - --type \ - --out testbench.sv -``` - -## Generate and Compile - -### With iverilog - -```bash -stg generate \ - --verilog path/to/dut.v \ - --module dut_module_name \ - --golden path/to/golden.v \ - --golden-module golden_module_name \ - --type \ - --out testbench.sv \ - --out-exe testbench_exe - -./testbench_exe -``` - -### With Verilator - -```bash -stg generate \ - --verilog path/to/dut.v \ - --module dut_module_name \ - --golden path/to/golden.v \ - --golden-module golden_module_name \ - --type \ - --out testbench.sv \ - --out-exe testbench_exe \ - --verilator - -./testbench_exe -``` - -### With Verilator MPI (Parallel Execution) - -For large designs, MPI enables parallel test execution: - -```bash -stg generate \ - --verilog path/to/dut.v \ - --module dut_module_name \ - --golden path/to/golden.v \ - --golden-module golden_module_name \ - --type \ - --out testbench.sv \ - --out-exe testbench_exe \ - --verilator-mpi - -mpirun -np 4 ./testbench_exe -``` - -## Recompiling a Modified Testbench - -If you manually edit the generated testbench, use `stg compile` to recompile: - -```bash -# With iverilog -stg compile \ - --verilog path/to/dut.v \ - --golden path/to/golden.v \ - --testbench testbench.sv \ - --out-exe testbench_exe - -# With Verilator -stg compile \ - --verilog path/to/dut.v \ - --golden path/to/golden.v \ - --testbench testbench.sv \ - --out-exe testbench_exe \ - --verilator -``` - -## Module Emplacement - -When DUT and golden modules share the same name, use `--emplace-module` to embed both into the testbench file with automatic prefix renaming: - -```bash -stg generate \ - --verilog dut.v \ - --golden golden.v \ - --type combinational \ - --out tb.sv \ - --emplace-module -``` - -This creates a single `.sv` file where module names are prefixed with `dut_` or `golden_` to avoid conflicts. - -```{note} -Module emplacement is only available in SV mode. In CC/SC mode, Verilator handles renaming automatically. -``` - -## See Also - -- [Modes Overview](modes_overview.md) — Compare SV, CC, and SC modes -- [Multi-DUT Support](multi_dut.md) — Test multiple implementations at once -- [CLI Reference](../reference/cli.md) — Full flag reference for `stg generate` diff --git a/examples/seq_detector/Makefile b/examples/seq_detector/Makefile deleted file mode 100644 index 3e9ed33..0000000 --- a/examples/seq_detector/Makefile +++ /dev/null @@ -1,522 +0,0 @@ -# Makefile for Sequence Detector State Coverage Test -# Tests the generate-fsm feature with LLM or deterministic FSM analysis - -# Paths -STG := cargo run -- -DUT := seq_detector.sv -GOLDEN := seq_detector_golden.sv -TB_OUT := tb_state_coverage.cpp -TB_EXE := tb_state_coverage_exe - -# Analysis JSON files (method-specific) -ANALYSIS_JSON_DETERMINISTIC := tb_state_coverage.deterministic.state_analysis.json -ANALYSIS_JSON_LLM := tb_state_coverage.llm.state_analysis.json - -# Traditional STG (no LLM) outputs -TB_TRADITIONAL := tb_traditional.sv -TB_TRADITIONAL_EXE := tb_traditional_exe -COVERAGE_TRADITIONAL_DIR := coverage_traditional -COVERAGE_TRADITIONAL_DAT := coverage_traditional.dat - -# Deterministic coverage outputs -COVERAGE_DETERMINISTIC_DIR := coverage_deterministic -COVERAGE_DETERMINISTIC_DAT := coverage_deterministic.dat - -# LLM coverage outputs -COVERAGE_LLM_DIR := coverage_llm -COVERAGE_LLM_DAT := coverage_llm.dat - -# LLM Configuration (can be overridden from command line) -LM_PROVIDER ?= gemini -LM_NAME ?= gemini-2.5-flash -LM_ENDPOINT ?= - -# Design Configuration -CLOCK := clk -RESET := rst_n -RESET_ACTIVE := low -RANDOM_SAMPLES := 32 - -# Compiler -SIMULATOR ?= iverilog -VERILATOR_FLAGS := --verilator - -.PHONY: all generate generate-deterministic generate-compile-deterministic compile run test clean help check-stg check-api-key -.PHONY: compile-coverage run-coverage coverage show-coverage test-coverage -.PHONY: generate-traditional compile-traditional run-traditional test-traditional -.PHONY: test-traditional-coverage compare-coverage - -# Default target -all: help - -# Help message -help: - @echo "Sequence Detector - State Coverage Test" - @echo "========================================" - @echo "" - @echo "Usage:" - @echo " make generate - Generate C++ testbench using LLM-based FSM analysis" - @echo " make generate-deterministic - Generate C++ testbench using deterministic FSM extraction (no LLM)" - @echo " make generate-compile-deterministic - Generate + compile in one step (no LLM)" - @echo " make compile - Compile the generated testbench" - @echo " make run - Run the compiled testbench" - @echo " make test - Full test: generate + compile + run" - @echo " make clean - Remove generated files" - @echo "" - @echo "Coverage (Verilator):" - @echo " make compile-coverage - Compile with Verilator coverage enabled" - @echo " make run-coverage - Run and generate coverage data" - @echo " make coverage - Generate annotated coverage report" - @echo " make show-coverage - Open coverage report" - @echo " make test-coverage - Full pipeline with coverage" - @echo "" - @echo "Comparison (STG-only vs Deterministic vs LLM-enhanced):" - @echo " make test-traditional-coverage - Run traditional STG with coverage" - @echo " make compare-coverage - Run all three methods and compare results" - @echo "" - @echo "LLM Options (can be overridden):" - @echo " LM_PROVIDER=openai - LLM provider: openai, gemini, openrouter" - @echo " LM_NAME=gpt-4 - Model name" - @echo " LM_ENDPOINT= - Custom API endpoint (optional)" - @echo "" - @echo "Examples:" - @echo " make test # Use default OpenAI gpt-4" - @echo " make test LM_PROVIDER=gemini LM_NAME=gemini-1.5-pro" - @echo " make test LM_PROVIDER=openrouter LM_NAME=anthropic/claude-3-opus" - @echo " make generate LM_ENDPOINT=http://localhost:8000/v1 LM_NAME=local-model" - @echo " make test-coverage # Full test with coverage report" - @echo " make compare-coverage # Compare STG-only vs LLM-enhanced" - @echo "" - -# Check if stg binary exists -check-stg: - @if [ ! -f $(STG) ]; then \ - echo "Error: stg binary not found at $(STG)"; \ - echo "Please run 'cargo build --release' in the project root first."; \ - exit 1; \ - fi - -# Check if API key is set -check-api-key: - @if [ "$(LM_PROVIDER)" = "openai" ] && [ -z "$$OPENAI_API_KEY" ]; then \ - echo "Error: OPENAI_API_KEY environment variable not set"; \ - echo "Run: export OPENAI_API_KEY='your-api-key'"; \ - exit 1; \ - fi - @if [ "$(LM_PROVIDER)" = "gemini" ] && [ -z "$$GOOGLE_API_KEY" ]; then \ - echo "Error: GOOGLE_API_KEY environment variable not set"; \ - echo "Run: export GOOGLE_API_KEY='your-api-key'"; \ - exit 1; \ - fi - @if [ "$(LM_PROVIDER)" = "openrouter" ] && [ -z "$$OPENROUTER_API_KEY" ]; then \ - echo "Error: OPENROUTER_API_KEY environment variable not set"; \ - echo "Run: export OPENROUTER_API_KEY='your-api-key'"; \ - exit 1; \ - fi - -# Generate state coverage testbench (LM-based) -generate: check-stg check-api-key - @echo "Generating state coverage testbench (LM-based)..." - @echo " LLM Provider: $(LM_PROVIDER)" - @echo " Model: $(LM_NAME)" - $(STG) generate-fsm \ - $(DUT) \ - --golden $(GOLDEN) \ - --out $(TB_OUT) \ - --clock $(CLOCK) \ - --reset $(RESET) \ - --reset-active $(RESET_ACTIVE) \ - --random-samples $(RANDOM_SAMPLES) \ - --fsm-method lm \ - --lm-provider $(LM_PROVIDER) \ - --lm-name $(LM_NAME) \ - $(if $(LM_ENDPOINT),--lm-endpoint $(LM_ENDPOINT),) - @echo "" - @echo "Generated files:" - @echo " - $(TB_OUT)" - @echo " - $(ANALYSIS_JSON_LLM)" - @echo "" - @echo "State analysis summary:" - @if [ -f $(ANALYSIS_JSON_LLM) ]; then \ - python3 -c "import json; d=json.load(open('$(ANALYSIS_JSON_LLM)')); print(' State machines:', len(d.get('state_machines', []))); [print(' -', sm.get('state_variable') + ':', len(sm.get('states', [])), 'states,', len(sm.get('transitions', [])), 'transitions') for sm in d.get('state_machines', [])]"; \ - fi - -# Generate state coverage testbench (deterministic, no LLM needed) -generate-deterministic: check-stg - @echo "Generating state coverage testbench (deterministic)..." - $(STG) generate-fsm \ - $(DUT) \ - --golden $(GOLDEN) \ - --out $(TB_OUT) \ - --clock $(CLOCK) \ - --reset $(RESET) \ - --reset-active $(RESET_ACTIVE) \ - --random-samples $(RANDOM_SAMPLES) \ - --fsm-method deterministic \ - --state-analysis $(ANALYSIS_JSON_DETERMINISTIC) - @echo "" - @echo "Generated files:" - @echo " - $(TB_OUT)" - @echo " - $(ANALYSIS_JSON_DETERMINISTIC)" - -# Generate + compile in one step (deterministic) -generate-compile-deterministic: check-stg - @echo "Generating and compiling state coverage testbench (deterministic)..." - $(STG) generate-fsm \ - $(DUT) \ - --golden $(GOLDEN) \ - --out $(TB_OUT) \ - --out-exe $(TB_EXE) \ - --clock $(CLOCK) \ - --reset $(RESET) \ - --reset-active $(RESET_ACTIVE) \ - --random-samples $(RANDOM_SAMPLES) \ - --fsm-method deterministic - @echo "Generated and compiled: $(TB_EXE)" - -# Compile C++ testbench (using stg generate-fsm --out-exe) -compile: $(TB_OUT) - @echo "Compiling C++ testbench with Verilator..." - $(STG) generate-fsm \ - $(DUT) \ - --golden $(GOLDEN) \ - --out $(TB_OUT) \ - --out-exe $(TB_EXE) \ - --clock $(CLOCK) \ - --reset $(RESET) \ - --reset-active $(RESET_ACTIVE) \ - --random-samples $(RANDOM_SAMPLES) \ - --fsm-method deterministic - @mv tb_state_coverage.state_analysis.json $(ANALYSIS_JSON_DETERMINISTIC) 2>/dev/null || true - @echo "Compiled: $(TB_EXE)" - -# Compile with Verilator (using generate-fsm --out-exe for C++ testbenches) -compile-verilator: check-stg $(TB_OUT) - @echo "Compiling C++ testbench with Verilator..." - $(STG) generate-fsm \ - $(DUT) \ - --golden $(GOLDEN) \ - --out $(TB_OUT) \ - --out-exe $(TB_EXE) \ - --clock $(CLOCK) \ - --reset $(RESET) \ - --reset-active $(RESET_ACTIVE) \ - --random-samples $(RANDOM_SAMPLES) \ - --fsm-method deterministic - @mv tb_state_coverage.state_analysis.json $(ANALYSIS_JSON_DETERMINISTIC) 2>/dev/null || true - @echo "Compiled: $(TB_EXE)" - -# Compile with Verilator coverage enabled (C++ testbench) -compile-coverage: check-stg $(TB_OUT) - @echo "Compiling C++ testbench with Verilator (coverage enabled)..." - $(STG) generate-fsm \ - $(DUT) \ - --golden $(GOLDEN) \ - --out $(TB_OUT) \ - --out-exe $(TB_EXE) \ - --clock $(CLOCK) \ - --reset $(RESET) \ - --reset-active $(RESET_ACTIVE) \ - --random-samples $(RANDOM_SAMPLES) \ - --fsm-method deterministic \ - --verilator-coverage - @mv tb_state_coverage.state_analysis.json $(ANALYSIS_JSON_DETERMINISTIC) 2>/dev/null || true - @echo "Compiled with coverage: $(TB_EXE)" - -# Run testbench and generate coverage data -run-coverage: $(TB_EXE) - @echo "Running testbench with coverage..." - @echo "========================================" - ./$(TB_EXE) - @mv coverage.dat $(COVERAGE_DETERMINISTIC_DAT) 2>/dev/null || true - @echo "========================================" - @echo "Coverage data written to: $(COVERAGE_DETERMINISTIC_DAT)" - -# Generate annotated coverage report -coverage: $(COVERAGE_DETERMINISTIC_DAT) - @echo "Generating coverage report..." - @mkdir -p $(COVERAGE_DETERMINISTIC_DIR) - verilator_coverage --annotate $(COVERAGE_DETERMINISTIC_DIR) $(COVERAGE_DETERMINISTIC_DAT) - @echo "" - @echo "Coverage report generated in: $(COVERAGE_DETERMINISTIC_DIR)/" - @echo "" - @echo "Coverage summary:" - @verilator_coverage --annotate-min 1 $(COVERAGE_DETERMINISTIC_DAT) 2>/dev/null | head -20 || true - -# Show coverage report summary -show-coverage: $(COVERAGE_DETERMINISTIC_DIR) - @echo "Coverage Report Files:" - @echo "========================================" - @ls -la $(COVERAGE_DETERMINISTIC_DIR)/ - @echo "" - @echo "Annotated source files (showing uncovered lines):" - @for f in $(COVERAGE_DETERMINISTIC_DIR)/*.v $(COVERAGE_DETERMINISTIC_DIR)/*.sv 2>/dev/null; do \ - if [ -f "$$f" ]; then \ - echo ""; \ - echo "=== $$f ==="; \ - cat "$$f"; \ - fi; \ - done - -# Full test pipeline with coverage -test-coverage: generate-deterministic compile-coverage run-coverage coverage - @echo "" - @echo "Test with coverage completed!" - @echo "View detailed report: make show-coverage" - -# Run testbench -run: $(TB_EXE) - @echo "Running testbench..." - @echo "========================================" - ./$(TB_EXE) - @echo "========================================" - @echo "Test completed." - -# Full test pipeline -test: generate compile run - -# Generate only (for inspection) -generate-only: generate - @echo "" - @echo "Generated testbench: $(TB_OUT)" - @echo "You can inspect the state analysis in: $(ANALYSIS_JSON)" - -# Show state analysis -show-analysis: $(ANALYSIS_JSON_DETERMINISTIC) - @echo "State Machine Analysis (Deterministic):" - @echo "========================================" - @python3 -c "import json; print(json.dumps(json.load(open('$(ANALYSIS_JSON_DETERMINISTIC)')), indent=2))" - -# Clean generated files -clean: - rm -f $(TB_OUT) $(TB_EXE) $(ANALYSIS_JSON_DETERMINISTIC) $(ANALYSIS_JSON_LLM) - rm -f tb_state_coverage_cpp.cpp tb_state_coverage_cpp_exe tb_state_coverage_cpp.state_analysis.json - rm -f $(TB_TRADITIONAL) $(TB_TRADITIONAL_EXE) - rm -f *.vcd $(COVERAGE_DETERMINISTIC_DAT) $(COVERAGE_LLM_DAT) $(COVERAGE_TRADITIONAL_DAT) - rm -f test_stats.json - rm -rf obj_dir $(COVERAGE_DETERMINISTIC_DIR) $(COVERAGE_LLM_DIR) $(COVERAGE_TRADITIONAL_DIR) - @echo "Cleaned generated files." - -# Check dependencies -check-deps: - @echo "Checking dependencies..." - @which python3 > /dev/null || (echo "Error: python3 not found" && exit 1) - @which iverilog > /dev/null || echo "Warning: iverilog not found (needed for compile)" - @python3 -c "import openai" 2>/dev/null || echo "Warning: openai package not installed (needed for OpenAI provider)" - @python3 -c "import google.generativeai" 2>/dev/null || echo "Warning: google-generativeai not installed (needed for Gemini provider)" - @echo "Dependency check complete." - -# ============================================================================ -# Traditional STG (No LLM) - For Coverage Comparison -# ============================================================================ - -# Generate traditional testbench using stg generate (no LLM) -generate-traditional: check-stg - @echo "Generating traditional testbench (STG-only, no LLM)..." - $(STG) generate \ - $(DUT) \ - --golden $(GOLDEN) \ - --type seq_clocked \ - --out $(TB_TRADITIONAL) \ - --clock $(CLOCK) \ - --reset $(RESET) \ - --reset-active $(RESET_ACTIVE) \ - --random-samples $(RANDOM_SAMPLES) - @echo "Generated: $(TB_TRADITIONAL)" - -# Compile traditional testbench with coverage -compile-traditional-coverage: check-stg $(TB_TRADITIONAL) - @echo "Compiling traditional testbench with Verilator (coverage enabled)..." - $(STG) compile \ - $(DUT) \ - --golden $(GOLDEN) \ - --testbench $(TB_TRADITIONAL) \ - --out-exe $(TB_TRADITIONAL_EXE) \ - --verilator \ - --verilator-coverage - @echo "Compiled with coverage: $(TB_TRADITIONAL_EXE)" - -# Run traditional testbench with coverage -run-traditional-coverage: $(TB_TRADITIONAL_EXE) - @echo "Running traditional testbench..." - @echo "========================================" - ./$(TB_TRADITIONAL_EXE) - @mv coverage.dat $(COVERAGE_TRADITIONAL_DAT) 2>/dev/null || true - @echo "========================================" - @echo "Coverage data written to: $(COVERAGE_TRADITIONAL_DAT)" - -# Generate traditional coverage report -coverage-traditional: $(COVERAGE_TRADITIONAL_DAT) - @echo "Generating traditional coverage report..." - @mkdir -p $(COVERAGE_TRADITIONAL_DIR) - verilator_coverage --annotate $(COVERAGE_TRADITIONAL_DIR) $(COVERAGE_TRADITIONAL_DAT) - @echo "Coverage report generated in: $(COVERAGE_TRADITIONAL_DIR)/" - -# Full traditional test with coverage -test-traditional-coverage: generate-traditional compile-traditional-coverage run-traditional-coverage coverage-traditional - @echo "" - @echo "Traditional STG test with coverage completed!" - -# ============================================================================ -# LLM-Enhanced State Coverage - For Coverage Comparison -# ============================================================================ - -# Compile LLM-enhanced testbench with coverage -compile-llm-coverage: check-stg check-api-key - @echo "Generating and compiling LLM-enhanced testbench with Verilator (coverage enabled)..." - $(STG) generate-fsm \ - $(DUT) \ - --golden $(GOLDEN) \ - --out $(TB_OUT) \ - --out-exe $(TB_EXE) \ - --clock $(CLOCK) \ - --reset $(RESET) \ - --reset-active $(RESET_ACTIVE) \ - --random-samples $(RANDOM_SAMPLES) \ - --fsm-method lm \ - --lm-provider $(LM_PROVIDER) \ - --lm-name $(LM_NAME) \ - $(if $(LM_ENDPOINT),--lm-endpoint $(LM_ENDPOINT),) \ - --verilator-coverage - @mv tb_state_coverage.state_analysis.json $(ANALYSIS_JSON_LLM) 2>/dev/null || true - @echo "Compiled with coverage: $(TB_EXE)" - -# Run LLM-enhanced testbench with coverage -run-llm-coverage: $(TB_EXE) - @echo "Running LLM-enhanced testbench..." - @echo "========================================" - ./$(TB_EXE) - @mv coverage.dat $(COVERAGE_LLM_DAT) 2>/dev/null || true - @echo "========================================" - @echo "Coverage data written to: $(COVERAGE_LLM_DAT)" - -# Generate LLM-enhanced coverage report -coverage-llm: $(COVERAGE_LLM_DAT) - @echo "Generating LLM-enhanced coverage report..." - @mkdir -p $(COVERAGE_LLM_DIR) - verilator_coverage --annotate $(COVERAGE_LLM_DIR) $(COVERAGE_LLM_DAT) - @echo "Coverage report generated in: $(COVERAGE_LLM_DIR)/" - -# ============================================================================ -# Deterministic State Coverage - For Coverage Comparison -# ============================================================================ - -# Compile deterministic testbench with coverage -compile-deterministic-coverage: check-stg - @echo "Compiling deterministic testbench with Verilator (coverage enabled)..." - $(STG) generate-fsm \ - $(DUT) \ - --golden $(GOLDEN) \ - --out $(TB_OUT) \ - --out-exe $(TB_EXE) \ - --clock $(CLOCK) \ - --reset $(RESET) \ - --reset-active $(RESET_ACTIVE) \ - --random-samples $(RANDOM_SAMPLES) \ - --fsm-method deterministic \ - --verilator-coverage - @echo "Compiled with coverage: $(TB_EXE)" - -# Run deterministic testbench with coverage -run-deterministic-coverage: $(TB_EXE) - @echo "Running deterministic testbench..." - @echo "========================================" - ./$(TB_EXE) - @mv coverage.dat $(COVERAGE_DETERMINISTIC_DAT) 2>/dev/null || true - @echo "========================================" - @echo "Coverage data written to: $(COVERAGE_DETERMINISTIC_DAT)" - -# Generate deterministic coverage report -coverage-deterministic: $(COVERAGE_DETERMINISTIC_DAT) - @echo "Generating deterministic coverage report..." - @mkdir -p $(COVERAGE_DETERMINISTIC_DIR) - verilator_coverage --annotate $(COVERAGE_DETERMINISTIC_DIR) $(COVERAGE_DETERMINISTIC_DAT) - @echo "Coverage report generated in: $(COVERAGE_DETERMINISTIC_DIR)/" - -# ============================================================================ -# Coverage Comparison: STG-only vs Deterministic vs LLM-enhanced -# ============================================================================ - -# Coverage directories and data files for deterministic method -COVERAGE_DETERMINISTIC_DIR := coverage_deterministic -COVERAGE_DETERMINISTIC_DAT := coverage_deterministic.dat - -compare-coverage: clean - @echo "============================================================" - @echo "Coverage Comparison: STG-only vs Deterministic vs LLM-enhanced" - @echo "============================================================" - @echo "" - @echo ">>> Step 1: Running Traditional STG (Random Testing)..." - @echo "" - @$(MAKE) generate-traditional - @$(MAKE) compile-traditional-coverage - @$(MAKE) run-traditional-coverage - @$(MAKE) coverage-traditional - @echo "" - @echo ">>> Step 2: Running Deterministic State Coverage..." - @echo "" - @$(MAKE) generate-deterministic - @$(MAKE) compile-coverage - @$(MAKE) run-coverage - @$(MAKE) coverage - @echo "" - @echo ">>> Step 3: Running LLM-enhanced State Coverage..." - @echo "" - @$(MAKE) compile-llm-coverage - @$(MAKE) run-llm-coverage - @$(MAKE) coverage-llm - - @echo "" - @echo "============================================================" - @echo "COVERAGE COMPARISON RESULTS" - @echo "============================================================" - @echo "" - @echo "--- Traditional STG (Random Testing) ---" - @if [ -f $(COVERAGE_TRADITIONAL_DAT) ]; then \ - verilator_coverage $(COVERAGE_TRADITIONAL_DAT) 2>/dev/null | grep "Total coverage" || echo " See $(COVERAGE_TRADITIONAL_DIR)/"; \ - fi - @echo "" - @echo "--- Deterministic State Coverage ---" - @if [ -f $(COVERAGE_DETERMINISTIC_DAT) ]; then \ - verilator_coverage $(COVERAGE_DETERMINISTIC_DAT) 2>/dev/null | grep "Total coverage" || echo " See $(COVERAGE_DETERMINISTIC_DIR)/"; \ - fi - @echo "" - @echo "--- LLM-enhanced State Coverage (Targeted Testing) ---" - @if [ -f $(COVERAGE_LLM_DAT) ]; then \ - verilator_coverage $(COVERAGE_LLM_DAT) 2>/dev/null | grep "Total coverage" || echo " See $(COVERAGE_LLM_DIR)/"; \ - fi - @echo "" - @echo "============================================================" - @echo "DUT State Coverage Analysis (seq_detector.sv)" - @echo "============================================================" - @echo "" - @echo "--- Traditional STG ---" - @if [ -f $(COVERAGE_TRADITIONAL_DIR)/seq_detector.sv ]; then \ - echo "States hit (lines with hits > 0):"; \ - grep -E "^\s*[0-9]+ +S[0-9]+:" $(COVERAGE_TRADITIONAL_DIR)/seq_detector.sv 2>/dev/null | wc -l | xargs -I {} echo " {} states covered"; \ - echo "States missed (lines with %000000):"; \ - grep -E "^%000000 +S[0-9]+:" $(COVERAGE_TRADITIONAL_DIR)/seq_detector.sv 2>/dev/null | wc -l | xargs -I {} echo " {} states NOT covered"; \ - fi - @echo "" - @echo "--- Deterministic State Coverage ---" - @if [ -f $(COVERAGE_DETERMINISTIC_DIR)/seq_detector.sv ]; then \ - echo "States hit (lines with hits > 0):"; \ - grep -E "^\s*[0-9]+ +S[0-9]+:" $(COVERAGE_DETERMINISTIC_DIR)/seq_detector.sv 2>/dev/null | wc -l | xargs -I {} echo " {} states covered"; \ - echo "States missed (lines with %000000):"; \ - grep -E "^%000000 +S[0-9]+:" $(COVERAGE_DETERMINISTIC_DIR)/seq_detector.sv 2>/dev/null | wc -l | xargs -I {} echo " {} states NOT covered"; \ - fi - @echo "" - @echo "--- LLM-enhanced ---" - @if [ -f $(COVERAGE_LLM_DIR)/seq_detector.sv ]; then \ - echo "States hit (lines with hits > 0):"; \ - grep -E "^\s*[0-9]+ +S[0-9]+:" $(COVERAGE_LLM_DIR)/seq_detector.sv 2>/dev/null | wc -l | xargs -I {} echo " {} states covered"; \ - echo "States missed (lines with %000000):"; \ - grep -E "^%000000 +S[0-9]+:" $(COVERAGE_LLM_DIR)/seq_detector.sv 2>/dev/null | wc -l | xargs -I {} echo " {} states NOT covered"; \ - fi - @echo "" - @echo "============================================================" - @echo "View detailed reports:" - @echo " Traditional: $(COVERAGE_TRADITIONAL_DIR)/seq_detector.sv" - @echo " Deterministic: $(COVERAGE_DETERMINISTIC_DIR)/seq_detector.sv" - @echo " LLM-enhanced: $(COVERAGE_LLM_DIR)/seq_detector.sv" - @echo "============================================================" diff --git a/examples/seq_detector/README.md b/examples/seq_detector/README.md deleted file mode 100644 index 1081c3d..0000000 --- a/examples/seq_detector/README.md +++ /dev/null @@ -1,115 +0,0 @@ -# Mealy Sequence Detector Example - -This example demonstrates the `generate-fsm` feature using a Mealy-style finite state machine that detects the pattern "101001010010100" (15-bit) in a serial input stream. - -## Design Description - -- **Type**: Sequential (clocked) Mealy FSM -- **Pattern**: Detects "101001010010100" (15 bits) -- **Overlapping**: Yes (sliding window detection) -- **DUT**: Traditional FSM with 15 states (no shift register) -- **Golden**: Behavioral model using shift register - -### State Machine - -``` -States (15 states for 15-bit pattern): - S0: Initial state (no match) - S1: Matched "1" - S2: Matched "10" - S3: Matched "101" - S4: Matched "1010" - S5: Matched "10100" - S6: Matched "101001" - S7: Matched "1010010" - S8: Matched "10100101" - S9: Matched "101001010" - S10: Matched "1010010100" - S11: Matched "10100101001" - S12: Matched "101001010010" - S13: Matched "1010010100101" - S14: Matched "10100101001010" - -Detection occurs when in S14 and din=0 (Mealy output) - -Key failure transitions (for sliding window): - S3 --din=1--> S1 ("1011" -> keep "1") - S4 --din=1--> S3 ("10101" -> keep "101") - S9 --din=1--> S3 ("1010010101" -> keep "101") - S12 --din=0--> S5 ("1010010100100" -> keep "10100") - S14 --din=1--> S3 ("101001010010101" -> keep "101") - S14 --din=0--> S5 (DETECT! "101001010010100" -> keep "10100") -``` - -## Files - -- `seq_detector.sv` - DUT: Mealy sequence detector (traditional FSM) -- `seq_detector_golden.sv` - Golden model using shift register -- `Makefile` - Build and test automation with coverage support - -## Usage - -### Prerequisites - -1. Set your LLM API key: - ```bash - # For OpenAI - export OPENAI_API_KEY="your-api-key" - - # For Gemini - export GOOGLE_API_KEY="your-api-key" - - # For OpenRouter - export OPENROUTER_API_KEY="your-api-key" - ``` - -2. Install `uv` and make sure it is visible in `$PATH` - -### Running Tests - -```bash -# Generate state coverage testbench (default: OpenAI gpt-4) -make generate - -# Generate with different LLM providers -make generate LM_PROVIDER=gemini LM_NAME=gemini-1.5-pro -make generate LM_PROVIDER=openrouter LM_NAME=anthropic/claude-3-opus - -# Compile and run the testbench -make run - -# Clean generated files -make clean - -# Full test (generate + compile + run) -make test -``` - -### Manual Usage - -```bash -# Using stg generate-fsm directly -../../target/release/stg generate-fsm \ - seq_detector.sv \ - --golden seq_detector_golden.sv \ - --out tb_state_coverage.sv \ - --out-exe tb_state_coverage_exe \ - --clock clk \ - --reset rst_n \ - --reset-active low \ - --lm-provider openai \ - --lm-name gpt-4 - -# Run the generated testbench -./tb_state_coverage_exe -``` - -## Expected Output - -The LLM should identify: -- 4-5 states (S0, S1, S2, S3, possibly S4) -- 8 transitions covering all state changes -- Control signal: `din` -- Clock: `clk`, Reset: `rst_n` (active low) - -The DFS algorithm will generate test sequences to cover all state transitions. diff --git a/examples/traffic_light/Makefile b/examples/traffic_light/Makefile deleted file mode 100644 index dc3e9ed..0000000 --- a/examples/traffic_light/Makefile +++ /dev/null @@ -1,419 +0,0 @@ -# Makefile for Traffic Light Controller - Coverage Comparison -# Compares traditional STG (random testing) vs coverage-enhanced STG (DFS-driven) - -# Paths -STG := cargo run -- -DUT := traffic_light_controller.sv -GOLDEN := traffic_light_controller_golden.sv - -# Enhanced state coverage outputs -TB_ENHANCED := tb_state_coverage.cpp -TB_ENHANCED_EXE := tb_state_coverage_exe - -# Analysis JSON files (method-specific) -ANALYSIS_JSON_DETERMINISTIC := tb_state_coverage.deterministic.state_analysis.json -ANALYSIS_JSON_LLM := tb_state_coverage.llm.state_analysis.json - -# Traditional STG outputs -TB_TRADITIONAL := tb_traditional.sv -TB_TRADITIONAL_EXE := tb_traditional_exe - -# Coverage outputs -COVERAGE_DETERMINISTIC_DIR := coverage_deterministic -COVERAGE_DETERMINISTIC_DAT := coverage_deterministic.dat -COVERAGE_LLM_DIR := coverage_llm -COVERAGE_LLM_DAT := coverage_llm.dat -COVERAGE_TRADITIONAL_DIR := coverage_traditional -COVERAGE_TRADITIONAL_DAT := coverage_traditional.dat - -# LLM Configuration (can be overridden from command line) -LM_PROVIDER ?= gemini -LM_NAME ?= gemini-2.5-flash -LM_ENDPOINT ?= - -# Design Configuration -CLOCK := clk -RESET := rst_n -RESET_ACTIVE := low -RANDOM_SAMPLES := 32 - -.PHONY: all help check-stg check-api-key -.PHONY: generate generate-deterministic generate-compile-deterministic compile run test -.PHONY: compile-coverage run-coverage coverage show-coverage test-coverage -.PHONY: generate-traditional compile-traditional run-traditional test-traditional -.PHONY: compile-traditional-coverage run-traditional-coverage coverage-traditional test-traditional-coverage -.PHONY: compare-coverage clean - -# Default target -all: help - -# Help message -help: - @echo "Traffic Light Controller - Coverage Comparison" - @echo "===============================================" - @echo "" - @echo "Enhanced State Coverage (DFS-driven testing):" - @echo " make generate - Generate C++ testbench (LLM-based FSM)" - @echo " make generate-deterministic - Generate C++ testbench (deterministic FSM)" - @echo " make generate-compile-deterministic - Generate + compile (deterministic, no LLM)" - @echo " make compile - Compile the generated testbench" - @echo " make run - Run the compiled testbench" - @echo " make test - Full: generate + compile + run" - @echo "" - @echo "Traditional STG (random testing):" - @echo " make generate-traditional - Generate SV testbench (random only)" - @echo " make test-traditional - Full: generate + compile + run" - @echo "" - @echo "Coverage Comparison:" - @echo " make test-coverage - Enhanced STG with Verilator coverage" - @echo " make test-traditional-coverage - Traditional STG with Verilator coverage" - @echo " make compare-coverage - Run all three methods and compare results" - @echo "" - @echo "LLM Options (can be overridden):" - @echo " LM_PROVIDER=gemini - LLM provider: openai, gemini, openrouter" - @echo " LM_NAME=gemini-2.5-flash - Model name" - @echo " LM_ENDPOINT= - Custom API endpoint (optional)" - @echo "" - @echo "Examples:" - @echo " make compare-coverage # Three-way comparison" - @echo " make test LM_PROVIDER=gemini LM_NAME=gemini-2.5-flash # Use Gemini" - @echo " make generate-compile-deterministic # Quick no-LLM test" - @echo "" - -# Check if stg binary exists -check-stg: - @if [ ! -f $(STG) ]; then \ - echo "Error: stg binary not found at $(STG)"; \ - echo "Please run 'cargo build --release' in the project root first."; \ - exit 1; \ - fi - -# Check if API key is set -check-api-key: - @if [ "$(LM_PROVIDER)" = "openai" ] && [ -z "$$OPENAI_API_KEY" ]; then \ - echo "Error: OPENAI_API_KEY environment variable not set"; \ - exit 1; \ - fi - @if [ "$(LM_PROVIDER)" = "gemini" ] && [ -z "$$GOOGLE_API_KEY" ]; then \ - echo "Error: GOOGLE_API_KEY environment variable not set"; \ - exit 1; \ - fi - -# ============================================================================ -# Enhanced State Coverage (DFS-driven) -# ============================================================================ - -# Generate state coverage testbench (LM-based) -generate: check-stg check-api-key - @echo "Generating state coverage testbench (LM-based)..." - @echo " LLM Provider: $(LM_PROVIDER)" - @echo " Model: $(LM_NAME)" - $(STG) generate-fsm \ - $(DUT) \ - --golden $(GOLDEN) \ - --out $(TB_ENHANCED) \ - --clock $(CLOCK) \ - --reset $(RESET) \ - --reset-active $(RESET_ACTIVE) \ - --random-samples $(RANDOM_SAMPLES) \ - --fsm-method lm \ - --lm-provider $(LM_PROVIDER) \ - --lm-name $(LM_NAME) \ - $(if $(LM_ENDPOINT),--lm-endpoint $(LM_ENDPOINT),) - @echo "" - @echo "Generated: $(TB_ENHANCED)" - @echo "" - @echo "State analysis summary:" - @if [ -f $(ANALYSIS_JSON_LLM) ]; then \ - python3 -c "import json; d=json.load(open('$(ANALYSIS_JSON_LLM)')); print(' State machines:', len(d.get('state_machines', []))); [print(' -', sm.get('state_variable') + ':', len(sm.get('states', [])), 'states,', len(sm.get('transitions', [])), 'transitions') for sm in d.get('state_machines', [])]"; \ - fi - -# Generate state coverage testbench (deterministic, no LLM needed) -generate-deterministic: check-stg - @echo "Generating state coverage testbench (deterministic)..." - $(STG) generate-fsm \ - $(DUT) \ - --golden $(GOLDEN) \ - --out $(TB_ENHANCED) \ - --clock $(CLOCK) \ - --reset $(RESET) \ - --reset-active $(RESET_ACTIVE) \ - --random-samples $(RANDOM_SAMPLES) \ - --fsm-method deterministic - @echo "" - @echo "Generated: $(TB_ENHANCED)" - @echo "State analysis: $(ANALYSIS_JSON_DETERMINISTIC)" - -# Generate + compile in one step (deterministic) -generate-compile-deterministic: check-stg - @echo "Generating and compiling state coverage testbench (deterministic)..." - $(STG) generate-fsm \ - $(DUT) \ - --golden $(GOLDEN) \ - --out $(TB_ENHANCED) \ - --out-exe $(TB_ENHANCED_EXE) \ - --clock $(CLOCK) \ - --reset $(RESET) \ - --reset-active $(RESET_ACTIVE) \ - --random-samples $(RANDOM_SAMPLES) \ - --fsm-method deterministic - @echo "Generated and compiled: $(TB_ENHANCED_EXE)" - -# Compile C++ testbench -compile: $(TB_ENHANCED) - @echo "Compiling C++ testbench with Verilator..." - $(STG) generate-fsm \ - $(DUT) \ - --golden $(GOLDEN) \ - --out $(TB_ENHANCED) \ - --out-exe $(TB_ENHANCED_EXE) \ - --clock $(CLOCK) \ - --reset $(RESET) \ - --reset-active $(RESET_ACTIVE) \ - --random-samples $(RANDOM_SAMPLES) \ - --fsm-method deterministic - @mv tb_state_coverage.state_analysis.json $(ANALYSIS_JSON_DETERMINISTIC) 2>/dev/null || true - @echo "Compiled: $(TB_ENHANCED_EXE)" - -# Compile with Verilator coverage enabled (C++ testbench) -compile-coverage: check-stg $(TB_ENHANCED) - @echo "Compiling C++ testbench with Verilator (coverage enabled)..." - $(STG) generate-fsm \ - $(DUT) \ - --golden $(GOLDEN) \ - --out $(TB_ENHANCED) \ - --out-exe $(TB_ENHANCED_EXE) \ - --clock $(CLOCK) \ - --reset $(RESET) \ - --reset-active $(RESET_ACTIVE) \ - --random-samples $(RANDOM_SAMPLES) \ - --fsm-method deterministic \ - --verilator-coverage - @mv tb_state_coverage.state_analysis.json $(ANALYSIS_JSON_DETERMINISTIC) 2>/dev/null || true - @echo "Compiled with coverage: $(TB_ENHANCED_EXE)" - -# Run testbench -run: $(TB_ENHANCED_EXE) - @echo "Running enhanced state coverage testbench..." - @echo "========================================" - ./$(TB_ENHANCED_EXE) - @echo "========================================" - @echo "Test completed." - -# Run testbench with coverage -run-coverage: $(TB_ENHANCED_EXE) - @echo "Running deterministic testbench with coverage..." - @echo "========================================" - ./$(TB_ENHANCED_EXE) - @mv coverage.dat $(COVERAGE_DETERMINISTIC_DAT) 2>/dev/null || true - @echo "========================================" - @echo "Coverage data: $(COVERAGE_DETERMINISTIC_DAT)" - -# Generate coverage report -coverage: $(COVERAGE_DETERMINISTIC_DAT) - @echo "Generating deterministic coverage report..." - @mkdir -p $(COVERAGE_DETERMINISTIC_DIR) - @verilator_coverage --annotate $(COVERAGE_DETERMINISTIC_DIR) $(COVERAGE_DETERMINISTIC_DAT) - @echo "Report: $(COVERAGE_DETERMINISTIC_DIR)/" - -# Full test pipeline -test: generate-compile-deterministic run - -# Full test with coverage -test-coverage: generate-deterministic compile-coverage run-coverage coverage - @echo "" - @echo "Deterministic state coverage test with coverage completed!" - @echo "View detailed report: ls $(COVERAGE_DETERMINISTIC_DIR)/" - -# ============================================================================ -# Traditional STG (Random Testing) -# ============================================================================ - -# Generate traditional testbench using stg generate -generate-traditional: check-stg - @echo "Generating traditional testbench (random only)..." - $(STG) generate \ - $(DUT) \ - --golden $(GOLDEN) \ - --type seq_clocked \ - --out $(TB_TRADITIONAL) \ - --clock $(CLOCK) \ - --reset $(RESET) \ - --reset-active $(RESET_ACTIVE) \ - --random-samples $(RANDOM_SAMPLES) - @echo "Generated: $(TB_TRADITIONAL)" - -# Compile traditional testbench with coverage -compile-traditional-coverage: check-stg $(TB_TRADITIONAL) - @echo "Compiling traditional testbench with Verilator (coverage enabled)..." - $(STG) compile \ - $(DUT) \ - --golden $(GOLDEN) \ - --testbench $(TB_TRADITIONAL) \ - --out-exe $(TB_TRADITIONAL_EXE) \ - --verilator \ - --verilator-coverage - @echo "Compiled with coverage: $(TB_TRADITIONAL_EXE)" - -# Run traditional testbench with coverage -run-traditional-coverage: $(TB_TRADITIONAL_EXE) - @echo "Running traditional testbench..." - @echo "========================================" - ./$(TB_TRADITIONAL_EXE) - @mv coverage.dat $(COVERAGE_TRADITIONAL_DAT) 2>/dev/null || true - @echo "========================================" - @echo "Coverage data: $(COVERAGE_TRADITIONAL_DAT)" - -# Generate traditional coverage report -coverage-traditional: $(COVERAGE_TRADITIONAL_DAT) - @echo "Generating traditional coverage report..." - @mkdir -p $(COVERAGE_TRADITIONAL_DIR) - verilator_coverage --annotate $(COVERAGE_TRADITIONAL_DIR) $(COVERAGE_TRADITIONAL_DAT) - @echo "Report: $(COVERAGE_TRADITIONAL_DIR)/" - -# Full traditional test with coverage -test-traditional-coverage: generate-traditional compile-traditional-coverage run-traditional-coverage coverage-traditional - @echo "" - @echo "Traditional STG test with coverage completed!" - -# ============================================================================ -# LLM-Enhanced State Coverage - For Coverage Comparison -# ============================================================================ - -# Compile LLM-enhanced testbench with coverage -compile-llm-coverage: check-stg check-api-key - @echo "Generating and compiling LLM-enhanced testbench with Verilator (coverage enabled)..." - $(STG) generate-fsm \ - $(DUT) \ - --golden $(GOLDEN) \ - --out $(TB_ENHANCED) \ - --out-exe $(TB_ENHANCED_EXE) \ - --clock $(CLOCK) \ - --reset $(RESET) \ - --reset-active $(RESET_ACTIVE) \ - --random-samples $(RANDOM_SAMPLES) \ - --fsm-method lm \ - --lm-provider $(LM_PROVIDER) \ - --lm-name $(LM_NAME) \ - $(if $(LM_ENDPOINT),--lm-endpoint $(LM_ENDPOINT),) \ - --verilator-coverage - @mv tb_state_coverage.state_analysis.json $(ANALYSIS_JSON_LLM) 2>/dev/null || true - @echo "Compiled with coverage: $(TB_ENHANCED_EXE)" - -# Run LLM-enhanced testbench with coverage -run-llm-coverage: $(TB_ENHANCED_EXE) - @echo "Running LLM-enhanced testbench..." - @echo "========================================" - ./$(TB_ENHANCED_EXE) - @mv coverage.dat $(COVERAGE_LLM_DAT) 2>/dev/null || true - @echo "========================================" - @echo "Coverage data: $(COVERAGE_LLM_DAT)" - -# Generate LLM-enhanced coverage report -coverage-llm: $(COVERAGE_LLM_DAT) - @echo "Generating LLM-enhanced coverage report..." - @mkdir -p $(COVERAGE_LLM_DIR) - @verilator_coverage --annotate $(COVERAGE_LLM_DIR) $(COVERAGE_LLM_DAT) - @echo "Report: $(COVERAGE_LLM_DIR)/" - -# Run traditional test (no coverage) -test-traditional: generate-traditional - @echo "Compiling and running traditional testbench..." - $(STG) compile \ - $(DUT) \ - --golden $(GOLDEN) \ - --testbench $(TB_TRADITIONAL) \ - --out-exe $(TB_TRADITIONAL_EXE) \ - --verilator - ./$(TB_TRADITIONAL_EXE) - -# ============================================================================ -# Coverage Comparison: Traditional STG vs Deterministic vs LLM-enhanced -# ============================================================================ - -compare-coverage: clean - @echo "============================================================" - @echo "Coverage Comparison: STG-only vs Deterministic vs LLM-enhanced" - @echo "============================================================" - @echo "" - @echo ">>> Step 1: Running Traditional STG (random testing)..." - @echo "" - @$(MAKE) generate-traditional - @$(MAKE) compile-traditional-coverage - @$(MAKE) run-traditional-coverage - @$(MAKE) coverage-traditional - @echo "" - @echo ">>> Step 2: Running Deterministic State Coverage..." - @echo "" - @$(MAKE) generate-deterministic - @$(MAKE) compile-coverage - @$(MAKE) run-coverage - @$(MAKE) coverage - @echo "" - @echo ">>> Step 3: Running LLM-enhanced State Coverage..." - @echo "" - @$(MAKE) compile-llm-coverage - @$(MAKE) run-llm-coverage - @$(MAKE) coverage-llm - @echo "" - @echo "============================================================" - @echo "COVERAGE COMPARISON RESULTS" - @echo "============================================================" - @echo "" - @echo "--- Traditional STG (Random Testing) ---" - @if [ -f $(COVERAGE_TRADITIONAL_DAT) ]; then \ - verilator_coverage $(COVERAGE_TRADITIONAL_DAT) 2>/dev/null | grep -i "total\|point" || echo " See $(COVERAGE_TRADITIONAL_DIR)/"; \ - fi - @echo "" - @echo "--- Deterministic State Coverage ---" - @if [ -f $(COVERAGE_DETERMINISTIC_DAT) ]; then \ - verilator_coverage $(COVERAGE_DETERMINISTIC_DAT) 2>/dev/null | grep -i "total\|point" || echo " See $(COVERAGE_DETERMINISTIC_DIR)/"; \ - fi - @echo "" - @echo "--- LLM-enhanced State Coverage (Targeted Testing) ---" - @if [ -f $(COVERAGE_LLM_DAT) ]; then \ - verilator_coverage $(COVERAGE_LLM_DAT) 2>/dev/null | grep -i "total\|point" || echo " See $(COVERAGE_LLM_DIR)/"; \ - fi - @echo "" - @echo "============================================================" - @echo "DUT State Coverage Analysis ($(DUT))" - @echo "============================================================" - @echo "" - @echo "--- Traditional STG ---" - @if [ -f $(COVERAGE_TRADITIONAL_DIR)/$(DUT) ]; then \ - echo "Lines hit (hits > 0):"; \ - grep -cE "^\s+[0-9]+" $(COVERAGE_TRADITIONAL_DIR)/$(DUT) 2>/dev/null | xargs -I {} echo " {} lines covered"; \ - echo "Lines missed (%000000):"; \ - grep -c "%000000" $(COVERAGE_TRADITIONAL_DIR)/$(DUT) 2>/dev/null | xargs -I {} echo " {} lines NOT covered"; \ - fi - @echo "" - @echo "--- Deterministic State Coverage ---" - @if [ -f $(COVERAGE_DETERMINISTIC_DIR)/$(DUT) ]; then \ - echo "Lines hit (hits > 0):"; \ - grep -cE "^\s+[0-9]+" $(COVERAGE_DETERMINISTIC_DIR)/$(DUT) 2>/dev/null | xargs -I {} echo " {} lines covered"; \ - echo "Lines missed (%000000):"; \ - grep -c "%000000" $(COVERAGE_DETERMINISTIC_DIR)/$(DUT) 2>/dev/null | xargs -I {} echo " {} lines NOT covered"; \ - fi - @echo "" - @echo "--- LLM-enhanced ---" - @if [ -f $(COVERAGE_LLM_DIR)/$(DUT) ]; then \ - echo "Lines hit (hits > 0):"; \ - grep -cE "^\s+[0-9]+" $(COVERAGE_LLM_DIR)/$(DUT) 2>/dev/null | xargs -I {} echo " {} lines covered"; \ - echo "Lines missed (%000000):"; \ - grep -c "%000000" $(COVERAGE_LLM_DIR)/$(DUT) 2>/dev/null | xargs -I {} echo " {} lines NOT covered"; \ - fi - @echo "" - @echo "============================================================" - @echo "View detailed reports:" - @echo " Traditional: $(COVERAGE_TRADITIONAL_DIR)/$(DUT)" - @echo " Deterministic: $(COVERAGE_DETERMINISTIC_DIR)/$(DUT)" - @echo " LLM-enhanced: $(COVERAGE_LLM_DIR)/$(DUT)" - @echo "============================================================" - -# Clean generated files -clean: - rm -f $(TB_ENHANCED) $(TB_ENHANCED_EXE) $(ANALYSIS_JSON_DETERMINISTIC) $(ANALYSIS_JSON_LLM) - rm -f $(TB_TRADITIONAL) $(TB_TRADITIONAL_EXE) - rm -f *.vcd *.dat test_stats.json - rm -rf obj_dir $(COVERAGE_DETERMINISTIC_DIR) $(COVERAGE_LLM_DIR) $(COVERAGE_TRADITIONAL_DIR) - @echo "Cleaned generated files." diff --git a/examples/traffic_light/README.md b/examples/traffic_light/README.md deleted file mode 100644 index 3564c77..0000000 --- a/examples/traffic_light/README.md +++ /dev/null @@ -1,107 +0,0 @@ -# Traffic Light Controller Example - -This example demonstrates the coverage advantage of `generate-fsm` (DFS-driven testing) versus traditional STG (random testing) using a traffic light controller with internal counters. - -## Design Description - -- **Type**: Sequential (clocked) FSM with internal timer -- **States**: 8 states controlling a main/side road intersection with pedestrian crossing -- **Inputs**: `clk`, `rst_n`, `sensor` (side road vehicle), `ped_request` (pedestrian button) -- **Outputs**: `main_light[1:0]`, `side_light[1:0]`, `ped_signal`, `walk_active` -- **No latches**: All signals assigned in all branches via `always_comb` defaults - -### Why This Is Hard for Random Testing - -The FSM uses an **internal counter** (`timer`) to gate most state transitions. For example: -- Reaching `S_SIDE_GREEN` from reset requires **15 clock cycles** (10 green + 3 yellow + 2 all-red) AND `sensor=1` at the right moment -- Reaching `S_PED_WALK` requires `ped_request=1` to be latched AND the timer to expire in `S_ALL_RED_1` or `S_ALL_RED_2` without `sensor=1` -- Random toggling of inputs is unlikely to satisfy these multi-cycle timing constraints - -### State Machine - -``` - timer >= 9 - S_MAIN_GREEN ──────────────────────────> S_MAIN_YELLOW - ^ | - | timer >= 2 - | v - | !sensor && !ped_latch S_ALL_RED_1 - +<─────────────────────────────┐ | - | | timer >= 1 - | | ┌────┴────┐ - | | | | - | !sensor sensor ped_latch - | !ped | | - | | v v - | | S_SIDE_GREEN S_PED_WALK - | | | | - | | timer>=9 timer>=5 - | | v v - | | S_SIDE_YELLOW S_PED_CLEAR - | | | | - | | timer>=2 timer>=3 - | | v | - | !ped_latch | S_ALL_RED_2 | - +<─────────────────────────────+────┘ | - +<────────────────────────────────────────────┘ -``` - -## Files - -- `traffic_light_controller.sv` - DUT: 8-state FSM with internal timer -- `traffic_light_controller_golden.sv` - Golden model: independent behavioral implementation -- `Makefile` - Build automation with coverage comparison support - -## Usage - -### Quick Test (no LLM needed) - -```bash -# Build stg first (from project root) -cargo build --release - -# Generate, compile, and run with deterministic FSM extraction -make generate-compile-deterministic -make run -``` - -### Coverage Comparison - -```bash -# Compare traditional (random) vs enhanced (DFS-driven) coverage -make compare-coverage -``` - -This runs both approaches with Verilator coverage instrumentation and shows side-by-side results. - -### Individual Commands - -```bash -# Enhanced state coverage (deterministic - no LLM) -make generate-deterministic # Generate C++ testbench -make compile # Compile with Verilator -make run # Execute - -# Enhanced state coverage (LLM-based) -make generate LM_PROVIDER=gemini LM_NAME=gemini-2.5-flash - -# Traditional STG (random testing) -make test-traditional - -# Clean all generated files -make clean -``` - -## Expected Results - -The coverage comparison should show that: - -1. **Traditional STG** (random inputs): Likely misses `S_PED_WALK` and `S_PED_CLEAR` states because reaching them requires: - - `ped_request` to be asserted and latched - - `sensor` to be low when `S_ALL_RED_1` timer expires - - All within the right timing window - -2. **Enhanced state coverage** (DFS-driven): Achieves full state/transition coverage because it: - - Identifies the FSM structure and internal counter conditions - - Generates targeted input sequences via DFS traversal - - Uses hierarchical signal access to monitor internal state diff --git a/src/cli.rs b/src/cli.rs index 35890b9..b126bdd 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -24,9 +24,6 @@ pub enum Commands { Compile(Box), /// Parse Verilog modules and print the module priority list Parse(Box), - /// Generate a state-coverage-enhanced C++ testbench using FSM analysis (LM or deterministic) - #[command(name = "generate-fsm")] - GenerateFSM(Box), } #[derive(Args, Debug)] @@ -326,118 +323,6 @@ impl ParseArgs { } } -#[derive(Args, Debug)] -pub struct GenerateFSMArgs { - /// 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(','))] - pub module: Option>, - - /// Path to golden reference Verilog/SystemVerilog file - #[arg(long, required = true)] - pub golden: PathBuf, - - /// Golden module name (if file has multiple) - #[arg(long)] - pub golden_module: Option, - - /// Output testbench path (.cpp) - #[arg(long, required = true)] - pub out: PathBuf, - - /// Output executable path. If not specified, no executable will be generated - #[arg(long)] - pub out_exe: Option, - - /// Clock signal name - #[arg(long)] - pub clock: Option, - - /// Reset signal name - #[arg(long)] - pub reset: Option, - - /// Reset polarity - #[arg(long, value_parser = ["high", "low", "unknown"])] - pub reset_active: Option, - - /// Random samples for additional random testing after DFS sequences - #[arg(long, default_value_t = 32)] - pub random_samples: i64, - - /// FSM identification method - #[arg(long, value_parser = ["lm", "deterministic"], default_value = "lm")] - pub fsm_method: String, - - /// LLM provider (for --fsm-method lm) - #[arg(long, value_parser = ["openai", "gemini", "openrouter"])] - pub lm_provider: Option, - - /// LLM model name (for --fsm-method lm) - #[arg(long)] - pub lm_name: Option, - - /// Custom LLM API endpoint (for --fsm-method lm) - #[arg(long)] - pub lm_endpoint: Option, - - /// Path to state analysis JSON file. If the file exists, it will be loaded - /// (skipping FSM identification). If it does not exist, FSM analysis will run - /// and the result will be saved to this path for future reuse. - #[arg(long)] - pub state_analysis: Option, - - /// Ignore some Verilator warnings: WIDTHTRUNC, WIDTHCONCAT, WIDTHEXPAND - #[arg(long, default_value_t = true)] - pub verilator_ignore_warnings: bool, - - /// Number of jobs to use for Verilator compilation - #[arg(long, default_value_t = 1)] - pub verilator_jobs: i32, - - /// Enable Verilator coverage analysis - #[arg(long, default_value_t = false)] - pub verilator_coverage: bool, - - /// Compile flags - #[arg(long)] - pub compile_flags: Option>, - - /// Transition timeout in clock cycles (for wait conditions) - #[arg(long, default_value_t = 100)] - pub transition_timeout: i64, - - /// Number of DFS passes with randomized data signals for edge coverage - #[arg(long, default_value_t = 5)] - pub dfs_passes: i64, - - /// Enable debug mode - #[arg(long, default_value_t = false)] - pub debug: bool, -} - -impl GenerateFSMArgs { - /// 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/generate_fsm.rs b/src/commands/generate_fsm.rs deleted file mode 100644 index c1f09cb..0000000 --- a/src/commands/generate_fsm.rs +++ /dev/null @@ -1,713 +0,0 @@ -use crate::cli::GenerateFSMArgs; -use crate::tools::{ - compiler::{CompileArgs, compile_state_coverage_cpp_verilator}, - fsm_analysis::{FsmAnalysis, load_fsm_analysis, save_fsm_analysis}, - fsm_extractor, - verilog_parser::{ModuleInfo, PortInfo, parse_verilog_modules}, -}; -use anyhow::{Context, Result}; -use serde::Serialize; -use std::fs; -use std::path::PathBuf; -use std::process::Command; -use tera::{Context as TeraContext, Tera}; - -fn load_state_coverage_cpp_template() -> Result { - Ok(include_str!("../templates/testbench_state_coverage_cpp.j2").to_string()) -} - -#[derive(Debug, Clone, Serialize)] -struct SerializablePortInfo { - name: String, - direction: String, - width: usize, -} - -impl From<&PortInfo> for SerializablePortInfo { - fn from(port: &PortInfo) -> Self { - Self { - name: port.name.clone(), - direction: port.direction.clone(), - width: port.width, - } - } -} - -/// Template-specific state machine representation with numeric state values. -/// The DFS algorithm is now implemented in C++, so we only pass the graph data. -#[derive(Debug, Clone, Serialize)] -struct TemplateStateMachine { - state_variable: String, - state_type: String, - initial_state: String, - states: Vec, - transitions: Vec, - control_signals: Vec, - data_signals: Vec, - internal_signals: Vec, - parameters: Vec, -} - -/// Template-specific state with both the original value string and a parsed numeric value. -#[derive(Debug, Clone, Serialize)] -struct TemplateState { - name: String, - value: String, - value_numeric: u64, -} - -/// Parse a Verilog state value string (e.g., "3'd0", "4'b1010", "0") to a numeric u64. -fn parse_state_value(value: &str) -> u64 { - let trimmed = value.trim(); - // Try plain integer - if let Ok(v) = trimmed.parse::() { - return v; - } - // Try Verilog literal - if let Some(pos) = trimmed.find('\'') - && pos + 2 <= trimmed.len() { - let base_char = trimmed.as_bytes().get(pos + 1).copied().unwrap_or(b'd'); - let digits = &trimmed[pos + 2..]; - let radix = match base_char { - b'b' | b'B' => 2, - b'o' | b'O' => 8, - b'd' | b'D' => 10, - b'h' | b'H' => 16, - _ => 10, - }; - return u64::from_str_radix(digits, radix).unwrap_or(0); - } - 0 -} - -pub fn run_generate_fsm(args: GenerateFSMArgs) -> Result<()> { - let verilog_files = args.get_verilog_files()?; - let verilog_paths = verilog_files - .iter() - .map(|path| { - path.canonicalize() - .with_context(|| format!("Failed to canonicalize DUT file: {}", path.display())) - }) - .collect::>>()?; - - let golden_path = args.golden.canonicalize().with_context(|| { - format!( - "Failed to canonicalize golden file: {}", - args.golden.display() - ) - })?; - - let out_path = std::path::absolute(&args.out) - .with_context(|| format!("Failed to resolve output file: {}", args.out.display()))?; - - // ======================================================================== - // Step 1: Parse DUT and golden Verilog files - // ======================================================================== - log::info!("Parsing DUT Verilog files..."); - let all_dut_modules: Vec> = verilog_paths - .iter() - .map(|path| { - parse_verilog_modules(path) - .with_context(|| format!("Failed to parse DUT file: {}", path.display())) - }) - .collect::>>>()?; - - for (idx, modules) in all_dut_modules.iter().enumerate() { - if modules.is_empty() { - anyhow::bail!( - "No modules found in DUT file {}", - verilog_paths[idx].display() - ); - } - } - - log::info!("Parsing golden Verilog file..."); - let golden_modules = parse_verilog_modules(&golden_path) - .with_context(|| format!("Failed to parse golden file: {}", golden_path.display()))?; - if golden_modules.is_empty() { - anyhow::bail!("No modules found in golden file {}", golden_path.display()); - } - - // Select DUT module - let dut_module = if let Some(ref module_names) = args.module { - let name = &module_names[0]; - all_dut_modules[0] - .iter() - .find(|m| &m.name == name) - .with_context(|| { - format!( - "DUT module '{}' not found in {}", - name, - verilog_paths[0].display() - ) - })? - .clone() - } else { - if all_dut_modules[0].len() > 1 { - log::warn!( - "Multiple DUT modules found, using first: {}", - all_dut_modules[0][0].name - ); - } - all_dut_modules[0][0].clone() - }; - - // Select golden module - let golden_module = if let Some(ref name) = args.golden_module { - golden_modules - .iter() - .find(|m| &m.name == name) - .with_context(|| format!("Golden module '{}' not found", name))? - .clone() - } else { - if golden_modules.len() > 1 { - log::warn!( - "Multiple golden modules found, using first: {}", - golden_modules[0].name - ); - } - golden_modules[0].clone() - }; - - // Resolve clock and reset names - let clock_name = args.clock.as_deref().unwrap_or("clk").to_string(); - let reset_name = args.reset.as_deref().unwrap_or("rst").to_string(); - let reset_active_high = args - .reset_active - .as_deref() - .map(|s| s == "high") - .unwrap_or(false); - - log::info!("DUT module: {}", dut_module.name); - log::info!("Golden module: {}", golden_module.name); - log::info!( - "Clock: {}, Reset: {} (active {})", - clock_name, - reset_name, - if reset_active_high { "high" } else { "low" } - ); - - // ======================================================================== - // Step 2: FSM Identification - // ======================================================================== - let analysis = if let Some(ref state_analysis_path) = args.state_analysis { - if state_analysis_path.exists() { - // Use pre-computed analysis (cache hit) - log::info!( - "Loading pre-computed state analysis from: {}", - state_analysis_path.display() - ); - load_fsm_analysis(state_analysis_path)? - } else { - // File doesn't exist yet — run analysis and save to this path - log::info!( - "State analysis file not found at: {}. Running FSM analysis...", - state_analysis_path.display() - ); - if args.fsm_method == "lm" { - run_lm_fsm_analysis( - &verilog_paths[0], - &out_path, - args.lm_provider.as_deref(), - args.lm_name.as_deref(), - args.lm_endpoint.as_deref(), - )? - } else { - log::info!("Running deterministic FSM extraction..."); - fsm_extractor::extract_fsm( - &verilog_paths[0], - &dut_module, - &clock_name, - &reset_name, - reset_active_high, - )? - } - } - } else if args.fsm_method == "lm" { - // LM-based FSM identification - run_lm_fsm_analysis( - &verilog_paths[0], - &out_path, - args.lm_provider.as_deref(), - args.lm_name.as_deref(), - args.lm_endpoint.as_deref(), - )? - } else { - // Deterministic FSM extraction - log::info!("Running deterministic FSM extraction..."); - fsm_extractor::extract_fsm( - &verilog_paths[0], - &dut_module, - &clock_name, - &reset_name, - reset_active_high, - )? - }; - - // Print summary (before splitting) - for sm in &analysis.state_machines { - log::info!( - "State machine '{}': {} states, {} transitions", - sm.state_variable, - sm.states.len(), - sm.transitions.len() - ); - } - - if analysis.state_machines.is_empty() { - anyhow::bail!( - "No state machines found in the design. Cannot generate state coverage testbench." - ); - } - - // ======================================================================== - // Step 2.5: Parse DUT for signal classification - // ======================================================================== - // Build control signals set for condition splitting - let mut control_signal_names: std::collections::HashSet = - std::collections::HashSet::new(); - control_signal_names.insert(clock_name.clone()); - control_signal_names.insert(reset_name.clone()); - for sm in &analysis.state_machines { - for sig in &sm.control_signals { - control_signal_names.insert(sig.clone()); - } - } - - // Post-process transitions to split input vs wait conditions - let mut analysis = analysis; // Make mutable - for sm in &mut analysis.state_machines { - split_transition_conditions(sm, &control_signal_names); - } - - // Save the analysis JSON — use the user-specified path if provided, - // otherwise derive from the output path - let analysis_path = if let Some(ref state_analysis_path) = args.state_analysis { - state_analysis_path.clone() - } else { - out_path.with_extension("state_analysis.json") - }; - save_fsm_analysis(&analysis, &analysis_path)?; - log::info!("State analysis saved to: {}", analysis_path.display()); - - // ======================================================================== - // Step 3: Prepare template data (graph data for C++ DFS engine) - // ======================================================================== - log::info!("Preparing FSM graph data for C++ DFS engine..."); - - let template_sms: Vec = analysis - .state_machines - .iter() - .map(|sm| TemplateStateMachine { - state_variable: sm.state_variable.clone(), - state_type: sm.state_type.clone(), - initial_state: sm.initial_state.clone(), - states: sm - .states - .iter() - .map(|s| TemplateState { - name: s.name.clone(), - value: s.value.clone(), - value_numeric: parse_state_value(&s.value), - }) - .collect(), - transitions: sm.transitions.clone(), - control_signals: sm.control_signals.clone(), - data_signals: sm.data_signals.clone(), - internal_signals: sm.internal_signals.clone(), - parameters: sm.parameters.clone(), - }) - .collect(); - - let total_edges: usize = template_sms.iter().map(|sm| sm.transitions.len()).sum(); - log::info!( - "FSM graph: {} state machine(s), {} total edges for DFS coverage", - template_sms.len(), - total_edges - ); - - // ======================================================================== - // Step 4: Generate C++ testbench with DFS engine - // ======================================================================== - log::info!("Generating C++ testbench with DFS engine..."); - - let template_text = load_state_coverage_cpp_template()?; - let mut tera = Tera::default(); - tera.add_raw_template("testbench_state_coverage_cpp.j2", &template_text) - .context("Failed to parse state coverage C++ template")?; - - // Gather signal information - let input_ports: Vec<&PortInfo> = dut_module - .ports - .iter() - .filter(|p| p.direction == "input") - .collect(); - - let output_ports: Vec = dut_module - .ports - .iter() - .filter(|p| p.direction == "output") - .map(SerializablePortInfo::from) - .collect(); - - // Build control signals and data signals from the FSM analysis - // (control_signal_names already built earlier for splitting) - let control_signals: Vec = input_ports - .iter() - .filter(|p| { - control_signal_names.contains(&p.name) && p.name != clock_name && p.name != reset_name - }) - .map(|p| SerializablePortInfo::from(*p)) - .collect(); - - let data_signals: Vec = input_ports - .iter() - .filter(|p| { - !control_signal_names.contains(&p.name) && p.name != clock_name && p.name != reset_name - }) - .map(|p| SerializablePortInfo::from(*p)) - .collect(); - - // Collect runtime signals and parameters from all state machines (deduplicated) - let mut all_runtime_signals: Vec = Vec::new(); - let mut all_parameters: Vec = Vec::new(); - for sm in &analysis.state_machines { - for sig in &sm.internal_signals { - if !all_runtime_signals.contains(sig) { - all_runtime_signals.push(sig.clone()); - } - } - for param in &sm.parameters { - if !all_parameters.iter().any(|p| p.name == param.name) { - all_parameters.push(param.clone()); - } - } - } - - log::info!( - "Runtime signals: {:?}, Parameters: {:?}", - all_runtime_signals, - all_parameters - .iter() - .map(|p| format!("{}={}", p.name, p.value)) - .collect::>() - ); - - let mut context = TeraContext::new(); - context.insert("module_name", &analysis.module_name); - context.insert("dut_module_name", &dut_module.name); - context.insert("golden_module_name", &golden_module.name); - context.insert("clock_signal", &clock_name); - context.insert("reset_signal", &reset_name); - context.insert("reset_active_high", &reset_active_high); - context.insert("state_machines", &template_sms); - context.insert("outputs", &output_ports); - context.insert("control_signals", &control_signals); - context.insert("data_signals", &data_signals); - context.insert("runtime_signals", &all_runtime_signals); - context.insert("parameters", &all_parameters); - context.insert("transition_timeout", &args.transition_timeout); - context.insert("dfs_passes", &args.dfs_passes); - context.insert("random_samples", &args.random_samples); - context.insert("debug", &args.debug); - - let cpp_text = tera - .render("testbench_state_coverage_cpp.j2", &context) - .context("Failed to render state coverage C++ testbench template")?; - - // Write output - if let Some(parent) = out_path.parent() { - fs::create_dir_all(parent)?; - } - fs::write(&out_path, &cpp_text)?; - log::info!("Wrote C++ testbench: {}", out_path.display()); - - // ======================================================================== - // Step 5: Compile (if --out-exe specified) - // ======================================================================== - if let Some(out_exe) = args.out_exe { - let out_exe_path = std::path::absolute(&out_exe).with_context(|| { - format!( - "Failed to resolve output executable path: {}", - out_exe.display() - ) - })?; - - log::info!("Compiling testbench..."); - - let mut compile_flags_vec = args.compile_flags.clone(); - if args.verilator_coverage { - let flags = compile_flags_vec.get_or_insert_with(Vec::new); - if !flags.iter().any(|f| f.contains("--coverage")) { - flags.push("--coverage".to_string()); - } - } - - let compile_args = CompileArgs { - emplace_module: false, - verilator_ignore_warnings: args.verilator_ignore_warnings, - verilator_mpi: false, - verilator_jobs: args.verilator_jobs, - compile_flags: compile_flags_vec, - }; - - let success = compile_state_coverage_cpp_verilator( - &compile_args, - &verilog_paths, - &dut_module.name, - &golden_path, - &golden_module.name, - &out_path, - &out_exe_path, - )?; - - if success { - log::info!("Wrote executable: {}", out_exe_path.display()); - } else { - anyhow::bail!("Failed to compile executable: {}", out_exe_path.display()); - } - } - - Ok(()) -} - -/// Run LM-based FSM analysis using the Python script via `uv`. -fn run_lm_fsm_analysis( - verilog_path: &std::path::Path, - out_path: &std::path::Path, - provider: Option<&str>, - model: Option<&str>, - endpoint: Option<&str>, -) -> Result { - let provider = provider.unwrap_or("gemini"); - let model_default = match provider { - "gemini" => "gemini-2.5-flash", - "openai" => "gpt-4", - "openrouter" => "anthropic/claude-3-opus", - _ => "gpt-4", - }; - let model = model.unwrap_or(model_default); - - log::info!( - "Running LM-based FSM analysis (provider={}, model={})...", - provider, - model - ); - - // Get the fsm_analyzer directory from embedded cache - let fsm_analyzer_dir = get_fsm_analyzer_dir()?; - - // Output path for the analysis JSON - let analysis_output = out_path.with_extension("state_analysis.json"); - - // Find .env file - let env_file = find_env_file(verilog_path); - - // Build the uv command - let mut cmd = Command::new("uv"); - cmd.arg("run"); - cmd.arg("--project").arg(&fsm_analyzer_dir); - cmd.arg("fsm-analyzer"); - cmd.arg("--verilog-source").arg(verilog_path); - cmd.arg("--output").arg(&analysis_output); - cmd.arg("--provider").arg(provider); - cmd.arg("--model").arg(model); - - if let Some(ep) = endpoint { - cmd.arg("--endpoint").arg(ep); - } - - if let Some(env_path) = &env_file { - cmd.arg("--env-file").arg(env_path); - } - - log::info!( - "Running: uv run --project {} fsm-analyzer ...", - fsm_analyzer_dir.display() - ); - - let output = cmd - .output() - .context("Failed to run `uv`. Is uv installed? (https://docs.astral.sh/uv/)")?; - - // Log stderr output - let stderr = String::from_utf8_lossy(&output.stderr); - for line in stderr.lines() { - log::info!("[fsm-analyzer] {}", line); - } - - if !output.status.success() { - anyhow::bail!( - "LM-based FSM analysis failed (exit code {:?}):\n{}", - output.status.code(), - stderr - ); - } - - // Load the result - load_fsm_analysis(&analysis_output) -} - -/// Find the scripts directory. Searches: -/// 1. Relative to the current executable -/// 2. Relative to the current working directory -/// 3. Standard project structure locations -/// -/// Get the fsm_analyzer directory path (from embedded cache) -fn get_fsm_analyzer_dir() -> Result { - crate::python_runtime::get_fsm_analyzer_path() -} - -/// Split transition conditions into input_condition (controllable) and wait_condition (internal). -/// A condition is "controllable" if it only references control_signals (module inputs). -/// A condition is "internal" if it references internal_signals (state variables, counters, etc.). -fn split_transition_conditions( - sm: &mut crate::tools::fsm_analysis::StateMachine, - control_signal_names: &std::collections::HashSet, -) { - for trans in &mut sm.transitions { - let cond = if !trans.input_condition.is_empty() { - &trans.input_condition - } else { - &trans.condition - }; - - if cond.is_empty() || cond == "true" { - trans.input_condition = String::new(); - trans.wait_condition = String::new(); - continue; - } - - // Parse the condition to separate input-based parts from internal-based parts - let (input_part, wait_part) = - separate_condition(cond, control_signal_names, &sm.internal_signals); - - trans.input_condition = input_part; - trans.wait_condition = wait_part; - } -} - -/// Separate a condition into input-based and internal-based parts. -/// Returns (input_condition, wait_condition). -fn separate_condition( - cond: &str, - control_signals: &std::collections::HashSet, - internal_signals: &[String], -) -> (String, String) { - // Simple heuristic: check if condition references any internal signal - let has_internal = internal_signals.iter().any(|sig| cond.contains(sig)); - - if !has_internal { - // Only references control signals (or is a simple boolean) - return (cond.to_string(), String::new()); - } - - // Check if condition also references control signals - let has_control = control_signals.iter().any(|sig| cond.contains(sig)); - - if !has_control { - // Only references internal signals - it's a pure wait condition - return (String::new(), cond.to_string()); - } - - // Condition references both - we need to split it - // For now, use a simple split on && or || - // Extract clauses and classify each - let mut input_clauses = Vec::new(); - let mut wait_clauses = Vec::new(); - - for clause in split_condition_clauses(cond) { - let has_int = internal_signals.iter().any(|sig| clause.contains(sig)); - if has_int { - wait_clauses.push(clause); - } else { - input_clauses.push(clause); - } - } - - let input_part = if input_clauses.is_empty() { - String::new() - } else { - input_clauses.join(" && ") - }; - - let wait_part = if wait_clauses.is_empty() { - String::new() - } else { - wait_clauses.join(" && ") - }; - - (input_part, wait_part) -} - -/// Split a condition expression into clauses on && and ||. -/// This is a simple splitter that doesn't handle nested parens perfectly. -fn split_condition_clauses(cond: &str) -> Vec { - let mut clauses = Vec::new(); - let mut current = String::new(); - let mut paren_depth = 0; - - let chars: Vec = cond.chars().collect(); - let mut i = 0; - - while i < chars.len() { - let ch = chars[i]; - - if ch == '(' { - paren_depth += 1; - current.push(ch); - } else if ch == ')' { - paren_depth -= 1; - current.push(ch); - } else if paren_depth == 0 && ch == '&' && i + 1 < chars.len() && chars[i + 1] == '&' { - // Found && at top level - clauses.push(current.trim().to_string()); - current.clear(); - i += 1; // Skip second & - } else if paren_depth == 0 && ch == '|' && i + 1 < chars.len() && chars[i + 1] == '|' { - // Found || at top level - treat as separator for now - clauses.push(current.trim().to_string()); - current.clear(); - i += 1; // Skip second | - } else { - current.push(ch); - } - - i += 1; - } - - if !current.trim().is_empty() { - clauses.push(current.trim().to_string()); - } - - if clauses.is_empty() { - clauses.push(cond.to_string()); - } - - clauses -} - -/// Find .env file by searching upward from the Verilog file location. -fn find_env_file(verilog_path: &std::path::Path) -> Option { - let mut current = verilog_path - .parent() - .and_then(|p| p.canonicalize().ok()) - .unwrap_or_else(|| PathBuf::from(".")); - - for _ in 0..10 { - let env_path = current.join(".env"); - if env_path.exists() { - return Some(env_path); - } - if let Some(parent) = current.parent() { - if parent == current { - break; - } - current = parent.to_path_buf(); - } else { - break; - } - } - - None -} diff --git a/src/commands/parse.rs b/src/commands/parse.rs index eb51c36..0fee5a3 100644 --- a/src/commands/parse.rs +++ b/src/commands/parse.rs @@ -24,7 +24,7 @@ pub fn run_parse(args: ParseArgs) -> Result<()> { 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(" ports:\n"); + 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)); diff --git a/src/lib.rs b/src/lib.rs index e2760ad..b4a1ce0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,9 @@ /// Structured Testbench Generation library pub mod cli; -pub mod python_runtime; pub mod tools { pub mod compiler; pub mod emplace_verilog; pub mod file_utils; - pub mod fsm_analysis; - pub mod fsm_extractor; pub mod generator; pub mod signal_classification; pub mod verilog_parser; @@ -18,7 +15,6 @@ pub mod tools { pub mod commands { pub mod compile; pub mod generate; - pub mod generate_fsm; pub mod identify; pub mod parse; } diff --git a/src/main.rs b/src/main.rs index ffd4599..89091ea 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,6 @@ use std::process; use stg::cli::{Cli, Commands}; use stg::commands::compile::run_compile; use stg::commands::generate::run_generate; -use stg::commands::generate_fsm::run_generate_fsm; use stg::commands::identify::run_identify; use stg::commands::parse::run_parse; @@ -17,7 +16,6 @@ fn main() { Commands::Identify(args) => run_identify(*args), Commands::Compile(args) => run_compile(*args), Commands::Parse(args) => run_parse(*args), - Commands::GenerateFSM(args) => run_generate_fsm(*args), }; if let Err(e) = result { diff --git a/src/python_runtime.rs b/src/python_runtime.rs deleted file mode 100644 index 312c840..0000000 --- a/src/python_runtime.rs +++ /dev/null @@ -1,145 +0,0 @@ -//! Python Runtime Management -//! -//! This module handles embedding, extraction, and execution of Python-based analysis tools. -//! The fsm_analyzer Python package is embedded into the binary at compile time and extracted -//! to a cache directory (~/.stg/ or /tmp/.stg-{uid}) on first use. Dependencies are managed -//! via uv, with caching to avoid redundant syncs across runs. - -use anyhow::{Context, Result}; -use include_dir::{include_dir, Dir}; -use std::fs; -use std::path::{Path, PathBuf}; -use std::process::Command; - -// Embed the fsm_analyzer directory at compile time -static FSM_ANALYZER_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/tools/fsm_analyzer"); - -/// Get or create the STG cache directory -/// Priority: ~/.stg/ > /tmp/.stg-{uid} -pub fn get_cache_dir() -> Result { - // Try home directory first - if let Ok(home) = std::env::var("HOME") { - let cache_dir = Path::new(&home).join(".stg"); - if cache_dir.exists() || fs::create_dir_all(&cache_dir).is_ok() { - return Ok(cache_dir); - } - } - - // Fall back to /tmp with uid suffix for multi-user safety - let uid = unsafe { libc::getuid() }; - let tmp_dir = PathBuf::from(format!("/tmp/.stg-{}", uid)); - fs::create_dir_all(&tmp_dir) - .context("Failed to create cache directory in /tmp")?; - Ok(tmp_dir) -} - -/// Check if uv is installed -pub fn check_uv_installed() -> Result<()> { - Command::new("uv") - .arg("--version") - .output() - .context("uv is not installed or not in PATH. Please install uv: https://docs.astral.sh/uv/getting-started/installation/")?; - Ok(()) -} - -/// Extract embedded fsm_analyzer to cache directory if not present or outdated -/// Returns the path to the extracted fsm_analyzer directory -pub fn extract_fsm_analyzer() -> Result { - let cache_dir = get_cache_dir()?; - let fsm_dir = cache_dir.join("fsm_analyzer"); - - // Check if we need to extract (directory doesn't exist or version mismatch) - let version_file = fsm_dir.join(".version"); - let current_version = env!("CARGO_PKG_VERSION"); - let needs_extraction = !fsm_dir.exists() - || !version_file.exists() - || fs::read_to_string(&version_file).unwrap_or_default().trim() != current_version; - - if needs_extraction { - log::info!("Extracting fsm_analyzer to cache directory: {}", fsm_dir.display()); - - // Remove old directory if it exists - if fsm_dir.exists() { - fs::remove_dir_all(&fsm_dir) - .context("Failed to remove old fsm_analyzer directory")?; - } - - // Extract all files - extract_dir(&FSM_ANALYZER_DIR, &fsm_dir) - .context("Failed to extract fsm_analyzer")?; - - // Write version file - fs::write(&version_file, current_version) - .context("Failed to write version file")?; - - log::info!("fsm_analyzer extracted successfully"); - } else { - log::debug!("Using cached fsm_analyzer from: {}", fsm_dir.display()); - } - - Ok(fsm_dir) -} - -/// Recursively extract a directory -fn extract_dir(dir: &Dir, target: &Path) -> Result<()> { - fs::create_dir_all(target)?; - - for file in dir.files() { - // file.path() is relative to the Dir root, so we need just the filename - if let Some(filename) = file.path().file_name() { - let file_path = target.join(filename); - fs::write(&file_path, file.contents())?; - } - } - - for subdir in dir.dirs() { - // Use only the directory name, not the full path - if let Some(dirname) = subdir.path().file_name() { - let subdir_path = target.join(dirname); - extract_dir(subdir, &subdir_path)?; - } - } - - Ok(()) -} - -/// Run uv sync in the fsm_analyzer directory if needed -pub fn ensure_fsm_analyzer_deps(fsm_dir: &Path) -> Result<()> { - let lock_file = fsm_dir.join("uv.lock"); - let venv_dir = fsm_dir.join(".venv"); - - // Check if we need to sync (no venv or lock file changed) - let needs_sync = !venv_dir.exists() || !lock_file.exists(); - - if needs_sync { - log::info!("Running uv sync for fsm_analyzer dependencies..."); - let status = Command::new("uv") - .arg("sync") - .current_dir(fsm_dir) - .status() - .context("Failed to run uv sync")?; - - if !status.success() { - anyhow::bail!("uv sync failed with exit code: {}", status); - } - log::info!("fsm_analyzer dependencies synced successfully"); - } else { - log::debug!("fsm_analyzer dependencies already synced"); - } - - Ok(()) -} - -/// Get the path to the fsm_analyzer directory, extracting and setting up if needed -pub fn get_fsm_analyzer_path() -> Result { - // Check uv is installed first - check_uv_installed()?; - - // Extract to cache - let fsm_dir = extract_fsm_analyzer()?; - - // Ensure dependencies are synced - ensure_fsm_analyzer_deps(&fsm_dir)?; - - Ok(fsm_dir) -} diff --git a/src/templates/testbench_state_coverage_cpp.j2 b/src/templates/testbench_state_coverage_cpp.j2 deleted file mode 100644 index 9bc9cf3..0000000 --- a/src/templates/testbench_state_coverage_cpp.j2 +++ /dev/null @@ -1,941 +0,0 @@ -// C++ State Coverage Testbench generated by STG generate-fsm -// Module: {{ module_name }} -// This testbench performs runtime DFS to cover all FSM state transitions. - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include "V{{ dut_module_name }}.h" -#include "V{{ golden_module_name }}.h" -#include "V{{ dut_module_name }}___024root.h" - -#ifdef COVERAGE -#include -#endif - -// ============================================================================ -// Random Number Generator -// ============================================================================ - -std::mt19937_64 rng; - -uint64_t get_random_bits(int bits) { - if (bits >= 64) return rng(); - return rng() & ((1ULL << bits) - 1); -} - -// ============================================================================ -// Test Statistics -// ============================================================================ - -int64_t total_transitions_tested = 0; -int64_t covered_transitions = 0; -int64_t timeout_transitions = 0; -int64_t dfs_passes_completed = 0; -bool simulation_passed = true; - -{%- for p in outputs %} -int64_t {{ p.name }}_total_checks = 0; -int64_t {{ p.name }}_error_checks = 0; -{%- endfor %} - -// ============================================================================ -// FSM Graph Data Structures -// ============================================================================ - -struct Edge { - int id; - std::string from_state; - std::string to_state; - std::string input_condition; - std::string wait_condition; - int priority; -}; - -struct FSMGraph { - std::string state_variable; - std::string initial_state; - std::vector state_names; - std::vector state_values; - std::vector edges; - std::map> adj; - - void build() { - adj.clear(); - for (size_t i = 0; i < edges.size(); i++) { - adj[edges[i].from_state].push_back(static_cast(i)); - } - for (auto& [_, indices] : adj) { - std::sort(indices.begin(), indices.end(), - [this](int a, int b) { return edges[a].priority < edges[b].priority; }); - } - } - - std::string value_to_name(uint64_t val) const { - for (size_t i = 0; i < state_values.size(); i++) { - if (state_values[i] == val) return state_names[i]; - } - return "UNKNOWN(" + std::to_string(val) + ")"; - } -}; - -// ============================================================================ -// Expression Parser — Recursive Descent for Verilog-style Conditions -// ============================================================================ -// -// Supports: identifiers, integer literals (decimal + Verilog N'bX/N'dX/N'hX/N'oX), -// && || & | ! ~ == != >= <= > < + - * ( ) -// -// Operator precedence (lowest to highest): -// ||, | → &&, & → ==, != → >=, <=, >, < → +, - → * → !, ~ → primary - -// ---- Token types ---- - -enum class TokType { - LIT, ID, LPAREN, RPAREN, - AND, OR, NOT, - EQ, NE, GE, LE, GT, LT, - PLUS, MINUS, MUL, - BAND, BOR, - END_TOK -}; - -struct Tok { - TokType ty; - std::string text; - uint64_t num; -}; - -// ---- Lexer / Tokenizer ---- - -class Lexer { - const std::string& src_; - size_t pos_; - - void ws() { - while (pos_ < src_.size() && std::isspace(static_cast(src_[pos_]))) - ++pos_; - } - -public: - explicit Lexer(const std::string& s) : src_(s), pos_(0) {} - - Tok next() { - ws(); - if (pos_ >= src_.size()) return {TokType::END_TOK, "", 0}; - - // Two-character operators (check first) - if (pos_ + 1 < src_.size()) { - char a = src_[pos_], b = src_[pos_ + 1]; - if (a == '&' && b == '&') { pos_ += 2; return {TokType::AND, "&&", 0}; } - if (a == '|' && b == '|') { pos_ += 2; return {TokType::OR, "||", 0}; } - if (a == '=' && b == '=') { pos_ += 2; return {TokType::EQ, "==", 0}; } - if (a == '!' && b == '=') { pos_ += 2; return {TokType::NE, "!=", 0}; } - if (a == '>' && b == '=') { pos_ += 2; return {TokType::GE, ">=", 0}; } - if (a == '<' && b == '=') { pos_ += 2; return {TokType::LE, "<=", 0}; } - } - - char c = src_[pos_]; - - // Single-character operators - switch (c) { - case '!': ++pos_; return {TokType::NOT, "!", 0}; - case '~': ++pos_; return {TokType::NOT, "~", 0}; - case '>': ++pos_; return {TokType::GT, ">", 0}; - case '<': ++pos_; return {TokType::LT, "<", 0}; - case '+': ++pos_; return {TokType::PLUS, "+", 0}; - case '-': ++pos_; return {TokType::MINUS, "-", 0}; - case '*': ++pos_; return {TokType::MUL, "*", 0}; - case '(': ++pos_; return {TokType::LPAREN, "(", 0}; - case ')': ++pos_; return {TokType::RPAREN, ")", 0}; - case '&': ++pos_; return {TokType::BAND, "&", 0}; - case '|': ++pos_; return {TokType::BOR, "|", 0}; - default: break; - } - - // Number literal (decimal or Verilog N'bX / N'dX / N'hX / N'oX) - if (std::isdigit(static_cast(c))) return lex_num(); - - // Verilog literal without width prefix: 'b0, 'hFF, etc. - if (c == '\'') return lex_verilog_no_width(); - - // Identifier or keyword - if (std::isalpha(static_cast(c)) || c == '_') return lex_id(); - - // Unknown character — skip it - ++pos_; - return {TokType::END_TOK, "", 0}; - } - -private: - Tok lex_num() { - size_t s = pos_; - while (pos_ < src_.size() && std::isdigit(static_cast(src_[pos_]))) ++pos_; - - // Check for Verilog literal suffix: N'bXXX / N'dXXX / N'hXXX / N'oXXX - if (pos_ < src_.size() && src_[pos_] == '\'') { - ++pos_; // skip ' - if (pos_ < src_.size()) { - char base = src_[pos_]; ++pos_; - size_t ds = pos_; - while (pos_ < src_.size() && - (std::isxdigit(static_cast(src_[pos_])) - || src_[pos_] == '_' || src_[pos_] == 'x' || src_[pos_] == 'X' - || src_[pos_] == 'z' || src_[pos_] == 'Z')) ++pos_; - std::string digits; - for (size_t k = ds; k < pos_; ++k) - if (src_[k] != '_') digits += src_[k]; - int radix = 10; - if (base == 'b' || base == 'B') radix = 2; - else if (base == 'o' || base == 'O') radix = 8; - else if (base == 'h' || base == 'H') radix = 16; - uint64_t v = 0; - try { v = std::stoull(digits, nullptr, radix); } catch (...) {} - return {TokType::LIT, src_.substr(s, pos_ - s), v}; - } - } - - uint64_t v = 0; - try { v = std::stoull(src_.substr(s, pos_ - s)); } catch (...) {} - return {TokType::LIT, src_.substr(s, pos_ - s), v}; - } - - Tok lex_verilog_no_width() { - // Handle 'b0, 'h1A, etc. (no width prefix) - size_t s = pos_; - ++pos_; // skip ' - if (pos_ < src_.size()) { - char base = src_[pos_]; ++pos_; - size_t ds = pos_; - while (pos_ < src_.size() && - (std::isxdigit(static_cast(src_[pos_])) - || src_[pos_] == '_' || src_[pos_] == 'x' || src_[pos_] == 'X' - || src_[pos_] == 'z' || src_[pos_] == 'Z')) ++pos_; - std::string digits; - for (size_t k = ds; k < pos_; ++k) - if (src_[k] != '_') digits += src_[k]; - int radix = 10; - if (base == 'b' || base == 'B') radix = 2; - else if (base == 'o' || base == 'O') radix = 8; - else if (base == 'h' || base == 'H') radix = 16; - uint64_t v = 0; - try { v = std::stoull(digits, nullptr, radix); } catch (...) {} - return {TokType::LIT, src_.substr(s, pos_ - s), v}; - } - return {TokType::LIT, "'", 0}; - } - - Tok lex_id() { - size_t s = pos_; - while (pos_ < src_.size() && - (std::isalnum(static_cast(src_[pos_])) || src_[pos_] == '_')) - ++pos_; - std::string w = src_.substr(s, pos_ - s); - if (w == "true") return {TokType::LIT, w, 1}; - if (w == "false") return {TokType::LIT, w, 0}; - if (w == "default") return {TokType::LIT, w, 1}; - return {TokType::ID, w, 0}; - } -}; - -// ---- AST Node ---- - -struct ASTNode { - enum Kind { LIT, SIG, UNOT, BIN } kind; - uint64_t val; // for LIT - std::string name; // for SIG - std::string op; // for BIN - std::unique_ptr lhs, rhs; - ASTNode() : kind(LIT), val(0) {} -}; - -using AST = std::unique_ptr; - -static inline AST mk_lit(uint64_t v) { - auto n = std::make_unique(); n->kind = ASTNode::LIT; n->val = v; return n; -} -static inline AST mk_sig(const std::string& s) { - auto n = std::make_unique(); n->kind = ASTNode::SIG; n->name = s; return n; -} -static inline AST mk_not(AST c) { - auto n = std::make_unique(); n->kind = ASTNode::UNOT; n->lhs = std::move(c); return n; -} -static inline AST mk_bin(const std::string& o, AST l, AST r) { - auto n = std::make_unique(); n->kind = ASTNode::BIN; n->op = o; - n->lhs = std::move(l); n->rhs = std::move(r); return n; -} - -// ---- Recursive-descent parser ---- -// -// Grammar (by precedence, lowest first): -// or_expr = and_expr ( ("||" | "|") and_expr )* -// and_expr = eq_expr ( ("&&" | "&") eq_expr )* -// eq_expr = cmp_expr ( ("==" | "!=") cmp_expr )* -// cmp_expr = add_expr ( (">=" | "<=" | ">" | "<") add_expr )* -// add_expr = mul_expr ( ("+" | "-") mul_expr )* -// mul_expr = unary ( "*" unary )* -// unary = ("!" | "~") unary | primary -// primary = NUMBER | IDENTIFIER | "(" or_expr ")" - -class ExprParser { - std::vector ts_; - size_t pos_; - const Tok& peek() const { return ts_[pos_]; } - Tok adv() { return ts_[pos_++]; } - bool is(TokType t) const { return peek().ty == t; } - -public: - explicit ExprParser(const std::string& expr) : pos_(0) { - Lexer lx(expr); - Tok t; - do { t = lx.next(); ts_.push_back(t); } while (t.ty != TokType::END_TOK); - } - - AST parse() { return p_or(); } - -private: - AST p_or() { - auto n = p_and(); - while (is(TokType::OR) || is(TokType::BOR)) { - adv(); n = mk_bin("||", std::move(n), p_and()); - } - return n; - } - AST p_and() { - auto n = p_eq(); - while (is(TokType::AND) || is(TokType::BAND)) { - adv(); n = mk_bin("&&", std::move(n), p_eq()); - } - return n; - } - AST p_eq() { - auto n = p_cmp(); - while (is(TokType::EQ) || is(TokType::NE)) { - auto o = adv().text; n = mk_bin(o, std::move(n), p_cmp()); - } - return n; - } - AST p_cmp() { - auto n = p_add(); - while (is(TokType::GE) || is(TokType::LE) || is(TokType::GT) || is(TokType::LT)) { - auto o = adv().text; n = mk_bin(o, std::move(n), p_add()); - } - return n; - } - AST p_add() { - auto n = p_mul(); - while (is(TokType::PLUS) || is(TokType::MINUS)) { - auto o = adv().text; n = mk_bin(o, std::move(n), p_mul()); - } - return n; - } - AST p_mul() { - auto n = p_unary(); - while (is(TokType::MUL)) { - adv(); n = mk_bin("*", std::move(n), p_unary()); - } - return n; - } - AST p_unary() { - if (is(TokType::NOT)) { adv(); return mk_not(p_unary()); } - return p_primary(); - } - AST p_primary() { - if (is(TokType::LIT)) { auto t = adv(); return mk_lit(t.num); } - if (is(TokType::ID)) { auto t = adv(); return mk_sig(t.text); } - if (is(TokType::LPAREN)) { - adv(); - auto n = p_or(); - if (is(TokType::RPAREN)) adv(); - return n; - } - // Fallback — consume and return 0 - if (!is(TokType::END_TOK)) adv(); - return mk_lit(0); - } -}; - -// ---- Constraint extraction for apply_input_condition ---- -// Walks the AST to determine what value each signal should be set to -// in order to make the top-level expression evaluate to true. - -static void extract_input_assignments(const ASTNode* n, std::map& out) { - if (!n) return; - switch (n->kind) { - case ASTNode::SIG: - // Bare signal name → must be non-zero → set to 1 - out[n->name] = 1; - break; - case ASTNode::LIT: - // Nothing to assign - break; - case ASTNode::UNOT: - if (n->lhs) { - if (n->lhs->kind == ASTNode::SIG) { - // !signal → set signal to 0 - out[n->lhs->name] = 0; - } else { - // Negate inner assignments - std::map inner; - extract_input_assignments(n->lhs.get(), inner); - for (auto& [k, v] : inner) out[k] = v ? 0 : 1; - } - } - break; - case ASTNode::BIN: - if (n->op == "&&") { - // Both sides must be true - extract_input_assignments(n->lhs.get(), out); - extract_input_assignments(n->rhs.get(), out); - } else if (n->op == "||") { - // Pick first disjunct (simplest satisfying strategy) - extract_input_assignments(n->lhs.get(), out); - } else if (n->op == "==") { - // signal == value → assign value to signal - if (n->lhs && n->lhs->kind == ASTNode::SIG && n->rhs && n->rhs->kind == ASTNode::LIT) - out[n->lhs->name] = n->rhs->val; - else if (n->rhs && n->rhs->kind == ASTNode::SIG && n->lhs && n->lhs->kind == ASTNode::LIT) - out[n->rhs->name] = n->lhs->val; - } else if (n->op == "!=") { - // signal != 0 → set to 1; signal != 1 → set to 0 - if (n->lhs && n->lhs->kind == ASTNode::SIG && n->rhs && n->rhs->kind == ASTNode::LIT) - out[n->lhs->name] = n->rhs->val ? 0 : 1; - else if (n->rhs && n->rhs->kind == ASTNode::SIG && n->lhs && n->lhs->kind == ASTNode::LIT) - out[n->rhs->name] = n->lhs->val ? 0 : 1; - } - break; - } -} - -// ============================================================================ -// Testbench Class -// ============================================================================ - -class StateCoverageTB { -public: - V{{ dut_module_name }}* dut; - V{{ golden_module_name }}* golden; - uint64_t sim_time; - int transition_timeout; - - StateCoverageTB(int timeout = {{ transition_timeout }}) : sim_time(0), transition_timeout(timeout) { - dut = new V{{ dut_module_name }}("DUT"); - golden = new V{{ golden_module_name }}("GOLDEN"); - } - ~StateCoverageTB() { delete dut; delete golden; } - - void eval() { dut->eval(); golden->eval(); sim_time++; } - void tick() { - dut->{{ clock_signal }} = 1; golden->{{ clock_signal }} = 1; eval(); - dut->{{ clock_signal }} = 0; golden->{{ clock_signal }} = 0; eval(); - } - - void do_reset() { -{%- if reset_active_high %} - dut->{{ reset_signal }} = 1; golden->{{ reset_signal }} = 1; -{%- else %} - dut->{{ reset_signal }} = 0; golden->{{ reset_signal }} = 0; -{%- endif %} - tick(); -{%- if reset_active_high %} - dut->{{ reset_signal }} = 0; golden->{{ reset_signal }} = 0; -{%- else %} - dut->{{ reset_signal }} = 1; golden->{{ reset_signal }} = 1; -{%- endif %} - eval(); - } - - void randomize_data_inputs() { -{%- for p in data_signals %} -{%- if p.width <= 64 %} - { uint64_t val = get_random_bits({{ p.width }}); dut->{{ p.name }} = val; golden->{{ p.name }} = val; } -{%- endif %} -{%- endfor %} - } - - void compare_outputs() { -{%- for p in outputs %} - {{ p.name }}_total_checks++; - if (dut->{{ p.name }} != golden->{{ p.name }}) { - {{ p.name }}_error_checks++; - simulation_passed = false; -{%- if debug %} - std::cerr << "MISMATCH {{ p.name }}: dut=0x" << std::hex << +dut->{{ p.name }} - << " golden=0x" << +golden->{{ p.name }} << std::dec << std::endl; -{%- endif %} - } -{%- endfor %} - } - - // ---- Signal Access ---- -{% for p in parameters %} - static uint64_t get_{{ p.name }}() { return {{ p.value }}ULL; } -{% endfor %} -{% for sig in runtime_signals %} - uint64_t get_{{ sig }}() { return dut->rootp->{{ module_name }}__DOT__{{ sig }}; } -{% endfor %} -{% for sm in state_machines %} - uint64_t get_state_raw_{{ loop.index0 }}() { return dut->rootp->{{ module_name }}__DOT__{{ sm.state_variable }}; } -{% endfor %} - - uint64_t resolve_signal(const std::string& name) { -{%- for p in control_signals %} - if (name == "{{ p.name }}") return static_cast(dut->{{ p.name }}); -{%- endfor %} -{%- for p in data_signals %} - if (name == "{{ p.name }}") return static_cast(dut->{{ p.name }}); -{%- endfor %} -{%- for sig in runtime_signals %} - if (name == "{{ sig }}") return get_{{ sig }}(); -{%- endfor %} -{%- for p in parameters %} - if (name == "{{ p.name }}") return get_{{ p.name }}(); -{%- endfor %} - return 0; - } - - // ---- Expression Evaluation (recursive AST walk) ---- - - uint64_t eval_ast(const ASTNode* n) { - if (!n) return 0; - switch (n->kind) { - case ASTNode::LIT: return n->val; - case ASTNode::SIG: return resolve_signal(n->name); - case ASTNode::UNOT: return eval_ast(n->lhs.get()) ? 0 : 1; - case ASTNode::BIN: { - // Short-circuit for logical operators - if (n->op == "&&") return (eval_ast(n->lhs.get()) && eval_ast(n->rhs.get())) ? 1 : 0; - if (n->op == "||") return (eval_ast(n->lhs.get()) || eval_ast(n->rhs.get())) ? 1 : 0; - uint64_t lv = eval_ast(n->lhs.get()), rv = eval_ast(n->rhs.get()); - if (n->op == "==") return lv == rv ? 1 : 0; - if (n->op == "!=") return lv != rv ? 1 : 0; - if (n->op == ">=") return lv >= rv ? 1 : 0; - if (n->op == "<=") return lv <= rv ? 1 : 0; - if (n->op == ">") return lv > rv ? 1 : 0; - if (n->op == "<") return lv < rv ? 1 : 0; - if (n->op == "+") return lv + rv; - if (n->op == "-") return lv - rv; - if (n->op == "*") return lv * rv; - return 0; - } - } - return 0; - } - - // ---- Condition Application & Evaluation ---- - - void apply_input_condition(const std::string& cond) { - if (cond.empty() || cond == "true" || cond == "1" || cond == "1'b1" || cond == "default") return; - randomize_data_inputs(); - // Parse the condition into an AST and extract signal assignments - ExprParser parser(cond); - auto ast = parser.parse(); - std::map assignments; - extract_input_assignments(ast.get(), assignments); - // Apply extracted assignments to control signals only -{%- for p in control_signals %} - if (assignments.count("{{ p.name }}")) { - uint64_t v = assignments["{{ p.name }}"]; - dut->{{ p.name }} = v; golden->{{ p.name }} = v; - } -{%- endfor %} - } - - bool evaluate_internal_condition(const std::string& cond) { - if (cond.empty() || cond == "true" || cond == "1" || cond == "1'b1") return true; - if (cond == "false" || cond == "0" || cond == "1'b0") return false; - ExprParser parser(cond); - auto ast = parser.parse(); - return eval_ast(ast.get()) != 0; - } - - bool wait_for_condition(const std::string& wait_cond) { - if (wait_cond.empty() || wait_cond == "true" || wait_cond == "1" || wait_cond == "1'b1") { tick(); return true; } - // Parse once, evaluate each cycle for efficiency - ExprParser parser(wait_cond); - auto ast = parser.parse(); - for (int cycle = 0; cycle < transition_timeout; cycle++) { - tick(); - if (eval_ast(ast.get()) != 0) return true; - } - timeout_transitions++; - return false; - } -}; - -// ============================================================================ -// DFS Coverage Engine -// ============================================================================ - -class DFSEngine { -public: - FSMGraph& graph; - StateCoverageTB& tb; - int sm_idx; - std::set covered; // Edges successfully traversed (persists across passes) - std::set pass_attempted; // Edges attempted this pass (reset each pass) - std::set failed; // Edges permanently failed after repeated timeouts - std::map fail_count; // Per-edge failure count across all passes - std::map edge_attempts; // Per-edge total attempt count across all passes - static constexpr int MAX_FAIL_ATTEMPTS = 2; // Mark as permanently failed after this many failures - - DFSEngine(FSMGraph& g, StateCoverageTB& t, int idx) : graph(g), tb(t), sm_idx(idx) {} - - std::string read_actual_state() { - uint64_t val = 0; -{%- for sm in state_machines %} - if (sm_idx == {{ loop.index0 }}) val = tb.get_state_raw_{{ loop.index0 }}(); -{%- endfor %} - return graph.value_to_name(val); - } - - bool try_edge(int eidx) { - const auto& e = graph.edges[eidx]; - total_transitions_tested++; - pass_attempted.insert(eidx); - edge_attempts[eidx]++; - tb.apply_input_condition(e.input_condition); - bool ok = tb.wait_for_condition(e.wait_condition); - tb.compare_outputs(); - if (ok) { - covered.insert(eidx); - covered_transitions++; - failed.erase(eidx); // Succeeded — remove from failed if previously marked - } else { - fail_count[eidx]++; - if (fail_count[eidx] >= MAX_FAIL_ATTEMPTS) { - failed.insert(eidx); - } - } -{%- if debug %} - std::string actual = read_actual_state(); - std::cout << " Edge " << eidx << ": " << e.from_state << " -> " << e.to_state - << " [" << e.input_condition << "]" - << (e.wait_condition.empty() ? "" : " wait=" + e.wait_condition) - << (ok ? " OK" : " TIMEOUT") - << " (actual: " << actual << ")" << std::endl; -{%- endif %} - return ok; - } - - std::vector find_bridge(const std::string& start) { - std::queue>> q; - std::set seen; - seen.insert(start); - q.push(std::make_pair(start, std::vector())); - while (!q.empty()) { - auto front = q.front(); q.pop(); - std::string state = front.first; std::vector path = front.second; - if (auto it = graph.adj.find(state); it != graph.adj.end()) { - if (!path.empty()) { for (int eidx : it->second) { if (!covered.count(eidx) && !pass_attempted.count(eidx) && !failed.count(eidx)) return path; } } - for (int eidx : it->second) { - if (covered.count(eidx)) { - const auto& e = graph.edges[eidx]; - if (!seen.count(e.to_state)) { seen.insert(e.to_state); auto np = path; np.push_back(eidx); q.push(std::make_pair(e.to_state, np)); } - } - } - } - } - return {}; - } - - std::vector find_path_to(const std::string& start, const std::string& target) { - if (start == target) return {}; - std::queue>> q; - std::set seen; - seen.insert(start); - q.push(std::make_pair(start, std::vector())); - while (!q.empty()) { - auto front = q.front(); q.pop(); - std::string state = front.first; std::vector path = front.second; - if (auto it = graph.adj.find(state); it != graph.adj.end()) { - for (int eidx : it->second) { - const auto& e = graph.edges[eidx]; - if (e.to_state == target) { auto r = path; r.push_back(eidx); return r; } - if (!seen.count(e.to_state)) { seen.insert(e.to_state); auto np = path; np.push_back(eidx); q.push(std::make_pair(e.to_state, np)); } - } - } - } - return {}; - } - - bool follow_path(const std::vector& path, std::string& current) { - for (int eidx : path) { if (!try_edge(eidx)) return false; current = graph.edges[eidx].to_state; } - return true; - } - - void run_pass() { - tb.do_reset(); - tb.randomize_data_inputs(); - pass_attempted.clear(); - std::string current = graph.initial_state; - int max_iter = static_cast(graph.edges.size()) * 3 + 1; - for (int iter = 0; iter < max_iter; iter++) { - int next_edge = -1; - if (auto it = graph.adj.find(current); it != graph.adj.end()) { - for (int eidx : it->second) { if (!covered.count(eidx) && !pass_attempted.count(eidx) && !failed.count(eidx)) { next_edge = eidx; break; } } - } - if (next_edge >= 0) { - if (try_edge(next_edge)) { current = graph.edges[next_edge].to_state; } - else { tb.do_reset(); tb.randomize_data_inputs(); current = graph.initial_state; } - } else { - auto bridge = find_bridge(current); - if (!bridge.empty()) { - if (follow_path(bridge, current)) continue; - else { tb.do_reset(); tb.randomize_data_inputs(); current = graph.initial_state; } - } else { - bool found = false; - for (size_t i = 0; i < graph.edges.size(); i++) { - int ii = static_cast(i); - if (covered.count(ii) || pass_attempted.count(ii) || failed.count(ii)) continue; - auto path = find_path_to(graph.initial_state, graph.edges[i].from_state); - tb.do_reset(); tb.randomize_data_inputs(); current = graph.initial_state; - if (follow_path(path, current)) { - if (try_edge(ii)) current = graph.edges[i].to_state; - else { tb.do_reset(); tb.randomize_data_inputs(); current = graph.initial_state; } - found = true; break; - } - } - if (!found) break; - } - } - } - } - - void run(int passes) { - for (int p = 0; p < passes; p++) { - if (covered.size() + failed.size() >= graph.edges.size()) break; - dfs_passes_completed++; -{%- if debug %} - std::cout << "=== DFS Pass " << (p+1) << "/" << passes << " (SM: " << graph.state_variable - << ", covered: " << covered.size() << "/" << graph.edges.size() << ") ===" << std::endl; -{%- endif %} - run_pass(); - } - } - - int get_covered() const { return static_cast(covered.size()); } - int get_total() const { return static_cast(graph.edges.size()); } - int get_failed_count() const { return static_cast(failed.size()); } - bool is_covered(int idx) const { return covered.count(idx) > 0; } - bool is_failed(int idx) const { return failed.count(idx) > 0; } - int get_edge_attempts(int idx) const { auto it = edge_attempts.find(idx); return it != edge_attempts.end() ? it->second : 0; } - std::string get_edge_status(int idx) const { - if (is_covered(idx)) return "covered"; - if (is_failed(idx)) return "failed"; - return "unreachable"; - } -}; - -// ============================================================================ -// Graph Initialization -// ============================================================================ - -void init_graphs(std::vector& graphs) { -{% for sm in state_machines %} - { - FSMGraph g; - g.state_variable = "{{ sm.state_variable }}"; - g.initial_state = "{{ sm.initial_state }}"; -{%- for s in sm.states %} - g.state_names.push_back("{{ s.name }}"); - g.state_values.push_back({{ s.value_numeric }}ULL); -{%- endfor %} -{%- for t in sm.transitions %} - { Edge e; e.id = {{ loop.index0 }}; e.from_state = "{{ t.from_state }}"; e.to_state = "{{ t.to_state }}"; e.input_condition = "{{ t.input_condition }}"; e.wait_condition = "{{ t.wait_condition }}"; e.priority = {{ t.priority }}; g.edges.push_back(e); } -{%- endfor %} - g.build(); - graphs.push_back(std::move(g)); - } -{% endfor %} -} - -// ============================================================================ -// Main -// ============================================================================ - -int main(int argc, char** argv) { - Verilated::commandArgs(argc, argv); - StateCoverageTB tb({{ transition_timeout }}); - - uint64_t seed = std::chrono::high_resolution_clock::now().time_since_epoch().count(); - std::string stats_file = "test_stats.json"; - for (int i = 1; i < argc; i++) { - std::string arg(argv[i]); - if (arg.find("+SEED=") == 0) seed = std::stoull(arg.substr(6)); - if (arg.find("+STATS_FILE=") == 0) stats_file = arg.substr(12); - } - rng.seed(seed); - - std::vector graphs; - init_graphs(graphs); - int dfs_passes = {{ dfs_passes }}; - int total_edges = 0; - for (auto& g : graphs) total_edges += static_cast(g.edges.size()); - - std::cout << "=====================================" << std::endl; - std::cout << "State Coverage Testbench (C++ DFS)" << std::endl; - std::cout << "Module: {{ module_name }}" << std::endl; - std::cout << "State machines: " << graphs.size() << std::endl; - for (size_t i = 0; i < graphs.size(); i++) { - std::cout << " " << graphs[i].state_variable << ": " << graphs[i].state_names.size() - << " states, " << graphs[i].edges.size() << " edges" << std::endl; - } - std::cout << "DFS passes: " << dfs_passes << std::endl; - std::cout << "Transition timeout: " << tb.transition_timeout << " cycles" << std::endl; - std::cout << "Random seed: " << seed << std::endl; - std::cout << "=====================================" << std::endl; - - std::vector engines; - for (size_t i = 0; i < graphs.size(); i++) { - auto* engine = new DFSEngine(graphs[i], tb, static_cast(i)); -{%- if debug %} - std::cout << std::endl << "--- DFS for SM: " << graphs[i].state_variable - << " (" << graphs[i].edges.size() << " edges) ---" << std::endl; -{%- endif %} - engine->run(dfs_passes); - engines.push_back(engine); - } - - // Additional random testing - std::cout << std::endl << "--- Additional Random Testing ({{ random_samples }} cycles) ---" << std::endl; - tb.do_reset(); - for (int64_t i = 0; i < {{ random_samples }}; i++) { - tb.randomize_data_inputs(); -{%- for p in control_signals %} - { uint64_t val = get_random_bits({{ p.width }}); tb.dut->{{ p.name }} = val; tb.golden->{{ p.name }} = val; } -{%- endfor %} - tb.tick(); - tb.compare_outputs(); - } - - // ---- Summary ---- - int total_covered = 0; - int total_failed = 0; - for (auto* eng : engines) { total_covered += eng->get_covered(); total_failed += eng->get_failed_count(); } - - std::cout << std::endl << "=====================================" << std::endl; - std::cout << "Test Summary" << std::endl; - std::cout << "=====================================" << std::endl; - std::cout << "DFS passes completed: " << dfs_passes_completed << std::endl; - std::cout << "Edge coverage: " << total_covered << "/" << total_edges - << " (" << std::fixed << std::setprecision(1) - << (total_edges > 0 ? 100.0 * total_covered / total_edges : 0.0) << "%)" << std::endl; - std::cout << "Total transitions tested: " << total_transitions_tested << std::endl; - std::cout << "Successful transitions: " << covered_transitions << std::endl; - std::cout << "Timed out transitions: " << timeout_transitions << std::endl; - if (total_failed > 0) - std::cout << "Permanently failed edges: " << total_failed << std::endl; - for (auto* eng : engines) { - std::cout << " SM '" << eng->graph.state_variable << "': " - << eng->get_covered() << "/" << eng->get_total() << " edges covered"; - if (eng->get_failed_count() > 0) - std::cout << ", " << eng->get_failed_count() << " failed"; - std::cout << std::endl; - for (size_t j = 0; j < eng->graph.edges.size(); j++) { - int jj = static_cast(j); - if (eng->is_failed(jj)) { - const auto& e = eng->graph.edges[j]; - std::cout << " FAILED: " << e.from_state << " -> " << e.to_state - << " [" << e.input_condition << "]" - << (e.wait_condition.empty() ? "" : " wait=" + e.wait_condition) - << " (" << eng->get_edge_attempts(jj) << " attempts)" << std::endl; - } else if (!eng->is_covered(jj)) { - const auto& e = eng->graph.edges[j]; - std::cout << " UNREACHABLE: " << e.from_state << " -> " << e.to_state - << " [" << e.input_condition << "]" << std::endl; - } - } - } -{%- for p in outputs %} - std::cout << "{{ p.name }}: " << {{ p.name }}_total_checks << " checks, " << {{ p.name }}_error_checks << " errors" << std::endl; -{%- endfor %} - - if (simulation_passed && total_covered == total_edges) - std::cout << std::endl << "*** SIMULATION PASSED (100% edge coverage, outputs correct) ***" << std::endl; - else if (!simulation_passed && total_covered == total_edges) - std::cout << std::endl << "*** SIMULATION FAILED (100% edge coverage, output mismatches) ***" << std::endl; - else if (!simulation_passed) - std::cout << std::endl << "*** SIMULATION FAILED (" << total_covered << "/" << total_edges << " edges covered, output mismatches) ***" << std::endl; - else if (total_failed > 0) - std::cout << std::endl << "*** SIMULATION COMPLETED (" << total_covered << "/" << total_edges << " edges covered, " << total_failed << " edges failed) ***" << std::endl; - else - std::cout << std::endl << "*** SIMULATION COMPLETED (" << total_covered << "/" << total_edges << " edges covered) ***" << std::endl; - std::cout << "=====================================" << std::endl; - - // ---- JSON Statistics ---- - { - std::ofstream json(stats_file); - json << "{\n"; - json << " \"summary\": {\n"; - json << " \"dfs_passes\": " << dfs_passes_completed << ",\n"; - json << " \"total_edges\": " << total_edges << ",\n"; - json << " \"covered_edges\": " << total_covered << ",\n"; - json << " \"failed_edges\": " << total_failed << ",\n"; - json << " \"edge_coverage_pct\": " << std::fixed << std::setprecision(2) << (total_edges > 0 ? 100.0 * total_covered / total_edges : 0.0) << ",\n"; - json << " \"total_transitions_tested\": " << total_transitions_tested << ",\n"; - json << " \"timeout_transitions\": " << timeout_transitions << ",\n"; - json << " \"random_seed\": " << seed << ",\n"; - json << " \"simulation_passed\": " << (simulation_passed ? "true" : "false") << "\n"; - json << " },\n"; - json << " \"state_machines\": [\n"; - for (size_t i = 0; i < engines.size(); i++) { - auto* eng = engines[i]; - json << " {\n"; - json << " \"name\": \"" << eng->graph.state_variable << "\",\n"; - json << " \"total_edges\": " << eng->get_total() << ",\n"; - json << " \"covered_edges\": " << eng->get_covered() << ",\n"; - json << " \"edges\": [\n"; - for (size_t j = 0; j < eng->graph.edges.size(); j++) { - const auto& e = eng->graph.edges[j]; - int jj = static_cast(j); - json << " {\"id\": " << e.id << ", \"from\": \"" << e.from_state << "\", \"to\": \"" << e.to_state - << "\", \"input_condition\": \"" << e.input_condition << "\", \"wait_condition\": \"" << e.wait_condition - << "\", \"status\": \"" << eng->get_edge_status(jj) - << "\", \"attempts\": " << eng->get_edge_attempts(jj) << "}"; - if (j + 1 < eng->graph.edges.size()) json << ","; - json << "\n"; - } - json << " ]\n }"; - if (i + 1 < engines.size()) json << ","; - json << "\n"; - } - json << " ],\n"; - json << " \"output_comparison\": {\n"; -{%- for p in outputs %} - json << " \"{{ p.name }}\": {" << "\"tests\": " << {{ p.name }}_total_checks - << ", \"success\": " << ({{ p.name }}_total_checks - {{ p.name }}_error_checks) - << ", \"score\": " << std::fixed << std::setprecision(2) - << ({{ p.name }}_total_checks > 0 ? 100.0 * ({{ p.name }}_total_checks - {{ p.name }}_error_checks) / {{ p.name }}_total_checks : 0.0) - << "}{%- if not loop.last %},{%- endif %}\n"; -{%- endfor %} - json << " }\n}\n"; - json.close(); - std::cout << "Statistics written to " << stats_file << std::endl; - } - - for (auto* eng : engines) delete eng; - -#ifdef COVERAGE - Verilated::threadContextp()->coveragep()->write("coverage.dat"); - std::cout << "Coverage data written to coverage.dat" << std::endl; -#endif - - return simulation_passed ? 0 : 1; -} diff --git a/src/tools/compiler.rs b/src/tools/compiler.rs index 846a796..7895f86 100644 --- a/src/tools/compiler.rs +++ b/src/tools/compiler.rs @@ -241,9 +241,6 @@ pub fn compile_testbench_verilator( for flag in &compile_flags { cmd.arg(flag); } - // Suppress MULTITOP warning since we intentionally pass both DUT and golden - // as separate top-level modules - cmd.arg("-Wno-MULTITOP"); cmd.arg("--main") .arg("--timing") .arg("--Mdir") @@ -573,184 +570,3 @@ pub fn compile_testbench_cpp_verilator( Ok(out_exe_path.exists()) } - -/// Compile a state-coverage C++ testbench where BOTH DUT and golden are Verilog files -/// that need to be verilated into C++ classes. -/// -/// This differs from `compile_testbench_cpp_verilator` in that the golden model -/// is also a Verilog file (not a C++ header), so we verilate both DUT and golden -/// separately with different prefixes. -pub fn compile_state_coverage_cpp_verilator( - args: &CompileArgs, - dut_paths: &[PathBuf], - dut_module_name: &str, - golden_path: &Path, - golden_module_name: &str, - cpp_testbench_path: &Path, - out_exe_path: &Path, -) -> Result { - // Check prerequisites - let version_check = Command::new("verilator") - .arg("--version") - .output() - .context("Failed to run verilator. Is it installed?")?; - if !version_check.status.success() { - anyhow::bail!("Verilator is not installed or not found in PATH"); - } - - let version_check = Command::new("g++") - .arg("--version") - .output() - .context("Failed to run g++. Is it installed?")?; - if !version_check.status.success() { - anyhow::bail!("G++ is not installed or not found in PATH"); - } - - if out_exe_path.exists() { - log::info!("Removing existing executable: {}", out_exe_path.display()); - fs::remove_file(out_exe_path).context("Failed to remove existing executable")?; - } - - let jobs_str = args.verilator_jobs.to_string(); - let mut compile_flags = vec!["-Wno-TIMESCALEMOD", "-j", &jobs_str]; - - let mut insert_dump_coverage = false; - if let Some(args_compile_flags) = &args.compile_flags { - for flag in args_compile_flags { - compile_flags.push(flag.as_str()); - if flag.contains("--coverage") { - insert_dump_coverage = true; - } - } - } - - if args.verilator_ignore_warnings { - compile_flags.extend_from_slice(&[ - "-Wno-WIDTHTRUNC", - "-Wno-WIDTHCONCAT", - "-Wno-WIDTHEXPAND", - ]); - } - - let mut c_flags = String::new(); - let ld_flags = String::new(); - - if insert_dump_coverage { - c_flags.push_str("-DCOVERAGE "); - } - - // Create temporary directory for Verilator build - let tmpdir = TempDir::new().context("Failed to create temporary directory")?; - - // Step 1: Verilate DUT - log::info!("Verilating DUT ({})...", dut_module_name); - { - let mut cmd = Command::new("verilator"); - for dut_path in dut_paths { - cmd.arg(dut_path); - } - cmd.arg("--top-module").arg(dut_module_name); - for flag in &compile_flags { - cmd.arg(flag); - } - cmd.arg("--cc"); - cmd.arg("--Mdir").arg(tmpdir.path()); - run_command(&mut cmd, "verilator (DUT)") - .with_context(|| format!("Failed to verilate DUT ({})", dut_module_name))?; - } - - // Step 2: Verilate golden with a different prefix - log::info!("Verilating golden ({})...", golden_module_name); - let _golden_prefix = format!("V{}", golden_module_name); - { - let mut cmd = Command::new("verilator"); - cmd.arg(golden_path); - cmd.arg("--top-module").arg(golden_module_name); - // Use a prefix to avoid name collisions if DUT and golden have the same module name - // Note: if they already have different names, Verilator defaults work fine - for flag in &compile_flags { - cmd.arg(flag); - } - cmd.arg("--cc"); - cmd.arg("--Mdir").arg(tmpdir.path()); - run_command(&mut cmd, "verilator (golden)") - .with_context(|| format!("Failed to verilate golden ({})", golden_module_name))?; - } - - // Step 3: Compile the C++ testbench with all generated classes - log::info!("Compiling C++ testbench..."); - - // Find Verilator include directory - let verilator_root_output = Command::new("verilator") - .arg("--getenv") - .arg("VERILATOR_ROOT") - .output() - .context("Failed to get VERILATOR_ROOT")?; - let verilator_root = String::from_utf8_lossy(&verilator_root_output.stdout) - .trim() - .to_string(); - let verilator_include = format!("{}/include", verilator_root); - - // Add include path for generated headers - if !c_flags.is_empty() { - c_flags.push(' '); - } - c_flags.push_str(&format!("-I{}", tmpdir.path().display())); - - let mut cmd = Command::new("g++"); - cmd.arg("-std=c++20"); - cmd.arg("-I").arg(tmpdir.path()); - cmd.arg("-I").arg(&verilator_include); - - // Add testbench source - cmd.arg(cpp_testbench_path); - - // Add Verilator support files - let verilated_cpp = format!("{}/verilated.cpp", verilator_include); - let verilated_timing_cpp = format!("{}/verilated_timing.cpp", verilator_include); - let verilated_threads_cpp = format!("{}/verilated_threads.cpp", verilator_include); - let verilated_cov_cpp = format!("{}/verilated_cov.cpp", verilator_include); - - cmd.arg(&verilated_cpp); - if std::path::Path::new(&verilated_timing_cpp).exists() { - cmd.arg(&verilated_timing_cpp); - } - if std::path::Path::new(&verilated_threads_cpp).exists() { - cmd.arg(&verilated_threads_cpp); - } - if insert_dump_coverage && std::path::Path::new(&verilated_cov_cpp).exists() { - cmd.arg(&verilated_cov_cpp); - } - - // Add all generated .cpp files from Verilator - let cpp_pattern = tmpdir.path().join("V*.cpp"); - for cpp_file in glob::glob(&cpp_pattern.to_string_lossy()) - .context("Failed to glob V*.cpp files")? - .filter_map(Result::ok) - { - cmd.arg(&cpp_file); - } - - cmd.arg("-o").arg(out_exe_path); - - if !c_flags.is_empty() { - for flag in c_flags.split_whitespace() { - cmd.arg(flag); - } - } - - if !ld_flags.is_empty() { - for flag in ld_flags.split_whitespace() { - cmd.arg(flag); - } - } - - run_command(&mut cmd, "g++ (state coverage testbench)") - .with_context(|| "Failed to compile state coverage testbench")?; - - if insert_dump_coverage { - log::info!("Coverage enabled. Data will be written to coverage.dat on exit."); - } - - Ok(out_exe_path.exists()) -} diff --git a/src/tools/file_utils.rs b/src/tools/file_utils.rs index 1451214..f87d195 100644 --- a/src/tools/file_utils.rs +++ b/src/tools/file_utils.rs @@ -57,7 +57,7 @@ pub fn concatenate_verilog_files(verilog_paths: &[PathBuf]) -> Result { .with_context(|| format!("Failed to read Verilog file: {}", verilog_path.display()))?; concatenated_content.push_str(&content); - concatenated_content.push('\n'); + concatenated_content.push_str("\n"); } // Write concatenated content to temporary file @@ -83,7 +83,7 @@ mod tests { let temp1 = NamedTempFile::new().unwrap(); let path1 = temp1.path().to_path_buf(); - let result = concatenate_verilog_files(std::slice::from_ref(&path1)).unwrap(); + let result = concatenate_verilog_files(&[path1.clone()]).unwrap(); assert_eq!(result, path1); } diff --git a/src/tools/fsm_analysis.rs b/src/tools/fsm_analysis.rs deleted file mode 100644 index 81757cc..0000000 --- a/src/tools/fsm_analysis.rs +++ /dev/null @@ -1,413 +0,0 @@ -use anyhow::{Context, Result}; -use serde::{Deserialize, Serialize}; -use std::collections::{HashMap, HashSet}; -use std::fs; -use std::path::Path; - -/// Top-level FSM analysis result -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct FsmAnalysis { - pub module_name: String, - pub state_machines: Vec, - pub clock_signal: String, - pub reset_signal: String, - pub reset_active_high: bool, -} - -/// A single state machine within a module -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct StateMachine { - pub state_variable: String, - #[serde(default)] - pub state_type: String, - pub states: Vec, - pub initial_state: String, - pub transitions: Vec, - #[serde(default)] - pub control_signals: Vec, - #[serde(default)] - pub data_signals: Vec, - #[serde(default)] - pub internal_signals: Vec, - /// Compile-time constants (localparam/parameter) referenced in transition conditions. - /// Each entry is { "name": "...", "value": }. - #[serde(default)] - pub parameters: Vec, -} - -/// A compile-time constant (localparam or parameter) extracted from the RTL. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Parameter { - pub name: String, - pub value: u64, -} - -/// A state in the FSM -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct State { - pub name: String, - #[serde(default)] - pub value: String, - #[serde(default)] - pub description: String, -} - -/// A transition between states -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Transition { - pub from_state: String, - pub to_state: String, - #[serde(default)] - pub condition: String, - #[serde(default)] - pub input_condition: String, - #[serde(default)] - pub wait_condition: String, - #[serde(default = "default_priority")] - pub priority: i32, -} - -fn default_priority() -> i32 { - 1 -} - -/// A test sequence: a series of transitions to execute starting from reset -#[derive(Debug, Clone, Serialize)] -pub struct TestSequence { - pub id: usize, - pub transitions: Vec, -} - -/// Load FSM analysis from a JSON file -pub fn load_fsm_analysis(path: &Path) -> Result { - let content = - fs::read_to_string(path).with_context(|| format!("Failed to read {}", path.display()))?; - serde_json::from_str(&content) - .with_context(|| format!("Failed to parse FSM analysis JSON from {}", path.display())) -} - -/// Save FSM analysis to a JSON file -pub fn save_fsm_analysis(analysis: &FsmAnalysis, path: &Path) -> Result<()> { - let json = serde_json::to_string_pretty(analysis) - .context("Failed to serialize FSM analysis to JSON")?; - if let Some(parent) = path.parent() { - fs::create_dir_all(parent)?; - } - fs::write(path, json) - .with_context(|| format!("Failed to write FSM analysis to {}", path.display())) -} - -/// Generate DFS-based test sequences that cover all transitions in all state machines. -/// -/// For each state machine, performs DFS from the initial state to visit every edge -/// (transition) at least once. Returns a list of test sequences; each sequence starts -/// from reset (initial state) and walks a path through the graph. -pub fn generate_dfs_test_sequences(analysis: &FsmAnalysis) -> Vec> { - let mut all_sequences = Vec::new(); - - for sm in &analysis.state_machines { - let sequences = dfs_cover_transitions(sm); - all_sequences.push(sequences); - } - - all_sequences -} - -/// DFS-based edge coverage for a single state machine. -/// -/// Algorithm: -/// 1. Build adjacency list (from_state -> Vec) -/// 2. Track visited edges (transition index) -/// 3. DFS from initial_state; when we traverse an unvisited edge, mark it visited -/// 4. When all edges from a node are visited, backtrack -/// 5. When we can't continue, emit the current path as a sequence and restart from -/// initial_state to cover remaining unvisited edges -fn dfs_cover_transitions(sm: &StateMachine) -> Vec { - // Build adjacency list: from_state -> Vec<(transition_index, Transition)> - let mut adj: HashMap> = HashMap::new(); - for (idx, t) in sm.transitions.iter().enumerate() { - adj.entry(t.from_state.clone()) - .or_default() - .push((idx, t.clone())); - } - - // Sort transitions by priority within each state for deterministic ordering - for edges in adj.values_mut() { - edges.sort_by_key(|(_, t)| t.priority); - } - - let total_transitions = sm.transitions.len(); - let mut visited_edges: HashSet = HashSet::new(); - let mut sequences: Vec = Vec::new(); - let mut seq_id = 0; - - // Keep generating sequences until all edges are covered - while visited_edges.len() < total_transitions { - let mut path: Vec = Vec::new(); - let mut current_state = sm.initial_state.clone(); - let mut made_progress = true; - - while made_progress { - made_progress = false; - - if let Some(edges) = adj.get(¤t_state) { - // First, try to find an unvisited edge - let mut found_unvisited = false; - for (idx, t) in edges { - if !visited_edges.contains(idx) { - visited_edges.insert(*idx); - path.push(t.clone()); - current_state = t.to_state.clone(); - made_progress = true; - found_unvisited = true; - break; - } - } - - // If all edges from this node are visited, follow a visited edge - // towards a node that still has unvisited outgoing edges - if !found_unvisited { - // Try to find a path to an unvisited edge via BFS - if let Some(bridge_path) = - find_bridge_to_unvisited(&adj, &visited_edges, ¤t_state) - { - for t in &bridge_path { - path.push(t.clone()); - } - current_state = bridge_path.last().unwrap().to_state.clone(); - made_progress = true; - } - } - } - } - - // Emit the path as a sequence if it's non-empty - if !path.is_empty() { - sequences.push(TestSequence { - id: seq_id, - transitions: path, - }); - seq_id += 1; - } else { - // No progress possible from initial state — try starting from states - // that have unvisited edges (reachable after forced transitions) - // This handles disconnected portions or "default" edges - let mut found_any = false; - for (idx, t) in sm.transitions.iter().enumerate() { - if !visited_edges.contains(&idx) { - // Try to reach from_state from initial_state - if let Some(reach_path) = find_path_to_state( - &adj, - &sm.initial_state, - &t.from_state, - ) { - let mut combined = reach_path; - combined.push(t.clone()); - visited_edges.insert(idx); - sequences.push(TestSequence { - id: seq_id, - transitions: combined, - }); - seq_id += 1; - found_any = true; - break; - } - } - } - if !found_any { - // Mark remaining unreachable transitions as covered to prevent infinite loop - for idx in 0..total_transitions { - visited_edges.insert(idx); - } - } - } - } - - sequences -} - -/// BFS to find a path from `start` to any state that has unvisited outgoing edges. -/// Returns the sequence of transitions to follow (using already-visited edges). -fn find_bridge_to_unvisited( - adj: &HashMap>, - visited_edges: &HashSet, - start: &str, -) -> Option> { - use std::collections::VecDeque; - - let mut queue: VecDeque<(String, Vec)> = VecDeque::new(); - let mut seen: HashSet = HashSet::new(); - seen.insert(start.to_string()); - queue.push_back((start.to_string(), vec![])); - - while let Some((state, path)) = queue.pop_front() { - // Check if this state has unvisited edges - if let Some(edges) = adj.get(&state) { - for (idx, _) in edges { - if !visited_edges.contains(idx) && !path.is_empty() { - // Found a state with unvisited edges via the bridge path - return Some(path); - } - } - } - - // Continue BFS through visited edges - if let Some(edges) = adj.get(&state) { - for (_, t) in edges { - if !seen.contains(&t.to_state) { - seen.insert(t.to_state.clone()); - let mut new_path = path.clone(); - new_path.push(t.clone()); - queue.push_back((t.to_state.clone(), new_path)); - } - } - } - } - - None -} - -/// BFS to find a path from `start` state to `target` state using any edges. -fn find_path_to_state( - adj: &HashMap>, - start: &str, - target: &str, -) -> Option> { - if start == target { - return Some(vec![]); - } - - use std::collections::VecDeque; - - let mut queue: VecDeque<(String, Vec)> = VecDeque::new(); - let mut seen: HashSet = HashSet::new(); - seen.insert(start.to_string()); - queue.push_back((start.to_string(), vec![])); - - while let Some((state, path)) = queue.pop_front() { - if let Some(edges) = adj.get(&state) { - for (_, t) in edges { - if t.to_state == target { - let mut result = path.clone(); - result.push(t.clone()); - return Some(result); - } - if !seen.contains(&t.to_state) { - seen.insert(t.to_state.clone()); - let mut new_path = path.clone(); - new_path.push(t.clone()); - queue.push_back((t.to_state.clone(), new_path)); - } - } - } - } - - None -} - -#[cfg(test)] -mod tests { - use super::*; - - fn make_simple_fsm() -> StateMachine { - StateMachine { - state_variable: "state".to_string(), - state_type: "enum".to_string(), - states: vec![ - State { - name: "S0".to_string(), - value: "0".to_string(), - description: "".to_string(), - }, - State { - name: "S1".to_string(), - value: "1".to_string(), - description: "".to_string(), - }, - ], - initial_state: "S0".to_string(), - transitions: vec![ - Transition { - from_state: "S0".to_string(), - to_state: "S1".to_string(), - condition: "din".to_string(), - input_condition: "din".to_string(), - wait_condition: "".to_string(), - priority: 1, - }, - Transition { - from_state: "S0".to_string(), - to_state: "S0".to_string(), - condition: "!din".to_string(), - input_condition: "!din".to_string(), - wait_condition: "".to_string(), - priority: 2, - }, - Transition { - from_state: "S1".to_string(), - to_state: "S0".to_string(), - condition: "din".to_string(), - input_condition: "din".to_string(), - wait_condition: "".to_string(), - priority: 1, - }, - Transition { - from_state: "S1".to_string(), - to_state: "S1".to_string(), - condition: "!din".to_string(), - input_condition: "!din".to_string(), - wait_condition: "".to_string(), - priority: 2, - }, - ], - control_signals: vec!["din".to_string()], - data_signals: vec![], - internal_signals: vec![], - parameters: vec![], - } - } - - #[test] - fn test_dfs_covers_all_transitions() { - let sm = make_simple_fsm(); - let sequences = dfs_cover_transitions(&sm); - - // Collect all edges covered - let mut covered: HashSet<(String, String, String)> = HashSet::new(); - for seq in &sequences { - for t in &seq.transitions { - covered.insert(( - t.from_state.clone(), - t.to_state.clone(), - t.condition.clone(), - )); - } - } - - // All 4 transitions should be covered - assert_eq!(covered.len(), 4); - assert!(covered.contains(&("S0".into(), "S1".into(), "din".into()))); - assert!(covered.contains(&("S0".into(), "S0".into(), "!din".into()))); - assert!(covered.contains(&("S1".into(), "S0".into(), "din".into()))); - assert!(covered.contains(&("S1".into(), "S1".into(), "!din".into()))); - } - - #[test] - fn test_json_roundtrip() { - let analysis = FsmAnalysis { - module_name: "test".to_string(), - state_machines: vec![make_simple_fsm()], - clock_signal: "clk".to_string(), - reset_signal: "rst_n".to_string(), - reset_active_high: false, - }; - - let json = serde_json::to_string_pretty(&analysis).unwrap(); - let parsed: FsmAnalysis = serde_json::from_str(&json).unwrap(); - - assert_eq!(parsed.module_name, "test"); - assert_eq!(parsed.state_machines.len(), 1); - assert_eq!(parsed.state_machines[0].states.len(), 2); - assert_eq!(parsed.state_machines[0].transitions.len(), 4); - } -} diff --git a/src/tools/fsm_extractor.rs b/src/tools/fsm_extractor.rs deleted file mode 100644 index d2d22ac..0000000 --- a/src/tools/fsm_extractor.rs +++ /dev/null @@ -1,760 +0,0 @@ -//! Deterministic FSM extraction from Verilog/SystemVerilog source. -//! -//! Extracts state machines by analyzing: -//! 1. `typedef enum` declarations for state encodings -//! 2. `always_ff` blocks for state register assignments (to find initial/reset state) -//! 3. `always_comb` blocks with `case` statements for next-state logic -//! -//! This is a best-effort static analysis that works well for standard FSM coding styles. - -use crate::tools::fsm_analysis::{FsmAnalysis, Parameter, State, StateMachine, Transition}; -use crate::tools::verilog_parser::ModuleInfo; -use anyhow::{Context, Result}; -use regex::Regex; -use std::collections::{HashMap, HashSet}; -use std::fs; -use std::path::Path; - -/// Extract FSM analysis from a Verilog source file using deterministic parsing. -pub fn extract_fsm( - verilog_path: &Path, - module_info: &ModuleInfo, - clock_name: &str, - reset_name: &str, - reset_active_high: bool, -) -> Result { - let source = fs::read_to_string(verilog_path) - .with_context(|| format!("Failed to read {}", verilog_path.display()))?; - - let state_machines = extract_state_machines(&source, module_info)?; - - Ok(FsmAnalysis { - module_name: module_info.name.clone(), - state_machines, - clock_signal: clock_name.to_string(), - reset_signal: reset_name.to_string(), - reset_active_high, - }) -} - -/// Extract all state machines from the source. -fn extract_state_machines(source: &str, module_info: &ModuleInfo) -> Result> { - let mut machines = Vec::new(); - - // Step 0: Extract all localparam/parameter constants from the source - let constants = extract_constants(source); - - // Step 1: Find enum type definitions - let enums = extract_enum_types(source); - - // Step 2: Find state variable declarations (variables using enum types) - let state_vars = find_state_variables(source, &enums); - - // Step 3: For each state variable, find the FSM structure - for (state_var, enum_name) in &state_vars { - if let Some(enum_def) = enums.iter().find(|e| &e.name == enum_name) { - // Find the initial/reset state from always_ff blocks - let initial_state = find_initial_state(source, state_var).unwrap_or_else(|| { - enum_def - .values - .first() - .map(|v| v.0.clone()) - .unwrap_or_default() - }); - - // Find transitions from always_comb case statements - let next_state_var = find_next_state_variable(source, state_var); - let case_var = next_state_var.as_deref().unwrap_or(state_var); - let transitions = - extract_transitions_from_case(source, state_var, case_var, &enum_def.values); - - // Classify signals - let input_names: HashSet = module_info - .ports - .iter() - .filter(|p| p.direction == "input") - .map(|p| p.name.clone()) - .collect(); - - let (control_signals, _data_signals, raw_internal_signals) = - classify_transition_signals(&transitions, &input_names); - - // Separate compile-time constants from runtime signals - let state_names: HashSet = - enum_def.values.iter().map(|(n, _)| n.clone()).collect(); - let (internal_signals, parameters) = - classify_internal_signals(&raw_internal_signals, &constants, &state_names); - - let states: Vec = enum_def - .values - .iter() - .map(|(name, value)| State { - name: name.clone(), - value: value.clone(), - description: String::new(), - }) - .collect(); - - machines.push(StateMachine { - state_variable: state_var.clone(), - state_type: "enum".to_string(), - states, - initial_state, - transitions, - control_signals, - data_signals: vec![], - internal_signals, - parameters, - }); - } - } - - // Fallback: if no enum-based FSMs found, try localparam/parameter-based - if machines.is_empty() - && let Some(sm) = try_extract_param_fsm(source, module_info, &constants) - { - machines.push(sm); - } - - Ok(machines) -} - -/// Represents an enum type definition. -#[derive(Debug)] -struct EnumDef { - name: String, - values: Vec<(String, String)>, // (name, value) -} - -/// Extract `typedef enum` definitions from the source. -fn extract_enum_types(source: &str) -> Vec { - let mut enums = Vec::new(); - - // Match: typedef enum logic [N:0] { ... } name_t; - let re = Regex::new( - r"(?s)typedef\s+enum\s+(?:logic\s*(?:\[\s*\d+\s*:\s*\d+\s*\])?\s*)?\{([^}]+)\}\s*(\w+)\s*;", - ) - .unwrap(); - - let val_re = Regex::new(r"(\w+)\s*=\s*([^,\s]+)").unwrap(); - let name_re = Regex::new(r"(\w+)").unwrap(); - - for cap in re.captures_iter(source) { - let body = &cap[1]; - let type_name = cap[2].to_string(); - - let mut values = Vec::new(); - // Parse enum values: NAME = VALUE - for vcap in val_re.captures_iter(body) { - values.push((vcap[1].to_string(), vcap[2].to_string())); - } - - // If no explicit values, extract names and assign indices - if values.is_empty() { - for (idx, ncap) in name_re.captures_iter(body).enumerate() { - values.push((ncap[1].to_string(), format!("{}", idx))); - } - } - - enums.push(EnumDef { - name: type_name, - values, - }); - } - - enums -} - -/// Find state variables: variables declared with an enum type, looking for pairs like -/// `state_t state, next_state;` or `state_t current_state;` -fn find_state_variables(source: &str, enums: &[EnumDef]) -> Vec<(String, String)> { - let mut vars = Vec::new(); - - for e in enums { - // Match: type_name var1, var2, ...; - let pattern = format!(r"{}\s+([\w,\s]+);", regex::escape(&e.name)); - let re = Regex::new(&pattern).unwrap(); - - for cap in re.captures_iter(source) { - let var_list = &cap[1]; - let var_names: Vec<&str> = var_list.split(',').map(|s| s.trim()).collect(); - - // The state variable is typically the one without "next" in the name - for var_name in &var_names { - if !var_name.contains("next") && !var_name.is_empty() { - vars.push((var_name.to_string(), e.name.clone())); - break; // Take first non-next variable - } - } - } - } - - vars -} - -/// Find the next_state variable name associated with a state variable. -fn find_next_state_variable(source: &str, state_var: &str) -> Option { - // Common patterns: next_state, nstate, ns, state_next - let candidates = [ - format!("next_{}", state_var), - format!("{}_next", state_var), - "next_state".to_string(), - "nstate".to_string(), - "ns".to_string(), - ]; - - for candidate in &candidates { - let pattern = format!(r"\b{}\b", regex::escape(candidate)); - if Regex::new(&pattern).unwrap().is_match(source) { - return Some(candidate.clone()); - } - } - - None -} - -/// Find the initial/reset state from always_ff blocks. -fn find_initial_state(source: &str, state_var: &str) -> Option { - // Look for reset assignment pattern: - // if (!rst_n) state <= S0; - // if (rst) state <= IDLE; - let pattern = format!( - r"(?:if\s*\(.*?(?:rst|reset).*?\))\s*(?:begin\s*)?{}(?:\s*<=\s*|\s*=\s*)(\w+)", - regex::escape(state_var) - ); - let re = Regex::new(&pattern).ok()?; - - if let Some(cap) = re.captures(source) { - return Some(cap[1].to_string()); - } - - None -} - -/// Extract transitions from `case(state)` blocks in `always_comb` or `always @*` blocks. -fn extract_transitions_from_case( - source: &str, - state_var: &str, - next_state_var: &str, - enum_values: &[(String, String)], -) -> Vec { - let mut transitions = Vec::new(); - - // Find the case block for the state variable - // Match: case (state) or case(state) - let case_pattern = format!( - r"(?s)case\s*\(\s*{}\s*\)(.*?)endcase", - regex::escape(state_var) - ); - let case_re = Regex::new(&case_pattern).unwrap(); - - let state_names: HashSet = enum_values.iter().map(|(n, _)| n.clone()).collect(); - - for case_match in case_re.captures_iter(source) { - let case_body = &case_match[1]; - - // Split case body into items manually (no lookahead needed) - // Find all positions where a state label starts: "STATE_NAME :" or "default :" - let label_pattern = format!( - r"\b({}|default)\s*:", - enum_values - .iter() - .map(|(n, _)| regex::escape(n)) - .collect::>() - .join("|"), - ); - let label_re = Regex::new(&label_pattern).unwrap(); - - // Collect (start_of_match, label_name, start_of_body) tuples - let mut items: Vec<(usize, String, usize)> = Vec::new(); - for m in label_re.captures_iter(case_body) { - let full_match = m.get(0).unwrap(); - let label = m[1].to_string(); - items.push((full_match.start(), label, full_match.end())); - } - - // Extract the body for each item (text between this label and the next) - for i in 0..items.len() { - let from_state = &items[i].1; - let body_start = items[i].2; - let body_end = if i + 1 < items.len() { - items[i + 1].0 - } else { - case_body.len() - }; - let item_body = &case_body[body_start..body_end]; - - let item_transitions = - extract_transitions_from_item(from_state, item_body, next_state_var, &state_names); - transitions.extend(item_transitions); - } - } - - transitions -} - -/// Extract transitions from a single case item body (e.g., the code after `S0: begin ... end`). -fn extract_transitions_from_item( - from_state: &str, - body: &str, - next_state_var: &str, - state_names: &HashSet, -) -> Vec { - let mut transitions = Vec::new(); - - // Find all assignments to next_state_var - let assign_pattern = format!(r"(?s){}\s*(?:<=|=)\s*(\w+)", regex::escape(next_state_var)); - let assign_re = Regex::new(&assign_pattern).unwrap(); - - // Strategy: parse if/else chains to extract conditions per assignment - // Split the body into condition blocks - let conditions = parse_if_else_chain(body, next_state_var, state_names); - - if !conditions.is_empty() { - for (priority, (condition, to_state)) in conditions.iter().enumerate() { - transitions.push(Transition { - from_state: from_state.to_string(), - to_state: to_state.clone(), - condition: condition.clone(), - input_condition: condition.clone(), // Will be refined later - wait_condition: String::new(), - priority: (priority + 1) as i32, - }); - } - } else { - // No if/else found — unconditional assignment - for cap in assign_re.captures_iter(body) { - let to_state = cap[1].to_string(); - if state_names.contains(&to_state) || to_state == from_state { - transitions.push(Transition { - from_state: from_state.to_string(), - to_state, - condition: "true".to_string(), - input_condition: String::new(), - wait_condition: String::new(), - priority: 1, - }); - break; - } - } - } - - transitions -} - -/// Parse if/else chains to extract (condition, next_state) pairs. -/// -/// Uses a line-based approach that handles common Verilog FSM patterns: -/// if (cond) next_state = X; else next_state = Y; -/// if (cond) begin next_state = X; end else begin next_state = Y; end -/// if (cond1) ... else if (cond2) ... else ... -fn parse_if_else_chain( - body: &str, - next_state_var: &str, - state_names: &HashSet, -) -> Vec<(String, String)> { - let mut results = Vec::new(); - - let assign_pattern = format!(r"{}\s*(?:<=|=)\s*(\w+)", regex::escape(next_state_var)); - let assign_re = Regex::new(&assign_pattern).unwrap(); - - // Regex patterns for line-level parsing - let if_re = Regex::new(r"\bif\s*\(([^)]+)\)").unwrap(); - let else_if_re = Regex::new(r"\belse\s+if\s*\(([^)]+)\)").unwrap(); - let else_re = Regex::new(r"\belse\b").unwrap(); - - // Track the current condition context - let mut current_conditions: Vec = Vec::new(); - let mut in_else = false; - - for line in body.lines() { - let trimmed = line.trim(); - if trimmed.is_empty() || trimmed.starts_with("//") { - continue; - } - - // Check for else if (must be checked before else) - if let Some(cap) = else_if_re.captures(trimmed) { - let cond = cap[1].trim().to_string(); - current_conditions.push(cond); - in_else = false; - - // Also check if there's an assignment on the same line - if let Some(acap) = assign_re.captures(trimmed) { - let to_state = acap[1].to_string(); - if state_names.contains(&to_state) { - let condition = current_conditions.last().unwrap().clone(); - results.push((condition, to_state)); - } - } - continue; - } - - // Check for plain else (not else if) - if else_re.is_match(trimmed) && !else_if_re.is_match(trimmed) { - in_else = true; - - // Also check if there's an assignment on the same line after else - if let Some(acap) = assign_re.captures(trimmed) { - let to_state = acap[1].to_string(); - if state_names.contains(&to_state) { - let condition = if current_conditions.len() == 1 { - format!("!({})", current_conditions[0]) - } else { - current_conditions - .iter() - .map(|c| format!("!({})", c)) - .collect::>() - .join(" && ") - }; - results.push((condition, to_state)); - } - } - continue; - } - - // Check for if (not else if) - if let Some(cap) = if_re.captures(trimmed) - && !else_if_re.is_match(trimmed) - { - let cond = cap[1].trim().to_string(); - current_conditions.clear(); - current_conditions.push(cond); - in_else = false; - - // Also check if there's an assignment on the same line - if let Some(acap) = assign_re.captures(trimmed) { - let to_state = acap[1].to_string(); - if state_names.contains(&to_state) { - results.push((current_conditions.last().unwrap().clone(), to_state)); - } - } - continue; - } - - // Check for assignment on a line that's not an if/else line - if let Some(acap) = assign_re.captures(trimmed) { - let to_state = acap[1].to_string(); - if state_names.contains(&to_state) { - let condition = if in_else && !current_conditions.is_empty() { - if current_conditions.len() == 1 { - format!("!({})", current_conditions[0]) - } else { - current_conditions - .iter() - .map(|c| format!("!({})", c)) - .collect::>() - .join(" && ") - } - } else if !current_conditions.is_empty() { - current_conditions.last().unwrap().clone() - } else { - "true".to_string() - }; - - results.push((condition, to_state)); - } - } - } - - // Deduplicate - results.dedup_by(|a, b| a.0 == b.0 && a.1 == b.1); - - results -} - -/// Classify signals referenced in transitions as control, data, or internal. -fn classify_transition_signals( - transitions: &[Transition], - input_names: &HashSet, -) -> (Vec, Vec, Vec) { - let mut control = HashSet::new(); - let mut internal = HashSet::new(); - let word_re = Regex::new(r"\b([a-zA-Z_]\w*)\b").unwrap(); - - for t in transitions { - // Extract signal names from conditions - for cap in word_re.captures_iter(&t.condition) { - let name = &cap[1]; - // Skip Verilog keywords and numeric literals - if is_verilog_keyword(name) || name.starts_with("1'") { - continue; - } - if input_names.contains(name) { - control.insert(name.to_string()); - } else if !name.is_empty() { - // Could be an internal signal or a state name — skip state names - // We check if it looks like a signal (not all caps) - internal.insert(name.to_string()); - } - } - } - - // Refine input/wait conditions - // (This would modify transitions in a mutable version, but we return classifications) - - ( - control.into_iter().collect(), - vec![], // data signals need separate analysis - internal.into_iter().collect(), - ) -} - -/// Extract all `localparam` and `parameter` constant values from the Verilog source. -/// -/// Returns a map from constant name to its resolved numeric value. -/// Handles common declarations like: -/// localparam int GREEN_TICKS = 10; -/// localparam GREEN_TICKS = 10; -/// localparam logic [3:0] YELLOW_TICKS = 3; -/// parameter N = 8; -fn extract_constants(source: &str) -> HashMap { - let mut constants: HashMap = HashMap::new(); - - // Match individual constant declarations: - // localparam [type] NAME = VALUE; - // parameter [type] NAME = VALUE; - // - // Also handles grouped declarations like: - // localparam int GREEN_TICKS = 10, YELLOW_TICKS = 3; - let decl_re = Regex::new( - r"(?:localparam|parameter)\s+(?:(?:int|integer|logic|reg|wire)\s*(?:\[\s*\d+\s*:\s*\d+\s*\])?\s*)?([^;]+);", - ) - .unwrap(); - - let assign_re = Regex::new(r"(\w+)\s*=\s*([^,;]+)").unwrap(); - - for cap in decl_re.captures_iter(source) { - let body = &cap[1]; - for acap in assign_re.captures_iter(body) { - let name = acap[1].trim().to_string(); - let value_str = acap[2].trim(); - if let Some(val) = parse_verilog_constant(value_str, &constants) { - constants.insert(name, val); - } - } - } - - constants -} - -/// Parse a Verilog constant expression to a u64. -/// -/// Handles: -/// - Plain decimal: "10", "42" -/// - Verilog literals: "3'd5", "8'hFF", "4'b1010", "32'd100" -/// - Simple arithmetic: "GREEN_TICKS - 1", "N + 2", "N * 2" -/// - References to previously resolved constants -fn parse_verilog_constant(expr: &str, known: &HashMap) -> Option { - let trimmed = expr.trim(); - - // Try plain decimal - if let Ok(v) = trimmed.parse::() { - return Some(v); - } - - // Try Verilog literal: ' - let lit_re = Regex::new(r"^\d*'([bBdDhHoO])([0-9a-fA-F_]+)$").ok()?; - if let Some(cap) = lit_re.captures(trimmed) { - let base = match &cap[1].to_lowercase()[..] { - "b" => 2, - "o" => 8, - "d" => 10, - "h" => 16, - _ => return None, - }; - let digits = cap[2].replace('_', ""); - return u64::from_str_radix(&digits, base).ok(); - } - - // Try known constant reference - if let Some(val) = known.get(trimmed) { - return Some(*val); - } - - // Try simple binary arithmetic: - for op in &[" + ", " - ", " * ", " / "] { - if let Some(pos) = trimmed.find(op) { - let lhs_str = trimmed[..pos].trim(); - let rhs_str = trimmed[pos + op.len()..].trim(); - let lhs = parse_verilog_constant(lhs_str, known)?; - let rhs = parse_verilog_constant(rhs_str, known)?; - return match *op { - " + " => Some(lhs.wrapping_add(rhs)), - " - " => Some(lhs.wrapping_sub(rhs)), - " * " => Some(lhs.wrapping_mul(rhs)), - " / " => { - if rhs == 0 { - None - } else { - Some(lhs / rhs) - } - } - _ => None, - }; - } - } - - None -} - -/// Given internal_signals and the extracted constants, separate them into: -/// - runtime signals (actual registers/wires accessible via hierarchical access) -/// - parameters (compile-time constants with known values) -/// -/// Also removes state names from internal_signals. -fn classify_internal_signals( - internal_signals: &[String], - constants: &HashMap, - state_names: &HashSet, -) -> (Vec, Vec) { - let mut runtime = Vec::new(); - let mut params = Vec::new(); - - for sig in internal_signals { - if state_names.contains(sig) { - // Skip state names — they're not signals - continue; - } - if let Some(&val) = constants.get(sig) { - params.push(Parameter { - name: sig.clone(), - value: val, - }); - } else { - runtime.push(sig.clone()); - } - } - - (runtime, params) -} - -fn is_verilog_keyword(s: &str) -> bool { - matches!( - s, - "if" | "else" - | "begin" - | "end" - | "case" - | "endcase" - | "default" - | "always" - | "always_comb" - | "always_ff" - | "assign" - | "wire" - | "reg" - | "logic" - | "input" - | "output" - | "inout" - | "module" - | "endmodule" - | "posedge" - | "negedge" - | "or" - | "and" - | "not" - | "true" - | "false" - ) -} - -/// Fallback: try to extract FSM from localparam/parameter-based state encoding. -fn try_extract_param_fsm( - source: &str, - module_info: &ModuleInfo, - constants: &HashMap, -) -> Option { - // Look for localparam patterns like: - // localparam S0 = 3'b000, S1 = 3'b001, ... - // localparam IDLE = 0, RUN = 1, ... - let param_re = Regex::new( - r"(?s)(?:localparam|parameter)\s+((?:\w+\s*=\s*[^;,]+(?:,\s*\w+\s*=\s*[^;,]+)*))\s*;", - ) - .ok()?; - - let mut best_states: Vec<(String, String)> = Vec::new(); - let val_re = Regex::new(r"(\w+)\s*=\s*(\S+)").unwrap(); - - for cap in param_re.captures_iter(source) { - let param_body = &cap[1]; - let values: Vec<(String, String)> = val_re - .captures_iter(param_body) - .map(|v| (v[1].to_string(), v[2].to_string())) - .collect(); - - // Heuristic: if there are 3+ values and names look like state names - if values.len() >= 3 && values.len() > best_states.len() { - let looks_like_states = values.iter().any(|(n, _)| { - n.starts_with('S') - || n.contains("IDLE") - || n.contains("INIT") - || n.contains("STATE") - }); - if looks_like_states { - best_states = values; - } - } - } - - if best_states.is_empty() { - return None; - } - - // Find the state variable by looking for case statements using these names - let state_name_pattern = best_states - .iter() - .map(|(n, _)| regex::escape(n)) - .collect::>() - .join("|"); - - let case_re = Regex::new(&format!( - r"case\s*\(\s*(\w+)\s*\).*?(?:{})", - state_name_pattern - )) - .ok()?; - - let state_var = case_re.captures(source).map(|c| c[1].to_string())?; - - let next_state_var = find_next_state_variable(source, &state_var); - let case_var = next_state_var.as_deref().unwrap_or(&state_var); - - let transitions = extract_transitions_from_case(source, &state_var, case_var, &best_states); - - let initial_state = - find_initial_state(source, &state_var).unwrap_or_else(|| best_states[0].0.clone()); - - let input_names: HashSet = module_info - .ports - .iter() - .filter(|p| p.direction == "input") - .map(|p| p.name.clone()) - .collect(); - - let (control_signals, _, raw_internal_signals) = - classify_transition_signals(&transitions, &input_names); - - let state_name_set: HashSet = best_states.iter().map(|(n, _)| n.clone()).collect(); - let (internal_signals, parameters) = - classify_internal_signals(&raw_internal_signals, constants, &state_name_set); - - Some(StateMachine { - state_variable: state_var, - state_type: "parameter".to_string(), - states: best_states - .iter() - .map(|(name, value)| State { - name: name.clone(), - value: value.clone(), - description: String::new(), - }) - .collect(), - initial_state, - transitions, - control_signals, - data_signals: vec![], - internal_signals, - parameters, - }) -} diff --git a/src/tools/verilog_parser.rs b/src/tools/verilog_parser.rs index 89a6204..41b8d1c 100644 --- a/src/tools/verilog_parser.rs +++ b/src/tools/verilog_parser.rs @@ -262,12 +262,13 @@ fn try_parse_with_sv_parser(verilog_text: &str) -> Result> { 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) - && let Some(&child_instances) = counts.get(child) { + 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; } + } } } // Count actual instantiations, not just unique types @@ -689,14 +690,14 @@ fn sort_with_sv_parser_priority( // Use sv-parser's order but with iverilog's port information and sv-parser's submodule_count let mut result: Vec = sv_modules .into_iter() - .map(|sv_module| { + .filter_map(|sv_module| { if let Some(mut iverilog_module) = iverilog_map.remove(&sv_module.name) { // Use iverilog's ports but sv-parser's submodule_count iverilog_module.submodule_count = sv_module.submodule_count; - iverilog_module + Some(iverilog_module) } else { // Module found by sv-parser but not iverilog, keep sv-parser version - sv_module + Some(sv_module) } }) .collect(); diff --git a/tools/fsm_analyzer/fsm_analyzer/__init__.py b/tools/fsm_analyzer/fsm_analyzer/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tools/fsm_analyzer/fsm_analyzer/main.py b/tools/fsm_analyzer/fsm_analyzer/main.py deleted file mode 100644 index 9756076..0000000 --- a/tools/fsm_analyzer/fsm_analyzer/main.py +++ /dev/null @@ -1,174 +0,0 @@ -"""CLI entry point for the FSM analyzer. - -Usage: - uv run fsm-analyzer --verilog-source path/to/dut.sv --output analysis.json --provider gemini --model gemini-2.5-flash -""" - -import argparse -import json -import os -import sys -from pathlib import Path - - -def main(): - parser = argparse.ArgumentParser( - description="Analyze Verilog RTL to extract FSM state machines using LLMs" - ) - parser.add_argument( - "--verilog-source", - required=True, - help="Path to the Verilog/SystemVerilog source file", - ) - parser.add_argument( - "--output", - required=True, - help="Output path for the state analysis JSON", - ) - parser.add_argument( - "--provider", - choices=["openai", "gemini", "openrouter"], - default="gemini", - help="LLM provider to use (default: gemini)", - ) - parser.add_argument( - "--model", - default=None, - help="Model name (default depends on provider)", - ) - parser.add_argument( - "--endpoint", - default=None, - help="Custom API endpoint URL (for openai-compatible providers)", - ) - parser.add_argument( - "--env-file", - default=None, - help="Path to .env file for API keys", - ) - - args = parser.parse_args() - - # Load .env file if specified or look for one in common locations - env_paths = [] - if args.env_file: - env_paths.append(args.env_file) - else: - # Search for .env in parent directories - current = Path(args.verilog_source).resolve().parent - for _ in range(10): - env_path = current / ".env" - if env_path.exists(): - env_paths.append(str(env_path)) - break - parent = current.parent - if parent == current: - break - current = parent - - if env_paths: - try: - from dotenv import load_dotenv - - for env_path in env_paths: - load_dotenv(env_path, override=False) - print(f"Loaded environment from: {env_path}", file=sys.stderr) - except ImportError: - print( - "Warning: python-dotenv not installed, .env file ignored", - file=sys.stderr, - ) - - # Read Verilog source - verilog_path = Path(args.verilog_source) - if not verilog_path.exists(): - print(f"Error: Verilog file not found: {verilog_path}", file=sys.stderr) - sys.exit(1) - - verilog_source = verilog_path.read_text() - - # Set default model based on provider - model = args.model - if model is None: - defaults = { - "openai": "gpt-4", - "gemini": "gemini-2.5-flash", - "openrouter": "anthropic/claude-3-opus", - } - model = defaults.get(args.provider, "gpt-4") - - # Run analysis - print( - f"Analyzing FSM with provider={args.provider}, model={model}", - file=sys.stderr, - ) - - try: - if args.provider == "gemini": - from .providers.gemini_provider import analyze - - api_key = os.environ.get("GOOGLE_API_KEY") - if not api_key: - print( - "Error: GOOGLE_API_KEY not set. Set it in .env or environment.", - file=sys.stderr, - ) - sys.exit(1) - result = analyze(verilog_source, model=model, api_key=api_key) - - elif args.provider in ("openai", "openrouter"): - from .providers.openai_provider import analyze - - if args.provider == "openrouter": - api_key = os.environ.get("OPENROUTER_API_KEY") - endpoint = args.endpoint or "https://openrouter.ai/api/v1" - if not api_key: - print( - "Error: OPENROUTER_API_KEY not set.", - file=sys.stderr, - ) - sys.exit(1) - else: - api_key = os.environ.get("OPENAI_API_KEY") - endpoint = args.endpoint - if not api_key: - print( - "Error: OPENAI_API_KEY not set.", - file=sys.stderr, - ) - sys.exit(1) - - result = analyze( - verilog_source, - model=model, - endpoint=endpoint, - api_key=api_key, - ) - else: - print(f"Error: Unknown provider: {args.provider}", file=sys.stderr) - sys.exit(1) - - except Exception as e: - print(f"Error during LLM analysis: {e}", file=sys.stderr) - sys.exit(1) - - # Write output - output_path = Path(args.output) - output_path.parent.mkdir(parents=True, exist_ok=True) - output_path.write_text(json.dumps(result, indent=2) + "\n") - print(f"FSM analysis written to: {output_path}", file=sys.stderr) - - # Print summary - state_machines = result.get("state_machines", []) - print(f"Found {len(state_machines)} state machine(s):", file=sys.stderr) - for sm in state_machines: - states = sm.get("states", []) - transitions = sm.get("transitions", []) - print( - f" - {sm.get('state_variable', '?')}: {len(states)} states, {len(transitions)} transitions", - file=sys.stderr, - ) - - -if __name__ == "__main__": - main() diff --git a/tools/fsm_analyzer/fsm_analyzer/prompt.py b/tools/fsm_analyzer/fsm_analyzer/prompt.py deleted file mode 100644 index 702a9b8..0000000 --- a/tools/fsm_analyzer/fsm_analyzer/prompt.py +++ /dev/null @@ -1,341 +0,0 @@ -"""LLM prompt template for FSM state machine extraction from Verilog RTL.""" - -import re - -SYSTEM_PROMPT = """\ -You are an expert digital design engineer specializing in FSM (Finite State Machine) analysis. -Your task is to analyze Verilog/SystemVerilog RTL code and extract all state machines present in the design. - -You must output ONLY valid JSON (no markdown, no explanation, no code fences) matching the schema below. -""" - -_FSM_ANALYSIS_TEMPLATE = """\ -Analyze the following Verilog/SystemVerilog module and extract all finite state machines (FSMs). - -For each state machine, identify: -1. The state variable name (e.g., "state", "current_state", "cs") -2. The state encoding type ("enum", "parameter", "localparam", "integer") -3. All states with their names, values, and descriptions -4. The initial/reset state -5. All transitions between states, including: - - from_state: source state name - - to_state: destination state name - - condition: the full transition condition expression - - input_condition: the part of the condition that depends on INPUT signals (external pins) - - wait_condition: the part of the condition that depends on INTERNAL signals (counters, registers, etc.) - - priority: transition priority (1 = highest, checked first in if/else chain) -6. Control signals: input signals that directly affect state transitions -7. Data signals: input signals that don't affect state transitions -8. Internal signals: internal RUNTIME registers/counters/wires that affect state transitions (e.g., timer, ped_latch). - Do NOT include localparam or parameter constants here — put those in "parameters". -9. Parameters: compile-time constants (localparam, parameter) that appear in transition conditions. - For each, provide the name and its resolved integer value. - -Also identify the clock signal, reset signal, and whether reset is active high. - -IMPORTANT rules for conditions: -- input_condition should ONLY reference input port signals -- wait_condition should ONLY reference internal signals (registers, counters, wires defined inside the module) -- If a transition condition mixes input and internal signals, split them appropriately -- If a transition depends only on inputs, wait_condition should be "" -- If a transition depends only on internal state (e.g., counter >= THRESHOLD - 1), input_condition should be "" and wait_condition should contain the expression -- Use Verilog syntax for conditions (e.g., "din", "!(din)", "sel == 2'b01", "counter == 8'd255") -- ONLY use signal and parameter names that actually exist in the module - -IMPORTANT rules for internal_signals vs parameters: -- "internal_signals" are RUNTIME signals: logic, reg, wire declarations that hold state at runtime (e.g., timer, counter, ped_latch) -- "parameters" are COMPILE-TIME constants: localparam or parameter declarations with fixed numeric values (e.g., GREEN_TICKS = 10, THRESHOLD = 255) -- A localparam or parameter MUST go in "parameters" with its resolved integer value, NOT in "internal_signals" - -IMPORTANT rules for signal references in conditions: -- Use ONLY the exact signal names as declared in the module -- Do NOT abbreviate or modify signal names -- Do NOT reference signals that do not exist in the module -- Every signal name in "control_signals", "data_signals", and "internal_signals" arrays must match an actual declaration in the source code -%s -Output ONLY a JSON object with this exact structure (no markdown, no extra text): -{ - "module_name": "", - "state_machines": [ - { - "state_variable": "", - "state_type": "enum|parameter|localparam|integer", - "states": [ - {"name": "", "value": "", "description": ""} - ], - "initial_state": "", - "transitions": [ - { - "from_state": "", - "to_state": "", - "condition": "", - "input_condition": "", - "wait_condition": "", - "priority": - } - ], - "control_signals": [""], - "data_signals": [""], - "internal_signals": [""], - "parameters": [ - {"name": "", "value": } - ] - } - ], - "clock_signal": "", - "reset_signal": "", - "reset_active_high": true|false -} - -Here is the Verilog source code to analyze: - -```verilog -%s -``` -""" - - -def _strip_comments(source: str) -> str: - """Strip Verilog comments from source code. - - Removes both line comments (// ...) and block comments (/* ... */) - to prevent the regex-based extractor from matching inside comments. - """ - # Remove block comments first (can span multiple lines) - source = re.sub(r"/\*.*?\*/", " ", source, flags=re.DOTALL) - # Remove line comments - source = re.sub(r"//[^\n]*", " ", source) - return source - - -def extract_module_symbols(verilog_source: str) -> str: - """Extract available symbols from Verilog source to provide as context to the LLM. - - Performs a lightweight regex-based scan of the Verilog source to discover - module ports, internal signals, parameters, and enum definitions. The result - is a human-readable summary suitable for inclusion in the LLM prompt so the - model knows exactly which symbol names are available for use in conditions. - - Returns: - A formatted string section listing all discoverable symbols, - or an empty string if nothing was found. - """ - # Strip comments to avoid false matches from text inside comments - clean_src = _strip_comments(verilog_source) - lines: list[str] = [] - - # --- Module name --- - m = re.search(r"\bmodule\s+(\w+)", clean_src) - if m: - lines.append(f"Module: {m.group(1)}") - - # --- Port declarations --- - # Handles both ANSI-style (inside module header) and non-ANSI (body declarations). - port_re = re.compile( - r"\b(input|output|inout)\s+" - r"(?:wire\s+|reg\s+|logic\s+)?" - r"(?:signed\s+)?" - r"(?:\[([^\]]*)\]\s*)?" - r"(\w+)", - ) - inputs: list[str] = [] - outputs: list[str] = [] - inouts: list[str] = [] - port_names: set[str] = set() - - for m in port_re.finditer(clean_src): - direction, width_expr, name = m.group(1), m.group(2), m.group(3) - port_names.add(name) - width_str = f" [{width_expr}]" if width_expr else "" - entry = f" - {name}{width_str}" - if direction == "input": - inputs.append(entry) - elif direction == "output": - outputs.append(entry) - else: - inouts.append(entry) - - if inputs: - lines.append( - "\nINPUT PORTS (candidates for control_signals or data_signals):" - ) - lines.extend(inputs) - if outputs: - lines.append("\nOUTPUT PORTS:") - lines.extend(outputs) - if inouts: - lines.append("\nINOUT PORTS:") - lines.extend(inouts) - - # --- Internal signal declarations --- - sig_re = re.compile( - r"\b(reg|wire|logic|integer)\s+" - r"(?:signed\s+)?" - r"(?:\[([^\]]*)\]\s*)?" - r"(\w+)", - ) - seen_sigs: set[str] = set() - internals: list[str] = [] - - for m in sig_re.finditer(clean_src): - sig_type, width_expr, name = m.group(1), m.group(2), m.group(3) - if name in port_names or name in seen_sigs: - continue - seen_sigs.add(name) - width_str = f" [{width_expr}]" if width_expr else "" - internals.append(f" - {sig_type}{width_str} {name}") - - if internals: - lines.append( - "\nINTERNAL SIGNALS (candidates for internal_signals or state variables):" - ) - lines.extend(internals) - - # --- Parameters and localparams --- - param_re = re.compile( - r"\b(localparam|parameter)\s+" - r"(?:\w+\s+)?" # optional type keyword - r"(?:\[([^\]]*)\]\s*)?" # optional width - r"(\w+)\s*=\s*([^;,\)\n]+)", - ) - params: list[str] = [] - - for m in param_re.finditer(clean_src): - kind, _, name, value = ( - m.group(1), - m.group(2), - m.group(3), - m.group(4).strip(), - ) - params.append(f" - {kind} {name} = {value}") - - if params: - lines.append( - '\nPARAMETERS / LOCALPARAMS (must go in "parameters" array, NOT in "internal_signals"):' - ) - lines.extend(params) - - # --- Enum / typedef enum state values --- - enum_re = re.compile(r"typedef\s+enum\s+[^{]*\{([^}]+)\}", re.DOTALL) - enum_vals: list[str] = [] - - for m in enum_re.finditer(clean_src): - body = m.group(1) - for item in body.split(","): - item = item.strip() - if item: - name_part = item.split("=")[0].strip() - if name_part: - enum_vals.append(f" - {name_part}") - - if enum_vals: - lines.append("\nENUM / STATE VALUES:") - lines.extend(enum_vals) - - if not lines: - return "" - - return ( - "\n=== Available Symbols in this Module ===\n" - + "\n".join(lines) - + "\n=== End of Available Symbols ===\n" - ) - - -def build_fsm_analysis_prompt(verilog_source: str) -> str: - """Build the complete FSM analysis prompt including extracted symbol context. - - This is the primary API for constructing the user prompt. It: - 1. Extracts available symbols from the Verilog source (ports, internals, params) - 2. Inserts them into the prompt template so the LLM knows which names to use - 3. Appends the full Verilog source for analysis - - Args: - verilog_source: The raw Verilog/SystemVerilog source code. - - Returns: - A fully-formatted prompt string ready to send to the LLM. - """ - symbols_section = extract_module_symbols(verilog_source) - return _FSM_ANALYSIS_TEMPLATE % (symbols_section, verilog_source) - - -# Keep backward compatibility: the old single-%s prompt (without symbols). -# Providers should prefer build_fsm_analysis_prompt() instead. -FSM_ANALYSIS_PROMPT = """\ -Analyze the following Verilog/SystemVerilog module and extract all finite state machines (FSMs). - -For each state machine, identify: -1. The state variable name (e.g., "state", "current_state", "cs") -2. The state encoding type ("enum", "parameter", "localparam", "integer") -3. All states with their names, values, and descriptions -4. The initial/reset state -5. All transitions between states, including: - - from_state: source state name - - to_state: destination state name - - condition: the full transition condition expression - - input_condition: the part of the condition that depends on INPUT signals (external pins) - - wait_condition: the part of the condition that depends on INTERNAL signals (counters, registers, etc.) - - priority: transition priority (1 = highest, checked first in if/else chain) -6. Control signals: input signals that directly affect state transitions -7. Data signals: input signals that don't affect state transitions -8. Internal signals: internal RUNTIME registers/counters/wires that affect state transitions (e.g., timer, ped_latch). - Do NOT include localparam or parameter constants here — put those in "parameters". -9. Parameters: compile-time constants (localparam, parameter) that appear in transition conditions. - For each, provide the name and its resolved integer value. - -Also identify the clock signal, reset signal, and whether reset is active high. - -IMPORTANT rules for conditions: -- input_condition should ONLY reference input port signals -- wait_condition should ONLY reference internal signals (registers, counters, wires defined inside the module) -- If a transition condition mixes input and internal signals, split them appropriately -- If a transition depends only on inputs, wait_condition should be "" -- If a transition depends only on internal state (e.g., counter >= THRESHOLD - 1), input_condition should be "" and wait_condition should contain the expression -- Use Verilog syntax for conditions (e.g., "din", "!(din)", "sel == 2'b01", "counter == 8'd255") - -IMPORTANT rules for internal_signals vs parameters: -- "internal_signals" are RUNTIME signals: logic, reg, wire declarations that hold state at runtime (e.g., timer, counter, ped_latch) -- "parameters" are COMPILE-TIME constants: localparam or parameter declarations with fixed numeric values (e.g., GREEN_TICKS = 10, THRESHOLD = 255) -- A localparam or parameter MUST go in "parameters" with its resolved integer value, NOT in "internal_signals" - -Output ONLY a JSON object with this exact structure (no markdown, no extra text): -{ - "module_name": "", - "state_machines": [ - { - "state_variable": "", - "state_type": "enum|parameter|localparam|integer", - "states": [ - {"name": "", "value": "", "description": ""} - ], - "initial_state": "", - "transitions": [ - { - "from_state": "", - "to_state": "", - "condition": "", - "input_condition": "", - "wait_condition": "", - "priority": - } - ], - "control_signals": [""], - "data_signals": [""], - "internal_signals": [""], - "parameters": [ - {"name": "", "value": } - ] - } - ], - "clock_signal": "", - "reset_signal": "", - "reset_active_high": true|false -} - -Here is the Verilog source code to analyze: - -```verilog -%s -``` -""" diff --git a/tools/fsm_analyzer/fsm_analyzer/providers/__init__.py b/tools/fsm_analyzer/fsm_analyzer/providers/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tools/fsm_analyzer/fsm_analyzer/providers/gemini_provider.py b/tools/fsm_analyzer/fsm_analyzer/providers/gemini_provider.py deleted file mode 100644 index f8739ab..0000000 --- a/tools/fsm_analyzer/fsm_analyzer/providers/gemini_provider.py +++ /dev/null @@ -1,63 +0,0 @@ -"""Google Gemini provider for FSM analysis.""" - -import json -import os - -from google import genai -from google.genai import types - -from ..prompt import SYSTEM_PROMPT, build_fsm_analysis_prompt - - -def analyze( - verilog_source: str, - model: str = "gemini-2.5-flash", - api_key: str | None = None, -) -> dict: - """Analyze Verilog source using Google Gemini API. - - Args: - verilog_source: The Verilog source code to analyze. - model: The model name (e.g., "gemini-2.5-flash", "gemini-2.5-pro"). - api_key: API key (optional, falls back to GOOGLE_API_KEY env var). - - Returns: - Parsed JSON dict with FSM analysis. - """ - # The new SDK reads GOOGLE_API_KEY from env by default, - # but also accepts GEMINI_API_KEY. Pass explicitly if provided. - client_kwargs = {} - if api_key: - client_kwargs["api_key"] = api_key - elif os.environ.get("GOOGLE_API_KEY"): - client_kwargs["api_key"] = os.environ["GOOGLE_API_KEY"] - - client = genai.Client(**client_kwargs) - - user_prompt = build_fsm_analysis_prompt(verilog_source) - - response = client.models.generate_content( - model=model, - contents=user_prompt, - config=types.GenerateContentConfig( - system_instruction=SYSTEM_PROMPT, - temperature=0.0, - response_mime_type="application/json", - ), - ) - - content = response.text - if not content: - raise ValueError("Empty response from Gemini API") - - # Strip markdown code fences if present - content = content.strip() - if content.startswith("```"): - # Remove opening fence - first_newline = content.index("\n") - content = content[first_newline + 1 :] - if content.endswith("```"): - content = content[: -3] - content = content.strip() - - return json.loads(content) diff --git a/tools/fsm_analyzer/fsm_analyzer/providers/openai_provider.py b/tools/fsm_analyzer/fsm_analyzer/providers/openai_provider.py deleted file mode 100644 index a7020e2..0000000 --- a/tools/fsm_analyzer/fsm_analyzer/providers/openai_provider.py +++ /dev/null @@ -1,50 +0,0 @@ -"""OpenAI-compatible provider for FSM analysis. Supports custom endpoints.""" - -import json -from openai import OpenAI - -from ..prompt import SYSTEM_PROMPT, build_fsm_analysis_prompt - - -def analyze( - verilog_source: str, - model: str = "gpt-4", - endpoint: str | None = None, - api_key: str | None = None, -) -> dict: - """Analyze Verilog source using OpenAI-compatible API. - - Args: - verilog_source: The Verilog source code to analyze. - model: The model name (e.g., "gpt-4", "gpt-4o"). - endpoint: Custom API endpoint URL (optional). - api_key: API key (optional, falls back to OPENAI_API_KEY env var). - - Returns: - Parsed JSON dict with FSM analysis. - """ - kwargs = {} - if endpoint: - kwargs["base_url"] = endpoint - if api_key: - kwargs["api_key"] = api_key - - client = OpenAI(**kwargs) - - user_prompt = build_fsm_analysis_prompt(verilog_source) - - response = client.chat.completions.create( - model=model, - messages=[ - {"role": "system", "content": SYSTEM_PROMPT}, - {"role": "user", "content": user_prompt}, - ], - temperature=0.0, - response_format={"type": "json_object"}, - ) - - content = response.choices[0].message.content - if not content: - raise ValueError("Empty response from OpenAI API") - - return json.loads(content) diff --git a/tools/fsm_analyzer/pyproject.toml b/tools/fsm_analyzer/pyproject.toml deleted file mode 100644 index efa0c6d..0000000 --- a/tools/fsm_analyzer/pyproject.toml +++ /dev/null @@ -1,17 +0,0 @@ -[project] -name = "fsm-analyzer" -version = "0.1.0" -description = "LLM-based FSM state machine analyzer for Verilog RTL" -requires-python = ">=3.10" -dependencies = [ - "openai", - "google-genai", - "python-dotenv", -] - -[project.scripts] -fsm-analyzer = "fsm_analyzer.main:main" - -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" diff --git a/tools/fsm_analyzer/uv.lock b/tools/fsm_analyzer/uv.lock deleted file mode 100644 index 2d4139c..0000000 --- a/tools/fsm_analyzer/uv.lock +++ /dev/null @@ -1,833 +0,0 @@ -version = 1 -revision = 3 -requires-python = ">=3.10" - -[[package]] -name = "annotated-types" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, -] - -[[package]] -name = "anyio" -version = "4.12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "idna" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" }, -] - -[[package]] -name = "certifi" -version = "2026.1.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" }, -] - -[[package]] -name = "cffi" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pycparser", marker = "implementation_name != 'PyPy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" }, - { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" }, - { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" }, - { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" }, - { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" }, - { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" }, - { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" }, - { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" }, - { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" }, - { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" }, - { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" }, - { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" }, - { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, - { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, - { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, - { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, - { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, - { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, - { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, - { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, - { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, - { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, - { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, - { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, - { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, - { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, - { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, - { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, - { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, - { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, - { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, - { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, - { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, - { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, - { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, - { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, - { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, - { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, - { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, - { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, - { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, - { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, - { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, - { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, - { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, - { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, - { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, - { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, - { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, - { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, - { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, - { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, - { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, - { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, - { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, - { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, - { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, - { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, - { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, - { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, - { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, - { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, - { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, - { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, - { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, - { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, - { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, - { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, - { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, - { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, - { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, -] - -[[package]] -name = "charset-normalizer" -version = "3.4.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/b8/6d51fc1d52cbd52cd4ccedd5b5b2f0f6a11bbf6765c782298b0f3e808541/charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", size = 209709, upload-time = "2025-10-14T04:40:11.385Z" }, - { url = "https://files.pythonhosted.org/packages/5c/af/1f9d7f7faafe2ddfb6f72a2e07a548a629c61ad510fe60f9630309908fef/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", size = 148814, upload-time = "2025-10-14T04:40:13.135Z" }, - { url = "https://files.pythonhosted.org/packages/79/3d/f2e3ac2bbc056ca0c204298ea4e3d9db9b4afe437812638759db2c976b5f/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", size = 144467, upload-time = "2025-10-14T04:40:14.728Z" }, - { url = "https://files.pythonhosted.org/packages/ec/85/1bf997003815e60d57de7bd972c57dc6950446a3e4ccac43bc3070721856/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", size = 162280, upload-time = "2025-10-14T04:40:16.14Z" }, - { url = "https://files.pythonhosted.org/packages/3e/8e/6aa1952f56b192f54921c436b87f2aaf7c7a7c3d0d1a765547d64fd83c13/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", size = 159454, upload-time = "2025-10-14T04:40:17.567Z" }, - { url = "https://files.pythonhosted.org/packages/36/3b/60cbd1f8e93aa25d1c669c649b7a655b0b5fb4c571858910ea9332678558/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", size = 153609, upload-time = "2025-10-14T04:40:19.08Z" }, - { url = "https://files.pythonhosted.org/packages/64/91/6a13396948b8fd3c4b4fd5bc74d045f5637d78c9675585e8e9fbe5636554/charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", size = 151849, upload-time = "2025-10-14T04:40:20.607Z" }, - { url = "https://files.pythonhosted.org/packages/b7/7a/59482e28b9981d105691e968c544cc0df3b7d6133152fb3dcdc8f135da7a/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", size = 151586, upload-time = "2025-10-14T04:40:21.719Z" }, - { url = "https://files.pythonhosted.org/packages/92/59/f64ef6a1c4bdd2baf892b04cd78792ed8684fbc48d4c2afe467d96b4df57/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", size = 145290, upload-time = "2025-10-14T04:40:23.069Z" }, - { url = "https://files.pythonhosted.org/packages/6b/63/3bf9f279ddfa641ffa1962b0db6a57a9c294361cc2f5fcac997049a00e9c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", size = 163663, upload-time = "2025-10-14T04:40:24.17Z" }, - { url = "https://files.pythonhosted.org/packages/ed/09/c9e38fc8fa9e0849b172b581fd9803bdf6e694041127933934184e19f8c3/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", size = 151964, upload-time = "2025-10-14T04:40:25.368Z" }, - { url = "https://files.pythonhosted.org/packages/d2/d1/d28b747e512d0da79d8b6a1ac18b7ab2ecfd81b2944c4c710e166d8dd09c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", size = 161064, upload-time = "2025-10-14T04:40:26.806Z" }, - { url = "https://files.pythonhosted.org/packages/bb/9a/31d62b611d901c3b9e5500c36aab0ff5eb442043fb3a1c254200d3d397d9/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", size = 155015, upload-time = "2025-10-14T04:40:28.284Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f3/107e008fa2bff0c8b9319584174418e5e5285fef32f79d8ee6a430d0039c/charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", size = 99792, upload-time = "2025-10-14T04:40:29.613Z" }, - { url = "https://files.pythonhosted.org/packages/eb/66/e396e8a408843337d7315bab30dbf106c38966f1819f123257f5520f8a96/charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", size = 107198, upload-time = "2025-10-14T04:40:30.644Z" }, - { url = "https://files.pythonhosted.org/packages/b5/58/01b4f815bf0312704c267f2ccb6e5d42bcc7752340cd487bc9f8c3710597/charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", size = 100262, upload-time = "2025-10-14T04:40:32.108Z" }, - { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, - { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, - { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, - { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, - { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, - { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, - { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, - { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, - { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, - { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, - { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, - { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, - { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, - { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, - { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, - { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, - { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, - { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, - { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, - { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, - { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, - { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, - { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, - { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, - { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, - { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, - { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, - { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, - { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, - { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, - { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, - { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, - { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, - { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, - { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, - { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, - { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, - { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, - { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, - { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, - { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, - { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, - { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, - { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, - { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, - { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, - { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, - { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, - { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, - { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, - { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, - { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, - { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, - { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, - { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, - { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, - { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, - { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, - { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, - { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, - { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, - { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, - { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, - { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, - { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, -] - -[[package]] -name = "colorama" -version = "0.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, -] - -[[package]] -name = "cryptography" -version = "46.0.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/78/19/f748958276519adf6a0c1e79e7b8860b4830dda55ccdf29f2719b5fc499c/cryptography-46.0.4.tar.gz", hash = "sha256:bfd019f60f8abc2ed1b9be4ddc21cfef059c841d86d710bb69909a688cbb8f59", size = 749301, upload-time = "2026-01-28T00:24:37.379Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/99/157aae7949a5f30d51fcb1a9851e8ebd5c74bf99b5285d8bb4b8b9ee641e/cryptography-46.0.4-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:281526e865ed4166009e235afadf3a4c4cba6056f99336a99efba65336fd5485", size = 7173686, upload-time = "2026-01-28T00:23:07.515Z" }, - { url = "https://files.pythonhosted.org/packages/87/91/874b8910903159043b5c6a123b7e79c4559ddd1896e38967567942635778/cryptography-46.0.4-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5f14fba5bf6f4390d7ff8f086c566454bff0411f6d8aa7af79c88b6f9267aecc", size = 4275871, upload-time = "2026-01-28T00:23:09.439Z" }, - { url = "https://files.pythonhosted.org/packages/c0/35/690e809be77896111f5b195ede56e4b4ed0435b428c2f2b6d35046fbb5e8/cryptography-46.0.4-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:47bcd19517e6389132f76e2d5303ded6cf3f78903da2158a671be8de024f4cd0", size = 4423124, upload-time = "2026-01-28T00:23:11.529Z" }, - { url = "https://files.pythonhosted.org/packages/1a/5b/a26407d4f79d61ca4bebaa9213feafdd8806dc69d3d290ce24996d3cfe43/cryptography-46.0.4-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:01df4f50f314fbe7009f54046e908d1754f19d0c6d3070df1e6268c5a4af09fa", size = 4277090, upload-time = "2026-01-28T00:23:13.123Z" }, - { url = "https://files.pythonhosted.org/packages/0c/d8/4bb7aec442a9049827aa34cee1aa83803e528fa55da9a9d45d01d1bb933e/cryptography-46.0.4-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5aa3e463596b0087b3da0dbe2b2487e9fc261d25da85754e30e3b40637d61f81", size = 4947652, upload-time = "2026-01-28T00:23:14.554Z" }, - { url = "https://files.pythonhosted.org/packages/2b/08/f83e2e0814248b844265802d081f2fac2f1cbe6cd258e72ba14ff006823a/cryptography-46.0.4-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0a9ad24359fee86f131836a9ac3bffc9329e956624a2d379b613f8f8abaf5255", size = 4455157, upload-time = "2026-01-28T00:23:16.443Z" }, - { url = "https://files.pythonhosted.org/packages/0a/05/19d849cf4096448779d2dcc9bb27d097457dac36f7273ffa875a93b5884c/cryptography-46.0.4-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:dc1272e25ef673efe72f2096e92ae39dea1a1a450dd44918b15351f72c5a168e", size = 3981078, upload-time = "2026-01-28T00:23:17.838Z" }, - { url = "https://files.pythonhosted.org/packages/e6/89/f7bac81d66ba7cde867a743ea5b37537b32b5c633c473002b26a226f703f/cryptography-46.0.4-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:de0f5f4ec8711ebc555f54735d4c673fc34b65c44283895f1a08c2b49d2fd99c", size = 4276213, upload-time = "2026-01-28T00:23:19.257Z" }, - { url = "https://files.pythonhosted.org/packages/da/9f/7133e41f24edd827020ad21b068736e792bc68eecf66d93c924ad4719fb3/cryptography-46.0.4-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:eeeb2e33d8dbcccc34d64651f00a98cb41b2dc69cef866771a5717e6734dfa32", size = 4912190, upload-time = "2026-01-28T00:23:21.244Z" }, - { url = "https://files.pythonhosted.org/packages/a6/f7/6d43cbaddf6f65b24816e4af187d211f0bc536a29961f69faedc48501d8e/cryptography-46.0.4-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:3d425eacbc9aceafd2cb429e42f4e5d5633c6f873f5e567077043ef1b9bbf616", size = 4454641, upload-time = "2026-01-28T00:23:22.866Z" }, - { url = "https://files.pythonhosted.org/packages/9e/4f/ebd0473ad656a0ac912a16bd07db0f5d85184924e14fc88feecae2492834/cryptography-46.0.4-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91627ebf691d1ea3976a031b61fb7bac1ccd745afa03602275dda443e11c8de0", size = 4405159, upload-time = "2026-01-28T00:23:25.278Z" }, - { url = "https://files.pythonhosted.org/packages/d1/f7/7923886f32dc47e27adeff8246e976d77258fd2aa3efdd1754e4e323bf49/cryptography-46.0.4-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2d08bc22efd73e8854b0b7caff402d735b354862f1145d7be3b9c0f740fef6a0", size = 4666059, upload-time = "2026-01-28T00:23:26.766Z" }, - { url = "https://files.pythonhosted.org/packages/eb/a7/0fca0fd3591dffc297278a61813d7f661a14243dd60f499a7a5b48acb52a/cryptography-46.0.4-cp311-abi3-win32.whl", hash = "sha256:82a62483daf20b8134f6e92898da70d04d0ef9a75829d732ea1018678185f4f5", size = 3026378, upload-time = "2026-01-28T00:23:28.317Z" }, - { url = "https://files.pythonhosted.org/packages/2d/12/652c84b6f9873f0909374864a57b003686c642ea48c84d6c7e2c515e6da5/cryptography-46.0.4-cp311-abi3-win_amd64.whl", hash = "sha256:6225d3ebe26a55dbc8ead5ad1265c0403552a63336499564675b29eb3184c09b", size = 3478614, upload-time = "2026-01-28T00:23:30.275Z" }, - { url = "https://files.pythonhosted.org/packages/b9/27/542b029f293a5cce59349d799d4d8484b3b1654a7b9a0585c266e974a488/cryptography-46.0.4-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:485e2b65d25ec0d901bca7bcae0f53b00133bf3173916d8e421f6fddde103908", size = 7116417, upload-time = "2026-01-28T00:23:31.958Z" }, - { url = "https://files.pythonhosted.org/packages/f8/f5/559c25b77f40b6bf828eabaf988efb8b0e17b573545edb503368ca0a2a03/cryptography-46.0.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:078e5f06bd2fa5aea5a324f2a09f914b1484f1d0c2a4d6a8a28c74e72f65f2da", size = 4264508, upload-time = "2026-01-28T00:23:34.264Z" }, - { url = "https://files.pythonhosted.org/packages/49/a1/551fa162d33074b660dc35c9bc3616fefa21a0e8c1edd27b92559902e408/cryptography-46.0.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dce1e4f068f03008da7fa51cc7abc6ddc5e5de3e3d1550334eaf8393982a5829", size = 4409080, upload-time = "2026-01-28T00:23:35.793Z" }, - { url = "https://files.pythonhosted.org/packages/b0/6a/4d8d129a755f5d6df1bbee69ea2f35ebfa954fa1847690d1db2e8bca46a5/cryptography-46.0.4-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:2067461c80271f422ee7bdbe79b9b4be54a5162e90345f86a23445a0cf3fd8a2", size = 4270039, upload-time = "2026-01-28T00:23:37.263Z" }, - { url = "https://files.pythonhosted.org/packages/4c/f5/ed3fcddd0a5e39321e595e144615399e47e7c153a1fb8c4862aec3151ff9/cryptography-46.0.4-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:c92010b58a51196a5f41c3795190203ac52edfd5dc3ff99149b4659eba9d2085", size = 4926748, upload-time = "2026-01-28T00:23:38.884Z" }, - { url = "https://files.pythonhosted.org/packages/43/ae/9f03d5f0c0c00e85ecb34f06d3b79599f20630e4db91b8a6e56e8f83d410/cryptography-46.0.4-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:829c2b12bbc5428ab02d6b7f7e9bbfd53e33efd6672d21341f2177470171ad8b", size = 4442307, upload-time = "2026-01-28T00:23:40.56Z" }, - { url = "https://files.pythonhosted.org/packages/8b/22/e0f9f2dae8040695103369cf2283ef9ac8abe4d51f68710bec2afd232609/cryptography-46.0.4-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:62217ba44bf81b30abaeda1488686a04a702a261e26f87db51ff61d9d3510abd", size = 3959253, upload-time = "2026-01-28T00:23:42.827Z" }, - { url = "https://files.pythonhosted.org/packages/01/5b/6a43fcccc51dae4d101ac7d378a8724d1ba3de628a24e11bf2f4f43cba4d/cryptography-46.0.4-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:9c2da296c8d3415b93e6053f5a728649a87a48ce084a9aaf51d6e46c87c7f2d2", size = 4269372, upload-time = "2026-01-28T00:23:44.655Z" }, - { url = "https://files.pythonhosted.org/packages/17/b7/0f6b8c1dd0779df2b526e78978ff00462355e31c0a6f6cff8a3e99889c90/cryptography-46.0.4-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:9b34d8ba84454641a6bf4d6762d15847ecbd85c1316c0a7984e6e4e9f748ec2e", size = 4891908, upload-time = "2026-01-28T00:23:46.48Z" }, - { url = "https://files.pythonhosted.org/packages/83/17/259409b8349aa10535358807a472c6a695cf84f106022268d31cea2b6c97/cryptography-46.0.4-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:df4a817fa7138dd0c96c8c8c20f04b8aaa1fac3bbf610913dcad8ea82e1bfd3f", size = 4441254, upload-time = "2026-01-28T00:23:48.403Z" }, - { url = "https://files.pythonhosted.org/packages/9c/fe/e4a1b0c989b00cee5ffa0764401767e2d1cf59f45530963b894129fd5dce/cryptography-46.0.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b1de0ebf7587f28f9190b9cb526e901bf448c9e6a99655d2b07fff60e8212a82", size = 4396520, upload-time = "2026-01-28T00:23:50.26Z" }, - { url = "https://files.pythonhosted.org/packages/b3/81/ba8fd9657d27076eb40d6a2f941b23429a3c3d2f56f5a921d6b936a27bc9/cryptography-46.0.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9b4d17bc7bd7cdd98e3af40b441feaea4c68225e2eb2341026c84511ad246c0c", size = 4651479, upload-time = "2026-01-28T00:23:51.674Z" }, - { url = "https://files.pythonhosted.org/packages/00/03/0de4ed43c71c31e4fe954edd50b9d28d658fef56555eba7641696370a8e2/cryptography-46.0.4-cp314-cp314t-win32.whl", hash = "sha256:c411f16275b0dea722d76544a61d6421e2cc829ad76eec79280dbdc9ddf50061", size = 3001986, upload-time = "2026-01-28T00:23:53.485Z" }, - { url = "https://files.pythonhosted.org/packages/5c/70/81830b59df7682917d7a10f833c4dab2a5574cd664e86d18139f2b421329/cryptography-46.0.4-cp314-cp314t-win_amd64.whl", hash = "sha256:728fedc529efc1439eb6107b677f7f7558adab4553ef8669f0d02d42d7b959a7", size = 3468288, upload-time = "2026-01-28T00:23:55.09Z" }, - { url = "https://files.pythonhosted.org/packages/56/f7/f648fdbb61d0d45902d3f374217451385edc7e7768d1b03ff1d0e5ffc17b/cryptography-46.0.4-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a9556ba711f7c23f77b151d5798f3ac44a13455cc68db7697a1096e6d0563cab", size = 7169583, upload-time = "2026-01-28T00:23:56.558Z" }, - { url = "https://files.pythonhosted.org/packages/d8/cc/8f3224cbb2a928de7298d6ed4790f5ebc48114e02bdc9559196bfb12435d/cryptography-46.0.4-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8bf75b0259e87fa70bddc0b8b4078b76e7fd512fd9afae6c1193bcf440a4dbef", size = 4275419, upload-time = "2026-01-28T00:23:58.364Z" }, - { url = "https://files.pythonhosted.org/packages/17/43/4a18faa7a872d00e4264855134ba82d23546c850a70ff209e04ee200e76f/cryptography-46.0.4-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3c268a3490df22270955966ba236d6bc4a8f9b6e4ffddb78aac535f1a5ea471d", size = 4419058, upload-time = "2026-01-28T00:23:59.867Z" }, - { url = "https://files.pythonhosted.org/packages/ee/64/6651969409821d791ba12346a124f55e1b76f66a819254ae840a965d4b9c/cryptography-46.0.4-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:812815182f6a0c1d49a37893a303b44eaac827d7f0d582cecfc81b6427f22973", size = 4278151, upload-time = "2026-01-28T00:24:01.731Z" }, - { url = "https://files.pythonhosted.org/packages/20/0b/a7fce65ee08c3c02f7a8310cc090a732344066b990ac63a9dfd0a655d321/cryptography-46.0.4-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:a90e43e3ef65e6dcf969dfe3bb40cbf5aef0d523dff95bfa24256be172a845f4", size = 4939441, upload-time = "2026-01-28T00:24:03.175Z" }, - { url = "https://files.pythonhosted.org/packages/db/a7/20c5701e2cd3e1dfd7a19d2290c522a5f435dd30957d431dcb531d0f1413/cryptography-46.0.4-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a05177ff6296644ef2876fce50518dffb5bcdf903c85250974fc8bc85d54c0af", size = 4451617, upload-time = "2026-01-28T00:24:05.403Z" }, - { url = "https://files.pythonhosted.org/packages/00/dc/3e16030ea9aa47b63af6524c354933b4fb0e352257c792c4deeb0edae367/cryptography-46.0.4-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:daa392191f626d50f1b136c9b4cf08af69ca8279d110ea24f5c2700054d2e263", size = 3977774, upload-time = "2026-01-28T00:24:06.851Z" }, - { url = "https://files.pythonhosted.org/packages/42/c8/ad93f14118252717b465880368721c963975ac4b941b7ef88f3c56bf2897/cryptography-46.0.4-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e07ea39c5b048e085f15923511d8121e4a9dc45cee4e3b970ca4f0d338f23095", size = 4277008, upload-time = "2026-01-28T00:24:08.926Z" }, - { url = "https://files.pythonhosted.org/packages/00/cf/89c99698151c00a4631fbfcfcf459d308213ac29e321b0ff44ceeeac82f1/cryptography-46.0.4-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:d5a45ddc256f492ce42a4e35879c5e5528c09cd9ad12420828c972951d8e016b", size = 4903339, upload-time = "2026-01-28T00:24:12.009Z" }, - { url = "https://files.pythonhosted.org/packages/03/c3/c90a2cb358de4ac9309b26acf49b2a100957e1ff5cc1e98e6c4996576710/cryptography-46.0.4-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:6bb5157bf6a350e5b28aee23beb2d84ae6f5be390b2f8ee7ea179cda077e1019", size = 4451216, upload-time = "2026-01-28T00:24:13.975Z" }, - { url = "https://files.pythonhosted.org/packages/96/2c/8d7f4171388a10208671e181ca43cdc0e596d8259ebacbbcfbd16de593da/cryptography-46.0.4-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:dd5aba870a2c40f87a3af043e0dee7d9eb02d4aff88a797b48f2b43eff8c3ab4", size = 4404299, upload-time = "2026-01-28T00:24:16.169Z" }, - { url = "https://files.pythonhosted.org/packages/e9/23/cbb2036e450980f65c6e0a173b73a56ff3bccd8998965dea5cc9ddd424a5/cryptography-46.0.4-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:93d8291da8d71024379ab2cb0b5c57915300155ad42e07f76bea6ad838d7e59b", size = 4664837, upload-time = "2026-01-28T00:24:17.629Z" }, - { url = "https://files.pythonhosted.org/packages/0a/21/f7433d18fe6d5845329cbdc597e30caf983229c7a245bcf54afecc555938/cryptography-46.0.4-cp38-abi3-win32.whl", hash = "sha256:0563655cb3c6d05fb2afe693340bc050c30f9f34e15763361cf08e94749401fc", size = 3009779, upload-time = "2026-01-28T00:24:20.198Z" }, - { url = "https://files.pythonhosted.org/packages/3a/6a/bd2e7caa2facffedf172a45c1a02e551e6d7d4828658c9a245516a598d94/cryptography-46.0.4-cp38-abi3-win_amd64.whl", hash = "sha256:fa0900b9ef9c49728887d1576fd8d9e7e3ea872fa9b25ef9b64888adc434e976", size = 3466633, upload-time = "2026-01-28T00:24:21.851Z" }, - { url = "https://files.pythonhosted.org/packages/59/e0/f9c6c53e1f2a1c2507f00f2faba00f01d2f334b35b0fbfe5286715da2184/cryptography-46.0.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:766330cce7416c92b5e90c3bb71b1b79521760cdcfc3a6a1a182d4c9fab23d2b", size = 3476316, upload-time = "2026-01-28T00:24:24.144Z" }, - { url = "https://files.pythonhosted.org/packages/27/7a/f8d2d13227a9a1a9fe9c7442b057efecffa41f1e3c51d8622f26b9edbe8f/cryptography-46.0.4-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c236a44acfb610e70f6b3e1c3ca20ff24459659231ef2f8c48e879e2d32b73da", size = 4216693, upload-time = "2026-01-28T00:24:25.758Z" }, - { url = "https://files.pythonhosted.org/packages/c5/de/3787054e8f7972658370198753835d9d680f6cd4a39df9f877b57f0dd69c/cryptography-46.0.4-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8a15fb869670efa8f83cbffbc8753c1abf236883225aed74cd179b720ac9ec80", size = 4382765, upload-time = "2026-01-28T00:24:27.577Z" }, - { url = "https://files.pythonhosted.org/packages/8a/5f/60e0afb019973ba6a0b322e86b3d61edf487a4f5597618a430a2a15f2d22/cryptography-46.0.4-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:fdc3daab53b212472f1524d070735b2f0c214239df131903bae1d598016fa822", size = 4216066, upload-time = "2026-01-28T00:24:29.056Z" }, - { url = "https://files.pythonhosted.org/packages/81/8e/bf4a0de294f147fee66f879d9bae6f8e8d61515558e3d12785dd90eca0be/cryptography-46.0.4-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:44cc0675b27cadb71bdbb96099cca1fa051cd11d2ade09e5cd3a2edb929ed947", size = 4382025, upload-time = "2026-01-28T00:24:30.681Z" }, - { url = "https://files.pythonhosted.org/packages/79/f4/9ceb90cfd6a3847069b0b0b353fd3075dc69b49defc70182d8af0c4ca390/cryptography-46.0.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be8c01a7d5a55f9a47d1888162b76c8f49d62b234d88f0ff91a9fbebe32ffbc3", size = 3406043, upload-time = "2026-01-28T00:24:32.236Z" }, -] - -[[package]] -name = "distro" -version = "1.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, -] - -[[package]] -name = "exceptiongroup" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, -] - -[[package]] -name = "fsm-analyzer" -version = "0.1.0" -source = { editable = "." } -dependencies = [ - { name = "google-genai" }, - { name = "openai" }, - { name = "python-dotenv" }, -] - -[package.metadata] -requires-dist = [ - { name = "google-genai" }, - { name = "openai" }, - { name = "python-dotenv" }, -] - -[[package]] -name = "google-auth" -version = "2.48.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cryptography" }, - { name = "pyasn1-modules" }, - { name = "rsa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0c/41/242044323fbd746615884b1c16639749e73665b718209946ebad7ba8a813/google_auth-2.48.0.tar.gz", hash = "sha256:4f7e706b0cd3208a3d940a19a822c37a476ddba5450156c3e6624a71f7c841ce", size = 326522, upload-time = "2026-01-26T19:22:47.157Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/83/1d/d6466de3a5249d35e832a52834115ca9d1d0de6abc22065f049707516d47/google_auth-2.48.0-py3-none-any.whl", hash = "sha256:2e2a537873d449434252a9632c28bfc268b0adb1e53f9fb62afc5333a975903f", size = 236499, upload-time = "2026-01-26T19:22:45.099Z" }, -] - -[package.optional-dependencies] -requests = [ - { name = "requests" }, -] - -[[package]] -name = "google-genai" -version = "1.62.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "distro" }, - { name = "google-auth", extra = ["requests"] }, - { name = "httpx" }, - { name = "pydantic" }, - { name = "requests" }, - { name = "sniffio" }, - { name = "tenacity" }, - { name = "typing-extensions" }, - { name = "websockets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/94/4c/71b32b5c8db420cf2fd0d5ef8a672adbde97d85e5d44a0b4fca712264ef1/google_genai-1.62.0.tar.gz", hash = "sha256:709468a14c739a080bc240a4f3191df597bf64485b1ca3728e0fb67517774c18", size = 490888, upload-time = "2026-02-04T22:48:41.989Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/09/5f/4645d8a28c6e431d0dd6011003a852563f3da7037d36af53154925b099fd/google_genai-1.62.0-py3-none-any.whl", hash = "sha256:4c3daeff3d05fafee4b9a1a31f9c07f01bc22051081aa58b4d61f58d16d1bcc0", size = 724166, upload-time = "2026-02-04T22:48:39.956Z" }, -] - -[[package]] -name = "h11" -version = "0.16.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, -] - -[[package]] -name = "httpcore" -version = "1.0.9" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "h11" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, -] - -[[package]] -name = "httpx" -version = "0.28.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "certifi" }, - { name = "httpcore" }, - { name = "idna" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, -] - -[[package]] -name = "idna" -version = "3.11" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, -] - -[[package]] -name = "jiter" -version = "0.13.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0d/5e/4ec91646aee381d01cdb9974e30882c9cd3b8c5d1079d6b5ff4af522439a/jiter-0.13.0.tar.gz", hash = "sha256:f2839f9c2c7e2dffc1bc5929a510e14ce0a946be9365fd1219e7ef342dae14f4", size = 164847, upload-time = "2026-02-02T12:37:56.441Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/5a/41da76c5ea07bec1b0472b6b2fdb1b651074d504b19374d7e130e0cdfb25/jiter-0.13.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2ffc63785fd6c7977defe49b9824ae6ce2b2e2b77ce539bdaf006c26da06342e", size = 311164, upload-time = "2026-02-02T12:35:17.688Z" }, - { url = "https://files.pythonhosted.org/packages/40/cb/4a1bf994a3e869f0d39d10e11efb471b76d0ad70ecbfb591427a46c880c2/jiter-0.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4a638816427006c1e3f0013eb66d391d7a3acda99a7b0cf091eff4497ccea33a", size = 320296, upload-time = "2026-02-02T12:35:19.828Z" }, - { url = "https://files.pythonhosted.org/packages/09/82/acd71ca9b50ecebadc3979c541cd717cce2fe2bc86236f4fa597565d8f1a/jiter-0.13.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19928b5d1ce0ff8c1ee1b9bdef3b5bfc19e8304f1b904e436caf30bc15dc6cf5", size = 352742, upload-time = "2026-02-02T12:35:21.258Z" }, - { url = "https://files.pythonhosted.org/packages/71/03/d1fc996f3aecfd42eb70922edecfb6dd26421c874503e241153ad41df94f/jiter-0.13.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:309549b778b949d731a2f0e1594a3f805716be704a73bf3ad9a807eed5eb5721", size = 363145, upload-time = "2026-02-02T12:35:24.653Z" }, - { url = "https://files.pythonhosted.org/packages/f1/61/a30492366378cc7a93088858f8991acd7d959759fe6138c12a4644e58e81/jiter-0.13.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcdabaea26cb04e25df3103ce47f97466627999260290349a88c8136ecae0060", size = 487683, upload-time = "2026-02-02T12:35:26.162Z" }, - { url = "https://files.pythonhosted.org/packages/20/4e/4223cffa9dbbbc96ed821c5aeb6bca510848c72c02086d1ed3f1da3d58a7/jiter-0.13.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3a377af27b236abbf665a69b2bdd680e3b5a0bd2af825cd3b81245279a7606c", size = 373579, upload-time = "2026-02-02T12:35:27.582Z" }, - { url = "https://files.pythonhosted.org/packages/fe/c9/b0489a01329ab07a83812d9ebcffe7820a38163c6d9e7da644f926ff877c/jiter-0.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe49d3ff6db74321f144dff9addd4a5874d3105ac5ba7c5b77fac099cfae31ae", size = 362904, upload-time = "2026-02-02T12:35:28.925Z" }, - { url = "https://files.pythonhosted.org/packages/05/af/53e561352a44afcba9a9bc67ee1d320b05a370aed8df54eafe714c4e454d/jiter-0.13.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2113c17c9a67071b0f820733c0893ed1d467b5fcf4414068169e5c2cabddb1e2", size = 392380, upload-time = "2026-02-02T12:35:30.385Z" }, - { url = "https://files.pythonhosted.org/packages/76/2a/dd805c3afb8ed5b326c5ae49e725d1b1255b9754b1b77dbecdc621b20773/jiter-0.13.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ab1185ca5c8b9491b55ebf6c1e8866b8f68258612899693e24a92c5fdb9455d5", size = 517939, upload-time = "2026-02-02T12:35:31.865Z" }, - { url = "https://files.pythonhosted.org/packages/20/2a/7b67d76f55b8fe14c937e7640389612f05f9a4145fc28ae128aaa5e62257/jiter-0.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9621ca242547edc16400981ca3231e0c91c0c4c1ab8573a596cd9bb3575d5c2b", size = 551696, upload-time = "2026-02-02T12:35:33.306Z" }, - { url = "https://files.pythonhosted.org/packages/85/9c/57cdd64dac8f4c6ab8f994fe0eb04dc9fd1db102856a4458fcf8a99dfa62/jiter-0.13.0-cp310-cp310-win32.whl", hash = "sha256:a7637d92b1c9d7a771e8c56f445c7f84396d48f2e756e5978840ecba2fac0894", size = 204592, upload-time = "2026-02-02T12:35:34.58Z" }, - { url = "https://files.pythonhosted.org/packages/a7/38/f4f3ea5788b8a5bae7510a678cdc747eda0c45ffe534f9878ff37e7cf3b3/jiter-0.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c1b609e5cbd2f52bb74fb721515745b407df26d7b800458bd97cb3b972c29e7d", size = 206016, upload-time = "2026-02-02T12:35:36.435Z" }, - { url = "https://files.pythonhosted.org/packages/71/29/499f8c9eaa8a16751b1c0e45e6f5f1761d180da873d417996cc7bddc8eef/jiter-0.13.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ea026e70a9a28ebbdddcbcf0f1323128a8db66898a06eaad3a4e62d2f554d096", size = 311157, upload-time = "2026-02-02T12:35:37.758Z" }, - { url = "https://files.pythonhosted.org/packages/50/f6/566364c777d2ab450b92100bea11333c64c38d32caf8dc378b48e5b20c46/jiter-0.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66aa3e663840152d18cc8ff1e4faad3dd181373491b9cfdc6004b92198d67911", size = 319729, upload-time = "2026-02-02T12:35:39.246Z" }, - { url = "https://files.pythonhosted.org/packages/73/dd/560f13ec5e4f116d8ad2658781646cca91b617ae3b8758d4a5076b278f70/jiter-0.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3524798e70655ff19aec58c7d05adb1f074fecff62da857ea9be2b908b6d701", size = 354766, upload-time = "2026-02-02T12:35:40.662Z" }, - { url = "https://files.pythonhosted.org/packages/7c/0d/061faffcfe94608cbc28a0d42a77a74222bdf5055ccdbe5fd2292b94f510/jiter-0.13.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ec7e287d7fbd02cb6e22f9a00dd9c9cd504c40a61f2c61e7e1f9690a82726b4c", size = 362587, upload-time = "2026-02-02T12:35:42.025Z" }, - { url = "https://files.pythonhosted.org/packages/92/c9/c66a7864982fd38a9773ec6e932e0398d1262677b8c60faecd02ffb67bf3/jiter-0.13.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:47455245307e4debf2ce6c6e65a717550a0244231240dcf3b8f7d64e4c2f22f4", size = 487537, upload-time = "2026-02-02T12:35:43.459Z" }, - { url = "https://files.pythonhosted.org/packages/6c/86/84eb4352cd3668f16d1a88929b5888a3fe0418ea8c1dfc2ad4e7bf6e069a/jiter-0.13.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ee9da221dca6e0429c2704c1b3655fe7b025204a71d4d9b73390c759d776d165", size = 373717, upload-time = "2026-02-02T12:35:44.928Z" }, - { url = "https://files.pythonhosted.org/packages/6e/09/9fe4c159358176f82d4390407a03f506a8659ed13ca3ac93a843402acecf/jiter-0.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24ab43126d5e05f3d53a36a8e11eb2f23304c6c1117844aaaf9a0aa5e40b5018", size = 362683, upload-time = "2026-02-02T12:35:46.636Z" }, - { url = "https://files.pythonhosted.org/packages/c9/5e/85f3ab9caca0c1d0897937d378b4a515cae9e119730563572361ea0c48ae/jiter-0.13.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9da38b4fedde4fb528c740c2564628fbab737166a0e73d6d46cb4bb5463ff411", size = 392345, upload-time = "2026-02-02T12:35:48.088Z" }, - { url = "https://files.pythonhosted.org/packages/12/4c/05b8629ad546191939e6f0c2f17e29f542a398f4a52fb987bc70b6d1eb8b/jiter-0.13.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0b34c519e17658ed88d5047999a93547f8889f3c1824120c26ad6be5f27b6cf5", size = 517775, upload-time = "2026-02-02T12:35:49.482Z" }, - { url = "https://files.pythonhosted.org/packages/4d/88/367ea2eb6bc582c7052e4baf5ddf57ebe5ab924a88e0e09830dfb585c02d/jiter-0.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d2a6394e6af690d462310a86b53c47ad75ac8c21dc79f120714ea449979cb1d3", size = 551325, upload-time = "2026-02-02T12:35:51.104Z" }, - { url = "https://files.pythonhosted.org/packages/f3/12/fa377ffb94a2f28c41afaed093e0d70cfe512035d5ecb0cad0ae4792d35e/jiter-0.13.0-cp311-cp311-win32.whl", hash = "sha256:0f0c065695f616a27c920a56ad0d4fc46415ef8b806bf8fc1cacf25002bd24e1", size = 204709, upload-time = "2026-02-02T12:35:52.467Z" }, - { url = "https://files.pythonhosted.org/packages/cb/16/8e8203ce92f844dfcd3d9d6a5a7322c77077248dbb12da52d23193a839cd/jiter-0.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:0733312953b909688ae3c2d58d043aa040f9f1a6a75693defed7bc2cc4bf2654", size = 204560, upload-time = "2026-02-02T12:35:53.925Z" }, - { url = "https://files.pythonhosted.org/packages/44/26/97cc40663deb17b9e13c3a5cf29251788c271b18ee4d262c8f94798b8336/jiter-0.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:5d9b34ad56761b3bf0fbe8f7e55468704107608512350962d3317ffd7a4382d5", size = 189608, upload-time = "2026-02-02T12:35:55.304Z" }, - { url = "https://files.pythonhosted.org/packages/2e/30/7687e4f87086829955013ca12a9233523349767f69653ebc27036313def9/jiter-0.13.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0a2bd69fc1d902e89925fc34d1da51b2128019423d7b339a45d9e99c894e0663", size = 307958, upload-time = "2026-02-02T12:35:57.165Z" }, - { url = "https://files.pythonhosted.org/packages/c3/27/e57f9a783246ed95481e6749cc5002a8a767a73177a83c63ea71f0528b90/jiter-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f917a04240ef31898182f76a332f508f2cc4b57d2b4d7ad2dbfebbfe167eb505", size = 318597, upload-time = "2026-02-02T12:35:58.591Z" }, - { url = "https://files.pythonhosted.org/packages/cf/52/e5719a60ac5d4d7c5995461a94ad5ef962a37c8bf5b088390e6fad59b2ff/jiter-0.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1e2b199f446d3e82246b4fd9236d7cb502dc2222b18698ba0d986d2fecc6152", size = 348821, upload-time = "2026-02-02T12:36:00.093Z" }, - { url = "https://files.pythonhosted.org/packages/61/db/c1efc32b8ba4c740ab3fc2d037d8753f67685f475e26b9d6536a4322bcdd/jiter-0.13.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04670992b576fa65bd056dbac0c39fe8bd67681c380cb2b48efa885711d9d726", size = 364163, upload-time = "2026-02-02T12:36:01.937Z" }, - { url = "https://files.pythonhosted.org/packages/55/8a/fb75556236047c8806995671a18e4a0ad646ed255276f51a20f32dceaeec/jiter-0.13.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a1aff1fbdb803a376d4d22a8f63f8e7ccbce0b4890c26cc7af9e501ab339ef0", size = 483709, upload-time = "2026-02-02T12:36:03.41Z" }, - { url = "https://files.pythonhosted.org/packages/7e/16/43512e6ee863875693a8e6f6d532e19d650779d6ba9a81593ae40a9088ff/jiter-0.13.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b3fb8c2053acaef8580809ac1d1f7481a0a0bdc012fd7f5d8b18fb696a5a089", size = 370480, upload-time = "2026-02-02T12:36:04.791Z" }, - { url = "https://files.pythonhosted.org/packages/f8/4c/09b93e30e984a187bc8aaa3510e1ec8dcbdcd71ca05d2f56aac0492453aa/jiter-0.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdaba7d87e66f26a2c45d8cbadcbfc4bf7884182317907baf39cfe9775bb4d93", size = 360735, upload-time = "2026-02-02T12:36:06.994Z" }, - { url = "https://files.pythonhosted.org/packages/1a/1b/46c5e349019874ec5dfa508c14c37e29864ea108d376ae26d90bee238cd7/jiter-0.13.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7b88d649135aca526da172e48083da915ec086b54e8e73a425ba50999468cc08", size = 391814, upload-time = "2026-02-02T12:36:08.368Z" }, - { url = "https://files.pythonhosted.org/packages/15/9e/26184760e85baee7162ad37b7912797d2077718476bf91517641c92b3639/jiter-0.13.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e404ea551d35438013c64b4f357b0474c7abf9f781c06d44fcaf7a14c69ff9e2", size = 513990, upload-time = "2026-02-02T12:36:09.993Z" }, - { url = "https://files.pythonhosted.org/packages/e9/34/2c9355247d6debad57a0a15e76ab1566ab799388042743656e566b3b7de1/jiter-0.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1f4748aad1b4a93c8bdd70f604d0f748cdc0e8744c5547798acfa52f10e79228", size = 548021, upload-time = "2026-02-02T12:36:11.376Z" }, - { url = "https://files.pythonhosted.org/packages/ac/4a/9f2c23255d04a834398b9c2e0e665382116911dc4d06b795710503cdad25/jiter-0.13.0-cp312-cp312-win32.whl", hash = "sha256:0bf670e3b1445fc4d31612199f1744f67f889ee1bbae703c4b54dc097e5dd394", size = 203024, upload-time = "2026-02-02T12:36:12.682Z" }, - { url = "https://files.pythonhosted.org/packages/09/ee/f0ae675a957ae5a8f160be3e87acea6b11dc7b89f6b7ab057e77b2d2b13a/jiter-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:15db60e121e11fe186c0b15236bd5d18381b9ddacdcf4e659feb96fc6c969c92", size = 205424, upload-time = "2026-02-02T12:36:13.93Z" }, - { url = "https://files.pythonhosted.org/packages/1b/02/ae611edf913d3cbf02c97cdb90374af2082c48d7190d74c1111dde08bcdd/jiter-0.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:41f92313d17989102f3cb5dd533a02787cdb99454d494344b0361355da52fcb9", size = 186818, upload-time = "2026-02-02T12:36:15.308Z" }, - { url = "https://files.pythonhosted.org/packages/91/9c/7ee5a6ff4b9991e1a45263bfc46731634c4a2bde27dfda6c8251df2d958c/jiter-0.13.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1f8a55b848cbabf97d861495cd65f1e5c590246fabca8b48e1747c4dfc8f85bf", size = 306897, upload-time = "2026-02-02T12:36:16.748Z" }, - { url = "https://files.pythonhosted.org/packages/7c/02/be5b870d1d2be5dd6a91bdfb90f248fbb7dcbd21338f092c6b89817c3dbf/jiter-0.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f556aa591c00f2c45eb1b89f68f52441a016034d18b65da60e2d2875bbbf344a", size = 317507, upload-time = "2026-02-02T12:36:18.351Z" }, - { url = "https://files.pythonhosted.org/packages/da/92/b25d2ec333615f5f284f3a4024f7ce68cfa0604c322c6808b2344c7f5d2b/jiter-0.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7e1d61da332ec412350463891923f960c3073cf1aae93b538f0bb4c8cd46efb", size = 350560, upload-time = "2026-02-02T12:36:19.746Z" }, - { url = "https://files.pythonhosted.org/packages/be/ec/74dcb99fef0aca9fbe56b303bf79f6bd839010cb18ad41000bf6cc71eec0/jiter-0.13.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3097d665a27bc96fd9bbf7f86178037db139f319f785e4757ce7ccbf390db6c2", size = 363232, upload-time = "2026-02-02T12:36:21.243Z" }, - { url = "https://files.pythonhosted.org/packages/1b/37/f17375e0bb2f6a812d4dd92d7616e41917f740f3e71343627da9db2824ce/jiter-0.13.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d01ecc3a8cbdb6f25a37bd500510550b64ddf9f7d64a107d92f3ccb25035d0f", size = 483727, upload-time = "2026-02-02T12:36:22.688Z" }, - { url = "https://files.pythonhosted.org/packages/77/d2/a71160a5ae1a1e66c1395b37ef77da67513b0adba73b993a27fbe47eb048/jiter-0.13.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed9bbc30f5d60a3bdf63ae76beb3f9db280d7f195dfcfa61af792d6ce912d159", size = 370799, upload-time = "2026-02-02T12:36:24.106Z" }, - { url = "https://files.pythonhosted.org/packages/01/99/ed5e478ff0eb4e8aa5fd998f9d69603c9fd3f32de3bd16c2b1194f68361c/jiter-0.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98fbafb6e88256f4454de33c1f40203d09fc33ed19162a68b3b257b29ca7f663", size = 359120, upload-time = "2026-02-02T12:36:25.519Z" }, - { url = "https://files.pythonhosted.org/packages/16/be/7ffd08203277a813f732ba897352797fa9493faf8dc7995b31f3d9cb9488/jiter-0.13.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5467696f6b827f1116556cb0db620440380434591e93ecee7fd14d1a491b6daa", size = 390664, upload-time = "2026-02-02T12:36:26.866Z" }, - { url = "https://files.pythonhosted.org/packages/d1/84/e0787856196d6d346264d6dcccb01f741e5f0bd014c1d9a2ebe149caf4f3/jiter-0.13.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:2d08c9475d48b92892583df9da592a0e2ac49bcd41fae1fec4f39ba6cf107820", size = 513543, upload-time = "2026-02-02T12:36:28.217Z" }, - { url = "https://files.pythonhosted.org/packages/65/50/ecbd258181c4313cf79bca6c88fb63207d04d5bf5e4f65174114d072aa55/jiter-0.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:aed40e099404721d7fcaf5b89bd3b4568a4666358bcac7b6b15c09fb6252ab68", size = 547262, upload-time = "2026-02-02T12:36:29.678Z" }, - { url = "https://files.pythonhosted.org/packages/27/da/68f38d12e7111d2016cd198161b36e1f042bd115c169255bcb7ec823a3bf/jiter-0.13.0-cp313-cp313-win32.whl", hash = "sha256:36ebfbcffafb146d0e6ffb3e74d51e03d9c35ce7c625c8066cdbfc7b953bdc72", size = 200630, upload-time = "2026-02-02T12:36:31.808Z" }, - { url = "https://files.pythonhosted.org/packages/25/65/3bd1a972c9a08ecd22eb3b08a95d1941ebe6938aea620c246cf426ae09c2/jiter-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:8d76029f077379374cf0dbc78dbe45b38dec4a2eb78b08b5194ce836b2517afc", size = 202602, upload-time = "2026-02-02T12:36:33.679Z" }, - { url = "https://files.pythonhosted.org/packages/15/fe/13bd3678a311aa67686bb303654792c48206a112068f8b0b21426eb6851e/jiter-0.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:bb7613e1a427cfcb6ea4544f9ac566b93d5bf67e0d48c787eca673ff9c9dff2b", size = 185939, upload-time = "2026-02-02T12:36:35.065Z" }, - { url = "https://files.pythonhosted.org/packages/49/19/a929ec002ad3228bc97ca01dbb14f7632fffdc84a95ec92ceaf4145688ae/jiter-0.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fa476ab5dd49f3bf3a168e05f89358c75a17608dbabb080ef65f96b27c19ab10", size = 316616, upload-time = "2026-02-02T12:36:36.579Z" }, - { url = "https://files.pythonhosted.org/packages/52/56/d19a9a194afa37c1728831e5fb81b7722c3de18a3109e8f282bfc23e587a/jiter-0.13.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ade8cb6ff5632a62b7dbd4757d8c5573f7a2e9ae285d6b5b841707d8363205ef", size = 346850, upload-time = "2026-02-02T12:36:38.058Z" }, - { url = "https://files.pythonhosted.org/packages/36/4a/94e831c6bf287754a8a019cb966ed39ff8be6ab78cadecf08df3bb02d505/jiter-0.13.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9950290340acc1adaded363edd94baebcee7dabdfa8bee4790794cd5cfad2af6", size = 358551, upload-time = "2026-02-02T12:36:39.417Z" }, - { url = "https://files.pythonhosted.org/packages/a2/ec/a4c72c822695fa80e55d2b4142b73f0012035d9fcf90eccc56bc060db37c/jiter-0.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2b4972c6df33731aac0742b64fd0d18e0a69bc7d6e03108ce7d40c85fd9e3e6d", size = 201950, upload-time = "2026-02-02T12:36:40.791Z" }, - { url = "https://files.pythonhosted.org/packages/b6/00/393553ec27b824fbc29047e9c7cd4a3951d7fbe4a76743f17e44034fa4e4/jiter-0.13.0-cp313-cp313t-win_arm64.whl", hash = "sha256:701a1e77d1e593c1b435315ff625fd071f0998c5f02792038a5ca98899261b7d", size = 185852, upload-time = "2026-02-02T12:36:42.077Z" }, - { url = "https://files.pythonhosted.org/packages/6e/f5/f1997e987211f6f9bd71b8083047b316208b4aca0b529bb5f8c96c89ef3e/jiter-0.13.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:cc5223ab19fe25e2f0bf2643204ad7318896fe3729bf12fde41b77bfc4fafff0", size = 308804, upload-time = "2026-02-02T12:36:43.496Z" }, - { url = "https://files.pythonhosted.org/packages/cd/8f/5482a7677731fd44881f0204981ce2d7175db271f82cba2085dd2212e095/jiter-0.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9776ebe51713acf438fd9b4405fcd86893ae5d03487546dae7f34993217f8a91", size = 318787, upload-time = "2026-02-02T12:36:45.071Z" }, - { url = "https://files.pythonhosted.org/packages/f3/b9/7257ac59778f1cd025b26a23c5520a36a424f7f1b068f2442a5b499b7464/jiter-0.13.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:879e768938e7b49b5e90b7e3fecc0dbec01b8cb89595861fb39a8967c5220d09", size = 353880, upload-time = "2026-02-02T12:36:47.365Z" }, - { url = "https://files.pythonhosted.org/packages/c3/87/719eec4a3f0841dad99e3d3604ee4cba36af4419a76f3cb0b8e2e691ad67/jiter-0.13.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:682161a67adea11e3aae9038c06c8b4a9a71023228767477d683f69903ebc607", size = 366702, upload-time = "2026-02-02T12:36:48.871Z" }, - { url = "https://files.pythonhosted.org/packages/d2/65/415f0a75cf6921e43365a1bc227c565cb949caca8b7532776e430cbaa530/jiter-0.13.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a13b68cd1cd8cc9de8f244ebae18ccb3e4067ad205220ef324c39181e23bbf66", size = 486319, upload-time = "2026-02-02T12:36:53.006Z" }, - { url = "https://files.pythonhosted.org/packages/54/a2/9e12b48e82c6bbc6081fd81abf915e1443add1b13d8fc586e1d90bb02bb8/jiter-0.13.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87ce0f14c6c08892b610686ae8be350bf368467b6acd5085a5b65441e2bf36d2", size = 372289, upload-time = "2026-02-02T12:36:54.593Z" }, - { url = "https://files.pythonhosted.org/packages/4e/c1/e4693f107a1789a239c759a432e9afc592366f04e901470c2af89cfd28e1/jiter-0.13.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c365005b05505a90d1c47856420980d0237adf82f70c4aff7aebd3c1cc143ad", size = 360165, upload-time = "2026-02-02T12:36:56.112Z" }, - { url = "https://files.pythonhosted.org/packages/17/08/91b9ea976c1c758240614bd88442681a87672eebc3d9a6dde476874e706b/jiter-0.13.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1317fdffd16f5873e46ce27d0e0f7f4f90f0cdf1d86bf6abeaea9f63ca2c401d", size = 389634, upload-time = "2026-02-02T12:36:57.495Z" }, - { url = "https://files.pythonhosted.org/packages/18/23/58325ef99390d6d40427ed6005bf1ad54f2577866594bcf13ce55675f87d/jiter-0.13.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c05b450d37ba0c9e21c77fef1f205f56bcee2330bddca68d344baebfc55ae0df", size = 514933, upload-time = "2026-02-02T12:36:58.909Z" }, - { url = "https://files.pythonhosted.org/packages/5b/25/69f1120c7c395fd276c3996bb8adefa9c6b84c12bb7111e5c6ccdcd8526d/jiter-0.13.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:775e10de3849d0631a97c603f996f518159272db00fdda0a780f81752255ee9d", size = 548842, upload-time = "2026-02-02T12:37:00.433Z" }, - { url = "https://files.pythonhosted.org/packages/18/05/981c9669d86850c5fbb0d9e62bba144787f9fba84546ba43d624ee27ef29/jiter-0.13.0-cp314-cp314-win32.whl", hash = "sha256:632bf7c1d28421c00dd8bbb8a3bac5663e1f57d5cd5ed962bce3c73bf62608e6", size = 202108, upload-time = "2026-02-02T12:37:01.718Z" }, - { url = "https://files.pythonhosted.org/packages/8d/96/cdcf54dd0b0341db7d25413229888a346c7130bd20820530905fdb65727b/jiter-0.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:f22ef501c3f87ede88f23f9b11e608581c14f04db59b6a801f354397ae13739f", size = 204027, upload-time = "2026-02-02T12:37:03.075Z" }, - { url = "https://files.pythonhosted.org/packages/fb/f9/724bcaaab7a3cd727031fe4f6995cb86c4bd344909177c186699c8dec51a/jiter-0.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:07b75fe09a4ee8e0c606200622e571e44943f47254f95e2436c8bdcaceb36d7d", size = 187199, upload-time = "2026-02-02T12:37:04.414Z" }, - { url = "https://files.pythonhosted.org/packages/62/92/1661d8b9fd6a3d7a2d89831db26fe3c1509a287d83ad7838831c7b7a5c7e/jiter-0.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:964538479359059a35fb400e769295d4b315ae61e4105396d355a12f7fef09f0", size = 318423, upload-time = "2026-02-02T12:37:05.806Z" }, - { url = "https://files.pythonhosted.org/packages/4f/3b/f77d342a54d4ebcd128e520fc58ec2f5b30a423b0fd26acdfc0c6fef8e26/jiter-0.13.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e104da1db1c0991b3eaed391ccd650ae8d947eab1480c733e5a3fb28d4313e40", size = 351438, upload-time = "2026-02-02T12:37:07.189Z" }, - { url = "https://files.pythonhosted.org/packages/76/b3/ba9a69f0e4209bd3331470c723c2f5509e6f0482e416b612431a5061ed71/jiter-0.13.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e3a5f0cde8ff433b8e88e41aa40131455420fb3649a3c7abdda6145f8cb7202", size = 364774, upload-time = "2026-02-02T12:37:08.579Z" }, - { url = "https://files.pythonhosted.org/packages/b3/16/6cdb31fa342932602458dbb631bfbd47f601e03d2e4950740e0b2100b570/jiter-0.13.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57aab48f40be1db920a582b30b116fe2435d184f77f0e4226f546794cedd9cf0", size = 487238, upload-time = "2026-02-02T12:37:10.066Z" }, - { url = "https://files.pythonhosted.org/packages/ed/b1/956cc7abaca8d95c13aa8d6c9b3f3797241c246cd6e792934cc4c8b250d2/jiter-0.13.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7772115877c53f62beeb8fd853cab692dbc04374ef623b30f997959a4c0e7e95", size = 372892, upload-time = "2026-02-02T12:37:11.656Z" }, - { url = "https://files.pythonhosted.org/packages/26/c4/97ecde8b1e74f67b8598c57c6fccf6df86ea7861ed29da84629cdbba76c4/jiter-0.13.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1211427574b17b633cfceba5040de8081e5abf114f7a7602f73d2e16f9fdaa59", size = 360309, upload-time = "2026-02-02T12:37:13.244Z" }, - { url = "https://files.pythonhosted.org/packages/4b/d7/eabe3cf46715854ccc80be2cd78dd4c36aedeb30751dbf85a1d08c14373c/jiter-0.13.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7beae3a3d3b5212d3a55d2961db3c292e02e302feb43fce6a3f7a31b90ea6dfe", size = 389607, upload-time = "2026-02-02T12:37:14.881Z" }, - { url = "https://files.pythonhosted.org/packages/df/2d/03963fc0804e6109b82decfb9974eb92df3797fe7222428cae12f8ccaa0c/jiter-0.13.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:e5562a0f0e90a6223b704163ea28e831bd3a9faa3512a711f031611e6b06c939", size = 514986, upload-time = "2026-02-02T12:37:16.326Z" }, - { url = "https://files.pythonhosted.org/packages/f6/6c/8c83b45eb3eb1c1e18d841fe30b4b5bc5619d781267ca9bc03e005d8fd0a/jiter-0.13.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:6c26a424569a59140fb51160a56df13f438a2b0967365e987889186d5fc2f6f9", size = 548756, upload-time = "2026-02-02T12:37:17.736Z" }, - { url = "https://files.pythonhosted.org/packages/47/66/eea81dfff765ed66c68fd2ed8c96245109e13c896c2a5015c7839c92367e/jiter-0.13.0-cp314-cp314t-win32.whl", hash = "sha256:24dc96eca9f84da4131cdf87a95e6ce36765c3b156fc9ae33280873b1c32d5f6", size = 201196, upload-time = "2026-02-02T12:37:19.101Z" }, - { url = "https://files.pythonhosted.org/packages/ff/32/4ac9c7a76402f8f00d00842a7f6b83b284d0cf7c1e9d4227bc95aa6d17fa/jiter-0.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0a8d76c7524087272c8ae913f5d9d608bd839154b62c4322ef65723d2e5bb0b8", size = 204215, upload-time = "2026-02-02T12:37:20.495Z" }, - { url = "https://files.pythonhosted.org/packages/f9/8e/7def204fea9f9be8b3c21a6f2dd6c020cf56c7d5ff753e0e23ed7f9ea57e/jiter-0.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2c26cf47e2cad140fa23b6d58d435a7c0161f5c514284802f25e87fddfe11024", size = 187152, upload-time = "2026-02-02T12:37:22.124Z" }, - { url = "https://files.pythonhosted.org/packages/79/b3/3c29819a27178d0e461a8571fb63c6ae38be6dc36b78b3ec2876bbd6a910/jiter-0.13.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b1cbfa133241d0e6bdab48dcdc2604e8ba81512f6bbd68ec3e8e1357dd3c316c", size = 307016, upload-time = "2026-02-02T12:37:42.755Z" }, - { url = "https://files.pythonhosted.org/packages/eb/ae/60993e4b07b1ac5ebe46da7aa99fdbb802eb986c38d26e3883ac0125c4e0/jiter-0.13.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:db367d8be9fad6e8ebbac4a7578b7af562e506211036cba2c06c3b998603c3d2", size = 305024, upload-time = "2026-02-02T12:37:44.774Z" }, - { url = "https://files.pythonhosted.org/packages/77/fa/2227e590e9cf98803db2811f172b2d6460a21539ab73006f251c66f44b14/jiter-0.13.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45f6f8efb2f3b0603092401dc2df79fa89ccbc027aaba4174d2d4133ed661434", size = 339337, upload-time = "2026-02-02T12:37:46.668Z" }, - { url = "https://files.pythonhosted.org/packages/2d/92/015173281f7eb96c0ef580c997da8ef50870d4f7f4c9e03c845a1d62ae04/jiter-0.13.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:597245258e6ad085d064780abfb23a284d418d3e61c57362d9449c6c7317ee2d", size = 346395, upload-time = "2026-02-02T12:37:48.09Z" }, - { url = "https://files.pythonhosted.org/packages/80/60/e50fa45dd7e2eae049f0ce964663849e897300433921198aef94b6ffa23a/jiter-0.13.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:3d744a6061afba08dd7ae375dcde870cffb14429b7477e10f67e9e6d68772a0a", size = 305169, upload-time = "2026-02-02T12:37:50.376Z" }, - { url = "https://files.pythonhosted.org/packages/d2/73/a009f41c5eed71c49bec53036c4b33555afcdee70682a18c6f66e396c039/jiter-0.13.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:ff732bd0a0e778f43d5009840f20b935e79087b4dc65bd36f1cd0f9b04b8ff7f", size = 303808, upload-time = "2026-02-02T12:37:52.092Z" }, - { url = "https://files.pythonhosted.org/packages/c4/10/528b439290763bff3d939268085d03382471b442f212dca4ff5f12802d43/jiter-0.13.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab44b178f7981fcaea7e0a5df20e773c663d06ffda0198f1a524e91b2fde7e59", size = 337384, upload-time = "2026-02-02T12:37:53.582Z" }, - { url = "https://files.pythonhosted.org/packages/67/8a/a342b2f0251f3dac4ca17618265d93bf244a2a4d089126e81e4c1056ac50/jiter-0.13.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bb00b6d26db67a05fe3e12c76edc75f32077fb51deed13822dc648fa373bc19", size = 343768, upload-time = "2026-02-02T12:37:55.055Z" }, -] - -[[package]] -name = "openai" -version = "2.17.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "distro" }, - { name = "httpx" }, - { name = "jiter" }, - { name = "pydantic" }, - { name = "sniffio" }, - { name = "tqdm" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9c/a2/677f22c4b487effb8a09439fb6134034b5f0a39ca27df8b95fac23a93720/openai-2.17.0.tar.gz", hash = "sha256:47224b74bd20f30c6b0a6a329505243cb2f26d5cf84d9f8d0825ff8b35e9c999", size = 631445, upload-time = "2026-02-05T16:27:40.953Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/44/97/284535aa75e6e84ab388248b5a323fc296b1f70530130dee37f7f4fbe856/openai-2.17.0-py3-none-any.whl", hash = "sha256:4f393fd886ca35e113aac7ff239bcd578b81d8f104f5aedc7d3693eb2af1d338", size = 1069524, upload-time = "2026-02-05T16:27:38.941Z" }, -] - -[[package]] -name = "pyasn1" -version = "0.6.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/b6/6e630dff89739fcd427e3f72b3d905ce0acb85a45d4ec3e2678718a3487f/pyasn1-0.6.2.tar.gz", hash = "sha256:9b59a2b25ba7e4f8197db7686c09fb33e658b98339fadb826e9512629017833b", size = 146586, upload-time = "2026-01-16T18:04:18.534Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/44/b5/a96872e5184f354da9c84ae119971a0a4c221fe9b27a4d94bd43f2596727/pyasn1-0.6.2-py3-none-any.whl", hash = "sha256:1eb26d860996a18e9b6ed05e7aae0e9fc21619fcee6af91cca9bad4fbea224bf", size = 83371, upload-time = "2026-01-16T18:04:17.174Z" }, -] - -[[package]] -name = "pyasn1-modules" -version = "0.4.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyasn1" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, -] - -[[package]] -name = "pycparser" -version = "3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, -] - -[[package]] -name = "pydantic" -version = "2.12.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "annotated-types" }, - { name = "pydantic-core" }, - { name = "typing-extensions" }, - { name = "typing-inspection" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, -] - -[[package]] -name = "pydantic-core" -version = "2.41.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/90/32c9941e728d564b411d574d8ee0cf09b12ec978cb22b294995bae5549a5/pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146", size = 2107298, upload-time = "2025-11-04T13:39:04.116Z" }, - { url = "https://files.pythonhosted.org/packages/fb/a8/61c96a77fe28993d9a6fb0f4127e05430a267b235a124545d79fea46dd65/pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2", size = 1901475, upload-time = "2025-11-04T13:39:06.055Z" }, - { url = "https://files.pythonhosted.org/packages/5d/b6/338abf60225acc18cdc08b4faef592d0310923d19a87fba1faf05af5346e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97", size = 1918815, upload-time = "2025-11-04T13:39:10.41Z" }, - { url = "https://files.pythonhosted.org/packages/d1/1c/2ed0433e682983d8e8cba9c8d8ef274d4791ec6a6f24c58935b90e780e0a/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9", size = 2065567, upload-time = "2025-11-04T13:39:12.244Z" }, - { url = "https://files.pythonhosted.org/packages/b3/24/cf84974ee7d6eae06b9e63289b7b8f6549d416b5c199ca2d7ce13bbcf619/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52", size = 2230442, upload-time = "2025-11-04T13:39:13.962Z" }, - { url = "https://files.pythonhosted.org/packages/fd/21/4e287865504b3edc0136c89c9c09431be326168b1eb7841911cbc877a995/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941", size = 2350956, upload-time = "2025-11-04T13:39:15.889Z" }, - { url = "https://files.pythonhosted.org/packages/a8/76/7727ef2ffa4b62fcab916686a68a0426b9b790139720e1934e8ba797e238/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a", size = 2068253, upload-time = "2025-11-04T13:39:17.403Z" }, - { url = "https://files.pythonhosted.org/packages/d5/8c/a4abfc79604bcb4c748e18975c44f94f756f08fb04218d5cb87eb0d3a63e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c", size = 2177050, upload-time = "2025-11-04T13:39:19.351Z" }, - { url = "https://files.pythonhosted.org/packages/67/b1/de2e9a9a79b480f9cb0b6e8b6ba4c50b18d4e89852426364c66aa82bb7b3/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2", size = 2147178, upload-time = "2025-11-04T13:39:21Z" }, - { url = "https://files.pythonhosted.org/packages/16/c1/dfb33f837a47b20417500efaa0378adc6635b3c79e8369ff7a03c494b4ac/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556", size = 2341833, upload-time = "2025-11-04T13:39:22.606Z" }, - { url = "https://files.pythonhosted.org/packages/47/36/00f398642a0f4b815a9a558c4f1dca1b4020a7d49562807d7bc9ff279a6c/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49", size = 2321156, upload-time = "2025-11-04T13:39:25.843Z" }, - { url = "https://files.pythonhosted.org/packages/7e/70/cad3acd89fde2010807354d978725ae111ddf6d0ea46d1ea1775b5c1bd0c/pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba", size = 1989378, upload-time = "2025-11-04T13:39:27.92Z" }, - { url = "https://files.pythonhosted.org/packages/76/92/d338652464c6c367e5608e4488201702cd1cbb0f33f7b6a85a60fe5f3720/pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9", size = 2013622, upload-time = "2025-11-04T13:39:29.848Z" }, - { url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" }, - { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" }, - { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" }, - { url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" }, - { url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" }, - { url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" }, - { url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" }, - { url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" }, - { url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" }, - { url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" }, - { url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" }, - { url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" }, - { url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" }, - { url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" }, - { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, - { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, - { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, - { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, - { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, - { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, - { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, - { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, - { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, - { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, - { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, - { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, - { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, - { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, - { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, - { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, - { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, - { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, - { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, - { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, - { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, - { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, - { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, - { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, - { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, - { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, - { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, - { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, - { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, - { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, - { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, - { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, - { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, - { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, - { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, - { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, - { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, - { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, - { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, - { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, - { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, - { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, - { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, - { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, - { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, - { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, - { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, - { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, - { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, - { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, - { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, - { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, - { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, - { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, - { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" }, - { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" }, - { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" }, - { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" }, - { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, - { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, - { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, - { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, - { url = "https://files.pythonhosted.org/packages/e6/b0/1a2aa41e3b5a4ba11420aba2d091b2d17959c8d1519ece3627c371951e73/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8", size = 2103351, upload-time = "2025-11-04T13:43:02.058Z" }, - { url = "https://files.pythonhosted.org/packages/a4/ee/31b1f0020baaf6d091c87900ae05c6aeae101fa4e188e1613c80e4f1ea31/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a", size = 1925363, upload-time = "2025-11-04T13:43:05.159Z" }, - { url = "https://files.pythonhosted.org/packages/e1/89/ab8e86208467e467a80deaca4e434adac37b10a9d134cd2f99b28a01e483/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b", size = 2135615, upload-time = "2025-11-04T13:43:08.116Z" }, - { url = "https://files.pythonhosted.org/packages/99/0a/99a53d06dd0348b2008f2f30884b34719c323f16c3be4e6cc1203b74a91d/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2", size = 2175369, upload-time = "2025-11-04T13:43:12.49Z" }, - { url = "https://files.pythonhosted.org/packages/6d/94/30ca3b73c6d485b9bb0bc66e611cff4a7138ff9736b7e66bcf0852151636/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093", size = 2144218, upload-time = "2025-11-04T13:43:15.431Z" }, - { url = "https://files.pythonhosted.org/packages/87/57/31b4f8e12680b739a91f472b5671294236b82586889ef764b5fbc6669238/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a", size = 2329951, upload-time = "2025-11-04T13:43:18.062Z" }, - { url = "https://files.pythonhosted.org/packages/7d/73/3c2c8edef77b8f7310e6fb012dbc4b8551386ed575b9eb6fb2506e28a7eb/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963", size = 2318428, upload-time = "2025-11-04T13:43:20.679Z" }, - { url = "https://files.pythonhosted.org/packages/2f/02/8559b1f26ee0d502c74f9cca5c0d2fd97e967e083e006bbbb4e97f3a043a/pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a", size = 2147009, upload-time = "2025-11-04T13:43:23.286Z" }, - { url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" }, - { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" }, - { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" }, - { url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" }, - { url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" }, - { url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" }, - { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" }, -] - -[[package]] -name = "python-dotenv" -version = "1.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" }, -] - -[[package]] -name = "requests" -version = "2.32.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "charset-normalizer" }, - { name = "idna" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, -] - -[[package]] -name = "rsa" -version = "4.9.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyasn1" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" }, -] - -[[package]] -name = "sniffio" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, -] - -[[package]] -name = "tenacity" -version = "9.1.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1e/4a/c3357c8742f361785e3702bb4c9c68c4cb37a80aa657640b820669be5af1/tenacity-9.1.3.tar.gz", hash = "sha256:a6724c947aa717087e2531f883bde5c9188f603f6669a9b8d54eb998e604c12a", size = 49002, upload-time = "2026-02-05T06:33:12.866Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/64/6b/cdc85edb15e384d8e934aad89638cc8646e118c80de94c60125d0fc0a185/tenacity-9.1.3-py3-none-any.whl", hash = "sha256:51171cfc6b8a7826551e2f029426b10a6af189c5ac6986adcd7eb36d42f17954", size = 28858, upload-time = "2026-02-05T06:33:11.219Z" }, -] - -[[package]] -name = "tqdm" -version = "4.67.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, -] - -[[package]] -name = "typing-extensions" -version = "4.15.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, -] - -[[package]] -name = "typing-inspection" -version = "0.4.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, -] - -[[package]] -name = "urllib3" -version = "2.6.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, -] - -[[package]] -name = "websockets" -version = "15.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/da/6462a9f510c0c49837bbc9345aca92d767a56c1fb2939e1579df1e1cdcf7/websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b", size = 175423, upload-time = "2025-03-05T20:01:35.363Z" }, - { url = "https://files.pythonhosted.org/packages/1c/9f/9d11c1a4eb046a9e106483b9ff69bce7ac880443f00e5ce64261b47b07e7/websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205", size = 173080, upload-time = "2025-03-05T20:01:37.304Z" }, - { url = "https://files.pythonhosted.org/packages/d5/4f/b462242432d93ea45f297b6179c7333dd0402b855a912a04e7fc61c0d71f/websockets-15.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a", size = 173329, upload-time = "2025-03-05T20:01:39.668Z" }, - { url = "https://files.pythonhosted.org/packages/6e/0c/6afa1f4644d7ed50284ac59cc70ef8abd44ccf7d45850d989ea7310538d0/websockets-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e", size = 182312, upload-time = "2025-03-05T20:01:41.815Z" }, - { url = "https://files.pythonhosted.org/packages/dd/d4/ffc8bd1350b229ca7a4db2a3e1c482cf87cea1baccd0ef3e72bc720caeec/websockets-15.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf", size = 181319, upload-time = "2025-03-05T20:01:43.967Z" }, - { url = "https://files.pythonhosted.org/packages/97/3a/5323a6bb94917af13bbb34009fac01e55c51dfde354f63692bf2533ffbc2/websockets-15.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb", size = 181631, upload-time = "2025-03-05T20:01:46.104Z" }, - { url = "https://files.pythonhosted.org/packages/a6/cc/1aeb0f7cee59ef065724041bb7ed667b6ab1eeffe5141696cccec2687b66/websockets-15.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d", size = 182016, upload-time = "2025-03-05T20:01:47.603Z" }, - { url = "https://files.pythonhosted.org/packages/79/f9/c86f8f7af208e4161a7f7e02774e9d0a81c632ae76db2ff22549e1718a51/websockets-15.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9", size = 181426, upload-time = "2025-03-05T20:01:48.949Z" }, - { url = "https://files.pythonhosted.org/packages/c7/b9/828b0bc6753db905b91df6ae477c0b14a141090df64fb17f8a9d7e3516cf/websockets-15.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c", size = 181360, upload-time = "2025-03-05T20:01:50.938Z" }, - { url = "https://files.pythonhosted.org/packages/89/fb/250f5533ec468ba6327055b7d98b9df056fb1ce623b8b6aaafb30b55d02e/websockets-15.0.1-cp310-cp310-win32.whl", hash = "sha256:1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256", size = 176388, upload-time = "2025-03-05T20:01:52.213Z" }, - { url = "https://files.pythonhosted.org/packages/1c/46/aca7082012768bb98e5608f01658ff3ac8437e563eca41cf068bd5849a5e/websockets-15.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41", size = 176830, upload-time = "2025-03-05T20:01:53.922Z" }, - { url = "https://files.pythonhosted.org/packages/9f/32/18fcd5919c293a398db67443acd33fde142f283853076049824fc58e6f75/websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431", size = 175423, upload-time = "2025-03-05T20:01:56.276Z" }, - { url = "https://files.pythonhosted.org/packages/76/70/ba1ad96b07869275ef42e2ce21f07a5b0148936688c2baf7e4a1f60d5058/websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57", size = 173082, upload-time = "2025-03-05T20:01:57.563Z" }, - { url = "https://files.pythonhosted.org/packages/86/f2/10b55821dd40eb696ce4704a87d57774696f9451108cff0d2824c97e0f97/websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905", size = 173330, upload-time = "2025-03-05T20:01:59.063Z" }, - { url = "https://files.pythonhosted.org/packages/a5/90/1c37ae8b8a113d3daf1065222b6af61cc44102da95388ac0018fcb7d93d9/websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562", size = 182878, upload-time = "2025-03-05T20:02:00.305Z" }, - { url = "https://files.pythonhosted.org/packages/8e/8d/96e8e288b2a41dffafb78e8904ea7367ee4f891dafc2ab8d87e2124cb3d3/websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792", size = 181883, upload-time = "2025-03-05T20:02:03.148Z" }, - { url = "https://files.pythonhosted.org/packages/93/1f/5d6dbf551766308f6f50f8baf8e9860be6182911e8106da7a7f73785f4c4/websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413", size = 182252, upload-time = "2025-03-05T20:02:05.29Z" }, - { url = "https://files.pythonhosted.org/packages/d4/78/2d4fed9123e6620cbf1706c0de8a1632e1a28e7774d94346d7de1bba2ca3/websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8", size = 182521, upload-time = "2025-03-05T20:02:07.458Z" }, - { url = "https://files.pythonhosted.org/packages/e7/3b/66d4c1b444dd1a9823c4a81f50231b921bab54eee2f69e70319b4e21f1ca/websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3", size = 181958, upload-time = "2025-03-05T20:02:09.842Z" }, - { url = "https://files.pythonhosted.org/packages/08/ff/e9eed2ee5fed6f76fdd6032ca5cd38c57ca9661430bb3d5fb2872dc8703c/websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf", size = 181918, upload-time = "2025-03-05T20:02:11.968Z" }, - { url = "https://files.pythonhosted.org/packages/d8/75/994634a49b7e12532be6a42103597b71098fd25900f7437d6055ed39930a/websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85", size = 176388, upload-time = "2025-03-05T20:02:13.32Z" }, - { url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065", size = 176828, upload-time = "2025-03-05T20:02:14.585Z" }, - { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" }, - { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" }, - { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" }, - { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152, upload-time = "2025-03-05T20:02:22.286Z" }, - { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096, upload-time = "2025-03-05T20:02:24.368Z" }, - { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523, upload-time = "2025-03-05T20:02:25.669Z" }, - { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790, upload-time = "2025-03-05T20:02:26.99Z" }, - { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165, upload-time = "2025-03-05T20:02:30.291Z" }, - { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160, upload-time = "2025-03-05T20:02:31.634Z" }, - { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395, upload-time = "2025-03-05T20:02:33.017Z" }, - { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841, upload-time = "2025-03-05T20:02:34.498Z" }, - { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, - { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, - { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, - { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" }, - { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" }, - { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" }, - { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" }, - { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" }, - { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" }, - { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" }, - { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, - { url = "https://files.pythonhosted.org/packages/02/9e/d40f779fa16f74d3468357197af8d6ad07e7c5a27ea1ca74ceb38986f77a/websockets-15.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3", size = 173109, upload-time = "2025-03-05T20:03:17.769Z" }, - { url = "https://files.pythonhosted.org/packages/bc/cd/5b887b8585a593073fd92f7c23ecd3985cd2c3175025a91b0d69b0551372/websockets-15.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1", size = 173343, upload-time = "2025-03-05T20:03:19.094Z" }, - { url = "https://files.pythonhosted.org/packages/fe/ae/d34f7556890341e900a95acf4886833646306269f899d58ad62f588bf410/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475", size = 174599, upload-time = "2025-03-05T20:03:21.1Z" }, - { url = "https://files.pythonhosted.org/packages/71/e6/5fd43993a87db364ec60fc1d608273a1a465c0caba69176dd160e197ce42/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9", size = 174207, upload-time = "2025-03-05T20:03:23.221Z" }, - { url = "https://files.pythonhosted.org/packages/2b/fb/c492d6daa5ec067c2988ac80c61359ace5c4c674c532985ac5a123436cec/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04", size = 174155, upload-time = "2025-03-05T20:03:25.321Z" }, - { url = "https://files.pythonhosted.org/packages/68/a1/dcb68430b1d00b698ae7a7e0194433bce4f07ded185f0ee5fb21e2a2e91e/websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122", size = 176884, upload-time = "2025-03-05T20:03:27.934Z" }, - { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, -]