# Welcome to riscv-dv’s documentation!

## Overview

RISCV-DV is a SV/UVM based open-source instruction generator for RISC-V processor verification. It currently supports the following features:

- Supported instruction set: RV32IMAFDC, RV64IMAFDC

- Supported privileged mode: machine mode, supervisor mode, user mode

- Page table randomization and exception

- Privileged CSR setup randomization

- Privileged CSR test suite

- Trap/interrupt handling

- Test suite to stress test MMU

- Sub-program generation and random program calls

- Illegal instruction and HINT instruction generation

- Random forward/backward branch instructions

- Supports mixing directed instructions with random instruction stream

- Debug mode support, with fully randomized debug ROM

- Instruction generation coverage model

- Communication of information to any integrated SV testbench

- Co-simulation with multiple ISS : spike, riscv-ovpsim

A CSR test generation script written in Python is also provided, to generate a directed test suite that stresses all CSR instructions on all of the CSRs that the core implements.

## Getting Started

### Prerequisites

To be able to run the instruction generator, you need to have an RTL simulator which supports SystemVerilog and UVM 1.2. This generator has been verified with Synopsys VCS, Cadence Incisive/Xcelium, Mentor Questa, and Aldec Riviera-PRO simulators. Please make sure the EDA tool environment is properly setup before running the generator.

### Install RISCV-DV

Getting the source

1. Install git

2. `git clone https://github.com/google/riscv-dv.git`

3. `cd riscv-dv`

There are two ways that you can run scripts from riscv-dv.

For developers which may work on multiple clones in parallel, using directly run by python3 script is highly recommended. Example:

    pip3 install -r requirements.txt     # install dependencies (only once)
    python3 run.py --help

For normal users, using the python package is recommended. First, cd to the directory where riscv-dv is cloned and run:

    export PATH=$HOME/.local/bin/:$PATH  # add ~/.local/bin to the $PATH (only once)
    pip3 install --user -e .

This installs riscv-dv in a mode where any changes within the repo are immediately available simply by running run/cov. There is no need to repeatedly run pip install . after each change. Example for running:

    run --help
    cov --help

### Setup RISCV-GCC compiler toolchain

1. Install riscv-gcc toolchain

2. Set environment variable RISCV_GCC to the RISC-V gcc executable executable. (example: <install_dir>/bin/riscv32-unknown-elf-gcc)

3. Set environment variable RISCV_OBJCOPY to RISC-v objcopy executable executable. (example: <install_dir>/bin/riscv32-unknown-elf-objcopy)

Sample .bashrc setup:

    export RISCV_TOOLCHAIN=<riscv_gcc_install_path>
    export RISCV_GCC="$RISCV_TOOLCHAIN/bin/riscv32-unknown-elf-gcc"
    export RISCV_OBJCOPY="$RISCV_TOOLCHAIN/bin/riscv32-unknown-elf-objcopy"

### Setup ISS (instruction set simulator)

Currently three ISS are supported, the default ISS is spike. You can install any one of below to run ISS simulation.

1. - spike setup

   - Follow the instructions to build spike

   - Build spike with “–enable-commitlog”

   - Set environment variable SPIKE_PATH to the directory of the spike binary

2. - riscv-ovpsim setup

   - Download the riscv-ovpsim binary

   - Set environment variable OVPSIM_PATH to the directory of the ovpsim binary

3. - whisper (swerv-ISS) setup

   - Follow the instruction to install the ISS, and set WHISPER_ISS to the whisper binary

4. - sail-riscv setup

   - Follow the sail-riscv steps to install sail-riscv

   - Set environment variable SAIL_RISCV to the path of sail-riscv binary

Sample .bashrc setup:

    export SPIKE_PATH=$RISCV_TOOLCHAIN/bin
    export SAIL_RISCV="xx/xxx/ocaml_emulator"
    export OVPSIM_PATH=/xx/xxx/riscv-ovpsim/bin/Linux64
    export WHISPER_ISS="xx/xxx/swerv-ISS/build-Linux/whisper"

### Running the generator

A simple script run.py is provided for you to run a single test or a regression.

You can use –help to get the complete command reference:

    run --help

Here is the command to run a single test:

    run --test=riscv_arithmetic_basic_test

You can specify the simulator by “-simulator” option:

    run --test riscv_arithmetic_basic_test --simulator ius
    run --test riscv_arithmetic_basic_test --simulator vcs
    run --test riscv_arithmetic_basic_test --simulator questa
    run --test riscv_arithmetic_basic_test --simulator dsim
    run --test riscv_arithmetic_basic_test --simulator qrun
    run --test riscv_arithmetic_basic_test --simulator riviera

The complete test list can be found in base testlist yaml. To run a full regression, simply use below command:

    run

You can also run multiple generator jobs in parallel through LSF:

    run --lsf_cmd="bsub -Is"

Here’s a few more examples of the run command:

    # Run a single test 10 times
    run --test riscv_arithmetic_basic_test --iterations 10

    # Run multiple tests
    run --test riscv_arithmetic_basic_test,riscv_rand_instr_test

    # Run a test with verbose logging
    run --test riscv_arithmetic_basic_test --verbose

    # Run a test with a specified seed
    run --test riscv_arithmetic_basic_test --seed 123

    # Skip the generation, run ISS simulation with previously generated program
    run --test riscv_arithmetic_basic_test --steps iss_sim

    # Run the generator only, do not compile and simluation with ISS
    run --test riscv_arithmetic_basic_test --steps gen

    # Compile the generator only, do not simulate
    run --test riscv_arithmetic_basic_test --co

    ....

### Run ISS simulation

You can use -iss to run with different ISS:

    # Run ISS with spike
    run --test riscv_arithmetic_basic_test --iss spike

    # Run ISS with riscv-ovpsim
    run --test riscv_rand_instr_test --iss ovpsim

    # Run ISS with whisper (swerv-ISS)
    run --test riscv_rand_instr_test --iss whisper

    # Run ISS with sail-riscv
    run --test riscv_rand_instr_test --iss sail

To run with ISS simulation for RV32IMC, you can specify ISA and ABI from command line like this:

    # Run a full regression with RV32IMC
    run --isa rv32imc --mabi ilp32

We have added a flow to run ISS simulation with both spike and riscv-ovpsim, the instruction trace from these runs will be cross compared. This could greatly speed up your development of new test without the need to simulate against a real RISC-V processor:

    run --test=riscv_rand_instr_test --iss=spike,ovpsim
    run --test=riscv_rand_instr_test --iss=ovpsim,whisper
    run --test=riscv_rand_instr_test --iss=spike,sail

### Run directed assembly/C tests

Sometimes it might be useful to run some hand-coded assembly/C tests to hit some corner cases:

    # Run a single/multiple assembly/C test
    run --asm_test asm_test_path1/asm_test1.S,asm_test_path2/asm_test2.S
    run --c_test c_test_path1/c_test1.c,c_test_path2/c_test2.c

    # Run regression with all assembly test(*.S)/ C test(*.c) under a given directory
    run --asm_test asm_test_path1,asm_test_path2
    run --c_test c_test_path1,c_test_path2

    # Run mix between the assembly/C test and assembly/C test under a directory
    run --asm_test asm_test_path1/asm_test1.S,asm_test_path2
    run --c_test c_test_path1/c_test1.c,c_test_path2

You could also use this approach to integrate the assembly/C tests from other sources to riscv-dv flow.

## Configuration

### Configure the generator to match your processor features

The default configuration of the instruction generator is RV32IMC (machine mode only). A few pre-defined configurations can be found under “target” directory, you can run with these targets if it matches your processor specificationi:

    run                   # Default target rv32imc
    run --target rv32i    # rv32i, machine mode only
    run --target rv32imc  # rv32imc, machine mode only
    run --target rv64imc  # rv64imc, machine mode only
    run --target rv64gc   # rv64gc, SV39, M/S/U mode

If you want to have a custom setting for your processor, you can make a copy of existing target directory as the template, and modify riscv_core_setting.sv to match your processor capability.

    // Bit width of RISC-V GPR
    parameter int XLEN = 64;

    // Parameter for SATP mode, set to BARE if address translation is not supported
    parameter satp_mode_t SATP_MODE = SV39;

    // Supported Privileged mode
    privileged_mode_t supported_privileged_mode[] = {USER_MODE,
                                                     SUPERVISOR_MODE,
                                                     MACHINE_MODE};

    // Unsupported instructions
    riscv_instr_name_t unsupported_instr[] = {};

    // ISA supported by the processor
    riscv_instr_group_t supported_isa[] = {RV32I, RV32M, RV64I, RV64M};

You can then run the generator with `--custom_target <target_dir>`:

    # You need to manually specify isa and mabi for your custom target
    run --custom_target <target_dir> --isa <isa> --mabi <mabi>
    ...

### Setup the memory map

