From e36c170204fa2beb4d9d948ba75c9a60b658ac38 Mon Sep 17 00:00:00 2001 From: Kunlin Han Date: Sat, 6 Apr 2024 16:47:32 -0700 Subject: [PATCH] Improve buildroot and linux vector. --- docs/docker/Dockerfile.builds | 17 +- docs/docker/Dockerfile.regression | 5 +- docs/docker/README.md | 37 +++ .../busybox.config | 0 .../linux.config | 0 .../main.config | 0 .../rootfs_overlay/.profile | 0 .../rootfs_overlay/etc/inittab | 0 docs/docker/run_regression.sh | 2 +- .../testvector-generation/EmulateLinux.sh | 47 ++++ docs/docker/testvector-generation/Makefile | 13 + .../testvector-generation/checkpointSweep.sh | 6 + docs/docker/testvector-generation/debug.sh | 21 ++ .../disassembleBootTrace.py | 74 ++++++ .../filterTrapsToInterrupts.py | 68 +++++ docs/docker/testvector-generation/fixBinMem.c | 33 +++ .../testvector-generation/genCheckpoint.sh | 146 +++++++++++ .../testvector-generation/genInitMem.sh | 63 +++++ .../testvector-generation/genRecording.sh | 38 +++ docs/docker/testvector-generation/genTrace.sh | 54 ++++ .../testvector-generation/parseGDBtoTrace.py | 243 ++++++++++++++++++ .../testvector-generation/parsePlicState.py | 106 ++++++++ .../testvector-generation/parseQEMUtoGDB.py | 145 +++++++++++ .../testvector-generation/parseState.py | 99 +++++++ .../testvector-generation/parseUartState.py | 50 ++++ 25 files changed, 1261 insertions(+), 6 deletions(-) rename docs/docker/{wally => buildroot-config-src}/busybox.config (100%) rename docs/docker/{wally => buildroot-config-src}/linux.config (100%) rename docs/docker/{wally => buildroot-config-src}/main.config (100%) rename docs/docker/{wally => buildroot-config-src}/rootfs_overlay/.profile (100%) rename docs/docker/{wally => buildroot-config-src}/rootfs_overlay/etc/inittab (100%) create mode 100755 docs/docker/testvector-generation/EmulateLinux.sh create mode 100644 docs/docker/testvector-generation/Makefile create mode 100755 docs/docker/testvector-generation/checkpointSweep.sh create mode 100755 docs/docker/testvector-generation/debug.sh create mode 100755 docs/docker/testvector-generation/disassembleBootTrace.py create mode 100755 docs/docker/testvector-generation/filterTrapsToInterrupts.py create mode 100644 docs/docker/testvector-generation/fixBinMem.c create mode 100755 docs/docker/testvector-generation/genCheckpoint.sh create mode 100755 docs/docker/testvector-generation/genInitMem.sh create mode 100755 docs/docker/testvector-generation/genRecording.sh create mode 100755 docs/docker/testvector-generation/genTrace.sh create mode 100755 docs/docker/testvector-generation/parseGDBtoTrace.py create mode 100755 docs/docker/testvector-generation/parsePlicState.py create mode 100755 docs/docker/testvector-generation/parseQEMUtoGDB.py create mode 100755 docs/docker/testvector-generation/parseState.py create mode 100755 docs/docker/testvector-generation/parseUartState.py diff --git a/docs/docker/Dockerfile.builds b/docs/docker/Dockerfile.builds index 7957af5a8..ec157de07 100644 --- a/docs/docker/Dockerfile.builds +++ b/docs/docker/Dockerfile.builds @@ -60,15 +60,24 @@ RUN opam init -y --disable-sandboxing && \ cp ${RISCV}/sail-riscv/c_emulator/riscv_sim_RV32 ${RISCV}/bin/riscv_sim_RV32 && \ rm -rf ${RISCV}/sail-riscv -COPY ./wally /opt/riscv/wally_buildroot +COPY ./buildroot-config-src /opt/riscv/buildroot-config-src +COPY ./testvector-generation /opt/riscv/testvector-generation -# Buildroot +# # Buildroot RUN git clone https://github.com/buildroot/buildroot.git && \ cd buildroot && \ git checkout 2021.05 && \ - cp -r /opt/riscv/wally_buildroot ./board/wally && \ + cp -r /opt/riscv/buildroot-config-src ./board/wally && \ cp ./board/wally/main.config .config && \ - make --jobs ${NUM_THREADS} + make --jobs ${NUM_THREADS} && \ + # generate files for buildroot regression + mkdir -p ${RISCV}/linux-testvectors && \ + cd /opt/riscv/testvector-generation && \ + curl https://raw.githubusercontent.com/openhwgroup/cvw/main/linux/devicetree/wally-virt.dts --output ${RISCV}/buildroot/output/images/wally-virt.dts && \ + dtc -I dts -O dtb ${RISCV}/buildroot/output/images/wally-virt.dts > ${RISCV}/buildroot/output/images/wally-virt.dtb && \ + make && ./genInitMem.sh && \ + chmod -R a+rw ${RISCV}/linux-testvectors && \ + rm -rf ${RISCV}/buildroot RUN pip3 install --no-cache-dir \ testresources riscv_config \ diff --git a/docs/docker/Dockerfile.regression b/docs/docker/Dockerfile.regression index 50579723b..3ff472909 100644 --- a/docs/docker/Dockerfile.regression +++ b/docs/docker/Dockerfile.regression @@ -5,8 +5,11 @@ ENV RUN_QUESTA=false ENV USERNAME=cad VOLUME [ "/home/${USERNAME}/cvw" ] +USER root + +COPY --chown=${USERNAME}:${USERNAME} . /home/${USERNAME} +RUN chown -R ${USERNAME}:${USERNAME} /home/${USERNAME} -COPY . /home/${USERNAME} USER ${USERNAME} WORKDIR /home/${USERNAME}/cvw diff --git a/docs/docker/README.md b/docs/docker/README.md index 65ca3ac5c..d5c4c1ffa 100644 --- a/docs/docker/README.md +++ b/docs/docker/README.md @@ -7,6 +7,8 @@ Hazards: - If there is any change in `${CVW_HOME}/linux/buildroot-config-src` folder with main.config, you have to copy it to the current folder to `buildroot-config-src` - If there is any change in `${CVW_HOME}/linux/testvector-generation` folder with main.config, you have to copy it to the current folder to `testvector-generation` +If you have any other questions, please read the [troubleshooting]() first. + ## TODOs - [ ] Pinning the tools version @@ -21,6 +23,7 @@ Hazards: - [x] Configure the license for Questa - [ ] Change the condition from empty string to 1 - [ ] Add linux testvector-generation + - [ ] Estimate the useless building intermediate files ## TL;DR @@ -284,6 +287,40 @@ There are stages in the old Dockerfile: - buildroot: `2021.05` - verilator: `v5.022` +## Troubleshooting + +### Permission Denied for .git + +Description: permission problem in `/home/$USERNAME/cvw`. + +```text +$ podman run -v cvw_temp:/home/cad/cvw -e CLEAN_CVW= -e BUILD_RISCOF= -e RUN_QUESTA= -v /cad/mentor/que +sta_sim-2023.4:/cad/mentor/questa_sim-xxxx.x_x --rm wallysoc/regression_wally +No CVW_GIT is provided +rm: cannot remove '/home/cad/cvw': Device or resource busy +Cloning into '/home/cad/cvw'... +/home/cad/cvw/.git: Permission denied +chmod: cannot access '/home/cad/cvw/setup.sh': No such file or directory +chmod: cannot access '/home/cad/cvw/site-setup.sh': No such file or directory +make: *** No rule to make target 'install'. Stop. +make: *** No rule to make target 'verify'. Stop. +make: *** No rule to make target 'coverage'. Stop. +make: *** No rule to make target 'benchmarks'. Stop. +/home/cad/run_regression.sh: line 64: cd: /home/cad/cvw/sim: No such file or directory +/home/cad/run_regression.sh: line 65: /home/cad/cvw/sim/regression_verilator.out: No such file or direc +tory +``` + +It may be caused by podman and I am not sure why it happens. + +Solution: get into the container with interaction as root and change the permission of the folder/volume `/home/$USERNAME/cvw` + +```shell +podman run -it --user root -v cvw_temp:/home/cad/cvw -e RUN_QUESTA= -v /cad/mentor/questa_sim-2023.4:/cad/mentor/questa_sim-xxxx.x_x --net=host --rm --privileged wallysoc/regression_wally /bin/bash + +chown -R $USERNAME:$USERNAME /home/$USERNAME +``` + ## References - Dockerfile Docs: https://docs.docker.com/reference/dockerfile/ diff --git a/docs/docker/wally/busybox.config b/docs/docker/buildroot-config-src/busybox.config similarity index 100% rename from docs/docker/wally/busybox.config rename to docs/docker/buildroot-config-src/busybox.config diff --git a/docs/docker/wally/linux.config b/docs/docker/buildroot-config-src/linux.config similarity index 100% rename from docs/docker/wally/linux.config rename to docs/docker/buildroot-config-src/linux.config diff --git a/docs/docker/wally/main.config b/docs/docker/buildroot-config-src/main.config similarity index 100% rename from docs/docker/wally/main.config rename to docs/docker/buildroot-config-src/main.config diff --git a/docs/docker/wally/rootfs_overlay/.profile b/docs/docker/buildroot-config-src/rootfs_overlay/.profile similarity index 100% rename from docs/docker/wally/rootfs_overlay/.profile rename to docs/docker/buildroot-config-src/rootfs_overlay/.profile diff --git a/docs/docker/wally/rootfs_overlay/etc/inittab b/docs/docker/buildroot-config-src/rootfs_overlay/etc/inittab similarity index 100% rename from docs/docker/wally/rootfs_overlay/etc/inittab rename to docs/docker/buildroot-config-src/rootfs_overlay/etc/inittab diff --git a/docs/docker/run_regression.sh b/docs/docker/run_regression.sh index 6bea790c3..3e202ed4b 100755 --- a/docs/docker/run_regression.sh +++ b/docs/docker/run_regression.sh @@ -14,7 +14,7 @@ # now only main branch is supported if [ -z "${CVW_GIT}" ]; then echo "No CVW_GIT is provided" - CVW_GIT="https://github.com/openhwgroup/cvw" + export CVW_GIT="https://github.com/openhwgroup/cvw" fi git config --global http.version HTTP/1.1 diff --git a/docs/docker/testvector-generation/EmulateLinux.sh b/docs/docker/testvector-generation/EmulateLinux.sh new file mode 100755 index 000000000..e2b50af4d --- /dev/null +++ b/docs/docker/testvector-generation/EmulateLinux.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +usage() { echo "Usage: $0 [-h] [-b ] [-d ]" 1>&2; exit 1; } + +help() { + echo "Usage: $0 [OPTIONS] " + echo " -b get images from given buildroot" + echo " -d specify device tree to use" + exit 0; +} + +# defaults +imageDir=$RISCV/buildroot/output/images +DEVICE_TREE=${imageDir}/wally-virt.dtb + +# Process options and arguments. The following code grabs the single +# sdcard device argument no matter where it is in the positional +# parameters list. +ARGS=() +while [ $OPTIND -le "$#" ] ; do + if getopts "hb:d:" arg ; then + case "${arg}" in + h) help + ;; + b) BUILDROOT=${OPTARG} + ;; + d) DEVICE_TREE=${OPTARG} + ;; + esac + else + ARGS+=("${!OPTIND}") + ((OPTIND++)) + fi +done + +# File location variables +imageDir=$BUILDROOT/output/images + +tvDir=$RISCV/linux-testvectors +tcpPort=1239 + +# QEMU Simulation +qemu-system-riscv64 \ +-M virt -m 256M -dtb $DEVICE_TREE \ +-nographic \ +-bios $imageDir/fw_jump.elf -kernel $imageDir/Image -append "root=/dev/vda ro" +-singlestep -rtc clock=vm -icount shift=0,align=off,sleep=on diff --git a/docs/docker/testvector-generation/Makefile b/docs/docker/testvector-generation/Makefile new file mode 100644 index 000000000..c31aae39d --- /dev/null +++ b/docs/docker/testvector-generation/Makefile @@ -0,0 +1,13 @@ +SHELL = /bin/sh + +CFLAG = -Wall -g +CC = gcc + +all: fixBinMem + +fixBinMem: fixBinMem.c + ${CC} ${CFLAGS} fixBinMem.c -o fixBinMem + chmod +x fixBinMem + +clean: + -rm -f fixBinMem diff --git a/docs/docker/testvector-generation/checkpointSweep.sh b/docs/docker/testvector-generation/checkpointSweep.sh new file mode 100755 index 000000000..075ca253c --- /dev/null +++ b/docs/docker/testvector-generation/checkpointSweep.sh @@ -0,0 +1,6 @@ +#!/bin/bash +for index in {450..500}; +do + instrs=$(($index*1000000)) + echo "y" | nice -n 5 ./genCheckpoint.sh $instrs +done diff --git a/docs/docker/testvector-generation/debug.sh b/docs/docker/testvector-generation/debug.sh new file mode 100755 index 000000000..7ca3e9b15 --- /dev/null +++ b/docs/docker/testvector-generation/debug.sh @@ -0,0 +1,21 @@ +#!/bin/bash +imageDir=$RISCV/buildroot/output/images +tvDir=$RISCV/linux-testvectors +tcpPort=1239 + +# QEMU Simulation +qemu-system-riscv64 \ +-M virt -dtb $imageDir/wally-virt.dtb \ +-nographic \ +-bios $imageDir/fw_jump.elf -kernel $imageDir/Image -append "root=/dev/vda ro" -initrd $imageDir/rootfs.cpio \ +-singlestep -rtc clock=vm -icount shift=0,align=off,sleep=on +# > ./qemu-serial \ +# -gdb tcp::$tcpPort -S) \ +# & riscv64-unknown-elf-gdb -quiet \ +# -ex "set pagination off" \ +# -ex "set logging overwrite on" \ +# -ex "set logging redirect on" \ +# -ex "set confirm off" \ +# -ex "target extended-remote :$tcpPort" \ +# -ex "maintenance packet Qqemu.PhyMemMode:1" \ +# -ex "file $imageDir/vmlinux" diff --git a/docs/docker/testvector-generation/disassembleBootTrace.py b/docs/docker/testvector-generation/disassembleBootTrace.py new file mode 100755 index 000000000..12e2202cb --- /dev/null +++ b/docs/docker/testvector-generation/disassembleBootTrace.py @@ -0,0 +1,74 @@ +#!/usr/bin/python3 +# +# disassembleBootTrace.py +# David_Harris@hmc.edu 22 November 2023 +# SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1 +# +# Reads boottrace.log and disassembles the machine code +# + +import csv +import os +import re + +# read a file from sim/logs/boottrace.log and extract the second comma-separated field containing the instruction +print("Reading boottrace.log") +trace = [] +count = 0 +with open('../../sim/logs/boottrace.log') as f: + reader = csv.reader(f, delimiter=',') + for row in reader: + trace.append(row) + count = count + 1 + if count > 50000000: + break +f.close() + +print("Disassembling boottrace.log instructions") +# Write an assembly language file with the machine code +with (open('boottrace.S', 'w')) as f: + f.write('main:\n') + for row in trace: + instr = row[1] + # scrape off leading white space from instr + instr = instr.lstrip() + # check if last character indicates an compressed or uncompressed instruction + lastNibble = instr[-1] + if (lastNibble == '3' or lastNibble == '7' or lastNibble == 'b' or lastNibble == 'f'): + # uncompressed + f.write('.word 0x' + instr + '\n') + else: + # compressed + instr = instr[-4:] + f.write('.hword 0x' + instr + '\n') +f.close() + +# Then assemble and disassemble the file +os.system('riscv64-unknown-elf-gcc -march=rv64gqc_zba_zbb_zbc_zbs_zfh_zicboz_zicbop_zicbom -mabi=lp64d -c boottrace.S') +os.system('riscv64-unknown-elf-objdump -D boottrace.o > boottrace.objdump') + +# Patch disassembly back into boottrace +print("Inserting disassembly into trace") +dumpedLines = [] +with (open('boottrace.objdump', 'r')) as f: + lines = f.readlines() + f.close() +lines = lines[7:] # skip header +p = r'[^:]*:\s*(\S*)\s*(.*)' +for line in lines: + match = re.search(p, line) + if (match): + dump = [match.group(1), match.group(2)] + dumpedLines.append(dump) + +linenum = 0 +for i in range(len(trace)): + row = trace[i] + row.insert(2, dumpedLines[i][1]) + +# write trace back to csv file +print("Writing trace back to boottrace_disasm.log") +with (open('boottrace_disasm.log', 'w')) as f: + writer = csv.writer(f) + writer.writerows(trace) +f.close() diff --git a/docs/docker/testvector-generation/filterTrapsToInterrupts.py b/docs/docker/testvector-generation/filterTrapsToInterrupts.py new file mode 100755 index 000000000..de0553098 --- /dev/null +++ b/docs/docker/testvector-generation/filterTrapsToInterrupts.py @@ -0,0 +1,68 @@ +#! /usr/bin/python3 +import sys, os +from functools import reduce + +################ +# Helper Funcs # +################ + +def tokenize(string): + tokens = [] + token = '' + whitespace = 0 + prevWhitespace = 0 + for char in string: + prevWhitespace = whitespace + whitespace = char in ' \t\n' + if (whitespace): + if ((not prevWhitespace) and (token != '')): + tokens.append(token) + token = '' + else: + token = token + char + return tokens + +def strip0x(num): + return num[2:] + +def stripZeroes(num): + num = num.strip('0') + if num=='': + return '0' + else: + return num + +############# +# Main Code # +############# +print("Begin filtering traps down to just external interrupts.") + +# Parse Args +if len(sys.argv) != 2: + sys.exit('Error filterTrapsToInterrupts.py expects 1 arg: ') +tvDir = sys.argv[1]+'/' +trapsFilePath = tvDir+'traps.txt' +if not os.path.exists(trapsFilePath): + sys.exit('Error input file '+trapsFilePath+'not found') + +with open(tvDir+'interrupts.txt', 'w') as interruptsFile: + with open(trapsFilePath, 'r') as trapsFile: + while True: + trap = trapsFile.readline() + if trap == '': + break + trapType = trap.split(' ')[-1] + if ('interrupt' in trap) and (('external' in trapType) or ('m_timer' in trapType)): # no s_timer because that is not controlled by CLINT + interruptsFile.write(trap) # overall line + interruptsFile.write(trapsFile.readline()) # attempted instr count + interruptsFile.write(trapsFile.readline()) # hart # + interruptsFile.write(trapsFile.readline()) # asynchronous + interruptsFile.write(trapsFile.readline()) # cause + interruptsFile.write(trapsFile.readline()) # epc + interruptsFile.write(trapsFile.readline()) # tval + interruptsFile.write(trapsFile.readline()) # description + else: + for i in range(7): + trapsFile.readline() + +print("Finished filtering traps down to just external interrupts.") diff --git a/docs/docker/testvector-generation/fixBinMem.c b/docs/docker/testvector-generation/fixBinMem.c new file mode 100644 index 000000000..fe071008b --- /dev/null +++ b/docs/docker/testvector-generation/fixBinMem.c @@ -0,0 +1,33 @@ +#include +#include +#include +int main(int argc, char *argv[]) { + if (argc < 3){ + fprintf(stderr, "Expected 2 arguments: \n"); + exit(1); + } + char* rawGDBfilePath = argv[1]; + FILE* rawGDBfile; + if ((rawGDBfile = fopen(rawGDBfilePath,"rb"))==NULL) { + fprintf(stderr, "File not found: %s\n",rawGDBfilePath); + exit(1); + } + char* outFilePath = argv[2]; + FILE* outFile = fopen(outFilePath,"w"); + uint64_t qemuWord; + uint64_t verilogWord; + int bytesReturned=0; + do { + bytesReturned=fread(&qemuWord, 8, 1, rawGDBfile); + verilogWord = (((qemuWord>>0 )&0xff)<<56 | + ((qemuWord>>8 )&0xff)<<48 | + ((qemuWord>>16)&0xff)<<40 | + ((qemuWord>>24)&0xff)<<32 | + ((qemuWord>>32)&0xff)<<24 | + ((qemuWord>>40)&0xff)<<16 | + ((qemuWord>>48)&0xff)<<8 | + ((qemuWord>>56)&0xff)<<0); + fwrite(&verilogWord, 8, 1, outFile); + } while(bytesReturned!=0); + return 0; +} diff --git a/docs/docker/testvector-generation/genCheckpoint.sh b/docs/docker/testvector-generation/genCheckpoint.sh new file mode 100755 index 000000000..f12223f54 --- /dev/null +++ b/docs/docker/testvector-generation/genCheckpoint.sh @@ -0,0 +1,146 @@ +#!/bin/bash +tcpPort=1238 +imageDir=$RISCV/buildroot/output/images +tvDir=$RISCV/linux-testvectors +recordFile="$tvDir/all.qemu" +traceFile="$tvDir/all.txt" + +# Parse Commandline Arg +if [ "$#" -ne 1 ]; then + echo "genCheckpoint requires 1 argument: " >&2 + exit 1 +fi +instrs=$1 +if ! [ "$instrs" -eq "$instrs" ] 2> /dev/null +then + echo "Error expected integer number of instructions, got $instrs" >&2 + exit 1 +fi + +checkPtDir="$tvDir/checkpoint$instrs" +outTraceFile="$checkPtDir/all.txt" +rawStateFile="$checkPtDir/stateGDB.txt" +rawUartStateFile="$checkPtDir/uartStateGDB.txt" +uartStateFile="$checkPtDir/checkpoint-UART" +rawPlicStateFile="$checkPtDir/plicStateGDB.txt" +plicStateFile="$checkPtDir/checkpoint-PLIC" +rawRamFile="$checkPtDir/ramGDB.bin" +ramFile="$checkPtDir/ram.bin" + +read -p "This scripts is going to create a checkpoint at $instrs instrs. +Is that what you wanted? (y/n) " -n 1 -r +echo +if [[ $REPLY =~ ^[Yy]$ ]] +then + echo "Creating checkpoint at $instrs instructions!" + if [ ! -d "$tvDir" ]; then + echo "Error: linux testvector directory $tvDir not found!">&2 + echo "Please create it. For example:">&2 + echo " sudo mkdir -p $tvDir">&2 + exit 1 + fi + test -w $tvDir + if [ ! $? -eq 0 ]; then + echo "Error: insuffcient write privileges for linux testvector directory $tvDir !">&2 + echo "Please chmod it. For example:">&2 + echo " sudo chmod -R a+rw $tvDir">&2 + exit 1 + fi + + mkdir -p $checkPtDir + + # Identify instruction in trace + instr=$(sed "${instrs}q;d" "$traceFile") + echo "Found ${instrs}th instr: ${instr}" + pc=$(echo $instr | cut -d " " -f1) + asm=$(echo $instr | cut -d " " -f2) + occurences=$(($(head -$instrs "$traceFile" | grep -c "${pc} ${asm}")-1)) + echo "It occurs ${occurences} times before the ${instrs}th instr." + + # Create GDB script because GDB is terrible at handling arguments / variables + cat > genCheckpoint.gdb <<- end_of_script + set pagination off + set logging overwrite on + set logging redirect on + set confirm off + target extended-remote :$tcpPort + maintenance packet Qqemu.PhyMemMode:1 + file $imageDir/vmlinux + # Step over reset vector into actual code + stepi 100 + shell echo \"GDB proceeding to checkpoint at $instrs instrs, pc $pc\" + b *0x$pc + ignore 1 $occurences + c + shell echo \"Reached checkpoint at $instrs instrs\" + shell echo \"GDB storing CPU state to $rawStateFile\" + set logging file $rawStateFile + set logging on + info all-registers + set logging off + shell echo \"GDB storing UART state to $rawUartStateFile\" + # Save value of LCR + set \$LCR=*0x10000003 & 0xff + set logging file $rawUartStateFile + set logging on + # Change LCR to set DLAB=0 to be able to read RBR and IER + set {char}0x10000003 &= ~0x80 + x/1xb 0x10000000 + x/1xb 0x10000001 + x/1xb 0x10000002 + # But log original value of LCR + printf "0x10000003:\t0x%02x\n", \$LCR + x/1xb 0x10000004 + x/1xb 0x10000005 + x/1xb 0x10000006 + x/1xb 0x10000007 + set logging off + shell echo \"GDB storing PLIC state to $rawPlicStateFile\" + shell echo \"Note: this dumping assumes a maximum of 63 PLIC sources\" + set logging file $rawPlicStateFile + set logging on + # Priority Levels for sources 1 thru 63 + x/63xw 0x0C000004 + # Interrupt Enables for sources 1 thru 63 for contexts 0 and 1 + x/2xw 0x0C002000 + x/2xw 0x0C002080 + # Global Priority Threshold for contexts 0 and 1 + x/1xw 0x0C200000 + x/1xw 0x0C201000 + set logging off + shell echo \"GDB storing RAM to $rawRamFile\" + dump binary memory $rawRamFile 0x80000000 0x87ffffff + kill + q +end_of_script + + # GDB+QEMU + echo "Starting QEMU in replay mode with attached GDB script at $(date +%H:%M:%S)" + (qemu-system-riscv64 \ + -M virt -dtb $imageDir/wally-virt.dtb \ + -nographic \ + -bios $imageDir/fw_jump.elf -kernel $imageDir/Image -append "root=/dev/vda ro" -initrd $imageDir/rootfs.cpio \ + -singlestep -rtc clock=vm -icount shift=0,align=off,sleep=on,rr=replay,rrfile=$recordFile \ + -gdb tcp::$tcpPort -S \ + 1>./qemu-serial) \ + & riscv64-unknown-elf-gdb --quiet -x genCheckpoint.gdb + + echo "Completed GDB script at $(date +%H:%M:%S)" + + # Post-Process GDB outputs + ./parseState.py "$checkPtDir" + ./parseUartState.py "$checkPtDir" + ./parsePlicState.py "$checkPtDir" + echo "Changing Endianness at $(date +%H:%M:%S)" + make fixBinMem + ./fixBinMem "$rawRamFile" "$ramFile" + echo "Copying over a truncated trace" + tail -n+$instrs $traceFile > $outTraceFile + + echo "Checkpoint completed at $(date +%H:%M:%S)" + echo "You may want to restrict write access to $tvDir now and give cad ownership of it." + echo "Run the following:" + echo " sudo chown -R cad:cad $tvDir" + echo " sudo chmod -R go-w $tvDir" +fi + diff --git a/docs/docker/testvector-generation/genInitMem.sh b/docs/docker/testvector-generation/genInitMem.sh new file mode 100755 index 000000000..b9c1d8c41 --- /dev/null +++ b/docs/docker/testvector-generation/genInitMem.sh @@ -0,0 +1,63 @@ +#!/bin/bash +tcpPort=1235 +imageDir=$RISCV/buildroot/output/images +tvDir=$RISCV/linux-testvectors +rawRamFile="$tvDir/ramGDB.bin" +ramFile="$tvDir/ram.bin" +rawBootmemFile="$tvDir/bootmemGDB.bin" +bootmemFile="$tvDir/bootmem.bin" +rawUntrimmedBootmemFile="$tvDir/untrimmedBootmemFileGDB.bin" +untrimmedBootmemFile="$tvDir/untrimmedBootmemFile.bin" +DEVICE_TREE=${imageDir}/wally-virt.dtb + +if [ ! -d "$tvDir" ]; then + echo "Error: linux testvector directory $tvDir not found!">&2 + echo "Please create it. For example:">&2 + echo " sudo mkdir -p $tvDir">&2 + exit 1 +fi +test -w $tvDir +if [ ! $? -eq 0 ]; then + echo "Error: insuffcient write privileges for linux testvector directory $tvDir !">&2 + echo "Please chmod it. For example:">&2 + echo " sudo chmod -R a+rw $tvDir">&2 + exit 1 +fi + +echo "Launching QEMU in replay mode!" +(qemu-system-riscv64 \ +-M virt -m 256M -dtb $DEVICE_TREE \ +-nographic \ +-bios $imageDir/fw_jump.elf -kernel $imageDir/Image -append "root=/dev/vda ro" -initrd $imageDir/rootfs.cpio \ +-gdb tcp::$tcpPort -S) \ +& riscv64-unknown-elf-gdb --quiet \ +-ex "set pagination off" \ +-ex "set logging overwrite on" \ +-ex "set logging redirect on" \ +-ex "set confirm off" \ +-ex "target extended-remote :$tcpPort" \ +-ex "maintenance packet Qqemu.PhyMemMode:1" \ +-ex "printf \"Creating $rawBootmemFile\n\"" \ +-ex "dump binary memory $rawBootmemFile 0x1000 0x1fff" \ +-ex "printf \"Creating $rawRamFile\n\"" \ +-ex "dump binary memory $rawRamFile 0x80000000 0x8fffffff" \ +-ex "kill" \ +-ex "q" + +#-ex "printf \"Warning - please verify that the second half of $rawUntrimmedBootmemFile is all 0s\n\"" \ +#-ex "printf \"Creating $rawUntrimmedBootmemFile\n\"" \ +#-ex "dump binary memory $rawUntrimmedBootmemFile 0x1000 0x2fff" \ + +echo "Changing Endianness" +make fixBinMem +./fixBinMem "$rawRamFile" "$ramFile" +./fixBinMem "$rawBootmemFile" "$bootmemFile" +#./fixBinMem "$rawUntrimmedBootmemFile" "$untrimmedBootmemFile" # doesn't seem to be used for anything +rm -f "$rawRamFile" "$rawBootmemFile" "$rawUntrimmedBootmemFile" + +echo "genInitMem.sh completed!" +echo "You may want to restrict write access to $tvDir now and give cad ownership of it." +echo "Run the following:" +echo " sudo chown -R cad:cad $tvDir" +echo " sudo chmod -R go-w $tvDir" + diff --git a/docs/docker/testvector-generation/genRecording.sh b/docs/docker/testvector-generation/genRecording.sh new file mode 100755 index 000000000..2696253c5 --- /dev/null +++ b/docs/docker/testvector-generation/genRecording.sh @@ -0,0 +1,38 @@ +#!/bin/bash +imageDir=$RISCV/buildroot/output/images +tvDir=$RISCV/linux-testvectors +recordFile="$tvDir/all.qemu" +DEVICE_TREE=${imageDir}/wally-virt.dtb + +read -p "Warning: running this script will overwrite $recordFile +Would you like to proceed? (y/n) " -n 1 -r +echo +if [[ $REPLY =~ ^[Yy]$ ]] +then + if [ ! -d "$tvDir" ]; then + echo "Error: linux testvector directory $tvDir not found!">&2 + echo "Please create it. For example:">&2 + echo " sudo mkdir -p $tvDir">&2 + exit 1 + fi + test -w $tvDir + if [ ! $? -eq 0 ]; then + echo "Error: insuffcient write privileges for linux testvector directory $tvDir !">&2 + echo "Please chmod it. For example:">&2 + echo " sudo chmod -R a+rw $tvDir">&2 + exit 1 + fi + + echo "Launching QEMU in record mode!" + qemu-system-riscv64 \ + -M virt -m 256M -dtb $DEVICE_TREE \ + -nographic \ + -bios $imageDir/fw_jump.elf -kernel $imageDir/Image -append "root=/dev/vda ro" -initrd $imageDir/rootfs.cpio \ + -singlestep -rtc clock=vm -icount shift=0,align=off,sleep=on,rr=record,rrfile=$recordFile + + echo "genRecording.sh completed!" + echo "You may want to restrict write access to $tvDir now and give cad ownership of it." + echo "Run the following:" + echo " sudo chown -R cad:cad $tvDir" + echo " sudo chmod -R go-w $tvDir" +fi diff --git a/docs/docker/testvector-generation/genTrace.sh b/docs/docker/testvector-generation/genTrace.sh new file mode 100755 index 000000000..5d04592ce --- /dev/null +++ b/docs/docker/testvector-generation/genTrace.sh @@ -0,0 +1,54 @@ +#!/bin/bash +tcpPort=1234 +imageDir=$RISCV/buildroot/output/images +tvDir=$RISCV/linux-testvectors +recordFile="$tvDir/all.qemu" +traceFile="$tvDir/all.txt" +trapsFile="$tvDir/traps.txt" +interruptsFile="$tvDir/interrupts.txt" +DEVICE_TREE=${imageDir}/wally-virt.dtb + +read -p "Warning: running this script will overwrite the contents of: + * $traceFile + * $trapsFile + * $interruptsFile +Would you like to proceed? (y/n) " -n 1 -r +echo +if [[ $REPLY =~ ^[Yy]$ ]] +then + if [ ! -d "$tvDir" ]; then + echo "Error: linux testvector directory $tvDir not found!">&2 + echo "Please create it. For example:">&2 + echo " sudo mkdir -p $tvDir">&2 + exit 1 + fi + test -w $tvDir + if [ ! $? -eq 0 ]; then + echo "Error: insuffcient write privileges for linux testvector directory $tvDir !">&2 + echo "Please chmod it. For example:">&2 + echo " sudo chmod -R a+rw $tvDir">&2 + exit 1 + fi + + touch $traceFile + touch $trapsFile + touch $interruptsFile + + # QEMU Simulation + echo "Launching QEMU in replay mode!" + (qemu-system-riscv64 \ + -M virt -m 256M -dtb $DEVICE_TREE \ + -nographic \ + -bios $imageDir/fw_jump.elf -kernel $imageDir/Image -append "root=/dev/vda ro" -initrd $imageDir/rootfs.cpio \ + -singlestep -rtc clock=vm -icount shift=0,align=off,sleep=on,rr=replay,rrfile=$recordFile \ + -d nochain,cpu,in_asm,int \ + 2>&1 >./qemu-serial | ./parseQEMUtoGDB.py | ./parseGDBtoTrace.py $trapsFile > $traceFile) + + ./filterTrapsToInterrupts.py $tvDir + + echo "genTrace.sh completed!" + echo "You may want to restrict write access to $tvDir now and give cad ownership of it." + echo "Run the following:" + echo " sudo chown -R cad:cad $tvDir" + echo " sudo chmod -R go-w $tvDir" +fi diff --git a/docs/docker/testvector-generation/parseGDBtoTrace.py b/docs/docker/testvector-generation/parseGDBtoTrace.py new file mode 100755 index 000000000..9a467e3e2 --- /dev/null +++ b/docs/docker/testvector-generation/parseGDBtoTrace.py @@ -0,0 +1,243 @@ +#! /usr/bin/python3 +import sys, fileinput, re + +# Ross Thompson +# July 27, 2021 +# Rewrite of the linux trace parser. + + +InstrStartDelim = '=>' +InstrEndDelim = '-----' + +#InputFile = 'noparse.txt' +#InputFile = sys.stdin +#InputFile = 'temp.txt' +#OutputFile = 'parsedAll.txt' + +HUMAN_READABLE = False + +def toDict(lst): + 'Converts the list of register values to a dictionary' + dct= {} + for item in lst: + regTup = item.split() + dct[regTup[0]] = int(regTup[2], 10) + del dct['pc'] + return dct + +def whichClass(text, Regs): + 'Which instruction class?' + #print(text, Regs) + if text[0:2] == 'ld' or text[0:2] == 'lw' or text[0:2] == 'lh' or text[0:2] == 'lb': + return ('load', WhatAddr(text, Regs), None, WhatMemDestSource(text)) + elif text[0:2] == 'sd' or text[0:2] == 'sw' or text[0:2] == 'sh' or text[0:2] == 'sb': + return ('store', WhatAddr(text, Regs), WhatMemDestSource(text), None) + elif text[0:3] == 'amo': + return ('amo', WhatAddrAMO(text, Regs), WhatMemDestSource(text), WhatMemDestSource(text)) + elif text[0:2] == 'lr': + return ('lr', WhatAddrLR(text, Regs), None, WhatMemDestSource(text)) + elif text[0:2] == 'sc': + return ('sc', WhatAddrSC(text, Regs), WhatMemDestSource(text), None) + else: + return ('other', None, None, None) + +def whatChanged(dct0, dct1): + 'Compares two dictionaries of instrution registers and indicates which registers changed' + dct = {} + for key in dct0: + if (dct1[key] != dct0[key]): + dct[key] = dct1[key] + return dct + +def WhatMemDestSource(text): + ''''What is the destination register. Used to compute where the read data is + on a load or the write data on a store.''' + return text.split()[1].split(',')[0] + +def WhatAddr(text, Regs): + 'What is the data memory address?' + Imm = text.split(',')[1] + (Imm, Src) = Imm.split('(') + Imm = int(Imm.strip(), 10) + Src = Src.strip(')').strip() + RegVal = Regs[Src] + return Imm + RegVal + +def WhatAddrAMO(text, Regs): + 'What is the data memory address?' + Src = text.split('(')[1] + Src = Src.strip(')').strip() + return Regs[Src] + +def WhatAddrLR(text, Regs): + 'What is the data memory address?' + Src = text.split('(')[1] + Src = Src.strip(')').strip() + return Regs[Src] + +def WhatAddrSC(text, Regs): + 'What is the data memory address?' + Src = text.split('(')[1] + Src = Src.strip(')').strip() + return Regs[Src] + +def PrintInstr(instr): + if instr[2] == None: + return + ChangedRegisters = instr[4] + GPR = '' + CSR = [] + for key in ChangedRegisters: + # filter out csr which are not checked. + if(key in RegNumber): + if(RegNumber[key] < 32): + # GPR + if(HUMAN_READABLE): + GPR = '{:-2d} {:016x}'.format(RegNumber[key], ChangedRegisters[key]) + else: + GPR = '{:d} {:x}'.format(RegNumber[key], ChangedRegisters[key]) + else: + if(HUMAN_READABLE): + CSR.extend([key, '{:016x}'.format(ChangedRegisters[key])]) + else: + CSR.extend([key, '{:x}'.format(ChangedRegisters[key])]) + + CSRStr = ' '.join(CSR) + + #print(instr) + + if (HUMAN_READABLE == True): + outString='{:016x} {:08x} {:25s}'.format(instr[0], instr[1], instr[2]) + if(len(GPR) != 0): + outString+=' GPR {}'.format(GPR) + if(instr[3] == 'load' or instr[3] == 'lr'): + outString+=' MemR {:016x} {:016x} {:016x}'.format(instr[5], 0, instr[7]) + if(instr[3] == 'store'): + outString+='\t\t\t MemW {:016x} {:016x} {:016x}'.format(instr[5], instr[6], 0) + if(len(CSR) != 0): + outString+=' CSR {}'.format(CSRStr) + else: + outString='{:x} {:x} {:s}'.format(instr[0], instr[1], instr[2].replace(' ', '_')) + if(len(GPR) != 0): + outString+=' GPR {}'.format(GPR) + if(instr[3] == 'load' or instr[3] == 'lr'): + outString+=' MemR {:x} {:x} {:x}'.format(instr[5], 0, instr[7]) + if(instr[3] == 'store'): + outString+=' MemW {:x} {:x} {:x}'.format(instr[5], instr[6], 0) + if(len(CSR) != 0): + outString+=' CSR {}'.format(CSRStr) + outString+='\n' + return outString + +# ========= +# Main Code +# ========= +# Parse argument for interrupt file +if len(sys.argv) != 2: + sys.exit('Error parseGDBtoTrace.py expects 1 arg:\n >') +interruptFname = sys.argv[1] +# reg number +RegNumber = {'zero': 0, 'ra': 1, 'sp': 2, 'gp': 3, 'tp': 4, 't0': 5, 't1': 6, 't2': 7, 's0': 8, 's1': 9, 'a0': 10, 'a1': 11, 'a2': 12, 'a3': 13, 'a4': 14, 'a5': 15, 'a6': 16, 'a7': 17, 's2': 18, 's3': 19, 's4': 20, 's5': 21, 's6': 22, 's7': 23, 's8': 24, 's9': 25, 's10': 26, 's11': 27, 't3': 28, 't4': 29, 't5': 30, 't6': 31, 'mhartid': 32, 'mstatus': 33, 'mip': 34, 'mie': 35, 'mideleg': 36, 'medeleg': 37, 'mtvec': 38, 'stvec': 39, 'mepc': 40, 'sepc': 41, 'mcause': 42, 'scause': 43, 'mtval': 44, 'stval': 45, 'mscratch': 46, 'sscratch': 47, 'satp': 48} +# initial state +CurrentInstr = ['0', '0', None, 'other', {'zero': 0, 'ra': 0, 'sp': 0, 'gp': 0, 'tp': 0, 't0': 0, 't1': 0, 't2': 0, 's0': 0, 's1': 0, 'a0': 0, 'a1': 0, 'a2': 0, 'a3': 0, 'a4': 0, 'a5': 0, 'a6': 0, 'a7': 0, 's2': 0, 's3': 0, 's4': 0, 's5': 0, 's6': 0, 's7': 0, 's8': 0, 's9': 0, 's10': 0, 's11': 0, 't3': 0, 't4': 0, 't5': 0, 't6': 0, 'mhartid': 0, 'mstatus': 0, 'mip': 0, 'mie': 0, 'mideleg': 0, 'medeleg': 0, 'mtvec': 0, 'stvec': 0, 'mepc': 0, 'sepc': 0, 'mcause': 0, 'scause': 0, 'mtval': 0, 'stval': 0, 'mscratch': 0, 'sscratch': 0, 'satp': 0}, {}, None, None, None] + +#with open (InputFile, 'r') as InputFileFP: +#lines = InputFileFP.readlines() +lineNum = 0 +StartLine = 0 +EndLine = 0 +numInstrs = 0 +#instructions = [] +MemAdr = 0 +lines = [] +interrupts=open(interruptFname,'w') +interrupts.close() + +prevInstrOutString='' +currInstrOutString='' +for line in fileinput.input('-'): + if line.startswith('riscv_cpu_do_interrupt'): + with open(interruptFname,'a') as interrupts: + # Write line + # Example line: hart:0, async:0, cause:0000000000000002, epc:0x0000000080008548, tval:0x0000000000000000, desc=illegal_instruction + interrupts.write(line) + # Write instruction count + interrupts.write(str(numInstrs)+'\n') + # Convert line to rows of info for easier Verilog parsing + vals=line.strip('riscv_cpu_do_interrupt: ').strip('\n').split(',') + vals=[val.split(':')[-1].strip(' ') for val in vals] + vals=[val.split('=')[-1].strip(' ') for val in vals] + for val in vals: + interrupts.write(val+'\n') + continue + lines.insert(lineNum, line) + if InstrStartDelim in line: + lineNum = 0 + StartLine = lineNum + elif InstrEndDelim in line: + EndLine = lineNum + (InstrBits, text) = lines[StartLine].split(':') + InstrBits = int(InstrBits.strip('=> '), 16) + text = text.strip() + PC = int(lines[StartLine+1].split(':')[0][2:], 16) + Regs = toDict(lines[StartLine+2:EndLine]) + (Class, Addr, WriteReg, ReadReg) = whichClass(text, Regs) + #print("CWR", Class, WriteReg, ReadReg) + PreviousInstr = CurrentInstr + + Changed = whatChanged(PreviousInstr[4], Regs) + + if (ReadReg !=None): ReadData = ReadReg + else: ReadData = None + + if (WriteReg !=None): WriteData = WriteReg + else: WriteData = None + + CurrentInstr = [PC, InstrBits, text, Class, Regs, Changed, Addr, WriteData, ReadData] + + #print(CurrentInstr[0:4], PreviousInstr[5], CurrentInstr[6:7], PreviousInstr[8]) + + # pc, instrbits, text and class come from the last line. + MoveInstrToRegWriteLst = PreviousInstr[0:4] + # updated registers come from the current line. + MoveInstrToRegWriteLst.append(CurrentInstr[5]) # destination regs + # memory address if present comes from the last line. + MoveInstrToRegWriteLst.append(PreviousInstr[6]) # MemAdrM + # write data from the previous line + #MoveInstrToRegWriteLst.append(PreviousInstr[7]) # WriteDataM + + if (PreviousInstr[7] != None): + MoveInstrToRegWriteLst.append(Regs[PreviousInstr[7]]) # WriteDataM + else: + MoveInstrToRegWriteLst.append(None) + + # read data from the current line + #MoveInstrToRegWriteLst.append(PreviousInstr[8]) # ReadDataM + if (PreviousInstr[8] != None): + MoveInstrToRegWriteLst.append(Regs[PreviousInstr[8]]) # ReadDataM + else: + MoveInstrToRegWriteLst.append(None) + + lines.clear() + #instructions.append(MoveInstrToRegWriteLst) + + prevInstrOutString = currInstrOutString + currInstrOutString = PrintInstr(MoveInstrToRegWriteLst) + # Remove duplicates + if (PreviousInstr[0] != CurrentInstr[0]) and (currInstrOutString != None): + sys.stdout.write(currInstrOutString) + numInstrs += 1 + if (numInstrs % 1e5 == 0): + sys.stderr.write('GDB trace parser reached '+str(numInstrs/1.0e6)+' million instrs.\n') + sys.stderr.flush() + lineNum += 1 + + +#for instruction in instructions[1::]: + + +#with open(OutputFile, 'w') as OutputFileFP: +# print('opened file') + + + diff --git a/docs/docker/testvector-generation/parsePlicState.py b/docs/docker/testvector-generation/parsePlicState.py new file mode 100755 index 000000000..49f2a558e --- /dev/null +++ b/docs/docker/testvector-generation/parsePlicState.py @@ -0,0 +1,106 @@ +#! /usr/bin/python3 +import sys, os +from functools import reduce + +################ +# Helper Funcs # +################ + +def tokenize(string): + tokens = [] + token = '' + whitespace = 0 + prevWhitespace = 0 + for char in string: + prevWhitespace = whitespace + whitespace = char in ' \t\n' + if (whitespace): + if ((not prevWhitespace) and (token != '')): + tokens.append(token) + token = '' + else: + token = token + char + return tokens + +def strip0x(num): + return num[2:] + +def stripZeroes(num): + num = int(num,16) + return hex(num)[2:] + +############# +# Main Code # +############# +print("Begin parsing PLIC state.") + +# Parse Args +if len(sys.argv) != 2: + sys.exit('Error parsePlicState.py expects 1 arg: ') +outDir = sys.argv[1]+'/' +rawPlicStateFile = outDir+'plicStateGDB.txt' +if not os.path.exists(rawPlicStateFile): + sys.exit('Error input file '+rawPlicStateFile+'not found') + +with open(rawPlicStateFile, 'r') as rawPlicStateFile: + plicIntPriorityArray = [] # iterates over number of different sources + # 0x0C000004 thru 0x0C000010 + plicIntPriorityArray += tokenize(rawPlicStateFile.readline())[1:] + # 0x0C000014 thru 0x0C000020 + plicIntPriorityArray += tokenize(rawPlicStateFile.readline())[1:] + # 0x0C000024 thru 0x0C000030 + plicIntPriorityArray += tokenize(rawPlicStateFile.readline())[1:] + # 0x0C000034 thru 0x0C000040 + plicIntPriorityArray += tokenize(rawPlicStateFile.readline())[1:] + # 0x0C000044 thru 0x0C000050 + plicIntPriorityArray += tokenize(rawPlicStateFile.readline())[1:] + # 0x0C000054 thru 0x0C000060 + plicIntPriorityArray += tokenize(rawPlicStateFile.readline())[1:] + # 0x0C000064 thru 0x0C000070 + plicIntPriorityArray += tokenize(rawPlicStateFile.readline())[1:] + # 0x0C000074 thru 0x0C000080 + plicIntPriorityArray += tokenize(rawPlicStateFile.readline())[1:] + # 0x0C000084 thru 0x0C000090 + plicIntPriorityArray += tokenize(rawPlicStateFile.readline())[1:] + # 0x0C000094 thru 0x0C0000a0 + plicIntPriorityArray += tokenize(rawPlicStateFile.readline())[1:] + # 0x0C0000a4 thru 0x0C0000b0 + plicIntPriorityArray += tokenize(rawPlicStateFile.readline())[1:] + # 0x0C0000b4 thru 0x0C0000c0 + plicIntPriorityArray += tokenize(rawPlicStateFile.readline())[1:] + # 0x0C0000c4 thru 0x0C0000d0 + plicIntPriorityArray += tokenize(rawPlicStateFile.readline())[1:] + # 0x0C0000d4 thru 0x0C0000e0 + plicIntPriorityArray += tokenize(rawPlicStateFile.readline())[1:] + # 0x0C0000e4 thru 0x0C0000f0 + plicIntPriorityArray += tokenize(rawPlicStateFile.readline())[1:] + # 0x0C0000f4 thru 0x0C0000fc + plicIntPriorityArray += tokenize(rawPlicStateFile.readline())[1:] + + plicIntEnableArray = [] # iterates over number of different contexts + # 0x0C020000 thru 0x0C020004 + plicIntEnable = tokenize(rawPlicStateFile.readline())[1:] + plicIntEnable = map(strip0x,plicIntEnable) + plicIntEnableArray.append(reduce(lambda x,y: y+x,plicIntEnable)) + # 0x0C020080 thru 0x0C020084 + plicIntEnable = tokenize(rawPlicStateFile.readline())[1:] + plicIntEnable = map(strip0x,plicIntEnable) + plicIntEnableArray.append(reduce(lambda x,y: y+x,plicIntEnable)) + + plicIntPriorityThresholdArray = [] # iterates over number of different contexts + # 0x0C200000 + plicIntPriorityThresholdArray += tokenize(rawPlicStateFile.readline())[1:] + # 0x0C201000 + plicIntPriorityThresholdArray += tokenize(rawPlicStateFile.readline())[1:] + +with open(outDir+'checkpoint-PLIC_INT_PRIORITY', 'w') as outFile: + for word in plicIntPriorityArray: + outFile.write(stripZeroes(word[2:])+'\n') +with open(outDir+'checkpoint-PLIC_INT_ENABLE', 'w') as outFile: + for word in plicIntEnableArray: + outFile.write(stripZeroes(word[2:])+'\n') +with open(outDir+'checkpoint-PLIC_THRESHOLD', 'w') as outFile: + for word in plicIntPriorityThresholdArray: + outFile.write(stripZeroes(word[2:])+'\n') + +print("Finished parsing PLIC state!") diff --git a/docs/docker/testvector-generation/parseQEMUtoGDB.py b/docs/docker/testvector-generation/parseQEMUtoGDB.py new file mode 100755 index 000000000..aea5e057e --- /dev/null +++ b/docs/docker/testvector-generation/parseQEMUtoGDB.py @@ -0,0 +1,145 @@ +#! /usr/bin/python3 +import fileinput, sys + +parseState = "idle" +beginPageFault = 0 +inPageFault = 0 +endPageFault = 0 +CSRs = {} +pageFaultCSRs = {} +regs = {} +pageFaultRegs = {} +instrs = {} +instrCount = 0 +returnAdr = 0 +sys.stderr.write("reminder: parse_qemu.py takes input from stdin\n") + +def printPC(l): + global parseState, inPageFault, CSRs, pageFaultCSRs, regs, pageFaultCSRs, instrs, instrCount + if not inPageFault: + inst = l.split() + if len(inst) > 3: + print(f'=> {inst[1]}:\t{inst[2]} {inst[3]}') + else: + print(f'=> {inst[1]}:\t{inst[2]}') + print(f'{inst[0]} 0x{inst[1]}') + instrCount += 1 + if ((instrCount % 100000) == 0): + sys.stderr.write("QEMU parser reached "+str(instrCount)+" instrs\n") + +def printCSRs(): + global parseState, inPageFault, CSRs, pageFaultCSRs, regs, pageFaultCSRs, instrs + global interrupt_line + if not inPageFault: + for (csr,val) in CSRs.items(): + print('{}{}{:#x} {}'.format(csr, ' '*(15-len(csr)), val, val)) + print('-----') # end of current instruction + if len(interrupt_line)>0: # squish interrupts in between instructions + print(interrupt_line) + interrupt_line="" + +def parseCSRs(l): + global parseState, inPageFault, CSRs, pageFaultCSRs, regs, pageFaultCSRs, instrs + if l.strip() and (not l.startswith("Disassembler")) and (not l.startswith("Please")): + # If we've hit the register file + if l.startswith(' x0/zero'): + parseState = "regFile" + if not inPageFault: + instr = instrs[CSRs["pc"]] + printPC(instr) + parseRegs(l) + # If we've hit a CSR + else: + csr = l.split()[0] + val = int(l.split()[1],16) + # Commented out this conditional because the pageFault instrs don't corrupt CSRs + #if inPageFault: + # Not sure if these CSRs should be updated or not during page fault. + #if l.startswith("mstatus") or l.startswith("mepc") or l.startswith("mcause") or l.startswith("mtval") or l.startswith("sepc") or l.startswith("scause") or l.startswith("stval"): + # We do update some CSRs + # CSRs[csr] = val + #else: + # Others we preserve until changed later + # pageFaultCSRs[csr] = val + #elif pageFaultCSRs and (csr in pageFaultCSRs): + # if (val != pageFaultCSRs[csr]): + # del pageFaultCSRs[csr] + # CSRs[csr] = val + #else: + # CSRs[csr] = val + # + # However SEPC and STVAL do get corrupted upon exiting + if endPageFault and ((csr == 'sepc') or (csr == 'stval')): + CSRs[csr] = returnAdr + pageFaultCSRs[csr] = val + elif pageFaultCSRs and (csr in pageFaultCSRs): + if (val != pageFaultCSRs[csr]): + del pageFaultCSRs[csr] + CSRs[csr] = val + else: + CSRs[csr] = val + +def parseRegs(l): + global parseState, inPageFault, CSRs, pageFaultCSRs, regs, pageFaultCSRs, instrs, pageFaultRegs + if "pc" in l: + printCSRs() + # New non-disassembled instruction + parseState = "CSRs" + parseCSRs(l) + elif l.startswith('--------'): + # End of disassembled instruction + printCSRs() + parseState = "idle" + else: + s = l.split() + for i in range(0,len(s),2): + if '/' in s[i]: + reg = s[i].split('/')[1] + val = int(s[i+1], 16) + if inPageFault: + pageFaultRegs[reg] = val + else: + if pageFaultRegs and (reg in pageFaultRegs): + if (val != pageFaultRegs[reg]): + del pageFaultRegs[reg] + regs[reg] = val + else: + regs[reg] = val + val = regs[reg] + print('{}{}{:#x} {}'.format(reg, ' '*(15-len(reg)), val, val)) + else: + sys.stderr.write("Whoops. Expected a list of reg file regs; got:\n"+l) + +############# +# Main Code # +############# +interrupt_line="" +for l in fileinput.input(): + #sys.stderr.write(l) + if l.startswith('riscv_cpu_do_interrupt'): + sys.stderr.write(l) + interrupt_line = l.strip('\n') + elif l.startswith('qemu-system-riscv64: QEMU: Terminated via GDBstub'): + break + elif l.startswith('IN:'): + # New disassembled instr + parseState = "instr" + elif (parseState == "instr") and l.startswith('0x'): + # New instruction + if "out of bounds" in l: + sys.stderr.write("Detected QEMU page fault error\n") + beginPageFault = not inPageFault + if beginPageFault: + returnAdr = int(l.split()[0][2:-1], 16) + sys.stderr.write('Saving SEPC of '+hex(returnAdr)+'\n') + inPageFault = 1 + else: + endPageFault = inPageFault + inPageFault = 0 + adr = int(l.split()[0][2:-1], 16) + instrs[adr] = l + parseState = "CSRs" + elif parseState == "CSRs": + parseCSRs(l) + elif parseState == "regFile": + parseRegs(l) diff --git a/docs/docker/testvector-generation/parseState.py b/docs/docker/testvector-generation/parseState.py new file mode 100755 index 000000000..1f7e93c09 --- /dev/null +++ b/docs/docker/testvector-generation/parseState.py @@ -0,0 +1,99 @@ +#! /usr/bin/python3 +import sys, os + +################ +# Helper Funcs # +################ + +def tokenize(string): + tokens = [] + token = '' + whitespace = 0 + prevWhitespace = 0 + for char in string: + prevWhitespace = whitespace + whitespace = char in ' \t\n' + if (whitespace): + if ((not prevWhitespace) and (token != '')): + tokens.append(token) + token = '' + else: + token = token + char + return tokens + +############# +# Main Code # +############# +print("Begin parsing CPU state.") + +# Parse Args +if len(sys.argv) != 2: + sys.exit('Error parseState.py expects 1 arg:\n parseState.py ') +outDir = sys.argv[1]+'/' +stateGDBpath = outDir+'stateGDB.txt' +if not os.path.exists(stateGDBpath): + sys.exit('Error input file '+stateGDBpath+'not found') + +singleCSRs = ['pc','mip','mie','mscratch','mcause','mepc','mtvec','medeleg','mideleg','sscratch','scause','sepc','stvec','sedeleg','sideleg','satp','mstatus','priv','sie','sip','sstatus'] +# priv (current privilege mode) isn't technically a CSR but we can log it with the same machinery +thirtyTwoBitCSRs = ['mcounteren','scounteren'] +listCSRs = ['hpmcounter','pmpaddr'] +pmpcfg = ['pmpcfg'] + +# Initialize List CSR files to empty +# (because later we'll open them in append mode) +for csr in listCSRs+pmpcfg: + outFileName = 'checkpoint-'+csr.upper() + outFile = open(outDir+outFileName, 'w') + outFile.close() + +# Initial State for Main Loop +currState = 'regFile' +regFileIndex = 0 +outFileName = 'checkpoint-RF' +outFile = open(outDir+outFileName, 'w') + +# Main Loop +with open(stateGDBpath, 'r') as stateGDB: + for line in stateGDB: + line = tokenize(line) + name = line[0] + val = line[1][2:] + if (currState == 'regFile'): + if (regFileIndex == 0 and name != 'zero'): + print('Whoops! Expected regFile registers to come first, starting with zero') + exit(1) + if (name != 'zero'): + # Wally doesn't need to know zero=0 + outFile.write(val+'\n') + regFileIndex += 1 + if (regFileIndex == 32): + outFile.close() + currState = 'CSRs' + elif (currState == 'CSRs'): + if name in singleCSRs: + outFileName = 'checkpoint-'+name.upper() + outFile = open(outDir+outFileName, 'w') + outFile.write(val+'\n') + outFile.close() + elif name in thirtyTwoBitCSRs: + outFileName = 'checkpoint-'+name.upper() + outFile = open(outDir+outFileName, 'w') + val = int(val,16) & 0xffffffff + outFile.write(hex(val)[2:]+'\n') + outFile.close() + elif name.strip('0123456789') in listCSRs: + outFileName = 'checkpoint-'+name.upper().strip('0123456789') + outFile = open(outDir+outFileName, 'a') + outFile.write(val+'\n') + outFile.close() + elif name.strip('0123456789') in pmpcfg: + outFileName = 'checkpoint-'+name.upper().strip('0123456789') + outFile = open(outDir+outFileName, 'a') + fourPmp = int(val,16) + for i in range(0,4): + byte = (fourPmp >> 8*i) & 0xff + outFile.write(hex(byte)[2:]+'\n') + outFile.close() + +print("Finished parsing CPU state!") diff --git a/docs/docker/testvector-generation/parseUartState.py b/docs/docker/testvector-generation/parseUartState.py new file mode 100755 index 000000000..611937054 --- /dev/null +++ b/docs/docker/testvector-generation/parseUartState.py @@ -0,0 +1,50 @@ +#! /usr/bin/python3 +import sys, os + +################ +# Helper Funcs # +################ + +def tokenize(string): + tokens = [] + token = '' + whitespace = 0 + prevWhitespace = 0 + for char in string: + prevWhitespace = whitespace + whitespace = char in ' \t\n' + if (whitespace): + if ((not prevWhitespace) and (token != '')): + tokens.append(token) + token = '' + else: + token = token + char + return tokens + +############# +# Main Code # +############# +print("Begin parsing UART state.") + +# Parse Args +if len(sys.argv) != 2: + sys.exit('Error parseUartState.py expects 1 arg: ') +outDir = sys.argv[1]+'/' +rawUartStateFile = outDir+'uartStateGDB.txt' +if not os.path.exists(rawUartStateFile): + sys.exit('Error input file '+rawUartStateFile+'not found') + +with open(rawUartStateFile, 'r') as rawUartStateFile: + uartBytes = [] + for i in range(0,8): + uartBytes += tokenize(rawUartStateFile.readline())[1:] +with open(outDir+'checkpoint-UART_IER', 'w') as outFile: + outFile.write(uartBytes[1][2:]) +with open(outDir+'checkpoint-UART_LCR', 'w') as outFile: + outFile.write(uartBytes[3][2:]) +with open(outDir+'checkpoint-UART_MCR', 'w') as outFile: + outFile.write(uartBytes[4][2:]) +with open(outDir+'checkpoint-UART_SCR', 'w') as outFile: + outFile.write(uartBytes[7][2:]) + +print("Finished parsing UART state!")