Skip to content

Commit

Permalink
Add Place-N-Route script
Browse files Browse the repository at this point in the history
Run multiple threads of nextpnr-ice40 to find a seed that gives a
layout that meets timing.
  • Loading branch information
jthornblad committed Oct 21, 2024
1 parent f666174 commit 6a38c8b
Showing 1 changed file with 237 additions and 0 deletions.
237 changes: 237 additions & 0 deletions hw/application_fpga/run_pnr.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
#!/bin/bash

usage() {
echo "Usage: $(basename $0) [OPTION]"
echo "Run multiple Place-N-Route threads with nextpnr-ice40"
echo ""
echo " -d Debugging mode, output all data from nextpnr-ice40"
echo " -f <freq> Set target frequency for design in MHz (might be overridden from value in synthesis file)"
echo " -l <log_file> Set log output filename (default: $LOG_FILE)"
echo " -n <num_threads> Set number of threads to run (default: $NUM_THREADS, max: $(nproc))"
echo " -o <out_file> Set Place-N-Route output filename (default: $OUT_FILE)"
echo " -p <pin_file> Set pin file to use (default: $PIN_FILE)"
echo " -s <synth_file> Set synthesis file to use (default: $SYNTH_FILE)"
echo " -t '<time>[SUFFIX]' Set time to run before script is killed"
echo " SUFFIX can be 's' for seconds, 'm' for minutes, 'h' for hours."
echo " Given two or more arguments, time is sum of the values."
}

export TOP_DIR=$(pwd)
export LOCKFILE=$(mktemp -u -p $TOP_DIR)
export LOCKDIR=$(mktemp -u -p $TOP_DIR)

export OUT_FILE=application_fpga_par.json
export LOG_FILE=application_fpga_par.txt

export SYNTH_FILE=synth.json
export SYNTH_FILE_SET=0

export PIN_FILE=data/application_fpga_tk1.pcf
export PIN_FILE_SET=0

export PACKAGE=sg48

export PNR_DEBUG=/dev/null

# Get number of available CPU threads
NUM_THREADS=$(nproc --ignore=1)

# Array to store process IDs
pids=()

# Trap ctrl-c and call ctrl_c()
trap 'ctrl_c' SIGINT

cleanup() {
echo "Running cleanup..."
# Kill all child processes of the script
pstree -p "$$" | grep -o '([0-9]\+)' | grep -o '[0-9]\+' | grep -v "$$" | xargs kill -9 >& /dev/null
# Do directory cleanup
rm -rf $TOP_DIR/tmp.*
echo "Cleanup finished."
}

ctrl_c() {
echo ""
cleanup
}

terminate_script() {
echo -n "Time limit reached!"
kill -SIGINT $$
}