Here’s a few cases that you might want to allocate the instruction and data sections to match the actual memory map

1. The processor has internal memories, and you want to test load/store from various internal/externel memory regions

2. The processor implments the PMP feature, and you want to configure the memory map to match PMP setting.

3. Virtual address translation is implmented and you want to test load/store from sparse memory locations to verify data TLB replacement logic.

You can configure the memory map in riscv_instr_gen_config.sv:

    mem_region_t mem_region[$] = '{
        '{name:"region_0", size_in_bytes: 4096,      xwr: 3'b111},
        '{name:"region_1", size_in_bytes: 4096 * 4,  xwr: 3'b111},
        '{name:"region_2", size_in_bytes: 4096 * 2,  xwr: 3'b111},
        '{name:"region_3", size_in_bytes: 512,       xwr: 3'b111},
        '{name:"region_4", size_in_bytes: 4096,      xwr: 3'b111}
    };

Each memory region belongs to a separate section in the generated assembly program. You can modify the link script to link each section to the target memory location. Please avoid setting a large memory range as it could takes a long time to randomly initializing the memory. You can break down a large memory region to a few representative small regions which covers all the boundary conditions for the load/store testing.

### Setup regression test list

Test list in YAML format

    # test            : Assembly test name
    # description     : Description of this test
    # gen_opts        : Instruction generator options
    # iterations      : Number of iterations of this test
    # no_iss          : Enable/disable ISS simulation (Optional)
    # gen_test        : Test name used by the instruction generator
    # rtl_test        : RTL simulation test name
    # cmp_opts        : Compile options passed to the instruction generator
    # sim_opts        : Simulation options passed to the instruction generator
    # no_post_compare : Enable/disable log comparison (Optional)
    # compare_opts    : Options for the RTL & ISS trace comparison

    - test: riscv_arithmetic_basic_test
      description: >
        Arithmetic instruction test, no load/store/branch instructions
      gen_opts: >
        +instr_cnt=10000
        +num_of_sub_program=0
        +no_fence=1
        +no_data_page=1'b1
        +no_branch_jump=1'b1
        +boot_mode=m
      iterations: 2
      gen_test: riscv_instr_base_test
      rtl_test: core_base_test

You can also add directed assembly/C test in the testlist

    - test: riscv_single_c_test
      description: >
         single c test entry
      iterations: 1
      c_test: sample_c.c

    - test: riscv_c_regression_test
      description: >
        Run all c tests under the given directory
      iterations: 1
      c_test: c_test_directory
      gcc_opts:
         # Some custom gcc options

    - test: riscv_single_asm_test
      description: >
         single assembly test entry
      iterations: 1
      asm_test: sample_asm.S

    - test: riscv_asm_regression_test
      description: >
        Run all assembly tests under the given directory
      iterations: 1
      asm_test: assembly_test_directory
      gcc_opts:
         # Some custom gcc options

### Runtime options of the generator

Option | Description | Default
--- | --- | ---
num_of_tests | Number of assembly tests to be generated | 1
num_of_sub_program | Number of sub-program in one test | 5
instr_cnt | Instruction count per test | 200
enable_page_table_exception | Enable page table exception | 0
enable_unaligned_load_store | Enable unaligned memory operations | 0
no_ebreak | Disable ebreak instruction | 1
no_wfi | Disable WFI instruction | 1
set_mstatus_tw | Enable WFI to be treated as illegal instruction | 0
no_dret | Disable dret instruction | 1
no_branch_jump | Disable branch/jump instruction | 0
no_csr_instr | Disable CSR instruction | 0
enable_illegal_csr_instruction | Enable illegal CSR instructions | 0
enable_access_invalid_csr_level | Enable accesses to higher privileged CSRs | 0
enable_dummy_csr_write | Enable some dummy CSR writes in setup routine | 0
enable_misaligned_instr | Enable jumps to misaligned instruction addresses | 0
no_fence | Disable fence instruction | 0
no_data_page | Disable data page generation | 0
disable_compressed_instr | Disable compressed instruction generation | 0
illegal_instr_ratio | Number of illegal instructions every 1000 instr | 0
hint_instr_ratio | Number of HINT instructions every 1000 instr | 0
boot_mode | m:Machine mode, s:Supervisor mode, u:User mode | m
no_directed_instr | Disable directed instruction stream | 0
require_signature_addr | Set to 1 if test needs to talk to testbench | 0
signature_addr | Write to this addr to send data to testbench | 0
enable_interrupt | Enable MStatus.MIE, used in interrupt test | 0
enable_timer_irq | Enable xIE.xTIE, used to enable timer interrupts | 0
gen_debug_section | Enables randomized debug_rom section | 0
num_debug_sub_program | Number of debug sub-programs in test | 0
enable_ebreak_in_debug_rom | Generate ebreak instructions inside debug ROM | 0
set_dcsr_ebreak | Randomly enable dcsr.ebreak(m/s/u) | 0
enable_debug_single_step | Enable debug single stepping functionality | 0
randomize_csr | Fully randomize main CSRs (xSTATUS, xIE) | 0

