Skip to content

Commit 8593772

Browse files
committed
Update HyperRAM test software to reflect size changes
Introduce a test for whether storing a capability to an untagged area of memory raises an exception as intended. Stores to areas of the HyperRAM that have associated tag bit storage should complete without an exception. In each case, check whether an exception does/does not occur as expected, and check the contents of the memory after the attempted store. An exception handler resumes execution after the faulting instruction but - importantly - is activated only for the very short time window during which we expect the possible exception to occur.
1 parent f5cbd97 commit 8593772

6 files changed

Lines changed: 207 additions & 17 deletions

File tree

sw/cheri/checks/hyperram_memset.S

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,3 +249,9 @@ memset_cd_8fix:
249249
bgtz a2, memset_b_desc_tail
250250
cret
251251

252+
// Utility function returning the `pcc` at which an exception occurred.
253+
.globl get_mepcc
254+
.p2align 5
255+
get_mepcc:
256+
cspecialr ca0, mepcc
257+
cret

sw/cheri/checks/hyperram_memset.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,6 @@ extern "C" void hyperram_memset_wr(volatile uint32_t *dst, int c, size_t n);
3030
extern "C" void hyperram_memset_wd(volatile uint32_t *dst, int c, size_t n);
3131
extern "C" void hyperram_memset_c(volatile uint64_t *dst, int c, size_t n);
3232
extern "C" void hyperram_memset_cd(volatile uint64_t *dst, int c, size_t n);
33+
34+
// Utility function to return the address at which an exception occurred.
35+
extern "C" void *get_mepcc(void);

sw/cheri/checks/hyperram_test.cc

Lines changed: 175 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,59 @@
2323

2424
#include "hyperram_memset.h"
2525

26+
// Simulation is much slower than execution on FPGA and these tests are primarily intended for
27+
// FPGA-based testing. Define this to 1 for use in simulation.
28+
#define SIMULATION 1
29+
2630
using namespace CHERI;
2731

2832
const int RandTestBlockSize = 256;
29-
const int HyperramSize = (1024 * 1024) / 4;
33+
#if SIMULATION
34+
// Note that this means many of the tests will be exercising only small fraction of the mapped
35+
// HyperRAM address range.
36+
const unsigned HyperramSize = HYPERRAM_BOUNDS / 1024;
37+
#else
38+
// Number of 32-bit words within the mapped HyperRAM.
39+
const unsigned HyperramSize = HYPERRAM_BOUNDS / 4;
40+
#endif
41+
42+
// The amount of HyperRAM that supports capability stores.
43+
const unsigned HyperramTagSize = HYPERRAM_TAG_BOUNDS / 4;
44+
45+
// Logging from the exception handling code.
46+
static Log *exc_log = NULL;
47+
48+
// Signals whether an exception should be trapped and the faulting instruction skipped.
49+
static volatile bool trap_err = false;
50+
51+
// Records whether an attempt to store a capability to the HyperRAM resulted in a
52+
// TL-UL bus error and thus an exception.
53+
static volatile bool act_err = false;
54+
55+
// TODO: #429 Presently the debugger cannot perform sub-word writes, so pad the BSS to 4 bytes.
56+
volatile uint16_t dummy;
57+
58+
extern "C" void exception_handler(void) {
59+
if (trap_err) {
60+
// Record the fact that an exception occurred.
61+
act_err = true;
62+
// Advance over the failing instruction; this is a `csc` instruction but it may or may not be
63+
// compressed.
64+
__asm volatile(
65+
" cspecialr ct0, mepcc\n"
66+
" lh t2, 0(ct0)\n"
67+
" li t1, 3\n"
68+
" and t2, t2, t1\n"
69+
" bne t2, t1, instr16\n"
70+
" cincoffset ct0, ct0, 2\n"
71+
"instr16: cincoffset ct0, ct0, 2\n"
72+
"update: cspecialw mepcc, ct0");
73+
} else if (exc_log) {
74+
uint32_t exc_addr = __builtin_cheri_address_get(get_mepcc());
75+
exc_log->println("Unexpected exception occurred at {:#x}", exc_addr);
76+
while (1) asm volatile(" ");
77+
}
78+
}
3079

