1 |
2 |
mballance |
# FWRISC Verification
|
2 |
|
|
|
3 |
|
|
FWRISC uses Verilator as the HDL simulator, and uses a [Googletest](https://github.com/abseil/googletest)-based testbench environment. The HDL top-level is written
|
4 |
|
|
in Verilog, while the testbench and tests are implemented in C++.
|
5 |
|
|
|
6 |
|
|
FWRISC currently has two testbench environments:
|
7 |
|
|
- Core-level testbench, which is used for unit tests, compliance tests, and Zephyr tests
|
8 |
|
|
- System-level testbench, which is used to sanity-test the FPGA top-level
|
9 |
|
|
|
10 |
|
|
The core-level testbench is located in the fwrisc/ve/fwrisc directory. The system-level testbench
|
11 |
|
|
is located in the fwrisc/ve/fwrisc_fpga directory.
|
12 |
|
|
|
13 |
|
|
## Core-level Tests
|
14 |
|
|
|
15 |
|
|
A diagram of the core-level testbench is shown below:
|
16 |
|
|
|
17 |
|
|
|
18 |
|
|

|
19 |
|
|
|
20 |
|
|
The _tracer_ module uses SV-DPI functions to send register-write, memory-write, and instruction trace
|
21 |
|
|
events to the testbench. These events are used to verify the result of running tests, as well
|
22 |
|
|
as implement features like function trace and the Zephyr RAM console.
|
23 |
|
|
|
24 |
|
|
|
25 |
|
|
### Unit Tests
|
26 |
|
|
|
27 |
|
|
The unit tests are each designed to test a single instruction of feature of the core. As such,
|
28 |
|
|
the goal is to keep each of these tests as short as possible. The test programs are captured
|
29 |
|
|
in-line as part of the test. The expected results are captured within the test as well.
|
30 |
|
|
|
31 |
|
|
Here is an example unit test for the add-imediate instruction:
|
32 |
|
|
|
33 |
|
|
```
|
34 |
|
|
TEST_F(fwrisc_instr_tests_arith, addi) {
|
35 |
|
|
reg_val_s exp[] = {
|
36 |
|
|
{1, 5},
|
37 |
|
|
{3, 11}
|
38 |
|
|
};
|
39 |
|
|
const char *program = R"(
|
40 |
|
|
entry:
|
41 |
|
|
li x1, 5
|
42 |
|
|
add x3, x1, 6
|
43 |
|
|
j done
|
44 |
|
|
)";
|
45 |
|
|
|
46 |
|
|
runtest(program, exp, sizeof(exp)/sizeof(reg_val_s));
|
47 |
|
|
}
|
48 |
|
|
```
|
49 |
|
|
|
50 |
|
|
All unit tests are expected to terminate by jumping to the 'done' symbol. The expected register-value array
|
51 |
|
|
specifies expected values for all accessed registers. Any registers that are unexpectedly accessed or
|
52 |
|
|
registers with an unexpected value will be reported as an error.
|
53 |
|
|
|
54 |
|
|
### Compliance Tests
|
55 |
|
|
Each compliance test runs one of the RISC-V RV32I compliance tests. Success of compliance tests is
|
56 |
|
|
judged by comparing a region of memory to a reference file. The compliance tests determine where this
|
57 |
|
|
region of memory is located by reading the ELF symbol table from the test executable, then comparing
|
58 |
|
|
the memory written during simulation against the content of memory at the end of the run.
|
59 |
|
|
|
60 |
|
|
|
61 |
|
|
### Zephyr Tests
|
62 |
|
|
With the exception of the 'Hello World' test, Zephyr tests tend to run for a long time. Consequently,
|
63 |
|
|
it is recommended to run these tests individually and interactively.
|
64 |
|
|
|
65 |
|
|
The Zephyr tests support two command-line options for debug purposes:
|
66 |
|
|
- +TRACE_FUNCS -- Displays a trace of function entry/exit driven by symbols read from the ELF file and the execution trace
|
67 |
|
|
- +TRACE_INSTR -- Displays each instruction that is executed.
|
68 |
|
|
|
69 |
|
|
The function trace is very helpful at debugging bringup of a new Zephyr port. Here is a trace of the beginning of Zephyr boot:
|
70 |
|
|
|
71 |
|
|
```
|
72 |
|
|
==> __initialize
|
73 |
|
|
==> _PrepC
|
74 |
|
|
==> _bss_zero
|
75 |
|
|
==> memset
|
76 |
|
|
==>
|
77 |
|
|
<==
|
78 |
|
|
==> _data_copy
|
79 |
|
|
==> memcpy
|
80 |
|
|
==>
|
81 |
|
|
<==
|
82 |
|
|
==> _Cstart
|
83 |
|
|
==> memset
|
84 |
|
|
==>
|
85 |
|
|
<==
|
86 |
|
|
<== memset
|
87 |
|
|
==> _sys_device_do_config_level
|
88 |
|
|
==> ram_console_init
|
89 |
|
|
==> __printk_hook_install
|
90 |
|
|
<== __printk_hook_install
|
91 |
|
|
==> __stdout_hook_install
|
92 |
|
|
<== __stdout_hook_install
|
93 |
|
|
<== ram_console_init
|
94 |
|
|
<== _sys_device_do_config_level
|
95 |
|
|
==> _sys_device_do_config_level
|
96 |
|
|
==> z_clock_driver_init
|
97 |
|
|
<== z_clock_driver_init
|
98 |
|
|
<== _sys_device_do_config_level
|
99 |
|
|
==> _sched_init
|
100 |
|
|
==> reset_time_slice
|
101 |
|
|
==> _get_next_timeout_expiry
|
102 |
|
|
==> k_spin_lock.isra.1
|
103 |
|
|
<== k_spin_lock.isra.1
|
104 |
|
|
<== _get_next_timeout_expiry
|
105 |
|
|
==> z_clock_elapsed
|
106 |
|
|
<== z_clock_elapsed
|
107 |
|
|
==> z_clock_set_timeout
|
108 |
|
|
<== _sched_init
|
109 |
|
|
==> _setup_new_thread
|
110 |
|
|
==> _new_thread
|
111 |
|
|
==> _init_thread_base
|
112 |
|
|
<== _init_thread_base
|
113 |
|
|
<== _new_thread
|
114 |
|
|
<== _setup_new_thread
|
115 |
|
|
```
|
116 |
|
|
|
117 |
|
|
The simulation-based Zephyr board definition uses the RAM console to display messages. The Zephyr-tests testbench
|
118 |
|
|
determines the local of the RAM console by reading the ELF symbol table. Messages sent to the RAM console are
|
119 |
|
|
displayed on the terminal with a '#' prefix. For example:
|
120 |
|
|
|
121 |
|
|
```
|
122 |
|
|
# ***** Booting Zephyr OS zephyr-v1.13.0-1808-gd18ff80 *****
|
123 |
|
|
# threadA: Hello World from fwrisc_sim!
|
124 |
|
|
# threadB: Hello World from fwrisc_sim!
|
125 |
|
|
# threadA: Hello World from fwrisc_sim!
|
126 |
|
|
# threadB: Hello World from fwrisc_sim!
|
127 |
|
|
|
128 |
|
|
```
|
129 |
|
|
|
130 |
|
|
|
131 |
|
|
## System-level Tests
|
132 |
|
|
The System-level tests have a similar structure to the unit-level tests. The main difference is that the testbench
|
133 |
|
|
only has access to the LED and UART Tx signals on the boundary of the design.
|
134 |
|
|
|
135 |
|
|
|
136 |
|
|
## Running a Test
|
137 |
|
|
|
138 |
|
|
Tests for both core- and system-level environments are run from the respective 'sim' directory using the _runtest.pl_ script.
|
139 |
|
|
Before running a test, be sure to configure your tool environment and source the FWRISC project setup script.
|
140 |
|
|
|
141 |
|
|
Each test is described using a test file in the 'tests' subdirectory. The test file specifies which GoogleTest
|
142 |
|
|
testcase to run, and any options to pass to the test. Here is the test file for the 'ECALL' unit test:
|
143 |
|
|
|
144 |
|
|
```
|
145 |
|
|
--gtest_filter=fwrisc_instr_tests_system.ecall
|
146 |
|
|
```
|
147 |
|
|
|
148 |
|
|
Here is the test file for the ADDI RISCV compliance test:
|
149 |
|
|
|
150 |
|
|
```
|
151 |
|
|
+SW_IMAGE=${BUILD_DIR}/esw/I-ADDI-01.elf
|
152 |
|
|
+REF_FILE=${FWRISC}/ve/fwrisc/tests/riscv-compliance/riscv-test-suite/rv32i/references/I-ADDI-01.reference_output
|
153 |
|
|
--gtest_filter=riscv_compliance_tests.coretest
|
154 |
|
|
|
155 |
|
|
```
|
156 |
|
|
|
157 |
|
|
Note how the ELF file and reference file are specified as plusargs to the test.
|
158 |
|
|
|
159 |
|
|
The example below shows how to run the 'ECALL' unit test:
|
160 |
|
|
|
161 |
|
|
```
|
162 |
|
|
% cd fwrisc/ve/fwrisc/sim
|
163 |
|
|
% runtest.pl -test tests/fwrisc_instr_tests_system_ecall.f
|
164 |
|
|
```
|
165 |
|
|
|
166 |
|
|
|
167 |
|
|
## Running a Testlist
|
168 |
|
|
|
169 |
|
|
Test suites are captured using testlists. Running a testlist is also done using the _runtest.pl_ script.
|
170 |
|
|
Here is an example of how to run the RISCV Compliance Tests testlist:
|
171 |
|
|
|
172 |
|
|
```
|
173 |
|
|
% cd fwrisc/ve/fwrisc/sim
|
174 |
|
|
% runtest.pl -testlist testlists/fwrisc_riscv_compliance_tests.tl
|
175 |
|
|
```
|
176 |
|
|
|
177 |
|
|
|
178 |
|
|
The full test suite is run with the fwrisc_riscv_all_tests.tl testlist:
|
179 |
|
|
|
180 |
|
|
```
|
181 |
|
|
% cd fwrisc/ve/fwrisc/sim
|
182 |
|
|
% runtest.pl -testlist testlists/fwrisc_riscv_all_tests.tl
|
183 |
|
|
```
|
184 |
|
|
|