### Setup Privileged CSR description (optional)

This YAML description file of all CSRs is only required for the privileged CSR test. All other standard tests do not use this description.

CSR descriptions in YAML format

    - csr: CSR_NAME
      description: >
        BRIEF_DESCRIPTION
      address: 0x###
      privilege_mode: MODE (D/M/S/H/U)
      rv32:
        - MSB_FIELD_NAME:
          - description: >
              BRIEF_DESCRIPTION
          - type: TYPE (WPRI/WLRL/WARL/R)
          - reset_val: RESET_VAL
          - msb: MSB_POS
          - lsb: LSB_POS
        - ...
        - ...
        - LSB_FIELD_NAME:
          - description: ...
          - type: ...
          - ...
      rv64:
        - MSB_FIELD_NAME:
          - description: >
              BRIEF_DESCRIPTION
          - type: TYPE (WPRI/WLRL/WARL/R)
          - reset_val: RESET_VAL
          - msb: MSB_POS
          - lsb: LSB_POS
        - ...
        - ...
        - LSB_FIELD_NAME:
          - description: ...
          - type: ...
          - ...

To specify what ISA width should be generated in the test, simply include the matching rv32/rv64/rv128 entry and fill in the appropriate CSR field entries.

### Privileged CSR Test Generation (optional)

The CSR generation script is located at scripts/gen_csr_test.py. The CSR test code that this script generates will execute every CSR instruction on every processor implemented CSR, writing values to the CSR and then using a prediction function to calculate a reference value that will be written into another GPR. The reference value will then be compared to the value actually stored in the CSR to determine whether to jump to the failure condition or continue executing, allowing it to be completely self checking. This script has been integrated with run.py. If you want to run it separately, you can get the command reference with –help:

    python3 scripts/gen_csr_test.py --help

#### Adding new instruction stream and test

Please refer to src/src/riscv_load_store_instr_lib.sv for an example on how to add a new instruction stream. After the new instruction stream is created, you can use a runtime option to mix it with random instructions:

    //+directed_instr_n=instr_sequence_name,frequency(number of insertions per 1000 instructions)
    +directed_instr_5=riscv_multi_page_load_store_instr_stream,4

    // An alternative command line options for directed instruction stream
    +stream_name_0=riscv_multi_page_load_store_instr_stream
    +stream_freq_0=4

#### Integrate a new ISS

You can add a new entry in iss.yaml:

    - iss: new_iss_name
      path_var: ISS_PATH
      cmd: >
        <path_var>/iss_executable --isa=<variant> -l <elf>

Simulate with the new ISS:

    run --test riscv_arithmetic_basic_test --iss new_iss_name

## Extension Support

### Bit Manipulation Extension

### Setup RISCV-GCC compiler toolchain and ISS simulator

1. Install riscv-gcc toolchain

> - Download the repo and add ” –enable-commitlog” at the end of line 6 in build_file as it’s not enabled by default

> - Follow the steps to install GCC and spike

2. Update environment variable RISCV_GCC to the RISC-V gcc executable executable. (example: <install_dir>/bin/riscv64-unknown-elf-gcc)

3. Update environment variable RISCV_OBJCOPY to RISC-V objcopy executable executable. (example: <install_dir>/bin/riscv64-unknown-elf-objcopy)

4. Update environment variable SPIKE_PATH to the directory of the spike binary

5. Update riscv-ovpsim to Nov 26, 2019 or later version

Sample .bashrc setup:

    export RISCV_TOOLCHAIN=<riscv_gcc_install_path>
    export RISCV_GCC="$RISCV_TOOLCHAIN/bin/riscv64-unknown-elf-gcc"
    export RISCV_OBJCOPY="$RISCV_TOOLCHAIN/bin/riscv64-unknown-elf-objcopy"
    export SPIKE_PATH="$RISCV_TOOLCHAIN/bin"

### Run bitmanip simulation