3180
// Ensure that all writing of code to memory has completed before commencing execution
3281
// of that code. Code has been written to [start, end] with both addresses being
@@ -129,10 +178,10 @@ int rand_cap_test(Capability<volatile uint32_t> hyperram_area,
129178
Capability<volatile uint32_t> read_cap;
130179

131180
do {
132-
rand_index = prng() % HyperramSize;
181+
rand_index = prng() % HyperramTagSize;
133182

134183
// Capability is double word in size.
135-
rand_cap_index = prng() % (HyperramSize / 2);
184+
rand_cap_index = prng() % (HyperramTagSize / 2);
136185
} while (rand_index / 2 == rand_cap_index);
137186

138187
rand_val = prng();
@@ -670,6 +719,110 @@ int linear_execution_test(Capability<volatile uint32_t> hyperram_w_area, ds::xor
670719
return failures;
671720
}
672721

722+
// Simple test of whether the full HyperRAM is mapped, as well checking that capabilities can
723+
// only be stored to the intended portion of this mapped range.
724+
int mapped_tagged_range_test(Capability<volatile uint32_t> hyperram_w_area,
725+
Capability<Capability<volatile uint32_t>> hyperram_cap_area, ds::xoroshiro::P64R32 &prng,
726+
Log &log, int iterations = 1) {
727+
const bool verbose = false;
728+
int failures = 0;
729+
730+
// In the event that the entire HyperRAM supports capabilities, we must reduce two of our
731+
// directed choices to be within bounds.
732+
uint32_t tag_bounds_plus_8 = HYPERRAM_TAG_BOUNDS + 8;
733+
uint32_t tag_bounds = HYPERRAM_TAG_BOUNDS;
734+
if (tag_bounds_plus_8 >= HYPERRAM_BOUNDS) {
735+
tag_bounds_plus_8 = HYPERRAM_BOUNDS - 8;
736+
tag_bounds = HYPERRAM_BOUNDS - 16;
737+
}
738+
739+
for (int iter = 0; iter < iterations; ++iter) {
740+
Capability<volatile uint32_t> read_cap;
741+
unsigned rand_choice = prng() & 7u;
742+
uint32_t rand_addr;
743+
744+
switch (rand_choice) {
745+
// Directed choices.
746+
case 0u:
747+
rand_addr = tag_bounds;
748+
break;
749+
case 1u:
750+
rand_addr = HYPERRAM_TAG_BOUNDS - 8;
751+
break;
752+
case 2u:
753+
rand_addr = HYPERRAM_BOUNDS - 8;
754+
break;
755+
case 3u:
756+
rand_addr = tag_bounds_plus_8;
757+
break;
758+
case 4u:
759+
rand_addr = 0u;
760+
break;
761+
// Randomised choices.
762+
default:
763+
rand_addr = prng() & (HYPERRAM_BOUNDS - 8u);
764+
break;
765+
}
766+
767+
// Predict whether we should see a TL-UL error in response; only the first portion of the
768+
// mapped HyperRAM supports tag bits. Anything at a higher address should raise a TL-UL error.
769+
bool exp_err = (rand_addr >= HYPERRAM_TAG_BOUNDS);
770+
if (verbose) {
771+
log.println("addr {:#x}", rand_addr);
772+
}
773+
774+
// We are expecting to generate a TL-UL error with some store operations.
775+
trap_err = true;
776+
// First store some data that does not constitute a sensible capability.
777+
const uint32_t exp_data1 = 0x87654321u;
778+
const uint32_t exp_data0 = ~0u;
779+
hyperram_w_area[rand_addr >> 2] = exp_data0;
780+
hyperram_w_area[(rand_addr >> 2) + 1] = exp_data1;
781+
782+
// Attempt to store a capability to the chosen address.
783+
// The capability stored doesn't really matter; just use the HyperRAM base.
784+
act_err = false;
785+
hyperram_cap_area[rand_addr >> 3] = hyperram_w_area;
786+
trap_err = false;
787+
if (verbose) {
788+
log.println("done write");
789+
}
790+
read_cap = hyperram_cap_area[rand_addr >> 3];
791+
792+
// Check that an error occurred iff expected.
793+
failures += (act_err != exp_err);
794+
if (verbose) {
795+
log.println("Act err {}, exp err {}", (int)act_err, (int)exp_err);
796+
}
797+
798+
// Check the memory contents.
799+
if (exp_err) {
800+
// If an error occurred then we expect _not_ to have performed the write, so the test data
801+
// should still be intact.
802+
uint32_t act_data1 = hyperram_w_area[(rand_addr >> 2) + 1];
803+
uint32_t act_data0 = hyperram_w_area[rand_addr >> 2];
804+
if (verbose) {
805+
log.println("Wrote {:#x}:{:#x}, read back {:#x}:{:#x}", exp_data0, exp_data1, act_data0, act_data1);
806+
}
807+
if (exp_data0 != act_data0 || exp_data1 != act_data1) {
808+
failures++;
809+
}
810+
} else {
811+
// If there was no error, the capability should have been stored as expected.
812+
if (verbose) {
813+
volatile uint32_t *exp = hyperram_w_area.get();
814+
volatile uint32_t *act = read_cap.get();
815+
log.println("Wrote {:#x}, read back {:#x}", (uint32_t)exp, (uint32_t)act);
816+
}
817+
if (read_cap != hyperram_w_area) {
818+
failures++;
819+
}
820+
}
821+
}
822+
823+
return failures;
824+
}
825+
673826
/**
674827
* C++ entry point for the loader. This is called from assembly, with the
675828
* read-write root in the first argument.
@@ -685,6 +838,8 @@ extern "C" [[noreturn]] void entry_point(void *rwRoot) {
685838
uart0->init(BAUD_RATE);
686839
WriteUart uart{uart0};
687840
Log log(uart);
841+
// Make logging available to the exception handler.
842+
exc_log = &log;
688843

689844
set_console_mode(log, CC_BOLD);
690845
log.print("\r\n\r\nGet hyped for hyperram!\r\n");
@@ -694,24 +849,32 @@ extern "C" [[noreturn]] void entry_point(void *rwRoot) {
694849
prng.set_state(0xDEADBEEF, 0xBAADCAFE);
695850

696851
// Default is word-based accesses, which is sufficient for most tests.
852+
//
853+
// Unfortunately it is not possible to construct a capability that covers exactly the 8MiB range
854+
// of the HyperRAM because of the encoding limitations of the CHERIoT capabilities.
855+
//
856+
// Here, in this manually-invoked test, we resort to mapping twice that address range because
857+
// it is more important that we test all of the physical HyperRAM connectivity and logic, rather
858+
// than the CHERIoT capabilities.
697859
Capability<volatile uint32_t> hyperram_area = root.cast<volatile uint32_t>();
860+
uint32_t bounds = 2 * HYPERRAM_BOUNDS;
698861
hyperram_area.address() = HYPERRAM_ADDRESS;
699-
hyperram_area.bounds() = HYPERRAM_BOUNDS;
862+
hyperram_area.bounds() = bounds;
700863

701864
Capability<Capability<volatile uint32_t>> hyperram_cap_area = root.cast<Capability<volatile uint32_t>>();
702865
hyperram_cap_area.address() = HYPERRAM_ADDRESS;
703-
hyperram_cap_area.bounds() = HYPERRAM_BOUNDS;
866+
hyperram_cap_area.bounds() = bounds;
704867

705868
// We also want byte, hword and dword access for some tests.
706869
Capability<volatile uint8_t> hyperram_b_area = root.cast<volatile uint8_t>();
707870
hyperram_b_area.address() = HYPERRAM_ADDRESS;
708-
hyperram_b_area.bounds() = HYPERRAM_BOUNDS;
871+
hyperram_b_area.bounds() = bounds;
709872
Capability<volatile uint16_t> hyperram_h_area = root.cast<volatile uint16_t>();
710873
hyperram_h_area.address() = HYPERRAM_ADDRESS;
711-
hyperram_h_area.bounds() = HYPERRAM_BOUNDS;
874+
hyperram_h_area.bounds() = bounds;
712875
Capability<volatile uint64_t> hyperram_d_area = root.cast<volatile uint64_t>();
713876
hyperram_d_area.address() = HYPERRAM_ADDRESS;
714-
hyperram_d_area.bounds() = HYPERRAM_BOUNDS;
877+
hyperram_d_area.bounds() = bounds;
715878

716879
// Run indefinitely, soak testing until we observe one or more failures.
717880
int failures = 0;
@@ -821,6 +984,10 @@ extern "C" [[noreturn]] void entry_point(void *rwRoot) {
821984
}
822985
log.print(" result...");
823986
write_test_result(log, failures);
987+
988+
log.println("Running mapped/tagged range test...");
989+
failures += mapped_tagged_range_test(hyperram_area, hyperram_cap_area, prng, log, 0x400u);
990+
write_test_result(log, failures);
824991
}
825992

826993
// Report test failure.

sw/cheri/common/sonata-devices.hh

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,12 @@ using PinmuxPtrs = std::pair<PinSinksPtr, BlockSinksPtr>;
7777
}
7878

7979
[[maybe_unused]] static HyperramPtr hyperram_ptr(CapRoot root) {
80+
// Unfortunately it is not possible to construct a capability that covers exactly the 8MiB range
81+
// of the HyperRAM because of the encoding limitations of the CHERIoT capabilities.
82+
// We therefore leave the final 16KiB inaccessible.
8083
CHERI::Capability<volatile uint32_t> hyperram = root.cast<volatile uint32_t>();
8184
hyperram.address() = HYPERRAM_ADDRESS;
82-
hyperram.bounds() = HYPERRAM_BOUNDS;
85+
hyperram.bounds() = HYPERRAM_BOUNDS - 0x4000u;
8386
return hyperram;
8487
}
8588

sw/cheri/tests/hyperram_tests.hh

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,24 @@ using namespace CHERI;
3333
* It can be overwride with a compilation flag
3434
*/
3535
#ifndef TEST_COVERAGE_AREA
36-
// Test only 1% of the total memory to be fast enough for Verilator.
37-
#define TEST_COVERAGE_AREA 1
36+
// Test only n/1024 of the total memory to be fast enough for Verilator.
37+
//
38+
// Note: we are deliberately using power-of-two quantities here because of the addressing
39+
// limitations of CHERIoT capabilities. There is 8MiB available but we must keep the
40+
// testing short, so we choose to use just ca. 0.2%
41+
#define TEST_COVERAGE_AREA 2
3842
#endif
39-
_Static_assert(TEST_COVERAGE_AREA <= 100, "TEST_COVERAGE_AREA Should be less than 100");
43+
static_assert(TEST_COVERAGE_AREA <= 1024, "TEST_COVERAGE_AREA Should be less than 1024");
4044

4145
#define TEST_BLOCK_SIZE 256
42-
#define HYPERRAM_SIZE (1024 * 1024) / 4
46+
// Size of mapped, tag-capable portion of HyperRAM, in 32-bit words.
47+
#define HYPERRAM_TAG_SIZE (HYPERRAM_TAG_BOUNDS / 4)
4348

4449
/*
4550
* Compute the number of addresses that will be tested.
4651
* We mask the LSB 8bits to makes sure it is aligned.
4752
*/
48-
#define HYPERRAM_TEST_SIZE (uint32_t)((HYPERRAM_SIZE * TEST_COVERAGE_AREA / 100) & ~0xFF)
53+
#define HYPERRAM_TEST_SIZE (uint32_t)((HYPERRAM_TAG_SIZE * TEST_COVERAGE_AREA / 0x400u) & ~0xFF)
4954

5055
/*
5156
* Write random values to a block of memory (size given by 'TEST_BLOCK_SIZE'
@@ -301,11 +306,15 @@ int perf_burst_test(Capability<volatile uint32_t> hyperram_area, ds::xoroshiro::
301306
}
302307

303308
void hyperram_tests(CapRoot root, Log &log) {
304-
auto hyperram_area = hyperram_ptr(root);
309+
// Unfortunately it is not possible to construct a capability that covers exactly the 8MiB range
310+
// of the HyperRAM because of the encoding limitations of the CHERIoT capabilities, but here we
311+
// are only concerned with testing a much smaller portion of the address range anyway.
312+
const uint32_t hr_bounds = HYPERRAM_TEST_SIZE * 4u;
313+
auto hyperram_area = hyperram_ptr(root);
305314

306315
Capability<Capability<volatile uint32_t>> hyperram_cap_area = root.cast<Capability<volatile uint32_t>>();
307316
hyperram_cap_area.address() = HYPERRAM_ADDRESS;
308-
hyperram_cap_area.bounds() = HYPERRAM_BOUNDS;
317+
hyperram_cap_area.bounds() = hr_bounds;
309318

310319
ds::xoroshiro::P64R32 prng;
311320
prng.set_state(0xDEADBEEF, 0xBAADCAFE);

sw/common/defs.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
#define SRAM_BOUNDS (0x0002'0000)
1414

1515
#define HYPERRAM_ADDRESS (0x4000'0000)
16-
#define HYPERRAM_BOUNDS (0x0010'0000)
16+
#define HYPERRAM_BOUNDS (0x0080'0000)
17+
// The portion of the HyperRAM that can support capabilities.
18+
#define HYPERRAM_TAG_BOUNDS (0x0040'0000)
1719

1820
#define SYSTEM_INFO_ADDRESS (0x8000'C000)
1921
#define SYSTEM_INFO_BOUNDS (0x0000'0020)

0 commit comments

Comments
 (0)