Skip to content

Commit 0eeae8f

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 8b43a18 commit 0eeae8f

File tree

4 files changed

+211
-18
lines changed

4 files changed

+211
-18
lines changed

sw/cheri/checks/hyperram_test.cc

Lines changed: 188 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,52 @@
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 0
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+
// Signals whether an exception should be trapped and the faulting instruction skipped.
46+
static volatile bool trap_err = false;
47+
48+
// Records whether an attempt to store a capability to the HyperRAM resulted in a
49+
// TL-UL bus error and thus an exception.
50+
static volatile bool act_err = false;
51+
52+
// TODO: #429 Presently the debugger cannot perform sub-word writes, so pad the BSS to 4 bytes.
53+
volatile uint16_t dummy;
54+
55+
extern "C" void exception_handler(void) {
56+
if (trap_err) {
57+
// Record the fact that an exception occurred.
58+
act_err = true;
59+
// Advance over the failing instruction; this is a `csc` instruction but it may or may not be
60+
// compressed.
61+
__asm volatile(
62+
" cspecialr ct0, mepcc\n"
63+
" lh t2, 0(ct0)\n"
64+
" li t1, 3\n"
65+
" and t2, t2, t1\n"
66+
" bne t2, t1, instr16\n"
67+
" cincoffset ct0, ct0, 2\n"
68+
"instr16: cincoffset ct0, ct0, 2\n"
69+
"update: cspecialw mepcc, ct0");
70+
}
71+
}
3072

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

131173
do {
132-
rand_index = prng() % HyperramSize;
174+
rand_index = prng() % HyperramTagSize;
133175

134176
// Capability is double word in size.
135-
rand_cap_index = prng() % (HyperramSize / 2);
177+
rand_cap_index = prng() % (HyperramTagSize / 2);
136178
} while (rand_index / 2 == rand_cap_index);
137179

138180
rand_val = prng();
@@ -670,6 +712,110 @@ int linear_execution_test(Capability<volatile uint32_t> hyperram_w_area, ds::xor
670712
return failures;
671713
}
672714

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

696842
// Default is word-based accesses, which is sufficient for most tests.
697843
Capability<volatile uint32_t> hyperram_area = root.cast<volatile uint32_t>();
698-
hyperram_area.address() = HYPERRAM_ADDRESS;
699-
hyperram_area.bounds() = HYPERRAM_BOUNDS;
844+
uint32_t bounds = HYPERRAM_BOUNDS;
845+
// Unfortunately it is not possible to construct a capability that covers exactly the 8MiB range
846+
// of the HyperRAM because of the encoding limitations of the CHERIoT capabilities.
847+
switch (1) {
848+
// Courtesy of Leo; CHERIoT can construct a capability that covers 8MiB - 16KiB.
849+
case 0:
850+
hyperram_area.address() = HYPERRAM_ADDRESS;
851+
bounds = HYPERRAM_BOUNDS - 0x4000;
852+
break;
853+
// Cover only the tagged region.
854+
case 1:
855+
hyperram_area.address() = HYPERRAM_ADDRESS;
856+
bounds = HYPERRAM_TAG_BOUNDS;
857+
break;
858+
// Cover just the untagged region.
859+
case 2:
860+
hyperram_area.address() = HYPERRAM_ADDRESS + HYPERRAM_TAG_BOUNDS;
861+
bounds = HYPERRAM_BOUNDS - HYPERRAM_TAG_BOUNDS;
862+
break;
863+
// Map twice the valid region.
864+
case 3:
865+
hyperram_area.address() = HYPERRAM_ADDRESS;
866+
bounds = 2 * HYPERRAM_BOUNDS;
867+
break;
868+
// This case does not work because the exponent becomes too large to be able to represent the
869+
// 8MiB range, and instead we end up with bounds of 16MiB and an invalid capability.
870+
default:
871+
hyperram_area.address() = HYPERRAM_ADDRESS;
872+
bounds = HYPERRAM_TAG_BOUNDS;
873+
break;
874+
}
700875

701876
Capability<Capability<volatile uint32_t>> hyperram_cap_area = root.cast<Capability<volatile uint32_t>>();
702877
hyperram_cap_area.address() = HYPERRAM_ADDRESS;
703-
hyperram_cap_area.bounds() = HYPERRAM_BOUNDS;
878+
hyperram_cap_area.bounds() = bounds;
704879

705880
// We also want byte, hword and dword access for some tests.
706881
Capability<volatile uint8_t> hyperram_b_area = root.cast<volatile uint8_t>();
707882
hyperram_b_area.address() = HYPERRAM_ADDRESS;
708-
hyperram_b_area.bounds() = HYPERRAM_BOUNDS;
883+
hyperram_b_area.bounds() = bounds;
709884
Capability<volatile uint16_t> hyperram_h_area = root.cast<volatile uint16_t>();
710885
hyperram_h_area.address() = HYPERRAM_ADDRESS;
711-
hyperram_h_area.bounds() = HYPERRAM_BOUNDS;
886+
hyperram_h_area.bounds() = bounds;
712887
Capability<volatile uint64_t> hyperram_d_area = root.cast<volatile uint64_t>();
713888
hyperram_d_area.address() = HYPERRAM_ADDRESS;
714-
hyperram_d_area.bounds() = HYPERRAM_BOUNDS;
889+
hyperram_d_area.bounds() = bounds;
715890

716891
// Run indefinitely, soak testing until we observe one or more failures.
717892
int failures = 0;
@@ -821,6 +996,10 @@ extern "C" [[noreturn]] void entry_point(void *rwRoot) {
821996
}
822997
log.print(" result...");
823998
write_test_result(log, failures);
999+
1000+
log.println("Running mapped/tagged range test...");
1001+
failures += mapped_tagged_range_test(hyperram_area, hyperram_cap_area, prng, log, 0x400u);
1002+
write_test_result(log, failures);
8241003
}
8251004

8261005
// 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)