Bit manipulation tests are added in target “rv32imcb” or “rv64imcb”. Here is the example to run bitmanip test with both ISS (spike and ovpsim). The instruction trace from these ISS will be cross compared:

    run --target rv32imcb --test riscv_b_ext_test --iss spike,ovpsim

In bitmanip testlist, there are a few bitmanip tests. Run option “+enable_b_extension=1” enables it and “+enable_bitmanip_groups=zbb,zbt” allows user to only enable one or some groups of bit manipulation instructions.

### Functional Coverage Support For Bitmanip

The functional covergroup is defined in riscv_instr_cover_group.sv. It includes below major categories:

- If the operand is a register, cover all possible reg values for each operand:

      cp_rs1 : coverpoint instr.rs1;
      cp_rs2 : coverpoint instr.rs2;
      cp_rd  : coverpoint instr.rd;

- If the operand is an integer value and the amount of these possible values is less than XLEN*2, cover all the values:

      // Cover all the count values of leading/Trailing zeros (0:XLEN-1) for clz, ctz
      `B_R_INSTR_NO_RS2_CG_BEGIN(clz)
        `CP_VALUE_RANGE(num_leading_zeros, instr.rd_value, 0, XLEN-1)
      `CG_END

      // Cover all the shift values (0:XLEN-1) for slo
      `B_R_INSTR_CG_BEGIN(slo)
        `CP_VALUE_RANGE(num_ones_shift, instr.rs2_value, 0, XLEN-1)
      `CG_END

- Hazard conditions

Before this issue is resolved, functional coverage can only be run with OVPsim:

    cov --dir out/ovpsim_sim --iss ovpsim --target rv32imc

## End to End Simulation Flow

We have collaborated with lowRISC to apply this flow for Ibex RISC-V core verification. You can use it as a reference to setup end-to-end co-simulation flow. This repo is still under active development, this is the recommended approach to customize the instruction generator while keeping the effort of merging upstream changes to a minimum.

1. Do not modify the upstream classes directly. When possible, extend from the upstream classses and implement your own functionalities.

2. Add your extensions under user_extension directory, and add the files to user_extension/user_extension.svh. If you prefer to put your extensions in a different directory, you can use “-ext <user_extension_path>” to override the user extension path.

3. Create a new target directory and customize the setting and testlist

4. Run the generator with `--custom_target <target_dir> --isa <isa> --mabi <mabi>`

5. Use command line type override to use your extended classes. `--sim_opts="+uvm_set_type_override=<upstream_class>,<extended_class>"`

6. If extending `riscv_asm_program_gen` class is desired, must use this command line override: `--sim_opts="+uvm_set_inst_override=riscv_asm_program_gen,<extended class>,'uvm_test_top.asm_gen'"`

You can refer to riscv-dv extension for Ibex for a working example.

We have plan to open-source the end-to-end environments of other advanced RISC-V processors. Stay tuned!

## Generator Flow

## Coverage Model

### Functional coverage (work in progress)

This flow extracts functional coverage information from the instruction trace generated by ISS. It’s independent of the instruction generation flow and does not require a tracer implementation in the RTL. You can use this flow as long as your program can be run with an ISS supported in this flow. The flow parses the instruction trace log and converts it to a CSV trace format. After that, a SV test is run to process the CSV trace files and sample functional coverage from there.

The functional covergroup is defined in riscv_instr_cover_group.sv. It includes below major categories:

- Cover all operands for each instruction

- Hazard conditions

- Corner cases like overflow, underflow, divide by zero

- Aligned/unaligned load/store

- Positive/negative immediate value

- Forward/backward branches, branch hit history

- Hint instruction

- Illegal instruction

- All compressed and non-compressed opcode

- Access to all implemened privileged CSR

- Exception and interrupt

The functional covergroup is still under active development. Please feel free to add anything you are interested or file a bug for any feature request.

Before start, please check the you have modified riscv_core_setting.sv to reflect your processor capabilities. The covergroup is selectively instantiated based on this setting so that you don’t need to deal with excluding unrelated coverpoints later. You also need to get the Spike ISS or riscvOVPsim ISS (riscv-ovpsim) setup before running this flow:

    # Process spike simulation log and collect functional coverage
    cov --dir out/spike_sim

    # Get the command reference
    cov --help

    # Run the coverage flow with predefined targets
    cov --dir out/spike_sim --target rv32imc

