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 address

  • type=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.