# whisper **Repository Path**: yangx_gitee/whisper ## Basic Information - **Project Name**: whisper - **Description**: No description available - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-11-25 - **Last Updated**: 2025-11-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README Whisper ================================================================= # Table of Contents [Introduction](#Introduction) [Requirements](#Requirements) [Compiling Whisper](#Compiling) [Preparing Target Programs](#Preparing) [Running Whisper](#Running) [Debugging RISCV Programs Using Gdb and Whisper](#Debugging) [Configuring Whisper](#Configuring) [Memory Consistency Checks](#Consistency) [Enabling Code Coverage](#Coverage) [Python Support](#Python) [Limitations](#Limitations) [Running riscv-arch-test Tests with RISCOF](#RISCOF) # Introduction Whisper is a RISCV instruction set simulator (ISS) initially developed for the verification of the Swerv micro-controller. It allows the user to run RISCV code without RISCV hardware. It has an interactive mode where the user can single step the target RISCV code and inspect/modify the RISCV registers or the simulated system memory. It can also run in lock step with a Verilog simulator serving as a "golden model" against which an implementation is checked after each instruction of a test program. # Requirements To use Whisper, you would need to download its source code, compile it, prepare some target test program, compile the test program to RISCV binary code and then run the RISCV binary within the Whisper simulator. In particular you would need: 1. A Linux machine to host the RISCV tool-chain and Whisper. 2. The RISCV tool-chain which contains a cross-compiler to compile C/C++ code to RISCV binary. This can be installed on most Linux distributions using your distros package manager (apt, dnf, pacman etc.). Otherwise it can be built from the upstream source code. Ubuntu ```shell $ sudo apt install gcc-riscv64-unknown-elf ``` Arch ```shell $ sudo pacman -Syu riscv64-elf-gcc ``` 3. The Whisper source code which can be downloaded from [github.](https://github.com/tenstorrent/SweRV-ISS) 4. The g++ compiler version 11 or higher to compile Whisper. The g++ compiler can be installed from a Linux distribution. Alternatively, the source code can be downloaded from [gnu.org/software/gcc.](https://www.gnu.org/software/gcc) 5. The boost library version 1.75 or higher compiled with c++-20. Boost source can be downloaded from [boost.org.](https://www.boost.org) 6. Optional: to use lz4-compression, install the the lz4 library and header file: ```shell $ sudo apt install liblz4-dev ``` 7. Optional: to allow simulated programs to have graphical output (frame-buffer), install the vncserver library and header files: ```shell $ sudo apt install libvncserver-dev ``` # Compiling Whisper On a Unix system, in the whisper directory, do the following: ``` make BOOST_DIR=x ``` where x is the path to your boost library installation. # Options There are various Makefile options that can be used. + `SOFT_FLOAT=1` to use the soft-float library for RISCV floating point operations. + `PCI=1` to build the PCI library. + `TRACE_READER=1` to build the trace reader library. + `MEM_CALLBACKS=1` to use the sparse memory model. + `FAST_SLOPPY=1` to enable faster (but not compliant) execution. + `LZ4_COMPRESS=1` to enable loading LZ4 files. + `REMOTE_FRAME_BUFFER=1` to enable graphics frame buffer. By default, PCI, TRACE_RADER, and MEM_CALLBACKS are set to 1. # Preparing Target Programs Standalone C/assembly programs not requiring operating system support (such programs cannot do any I/O) should be compiled as follows: ``` $ riscv32-unknown-elf-gcc -mabi=ilp32 -march=rv32imc -static -O3 -nostdlib -o test1 test1.c ``` The key switch in the above compilation command is "-nostdlib" which prevents the compiler from linking-in the standard C library. Note that without the standard C library, there is no "_start" symbol. The linker will complain that the start symbol is missing and will use another symbol as the default start address of the program. The user can always override that start address (program counter at the beginning of the simulation) by using the --startpc command line option. Also note that without an operating system, the simulator does not know when the program finishes. It will execute instructions indefinitely. Consider the following test program: ``` int main(int argc, char* argv[]) { int x = 1; int y = 2; int z = x + y; return z; } ``` The simulator will start execution at the ELF file entry point (address corresponding to main) and will return to address 0 (initial value of return address register) when the instruction corresponding to "return z" is executed. This will most likely cause an illegal instruction exception and given that no trap handlers are loaded into the memory, it will cause an infinite loop of illegal traps. To avoid this, simple stand-alone no-operating-system programs should define a global 32-bit integer named "tohost" and should write to that location at the end of the program. This signals the simulator to terminate the program. Here's a modified version of the above program that stops once main is done: ``` #include volatile uint32_t tohost = 0; int main(int argc, char* argv[]) { int x = 1; int y = 2; int z = x + y; return z; } void _start() { main(0, 0); tohost = 1; } ``` And here's how to compile and run the above program ``` $ riscv32-unknown-elf-gcc -mabi=ilp32 -march=rv32imc -nostdlib -g -o test2 test2.c $ whisper test2 ``` If no global variable named "tohost" is written by the program, the simulator will stop on its own if a sequence of 8 consecutive illegal instructions is encountered. If the above program is compiled for RV64, it will crash with 8 consecutive illegal instructions. The reason is that the generated code will attempt to push data on the stack and the default stack pointer value is 0. Pushing on the stack will make the stack pointer a very large number that exceeds memory size (default is 4GB) which will trigger an access fault and, without an exception handler, will result in a cascade of illegal instruction exceptions. To fix that, run the RV64 version of the test2 binary under whisper with "--setreg sp=0xf0000000" which initializes the stack pointer to an address within the default memory address range: ``` $ riscv64-unknown-elf-gcc -mabi=lp64 -march=rv64imc -nostdlib -g -o test2 test2.c $ whisper test2 # this will crash $ whisper test2 --setreg sp=0xf0000000 # this will run ``` For programs requiring minimal operating system support (e.g. brk, open, read and write) the user can compile with the newlib C library and use the simulator with the "--newlib" option. Here's a sample program: ``` #include int main(int argc, char* argv[]) { printf("hello world\n"); return 0; } ``` And here's how to compile and run it (assuming riscv32-unknown-elf-gcc was compiled with newlib): ``` $ riscv32-unknown-elf-gcc -mabi=ilp32 -march=rv32imc -static -O3 -o test3 test3.c $ whisper --newlib test3 ``` Note that in this case the simulator will intercept the exit system call invoked by the C library code and terminate the program accordingly. There is no need for the "tohost" mechanism. # Running Whisper Running whisper with -h or --help will print a brief description of all the command line options. To run a RISCV program, prog, in whisper, one would issue the Linux command: ``` whisper prog ``` which will run the program until it writes to the "tohost" location. A program compiled with the newlib C library need not have a "tohost" location. Such a program will run until it calls exit. Such a program would be run as follows: ``` whisper --newlib prog ``` ## Command Line Options The following is a brief description of the command line options: --help Produce help message. --log Enable tracing to standard output of executed instructions. --xlen len Specify register width (32 or 64), defaults to 32. --isa string Select the RISCV extensions to enable. The currently supported options are a (atomic), c (compressed instructions), d (double precision fp), f (single precision fp), i (base integer), m (multiply divide), s (supervisor mode), u (user mode), and v). By default, only i, m and c are enabled. Canonical ISA strings with versioned extension are supported. Examples: --isa imacf, --isa rv32i2p0_m2p0_f2p0_v1p0_zfh0p1 --target program Specify target program (ELF file) to load into simulated memory. In newlib emulations mode, program options may follow program name. --hex file Hexadecimal file to load into simulator memory. --logfile file Enable tracing to given file of executed instructions. --csv Use CSV (comma separated values) format for the trace log file produced by the --logfile option. The first output line contains the headers of the columns in the rest of the log file. Each executed instruction results in a row that includes the PC, opcode, changed registers, changed memory locations, ... Fields are separated by commas. Multiple values within a field are separated by semicolons. --consoleoutfile file Redirect console output to given file. --commandlog file Enable logging of interactive/socket commands to the given file. --startpc address Set program entry point to the given address (in hex notation with a 0x prefix). If not specified, use the ELF file entry point. --endpc address Set stop program counter to the given address (in hex notation with a 0x prefix). Simulator will stop once instruction at the stop program counter is executed. If not specified, use the ELF file _finish symbol. --tohost address Memory address to which a write stops the simulator (in hex with 0x prefix). --consoleio address Memory address corresponding to console io (in hex with 0x prefix). Reading/writing a byte (using lb/sb instruction) from given address reads/writes a byte from the console. --maxinst limit Limit executed instruction count to given number. --interactive After loading any target file into memory, the simulator enters interactive mode. --triggers Enable debug triggers (triggers are automatically enabled in interactive and server modes). --counters Enable performance counters. --gdb Run in gdb mode enabling remote debugging from gdb. --profileinst file Report executed instruction frequencies to the given file. --setreg spec ... Initialize registers. Example --setreg x1=4 x2=0xff --configfile file Configuration file (JSON file defining system features). --snapshotdir path Directory prefix for saving snapshots: Snapshots (see --sanpshotpreid) are placed in sub-directories of the given path. Default: "snapshot". --snapshotperiod n Snapshot period: Save a snapshot every n instructions putting data in directory specified by --snapshotdir. --loadfrom path Snapshot directory from which to restore a previously saved (snapshot) state. --snapcompressiontype [lz4 | gzip] Specify which compression scheme to use to store the snapshot. If the flag is absent, gzip is used by default. --snapdecompressiontype [lz4 | gzip] Specify which decompression scheme to use to load the snapshot. If the flag is absent, gzip is used by default. --newlib Emulate limited emulation of newlib system calls. Done automatically if newlib symbols are detected in the target ELF file. --linux Emulate limited emulation of Linux system calls. Done automatically if Linux symbols are detected in the target ELF file. --raw Bare metal mode: Disable emulation of Linux/newlib system call emulation even if Linux/newlib symbols detected in the target ELF file. --stdout path Redirect the standard output of the newlib/Linux target program to the file specified by the given path. --stderr path Redirect the standard error of the newlib/Linux target program to the file specified by the given path. --alarm period External interrupt period in micro-seconds: Convert period to an instruction count, n, assuming a 1ghz clock, and set to 1 the timer bit of the MIP CSR every n instructions. The timer bit of MIP is automatically cleared if the interrupt is actually taken (interrupts enabled in MSTATUS and timer bit set in MIE CSR). No-op if n is zero. --abinames Use ABI register names (e.g. sp instead of x2) in instruction disassembly. --verbose Produce additional messages. --version Print version. ## Interactive Mode Whisper is started in interactive mode using the "--interactive" command line option. Here's are some examples: ``` $ whisper --interactive $ whisper --interactive test1 ``` In the second example, the program test1 is first loaded into the simulated memory. In interactive mode the user can issue commands to control the execution of the target program and to set/examine the registers and memory location of the simulated system. The help command will produce a list of all available interactive commands. The "help x" command will produce information about command x. Here's the output of the "help" command: The arguments hart= and.or time= may be used with any command to select a hart and specify event time (relevant to memory model) They persist until explicitly changed. help [] Print help for given command or for all commands if no command given. run Run till interrupted. until
Run until address or interrupted. step [] Execute n instructions (1 if n is missing). peek Print value of resource res (one of r, f, c, v, m) and address addr. For memory (m) up to 2 addresses may be provided to define a range of memory locations to be printed; also, an optional file name after the two addresses writes the command output to that file. examples: peek r x1 peek c mtval peek m 0x4096 peek m 0x10 0x40 out peek pc Print value of the program counter. peek all Print value of all non-memory resources poke res addr value Set value of resource res (one of r, c or m) and address addr Examples: poke r x1 0xff poke c 0x4096 0xabcd disass opcode ... Disassemble opcodes. Example: disass opcode 0x3b 0x8082 disass function Disassemble function with given name. Example: disas func main disass > Disassemble memory locations between addr1 and addr2. elf file Load elf file into simulated memory. hex file Load hex file into simulated memory. replay_file file Open command file for replay. replay n Execute the next n commands in the replay file or all the remaining commands if n is missing. replay step n Execute consecutive commands from the replay file until n step commands are executed or the file is exhausted reset [] Reset hart. If reset_pc is given, then change the reset program counter to the given reset_pc before resetting the hart. symbols List all the symbols in the loaded ELF file(s). pagetable Print the entries of the address translation table. nmi [] Post a non-maskable interrupt with a given cause number (default 0). mread tag addr size data i|e Perform a memory model (out of order) read for load/amo instruction with given tag. Data is the RTL data to be compared with whisper data when instruction is later retired. The whisper data is obtained forwarding from preceding instructions if 'i' is present; otherwise, it is obtained from memory. mbwrite addr data Perform a memory model merge-buffer-write for given address. Given data (hexadecimal string) is from a different model (RTL) and is compared to whisper data. Addr should be a multiple of cache-line size. If hex string is smaller than twice the cache-line size, it will be padded with zeros on the most significant side. mbbypass tag addr size data Perform a memory write operation bypassing the merge buffer. Given data (hexadecimal string) is from a different model (RTL) and is compared to whisper data. pmp [
] Print the pmp map (all) or for a matching address pma [
] Print the pma map (all) or for a matching address translate [ []] Translate given virtual address to a physical address assuming given permission (defaults to read) and privilege mode (defaults to user) Allowed permission: r for read, w for write, or x for execute. Allowed privilege: u to user or s for supervisor quit Terminate the simulator ## Newlib Emulation Whisper will emulate the newlib open, close, read, write, brk and exit system calls. This allows simple programs to run and use the newlib C-library functions such as printf, fopen, fread, fwrite, fclose, malloc, free and exit. Here an example of running a program with limited C-library support: ``` $ whisper --newlib test3 ``` And here is an example of passing the command line arguments arg1 and arg2 to the to the target program test3: ``` $ whisper --newlib "test3 arg1 arg2" ``` And examples of passing command line switches to a target program that requires them: ``` $ whisper --newlib "test4 -opt1 val1 -opt2" $ whisper --newlib --target "test4 -opt1 val1 -opt2" ``` # Debugging RISCV Programs Using Gdb and Whisper With the --gdb option, whisper will follow the gdb remote debugging protocol. This allows the user to debug a RISCV program using a cross-compiled gdb and whisper. For example, to debug a RISCV program named xyz on a Linux x86 machine, we would start the (cross-compiled) RISCV gdb as follows: ``` $ riscv-unknown-elf-gdb xyz ``` at the gdb prompt, we would connect to whisper by issuing a "target remote" gdb command as follows: ``` target remote | whisper --gdb xyz ``` # Configuring Whisper A JSON configuration file may be specified on the command line using the --configfile switch. Numeric parameters may be specified as integers or as strings. For example, a core count of 4 may be specified as: ``` "cores" : 4 ``` or ``` "cores" : "4" ``` If expressed as a string, a numeric value may be prefixed with 0x to specify hexadecimal notation (JSON does not support hexadecimal notation for integers). The value of a Boolean parameters may be specified as an integer with 0 indicating false and non-zero indicating true. Alternatively it may be specified using the strings "false", "False", "true", or "True". Command line options override settings in the configuration file. C++ style comments are ignored when the file is parsed. Here is a sample configuration file: ``` { "isa" : "rv32imafd_zfh_zba_zbb_zbc_sbs", "abi_names" : "true", "csr" : { "misa" : { "reset-comment" : "imabfv", "reset" : "0x40201123", "mask-comment" : "Misa is not writable by CSR instructions", "mask" : "0x0" }, "mstatus" : { "mstatus-comment" : "Hardwired to zero except for FS, VS, and SD.", "reset" : "0x80006600", "mask" : "0x0", "poke_mask" : "0x0" } } } ``` A schema for the JSON config file is located in the configuration folder. It can be used for code completion and validation by adding the following to a config file: ``` "$schema": "/configuration/config_schema.json", ``` ## Configuration parameters ### cores Number of cores in simulated system. Default is 1. ### harts Number of harts per core. Default is 1. ### core_hart_id_offset Stride, s, between the value of MHARTID CSR of the first hart in one core and that of the first hart in the next core. Default is c*h where c and h are the number of cores and the number of harts per core respectively. For example, if s/c/h are 7/2/3 then the values of MMHARTID CSRs in the system will be: 0 1 2 7 8 9. ### isa Enable instruction set architecture (isa) features. Example: ``` "isa" : "rv32imaf" ``` ### memmap Object defining memory organization. Fields of memmap: * size: Field defining physical memory size * page_size: Field defining page size * pma: Array of entries defining physical memory attributes. Each entry is an object with a "low" and "high" addresses and an "attribs" array defining the physical memory attributes. Example: ``` "memmap" : { "size" : "0x100000000", "page_size" : 4096, "pma" : [ { "low" : "0x80000000", "high" : "0x801fffff", "attribs" : [ "read", "write", "exec", "amo", "rsrv", "idempotent" ] }, { "low" : "0x0", "high" : "0xffffffff", "attribs" : [ "read", "write", "amo", "rsrv", "idempotent" ] } ] } ``` ### num_mmode_perf_regs Number of implemented performance counters. If specified number is n, then CSRs (counters) mhpmcounter3 to mhpmcounter3+n-1 are implemented and the remaining counters are hardwired to zero. Same for the mhpmevent CSRs. ### enable_performance_counters Whisper will count events associated with performance counters when this is set to true. Note that pipeline specific events (such as mis-predicted branches) are not supported. Synchronous events (such as count retired load instructions) are supported. ### abi_names If set to true then registers are identified by their ABI names in the log file (e.g. ra instead of x1). ### trace_ptw If set to true then page table walk information is emitted to the log file. ### reservation_bytes Defines the size of a lr.w/lr.d reservation (default is 4 for RV32 and 8 for RV64). ### enable_misaligned_data If set to false then a misaligned data access by a load/store instructions will trigger an exception. ### misaligned_has_priority When true, makes misaligned data exceptions have priority over page and access fault exceptions. Default is true. ### page_fault_on_first_access When true, makes first access to a page table entry trigger a page fault. Default is true. ### tlb_entries Defines the number of translation look-aside buffer entries. Default is 32. ### clear_mprv_on_ret When true (default), makes the mret/sret instruction clear the mprv bit in the mstatus/status CSR. ### clear_mtval_on_illegal_instruction When true, causes the illegal instruction exception to clear the mtval CSR. Default is false. ### clear_tinst_on_cbo_flush When true, clear the MTINST/STINST CSR when a cbo.flush entouters an exception. ### clear_tinst_on_cbo_inval When true, clear the MTINST/STINST CSR when a cbo.inval entouters an exception. ### align_cbo_address When true (default), align to a cahce line boundary the effective address of a cbo/cmo instruction before doing address translation: In case of an exception the reported value in MTVAL/STVAL will be the aligned address. When false, the effective address is used as is. ### cancel_lr_on_trap When true (default), causes reservations to be canceled on traps. ### debug_park_loop Defines the address of the entry point of the debug mode park look. Whisper will jump to this address upon entering debug mode if this address is not an all ones bit pattern. Default: all ones bit pattern. ### debug_trap_address Defines the address of the debug mode exception handler. Whisper will jump to this address upon encountering an exception in debug mode if this address is not an all ones bit pattern. Default: all ones bit pattern. ### physical_memory_protection_grain Defines the G value of the physical memory protection grain. This is the log base 2 of the grain minus 2. The default is G=0 (implying a grain of 4). ### guest_interrupt_count Defines the maximum number of guest external interrupt count (GEILEN). Default is zero. ### csr The CSR configuration is a map where each key is a CSR name and the corresponding value is an object with the following fields: "number", "reset", "mask", "poke_mask", "exists", and "shared". Set "exists" to "false" to mark a non implemented CSR (read/write instructions to such a CSR will trigger illegal instruction exception). Set "mask" to the write mask of the CSR (zero bits correspond to bits that will be preserved by write instructions). Set "reset" to reset value of the CSR. Set "shared" to "true" for CSRs that are shared between harts. The "number" fields should be used to define the number (address) of a non-standard CSR. The poke_mask should be used for the rare cases where poke operation may modify some bits that are not modifiable by CSR write instructions. For CSRs with shared base name and different integer suffixes (e.g. pmpaddr0, pmpaddr1, ...), the configurations may be defined for each CSR or a common configuration may be defined with a range attribute. Example: ``` "csr" : { "pmpaddr0" : { "mask" : "0xffffffff" }, "pmpaddr" : { "exists" : "false", "range" : [1 , 63] } } ``` ### vector The vector configuration is an object with the following fields: * bytes_per_vec: vector size in bytes. * min_bytes_per_elem: narrowest supported element size in bytes (default 1). * max_bytes_per_elem: widest supported element size in bytes (no default). * min_bytes_per_lmul: map of lmul to min-element-width-in bytes (default: no min). * min_bytes_per_lmul: map of lmul to max-element-width-in bytes (default: no max). * mask_agnostic_policy: "ones" or "undisturb" to set behavior of mask-anostic instructions, default is "ones". * tail_agnostic_policy: "ones" or "undisturb" to set behavior of tail-anostic instructions, default is "ones". * trap_non_zero_vstart: causes vector instruction to trap on non-zero vstart, default is true. * trap_out_of_bounds_vstart: causes vector instruction to trap on a vstart value that is out of bounds (greater or equal to vlmax), default is false. * update_whole_mask: when true, compute all the elements of the destination mask register for mask-logical and mask-manipulation instructions regardless of VL. * trap_invalid_vtype: when true, trap on invalid/unsupported vtype configurations, when false set vtype.vill instead. * legalize_vsetvl_avl: when true, legalize VL to VLMAX if it would be greater than VLMAX after a vsetvl instruction. * legalize_vsetvli_avl: when true, legalize VL to VLMAX if it would be greater than VLMAX after a vsetvli instruction. * tt_fp_usum_tree_reduction: for each EEW, enables Tenstorrent tree reduction-style vfredusum/vfwredusum, default is false. * fp_usum_nan_canonicalize: for each EEW, enables NaN canonicalization of vfredusum/vfwredusum result, default is false. * partial_segment_update: partially commit the fields of a load/store segment encountering an exception/trigger-hit at a given index when true and commit no field in the case of an exception when false, default is false. * always_mark_dirty: if a vector instruction would write to a vector register, always mark vector state dirty regardless of whether the instruction updates the vector register. * vmvr_ignore_vill: when true, vmvr instructions ignore the vtype.vill bit. * tt_clear_tval_vl_egs: when true, we clear the \*tval register if a vector crypto instruction would fail the "vl is an integer multiple of EGS" constraint. Example: ``` "vector" : { "bytes_per_vec" : 16, "max_bytes_per_elem" : 8, "tail_agnositic_policy" : "undisturb", "mask_agnositic_policy" : "ones", "min_bytes_per_lmul" : { "m2" : 2, "m4" : 2 }, "max_bytes_per_lmul" : { "mf8" : 4 }, "tt_fp_usum_tree_reduction" : [ "e16", "e32", "e64" ] } ``` ### aclint The advanced core local interrupt controller (aclint) configuration is an object with the following fields: * base: base address of the memory area associated with the ACLINT. * sw_offset: offset to software interrupt region within the ACLINT area). * timer_offset: offset to timer within the ACLINT area. * time_offset: offset to time-compare region within the ACLINT area). * software_interrupt_on_reset: when set to true, write to software interrupt of core 0 on reset. * deliver_interrupts: when set to true, deliver ACLNT interrupts. This supports the test-bench which may decide to deliver ACLINT interrupts by poking the MIP CSR, in which case deliver_interrupts should be set to false. * adjust_time: value to artificially add to a time_compare register of the ACLINT whenever such register is written by a store instruction, this is used to reduce the frequency of timer interrupts and is relevant for booting a Linux image (Whisper uses the instruction count to fake a timer value and that is too fast for Linux which expect a much lower frequency for its timer). Default value is 10000. * timecmp_reset: reset value of mtimecmp ### reset_vec Defines the program counter (PC) value after reset. The ELF file entry point will supersede the reset_vec value unless --raw is used. The value of the --startpc option will supersede both the reset_vec and the ELF file entry point. In interactive mode, a reset command will change the program counter to the value of reset_vec. ### nmi_vec Defines the address of the handler of non-maskable interrupts. ### nmi_exception_vec Defines the address of the handler of exceptions encountered while handling non-maskable interrupts. ### indexed_nmi When false, the PC after an NMI will be base value defined by nmi_vec. When true, the PC will be the base plus 4 times the NMI cause. Default value is false. Similarly, when false, then after an exception while in the NMI interrupt handler, the PC will be the base value defined by nmi_exception_vec. When true, the PC will be the base plus 4 times the exception cause. ### auto_increment_timer When false, Whisper will not increment the timer value after each executed instruction. This is useful to the test-bench which may want to explicitly set the timer values to control when a timer interrupt should be delivered. Default value is true. ### enable_triggers Enable support for debug triggers when set to true. ### trigger_use_tcontrol When set to true, the MTE field of the TCONTROL CSR controls the firing of triggers in machine mode. When set to false, the triggers fire in machine mode only if MSTATUS.MIE is zero. ### perf_count_atomic_load_store When true, the lr/sc instructions will be counted as load/store by the performance counters. ### trigger registers Each trigger register is associated with up to 4 components tdata1, tdata2, tdata3, and tinfo. Here's an example of how to configure the reset values and masks of these components in a system with 2 trigger registers (the mask and reset values are made up): ``` "triggers" : [ { "reset" : [0, 0, 0, "0x1008040"], "mask" : ["0xffffffff", "0xffffffff", "0xffffffff", 0], "poke_mask": ["0xffffffff", "0xffffffff", "0xffffffff", 0] }, { "reset" : [0, 0, 0, "0x1008040"], "mask" : ["0xffffffff", "0xffffffff", "0xffffffff", 0], "poke_mask": ["0xffffffff", "0xffffffff", "0xffffffff", 0] } ], ``` ### all_ld_st_addr_trigger Value is true or flase (default is true). Enable/disable matching on all possible addresses in a load/store access [address, address+size-1]. If disabled, matching will be done on the first address of a load/store access. ### all_inst_addr_trigger Enable/disable matching on all possible addresses in a instruction fetch access [address, address+size-1]. If disabled, matching will be done on the first address of an instruction. ### trigger_on_all_data_addr Enable/disable matching on all possible addresses in a load/store access for a particular match type. Value is an array where each element is itself an array of 2 elements: the first is an integer indicating the match type (see match field in MCONTROL6 in debug spec), and the second is a boolean indicating whether or not all-address-matching is enabled. ### trigger_on_all_isntr_addr Similar to trigger_on_all_data_addr but for instruction addresses. ### trigger_types Define the supported trigger types (type field in tdata1). Example: ``` "trigger_types" : [ "none", "disabled", "mcontrol6" ] ``` The types "none" and "disabled" must not be excluded from "trigger_types". Possible values that can be included with in "trigger_types" are: ``` "none", "mcontrol", "icount", "itrigger", "etriger", "mcontrol6", "tmexttriger", and "disabled" ``` ### trigger_actions Define the supported trigger actions (action field in tdata1). Example: The action "raisebreak" cannot be excluded. Possible values that can be included with in "trigger_actions" are: ``` "raisebreak", "enterdebug", "starttrace", "stoptrace", "emittrace", "external0", and "external1" ``` ### trigger_napot_maskmax Define the number of maximum bits that the NAPOT mask can support. The maximum possible value of this number is 63 for an RV64 configuration. ### perf_count_fp_load_store When true, the floating point load/store instructions will be counted as load/store by the performance counters. ### stee The static trusted execution environment (STEE) configuration is an object with the following fields: * zero_mask: if bit i is set in the zero_mask value, then bit i must be zero in every load/store address; otherwise, the address will be invalid and will result in an access-fault exception. * secure_mask: if bit i is set in the secure_mask value, then bit i must be one in a load/sore address in order for that address to be considered secure. * secure_region: insecure writes to this region will be ignored, insecure reads will either trap or will produce zero depending on trap_insecure_load. * trap_insecure_load: when set to true, an insecure read from a secure region will trap; when set to false (default), such a read will yield zero. This applies to reads resulting from data loads or from instruction fetches. Example: ``` "stee" : { "zero_mask" : "0xff70000000000000", "secure_mask": "0x0080000000000000", "secure_region": ["0x0001000000000000", "0x0002000000000000"] }, ``` ### APLIC Place holder for APLIC configuration. ### IOMMU Place holder for IOMMU configuration. # Memory Consistency Checks When run in server or interactive modes, Whisper will check the RISCV weak memory ordering rules also known as preserved program order (PPO) rules. This feature is enabled by setting the "enable_memory_consistency" to "true" in the configuration file or by using "--mcm" on the command line. Detailed information about the PPO rules can be found in chapter 17 of the the [RISCV unprivileged specs.](https://github.com/riscv/riscv-isa-manual/releases/download/draft-20221206-b7080e0/riscv-spec.pdf) By default, we check the ordering rules of the weak memory ordering model (RVWMO). If the enable_tso configuration tag is set to true, we check the re-ordering rules of the total-store-order memory model. Whisper expects to be notified about read and write operations and it expects such operations to be associated with time stamps. Each memory instruction (load/store/amo) is associated with one or more memory read/write operation. A memory operation may occur before/after the corresponding instruction is retired. Memory operations from two different instructions may occur in a global memory order that is different than the program order of those instructions. A read operation has an instruction tag, an address, a size, a data value, and an indication of whether or not the data was forwarded from inside the core (internal) or it came from the memory system (external). The interactive command for a read operation has the form: ``` time= hart= mread ``` A merge buffer insert has an instruction tag, an address, a size, and data value. The operation signifies a transfer of the data to the store buffer. The interactive command for a merge buffer insert has the form: ``` time= hart= mbinsert ``` A merge buffer write implies the transfer of data from the merge buffer to the external memory. This is when the write operations accumulated in the merge buffer become visible to the global memory system. The interactive command for a merge buffer write is: ``` time= hart= mbwrite ``` Similarly, we provide server mode commands that allows a client (typically test bench code running in a Verilog simulator) to provide whisper information about the time, hart-id, size, instruction tag and data of read/write operations associated with load/store/amo instructions. We use such information to check the preserved program order (ppo) rules of RISCV. # Enabling Code Coverage C++ code coverage for Whisper can be configured and collected on Linux with minimal effort using [gcov](https://gcc.gnu.org/onlinedocs/gcc/Gcov-Intro.html) and [lcov](https://github.com/linux-test-project/lcov). To enable and collect C++ code coverage: 1. Pass the `--coverage` flag to the compiler and linker. Note: coverage should be enabled with optimization disabled to get the most accurate line information. - When compiling using the Makefile, invoke `make` with `OFLAGS="-g -O0 --coverage"`. If done successfully, a .gcno file should exist in the build folder beside the .d and .o file for each source file included in the build. - When compile using bazel, invoke `bazel build` with `--compilation_mode dbg --collect_code_coverage --instrumentation_filter=rvcore,whisper`. If done successfully, a .gcno file should exist in the bazel-bin/_objs folder (or sub-folder for the target) beside the .d and .o file for each source file included in the build. 2. Run a test or set of tests that invokes the whisper executable created in step 1. Coverage information will be aggregated across invocations into a .gcda file for each source file in the build. These .gcda files should be created beside the .gcno file but may be put in a different place depending on the working directory. Running tests from the repository root should prevent this issue, but some testing tools (e.g. riscof) run tests in their own working directory. The output directory of these .gcda files can be controlled using the `GCOV_PREFIX` and `GCOV_PREFIX_STRIP` environment variables, so set these values if necessary so that the .gcda file is created next to the .gcno file (for example, running riscof with a local bazel build likely requires `GCOV_PREFIX=[Whisper repo root absolute path]` and `GCOV_PREFIX_STRIP=3`). 3. Use `lcov` to aggregate the coverage information from the .gcda files into a single coverage report. Invoke `lcov` with `--output-file [report name; for example, _coverage_report.dat] --directory [path to directory containing .gcno and .gcda files] --base-directory [Whisper repo root absolute path] --capture --no-external`. If done correctly, a file with the name passed to `lcov` in the `--output-file` parameter will be created and should not be empty. 4. (optional) Generate an html report using `genhtml`. `genhtml` should be invoked using `-o [collateral output directory] [coverage report file from step 3]`. The index.html page in the output directory can then be opened using a web browser. Note that for any test defined using bazel and run with `bazel test`, steps 2 and 3 will be handled automatically by bazel, and the final coverage report should exist inside bazel-out/_coverage. # Python Support There is basic support for python bindings with [pybind11](https://pybind11.readthedocs.io/en/stable/) and its corresponding [Bazel support](https://github.com/pybind/pybind11_bazel). The shared library can be built with `make all`. Example: ``` import whisper s = whisper.system.System64("config.json") h = s.harts()[0] h.step() print(h.x1) ``` The hart registers are exposed as class attributes and implement step/run functionality. # Limitations It is not possible to change XLEN at run time by writing to the MISA register. The "round to nearest break tie to max magnitude" rounding mode is not implemented unless you compile with the softfloat library: ``` make SOFT_FLOAT=1 ``` in which case simulation of floating point instructions slows down significantly. Suppprted extensions: A, B, C, D, F, H, I, M, S, U, V, ZFH, ZFHMIN, ZBA, ZBB, ZBS, ZKND, ZKNE, ZKNH, ZBKB, ZKSED, ZKSH, SVINVAL, SVNAPOT, ZICBOM, ZICBOZ, ZWARS, ZMMUL, ZVFH, ZVFH, ZVFHMIN, ZVBB, ZVBC, ZVKG, ZVKNED, ZVKNHA, ZVKNHB, ZVKSED, ZVKSH, ZICOND, ZCB, ZFA, ZFBFMIN, ZVFBFMIN, ZVFBFWMA, SSTC, SVPBMT, SMAIA, SSAIA, ZACAS. # Running riscv-arch-test Tests with RISCOF [riscv-arch-test](https://github.com/riscv-non-isa/riscv-arch-test) is a repository containing RISC-V compliance tests, and [RISCOF](https://github.com/riscv-software-src/riscof) is a tool that simplifies building and running these tests against a known reference model (Sail and/or Spike). Whisper includes the functionality necessary to run these tests and a plugin used to run and score the tests with RISCOF. To run a test or set of tests with RISCOF: 1. Install RISCOF via pip. For more information, see the [RISCOF docs](https://riscof.readthedocs.io/en/stable/installation.html). 2. Clone the [riscv-arch-test](https://github.com/riscv-non-isa/riscv-arch-test) repository. Note that this can also be achieved using `riscof arch-test --clone` (riscof provides functionality to specify the clone directory and to update an existing checkout; use `riscof arch-test --help` for more info). 3. Create RISCOF's config.ini file by running `riscof setup --dutname whisper`. It defaults to using Sail as the reference model; append `--refname spike` to the command to use Spike. 4. Update the `DUTPluginPath` in the `RISCOF` section in the config.ini file to the arch_test_target folder from this repository. Likewise, set the `pluginpath`, `ispec`, and `pspec` paths to the appropriate locations within the arch_test_target folder. Note that whisper_isa32.yaml is to be used when running an RV32 architecture; whisper_isa.yaml is for RV64. RISCOF does not appear to have the ability to configure both architectures in a single file and dynamically switch based on the test. 5. (Optional) set the `jobs` field in the `whisper` and \ sections to a number larger than 1 to allow running tests in parallel. 6. Update sail_cSim/riscof_sail_cSim.py and/or spike/riscof_spike.py as necessary based on desired usage. Some modifications may include: - Replace the dynamic switching of 32 vs 64 based on ISA when running gcc and objdump to just 64 if your toolchain is compiled for multilib. - Disable logging to file and creating dis-assembly files. Some tests (particularly some floating point tests) are very large, so generating dis-assembly and log files for these tests is very time consuming and can consume large amounts of space. These files are unused for scoring, so they can safely be disabled if just scoring tests. - Ensure extensions for all desired tests are included in the architecture string passed to the compile command and/or executable invocations. 7. Build Whisper and the reference model simulator. See the Sail or Spike documentation on how to do so. 8. Ensure the paths to the RISC-V toolchain (i.e. gcc and objdump), the reference model executable, and whisper executable are in the `PATH` environment variable. All need to be able to be invoked without a path. 9. Run the desired test suite using `riscof run`. The `--suite` parameter should be provided with the riscv-arch-test/riscv-test-suite directory (or a sub-directory) from the clone from step 2 above, and the `--env` folder should be provided with the riscv-arch-test/riscv-test-suite/env folder. - By default, the run command will produce an HTML report containing information about which tests passed and failed and will attempt to open this report in the browser once all tests have completed. If this behavior is undesirable (e.g. running on a headless node or as part of CI), provide the `--no-browser` argument.