The coverage sampling from the CSV could be time consuming if you have a large number of log to process. You can split them to small batches and run with LSF in parallel:

    # Split the run to process 5 CSV at a time, and run with LSF
    cov --dir out/spike_sim --lsf_cmd "bsub ....." -bz 5

## Customize and Extend Generator

### Add custom instructions

1. Add the new instruction enum to riscv_custom_instr_enum.sv

    CUSTOM_ADD,
    CUSTOM_SUB,
    ...

2. Add custom instruction definition to rv32x_instr.sv/rv64x_instr.sv

    `DEFINE_CUSTOM_INSTR(CUSTOM_ADD, R_FORMAT, ARITHMETIC, RV32X)
    `DEFINE_CUSTOM_INSTR(CUSTOM_SUB, R_FORMAT, ARITHMETIC, RV32X)
    ...

3. Extend riscv_custom_instr.sv and implement key functions like get_instr_name, convert2asm

4. Add RV32X/RV64X to supported_isa in riscv_core_setting.sv

## Class Reference

## Command Line Reference

## Overview

While the `RISCV-DV` Instruction Generator provides a `debug_rom` section as well as various interrupt and exception handlers, from a verification standpoint this is not enough to help ensure that a core’s internal state is being updated correctly upon receiving external stimulus such as interrupts or debug requests, such as the values of any relevant CSRs. To help with this issue, the instruction generator also provides a mechanism by which to communicate information to a RTL simulation environment via a handshaking protocol.

### Usage

Every handshake produced by the instruction generator is just a small segment of RISC-V assembly code, that end in one or more `sw` instructions to a specified memory address `signature_addr`. This `signature_addr` is completely customizable, and can be specified through a `plusarg` runtime option to the generator. There is also an enable bit `require_signature_addr` that must be set through another `plusarg` argument to enable these handshake code segments to be generated in the main random assembly program.

A `RISCV-DV` based CPU verification environment that utilizes that handshaking mechanism should provide a basic set of tasks to monitor this `signature_addr` for any writes - this will indicate that the DUT is executing a particular handshake assembly sequence and is transmitting some information to the testbench for analysis. As a result, this `signature_addr` acts as a memory-mapped address that the testbench will monitor, and as such, case should be taken when setting this address to ensure that the generator’s program randomization will not somehow create a sequence of random load/store instructions that access the same `signature_addr`. A suggested value for this `signature_addr` is the value `0x8ffffffc`, but can be set to any other address depending on the CPU memory map. More details, and an example, as to how to interface the testbench with this handshake mechanism will be provided below.

### Handshake Sequence Architecture

The function `gen_signature_handshake(...)` contained in src/riscv_asm_program_gen.sv. is used to actually generate the handshaking code and push it into the specified instruction queue. Its usage can be seen repeatedly throughout the program generation in various places, such as trap handlers and the debug ROM, where it is important to send information to a testbench for further verification. The `signature_type_t`, `core_status_t`, and `test_result_t` enums specified as input values to this function are defined in src/riscv_signature_pkg.sv. Note that all of these definitions are within a standalone package, this is so that an RTL simulation environment can also import this package to gain access to these enums. The `signature_type_t` enum is by far the most important enum value, as this specifies what kind of handshake will be generated.

There are currently 4 defined values of `signature_type`, each corresponding to a different handshake type that will be generated; each will be explained below. Note that two GPRs must be used to temporarily hold the store address and the actual data to store to this address; the generator randomizes these two GPRs for every generated program, but for the purposes of this document, `x1` and `x2` will be used, and `0x8ffffffc` will be used as the example `signature_addr`.

#### CORE_STATUS

When the `signature_type` argument is specified as `CORE_STATUS`, a single word of data will be written to the `signature_addr`. As the actual `signature_type` value is 8 bits wide, as specified in the `riscv_signature_pkg`, this generated data word will contain the `CORE_STATUS` value in its bottom 8 bits, and will contain the specified value of `core_status_t` in the upper 24 bits. This signature handshake is intended to convey basic information about the core’s execution state to an RTL simulation environment; a handshake containing a `core_status` of `IN_DEBUG_MODE` is added to the debug ROM to indicate to a testbench that the core has jumped into Debug Mode and is executing the debug ROM, a handshake containing a `core_status` of `ILLEGAL_INSTR_EXCEPTION` is added to the illegal instruction exception handler code created by the generator to indicate to a testbench that the core has trapped to and is executing the proper handler after encountering an illegal instruction, and so on for the rest of the defined `core_status_t` enum values.

