Randomizing Data & Addresses
This tutorial walks you through adding randomization to your RiescueD tests, building from simple random values and random addresses.
Randomization helps find bugs that fixed test data might miss. Instead of always testing with the same values, you can generate thousands of different test cases automatically while keeping them reproducible for debugging.
Adding Some Random Data and Addresses
Let’s start by adding a random value to a simple test. Create a file called random_tutorial.s
:
;#test.arch rv64
;#test.priv machine
;#test.env bare_metal
;#test.paging disable
.section .code, "ax"
test_setup:
;#test_passed()
.equ data_region2_val, 0xACEDBEEF
;#random_data(name=my_8_bit_number, type=bits8, and_mask=0xFF)
;#random_addr(name=aligned_addr, type=physical, size=0x1000, and_mask=0xFFFFFF00)
;#random_data(name=my_16_bit_number, type=bits16, and_mask=0xFFFF)
;#discrete_test(test=test_random_data)
test_random_data:
li t0, my_8_bit_number
li t1, 0xFF
bleu t0, t1, test_16_bit_random_data # Assert the 8-bit value <= 255
;#test_failed()
test_16_bit_random_data:
li t2, my_16_bit_number
li t3, 0xFFFF
bleu t2, t3, random_data_passed
;#test_failed()
random_data_passed:
;#test_passed()
;#discrete_test(test=test_aligned_addr)
test_aligned_addr:
li t0, aligned_addr
andi t1, t0, 0xFF
bnez t1, failed # Should be zero (256-byte aligned)
;#test_passed()
;#discrete_test(test=load_from_data_regions)
load_from_data_regions:
li t1, data_region1
lw t2, 0(t1) # Load word from data_region1
li t1, data_region2
lw t3, 0(t1) # Load word from data_region2
li t4, 0xFFFFFFFF
and t3, t3, t4
li t2, data_region2_val
beq t2, t3, test_random_data_pass # Assert the word from data_region2 is equal to data_region2_val
;#test_failed()
test_random_data_pass:
;#test_passed()
test_cleanup:
;#test_passed()
.section .data
;#random_addr(name=data_region1, type=physical)
;#random_addr(name=data_region2, type=physical)
;#init_memory @data_region1
.byte my_8_bit_number
;#init_memory @data_region2
.word data_region2_val
We can run the test using:
riescued --testname random_tutorial.s --run_iss
Each time you run it, my_8_bit_value
gets a different random value between 0-255. You can check the disassembly file to see what value was generated when no seed is specified.
;#discrete_test
- Multiple Discrete Tests
This test includes multiple discrete tests by repeating the ;#discrete_test
directive with the test label.
The order of the discrete tests is determined by the order of the ;#discrete_test
directives.
Additionally, the number of times the discrete test is executed is determined by the repeat_times
parameter. More info on that can be found in the Configuration docs.
Test Configuration Headers
This example has introduced a new header:
;#test.paging
Header
Here we are using the ;#test.paging
header to specify the paging mode for the test. disable
means no paging is enabled for the test or addresses generated.
Random Data Generation
This uses the ;#random_data
directive to generate random data like the first example, but with additional constraints.
;#random_data
- Generating an 8-bit Random Value
;#random_data(name=my_8_bit_value, type=bits8, and_mask=0xFF)
This is creating a random 8-bit value and storing it in the constant my_8_bit_value
. Looking at the additional parameters:
- The type=bits8
is requesting an 8-bit random value. Any arbitrary bit width can be requested.
- and_mask=0xFF
is a mask that will be applied to the random value to ensure it is in the range of 0-255. Without it generates a random value between 0-255.
Note
The and_mask
parameter is optional but ensures that random values are generated within the range of the mask.
;#random_data
- Generating a 16-bit Random Value
;#random_data(name=my_16_bit_value, type=bits16, and_mask=0xFFFF)
This is creating a random 16-bit value and storing it in the constant my_16_bit_value
.
The 0xFFFF
is a mask that will be applied to the random value to ensure it generates a 16-bit value.
Random Memory Initialization
The load_from_data_regions
test uses the ;#random_data
directive to request an address in memory. The first one used is
;#random_addr
- Generating Random Addresses
;#random_addr(name=aligned_addr, type=physical, size=0x1000, and_mask=0xFFFFFF00)
This generates a 4-KiB section of memory that is aligned to 256-byte boundaries.
Looking at the parameters:
name=aligned_addr
creates a symbol that holds the random addresstype=physical
specifies this is a physical address (required parameter)size=0x1000
controls the size of the memory region (4-KiB in this case)and_mask=0xFFFFFF00
controls the alignment by masking the lower 8 bits to zero, creating 256-byte alignment
Note
Currently the type
parameter is required for ;#random_addr
. physical
should be used for physical addresses and platforms that don’t support virtual memory.
Loading Data from a Data Section
This tests loads data_region1
and data_region2
with the random values generated.
By loading the symbol matching the address of these sections, the values can be loaded and stored.
;#init_memory
- Initializing and Populating Memory
To populate the memory with data, we can use the ;#init_memory
directive and add code or data to the section.
For example, to place a random 8-bit value in data_region1
, we can use the following code:
.. code-block:: asm
- ;#init_memory @data_region1
.byte my_8_bit_number
This stores the data at the address of data_region1
.
Randomization with Seeds
Let’s see how seeds work for reproducible testing. Run the same test multiple times:
# Run 1 - note the seed in the output
riescued --testname random_tutorial.s
# Run 2 - different random values
riescued --testname random_tutorial.s
# Run 3 - reproduce Run 1 exactly (use seed from Run 1 output)
riescued --testname random_tutorial.s --seed 1234567890
The third run will generate identical random values to the first run, making debugging much easier. Without specifying a seed, a 2^32 bit seed is selected autoamtically.
For complete documentation of all available directives, see the RiESCUE Directives Reference and Test Headers Reference.