Test Memory Map
July 29, 2026 ยท View on GitHub
The test memory layout is divided into ordered linker sections so that the DUT ELF and the reference-model ELF produce identical addresses for all test-visible symbols.
The test memory map has several requirements:
- Place the beginning of the test code at the right place (usually the reset vector, unless the DUT has a separate boot loader)
- Ensure that the addresses of the test code body are the same when running it on the RISC-V Sail model to produce expected results and on a DUT to check against the expected results.
- Ensure the addresses of test data are the same when running on the RISC-V Sail model and on a DUT so expected results do not differ for address-dependent results.
The second requirement is subtle. The program is compiled twice, once to run on the RISC-V Sail model to produce an expected signature (the results), and once to run on a DUT to check against the expected signature. The RVMODEL macros may differ between the Sail model and DUT because the two targets might use different methods of printing to the console, terminating a simulation, etc.
The address of the test code body (the part that tests a feature and checks expected results) must be the same because some instructions (such as auipc) produce an expected result that depends on the address. Moreover, instructions that trap record the trap address in xepc, which should match on the Sail model and the DUT. Therefore, the RVMODEL macros cannot be called directly in the test code body because they might expand to different sizes for Sail vs. the DUT. Instead, they go in a different section placed after the test code body and are called from the test body via jal.
Some instructions (such as AMOs) may use the address of the scratch region of memory as part of the result, so the .data section must have constant addresses between the Sail model and DUT as well.
Section Layout
The linker places the following output sections in order:
| Section | Permissions | Contents |
|---|---|---|
.text.init | R X | Entry point (rvtest_entry_point), boot call, and jump to .text.rvtest. |
.text.rvtest | R X | All test code: initialization, trap handlers, the test body, and failure-reporting code. |
.rodata (aligned to 0x4000) | R | Read-only C data such as string literals. |
.data | RW | Initialized test data and the signature region (see below). |
.bss | RW | Zero-initialized C data, bounded by __bss_start and __bss_end. |
| Stack | RW | __stack_size * __num_harts bytes, bounded by __stack_bottom and __stack_top. |
.text.rvmodel (aligned to 0x1000) | R X | Out-of-line DUT-specific helpers (rvmodel_boot, rvmodel_halt_pass, etc.) and catch-all for remaining .text/.text.* input sections. |
Any additional DUT-specific data sections (such as .tohost for HTIF) are emitted via .pushsection in the RVMODEL macros and placed by the linker as orphan sections.
rvtest_entry_point is the first symbol in the .text.init section (which is the first section), so its address is set by TEST_BASE at the top of the linker script. For most DUTs, TEST_BASE is the same as RAM_ORIGIN and should be set to the reset vector of your processor. Advanced users can set the starting address to a different address, run a custom bootloader, and then jump to rvtest_entry_point to start the test. When RAM_ORIGIN differs from TEST_BASE, the .text.init output section uses an explicit TEST_BASE address so the linker does not start it at the beginning of the MEMORY region.
The sample linker scripts also define RAM_ORIGIN, RAM_LENGTH, and NUM_HARTS as user-editable variables above the /* Most users should not need to modify anything below this line. */ comment. RAM_ORIGIN and RAM_LENGTH define the ram linker MEMORY region and should match the corresponding RISC-V Sail memory.regions entry in sail.json. The scripts assert that TEST_BASE is within ram and that the ELF fits in ram; if those assertions fail, update link.ld and the matching sail.json memory region. STACK_SIZE defaults to 0x20000 bytes per hart below that comment. NUM_HARTS and STACK_SIZE are provided to the ACT runtime as __num_harts and __stack_size.
Additional details on what each section contains and why each section is needed are provided below.
.text.init Section Layout
| Symbol / Region | Purpose |
|---|---|
rvtest_entry_point | Entry point. Calls rvmodel_boot (in the .text.rvmodel section) then falls through to .text.rvtest. |
.text.init is intentionally kept in a separate output section so that .balign/.p2align directives in test code do not increase .text.init's alignment and shift rvtest_entry_point to an unexpected address. For example, CV32E20 has a reset vector of 0x4000 (TEST_BASE = 0x00004000 in the linker script). Some tests (e.g. I-jal-00) have large .p2align directives (.p2align 14). Output sections are aligned to the maximum internal alignment of all input sections, so if the test code was in the same section as the entry point, then the entry point would need to be aligned to 16K and the CV32E20 reset vector would not line up.
.text.rvtest Section Layout
| Symbol / Region | Purpose |
|---|---|
rvtest_init | Trap prologs, PMP setup, and register initialization. |
rvtest_code_begin | Start of the test body (signature pointer initialized here). |
| (test code) | The actual test, generated between RVTEST_CODE_BEGIN and RVTEST_CODE_END. |
rvtest_code_end | End of the test body. Switches back to M-mode. |
cleanup_epilogs | Trap epilogs (restore xTVEC, trampoline, and saved registers per mode). |
exit_cleanup / abort_test | Test termination paths (calls rvmodel_halt_pass or rvmodel_halt_fail). |
| Trap handlers | One handler per privilege mode (RVTEST_TRAP_HANDLER). |
| Failure code | Failure detection and diagnostic reporting (RVTEST_FAILURE_CODE). |
rvtest_identity_map | Forms identity-mapped superpages for S-mode trap handler access. |
.text.rvtest contains all of the actual test code that is common across all DUTs. For any DUT-specific operations, the DUT jumps to a function in the .text.rvmodel section to ensure the .text.rvtest section remains constant length.
.rodata, .data, .bss, and Stack Layout
| Symbol / Region | Purpose |
|---|---|
scratch | Scratch memory for loads/stores not part of the signature. Pre-initialized with distinct marker values. |
| Trap save areas | One save area per privilege mode trap handler. |
rvtest_data_begin | Start of test specific data label. |
| (test-specific data) | Data defined by individual tests between RVTEST_DATA_BEGIN and RVTEST_DATA_END. |
| Page tables | Root page tables for S-mode, H-mode, and VS-mode (when corresponding trap routines are defined). |
| Failure scratch & strings | Scratch area for failure reporting, followed by diagnostic strings (successstr, failstr, etc.). |
rvtest_data_end | End of test specific data label. |
begin_signature / rvtest_sig_begin | Start of the signature region (aligned to 16 bytes). |
| (signature data) | Main signature region written by test code via RVTEST_SIGUPD. |
| (trap signature) | Trap handler signature region (whenever a trap handler is defined, which should be whenever there is any privileged support). |
end_signature / rvtest_sig_end | End of the signature region. |
RVMODEL_DATA_SECTION | DUT-specific data defined in rvmodel_macros.h (e.g. tohost/fromhost for HTIF). May be empty. Placed last so variable-size DUT data does not affect any test-visible symbol addresses. |
The .bss section defines __bss_start and __bss_end; C test startup clears this range before calling main. The stack region follows .bss; each hart gets __stack_size bytes, and the total stack allocation is __stack_size * __num_harts.
All addresses in these data sections are constant and DUT-independent except for the contents of RVMODEL_DATA_SECTION, which is why it is placed last. This ensures the rest of the data layout has addresses that are the same for both the reference model and DUT.
.text.rvmodel Section Layout
| Symbol / Region | Purpose |
|---|---|
rvmodel_boot | DUT-specific boot code (RVMODEL_BOOT), I/O init (RVMODEL_IO_INIT), then jump to rvtest_init. |
rvmodel_io_write_str | Wrapper for RVMODEL_IO_WRITE_STR. |
rvmodel_halt_pass | Wrapper for RVMODEL_HALT_PASS. |
rvmodel_halt_fail | Wrapper for RVMODEL_HALT_FAIL. |
| Interrupt helpers | rvtest_set_msw_int, rvtest_clr_msw_int, rvtest_set_mext_int, etc. (when trap routines are defined). |
This section also acts as a catch-all for any remaining .text or .text.* input sections that might be provided by the DUT.
.text.rvmodel must be a separate section at the end of the test that follows .data because the RVMODEL macros expand to different code sizes in the DUT build versus the Sail reference-model build. If this variable-size code were in .text.rvtest (before .data), the .data section would start at different addresses in the two ELFs. Because some tests write address-dependent values (e.g. mtval) into the signature, different scratch or begin_signature addresses cause signature mismatches. Placing all RVMODEL code after .data ensures that test code and test data have identical addresses in both builds.