Note that when generating these specific handshakes, it is only necessary to specify the parameters `instr`, `signature_type`, and `core_status`. For example, to generate this handshake to signal status `IN_MACHINE_MODE` to the testbench, the call to the function looks like this:

    gen_signature_handshake(.instr(instr_string_queue),
                            .signature_type(CORE_STATUS),
                            .core_status(IN_MACHINE_MODE));

The sequence of assembly code generated by this call looks like the following:

    // First, load the signature address into a GPR
    li x2, 0x8ffffffc
    // Load the intended core_status_t enum value into
    // a second GPR
    li x1, 0x2
    // Left-shift the core_status value by 8 bits
    // to make room for the signature_type
    slli x1, x1, 8
    // Load the intended signature_type_t enum value into
    // the bottom 8 bits of the data word
    addi x1, x1, 0x0
    // Store the data word to memory at the location of the signature_addr
    sw x1, 0(x2)

#### TEST_RESULT

As before, when `signature_type` is set to `TEST_RESULT` a single word of data will be written to the signature address, and the value `TEST_RESULT` will be placed in the bottom 8 bits. The upper 24 bits will then contain a value of type `test_result_t`, either `TEST_PASS` or `TEST_FAIL`, to indicate to the testbench the exit status of the test. As the ISS co-simulation flow provides a robust end-of-test correctness check, the only time that this signature handshake is used is in the `riscv_csr_test`. Since this test is generated with a Python script and is entirely self-checking, we must send an exit status of `TEST_PASS` or `TEST_FAIL` to the testbench to indicate to either throw an error or end the test correctly.

Note that when generating these handshakes, the only arguments that need to be specified are `instr`, `signature_type`, and `test_result`. For example, to generate a handshake to communicate `TEST_PASS` to a testbench, the function call would look like this:

    gen_signature_handshake(.instr(instr_string_queue),
                            .signature_type(TEST_RESULT),
                            .test_result(TEST_PASS));

The sequence of generated assembly code with this function call would look like the following:

    // Load the signature address into a GPR
    li x2 0x8ffffffc
    // Load the intended test_result_t enum value
    li x1, 0x0
    // Left-shift the test_result value by 8 bits
    slli x1, x1, 8
    // Load the intended signature_type_t enum value into
    // the bottom 8 bits of the data word
    addi x1, x1, 0x1
    // Store this formatted word to memory at the signature address
    sw x1, 0(x2)

#### WRITE_GPR

When a `signature_type` of `WRITE_GPR` is passed to the `gen_signature_handshake(...)` function, one data word will initially be written to the signature address, containing the `signature_type` of `WRITE_GPR` in the lower 8 bits. After this, the value held by each of the 32 RISC-V general purpose registers from `x0` to `x31` will be written to the signature address with `sw` instructions.

For this particular handshake, the only function arguments that need to be specified are `instr` and `signature_type`. A function call to generate this particular handshake would look like the following:

    gen_signature_handshake(.instr(instr_string_queue),
                            .signature_type(WRITE_GPR));

The generated assembly sequence would look like this:

    // Load the signature address into a GPR
    li x2, 0x8ffffffc
    // Load the value of WRITE_GPR into a second GPR
    li x1, 0x2
    // Store this word to memory at the signature address
    sw x1, 0(x2)
    // Iterate through all 32 GPRs and write each one to
    // memory at the signature address
    sw x0, 0(x2)
    sw x1, 0(x2)
    sw x2, 0(x2)
    sw x3, 0(x2)
    ...
    sw x30, 0(x2)
    sw x31, 0(x2)

#### WRITE_CSR

When `gen_signature_handshake(...)` is called with `WRITE_CSR` as the `signature_type` argument, we will generate a first `sw` instruction that writes a data word to the `signature_addr` that contains the value `WRITE_CSR` in the bottom 8 bits, and the address of the desired CSR in the upper 24 bits, to indicate to the testbench which CSR will be written. This first generated `sw` instruction is then followed by a second one, which writes the actual data contained in the specified CSR to the signature address.

Note the only function arguments that have to be specified to generate this handshake are `instr`, `signature_type`, and `csr`. As an example, to generate a handshake that writes the value of the mie CSR to the RTL simulation environment, the function call would look like this:

    gen_signature_handshake(.instr(instr_string_queue),
                            .signature_type(WRITE_CSR),
                            .csr(MIE));