start_thread() {
OUT_DIR="$(mktemp -d -p $TOP_DIR)" || { echo "Failed to create tempdir"; exit 1; }
cd $OUT_DIR
echo "Starting nextpnr run on PID = $BASHPID, tempdir = $OUT_DIR"
while true; do
nextpnr-ice40 \
-l $LOG_FILE \
--randomize-seed \
--freq $FREQ \
--ignore-loops \
--up5k \
--package $PACKAGE \
--json $SYNTH_FILE \
--pcf $PIN_FILE \
--write $OUT_FILE >& $PNR_DEBUG
exit_code=$?

if [ $exit_code -eq 0 ]; then
mkdir $LOCKDIR 2>/dev/null
if [ $? -ne 0 ]; then
return 1;
fi

(
flock -x 200

SEED=$(grep "Generated random seed" $LOG_FILE | awk '{print $5}' | awk --bignum '{printf "%20d\n", $0}')
MAX_CLK_PLACE=$(grep "Max frequency for clock" $LOG_FILE | awk 'NR==1{print $7}') # First occurence
MAX_CLK_ROUTE=$(grep "Max frequency for clock" $LOG_FILE | awk 'NR==2{print $7}') # Second occurence
TARGET_CLK=$(grep "Max frequency for clock" $LOG_FILE | awk 'NR==1{print $11}') # First occurence

echo -n "PASS! Seed = $SEED, Max frequency for clock: place = $MAX_CLK_PLACE MHz, "
echo "route = $MAX_CLK_ROUTE MHz, target = $TARGET_CLK MHz"
cp -v $OUT_FILE $TOP_DIR/
cp -v $LOG_FILE $TOP_DIR/

) 200>$LOCKFILE

return 0
else

(
flock -x 200

SEED=$(grep "Generated random seed" $LOG_FILE | awk '{print $5}' | awk --bignum '{printf "%20d\n", $0}')
MAX_CLK_PLACE=$(grep "Max frequency for clock" $LOG_FILE | awk 'NR==1{print $7}') # First occurence
MAX_CLK_ROUTE=$(grep "Max frequency for clock" $LOG_FILE | awk 'NR==2{print $7}') # Second occurence
TARGET_CLK=$(grep "Max frequency for clock" $LOG_FILE | awk 'NR==1{print $11}') # First occurence

echo -n "FAIL! Seed = $SEED, Max frequency for clock: place = $MAX_CLK_PLACE MHz, "
echo "route = $MAX_CLK_ROUTE MHz, target = $TARGET_CLK MHz, restarting..."
rm -rf $OUT_DIR/*

) 200>$LOCKFILE
fi
done
}

# Check flags
while getopts "df:l:n:o:p:s:t:h" arg; do
case $arg in
d)
PNR_DEBUG=1
;;
f)
FREQ=${OPTARG}
;;
l)
LOG_FILE=${OPTARG}
;;
n)
NUM_THREADS=${OPTARG}
;;
o)
OUT_FILE=${OPTARG}
;;
p)
PIN_FILE=${OPTARG}
PIN_FILE_SET=1
;;
s)
SYNTH_FILE=${OPTARG}
SYNTH_FILE_SET=1
;;
t)
TIMEOUT=${OPTARG}
;;
h)
usage
exit 0
;;
*)
#echo "Invalid argument: ${OPTARG}"
exit 1
;;
esac
done

# Function to handle failure of the sleep command
handle_sleep_failure() {
exit 1
}

export SYNTH_FILE=$(realpath $SYNTH_FILE)
export PIN_FILE=$(realpath $PIN_FILE)
export OUT_FILE=$(basename $OUT_FILE)
export LOG_FILE=$(basename $LOG_FILE)

if [ ! -f $SYNTH_FILE ]; then
echo "No $SYNTH_FILE file found. Did you run Yosys?"
exit 1
fi

if [ -f $OUT_FILE ]; then
echo "Place-N-Route output file $OUT_FILE found. Remove before starting again!"
exit 1
fi

if [ -f $LOG_FILE ]; then
echo "Log file $LOG_FILE found. Remove before starting again!"
exit 1
fi

if [ -z "$FREQ" ]; then
echo "No target frequency set!"
exit 1
else
export FREQ
fi

if [ -n "$TIMEOUT" ]; then
echo "Timeout: $TIMEOUT"
# Start background timer
( sleep ${TIMEOUT[@]} && terminate_script ) &
if ! ps -p $! > /dev/null; then
exit 1
fi
fi

echo "Threads: $NUM_THREADS"
echo "Target frequency: $FREQ MHz (might be overridden from value in synthesis file)"
echo "Synthesis file: $SYNTH_FILE"
echo "Pin file: $PIN_FILE"
echo "Package: $PACKAGE"
echo "Out file: $OUT_FILE"
echo "Log file: $LOG_FILE"
echo ""
echo "Lock file: $LOCKFILE"
echo "Lock dir: $LOCKDIR"
echo ""
# Start threads equal to the number of CPU cores
for i in $(seq 1 $NUM_THREADS); do
# Start the thread in the background
start_thread &
# Collect the PID
pids+=($!)
done

wait_for_success() {
while true; do
# Check for exit 0 status of all collected PID processes
if wait -n ${pids[@]}; then
cleanup
echo "Exiting..."
exit 0
else
echo "Exiting..."
exit 1
fi
done
}

# Wait for all threads
wait_for_success

0 comments on commit 6a38c8b

Please sign in to comment.