diff --git a/.github/workflows/sta.yml b/.github/workflows/sta.yml new file mode 100644 index 000000000..591615bcf --- /dev/null +++ b/.github/workflows/sta.yml @@ -0,0 +1,102 @@ +name: STA Nightly Regression + +on: + schedule: + # 12:00 UTC == 20:00 UTC+8 + - cron: '00 12 * * *' + workflow_dispatch: + pull_request: + +jobs: + run-sta-xiangshan: + timeout-minutes: 2880 + runs-on: [self-hosted, node] + + steps: + - uses: actions/checkout@v4 + + - name: Prepare XiangShan + run: | + cd $GITHUB_WORKSPACE/.. + rm -rf XiangShan + proxychains git clone --single-branch --branch main --depth 1 https://github.com/OpenXiangShan/XiangShan.git + cd XiangShan && make init && rm -rf difftest && cp -r $GITHUB_WORKSPACE . + echo "NOOP_HOME=$(pwd)" >> $GITHUB_ENV + echo "DIFF_RTL_HOME=$(pwd)/difftest/build" >> $GITHUB_ENV + + - name: Build XiangShan + run: | + cd $NOOP_HOME + make verilog DEBUG_ARGS="--difftest-config ESBIFDU --difftest-exclude Vec" FPGA=1 WITH_CHISELDB=0 WITH_CONSTANTIN=0 CONFIG=XSNoCDiffTopConfig -j10 + + - name: Build DiffTest + run: | + cd $NOOP_HOME/difftest + export NOOP_HOME=$(pwd) + make difftest_verilog PROFILE=../build/generated-src/difftest_profile.json NUMCORES=1 CONFIG=ESBIFDU + + - name: Run STA + run: | + export PATH=/nfs/home/share/tools/yosys:$PATH + export YOSYS_STA_DIR=/nfs/home/share/tools/yosys-sta + export ABC_THREADS=16 + export YOSYS_TIMEOUT=12h + export ISTA_TIMEOUT=24h + export TIMEOUT_KILL_AFTER=10m + RUN_STA_TOTAL_TIMEOUT=40h + cd $NOOP_HOME/difftest + RUN_STA_LOG=./scripts/ieda/sta_results/run_sta.log + RUN_STA_STATUS=./scripts/ieda/sta_results/run_sta.status + # Keep STA output in file to avoid CI pipeline log-streaming side effects. + set +e + if ! command -v timeout >/dev/null 2>&1; then + echo "ERROR: timeout command not found on runner" + exit 2 + fi + RUN_STA_LOG_TO_FILE=1 RUN_STA_LOG_CONSOLE=0 RUN_STA_LOG_FILE=$RUN_STA_LOG RUN_STA_STATUS_FILE=$RUN_STA_STATUS \ + timeout --foreground --signal=TERM --kill-after=15m "$RUN_STA_TOTAL_TIMEOUT" \ + bash ./scripts/ieda/run_sta.sh $DIFF_RTL_HOME GatewayEndpoint + EXIT_CODE=$? + set -e + echo "run_sta exit code: $EXIT_CODE" + # Print a compact summary to CI logs after completion. + if [ -f "$RUN_STA_STATUS" ]; then + echo "=== run_sta status markers (from $RUN_STA_STATUS) ===" + tail -n 100 "$RUN_STA_STATUS" || true + else + echo "WARNING: run_sta status file not found: $RUN_STA_STATUS" + fi + if [ -f "$RUN_STA_LOG" ]; then + echo "=== run_sta summary (from $RUN_STA_LOG) ===" + grep -E '(^Step|^===|^---|^Error:|^ERROR:|completed|saved|Results|ABC threads|yosys-sta|RTL directory|Clock|PDK|Netlist|Timing analysis|Critical path|Summary)' \ + "$RUN_STA_LOG" | tail -n 200 || true + else + echo "WARNING: run_sta log not found: $RUN_STA_LOG" + fi + if [ "$EXIT_CODE" -ne 0 ] && [ -f "$RUN_STA_LOG" ]; then + echo "=== run_sta log tail (last 200 lines) ===" + tail -n 200 "$RUN_STA_LOG" || true + fi + if [ "$EXIT_CODE" -eq 124 ] || [ "$EXIT_CODE" -eq 137 ]; then + echo "ERROR: run_sta timeout reached (total=$RUN_STA_TOTAL_TIMEOUT, yosys=$YOSYS_TIMEOUT, ista=$ISTA_TIMEOUT)" + fi + # Keep original non-zero exit code from run_sta.sh (including timeout 124). + if [ "$EXIT_CODE" -ne 0 ]; then + exit $EXIT_CODE + fi + # Check output artifacts only when run_sta.sh completed successfully. + if [ ! -f "./scripts/ieda/sta_results/critical_paths.txt" ]; then + echo "ERROR: run_sta succeeded but critical_paths.txt not found" + exit 1 + fi + exit $EXIT_CODE + + - name: Print critical results + run: | + cd $NOOP_HOME/difftest + if [ -f "./scripts/ieda/sta_results/critical_paths.txt" ]; then + cat ./scripts/ieda/sta_results/critical_paths.txt + else + echo "ERROR: critical_paths.txt not found!" + exit 1 + fi diff --git a/scripts/ieda/README.md b/scripts/ieda/README.md new file mode 100644 index 000000000..04bce6303 --- /dev/null +++ b/scripts/ieda/README.md @@ -0,0 +1,298 @@ +# Yosys Synthesis and Static Timing Analysis Tool + +A comprehensive toolchain for RTL synthesis and static timing analysis using Yosys and iEDA/iSTA. + +## Overview + +This tool provides an automated workflow for: +- RTL synthesis using Yosys +- Static timing analysis using iEDA/iSTA +- SystemVerilog preprocessing for Yosys compatibility +- Timing report extraction and analysis + +## Directory Structure + +``` +yosys_synth_tool/ +├── run_sta.sh # Main script for synthesis and timing analysis +├── sv_preprocessor.py # SystemVerilog preprocessor +├── extract_timing_report.sh # Timing report extraction script +├── README.md # This file +└── sta_results/ # Generated results directory + +yosys-sta/ (installed separately, location specified via YOSYS_STA_DIR) +├── scripts/ +│ ├── yosys.tcl # Yosys synthesis script +│ ├── sta.tcl # iSTA timing analysis script +│ └── default.sdc # Default SDC constraints +├── pdk/ +│ ├── nangate45/ # Nangate 45nm PDK +│ └── icsprout55/ # ICSPROUT 55nm PDK +└── bin/ + └── iEDA # iEDA binary +``` + +## Prerequisites + +- Yosys (installed locally and available in PATH) +- Python 3.x +- Bash shell +- iEDA/iSTA (yosys-sta installation) +- Yosys-sta directory set via `YOSYS_STA_DIR` environment variable + +## Quick Start + +### 1. Set up yosys-sta Location + +Set the `YOSYS_STA_DIR` environment variable to point to your yosys-sta installation: + +```bash +# Add to your ~/.bashrc or ~/.zshrc for persistent configuration +export YOSYS_STA_DIR=/path/to/yosys-sta +``` + +Or specify it inline when running the script. + +### 2. Basic Usage + +```bash +./run_sta.sh [top_module_name] +``` + +**Example:** +```bash +./run_sta.sh /path/to/rtl/files MyTopModule +``` + +### 3. With Environment Variables + +```bash +YOSYS_STA_DIR=/path/to/yosys-sta CLK_PORT_NAME=clock CLK_FREQ_MHZ=1000 ./run_sta.sh /path/to/rtl/files MyTopModule +``` + +## Environment Variables + +| Variable | Description | Default | +|----------|-------------|---------| +| `YOSYS_STA_DIR` | Path to yosys-sta installation directory | `./yosys-sta` | +| `CLK_FREQ_MHZ` | Clock frequency in MHz | `500` | +| `CLK_PORT_NAME` | Clock port name | `clock` | +| `SDC_FILE` | Custom SDC constraints file | `$YOSYS_STA_DIR/scripts/default.sdc` | +| `PDK` | Process design kit | `nangate45` | + +## Supported File Types + +- **`.v`** - Verilog design files +- **`.vh`** - Verilog header files +- **`.sv`** - SystemVerilog design files +- **`.svh`** - SystemVerilog header files (automatically handled) + +## Features + +### 1. Automatic RTL File Collection + +The script automatically discovers RTL files in the input directory and all subdirectories, excluding test directories: +- `test/` +- `sim/` +- `tb/` +- `verification/` +- `bench/` + +### 2. SystemVerilog Preprocessing + +The tool includes a preprocessor that converts Yosys-incompatible SystemVerilog syntax: + +#### Supported Conversions + +| Original Syntax | Converted Syntax | +|----------------|------------------| +| `string var_name;` | `// string var_name;` (commented out) | +| `automatic logic func();` | `logic func();` (keyword removed) | +| `wire [15:0][3:0] var = '{val1, val2, ...};` | `wire [15:0][3:0] var; assign var[0] = val1; ...` | + +### 3. Header File Handling + +`.svh` header files are automatically: +- Discovered in the RTL directory +- Copied to the preprocessing directory +- Added to Yosys include path + +### 4. Duplicate File Detection + +Files with identical names are automatically deduplicated (first occurrence is kept). + +### 5. Comprehensive Timing Reports + +The tool generates multiple output files: + +#### Synthesis Outputs +- `*.netlist.v` - Synthesized netlist +- `yosys.log` - Yosys synthesis log +- `synth_stat.txt` - Synthesis statistics +- `synth_check.txt` - Design check results + +#### Timing Analysis Outputs +- `*.rpt` - Main timing report +- `sta.log` - iSTA analysis log +- `*_setup.skew` - Setup skew report +- `*_hold.skew` - Hold skew report +- `*.cap` - Capacitance report +- `*.fanout` - Fanout report +- `*.trans` - Transition report + +## Output Directory + +Results are stored in a fixed directory: +``` +sta_results/ +├── GatewayEndpoint.netlist.v +├── GatewayEndpoint.rpt +├── yosys.log +├── sta.log +└── ... +``` + +## Timing Report Extraction + +Extract key timing information from the generated report: + +```bash +./extract_timing_report.sh sta_results/GatewayEndpoint.rpt +``` + +### Report Sections + +1. **TNS Summary** - Total Negative Slack by clock +2. **Setup Critical Paths** - Top 10 worst setup paths +3. **Hold Critical Paths** - Top 10 worst hold paths +4. **Path Details** - Detailed timing breakdown +5. **Statistics** - Path counts and violation detection + +## Examples + +### Example 1: Basic Synthesis + +```bash +./run_sta.sh /home/user/project/rtl TopModule +``` + +### Example 1: Basic Synthesis + +```bash +# Set yosys-sta location +export YOSYS_STA_DIR=/opt/yosys-sta + +# Run synthesis and timing analysis +./run_sta.sh /home/user/project/rtl TopModule +``` + +### Example 2: Custom Clock Frequency + +```bash +YOSYS_STA_DIR=/opt/yosys-sta CLK_FREQ_MHZ=1000 ./run_sta.sh /home/user/project/rtl TopModule +``` + +### Example 3: Custom Clock Port + +```bash +YOSYS_STA_DIR=/opt/yosys-sta CLK_PORT_NAME=clk ./run_sta.sh /home/user/project/rtl TopModule +``` + +### Example 4: Custom SDC File + +```bash +YOSYS_STA_DIR=/opt/yosys-sta SDC_FILE=/path/to/custom.sdc ./run_sta.sh /home/user/project/rtl TopModule +``` + +### Example 5: Different PDK + +```bash +YOSYS_STA_DIR=/opt/yosys-sta PDK=icsprout55 ./run_sta.sh /home/user/project/rtl TopModule +``` + +## Troubleshooting + +### Common Issues + +#### 1. "yosys-sta directory not found" + +**Cause:** `YOSYS_STA_DIR` environment variable not set or incorrect + +**Solution:** Set the `YOSYS_STA_DIR` environment variable: +```bash +export YOSYS_STA_DIR=/path/to/yosys-sta +``` + +#### 2. "Can't open include file" + +**Cause:** `.svh` header files not found + +**Solution:** Ensure header files are in the RTL directory or its subdirectories + +#### 3. "syntax error, unexpected TOK_AUTOMATIC" + +**Cause:** SystemVerilog `automatic` keyword not supported + +**Solution:** The preprocessor automatically removes this keyword + +#### 4. "get_ports clk was not found" + +**Cause:** Clock port name mismatch + +**Solution:** Set the correct clock port name: +```bash +CLK_PORT_NAME=your_clock_name ./run_sta.sh ... +``` + +#### 5. "Re-definition of module" + +**Cause:** Multiple files with the same module definition + +**Solution:** The script automatically deduplicates files by name + +## Workflow + +``` +┌─────────────────────┐ +│ Input RTL Files │ +└──────────┬──────────┘ + │ + ▼ +┌─────────────────────┐ +│ SV Preprocessing │ ← Convert incompatible syntax +└──────────┬──────────┘ + │ + ▼ +┌─────────────────────┐ +│ Yosys Synthesis │ ← Generate netlist +└──────────┬──────────┘ + │ + ▼ +┌─────────────────────┐ +│ iSTA Timing Analysis│ ← Static timing analysis +└──────────┬──────────┘ + │ + ▼ +┌─────────────────────┐ +│ Timing Reports │ ← Extract critical paths +└─────────────────────┘ +``` + +## License + +This tool uses: +- Yosys (https://github.com/YosysHQ/yosys) +- iEDA/iSTA (https://github.com/OSCC-Project/iEDA) + +Please refer to their respective licenses. + +## Contributing + +Contributions are welcome! Please ensure: +- All scripts use POSIX-compatible bash +- Python scripts follow PEP 8 style guidelines +- Comments and documentation are in English + +## Contact + +For issues or questions, please refer to the project documentation or create an issue in the project repository. diff --git a/scripts/ieda/extract_timing_report.sh b/scripts/ieda/extract_timing_report.sh new file mode 100644 index 000000000..61816b009 --- /dev/null +++ b/scripts/ieda/extract_timing_report.sh @@ -0,0 +1,315 @@ +#!/bin/bash +#*************************************************************************************** +# Copyright (c) 2024 Beijing Institute of Open Source Chip (BOSC) +# +# DiffTest is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# +# See the Mulan PSL v2 for more details. +#*************************************************************************************** + +############################################################################### +# Extract Critical Path Information from Timing Reports +# Compatible with iEDA/iSTA generated timing report format +############################################################################### + +# Color definitions +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +MAGENTA='\033[0;35m' +NC='\033[0m' # No Color + +print_usage() { + echo "Usage: $0 " + echo "" + echo "Parameters:" + echo " timing_report_file - iEDA generated timing report file (.rpt)" + echo "" + echo "Example:" + echo " $0 sta_results/GatewayEndpoint.rpt" + echo "" + exit 1 +} + +# Check parameters +if [ $# -lt 1 ]; then + echo -e "${RED}Error: Missing timing report file parameter${NC}" + print_usage +fi + +RPT_FILE="$1" + +# Check if file exists +if [ ! -f "$RPT_FILE" ]; then + echo -e "${RED}Error: File not found: $RPT_FILE${NC}" + exit 1 +fi + +echo -e "${GREEN}========================================${NC}" +echo -e "${GREEN} Timing Report Critical Path Analysis${NC}" +echo -e "${GREEN}========================================${NC}" +echo -e "Report file: ${CYAN}$RPT_FILE${NC}" +echo "" + +# 1. Extract TNS (Total Negative Slack) +echo -e "${YELLOW}===========================================${NC}" +echo -e "${YELLOW}1. Timing Violation Summary (TNS)${NC}" +echo -e "${YELLOW}===========================================${NC}" + +# Find TNS table +echo -e "${CYAN}--- TNS (Total Negative Slack) ---${NC}" +awk ' +/^\|\s+Clock\s+\|\s+Delay Type\s+\|\s+TNS/ { + in_tns = 1 + next +} +in_tns && /^\+/ { + next +} +in_tns && /^\|/ { + print $0 + if (++count >= 10) exit +} +in_tns && /^$/ { + exit +} +' "$RPT_FILE" + +echo "" + +# 2. Extract Top Critical Paths (Setup - max) +echo -e "${YELLOW}===========================================${NC}" +echo -e "${YELLOW}2. Setup Critical Paths (Top 10)${NC}" +echo -e "${YELLOW}===========================================${NC}" + +# Find Setup (max) paths and sort by Slack, take top 10 +echo -e "${CYAN}--- Worst Setup Slack (Critical Paths) ---${NC}" +awk ' +BEGIN { in_table = 0; count = 0 } +/^\| Endpoint.*\| Clock Group.*\| Delay Type.*\| Path Delay.*\| Path Required.*\| CPPR.*\| Slack.*\| Freq/ { + in_table = 1 + print $0 + next +} +in_table && /^\+/ { + next +} +in_table && /^\|/ { + # Check if it is max type (Setup) + for (i = 1; i <= NF; i++) { + if ($i == "max") { + print $0 + count++ + break + } + } + if (count >= 10) exit +} +in_table && /^$/ { + exit +} +' "$RPT_FILE" | head -15 + +echo "" + +# 3. Extract Hold Critical Paths (min) +echo -e "${YELLOW}===========================================${NC}" +echo -e "${YELLOW}3. Hold Critical Paths (Top 10)${NC}" +echo -e "${YELLOW}===========================================${NC}" + +echo -e "${CYAN}--- Worst Hold Slack (Critical Paths) ---${NC}" +awk ' +BEGIN { in_table = 0; count = 0 } +/^\| Endpoint.*\| Clock Group.*\| Delay Type.*\| Path Delay.*\| Path Required.*\| CPPR.*\| Slack.*\| Freq/ { + in_table = 1 + next +} +in_table && /^\+/ { + next +} +in_table && /^\|/ { + # Check if it is min type (Hold) + for (i = 1; i <= NF; i++) { + if ($i == "min") { + print $0 + count++ + break + } + } + if (count >= 10) exit +} +in_table && /^$/ { + exit +} +' "$RPT_FILE" | head -15 + +echo "" + +# 4. Extract worst path detailed timing +echo -e "${YELLOW}===========================================${NC}" +echo -e "${YELLOW}4. Worst Setup Path Detailed Timing${NC}" +echo -e "${YELLOW}===========================================${NC}" + +# Extract detailed timing for first max path +awk ' +BEGIN { in_path = 0; count = 0; max_lines = 50 } +/Point.*Fanout.*Capacitance.*Resistance.*Transition.*Delta Delay.*Incr.*Path/ { + in_path = 1 + print $0 + next +} +in_path && /^\+/ { + next +} +in_path && /^\|/ { + print $0 + count++ + if (count >= max_lines) exit +} +in_path && /^$/ { + if (count > 5) exit +} +' "$RPT_FILE" | head -60 + +echo "" + +# 5. Statistics +echo -e "${YELLOW}===========================================${NC}" +echo -e "${YELLOW}5. Path Statistics${NC}" +echo -e "${YELLOW}===========================================${NC}" + +# Count max type paths +MAX_COUNT=$(awk ' +BEGIN { in_table = 0; count = 0 } +/^\| Endpoint.*\| Clock Group.*\| Delay Type.*\| Path Delay.*\| Path Required.*\| CPPR.*\| Slack.*\| Freq/ { + in_table = 1 + next +} +in_table && /^\+/ { + next +} +in_table && /^\|/ { + for (i = 1; i <= NF; i++) { + if ($i == "max") { + count++ + break + } + } +} +in_table && /^$/ { + exit +} +END { print count } +' "$RPT_FILE") + +# Count min type paths +MIN_COUNT=$(awk ' +BEGIN { in_table = 0; count = 0 } +/^\| Endpoint.*\| Clock Group.*\| Delay Type.*\| Path Delay.*\| Path Required.*\| CPPR.*\| Slack.*\| Freq/ { + in_table = 1 + next +} +in_table && /^\+/ { + next +} +in_table && /^\|/ { + for (i = 1; i <= NF; i++) { + if ($i == "min") { + count++ + break + } + } +} +in_table && /^$/ { + exit +} +END { print count } +' "$RPT_FILE") + +echo -e "${CYAN}Setup (max) path count: ${MAX_COUNT}${NC}" +echo -e "${CYAN}Hold (min) path count: ${MIN_COUNT}${NC}" + +# Check for negative Slack +VIOLATE_MAX=$(awk ' +BEGIN { in_table = 0; count = 0 } +/^\| Endpoint.*\| Clock Group.*\| Delay Type.*\| Path Delay.*\| Path Required.*\| CPPR.*\| Slack.*\| Freq/ { + in_table = 1 + next +} +in_table && /^\+/ { + next +} +in_table && /^\|/ { + for (i = 1; i <= NF; i++) { + if ($i == "max") { + # Find Slack column value (second to last column) + slack = $(NF-1) + # Remove possible pipe characters + gsub(/\|/, "", slack) + # Check if negative + if (slack + 0 < 0) { + count++ + } + break + } + } +} +in_table && /^$/ { + exit +} +END { print count } +' "$RPT_FILE") + +VIOLATE_MIN=$(awk ' +BEGIN { in_table = 0; count = 0 } +/^\| Endpoint.*\| Clock Group.*\| Delay Type.*\| Path Delay.*\| Path Required.*\| CPPR.*\| Slack.*\| Freq/ { + in_table = 1 + next +} +in_table && /^\+/ { + next +} +in_table && /^\|/ { + for (i = 1; i <= NF; i++) { + if ($i == "min") { + slack = $(NF-1) + gsub(/\|/, "", slack) + if (slack + 0 < 0) { + count++ + } + break + } + } +} +in_table && /^$/ { + exit +} +END { print count } +' "$RPT_FILE") + +echo "" +if [ "$VIOLATE_MAX" -gt 0 ]; then + echo -e "${RED}⚠️ Setup violation paths: ${VIOLATE_MAX}${NC}" +else + echo -e "${GREEN}✓ Setup timing met (no violations)${NC}" +fi + +if [ "$VIOLATE_MIN" -gt 0 ]; then + echo -e "${RED}⚠️ Hold violation paths: ${VIOLATE_MIN}${NC}" +else + echo -e "${GREEN}✓ Hold timing met (no violations)${NC}" +fi + +echo "" +echo -e "${GREEN}========================================${NC}" +echo -e "${GREEN} Analysis Complete${NC}" +echo -e "${GREEN}========================================${NC}" diff --git a/scripts/ieda/run_sta.sh b/scripts/ieda/run_sta.sh new file mode 100644 index 000000000..8999c6b02 --- /dev/null +++ b/scripts/ieda/run_sta.sh @@ -0,0 +1,557 @@ +#!/bin/bash +#*************************************************************************************** +# Copyright (c) 2024 Beijing Institute of Open Source Chip (BOSC) +# +# DiffTest is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# +# See the Mulan PSL v2 for more details. +#*************************************************************************************** + +############################################################################### +# run_sta.sh - RTL Static Timing Analysis Script (using yosys-sta toolchain) +# +# Usage: ./run_sta.sh [top_module_name] +# +# Parameters: +# RTL_directory_path - Directory containing RTL design files +# top_module_name - (Optional) Top module name of the design, auto-detected if not provided +# +# Environment Variables: +# YOSYS_STA_DIR - Path to yosys-sta installation directory (required if not in ./yosys-sta) +# CLK_FREQ_MHZ - Clock frequency (MHz), default 500 +# CLK_PORT_NAME - Clock port name, default clock +# SDC_FILE - SDC constraints file path +# PDK - Process design kit, default nangate45 +# +# Examples: +# # Use yosys-sta from environment variable +# export YOSYS_STA_DIR=/path/to/yosys-sta +# ./run_sta.sh /path/to/rtl/files my_top_module +# +# # Or specify inline +# YOSYS_STA_DIR=/path/to/yosys-sta CLK_FREQ_MHZ=1000 ./run_sta.sh /path/to/rtl/files +############################################################################### + +set -e + +# Color output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Print help information +print_help() { + echo "Usage: $0 [top_module_name]" + echo "" + echo "Parameters:" + echo " RTL_directory_path - Directory containing RTL design files (required)" + echo " Supported file types: .v, .vh, .sv, .svh" + echo " top_module_name - Top module name of the design (optional)" + echo "" + echo "Environment Variables:" + echo " YOSYS_STA_DIR - Path to yosys-sta installation (required if not in script dir)" + echo " Default: ./yosys-sta" + echo " CLK_FREQ_MHZ - Clock frequency (MHz), default 500" + echo " CLK_PORT_NAME - Clock port name, default clock" + echo " SDC_FILE - SDC constraints file path" + echo " PDK - Process design kit, default nangate45" + echo "" + echo "Notes:" + echo " - .v, .vh, .sv files will be processed as design files" + echo " - .svh files will be automatically added to include path" + echo " - Test directories are automatically excluded (test, sim, tb, verification, bench)" + echo "" + echo "Examples:" + echo " # Use default yosys-sta location (./yosys-sta)" + echo " $0 /path/to/rtl/files" + echo "" + echo " # Specify yosys-sta location via environment variable" + echo " export YOSYS_STA_DIR=/path/to/yosys-sta" + echo " $0 /path/to/rtl/files my_top_module" + echo "" + echo " # Specify top module and clock settings" + echo " CLK_FREQ_MHZ=1000 CLK_PORT_NAME=core_clk $0 /path/to/rtl/files" + exit 0 +} + +# Check parameters +if [ $# -lt 1 ]; then + echo -e "${RED}Error: Missing RTL directory path parameter${NC}" + print_help +fi + +# Default excluded directories (testbench, simulation code, etc.) +EXCLUDE_DIRS="test|sim|tb|verification|bench" + +# Get script directory +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +RTL_DIR="$1" +TOP_MODULE="${2:-}" + +# Set yosys-sta directory from environment variable or use default +# User can set YOSYS_STA_DIR environment variable to point to yosys-sta installation +if [ -z "$YOSYS_STA_DIR" ]; then + YOSYS_STA_DIR="${SCRIPT_DIR}/yosys-sta" +fi + +# Set environment variables +export CLK_FREQ_MHZ="${CLK_FREQ_MHZ:-500}" +export CLK_PORT_NAME="${CLK_PORT_NAME:-clock}" + +# Set PDK +PDK="${PDK:-nangate45}" + +# Set result directory (fixed location, no timestamp) +RESULT_DIR="${SCRIPT_DIR}/sta_results" + +# Create result directory early so run log can be placed in it. +mkdir -p "${RESULT_DIR}" + +# Run-level logging controls. +# RUN_STA_LOG_TO_FILE=1 : save all script output to RUN_STA_LOG_FILE (default on) +# RUN_STA_LOG_CONSOLE=1 : keep printing to console while saving to file (default on) +# RUN_STA_LOG_CONSOLE=0 : write only to file (useful for CI to avoid log streaming issues) +RUN_STA_LOG_TO_FILE="${RUN_STA_LOG_TO_FILE:-1}" +RUN_STA_LOG_CONSOLE="${RUN_STA_LOG_CONSOLE:-1}" +RUN_STA_LOG_FILE="${RUN_STA_LOG_FILE:-${RESULT_DIR}/run_sta.log}" +RUN_STA_STATUS_FILE="${RUN_STA_STATUS_FILE:-${RESULT_DIR}/run_sta.status}" +# Timeout controls. Set to 0 to disable timeout for one stage. +YOSYS_TIMEOUT="${YOSYS_TIMEOUT:-12h}" +ISTA_TIMEOUT="${ISTA_TIMEOUT:-24h}" +TIMEOUT_KILL_AFTER="${TIMEOUT_KILL_AFTER:-10m}" + +# Resolve log/status paths to absolute paths because cwd changes during flow. +if [[ "${RUN_STA_LOG_FILE}" != /* ]]; then + RUN_STA_LOG_FILE="$(pwd)/${RUN_STA_LOG_FILE}" +fi +if [[ "${RUN_STA_STATUS_FILE}" != /* ]]; then + RUN_STA_STATUS_FILE="$(pwd)/${RUN_STA_STATUS_FILE}" +fi +mkdir -p "$(dirname "${RUN_STA_LOG_FILE}")" "$(dirname "${RUN_STA_STATUS_FILE}")" + +if [ "${RUN_STA_LOG_TO_FILE}" = "1" ]; then + if [ "${RUN_STA_LOG_CONSOLE}" = "1" ]; then + exec > >(tee -a "${RUN_STA_LOG_FILE}") 2>&1 + else + exec >> "${RUN_STA_LOG_FILE}" 2>&1 + fi +fi + +status_mark() { + printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S %z')" "$1" >> "${RUN_STA_STATUS_FILE}" 2>/dev/null || true +} + +: > "${RUN_STA_STATUS_FILE}" 2>/dev/null || true +status_mark "START rtl=${RTL_DIR} top=${TOP_MODULE:-AUTO}" + +on_exit() { + local rc=$? + status_mark "EXIT rc=${rc}" +} +trap on_exit EXIT + +on_signal() { + local sig="$1" + local rc="$2" + status_mark "SIGNAL ${sig} rc=${rc}" + exit "${rc}" +} +trap 'on_signal TERM 143' TERM +trap 'on_signal INT 130' INT +trap 'on_signal HUP 129' HUP +trap 'on_signal QUIT 131' QUIT + +# Set ABC threads +ABC_THREADS="${ABC_THREADS:-16}" +[ "$STA_VERBOSE" = "1" ] && echo "ABC threads: ${ABC_THREADS}" + +# Set synthesized netlist path +NETLIST_SYN_V="${RESULT_DIR}/${TOP_MODULE}.netlist.v" + +echo -e "${GREEN}========================================${NC}" +echo -e "${GREEN} RTL Timing Analysis (using yosys-sta)${NC}" +echo -e "${GREEN}========================================${NC}" +echo "yosys-sta directory: ${YOSYS_STA_DIR}" +echo "RTL directory: ${RTL_DIR}" +echo "Top module: ${TOP_MODULE}" +echo "Clock frequency: ${CLK_FREQ_MHZ} MHz" +echo "Clock port: ${CLK_PORT_NAME}" +echo "PDK: ${PDK}" +echo "Result directory: ${RESULT_DIR}" +echo "Yosys timeout: ${YOSYS_TIMEOUT}" +echo "iSTA timeout: ${ISTA_TIMEOUT}" +echo "" + +# Check yosys-sta directory +if [ ! -d "${YOSYS_STA_DIR}" ]; then + echo -e "${RED}Error: yosys-sta directory not found: ${YOSYS_STA_DIR}${NC}" + echo -e "${YELLOW}Please set YOSYS_STA_DIR environment variable to point to yosys-sta installation${NC}" + echo -e "${YELLOW}Example: export YOSYS_STA_DIR=/path/to/yosys-sta${NC}" + exit 1 +fi + +# Check iEDA executable +if [ ! -f "${YOSYS_STA_DIR}/bin/iEDA" ]; then + echo -e "${RED}Error: iEDA executable not found: ${YOSYS_STA_DIR}/bin/iEDA${NC}" + exit 1 +fi + +# Check yosys +if ! command -v yosys &> /dev/null; then + echo -e "${RED}Error: yosys command not found${NC}" + echo "Please install yosys (version >= 0.48) and add to PATH" + exit 1 +fi + +# Check PDK files +PDK_DIR="${YOSYS_STA_DIR}/pdk/${PDK}" +if [ ! -d "${PDK_DIR}" ]; then + echo -e "${RED}Error: PDK directory not found: ${PDK_DIR}${NC}" + exit 1 +fi + +# Collect RTL files +echo -e "${YELLOW}Collecting RTL files...${NC}" +echo -e "${YELLOW}Search path: ${RTL_DIR}${NC}" +RTL_FILES="" + +if [ -d "${RTL_DIR}" ]; then + # Find all Verilog/SystemVerilog design files (exclude svh headers and test directories) + # Use find -prune option to properly exclude test directories + RTL_FILES=$(find "${RTL_DIR}" \ + -type d \( -name test -o -name sim -o -name tb -o -name verification -o -name bench \) -prune -o \ + -type f \( -name "*.v" -o -name "*.vh" -o -name "*.sv" \) \ + ! -name "*.svh" -print 2>/dev/null | tr '\n' ' ') + + # Display file count + FILE_COUNT=$(echo "$RTL_FILES" | wc -w) + echo -e "${YELLOW}Found ${FILE_COUNT} RTL files${NC}" + + # Check and remove duplicate file names (keep first occurrence only) + echo -e "${YELLOW}Checking for duplicate files...${NC}" + SEEN_FILES="" + UNIQUE_FILES="" + DUPLICATE_COUNT=0 + + for file in $RTL_FILES; do + basename=$(basename "$file") + if echo "$SEEN_FILES" | grep -qF "$basename"; then + echo -e "${YELLOW} Skipping duplicate file: $basename ($(basename $(dirname $file)))${NC}" + DUPLICATE_COUNT=$((DUPLICATE_COUNT + 1)) + else + UNIQUE_FILES="$UNIQUE_FILES $file" + SEEN_FILES="$SEEN_FILES $basename" + fi + done + + RTL_FILES="$UNIQUE_FILES" + + if [ $DUPLICATE_COUNT -gt 0 ]; then + echo -e "${YELLOW}Skipped ${DUPLICATE_COUNT} duplicate files${NC}" + fi + + if [ -z "$RTL_FILES" ]; then + echo -e "${RED}Error: No Verilog files found in RTL directory (.v, .vh, .sv)${NC}" + echo -e "${YELLOW}Note: .svh files are header files and need to be used with .sv files${NC}" + echo -e "${YELLOW}Excluded directories: $EXCLUDE_DIRS${NC}" + exit 1 + fi + + # Save file list to result directory instead of printing + echo -e "${GREEN}Found ${FILE_COUNT} RTL design files (list saved to ${RESULT_DIR}/rtl_files.txt)${NC}" + find "${RTL_DIR}" \ + -type d \( -name test -o -name sim -o -name tb -o -name verification -o -name bench \) -prune -o \ + -type f \( -name "*.v" -o -name "*.vh" -o -name "*.sv" \) \ + ! -name "*.svh" -print 2>/dev/null > "${RESULT_DIR}/rtl_files.txt" +else + # If file is passed directly + RTL_FILES="${RTL_DIR}" +fi + +# Infer top module name (if not provided) +if [ -z "$TOP_MODULE" ]; then + echo -e "${YELLOW}Top module not specified, attempting auto-detection...${NC}" + + # Method 1: Search for module definition + TOP_MODULE=$(grep -h "^module " ${RTL_DIR}/*.v 2>/dev/null | head -1 | sed 's/module //' | sed 's/ .*//' | sed 's/(.*//') + + if [ -z "$TOP_MODULE" ]; then + echo -e "${YELLOW}Warning: Unable to auto-detect top module name${NC}" + TOP_MODULE="top" + fi + echo -e "${GREEN}Using top module name: ${TOP_MODULE}${NC}" +fi + +# Set SDC file +if [ -z "$SDC_FILE" ]; then + SDC_FILE="${YOSYS_STA_DIR}/scripts/default.sdc" +fi + +if [ ! -f "$SDC_FILE" ]; then + echo -e "${RED}Error: SDC file not found: $SDC_FILE${NC}" + exit 1 +fi + +echo -e "${YELLOW}Using SDC file: $SDC_FILE${NC}" + +# Preprocess RTL files (convert incompatible SV syntax) +echo -e "${YELLOW}Preprocessing RTL files...${NC}" +PREPROCESSED_RTL_DIR="${RESULT_DIR}/rtl_preprocessed" +mkdir -p "${PREPROCESSED_RTL_DIR}" + +PREPROCESSOR_SCRIPT="${SCRIPT_DIR}/sv_preprocessor.py" +PREPROCESSED_FILES="" + +for rtl_file in $RTL_FILES; do + basename=$(basename "$rtl_file") + output_file="${PREPROCESSED_RTL_DIR}/${basename}" + + # Use preprocessor to convert file + python3 "${PREPROCESSOR_SCRIPT}" "$rtl_file" "$output_file" + + if [ $? -eq 0 ]; then + PREPROCESSED_FILES="$PREPROCESSED_FILES $output_file" + else + # If preprocessing fails, copy original file + echo -e "${YELLOW} Warning: Preprocessing failed, using original file: $basename${NC}" + cp "$rtl_file" "$output_file" + PREPROCESSED_FILES="$PREPROCESSED_FILES $output_file" + fi +done + +# Use preprocessed file list +RTL_FILES="$PREPROCESSED_FILES" + +# Copy .svh header files to preprocessed directory (Yosys needs them) +echo -e "${YELLOW}Copying header files...${NC}" +SVH_FILES=$(find "${RTL_DIR}" \ + -type d \( -name test -o -name sim -o -name tb -o -name verification -o -name bench \) -prune -o \ + -type f -name "*.svh" -print 2>/dev/null) + +for svh_file in $SVH_FILES; do + cp "$svh_file" "${PREPROCESSED_RTL_DIR}/" +done + +# Display copied header file count +SVH_COUNT=$(echo "$SVH_FILES" | wc -w) +if [ $SVH_COUNT -gt 0 ]; then + echo -e "${YELLOW}Copied ${SVH_COUNT} header files${NC}" +fi + +# Step 1: Run Yosys synthesis using yosys-sta's yosys.tcl +echo -e "${GREEN}========================================${NC}" +echo -e "${YELLOW}Step 1: Running Yosys synthesis...${NC}" +echo -e "${GREEN}========================================${NC}" + +cd "${YOSYS_STA_DIR}" + +# Set Yosys include path (points to preprocessed directory containing .svh headers) +# Use read_verilog options to avoid redefinition issues +YOSYS_INC_CMD="verilog_defaults -add -I${PREPROCESSED_RTL_DIR}" + +status_mark "YOSYS_START timeout=${YOSYS_TIMEOUT}" +set +e +if [ "${YOSYS_TIMEOUT}" != "0" ] && command -v timeout >/dev/null 2>&1; then + echo "${YOSYS_INC_CMD}; tcl scripts/yosys.tcl ${TOP_MODULE} ${PDK} \"${RTL_FILES}\" ${NETLIST_SYN_V}" | \ + timeout --foreground --signal=TERM --kill-after="${TIMEOUT_KILL_AFTER}" "${YOSYS_TIMEOUT}" \ + yosys -g -l "${RESULT_DIR}/yosys.log" -s - +else + echo "${YOSYS_INC_CMD}; tcl scripts/yosys.tcl ${TOP_MODULE} ${PDK} \"${RTL_FILES}\" ${NETLIST_SYN_V}" | \ + yosys -g -l "${RESULT_DIR}/yosys.log" -s - +fi +YOSYS_EXIT_CODE=$? +set -e +status_mark "YOSYS_DONE rc=${YOSYS_EXIT_CODE}" + +if [ ${YOSYS_EXIT_CODE} -eq 0 ]; then + echo -e "${GREEN}Yosys synthesis completed!${NC}" + echo -e "${GREEN}Netlist file: ${NETLIST_SYN_V}${NC}" +elif [ ${YOSYS_EXIT_CODE} -eq 124 ] || [ ${YOSYS_EXIT_CODE} -eq 137 ]; then + echo -e "${RED}Yosys synthesis timed out after ${YOSYS_TIMEOUT}${NC}" + echo -e "${YELLOW}Check log: ${RESULT_DIR}/yosys.log${NC}" + exit 124 +else + echo -e "${RED}Yosys synthesis failed!${NC}" + echo -e "${YELLOW}Exit code: ${YOSYS_EXIT_CODE}${NC}" + echo -e "${YELLOW}Check log: ${RESULT_DIR}/yosys.log${NC}" + exit 1 +fi + +# Step 2: Run iSTA timing analysis +echo "" +echo -e "${GREEN}========================================${NC}" +echo -e "${YELLOW}Step 2: Running iSTA timing analysis...${NC}" +echo -e "${GREEN}========================================${NC}" + +set -o pipefail + +# Run iEDA without pipe to ensure proper exit detection +status_mark "ISTA_START timeout=${ISTA_TIMEOUT}" +set +e +if [ "${ISTA_TIMEOUT}" != "0" ] && command -v timeout >/dev/null 2>&1; then + timeout --foreground --signal=TERM --kill-after="${TIMEOUT_KILL_AFTER}" "${ISTA_TIMEOUT}" \ + ./bin/iEDA -script scripts/sta.tcl "$SDC_FILE" "$NETLIST_SYN_V" "$TOP_MODULE" "$PDK" > "${RESULT_DIR}/sta.log" 2>&1 +else + ./bin/iEDA -script scripts/sta.tcl "$SDC_FILE" "$NETLIST_SYN_V" "$TOP_MODULE" "$PDK" > "${RESULT_DIR}/sta.log" 2>&1 +fi + +STA_EXIT_CODE=$? +set -e +status_mark "ISTA_DONE rc=${STA_EXIT_CODE}" +if [ $STA_EXIT_CODE -eq 0 ]; then + echo -e "${GREEN}Timing analysis completed!${NC}" +elif [ $STA_EXIT_CODE -eq 124 ] || [ $STA_EXIT_CODE -eq 137 ]; then + echo -e "${RED}iSTA timing analysis timed out after ${ISTA_TIMEOUT}${NC}" + echo -e "${YELLOW}Check log: ${RESULT_DIR}/sta.log${NC}" + exit 124 +else + echo -e "${YELLOW}Timing analysis completed with warnings (exit code: ${STA_EXIT_CODE})${NC}" +fi + +# Debug: List files generated by STA +echo -e "${YELLOW}Files in ${RESULT_DIR} after STA:${NC}" +ls -la "${RESULT_DIR}" | grep -E '\.(rpt|log|txt)$' || echo "No report files found" + +# Step 3: Generate summary report +echo "" +echo -e "${GREEN}========================================${NC}" +echo -e "${YELLOW}Generating summary report...${NC}" +echo -e "${GREEN}========================================${NC}" + +SUMMARY="${RESULT_DIR}/sta_summary.txt" +cat > "${SUMMARY}" << EOF +======================================== + Timing Analysis Summary Report +======================================== + +Analysis time: $(date) +RTL directory: ${RTL_DIR} +Top module: ${TOP_MODULE} +PDK: ${PDK} +Clock frequency: ${CLK_FREQ_MHZ} MHz +Clock port: ${CLK_PORT_NAME} + +---------------------------------------- +Synthesis Results: +---------------------------------------- + +EOF + +# Add synthesis statistics +if [ -f "${RESULT_DIR}/synth_stat.txt" ]; then + echo "" >> "${SUMMARY}" + echo "Synthesis Statistics:" >> "${SUMMARY}" + echo "----------------------------------------" >> "${SUMMARY}" + cat "${RESULT_DIR}/synth_stat.txt" >> "${SUMMARY}" +fi + +# Add timing information +if [ -f "${RESULT_DIR}/${TOP_MODULE}.rpt" ]; then + echo "" >> "${SUMMARY}" + echo "Timing Analysis Report:" >> "${SUMMARY}" + echo "----------------------------------------" >> "${SUMMARY}" + head -50 "${RESULT_DIR}/${TOP_MODULE}.rpt" >> "${SUMMARY}" +fi + +# List generated files +echo "" >> "${SUMMARY}" +echo "----------------------------------------" >> "${SUMMARY}" +echo "Generated Files:" >> "${SUMMARY}" +echo "----------------------------------------" >> "${SUMMARY}" +ls -lh "${RESULT_DIR}" >> "${SUMMARY}" + +echo -e "${GREEN}Timing analysis completed!${NC}" +echo "" +echo -e "${YELLOW}Results saved in: ${RESULT_DIR}${NC}" +echo "" +echo -e "${YELLOW}Main report files:${NC}" +echo " - ${RESULT_DIR}/sta_summary.txt - Summary report" +echo " - ${RESULT_DIR}/${TOP_MODULE}.netlist.v - Synthesized netlist" +echo " - ${RESULT_DIR}/${TOP_MODULE}.rpt - Timing analysis report" +echo " - ${RESULT_DIR}/synth_stat.txt - Synthesis statistics" +echo " - ${RESULT_DIR}/yosys.log - Yosys log" +echo " - ${RESULT_DIR}/sta.log - iSTA log" +echo "" + +# Display summary +if [ -f "${SUMMARY}" ]; then + echo -e "${YELLOW}===== Timing Analysis Summary =====${NC}" + cat "${SUMMARY}" +fi + +# Step 4: Extract critical path information +echo "" +echo -e "${GREEN}========================================${NC}" +echo -e "${YELLOW}Extracting Critical Path Information...${NC}" +echo -e "${GREEN}========================================${NC}" +status_mark "EXTRACT_START" + +CRITICAL_PATH_REPORT="${RESULT_DIR}/critical_paths.txt" +EXTRACTOR_SCRIPT="${SCRIPT_DIR}/extract_timing_report.sh" +RPT_FILE="${RESULT_DIR}/${TOP_MODULE}.rpt" + +# Check if required files exist +if [ ! -f "${EXTRACTOR_SCRIPT}" ]; then + echo -e "${RED}Error: Extractor script not found: ${EXTRACTOR_SCRIPT}${NC}" + exit 1 +fi + +if [ ! -f "${RPT_FILE}" ]; then + echo -e "${RED}Error: Timing report not found: ${RPT_FILE}${NC}" + echo -e "${RED}Available .rpt files in ${RESULT_DIR}:${NC}" + ls -la "${RESULT_DIR}"/*.rpt 2>/dev/null || echo " (No .rpt files found)" + echo -e "${RED}All files in ${RESULT_DIR}:${NC}" + ls -la "${RESULT_DIR}" + exit 1 +fi + +# Extract critical path information +echo -e "${YELLOW}Running timing report extraction...${NC}" +bash "${EXTRACTOR_SCRIPT}" "${RPT_FILE}" > "${CRITICAL_PATH_REPORT}" + +if [ $? -eq 0 ]; then + echo -e "${GREEN}Critical path extraction completed!${NC}" + echo -e "${GREEN}Report saved to: ${CRITICAL_PATH_REPORT}${NC}" + echo "" + echo -e "${YELLOW}===== Critical Path Analysis =====${NC}" + cat "${CRITICAL_PATH_REPORT}" +else + echo -e "${RED}Error: Critical path extraction failed${NC}" + exit 1 +fi +status_mark "EXTRACT_DONE" + +# Append area/utilization information to critical_paths.txt +if [ -f "${RESULT_DIR}/synth_stat.txt" ]; then + echo "" >> "${CRITICAL_PATH_REPORT}" + echo "========================================" >> "${CRITICAL_PATH_REPORT}" + echo "Area/Utilization Statistics" >> "${CRITICAL_PATH_REPORT}" + echo "========================================" >> "${CRITICAL_PATH_REPORT}" + cat "${RESULT_DIR}/synth_stat.txt" >> "${CRITICAL_PATH_REPORT}" + echo -e "${GREEN}Area statistics appended to critical_paths.txt${NC}" +fi + +echo "" +echo -e "${GREEN}========================================${NC}" +echo -e "${GREEN}Analysis Complete!${NC}" +echo -e "${GREEN}========================================${NC}" +echo "" +echo -e "${YELLOW}All report files:${NC}" +echo " - ${RESULT_DIR}/sta_summary.txt - Summary report" +echo " - ${RESULT_DIR}/critical_paths.txt - Critical path analysis" +echo " - ${RESULT_DIR}/${TOP_MODULE}.netlist.v - Synthesized netlist" +echo " - ${RESULT_DIR}/${TOP_MODULE}.rpt - Timing analysis report" +echo " - ${RESULT_DIR}/synth_stat.txt - Synthesis statistics" +echo " - ${RESULT_DIR}/yosys.log - Yosys log" +echo " - ${RESULT_DIR}/sta.log - iSTA log" +echo " - ${RUN_STA_STATUS_FILE} - run status markers" +echo "" +status_mark "ALL_DONE" diff --git a/scripts/ieda/sv_preprocessor.py b/scripts/ieda/sv_preprocessor.py new file mode 100644 index 000000000..cdac4c540 --- /dev/null +++ b/scripts/ieda/sv_preprocessor.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python3 +#*************************************************************************************** +# Copyright (c) 2024 Beijing Institute of Open Source Chip (BOSC) +# +# DiffTest is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# +# See the Mulan PSL v2 for more details. +#*************************************************************************************** +""" +SystemVerilog Preprocessor - Convert Yosys-incompatible syntax + +Main processing: +1. string type declarations - Comment out +2. automatic keyword - Remove +3. Assignment pattern syntax '{...} - Convert to compatible format +""" + +import re +import sys +import os + +def preprocess_automatic_keyword(content): + """ + Remove automatic keyword (not supported by Yosys) + + Original: automatic logic func_name(); + Converted: logic func_name(); + + Original: function automatic void func_name(); + Converted: function void func_name(); + """ + # Remove automatic keyword (followed by whitespace) + # Match automatic followed by space or tab + content = re.sub(r'\bautomatic\b\s+', '', content) + + return content + +def preprocess_string_type(content): + """ + Comment out string type declarations (not supported by Yosys) + + Original: string var_name; + Converted: // string var_name; // Commented out for Yosys compatibility + """ + # Match string type declarations + pattern = r'^(\s*)string\s+(\w+)\s*(?:\[[^\]]+\])?\s*(?:=\s*[^;]+)?;' + + def replace_func(match): + indent = match.group(1) + rest = match.group(0) + return f"{indent}// {rest} // Commented out for Yosys compatibility\n" + + return re.sub(pattern, replace_func, content, flags=re.MULTILINE) + +def parse_assignment_pattern_values(values_str): + """ + Intelligently parse assignment pattern value list, correctly handling nested expressions + Example: "4'h0, 4'h0, in_0_valid ? 9'h101 : 9'h0" should return 3 values + """ + values = [] + current = [] + depth = 0 # Bracket nesting depth + i = 0 + + while i < len(values_str): + char = values_str[i] + + if char == '{' or char == '(': + depth += 1 + current.append(char) + elif char == '}' or char == ')': + depth -= 1 + current.append(char) + elif char == ',' and depth == 0: + # Only split on comma at top level + values.append(''.join(current).strip()) + current = [] + else: + current.append(char) + + i += 1 + + # Append the last value + if current: + values.append(''.join(current).strip()) + + return values + +def preprocess_assignment_pattern(content): + """ + Convert assignment pattern syntax '{...} to compatible format + + Original: wire [15:0][3:0] _GEN = '{4'h0, 4'h0, ...}; + Converted: wire [15:0][3:0] _GEN; + assign _GEN[0] = 4'h0; + assign _GEN[1] = 4'h0; + ... + """ + # Match assignment pattern: type [msb:lsb] name = '{values}; + # Note: Match is for apostrophe '{ not regular brace { + pattern = r'(\w+)\s*((?:\[[^\]]+\](?:\s*\[[^\]]+\])?)*)\s*(\w+)\s*=\s*\'\{\s*\n(.*?)\s*\}\s*;' + + def replace_func(match): + data_type = match.group(1) + dimensions = match.group(2) + name = match.group(3) + values_str = match.group(4) + + # Intelligently parse value list + values = parse_assignment_pattern_values(values_str) + + # Generate assignment statements + result = f"{data_type} {dimensions} {name};\n" + for i, val in enumerate(values): + if dimensions and val: # Use index only when dimensions exist and value is present + result += f" assign {name}[{i}] = {val};\n" + elif val: + result += f" assign {name} = {val};\n" + + return result + + return re.sub(pattern, replace_func, content, flags=re.DOTALL) + +def preprocess_file(input_file, output_file): + """Preprocess a single file""" + try: + with open(input_file, 'r', encoding='utf-8') as f: + content = f.read() + + # Remove automatic keyword + content = preprocess_automatic_keyword(content) + + # Comment out string type declarations + content = preprocess_string_type(content) + + # Convert assignment patterns + content = preprocess_assignment_pattern(content) + + # Write output file + with open(output_file, 'w', encoding='utf-8') as f: + f.write(content) + + return True, None + except Exception as e: + return False, str(e) + +def main(): + if len(sys.argv) < 3: + print("Usage: sv_preprocessor.py ") + sys.exit(1) + + input_file = sys.argv[1] + output_file = sys.argv[2] + + # Create output directory + os.makedirs(os.path.dirname(output_file), exist_ok=True) + + success, error = preprocess_file(input_file, output_file) + + if success: + print(f"[OK] {input_file} -> {output_file}") + sys.exit(0) + else: + print(f"[ERROR] {input_file}: {error}", file=sys.stderr) + sys.exit(1) + +if __name__ == "__main__": + main()