-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Udi
committed
Nov 4, 2019
1 parent
277055e
commit 761d3f6
Showing
2 changed files
with
272 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,250 @@ | ||
## 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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters