diff --git a/batch_synth_simplified.sh b/batch_synth_simplified.sh new file mode 100755 index 0000000..6481298 --- /dev/null +++ b/batch_synth_simplified.sh @@ -0,0 +1,615 @@ +#!/bin/bash +# Batch synthesis script - run multiple DSL files in parallel +# Usage: ./batch_synth.sh [OPTIONS] ... + +set -e + +# Default values +MAX_PARALLEL=4 +BASE_OUTPUT_DIR="output" +EXTRA_ARGS="" +USE_GNU_PARALLEL=false +VERBOSE=false +RUNS_PER_DSL=1 +HAS_GATES_FLAG=false +HAS_ODC_FLAG=false +CLEAN_OUTPUT=false + +# Color codes for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Parse command line arguments +while [[ "$#" -gt 0 ]]; do + case $1 in + -j|--jobs) + MAX_PARALLEL="$2" + shift 2 + ;; + -o|--output-dir|--output) + BASE_OUTPUT_DIR="$2" + shift 2 + ;; + --gates) + EXTRA_ARGS="$EXTRA_ARGS --gates" + HAS_GATES_FLAG=true + shift + ;; + --3stage) + EXTRA_ARGS="$EXTRA_ARGS --3stage" + shift + ;; + --abc-depth) + EXTRA_ARGS="$EXTRA_ARGS --abc-depth $2" + shift 2 + ;; + --config) + EXTRA_ARGS="$EXTRA_ARGS --config $2" + shift 2 + ;; + --core) + EXTRA_ARGS="$EXTRA_ARGS --core $2" + shift 2 + ;; + --odc-analysis) + EXTRA_ARGS="$EXTRA_ARGS --odc-analysis" + HAS_ODC_FLAG=true + shift + ;; + --clean) + CLEAN_OUTPUT=true + shift + ;; + --gnu-parallel) + USE_GNU_PARALLEL=true + shift + ;; + --runs) + RUNS_PER_DSL="$2" + shift 2 + ;; + -v|--verbose) + VERBOSE=true + shift + ;; + -h|--help) + echo "Usage: $0 [OPTIONS] ..." + echo "" + echo "Run multiple DSL synthesis jobs in parallel" + echo "" + echo "Options:" + echo " -j, --jobs N Maximum parallel jobs (default: 4)" + echo " -o, --output-dir, --output DIR Base output directory (default: output)" + echo " --runs N Number of runs per DSL file (default: 1)" + echo " --gates Pass --gates to synthesis script" + echo " --3stage Pass --3stage to synthesis script" + echo " --abc-depth N Pass --abc-depth N to synthesis script" + echo " --config FILE Pass --config FILE to synthesis script (config mode)" + echo " --core NAME Pass --core NAME to synthesis script (auto-config)" + echo " --odc-analysis Run ODC analysis after synthesis for each DSL" + echo " --clean Clean output directory before starting" + echo " --gnu-parallel Use GNU parallel if available (faster)" + echo " -v, --verbose Show detailed output from each job" + echo " -h, --help Show this help message" + echo "" + echo "Arguments:" + echo " Can be DSL files, directories containing DSL files, or wildcards" + echo "" + echo "Examples:" + echo " $0 ../PdatDsl/examples/ # Process all DSL files in directory" + echo " $0 -j 8 dir1/ dir2/ file.dsl # Mix directories and files" + echo " $0 --gates -j 4 ../PdatDsl/examples/ # All files with gates" + echo " $0 rules/*.dsl # Wildcard expansion" + echo " $0 -j 8 test1.dsl test2.dsl # Specific files" + echo " $0 --config configs/ibex.yaml rules/*.dsl # Use config file" + echo " $0 --odc-analysis -j 8 rules/*.dsl # Run ODC analysis on all files" + echo "" + echo "Each DSL file will be processed to its own subfolder:" + echo " test.dsl → output/test/" + exit 0 + ;; + *) + break + ;; + esac +done + +# Process remaining arguments - can be DSL files or directories +RAW_ARGS=("$@") +DSL_FILES=() + +# Expand directories to DSL files +for arg in "${RAW_ARGS[@]}"; do + if [ -d "$arg" ]; then + # It's a directory - find all .dsl files in it + echo "Scanning directory: $arg" + while IFS= read -r -d '' file; do + DSL_FILES+=("$file") + done < <(find "$arg" -maxdepth 1 -name "*.dsl" -type f -print0 | sort -z) + + # Report what we found + num_found=$(find "$arg" -maxdepth 1 -name "*.dsl" -type f | wc -l) + echo " Found $num_found DSL files in $arg" + elif [ -f "$arg" ]; then + # It's a file - add it directly + DSL_FILES+=("$arg") + else + echo -e "${YELLOW}Warning: '$arg' is not a file or directory, skipping${NC}" + fi +done + +if [ ${#DSL_FILES[@]} -eq 0 ]; then + echo -e "${RED}Error: No DSL files found${NC}" + echo "Run with -h for help" + exit 1 +fi + +# Check if synthesis script exists +if [ ! -f "./synth_core_simplified.sh" ]; then + echo -e "${RED}Error: synth_core_simplified.sh not found in current directory${NC}" + exit 1 +fi + +# Check if GNU parallel is available and requested +if [ "$USE_GNU_PARALLEL" = true ] && command -v parallel &> /dev/null; then + echo -e "${GREEN}Using GNU parallel for job management${NC}" + USE_GNU_PARALLEL=true +else + USE_GNU_PARALLEL=false +fi + +echo "==========================================" +echo "Batch Synthesis Configuration" +echo "==========================================" +echo "DSL files: ${#DSL_FILES[@]} files" +echo "Runs per DSL: $RUNS_PER_DSL" +echo "Max parallel: $MAX_PARALLEL" +echo "Output directory: $BASE_OUTPUT_DIR" +echo "Extra arguments: $EXTRA_ARGS" +echo "" + +# Create or clean output directory +if [ "$CLEAN_OUTPUT" = true ] && [ -d "$BASE_OUTPUT_DIR" ]; then + echo -e "${YELLOW}Cleaning output directory: $BASE_OUTPUT_DIR${NC}" + rm -rf "$BASE_OUTPUT_DIR" +fi +mkdir -p "$BASE_OUTPUT_DIR" + +# Function to run a single synthesis job +run_synthesis() { + local dsl_file="$1" + local job_num="$2" + local total_jobs="$3" + local run_num="${4:-1}" # Default to run 1 if not specified + + # Get base name for status display + local dsl_basename=$(basename "$dsl_file" .dsl) + + # Determine output directory based on whether we have multiple runs + if [ "$RUNS_PER_DSL" -gt 1 ]; then + # Pass parent directory - synth script will create dsl_basename subdirectory + local parent_dir="$BASE_OUTPUT_DIR/run_${run_num}" + local actual_output_dir="${parent_dir}/${dsl_basename}" + local display_name="${dsl_basename} (run ${run_num})" + else + local parent_dir="$BASE_OUTPUT_DIR" + local actual_output_dir="$BASE_OUTPUT_DIR/${dsl_basename}" + local display_name="${dsl_basename}" + fi + + # Determine log file location (will be created by synth script) + local log_file="${actual_output_dir}/synthesis.log" + + # Create parent and actual output directories + mkdir -p "${actual_output_dir}" + + # Print start message + echo -e "${BLUE}[$job_num/$total_jobs] Starting: ${display_name}${NC}" + + # Run synthesis + local start_time=$(date +%s) + + if [ "$VERBOSE" = true ]; then + # Show output directly + ./synth_core_simplified.sh $EXTRA_ARGS "$dsl_file" "$parent_dir" 2>&1 | tee "$log_file" + local exit_code=${PIPESTATUS[0]} + else + # Redirect to log file + ./synth_core_simplified.sh $EXTRA_ARGS "$dsl_file" "$parent_dir" > "$log_file" 2>&1 + local exit_code=$? + fi + + local end_time=$(date +%s) + local duration=$((end_time - start_time)) + + # Print completion message + if [ $exit_code -eq 0 ]; then + echo -e "${GREEN}[$job_num/$total_jobs] ✓ Completed: ${display_name} (${duration}s)${NC}" + # Show key metrics based on what was run + # Check ODC-optimized results if --odc-analysis was used + if [ "$HAS_ODC_FLAG" = true ] && [ -f "$actual_output_dir/odc_optimized_synthesis/abc.log" ]; then + local stats=$(grep "i/o =" "$actual_output_dir/odc_optimized_synthesis/abc.log" | tail -1) + if [ ! -z "$stats" ]; then + echo " └─ ODC-optimized: $stats" + fi + elif [ -f "$actual_output_dir/ibex_optimized_abc.log" ]; then + local stats=$(grep "i/o =" "$actual_output_dir/ibex_optimized_abc.log" | tail -1) + if [ ! -z "$stats" ]; then + echo " └─ Final: $stats" + fi + fi + + # Store total chip area if --gates was used + if [ "$HAS_GATES_FLAG" = true ]; then + if [ "$HAS_ODC_FLAG" = true ]; then + # Check for ODC-optimized total_area.txt file first + local odc_area_file=$(ls "$actual_output_dir/odc_optimized_synthesis/"*"_optimized_total_area.txt" 2>/dev/null | head -1) + if [ -f "$odc_area_file" ]; then + cat "$odc_area_file" > "$actual_output_dir/area.txt" 2>/dev/null || true + elif [ -f "$actual_output_dir/ibex_optimized_total_area.txt" ]; then + # Fallback to regular file if ODC didn't produce optimized version + cat "$actual_output_dir/ibex_optimized_total_area.txt" > "$actual_output_dir/area.txt" 2>/dev/null || true + fi + else + # Use regular total_area.txt file + if [ -f "$actual_output_dir/ibex_optimized_total_area.txt" ]; then + cat "$actual_output_dir/ibex_optimized_total_area.txt" > "$actual_output_dir/area.txt" 2>/dev/null || true + fi + fi + + # Store frequency if timing metrics available + if [ "$HAS_ODC_FLAG" = true ]; then + local timing_json=$(ls "$actual_output_dir/odc_optimized_synthesis/"*"_timing_metrics.json" 2>/dev/null | head -1) + if [ -f "$timing_json" ]; then + python3 -c "import json; print(json.load(open('$timing_json')).get('max_frequency_mhz', 'N/A'))" 2>/dev/null > "$actual_output_dir/freq.txt" || true + elif [ -f "$actual_output_dir/ibex_optimized_timing_metrics.json" ]; then + # Fallback to regular timing file if ODC didn't produce timing + python3 -c "import json; print(json.load(open('$actual_output_dir/ibex_optimized_timing_metrics.json')).get('max_frequency_mhz', 'N/A'))" 2>/dev/null > "$actual_output_dir/freq.txt" || true + fi + else + if [ -f "$actual_output_dir/ibex_optimized_timing_metrics.json" ]; then + python3 -c "import json; print(json.load(open('$actual_output_dir/ibex_optimized_timing_metrics.json')).get('max_frequency_mhz', 'N/A'))" 2>/dev/null > "$actual_output_dir/freq.txt" || true + fi + fi + fi + else + echo -e "${RED}[$job_num/$total_jobs] ✗ Failed: ${display_name} (${duration}s)${NC}" + echo " └─ Check log: $log_file" + fi + + return $exit_code +} + +# Function to select the best run for a given DSL +select_best_run() { + local dsl_basename="$1" + local best_area=999999999999 + local best_run="" + local best_and_gates=999999999 + + # Only needed if we have multiple runs + if [ "$RUNS_PER_DSL" -eq 1 ]; then + return 0 + fi + + # Find run with minimum area (or AND gates if area not available) + # New structure: output/run_N/dsl_name/ + for run_num in $(seq 1 $RUNS_PER_DSL); do + local run_dir="$BASE_OUTPUT_DIR/run_${run_num}/${dsl_basename}" + + if [ ! -d "$run_dir" ]; then + continue + fi + + # First try to get chip area + if [ -f "$run_dir/area.txt" ] && [ -s "$run_dir/area.txt" ]; then + local area=$(cat "$run_dir/area.txt") + if [ ! -z "$area" ] && (( $(echo "$area < $best_area" | bc -l 2>/dev/null || echo 0) )); then + best_area=$area + best_run="run_${run_num}" + fi + elif [ -f "$run_dir/ibex_optimized_abc.log" ]; then + # Fall back to AND gate count if no area + local and_gates=$(grep "and =" "$run_dir/ibex_optimized_abc.log" | tail -1 | sed -n 's/.*and = *\([0-9]*\).*/\1/p') + if [ ! -z "$and_gates" ] && [ "$and_gates" -lt "$best_and_gates" ]; then + best_and_gates=$and_gates + best_run="run_${run_num}" + best_area=$and_gates # For display + fi + fi + done + + # Create symlink to best run + if [ -n "$best_run" ]; then + mkdir -p "$BASE_OUTPUT_DIR/${dsl_basename}" + ln -sfn "../${best_run}/${dsl_basename}" "$BASE_OUTPUT_DIR/${dsl_basename}/best" + echo "$best_run (area/gates: $best_area)" + else + echo "No successful runs found" + fi +} + +# Export function and variables for parallel execution +export -f run_synthesis +export EXTRA_ARGS BASE_OUTPUT_DIR VERBOSE RED GREEN YELLOW BLUE NC RUNS_PER_DSL HAS_GATES_FLAG HAS_ODC_FLAG + +# Track start time +OVERALL_START=$(date +%s) + +echo -e "${YELLOW}Starting synthesis jobs...${NC}" +echo "" + +# Run jobs based on method +if [ "$USE_GNU_PARALLEL" = true ]; then + # Use GNU parallel - generate all combinations of files and run numbers + TOTAL_JOBS=$((${#DSL_FILES[@]} * RUNS_PER_DSL)) + + # Generate all job combinations + for dsl_file in "${DSL_FILES[@]}"; do + for run_num in $(seq 1 $RUNS_PER_DSL); do + echo "$dsl_file $run_num" + done + done | parallel -j "$MAX_PARALLEL" --line-buffer --colsep ' ' \ + --tagstring "[{#}/$TOTAL_JOBS]" \ + run_synthesis {1} {#} $TOTAL_JOBS {2} + + EXIT_CODE=$? + + # Select best run for each DSL + if [ "$RUNS_PER_DSL" -gt 1 ]; then + echo "" + echo "Selecting best runs..." + for dsl_file in "${DSL_FILES[@]}"; do + dsl_basename=$(basename "$dsl_file" .dsl) + echo -n " $dsl_basename: " + best=$(select_best_run "$dsl_basename") + echo "$best" + done + fi +else + # Use bash job control + JOB_COUNT=0 + FAILED_JOBS=() + TOTAL_JOBS=$((${#DSL_FILES[@]} * RUNS_PER_DSL)) + + # Start all jobs, respecting MAX_PARALLEL limit + for i in "${!DSL_FILES[@]}"; do + dsl_file="${DSL_FILES[$i]}" + dsl_basename=$(basename "$dsl_file" .dsl) + + # Run multiple times for each DSL + for run_num in $(seq 1 $RUNS_PER_DSL); do + # Calculate job number for display + job_num=$(( i * RUNS_PER_DSL + run_num )) + + # Start job in background + run_synthesis "$dsl_file" "$job_num" "$TOTAL_JOBS" "$run_num" & + JOB_COUNT=$((JOB_COUNT + 1)) + + # Throttle if needed + if [ "$JOB_COUNT" -lt "$TOTAL_JOBS" ] && [ $(jobs -r | wc -l) -ge "$MAX_PARALLEL" ]; then + # Wait for at least one job to finish before continuing + while [ $(jobs -r | wc -l) -ge "$MAX_PARALLEL" ]; do + sleep 0.5 + done + fi + done + done + + # Wait for all jobs to complete + wait + EXIT_CODE=$? + + # Select best run for each DSL + if [ "$RUNS_PER_DSL" -gt 1 ]; then + echo "" + echo "Selecting best runs..." + for dsl_file in "${DSL_FILES[@]}"; do + dsl_basename=$(basename "$dsl_file" .dsl) + echo -n " $dsl_basename: " + best=$(select_best_run "$dsl_basename") + echo "$best" + done + fi +fi + +# Calculate total time +OVERALL_END=$(date +%s) +OVERALL_DURATION=$((OVERALL_END - OVERALL_START)) + +# Print summary +echo "" +echo "==========================================" +echo "Batch Synthesis Summary" +echo "==========================================" +echo "Total time: ${OVERALL_DURATION} seconds" +echo "Output directory: $BASE_OUTPUT_DIR" + +# List results +echo "" +echo "Results:" +for dsl_file in "${DSL_FILES[@]}"; do + dsl_basename=$(basename "$dsl_file" .dsl) + + # Check for success based on whether we have multiple runs + if [ "$RUNS_PER_DSL" -gt 1 ]; then + # Check if any run succeeded + success=false + for run_num in $(seq 1 $RUNS_PER_DSL); do + if [ -f "$BASE_OUTPUT_DIR/run_${run_num}/${dsl_basename}/ibex_optimized_post_abc.aig" ]; then + success=true + break + fi + done + if [ "$success" = true ]; then + echo -e " ${GREEN}✓${NC} $dsl_basename" + else + echo -e " ${RED}✗${NC} $dsl_basename" + fi + else + # Single run - check directly + if [ -f "$BASE_OUTPUT_DIR/${dsl_basename}/ibex_optimized_post_abc.aig" ]; then + echo -e " ${GREEN}✓${NC} $dsl_basename" + else + echo -e " ${RED}✗${NC} $dsl_basename" + fi + fi +done + +echo "" + +# Generate comparison CSV if multiple files succeeded +SUCCESS_COUNT=0 +for dsl_file in "${DSL_FILES[@]}"; do + dsl_basename=$(basename "$dsl_file" .dsl) + + # Check for success based on whether we have multiple runs + if [ "$RUNS_PER_DSL" -gt 1 ]; then + # Check if any run succeeded + for run_num in $(seq 1 $RUNS_PER_DSL); do + if [ -f "$BASE_OUTPUT_DIR/run_${run_num}/${dsl_basename}/ibex_optimized_post_abc.aig" ]; then + SUCCESS_COUNT=$((SUCCESS_COUNT + 1)) + break + fi + done + else + if [ -f "$BASE_OUTPUT_DIR/${dsl_basename}/ibex_optimized_post_abc.aig" ]; then + SUCCESS_COUNT=$((SUCCESS_COUNT + 1)) + fi + fi +done + +if [ $SUCCESS_COUNT -gt 1 ]; then + echo "Generating comparison report..." + CSV_FILE="$BASE_OUTPUT_DIR/synthesis_comparison.csv" + + # Determine what metrics we have based on flags + CSV_HAS_AREA=false + CSV_HAS_FREQ=false + + # Check if we have chip area data (from --gates flag) + if [ "$HAS_GATES_FLAG" = true ]; then + CSV_HAS_AREA=true + # Check if any result has frequency data + for dsl_file in "${DSL_FILES[@]}"; do + dsl_basename=$(basename "$dsl_file" .dsl) + if [ "$RUNS_PER_DSL" -gt 1 ]; then + for run_num in $(seq 1 $RUNS_PER_DSL); do + if [ -f "$BASE_OUTPUT_DIR/run_${run_num}/${dsl_basename}/freq.txt" ]; then + CSV_HAS_FREQ=true + break 2 + fi + done + else + if [ -f "$BASE_OUTPUT_DIR/${dsl_basename}/freq.txt" ]; then + CSV_HAS_FREQ=true + break + fi + fi + done + fi + + # Header - include chip area and/or frequency if available + if [ "$CSV_HAS_AREA" = true ] && [ "$CSV_HAS_FREQ" = true ]; then + echo "DSL,Result_Type,Inputs,Outputs,Constraints,Latches,AND_gates,Levels,Total_area_um2,Max_freq_MHz" > "$CSV_FILE" + elif [ "$CSV_HAS_AREA" = true ]; then + echo "DSL,Result_Type,Inputs,Outputs,Constraints,Latches,AND_gates,Levels,Total_area_um2" > "$CSV_FILE" + else + echo "DSL,Result_Type,Inputs,Outputs,Constraints,Latches,AND_gates,Levels" > "$CSV_FILE" + fi + + # Process each result + for dsl_file in "${DSL_FILES[@]}"; do + dsl_basename=$(basename "$dsl_file" .dsl) + + # Determine which directory to use + if [ "$RUNS_PER_DSL" -gt 1 ] && [ -L "$BASE_OUTPUT_DIR/${dsl_basename}/best" ]; then + result_dir="$BASE_OUTPUT_DIR/${dsl_basename}/best" + else + result_dir="$BASE_OUTPUT_DIR/${dsl_basename}" + fi + + # Determine which log file to use based on flags + if [ "$HAS_ODC_FLAG" = true ] && [ -f "$result_dir/odc_optimized_synthesis/abc.log" ]; then + log_file="$result_dir/odc_optimized_synthesis/abc.log" + result_type="ODC-optimized" + else + log_file="$result_dir/ibex_optimized_abc.log" + result_type="optimized" + fi + + if [ -f "$log_file" ]; then + # Extract final stats from ABC log + # Format: "i/o = 1338/ 432(c=1) lat = 761 and = 14372 lev =115" + stats=$(grep "i/o =" "$log_file" | tail -1) + if [ ! -z "$stats" ]; then + inputs=$(echo "$stats" | sed -n 's/.*i\/o = *\([0-9]*\).*/\1/p') + outputs=$(echo "$stats" | sed -n 's/.*i\/o = *[0-9]*\/ *\([0-9]*\).*/\1/p') + constraints=$(echo "$stats" | sed -n 's/.*c=\([0-9]*\).*/\1/p') + latches=$(echo "$stats" | sed -n 's/.*lat = *\([0-9]*\).*/\1/p') + and_gates=$(echo "$stats" | sed -n 's/.*and = *\([0-9]*\).*/\1/p') + levels=$(echo "$stats" | sed -n 's/.*lev = *\([0-9]*\).*/\1/p') + + # Default to 0 if not found + constraints=${constraints:-0} + + # Extract total chip area from area.txt if available + chip_area="N/A" + if [ "$CSV_HAS_AREA" = true ] && [ -f "$result_dir/area.txt" ]; then + chip_area=$(cat "$result_dir/area.txt" 2>/dev/null || echo "N/A") + chip_area=${chip_area:-"N/A"} + fi + + # Extract frequency from freq.txt if available + freq="N/A" + if [ "$CSV_HAS_FREQ" = true ] && [ -f "$result_dir/freq.txt" ]; then + freq=$(cat "$result_dir/freq.txt" 2>/dev/null || echo "N/A") + freq=${freq:-"N/A"} + fi + + # Write to CSV based on what columns we have + if [ "$CSV_HAS_AREA" = true ] && [ "$CSV_HAS_FREQ" = true ]; then + echo "$dsl_basename,$result_type,$inputs,$outputs,$constraints,$latches,$and_gates,$levels,$chip_area,$freq" >> "$CSV_FILE" + elif [ "$CSV_HAS_AREA" = true ]; then + echo "$dsl_basename,$result_type,$inputs,$outputs,$constraints,$latches,$and_gates,$levels,$chip_area" >> "$CSV_FILE" + else + echo "$dsl_basename,$result_type,$inputs,$outputs,$constraints,$latches,$and_gates,$levels" >> "$CSV_FILE" + fi + fi + fi + done + + echo -e "${GREEN}Comparison saved to: $CSV_FILE${NC}" + + # Show quick comparison + echo "" + if [ "$CSV_HAS_AREA" = true ] && [ "$CSV_HAS_FREQ" = true ]; then + echo "Quick comparison (sorted by total chip area):" + # Skip header, replace non-numeric values with large values for sorting, sort by chip area column (9) + tail -n +2 "$CSV_FILE" | awk -F',' '{ + # If chip area (column 9) is not a number, replace with a large value for sorting + if ($9 !~ /^[0-9.]+$/) $9 = "999999999"; + print $0; + }' OFS=',' | sort -t',' -k9 -g | head -20 | \ + awk -F',' 'BEGIN {printf "%-25s %-15s %10s %8s %15s %12s\n", "DSL", "Result_Type", "AND_gates", "Levels", "Total_area(µm²)", "Max_freq(MHz)"} + {printf "%-25s %-15s %10s %8s %15s %12s\n", $1, $2, $7, $8, $9, $10}' + elif [ "$CSV_HAS_AREA" = true ]; then + echo "Quick comparison (sorted by total chip area):" + # Skip header, replace non-numeric chip area with a large value, sort by chip area column (9) + tail -n +2 "$CSV_FILE" | awk -F',' '{ + # If chip area (column 9) is not a number, replace with a large value for sorting + if ($9 !~ /^[0-9.]+$/) $9 = "999999999"; + print $0; + }' OFS=',' | sort -t',' -k9 -g | head -20 | \ + awk -F',' 'BEGIN {printf "%-25s %-15s %10s %8s %15s\n", "DSL", "Result_Type", "AND_gates", "Levels", "Total_area(µm²)"} + {printf "%-25s %-15s %10s %8s %15s\n", $1, $2, $7, $8, $9}' + else + echo "Quick comparison (sorted by AND gates):" + sort -t',' -k7 -n "$CSV_FILE" | column -t -s',' | head -20 + fi +fi + +exit $EXIT_CODE \ No newline at end of file diff --git a/scripts/synth_to_gates.sh b/scripts/synth_to_gates.sh index ae73a8a..69efe2f 100755 --- a/scripts/synth_to_gates.sh +++ b/scripts/synth_to_gates.sh @@ -208,7 +208,7 @@ if [ ${PIPESTATUS[0]} -eq 0 ]; then DFF_AREA=$(python3 -c "print(f'{$DFF_COUNT * 20.0:.2f}')" 2>/dev/null || echo "0") # Total area = combinational + sequential - CHIP_AREA=$(python3 -c "print(f'{float('${CHIP_AREA_COMB:-0}') + float('${DFF_AREA:-0}'):.2f}')" 2>/dev/null || echo "$CHIP_AREA_COMB") + CHIP_AREA=$(python3 -c "print(f'{float(${CHIP_AREA_COMB:-0}) + float(${DFF_AREA:-0}):.2f}')") else # Generic cells - extract gate and FF counts instead of area DFF_COUNT=$(grep -oP '\$_DFF_P_\s+\K\d+' "$GATES_LOG" | head -1) @@ -235,7 +235,7 @@ if [ ${PIPESTATUS[0]} -eq 0 ]; then echo "Flip-flops: $DFF_COUNT (estimated area: $DFF_AREA µm²)" fi if [ -n "$CHIP_AREA" ]; then - echo "Total chip area: $CHIP_AREA µm² (comb + seq)" + echo "Total chip area: $CHIP_AREA µm² (comb $CHIP_AREA_COMB + seq $DFF_AREA)" # Save total area to file for comparison scripts echo "$CHIP_AREA" > "${INPUT_BASE}_total_area.txt" fi diff --git a/synth_core.sh b/synth_core.sh index 880f88f..3312fcf 100755 --- a/synth_core.sh +++ b/synth_core.sh @@ -794,8 +794,8 @@ PYEOF OPTIMIZED_AREA=$(grep "Chip area for module" "$OPTIMIZED_SYNTH_DIR/${OPTIMIZED_BASE}_gates.log" 2>/dev/null | tail -1 | awk '{print $NF}') if [ -n "$BASELINE_AREA" ] && [ -n "$OPTIMIZED_AREA" ]; then - AREA_REDUCTION=$(python3 -c "print(f'{float('$BASELINE_AREA') - float('$OPTIMIZED_AREA'):.2f}')") - AREA_PERCENT=$(python3 -c "print(f'{100.0 * (float('$BASELINE_AREA') - float('$OPTIMIZED_AREA')) / float('$BASELINE_AREA'):.2f}')") + AREA_REDUCTION=$(python3 -c "print(f'{float(${BASELINE_AREA:-0}) - float(${OPTIMIZED_AREA:-0}):.2f}')") + AREA_PERCENT=$(python3 -c "print(f'{100.0 * (float(${BASELINE_AREA:-0}) - float(${OPTIMIZED_AREA:-0})) / float(${BASELINE_AREA:-1}):.2f}')") echo "" echo "Chip Area Comparison:" @@ -804,6 +804,7 @@ PYEOF echo " Reduction: $AREA_REDUCTION µm² ($AREA_PERCENT%)" fi + # Compare timing if metrics are available BASELINE_TIMING="${BASE}_timing_metrics.json" OPTIMIZED_TIMING="${OPTIMIZED_BASE_PATH}_timing_metrics.json" @@ -813,8 +814,8 @@ PYEOF OPTIMIZED_FREQ=$(python3 -c "import json; print(json.load(open('$OPTIMIZED_TIMING')).get('max_frequency_mhz', 'N/A'))" 2>/dev/null || echo "N/A") if [ "$BASELINE_FREQ" != "N/A" ] && [ "$OPTIMIZED_FREQ" != "N/A" ]; then - FREQ_CHANGE=$(python3 -c "print(f'{float('$OPTIMIZED_FREQ') - float('$BASELINE_FREQ'):.2f}')" 2>/dev/null || echo "0.00") - FREQ_PERCENT=$(python3 -c "print(f'{100.0 * (float('$OPTIMIZED_FREQ') - float('$BASELINE_FREQ')) / float('$BASELINE_FREQ'):.2f}')" 2>/dev/null || echo "0.00") + FREQ_CHANGE=$(python3 -c "print(f'{float(${OPTIMIZED_FREQ:-0}) - float(${BASELINE_FREQ:-0}):.2f}')") + FREQ_PERCENT=$(python3 -c "print(f'{100.0 * (float(${OPTIMIZED_FREQ:-0}) - float(${BASELINE_FREQ:-0})) / float(${BASELINE_FREQ:-1}):.2f}')") echo "" echo "Timing Comparison (10ns target period):" @@ -822,6 +823,7 @@ PYEOF echo " Optimized: $OPTIMIZED_FREQ MHz" echo " Change: $FREQ_CHANGE MHz ($FREQ_PERCENT%)" fi + fi fi fi diff --git a/synth_core_simplified.sh b/synth_core_simplified.sh new file mode 100755 index 0000000..2131555 --- /dev/null +++ b/synth_core_simplified.sh @@ -0,0 +1,918 @@ +#!/bin/bash +# End-to-end script: DSL file → Optimized RISC-V core with instruction constraints +# +# Supports multiple cores via YAML config files (Ibex, BOOM, Rocket, CVA6, etc.) +# Usage: ./synth_core.sh [OPTIONS] [output_dir] + +set -e + +# Parse arguments +SYNTHESIZE_GATES=false +ABC_DEPTH=2 +WRITEBACK_STAGE=false +CONFIG_FILE="" +CORE_NAME="ibex" # Default to ibex for this project +RUN_ODC_ANALYSIS=false +while [[ "$#" -gt 0 ]]; do + case $1 in + --gates) SYNTHESIZE_GATES=true; shift ;; + --3stage) WRITEBACK_STAGE=true; ABC_DEPTH=3; shift ;; + --abc-depth) + ABC_DEPTH="$2" + ABC_DEPTH_USER_SET=true + if ! [[ "$ABC_DEPTH" =~ ^[0-9]+$ ]] || [ "$ABC_DEPTH" -lt 1 ]; then + echo "ERROR: --abc-depth must be a positive integer" + exit 1 + fi + shift 2 + ;; + --config) + CONFIG_FILE="$2" + shift 2 + ;; + --core) + CORE_NAME="$2" + shift 2 + ;; + --no-config) + CORE_NAME="" # Disable default, force legacy mode + CONFIG_FILE="" + shift + ;; + --odc-analysis) RUN_ODC_ANALYSIS=true; shift ;; + -h|--help) + echo "Usage: $0 [OPTIONS] [output_dir|output.il]" + echo "" + echo "Generates optimized RISC-V core with instruction constraints from DSL file" + echo "" + echo "Options:" + echo " --gates Also synthesize to gate-level netlist with Skywater PDK" + echo " --3stage Enable Ibex 3-stage pipeline (IF, ID/EX, WB) and set ABC depth=3" + echo " --abc-depth N Set ABC k-induction depth (default: 2, matches 2-stage pipeline)" + echo " --config FILE Use YAML config file (overrides --core default)" + echo " --core NAME Core name for auto-config lookup (default: ibex, looks for configs/NAME.yaml)" + echo " --no-config Disable config mode and use legacy hardcoded paths" + echo " --odc-analysis Run complete ODC analysis on ALL fields (shamt, rd, rs1, rs2, imm)" + echo " Automatically detects, verifies, and applies all ODC optimizations" + echo "" + echo "Arguments:" + echo " rules.dsl DSL file with instruction constraints" + echo " output_dir Base directory for outputs (default: output/)" + echo " output.il Specific output file path (if ends with .il)" + echo "" + echo "Config Mode (DEFAULT):" + echo " Config mode is enabled by default (--core ibex). Source files and paths are" + echo " read from configs/ibex.yaml. Use --no-config to force legacy mode with" + echo " hardcoded paths. Config mode supports multiple cores (Ibex, BOOM, Rocket, etc.)" + echo "" + echo "Output organization:" + echo " - Files are organized in subfolders named after the DSL file" + echo " - e.g., my_rules.dsl → output/my_rules/_optimized.il" + echo " - In legacy mode (no --config): ibex_optimized.il" + echo " - In config mode: _optimized.il (e.g., boom_optimized.il)" + echo "" + echo "Examples:" + echo " $0 my_rules.dsl # Outputs to output/my_rules/" + echo " $0 --gates my_rules.dsl # RTLIL + gates in output/my_rules/" + echo " $0 --3stage my_rules.dsl # 3-stage pipeline in output/my_rules/" + echo " $0 --abc-depth 1 my_rules.dsl # k=1 induction in output/my_rules/" + echo " $0 my_rules.dsl results # Outputs to results/my_rules/" + echo " $0 my_rules.dsl output/custom.il # Specific path output/custom.il" + echo " $0 --config configs/ibex.yaml my_rules.dsl # Use config file" + echo " $0 --core ibex my_rules.dsl # Auto-load configs/ibex.yaml" + echo " $0 --odc-analysis my_rules.dsl # Run ODC analysis after optimization" + echo "" + echo "All intermediate files are placed in the same directory as the final output." + exit 0 + ;; + *) break ;; + esac +done + +# Handle --core flag: auto-find config file +if [ -n "$CORE_NAME" ] && [ -z "$CONFIG_FILE" ]; then + CONFIG_FILE="configs/${CORE_NAME}.yaml" + if [ ! -f "$CONFIG_FILE" ]; then + echo "ERROR: Config file not found: $CONFIG_FILE" + echo "Available configs:" + ls -1 configs/*.yaml 2>/dev/null | grep -v schema.yaml | sed 's/configs\// - /' || echo " (none)" + exit 1 + fi + echo "Using config file: $CONFIG_FILE" +fi + +# Check DSL file exists +if [ "$#" -lt 1 ]; then + echo "ERROR: Missing required argument " + echo "Run with --help for usage information" + exit 1 +fi + +# Check DSL file exists +if [ ! -f "$1" ]; then + echo "ERROR: DSL file '$1' not found" + exit 1 +fi + +INPUT_DSL="$1" + +# Extract DSL base name (without path and extension) for subfolder +DSL_BASENAME=$(basename "$INPUT_DSL" .dsl) + +# Determine output file prefix based on mode +if [ -n "$CONFIG_FILE" ]; then + # Get core name, default ABC depth, clock name, and module name from config + CONFIG_VALUES=$(python3 -c " +import sys +sys.path.insert(0, 'scripts') +try: + from config_loader import ConfigLoader + config = ConfigLoader.load_config('$CONFIG_FILE') + core_name = config.core_name + default_depth = config.synthesis.abc_config.get('default_depth', 2) if config.synthesis.abc_config else 2 + clk_name = config.signals.get('clk', 'clk_i') if hasattr(config, 'signals') and config.signals else 'clk_i' + top_module = config.synthesis.top_module if hasattr(config.synthesis, 'top_module') else 'ibex_core' + print(f'{core_name}_optimized') + print(default_depth) + print(clk_name) + print(top_module) +except Exception as e: + print('core_optimized') + print('2') + print('clk_i') + print('ibex_core') +") + OUTPUT_PREFIX=$(echo "$CONFIG_VALUES" | sed -n '1p') + CONFIG_DEFAULT_DEPTH=$(echo "$CONFIG_VALUES" | sed -n '2p') + CLK_NAME=$(echo "$CONFIG_VALUES" | sed -n '3p') + MODULE_NAME=$(echo "$CONFIG_VALUES" | sed -n '4p') + + # Override ABC_DEPTH with config default if not explicitly set via --abc-depth + # Check if ABC_DEPTH is still at default value (2) - if so, use config default + if [ "$ABC_DEPTH" -eq 2 ] && [ -z "$ABC_DEPTH_USER_SET" ]; then + ABC_DEPTH=$CONFIG_DEFAULT_DEPTH + echo " Using ABC depth from config: $ABC_DEPTH" + fi +else + # Legacy mode: use ibex prefix, default clock name, and default module + OUTPUT_PREFIX="ibex_optimized" + CLK_NAME="clk_i" + MODULE_NAME="ibex_core" +fi + +# Handle output argument: +# - If not provided: output//.il +# - If ends with .il: use as full path +# - Otherwise: treat as directory and use /.il +if [ -z "$2" ]; then + OUTPUT_DIR="output/$DSL_BASENAME" + OUTPUT_IL="$OUTPUT_DIR/${OUTPUT_PREFIX}.il" +elif [[ "$2" == *.il ]]; then + OUTPUT_IL="$2" + OUTPUT_DIR=$(dirname "$OUTPUT_IL") +else + OUTPUT_DIR="$2/$DSL_BASENAME" + OUTPUT_IL="$OUTPUT_DIR/${OUTPUT_PREFIX}.il" +fi + +# Ensure output directory exists +mkdir -p "$OUTPUT_DIR" + +# Derive intermediate filenames from output +BASE="${OUTPUT_IL%.il}" +ASSUMPTIONS_CODE="${BASE}_assumptions.sv" +ID_STAGE_SV="${BASE}_id_stage.sv" +SYNTH_SCRIPT="${BASE}_synth.ys" + +TIMING_CODE="${BASE}_assumptions_timing.sv" # Cache timing constraints +CORE_SV="${BASE}_core.sv" # Modified core with cache timing + +echo "==========================================" +if [ -n "$CONFIG_FILE" ]; then + CORE_DISPLAY=$(python3 -c " +import sys +sys.path.insert(0, 'scripts') +try: + from config_loader import ConfigLoader + config = ConfigLoader.load_config('$CONFIG_FILE') + print(config.core_name.upper()) +except: + print('Core') +" 2>/dev/null) + echo "$CORE_DISPLAY Synthesis with Instruction Constraints" +else + echo "Ibex Synthesis with Instruction Constraints" +fi +echo "==========================================" +echo "Input DSL: $INPUT_DSL" +echo "Output folder: $OUTPUT_DIR" +echo "Output AIGER: ${BASE}_post_abc.aig" +echo "" + +# Determine total steps +if [ "$SYNTHESIZE_GATES" = true ]; then + TOTAL_STEPS=4 +else + TOTAL_STEPS=3 +fi + +# Step 1: Generate assumptions code (inline, no module) +echo "[1/$TOTAL_STEPS] Generating instruction assumptions..." + +# Pass config file to codegen if in config mode (for signal name mappings) +if [ -n "$CONFIG_FILE" ]; then + # pdat-dsl needs config from PdatRiscvDsl/configs/, not PdatScorr/configs/ + # Try to find corresponding config in PdatRiscvDsl + DSL_CONFIG="../PdatRiscvDsl/configs/$(basename "$CONFIG_FILE")" + if [ -f "$DSL_CONFIG" ]; then + pdat-dsl codegen --config "$DSL_CONFIG" "$INPUT_DSL" "$ASSUMPTIONS_CODE" + else + echo " Warning: DSL config not found at $DSL_CONFIG, generating without config" + pdat-dsl codegen "$INPUT_DSL" "$ASSUMPTIONS_CODE" + fi +else + pdat-dsl codegen "$INPUT_DSL" "$ASSUMPTIONS_CODE" +fi + +if [ $? -ne 0 ]; then + echo "ERROR: Failed to generate assumptions" + exit 1 +fi + +# Step 2: Inject assumptions into ID stage +echo "[2/$TOTAL_STEPS] Injecting assumptions into ID stage..." + +# Determine core root path based on mode +if [ -n "$CONFIG_FILE" ]; then + # Config mode: Validate config file exists first + if [ ! -f "$CONFIG_FILE" ]; then + echo "ERROR: Config file not found: $CONFIG_FILE" + exit 1 + fi + + # Get core root from config file + CORE_ROOT=$(python3 -c " +import sys +sys.path.insert(0, 'scripts') +try: + from config_loader import ConfigLoader + config = ConfigLoader.load_config('$CONFIG_FILE') + print(config.synthesis.core_root_resolved) +except Exception as e: + print(f'ERROR: {e}', file=sys.stderr) + sys.exit(1) +") + + if [ $? -ne 0 ] || [ -z "$CORE_ROOT" ] || [[ "$CORE_ROOT" == ERROR:* ]]; then + echo "ERROR: Failed to load core root from config file" + echo "$CORE_ROOT" + exit 1 + fi + + # Get ID stage source file from config + ID_STAGE_SOURCE=$(python3 -c " +import sys +sys.path.insert(0, 'scripts') +try: + from config_loader import ConfigLoader + config = ConfigLoader.load_config('$CONFIG_FILE') + inj = config.get_injection('isa') + if inj: + print(f'{config.synthesis.core_root_resolved}/{inj.source_file}') + else: + print('ERROR: No ISA injection point found', file=sys.stderr) + sys.exit(1) +except Exception as e: + print(f'ERROR: {e}', file=sys.stderr) + sys.exit(1) +") + + if [ $? -ne 0 ] || [ -z "$ID_STAGE_SOURCE" ] || [[ "$ID_STAGE_SOURCE" == ERROR:* ]]; then + echo "ERROR: Could not find ISA injection point in config" + echo "$ID_STAGE_SOURCE" + exit 1 + fi +else + # Legacy mode: Use IBEX_ROOT + # 1. Use IBEX_ROOT environment variable if set + # 2. Try ../PdatCoreSim/cores/ibex + # 3. Try ../CoreSim/cores/ibex + # 4. Error if none found + if [ -z "$IBEX_ROOT" ]; then + if [ -d "../PdatCoreSim/cores/ibex" ]; then + IBEX_ROOT="../PdatCoreSim/cores/ibex" + elif [ -d "../CoreSim/cores/ibex" ]; then + IBEX_ROOT="../CoreSim/cores/ibex" + else + echo "ERROR: Could not find Ibex core directory. Tried:" + echo " - ../PdatCoreSim/cores/ibex" + echo " - ../CoreSim/cores/ibex" + echo "" + echo "Please set IBEX_ROOT environment variable or ensure Ibex is in one of these locations" + exit 1 + fi + fi + + CORE_ROOT="$IBEX_ROOT" + ID_STAGE_SOURCE="$IBEX_ROOT/rtl/ibex_id_stage.sv" +fi + +echo "Using core root: $CORE_ROOT" + +python3 scripts/inject_checker.py --assumptions-file "$ASSUMPTIONS_CODE" "$ID_STAGE_SOURCE" "$ID_STAGE_SV" + +if [ $? -ne 0 ]; then + echo "ERROR: Failed to inject ISA assumptions" + exit 1 +fi + +# Step 2.5: Check if timing constraints were generated and inject into core +CORE_MODIFIED_FLAG="" +if [ -f "$TIMING_CODE" ]; then + echo "[2.5/$TOTAL_STEPS] Detected timing constraints, injecting into core..." + + if [ -n "$CONFIG_FILE" ]; then + # Config mode: Get core source file from config + CORE_SOURCE=$(python3 -c " +import sys +sys.path.insert(0, 'scripts') +try: + from config_loader import ConfigLoader + config = ConfigLoader.load_config('$CONFIG_FILE') + inj = config.get_injection('timing') + if inj: + print(f'{config.synthesis.core_root_resolved}/{inj.source_file}') + else: + print('ERROR: No timing injection point found', file=sys.stderr) + sys.exit(1) +except Exception as e: + print(f'ERROR: {e}', file=sys.stderr) + sys.exit(1) +") + + if [ $? -ne 0 ] || [ -z "$CORE_SOURCE" ] || [[ "$CORE_SOURCE" == ERROR:* ]]; then + echo "ERROR: Could not find timing injection point in config" + echo "$CORE_SOURCE" + exit 1 + fi + else + # Legacy mode + CORE_SOURCE="$CORE_ROOT/rtl/ibex_core.sv" + fi + + python3 scripts/inject_core_timing.py \ + --timing-file "$TIMING_CODE" \ + "$CORE_SOURCE" \ + "$CORE_SV" + + if [ $? -ne 0 ]; then + echo "ERROR: Failed to inject timing constraints" + exit 1 + fi + + CORE_MODIFIED_FLAG="--core-modified $CORE_SV" + echo " Timing constraints injected successfully" +else + echo " No timing constraints detected (this is normal for ISA-only optimization)" +fi + +# Step 3: Generate synthesis script +echo "[3/$TOTAL_STEPS] Generating synthesis script..." + +if [ -n "$CONFIG_FILE" ]; then + # Config mode + echo " Using config file: $CONFIG_FILE" + + # Build modified-files argument + MODIFIED_FILES_ARGS="--modified-files id_stage_isa=${ID_STAGE_SV}" + if [ -f "$CORE_SV" ]; then + MODIFIED_FILES_ARGS="$MODIFIED_FILES_ARGS core_timing=${CORE_SV}" + fi + + # Check for ODC-optimized register file + RF_OPT="${OUTPUT_DIR}/odc_optimized_rtl/ibex_register_file_ff_optimized.sv" + if [ -f "$RF_OPT" ]; then + echo " Found ODC-optimized register file: $RF_OPT" + MODIFIED_FILES_ARGS="$MODIFIED_FILES_ARGS register_file_opt=${RF_OPT}" + fi + + # Build writeback-stage flag if needed + WRITEBACK_FLAG="" + if [ "$WRITEBACK_STAGE" = true ]; then + echo " Enabling 3-stage pipeline (WritebackStage=1)" + WRITEBACK_FLAG="--writeback-stage" + fi + + python3 scripts/make_synthesis_script.py \ + --config "$CONFIG_FILE" \ + $MODIFIED_FILES_ARGS \ + $WRITEBACK_FLAG \ + -o "$SYNTH_SCRIPT" \ + -a "${BASE}" +else + # Legacy mode + if [ "$WRITEBACK_STAGE" = true ]; then + echo " Enabling 3-stage pipeline (WritebackStage=1)" + python3 scripts/make_synthesis_script.py "$ID_STAGE_SV" \ + -o "$SYNTH_SCRIPT" -a "${BASE}" --ibex-root "$CORE_ROOT" --writeback-stage \ + $CORE_MODIFIED_FLAG + else + python3 scripts/make_synthesis_script.py "$ID_STAGE_SV" \ + -o "$SYNTH_SCRIPT" -a "${BASE}" --ibex-root "$CORE_ROOT" \ + $CORE_MODIFIED_FLAG + fi +fi + +if [ $? -ne 0 ]; then + echo "ERROR: Failed to generate synthesis script" + exit 1 +fi + +# Step 4: Run synthesis +echo "[4/$TOTAL_STEPS] Running synthesis with Synlig (this may take several minutes)..." +YOSYS_LOG="${BASE}_yosys.log" + +# Set unique Surelog cache directory to avoid conflicts with parallel runs +# Use PID + random to ensure uniqueness even in nested parallel execution +# (e.g., batch_synth.sh running multiple jobs in parallel) +export SLPP_ALL="$OUTPUT_DIR/slpp_all_$$_$RANDOM" +mkdir -p "$SLPP_ALL" + +# Run Synlig from OUTPUT_DIR to ensure slpp_all is created there, not in current directory +# This prevents race conditions when running multiple synthesis jobs in parallel +(cd "$OUTPUT_DIR" && synlig -s "$(basename "$SYNTH_SCRIPT")") 2>&1 | tee "$YOSYS_LOG" + +if [ ${PIPESTATUS[0]} -ne 0 ]; then + echo "ERROR: Synthesis failed" + exit 1 +fi + +echo "" +echo "==========================================" +echo "SUCCESS!" +echo "==========================================" +echo "Generated files:" +echo " - $ASSUMPTIONS_CODE (ISA assumptions)" +echo " - $ID_STAGE_SV (modified ibex_id_stage.sv)" +if [ -f "$TIMING_CODE" ]; then + echo " - $TIMING_CODE (cache timing constraints)" + echo " - $CORE_SV (modified ibex_core.sv)" +fi +echo " - $SYNTH_SCRIPT (synthesis script)" +echo " - ${BASE}_yosys.aig (AIGER from Yosys, before ABC)" +echo " - $YOSYS_LOG (Yosys synthesis log)" +echo "" + +# Step 5: Run external ABC if available +if command -v abc &> /dev/null; then + ABC_INPUT="${BASE}_yosys.aig" + ABC_OUTPUT="${BASE}_post_abc.aig" + ABC_LOG="${BASE}_abc.log" + + if [ -f "$ABC_INPUT" ] && [ -s "$ABC_INPUT" ]; then + echo "Running external ABC with sequential optimization (scorr)..." + echo "Input: $ABC_INPUT" + echo "Output: $ABC_OUTPUT" + echo "" + + echo "ABC k-induction depth: $ABC_DEPTH (should match pipeline depth)" + # Two-stage optimization for best results: + # 1. First optimize WITH constraints for maximum reduction + # 2. Then extract clean outputs without constraints + + # Get the number of real outputs (before constraints) + ABC_STATS=$(abc -c "read_aiger $ABC_INPUT; print_stats" 2>&1 | grep "i/o") + if echo "$ABC_STATS" | grep -q "(c="; then + TOTAL_OUTPUTS=$(echo "$ABC_STATS" | sed -n 's/.*i\/o = *[0-9]*\/ *\([0-9]*\).*/\1/p') + NUM_CONSTRAINTS=$(echo "$ABC_STATS" | sed -n 's/.*c=\([0-9]*\).*/\1/p') + REAL_OUTPUTS=$((TOTAL_OUTPUTS - NUM_CONSTRAINTS)) + echo "Detected $NUM_CONSTRAINTS constraints, will extract $REAL_OUTPUTS real outputs" + + # Build constraint removal commands for ALL constraints + CONSTRAINT_CMDS="constr -r;" + # Remove each constraint output from highest index downward + for ((i=TOTAL_OUTPUTS-1; i>=REAL_OUTPUTS; i--)); do + CONSTRAINT_CMDS="$CONSTRAINT_CMDS removepo -N $i;" + done + else + REAL_OUTPUTS="" + NUM_CONSTRAINTS=0 + TOTAL_OUTPUTS=0 + echo "No constraints detected" + # No constraint removal needed + CONSTRAINT_CMDS="" + fi + + # Single unified optimization flow + # Always use -c -m flags (they work fine even without constraints) + abc -c "read_aiger $ABC_INPUT; strash; cycle 100; scorr -c -m -F $ABC_DEPTH -C 30000 -S 20 -v; $CONSTRAINT_CMDS rewrite -l; balance -l; print_stats; write_aiger $ABC_OUTPUT" 2>&1 | tee "$ABC_LOG" | grep -E "^output|i/o =|lat =|and =|constraint|Removed equivs" + + if [ ${PIPESTATUS[0]} -eq 0 ] && [ -f "$ABC_OUTPUT" ]; then + echo "" + echo "External ABC optimization completed!" + echo " - $ABC_OUTPUT (optimized AIGER)" + echo " - $ABC_LOG (ABC optimization log)" + else + echo "WARNING: External ABC optimization failed" + fi + fi +else + echo "External ABC not found - skipping sequential optimization" + echo "Install from: https://github.com/berkeley-abc/abc" +fi + +echo "" + +# Step 5.5 (optional): ODC Analysis +if [ "$RUN_ODC_ANALYSIS" = true ]; then + echo "==========================================" + echo "ODC Analysis (Error Injection + Bounded SEC)" + echo "==========================================" + echo "" + + # Determine which config file to use + if [ -n "$CONFIG_FILE" ]; then + ODC_CONFIG="$CONFIG_FILE" + else + # Legacy mode - use default ibex config + ODC_CONFIG="configs/ibex.yaml" + fi + + # Determine reference AIGER file for ODC analysis + # Use Yosys output (NOT ABC-optimized) to ensure both circuits have same structure + # ABC optimization removes constraints which causes miter issues + if [ -f "${BASE}_yosys.aig" ]; then + BASELINE_AIG="${BASE}_yosys.aig" + echo "Using Yosys-generated circuit as reference: $BASELINE_AIG" + else + echo "ERROR: No Yosys AIGER file found for reference. Cannot run ODC analysis." + RUN_ODC_ANALYSIS=false + fi + + if [ "$RUN_ODC_ANALYSIS" = true ]; then + ODC_OUTPUT_DIR="$OUTPUT_DIR/odc_analysis" + + echo "Running ODC analysis with k=$ABC_DEPTH..." + echo " DSL: $INPUT_DSL" + echo " Reference: $BASELINE_AIG" + echo " Config: $ODC_CONFIG" + echo " Output: $ODC_OUTPUT_DIR" + echo "" + + python3 scripts/odc_analysis.py "$INPUT_DSL" \ + --baseline-aig "$BASELINE_AIG" \ + --output-dir "$ODC_OUTPUT_DIR" \ + --k-depth "$ABC_DEPTH" \ + --config "$ODC_CONFIG" \ + --scope all + + if [ $? -eq 0 ]; then + echo "" + echo "ODC analysis complete!" + echo " Reports: $ODC_OUTPUT_DIR/odc_report.{json,md}" + echo "" + + # Check if any ODCs were found (bit-level or mux-level) + ODC_REPORT="$ODC_OUTPUT_DIR/odc_report.json" + MUX_ODC_REPORT="$ODC_OUTPUT_DIR/mux_odc_analysis.json" + + ODC_COUNT=$(python3 -c " +import json +count = 0 +try: + with open('$ODC_REPORT') as f: + report = json.load(f) + count = report.get('metadata', {}).get('confirmed_odcs', 0) +except: + pass +print(count) +" 2>/dev/null || echo "0") + + MUX_ODC_COUNT=$(python3 -c " +import json +count = 0 +try: + with open('$MUX_ODC_REPORT') as f: + report = json.load(f) + count = len([c for c in report.get('unreachable_mux_cases', []) if c.get('sec_verified', False)]) +except: + pass +print(count) +" 2>/dev/null || echo "0") + + TOTAL_ODC_COUNT=$((ODC_COUNT + MUX_ODC_COUNT)) + + # Always create ODC synthesis directory for consistency + OPTIMIZED_RTL_DIR="$OUTPUT_DIR/odc_optimized_rtl" + OPTIMIZED_SYNTH_DIR="$OUTPUT_DIR/odc_optimized_synthesis" + mkdir -p "$OPTIMIZED_SYNTH_DIR" + + if [ "$TOTAL_ODC_COUNT" -gt 0 ]; then + echo "Found $ODC_COUNT bit-level ODCs and $MUX_ODC_COUNT mux-level ODCs - applying optimizations..." + echo "" + + # Apply bit-level optimizations (if any) + if [ "$ODC_COUNT" -gt 0 ]; then + echo "Applying bit-level ODC optimizations..." + python3 scripts/apply_odc_optimizations.py "$ODC_REPORT" \ + --rtl-dir "$CORE_ROOT/rtl" \ + --output-dir "$OPTIMIZED_RTL_DIR" \ + --config "$ODC_CONFIG" + fi + + # Apply mux-level optimizations (if any) + if [ "$MUX_ODC_COUNT" -gt 0 ]; then + echo "Applying mux-level ODC optimizations..." + # If bit-level already ran, use its output as input; otherwise use original + if [ -f "$OPTIMIZED_RTL_DIR/ibex_alu_optimized.sv" ]; then + INPUT_ALU="$OPTIMIZED_RTL_DIR/ibex_alu_optimized.sv" + else + INPUT_ALU="$CORE_ROOT/rtl/ibex_alu.sv" + mkdir -p "$OPTIMIZED_RTL_DIR" + fi + + python3 scripts/apply_mux_optimizations.py "$MUX_ODC_REPORT" \ + --config "$ODC_CONFIG" \ + --output-dir "$OPTIMIZED_RTL_DIR" + + # The output is ibex_alu_mux_optimized.sv, rename to ibex_alu_optimized.sv + if [ -f "$OPTIMIZED_RTL_DIR/ibex_alu_mux_optimized.sv" ]; then + mv "$OPTIMIZED_RTL_DIR/ibex_alu_mux_optimized.sv" "$OPTIMIZED_RTL_DIR/ibex_alu_optimized.sv" + fi + fi + + # Check if we have optimized RTL files (discover dynamically) + OPTIMIZED_FILES=$(ls "$OPTIMIZED_RTL_DIR"/*_optimized.sv 2>/dev/null || true) + + if [ -n "$OPTIMIZED_FILES" ]; then + # Copy all optimized RTL to synthesis directory + mkdir -p "$OPTIMIZED_SYNTH_DIR" + + echo "Found optimized RTL files:" + for opt_file in $OPTIMIZED_FILES; do + cp "$opt_file" "$OPTIMIZED_SYNTH_DIR/" + filename=$(basename $opt_file) + echo " - $filename" + + # Track what type of optimization this is + if [[ "$filename" == *"register_file"* ]]; then + echo " (Register file optimization - eliminates unused register storage)" + elif [[ "$filename" == *"alu"* ]]; then + echo " (ALU optimization - eliminates unused functional units)" + elif [[ "$filename" == *"id_stage"* ]]; then + echo " (ID stage optimization - may include multiple optimizations)" + fi + done + + echo "Re-synthesizing with ODC optimizations..." + + # Determine which optimized file to use as primary for synthesis + # Priority: register_file > ALU > ID stage + # (Register file is most fundamental - all others can use it) + OPTIMIZED_RTL_FILE="" + + # Check for optimized register file (highest priority - eliminates storage) + REGFILE_OPT=$(ls "$OPTIMIZED_SYNTH_DIR"/*register_file*_optimized.sv 2>/dev/null | head -1) + if [ -n "$REGFILE_OPT" ]; then + OPTIMIZED_RTL_FILE=$(basename "$REGFILE_OPT") + echo " Primary: $OPTIMIZED_RTL_FILE (register file - eliminates unused storage)" + else + # Check for ALU optimization + ALU_OPT=$(ls "$OPTIMIZED_SYNTH_DIR"/*alu*_optimized.sv 2>/dev/null | head -1) + if [ -n "$ALU_OPT" ]; then + OPTIMIZED_RTL_FILE=$(basename "$ALU_OPT") + echo " Primary: $OPTIMIZED_RTL_FILE (ALU optimization)" + else + # Fallback to ID stage or any optimized file + ANY_OPT=$(ls "$OPTIMIZED_SYNTH_DIR"/*_optimized.sv 2>/dev/null | head -1) + if [ -n "$ANY_OPT" ]; then + OPTIMIZED_RTL_FILE=$(basename "$ANY_OPT") + echo " Primary: $OPTIMIZED_RTL_FILE" + fi + fi + fi + + if [ -z "$OPTIMIZED_RTL_FILE" ]; then + echo " ERROR: No optimized RTL file found in $OPTIMIZED_SYNTH_DIR" + exit 1 + fi + + # Synthesize optimized version + python3 << PYEOF +import sys +from pathlib import Path +sys.path.insert(0, 'odc') +from synthesis import synthesize_error_injected_circuit + +result = synthesize_error_injected_circuit( + error_injected_rtl=Path('$OPTIMIZED_SYNTH_DIR/$OPTIMIZED_RTL_FILE'), + dsl_file=Path('$INPUT_DSL'), + output_dir=Path('$OPTIMIZED_SYNTH_DIR'), + config_file=Path('$ODC_CONFIG'), + k_depth=$ABC_DEPTH +) +sys.exit(0 if result else 1) +PYEOF + + ODC_SYNTH_EXIT=$? + + # Run ABC optimization on optimized circuit + # Use the base name from the optimized RTL file + OPTIMIZED_BASE="${OPTIMIZED_RTL_FILE%.sv}" + OPTIMIZED_YOSYS_AIG="$OPTIMIZED_SYNTH_DIR/${OPTIMIZED_BASE}_yosys.aig" + OPTIMIZED_ABC_AIG="$OPTIMIZED_SYNTH_DIR/${OPTIMIZED_BASE}_post_abc.aig" + + # Get constraint info + ABC_STATS_OPT=$(abc -c "read_aiger $OPTIMIZED_YOSYS_AIG; print_stats" 2>&1 | grep "i/o") + + if echo "$ABC_STATS_OPT" | grep -q "(c="; then + TOTAL_OUTPUTS=$(echo "$ABC_STATS_OPT" | grep -oP 'i/o\s*=\s*\d+/\s*\K\d+') + CONSTR_COUNT=$(echo "$ABC_STATS_OPT" | grep -oP '\(c=\K\d+') + REAL_OUTPUTS=$((TOTAL_OUTPUTS - CONSTR_COUNT)) + + CONSTRAINT_CMDS="constr -r;" + for ((i=TOTAL_OUTPUTS-1; i>=REAL_OUTPUTS; i--)); do + CONSTRAINT_CMDS="$CONSTRAINT_CMDS removepo -N $i;" + done + else + CONSTRAINT_CMDS="" + fi + + abc -c " + read_aiger $OPTIMIZED_YOSYS_AIG; + strash; + cycle 100; + scorr -c -m -F $ABC_DEPTH -C 30000 -S 20 -v; + $CONSTRAINT_CMDS + rewrite -l; + fraig; + balance -l; + print_stats; + write_aiger $OPTIMIZED_ABC_AIG; + " > "$OPTIMIZED_SYNTH_DIR/abc.log" 2>&1 + + # Get optimized stats + OPTIMIZED_STATS=$(abc -c "read_aiger $OPTIMIZED_ABC_AIG; print_stats" 2>&1 | grep "i/o =") + OPTIMIZED_AND=$(echo "$OPTIMIZED_STATS" | grep -oP 'and\s*=\s*\K\d+') + + if [ -n "$OPTIMIZED_AND" ]; then + echo "" + echo "ODC Optimization Results:" + echo " Optimized: $OPTIMIZED_AND AND gates" + echo " ✓ ODC optimization complete!" + echo "" + fi + fi + else + # No ODCs found, but run same ABC optimization on baseline for consistency + echo "No ODCs found - running ABC optimization on baseline circuit..." + echo "" + + # Copy baseline AIGER to optimized synthesis directory + if [ -f "${BASE}_yosys.aig" ]; then + OPTIMIZED_BASE="ibex_core_baseline_optimized" + OPTIMIZED_YOSYS_AIG="$OPTIMIZED_SYNTH_DIR/${OPTIMIZED_BASE}_yosys.aig" + OPTIMIZED_ABC_AIG="$OPTIMIZED_SYNTH_DIR/${OPTIMIZED_BASE}_post_abc.aig" + + cp "${BASE}_yosys.aig" "$OPTIMIZED_YOSYS_AIG" + + # Get constraint info + ABC_STATS_OPT=$(abc -c "read_aiger $OPTIMIZED_YOSYS_AIG; print_stats" 2>&1 | grep "i/o") + + if echo "$ABC_STATS_OPT" | grep -q "(c="; then + TOTAL_OUTPUTS=$(echo "$ABC_STATS_OPT" | grep -oP 'i/o\s*=\s*\d+/\s*\K\d+') + CONSTR_COUNT=$(echo "$ABC_STATS_OPT" | grep -oP '\(c=\K\d+') + REAL_OUTPUTS=$((TOTAL_OUTPUTS - CONSTR_COUNT)) + + CONSTRAINT_CMDS="constr -r;" + for ((i=TOTAL_OUTPUTS-1; i>=REAL_OUTPUTS; i--)); do + CONSTRAINT_CMDS="$CONSTRAINT_CMDS removepo -N $i;" + done + else + CONSTRAINT_CMDS="" + fi + + abc -c " + read_aiger $OPTIMIZED_YOSYS_AIG; + strash; + cycle 100; + scorr -c -m -F $ABC_DEPTH -C 30000 -S 20 -v; + $CONSTRAINT_CMDS + rewrite -l; + fraig; + balance -l; + print_stats; + write_aiger $OPTIMIZED_ABC_AIG; + " > "$OPTIMIZED_SYNTH_DIR/abc.log" 2>&1 + + # Get optimized stats + OPTIMIZED_STATS=$(abc -c "read_aiger $OPTIMIZED_ABC_AIG; print_stats" 2>&1 | grep "i/o =") + OPTIMIZED_AND=$(echo "$OPTIMIZED_STATS" | grep -oP 'and\s*=\s*\K\d+') + + if [ -n "$OPTIMIZED_AND" ]; then + echo "" + echo "ABC Optimization Results (baseline):" + echo " Optimized: $OPTIMIZED_AND AND gates" + echo " ✓ ABC optimization complete!" + echo "" + fi + else + echo "WARNING: Baseline AIGER file ${BASE}_yosys.aig not found" + fi + fi + else + echo "" + echo "WARNING: ODC analysis failed or incomplete" + echo "" + fi + fi +fi + +# Step 6 (optional): Gate-level synthesis +if [ "$SYNTHESIZE_GATES" = true ]; then + echo "==========================================" + echo "Gate-Level Synthesis" + echo "==========================================" + echo "" + + # Check if ODC optimization produced an optimized circuit + # If so, synthesize the ODC-optimized version; otherwise synthesize the regular circuit + if [ -n "$OPTIMIZED_BASE" ] && [ -f "$OPTIMIZED_SYNTH_DIR/${OPTIMIZED_BASE}_post_abc.aig" ]; then + echo "Synthesizing ODC-optimized circuit to gate level..." + OPTIMIZED_BASE_PATH="$OPTIMIZED_SYNTH_DIR/$OPTIMIZED_BASE" + ./scripts/synth_to_gates.sh "$OPTIMIZED_BASE_PATH" "" "$CLK_NAME" "$MODULE_NAME" + + if [ $? -eq 0 ]; then + # Extract optimized total chip area (combinational + flip-flops) + OPTIMIZED_TOTAL_AREA=$(grep "Total chip area:" "$OPTIMIZED_SYNTH_DIR/${OPTIMIZED_BASE}_gates.log" 2>/dev/null | tail -1 | awk '{print $4}') + + if [ -n "$OPTIMIZED_TOTAL_AREA" ]; then + echo "" + echo "Total Chip Area:" + echo " Optimized: $OPTIMIZED_TOTAL_AREA µm²" + fi + + # Check timing metrics if available + OPTIMIZED_TIMING="${OPTIMIZED_BASE_PATH}_timing_metrics.json" + + if [ -f "$OPTIMIZED_TIMING" ]; then + OPTIMIZED_FREQ=$(python3 -c "import json; print(json.load(open('$OPTIMIZED_TIMING')).get('max_frequency_mhz', 'N/A'))" 2>/dev/null || echo "N/A") + + if [ "$OPTIMIZED_FREQ" != "N/A" ]; then + echo "" + echo "Timing (10ns target period):" + echo " Optimized: $OPTIMIZED_FREQ MHz" + fi + fi + else + echo "ERROR: Gate-level synthesis failed" + exit 1 + fi + else + echo "Synthesizing optimized circuit to gate level..." + ./scripts/synth_to_gates.sh "$BASE" "" "$CLK_NAME" "$MODULE_NAME" + + if [ $? -eq 0 ]; then + # Extract total chip area + TOTAL_AREA=$(grep "Total chip area:" "${BASE}_gates.log" 2>/dev/null | tail -1 | awk '{print $4}') + + if [ -n "$TOTAL_AREA" ]; then + echo "" + echo "Total Chip Area:" + echo " Optimized: $TOTAL_AREA µm²" + fi + + # Check timing metrics + TIMING_FILE="${BASE}_timing_metrics.json" + + if [ -f "$TIMING_FILE" ]; then + FREQ=$(python3 -c "import json; print(json.load(open('$TIMING_FILE')).get('max_frequency_mhz', 'N/A'))" 2>/dev/null || echo "N/A") + + if [ "$FREQ" != "N/A" ]; then + echo "" + echo "Timing (10ns target period):" + echo " Optimized: $FREQ MHz" + fi + fi + else + echo "ERROR: Gate-level synthesis failed" + exit 1 + fi + fi + echo "" +else + echo "To synthesize to gates, run:" + echo " ./scripts/synth_to_gates.sh $BASE \"\" \"$CLK_NAME\" \"$MODULE_NAME\"" + echo "Or use --gates flag with this script." +fi + +echo "" +if [ -f "$TIMING_CODE" ]; then + echo "The design has been synthesized with ISA + timing constraints." + echo "Logic for outlawed instructions and impossible timing scenarios" + echo "should be optimized away via assumptions and ABC optimization." +else + echo "The design has been synthesized with ISA constraints." + echo "Logic for outlawed instructions should be optimized away via" + echo "assumptions and ABC optimization." +fi