Update google_riscv-dv to google/riscv-dv@1ad73cc

Update code from upstream repository https://github.com/google/riscv-
dv to revision 1ad73cc43f8f84d93d49040f8b2928e74efdd854

* Fixes for ML tests (Udi Jonnalagadda)
* Add missing default case to pmp_config (google/riscv-dv#583)
  (udinator)
* various PMP exception handler fixes (google/riscv-dv#581) (udinator)
* convert handshake doc to rst format (google/riscv-dv#580) (udinator)
* Update coverage (google/riscv-dv#584) (weicaiyang)

Signed-off-by: Udi <udij@google.com>
This commit is contained in:
Udi 2020-05-31 22:21:36 -07:00 committed by udinator
parent 4b01580a7b
commit f4366264e4
9 changed files with 430 additions and 377 deletions

View file

@ -9,6 +9,6 @@
upstream:
{
url: https://github.com/google/riscv-dv
rev: 7b38e54c5e833f147edc03717b3fd711be923026
rev: 1ad73cc43f8f84d93d49040f8b2928e74efdd854
}
}

View file

@ -1,250 +0,0 @@
## 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 by means of 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.
An RTL simulation environment that utilizes
this handshaking mechanism should provide a basic set of tasks to monitor this
`signature_addr` for any writes, as this will indicate that the core under test 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 sort of 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`.
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](https://github.com/google/riscv-dv/blob/master/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](https://github.com/google/riscv-dv/blob/master/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 with
this mechanism.
This handshaking mechanism has been put to use for verification of the [Ibex
RISC-V core](https://github.com/lowRISC/ibex), 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
[core_ibex_base_test.sv](https://github.com/lowRISC/ibex/blob/master/dv/uvm/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
[core_ibex_base_test.sv](https://github.com/lowRISC/ibex/blob/master/dv/uvm/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
[core_ibex_test_lib.sv](https://github.com/lowRISC/ibex/blob/master/dv/uvm/tests/core_ibex_test_lib.sv), as these are heavily used to verify the core's response to external debug and interrupt stimulus.

View file

@ -0,0 +1,270 @@
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 <https://github.com/google/riscv-dv/blob/master/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 <https://github.com/google/riscv-dv/blob/master/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:
.. code-block:: verilog
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:
.. code-block:: verilog
// 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:
.. code-block:: verilog
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:
.. code-block:: verilog
// 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:
.. code-block:: verilog
gen_signature_handshake(.instr(instr_string_queue),
.signature_type(WRITE_GPR));
The generated assembly sequence would look like this:
.. code-block:: verilog
// 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:
.. code-block:: verilog
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:
.. code-block:: verilog
// 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 <https://github.com/lowRISC/ibex>`_, 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 <https://github.com/lowRISC/ibex/blob/master/dv/uvm/core_ibex/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 <https://github.com/lowRISC/ibex/blob/master/dv/uvm/core_ibex/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 <https://github.com/lowRISC/ibex/blob/master/dv/uvm/core_ibex/tests/core_ibex_test_lib.sv>`_, as these are heavily used to verify the core's response to external debug and interrupt stimulus.

View file

@ -20,6 +20,7 @@ Welcome to riscv-dv's documentation!
customize_extend_generator
class_reference
cmd_line_reference
handshake
appendix

View file

@ -497,6 +497,38 @@ class riscv_b_instr extends riscv_instr;
);
endfunction
// coverage related functons
virtual function void update_src_regs(string operands[$]);
// handle special I_FORMAT (FSRI, FSRIW) and R4_FORMAT
case(format)
I_FORMAT: begin
if (instr_name inside {FSRI, FSRIW}) begin
`DV_CHECK_FATAL(operands.size() == 4, instr_name)
// fsri rd, rs1, rs3, imm
rs1 = get_gpr(operands[1]);
rs1_value = get_gpr_state(operands[1]);
rs3 = get_gpr(operands[2]);
rs3_value = get_gpr_state(operands[2]);
get_val(operands[3], imm);
return;
end
end
R4_FORMAT: begin
`DV_CHECK_FATAL(operands.size() == 4)
rs1 = get_gpr(operands[1]);
rs1_value = get_gpr_state(operands[1]);
rs2 = get_gpr(operands[2]);
rs2_value = get_gpr_state(operands[2]);
rs3 = get_gpr(operands[3]);
rs3_value = get_gpr_state(operands[3]);
return;
end
default: ;
endcase
// reuse base function to handle the other instructions
super.update_src_regs(operands);
endfunction : update_src_regs
endclass

View file

@ -150,4 +150,77 @@ class riscv_floating_point_instr extends riscv_instr;
fd.rand_mode(has_fd);
endfunction
// coverage related functons
virtual function void update_src_regs(string operands[$]);
if(category inside {LOAD, CSR}) begin
super.update_src_regs(operands);
return;
end
case(format)
I_FORMAT: begin
`DV_CHECK_FATAL(operands.size() == 2)
if (has_fs1) begin
fs1 = get_fpr(operands[1]);
fs1_value = get_gpr_state(operands[1]);
end else if (has_rs1) begin
rs1 = get_gpr(operands[1]);
rs1_value = get_gpr_state(operands[1]);
end
end
S_FORMAT: begin
`DV_CHECK_FATAL(operands.size() == 3)
// FSW rs2 is fp
fs2 = get_fpr(operands[0]);
fs2_value = get_gpr_state(operands[0]);
rs1 = get_gpr(operands[2]);
rs1_value = get_gpr_state(operands[2]);
get_val(operands[1], imm);
end
R_FORMAT: begin
if (has_fs2 || category == CSR) begin
`DV_CHECK_FATAL(operands.size() == 3)
end else begin
`DV_CHECK_FATAL(operands.size() == 2)
end
if(category != CSR) begin
fs1 = get_fpr(operands[1]);
fs1_value = get_gpr_state(operands[1]);
if (has_fs2) begin
fs2 = get_fpr(operands[2]);
fs2_value = get_gpr_state(operands[2]);
end
end
end
R4_FORMAT: begin
`DV_CHECK_FATAL(operands.size() == 4)
fs1 = get_fpr(operands[1]);
fs1_value = get_gpr_state(operands[1]);
fs2 = get_fpr(operands[2]);
fs2_value = get_gpr_state(operands[2]);
fs3 = get_fpr(operands[3]);
fs3_value = get_gpr_state(operands[3]);
end
default: `uvm_fatal(`gfn, $sformatf("Unsupported format %0s", format))
endcase
endfunction : update_src_regs
virtual function void update_dst_regs(string reg_name, string val_str);
$display("update_dst_regs %0s", reg_name);
get_val(val_str, gpr_state[reg_name], .hex(1));
if (has_fd) begin
fd = get_fpr(reg_name);
fd_value = get_gpr_state(reg_name);
end else if (has_rd) begin
rd = get_gpr(reg_name);
rd_value = get_gpr_state(reg_name);
end
endfunction : update_dst_regs
virtual function riscv_fpr_t get_fpr(input string str);
str = str.toupper();
if (!fpr_enum::from_name(str, get_fpr)) begin
`uvm_fatal(`gfn, $sformatf("Cannot convert %0s to FPR", str))
end
endfunction : get_fpr
endclass

View file

@ -62,8 +62,7 @@
logical_similarity_e logical_similarity;
string trace;
// TODO, remove it?
//`VECTOR_INCLUDE("riscv_instr_cov_item_inc_declares.sv")
`VECTOR_INCLUDE("riscv_instr_cov_item_inc_declares.sv")
virtual function void pre_sample();
unaligned_pc = (pc[1:0] != 2'b00);
@ -274,13 +273,7 @@
get_val(operands[1], imm);
end
I_FORMAT: begin
// TODO, support I_FORMAT floating point later
// if (group == RV32F) return;
if (instr_name inside {FSRI, FSRIW}) begin
`DV_CHECK_FATAL(operands.size() == 4, instr_name)
end else begin
`DV_CHECK_FATAL(operands.size() == 3, instr_name)
end
`DV_CHECK_FATAL(operands.size() == 3, instr_name)
if(category == LOAD) begin
// load rd, imm(rs1)
rs1 = get_gpr(operands[2]);
@ -294,13 +287,6 @@
end else begin
get_val(operands[1], csr);
end
//end else if (instr_name inside {FSRI, FSRIW}) begin
// // fsri rd, rs1, rs3, imm
// rs1 = get_gpr(operands[1]);
// rs1_value = get_gpr_state(operands[1]);
// rs3 = get_gpr(operands[2]);
// rs3_value = get_gpr_state(operands[2]);
// get_val(operands[3], imm);
end else begin
// addi rd, rs1, imm
rs1 = get_gpr(operands[1]);
@ -311,11 +297,6 @@
S_FORMAT, B_FORMAT: begin
`DV_CHECK_FATAL(operands.size() == 3)
if(category == STORE) begin
// sw rs2,imm(rs1)
//update_instr_reg_by_abi_name(operands[0], // FSW rs2 is fp, TODO
// rs2, rs2_value,
// fs2, fs2_value);
rs2 = get_gpr(operands[0]);
rs2_value = get_gpr_state(operands[0]);
rs1 = get_gpr(operands[2]);
@ -331,8 +312,7 @@
end
end
R_FORMAT: begin
if ((has_rs2 || category == CSR) &&
!(instr_name inside {FCLASS_S, FCLASS_D})) begin
if (has_rs2 || category == CSR) begin
`DV_CHECK_FATAL(operands.size() == 3)
end else begin
`DV_CHECK_FATAL(operands.size() == 2)
@ -347,16 +327,6 @@
rs1 = get_gpr(operands[2]);
rs1_value = get_gpr_state(operands[2]);
end
//else if (group inside {RV32F, RV64F, RV32D, RV64D}) begin // TODO
// // fs1
// fs1 = get_fpr(operands[1]);
// fs1_value = get_gpr_state(operands[1]);
// // fs2
// if (!instr_name inside {FCLASS_S, FCLASS_D}) begin
// fs2 = get_fpr(operands[2]);
// fs2_value = get_gpr_state(operands[2]);
// end
//end
else begin
// add rd, rs1, rs2
rs1 = get_gpr(operands[1]);
@ -369,15 +339,6 @@
end
R4_FORMAT: begin
`DV_CHECK_FATAL(operands.size() == 4)
//update_instr_reg_by_abi_name(operands[1], // TODO
// rs1, rs1_value,
// fs1, fs1_value);
//update_instr_reg_by_abi_name(operands[2],
// rs2, rs2_value,
// fs2, fs2_value);
//update_instr_reg_by_abi_name(operands[3],
// rs3, rs3_value,
// fs3, fs3_value);
rs1 = get_gpr(operands[1]);
rs1_value = get_gpr_state(operands[1]);
rs2 = get_gpr(operands[2]);
@ -453,11 +414,11 @@
// c.j imm
get_val(operands[0], imm);
end
default: `uvm_fatal(`gfn, $sformatf("Unsupported format %0s", format))
endcase
endfunction : update_src_regs
virtual function void update_dst_regs(string reg_name, string val_str);
// update_instr_reg_by_abi_name(pair[0], instr.rd, instr.rd_value, instr.fd, instr.fd_value);
get_val(val_str, gpr_state[reg_name], .hex(1));
rd = get_gpr(reg_name);
rd_value = get_gpr_state(reg_name);
@ -480,31 +441,3 @@
return 0;
end
endfunction : get_gpr_state
// TODO
// virtual function riscv_fpr_t get_fpr(input string str);
// str = str.toupper();
// if (!fpr_enum::from_name(str, get_fpr)) begin
// `uvm_fatal(`gfn, $sformatf("Cannot convert %0s to FPR", str))
// end
// endfunction : get_fpr
//
// function bit is_fp_reg(input string str);
// riscv_fpr_t tmp;
// str = str.toupper();
// return fpr_enum::from_name(str, tmp);
// endfunction : is_fp_reg
//
// virtual function void update_instr_reg_by_abi_name(string abi_name,
// ref riscv_reg_t rs,
// ref bit [XLEN-1:0] rs_value,
// ref riscv_fpr_t fs,
// ref bit [XLEN-1:0] fs_value);
// if (is_fp_reg(abi_name)) begin
// fs = get_fpr(abi_name);
// fs_value = get_gpr_state(abi_name);
// end else begin
// rs = get_gpr(abi_name);
// rs_value = get_gpr_state(abi_name);
// end
// endfunction : update_instr_reg_by_abi_name

View file

@ -83,6 +83,7 @@ class riscv_pmp_cfg extends uvm_object;
constraint grain_addr_mode_c {
foreach (pmp_cfg[i]) {
(pmp_granularity == 0) -> (pmp_cfg[i].a != NAPOT);
(pmp_granularity >= 1) -> (pmp_cfg[i].a != NA4);
}
}
@ -121,10 +122,13 @@ class riscv_pmp_cfg extends uvm_object;
super.new(name);
cfg_per_csr = XLEN / 8;
inst = uvm_cmdline_processor::get_inst();
if (inst.get_arg_value("+pmp_num_regions=", s)) begin
pmp_num_regions = s.atoi();
pmp_num_regions.rand_mode(0);
end
get_int_arg_value("+pmp_granularity=", pmp_granularity);
get_bool_arg_value("+pmp_randomize=", pmp_randomize);
get_bool_arg_value("+pmp_allow_addr_overlap=", pmp_allow_addr_overlap);
get_int_arg_value("+pmp_granularity=", pmp_granularity);
get_int_arg_value("+pmp_num_regions=", pmp_num_regions);
get_hex_arg_value("+pmp_max_offset=", pmp_max_offset);
`uvm_info(`gfn, $sformatf("pmp max offset: 0x%0x", pmp_max_offset), UVM_LOW)
pmp_cfg = new[pmp_num_regions];
@ -417,17 +421,17 @@ class riscv_pmp_cfg extends uvm_object;
// based on address match mode, branch to appropriate "handler" //
//////////////////////////////////////////////////////////////////
// pmpcfg[i].A == OFF
$sformatf("beqz x%0d, 21f", scratch_reg[4]),
$sformatf("beqz x%0d, 20f", scratch_reg[4]),
// pmpcfg[i].A == TOR
// scratch_reg[5] will contain pmpaddr[i-1]
$sformatf("li x%0d, 1", scratch_reg[0]),
$sformatf("beq x%0d, x%0d, 22f", scratch_reg[4], scratch_reg[0]),
$sformatf("beq x%0d, x%0d, 21f", scratch_reg[4], scratch_reg[0]),
// pmpcfg[i].A == NA4
$sformatf("li x%0d, 2", scratch_reg[0]),
$sformatf("beq x%0d, x%0d, 26f", scratch_reg[4], scratch_reg[0]),
$sformatf("beq x%0d, x%0d, 25f", scratch_reg[4], scratch_reg[0]),
// pmpcfg[i].A == NAPOT
$sformatf("li x%0d, 3", scratch_reg[0]),
$sformatf("beq x%0d, x%0d, 28f", scratch_reg[4], scratch_reg[0]),
$sformatf("beq x%0d, x%0d, 27f", scratch_reg[4], scratch_reg[0]),
// Error check, if no address modes match, something has gone wrong
$sformatf("j test_done"),
/////////////////////////////////////////////////////////////////
@ -443,26 +447,14 @@ class riscv_pmp_cfg extends uvm_object;
// load number of pmp regions - loop limit
$sformatf("li x%0d, %0d", scratch_reg[1], pmp_num_regions),
// if counter < pmp_num_regions => branch to beginning of loop,
// otherwise jump to the end of the loop.
// otherwise jump to the end of the loop
$sformatf("ble x%0d, x%0d, 19f", scratch_reg[1], scratch_reg[0]),
$sformatf("j 0b"),
// If we reach here, it means that no PMP entry has matched the request.
// If the request was made from S-mode or U-mode, jump immediately to <test_done>.
// To determine the privilege mode of the access, we must read xSTATUS.xPP.
//
// TODO(udinator) - need to update to support execution of this handler in S-mode.
$sformatf("19: csrr x%0d, 0x%0x", scratch_reg[0], MSTATUS),
// Get mstatus.MPP by rightshifting and leftshifting the full CSR value.
$sformatf("slli x%0d, x%0d, %0d", scratch_reg[0], scratch_reg[0], XLEN-13),
$sformatf("srli x%0d, x%0d, %0d", scratch_reg[0], scratch_reg[0], XLEN-2),
// If the MPP field is less than 2'b11 (e.g. S-mode, H-mode, or U-mode),
// jump to <test_done>.
// If the MPP field is set to M-mode jump to the end of the handler,
// otherwise jump to <test_done>.
$sformatf("li x%0d, 3", scratch_reg[1]),
$sformatf("beq x%0d, x%0d, 20f", scratch_reg[0], scratch_reg[1]),
$sformatf("j test_done"),
$sformatf("20: j 34f")
// We must immediately jump to <test_done> since the CPU is taking a PMP exception,
// but this routine is unable to find a matching PMP region for the faulting access -
// there is a bug somewhere.
$sformatf("19: j test_done")
};
/////////////////////////////////////////////////
@ -477,35 +469,36 @@ class riscv_pmp_cfg extends uvm_object;
// Sub-section to deal with address matching mode OFF.
// If entry is OFF, simply continue looping through other PMP CSR.
instr = {instr, "21: j 18b"};
instr = {instr, "20: j 18b"};
// Sub-section to handle address matching mode TOR.
instr = {instr,
$sformatf("22: csrr x%0d, 0x%0x", scratch_reg[0], MSCRATCH),
$sformatf("21: csrr x%0d, 0x%0x", scratch_reg[0], MSCRATCH),
$sformatf("csrr x%0d, 0x%0x", scratch_reg[4], MTVAL),
$sformatf("srli x%0d, x%0d, 2", scratch_reg[4], scratch_reg[4]),
// If loop_counter==0, compare fault_addr to 0
$sformatf("bnez x%0d, 23f", scratch_reg[0]),
$sformatf("bnez x%0d, 22f", scratch_reg[0]),
// If fault_addr < 0 : continue looping
$sformatf("bltz x%0d, 18b", scratch_reg[4]),
$sformatf("j 24f"),
$sformatf("j 23f"),
// If fault_addr < pmpaddr[i-1] : continue looping
$sformatf("23: bgtu x%0d, x%0d, 18b", scratch_reg[5], scratch_reg[4]),
$sformatf("22: bgtu x%0d, x%0d, 18b", scratch_reg[5], scratch_reg[4]),
// If fault_addr >= pmpaddr[i] : continue looping
$sformatf("24: bleu x%0d, x%0d, 18b", scratch_reg[1], scratch_reg[4]),
$sformatf("23: bleu x%0d, x%0d, 18b", scratch_reg[1], scratch_reg[4]),
// If we get here, there is a TOR match, if the entry is locked jump to
// <test_done>, otherwise modify access bits and return
$sformatf("andi x%0d, x%0d, 128", scratch_reg[4], scratch_reg[3]),
$sformatf("beqz x%0d, 25f", scratch_reg[4]),
$sformatf("beqz x%0d, 24f", scratch_reg[4]),
$sformatf("j test_done"),
$sformatf("25: j 30f")
// TODO : update with correct label
$sformatf("24: j 29f")
};
// Sub-section to handle address matching mode NA4.
// TODO(udinator) : add rv64 support
instr = {instr,
$sformatf("26: csrr x%0d, 0x%0x", scratch_reg[0], MTVAL),
$sformatf("25: csrr x%0d, 0x%0x", scratch_reg[0], MTVAL),
$sformatf("srli x%0d, x%0d, 2", scratch_reg[0], scratch_reg[0]),
// Zero out pmpaddr[i][31:30]
$sformatf("slli x%0d, x%0d, 2", scratch_reg[4], scratch_reg[1]),
@ -516,14 +509,15 @@ class riscv_pmp_cfg extends uvm_object;
// If we get here, there is an NA4 address match, jump to <test_done> if the
// entry is locked, otherwise modify access bits
$sformatf("andi x%0d, x%0d, 128", scratch_reg[4], scratch_reg[3]),
$sformatf("beqz x%0d, 27f", scratch_reg[4]),
$sformatf("beqz x%0d, 26f", scratch_reg[4]),
$sformatf("j test_done"),
$sformatf("27: j 30f")
// TODO : update with correct label
$sformatf("26: j 29f")
};
// Sub-section to handle address matching mode NAPOT.
instr = {instr,
$sformatf("28: csrr x%0d, 0x%0x", scratch_reg[0], MTVAL),
$sformatf("27: csrr x%0d, 0x%0x", scratch_reg[0], MTVAL),
// get fault_addr[31:2]
$sformatf("srli x%0d, x%0d, 2", scratch_reg[0], scratch_reg[0]),
// mask the bottom pmp_granularity bits of fault_addr
@ -542,22 +536,26 @@ class riscv_pmp_cfg extends uvm_object;
$sformatf("andi x%0d, x%0d, 128", scratch_reg[4], scratch_reg[3]),
$sformatf("beqz x%0d, 29f", scratch_reg[4]),
$sformatf("j test_done"),
$sformatf("29: j 30f")
// TODO : update with correct label
$sformatf("28: j 29f")
};
// This case statement creates a bitmask that enables the correct access permissions
// and ORs it with the 8-bit configuration fields.
case (fault_type)
INSTRUCTION_ACCESS_FAULT: begin
instr.push_back($sformatf("30: ori x%0d, x%0d, 4", scratch_reg[3], scratch_reg[3]));
instr.push_back($sformatf("29: ori x%0d, x%0d, 4", scratch_reg[3], scratch_reg[3]));
end
STORE_AMO_ACCESS_FAULT: begin
// The combination of W:1 and R:0 is reserved, so if we are enabling write
// permissions, also enable read permissions to adhere to the spec.
instr.push_back($sformatf("30: ori x%0d, x%0d, 3", scratch_reg[3], scratch_reg[3]));
instr.push_back($sformatf("29: ori x%0d, x%0d, 3", scratch_reg[3], scratch_reg[3]));
end
LOAD_ACCESS_FAULT: begin
instr.push_back($sformatf("30: ori x%0d, x%0d, 1", scratch_reg[3], scratch_reg[3]));
instr.push_back($sformatf("29: ori x%0d, x%0d, 1", scratch_reg[3], scratch_reg[3]));
end
default: begin
`uvm_fatal(`gfn, "Invalid PMP fault type")
end
endcase
instr = {instr,
@ -587,23 +585,23 @@ class riscv_pmp_cfg extends uvm_object;
// All other scratch_reg[*] can be used.
// scratch_reg[0] contains the index of the correct pmpcfg CSR.
// We simply check the index and then write to the correct pmpcfg CSR based on its value.
$sformatf("beqz x%0d, 31f", scratch_reg[0]),
$sformatf("beqz x%0d, 30f", scratch_reg[0]),
$sformatf("li x%0d, 1", scratch_reg[4]),
$sformatf("beq x%0d, x%0d, 32f", scratch_reg[0], scratch_reg[4]),
$sformatf("beq x%0d, x%0d, 31f", scratch_reg[0], scratch_reg[4]),
$sformatf("li x%0d, 2", scratch_reg[4]),
$sformatf("beq x%0d, x%0d, 33f", scratch_reg[0], scratch_reg[4]),
$sformatf("beq x%0d, x%0d, 32f", scratch_reg[0], scratch_reg[4]),
$sformatf("li x%0d, 3", scratch_reg[4]),
$sformatf("beq x%0d, x%0d, 34f", scratch_reg[0], scratch_reg[4]),
$sformatf("31: csrw 0x%0x, x%0d", PMPCFG0, scratch_reg[2]),
$sformatf("j 35f"),
$sformatf("32: csrw 0x%0x, x%0d", PMPCFG1, scratch_reg[2]),
$sformatf("j 35f"),
$sformatf("33: csrw 0x%0x, x%0d", PMPCFG2, scratch_reg[2]),
$sformatf("j 35f"),
$sformatf("34: csrw 0x%0x, x%0d", PMPCFG3, scratch_reg[2]),
$sformatf("beq x%0d, x%0d, 33f", scratch_reg[0], scratch_reg[4]),
$sformatf("30: csrw 0x%0x, x%0d", PMPCFG0, scratch_reg[2]),
$sformatf("j 34f"),
$sformatf("31: csrw 0x%0x, x%0d", PMPCFG1, scratch_reg[2]),
$sformatf("j 34f"),
$sformatf("32: csrw 0x%0x, x%0d", PMPCFG2, scratch_reg[2]),
$sformatf("j 34f"),
$sformatf("33: csrw 0x%0x, x%0d", PMPCFG3, scratch_reg[2]),
// End the pmp handler with a labeled nop instruction, this provides a branch target
// for the internal routine after it has "fixed" the pmp configuration CSR.
$sformatf("35: nop")
$sformatf("34: nop")
};
endfunction

View file

@ -47,10 +47,6 @@ class riscv_ml_test extends riscv_instr_base_test;
`uvm_component_new
virtual function void randomize_cfg();
cfg.no_fence = 0;
cfg.init_privileged_mode = MACHINE_MODE;
cfg.init_privileged_mode.rand_mode(0);
cfg.enable_unaligned_load_store = 1'b1;
cfg.addr_translaction_rnd_order_c.constraint_mode(0);
`DV_CHECK_RANDOMIZE_FATAL(cfg)
cfg.addr_translaction_rnd_order_c.constraint_mode(1);