The sequence of assembly generated by this call would look like the following:

    // Load the signature address into a GPR
    li x2, 0x8ffffffc
    // Load the address of MIE into the second GPR
    li x1, 0x304
    // Left-shift the CSR address by 8 bits
    slli x1, x1, 8
    // Load the WRITE_CSR signature_type value into
    // the bottom 8 bits of the data word.
    // At this point, the data word is 0x00030403
    addi x1, x1, 0x3
    // Store this formatted word to memory at the signature address
    sw x1, 0(x2)
    // Read the actual CSR value into the second GPR
    csrr x1, 0x304
    // Write the value held by the CSR into memory at the signature address
    sw x1, 0(x2)

### Sample Testbench Integration

Everything previously outlined has been relating to how this handshake generation is implemented from the perspective of the `RISCV-DV` instruction generator, but some work must be done in the RTL simulation environment to actually interface with and use these handshakes to improve verification.

This handshaking mechanism has been put to use for verification of the Ibex RISC-V core, in collaboration with lowRISC. To interface with the handshaking code produced in the generator, this testbench makes heavy use of the task `wait_for_mem_txn(...)` found in tests/core_ibex_base_test.sv. This task polls the Ibex core’s data memory interface for any writes to the chosen signature address (`0x8ffffffc`), and then based on the value of `signature_type` encoded by the generated handshake code, this task takes appropriate action and stores the relevant data into a queue instantiated in the base test class.

For example upon detecting a transaction written to the signature address that has a `signature_type` of `WRITE_CSR`, it right-shifts the collected data word by 8 to obtain the CSR address, which is then stored to the local queue. However, since for `WRITE_CSR` signatures there is a second data word that gets written to memory at the signature address, the task waits for the second write containing the CSR data to arrive, and then stores that into the queue as well. After this task completes, it is now possible to pop the stored data off of the queue for analysis anywhere else in the test class, in this case examining the values of various CSR fields.

Additionally, the Ibex testbench provides a fairly basic API of some tasks wrapping `wait_for_mem_txn(...)` for frequently used functionalities in various test classes. This API is also found in tests/core_ibex_base_test.sv. Examples of use-cases for these API functions can be found throughout the library of tests written for the Ibex core, found at tests/core_ibex_test_lib.sv, as these are heavily used to verify the core’s response to external debug and interrupt stimulus.

## Appendix

### Trace CSV format

A standard CSV format is defined for the instruction execution trace. It’s used for co-simulation result comparison and functional coverage collection.

The CSV format includes the following fields:

- pc : Program counter (instruction memory address)

- instr: Instruction name

- gpr: General purpose register updated by the instruction (rd, fd, vd etc.)

  - Format: <GPR name>:<Value>

  - GPR can be any integer/floating point/vector register

  - If more than one general purpose registers are updated, separate them with semicolon

- csr: Privileged CSR updated by the instruction

  - The same format as the GPR field

- binary: Instruction binary

- instr_str: Instruction in assembly format

- operand: Instruction operands

- pad: Unused

Here’s a sample of the CSV trace file:

    pc,instr,gpr,csr,binary,mode,instr_str,operand,pad
    ffffffff8000000c,c.addi,ra:daab700e,,000000b9,3,"c.addi  ra, 14","ra,14",
    ffffffff8000000e,lui,sp:ff8e6000,,ff8e6137,3,"lui     sp, 0xff8e6","sp,0xff8e6",
    ffffffff80000012,addi,sp:ff8e6541,,54110113,3,"addi    sp, sp, 1345","sp,sp,1345",
    ffffffff80000016,c.li,gp:00000000,,00004181,3,"c.li    gp, 0","gp,0",
    ffffffff80000018,lui,tp:80000000,,80000237,3,"lui     tp, 0x80000","tp,0x80000",
    ffffffff8000001c,lui,t0:f999d000,,f999d2b7,3,"lui     t0, 0xf999d","t0,0xf999d",
    ffffffff80000020,addi,t0:f999cbf0,,bf028293,3,"addi    t0, t0, -1040","t0,t0,-1040",
    ffffffff80000024,lui,t1:0416b000,,0416b337,3,"lui     t1, 0x416b","t1,0x416b",
    ffffffff80000028,addi,t1:0416b6ee,,6ee30313,3,"addi    t1, t1, 1774","t1,t1,1774",
    ffffffff8000002c,lui,t2:e6420000,,e64203b7,3,"lui     t2, 0xe6420","t2,0xe6420",
    ...

To integrate a new ISS or processor with the co-simualtion or coverage flow, user must implement a python script to convert the custom trace log format to this CSV format. You can find a sample script here.

# Indices and tables

- Index

- Module Index

- Search Page