mirror of
				https://github.com/openhwgroup/cvw
				synced 2025-02-11 06:05:49 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			285 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			285 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #!/usr/bin/python3
 | |
| ##################################
 | |
| # testgen-VIRTUALMEMORY.py
 | |
| #
 | |
| # Jessica Torrey <jtorrey@hmc.edu>  01 March 2021
 | |
| # Thomas Fleming <tfleming@hmc.edu> 01 March 2021
 | |
| #
 | |
| # Generate an ad-hoc test program for RISC-V Design Validation. Tests the
 | |
| # functionality of the memory management unit (MMU).
 | |
| ##################################
 | |
| 
 | |
| ##################################
 | |
| # libraries
 | |
| ##################################
 | |
| from datetime import datetime
 | |
| from random import randint, seed, getrandbits
 | |
| from textwrap import dedent
 | |
| from virtual_memory_util import *
 | |
| 
 | |
| ##################################
 | |
| # global structures
 | |
| ##################################
 | |
| 
 | |
| testcase_num = 0
 | |
| signature_len = 2000
 | |
| signature = [0xff for _ in range(signature_len)]
 | |
| 
 | |
| ##################################
 | |
| # functions
 | |
| ##################################
 | |
| 
 | |
| def rand_reg():
 | |
|   """
 | |
|   Produce a random register from 1 to 31 (skipping 6, since r6 is used for
 | |
|   other things).
 | |
|   """
 | |
|   r = randint(1,30)
 | |
|   if r >= 6:
 | |
|     r += 1
 | |
|   return r
 | |
| 
 | |
| def rand_regs():
 | |
|   """
 | |
|   Generate two random, unequal register numbers (skipping x6).
 | |
|   """
 | |
|   rs1 = rand_reg()
 | |
|   rs2 = rand_reg()
 | |
|   while rs1 == rs2:
 | |
|     rs2 = rand_reg()
 | |
|   
 | |
|   return rs1, rs2
 | |
| 
 | |
| def initialize_page_directory()
 | |
| 
 | |
| def generate_case(xlen, instruction, value_register, value, addr_register, offset, base_delta):
 | |
|   """
 | |
|   Create assembly code for a given STORE test case, returned as a string.
 | |
| 
 | |
|   Generates an `xlen`-bit test case for `instruction` (one of sb, sh, sw, or
 | |
|   sd) that loads `value` into `value_register`, then attempts to store that
 | |
|   value in memory at address (x6 + `base_delta`). The test case
 | |
|   assumes the current base address for the signature is in register x6.
 | |
| 
 | |
|   The form of the STORE instruction is as follows:
 | |
| 
 | |
|   `instruction` `value_register` `offset`(`addr_register`)
 | |
|   """
 | |
|   global testcase_num
 | |
| 
 | |
|   hex_value = f"{value:0{xlen // 4}x}"
 | |
| 
 | |
|   data = f"""# Testcase {testcase_num}: source: x{value_register} (value 0x{hex_value}), destination: {offset}(x{addr_register}) ({base_delta} bytes into signature)
 | |
|     addi x{addr_register}, x6, {base_delta}
 | |
|     li x{value_register}, MASK_XLEN({-offset})
 | |
|     add x{addr_register}, x{addr_register}, x{value_register}
 | |
|     li x{value_register}, 0x{hex_value}
 | |
|     {instruction} x{value_register}, {offset}(x{addr_register})
 | |
|     """
 | |
| 
 | |
|   #update_signature(store_to_size[instruction], value, base_delta)
 | |
| 
 | |
|   testcase_num += 1
 | |
|   return data
 | |
| 
 | |
| def validate_memory(scratch_register, value_register, value, base_delta, length):
 | |
|   """
 | |
|   Create assembly code to verify that `length` bytes at mem[x6 + `base_delta`]
 | |
|   store `value`.
 | |
| 
 | |
|   Assumes x6 stores the current base address for the signature.
 | |
|   """
 | |
|   truncated_value = value & (2**(length * 8) - 1)
 | |
|   hex_value = f"{truncated_value:0{length * 2}x}"
 | |
| 
 | |
|   load = size_to_load[length]
 | |
|   data = f"""addi x{scratch_register}, x6, {base_delta}
 | |
|     {load} x{value_register}, 0(x{scratch_register})
 | |
|     RVTEST_IO_ASSERT_GPR_EQ(x{scratch_register}, x{value_register}, 0x{hex_value})
 | |
| 
 | |
|     """
 | |
|   return data
 | |
| 
 | |
| def update_signature(length, value, base_delta):
 | |
|   """
 | |
|   Write the lower `length` bytes of `value` to the little-endian signature
 | |
|   array, starting at byte `base_delta`.
 | |
|   """
 | |
|   truncated_value = value & (2**(length * 8) - 1)
 | |
|   value_bytes = truncated_value.to_bytes(length, 'little')
 | |
|   #print("n: {}, value: {:x}, trunc: {:x}, length: {}, bd: {:x}".format(testcase_num, value, truncated_value, length, base_delta))
 | |
|   for i in range(length):
 | |
|     signature[base_delta + i] = value_bytes[i]
 | |
| 
 | |
| def write_signature(outfile):
 | |
|   """
 | |
|   Writes successive 32-bit words from the signature array into a given file.
 | |
|   """
 | |
|   for i in range(0, signature_len, 4):
 | |
|     word = list(reversed(signature[i:i+4]))
 | |
|     hexword = bytearray(word).hex()
 | |
|     outfile.write(f"{hexword}\n")
 | |
| 
 | |
| def write_header(outfile):
 | |
|   """
 | |
|   Write the name of the test file, authors, and creation date.
 | |
|   """
 | |
|   outfile.write(dedent(f"""\
 | |
|     ///////////////////////////////////////////
 | |
|     //
 | |
|     // WALLY-STORE
 | |
|     //
 | |
|     // Author: {author}
 | |
|     //
 | |
|     // Created {str(datetime.now())}
 | |
|     """))
 | |
|   outfile.write(open("testgen_header.S", "r").read())
 | |
| 
 | |
| def write_footer(outfile):
 | |
|   """
 | |
|   Write necessary closing code, including a data section for the signature.
 | |
|   """
 | |
|   outfile.write(open("testgen_footer.S", 'r').read())
 | |
|   data_section = dedent(f"""\
 | |
|     \t.fill {signature_len}, 1, -1
 | |
|     RV_COMPLIANCE_DATA_END
 | |
|     """)
 | |
|   outfile.write(data_section)
 | |
| 
 | |
| ##################################
 | |
| # test cases
 | |
| ##################################
 | |
| 
 | |
| def write_basic_tests(outfile, xlen, instr_len, num, base_delta):
 | |
|   """
 | |
|   Test basic functionality of STORE, using random registers, offsets, and
 | |
|   values.
 | |
| 
 | |
|   Creates `num` tests for a single store instruction of length `instr_len`,
 | |
|   writing to memory at consecutive locations, starting `base_delta` bytes from
 | |
|   the start of the signature.
 | |
| 
 | |
|   Returns the number of bytes from the start of the signature where testing
 | |
|   ended.
 | |
|   """
 | |
|   instruction = size_to_store[instr_len]
 | |
|   for i in range(num):
 | |
|     value_register, addr_register = rand_regs()
 | |
|     offset = randint(-2**(12 - 1), 2**(12 - 1) - 1)
 | |
|     value = randint(0, 2**(instr_len * 8) - 1)
 | |
|     test = generate_case(xlen, instruction, value_register, value, addr_register, offset, base_delta)
 | |
|     validate = validate_memory(addr_register, value_register, value, base_delta, instr_len)
 | |
|     outfile.write(test)
 | |
|     outfile.write(validate)
 | |
|     base_delta += instr_len
 | |
|   return base_delta
 | |
| 
 | |
| def write_random_store_tests(outfile, xlen, instr_len, num, min_base_delta):
 | |
|   """
 | |
|   Test random access of STORE, using random registers, offsets, values, and
 | |
|   memory locations.
 | |
| 
 | |
|   Creates `num` tests for a single store instruction of length `instr_len`,
 | |
|   writing to memory at random locations between `min_base_delta` bytes past
 | |
|   the start of the signature to the end of the signature.
 | |
|   """
 | |
|   instruction = size_to_store[instr_len]
 | |
|   for i in range(num):
 | |
|     base_delta = randint(min_base_delta, signature_len - 1)
 | |
|     base_delta -= (base_delta % instr_len)
 | |
|     value_register, addr_register = rand_regs()
 | |
|     offset = randint(-2**(12 - 1), 2**(12 - 1) - 1)
 | |
|     value = randint(0, 2**(instr_len * 8) - 1)
 | |
| 
 | |
|     test = generate_case(xlen, instruction, value_register, value, addr_register, offset, base_delta)
 | |
|     validate = validate_memory(addr_register, value_register, value, base_delta, instr_len)
 | |
|     outfile.write(test)
 | |
|     outfile.write(validate)
 | |
| 
 | |
| def write_repeated_store_tests(outfile, xlen, instr_len, num, base_delta):
 | |
|   """
 | |
|   Test repeated access of STORE, using random registers, offsets, values, and a
 | |
|   single memory location.
 | |
| 
 | |
|   Creates `num` tests for a single store instruction of length `instr_len`,
 | |
|   writing to memory `base_delta` bytes past the start of the signature.
 | |
|   """
 | |
|   instruction = size_to_store[instr_len]
 | |
|   for i in range(num):
 | |
|     value_register, addr_register = rand_regs()
 | |
|     offset = 0
 | |
|     value = (1 << ((2 * i) % xlen))
 | |
| 
 | |
|     test = generate_case(xlen, instruction, value_register, value, addr_register, offset, base_delta)
 | |
|     validate = validate_memory(addr_register, value_register, value, base_delta, instr_len)
 | |
| 
 | |
|     outfile.write(test)
 | |
|     outfile.write(validate)
 | |
| 
 | |
| def write_corner_case_tests(outfile, xlen, instr_len, base_delta):
 | |
|   instruction = size_to_store[instr_len]
 | |
| 
 | |
|   corner_cases_32 = [0x0, 0x10000001, 0x01111111]
 | |
|   corner_cases_64 = [0x1000000000000001, 0x0111111111111111]
 | |
|   corner_cases = corner_cases_32
 | |
|   if xlen == 64:
 | |
|     corner_cases += corner_cases_64
 | |
| 
 | |
|   for offset in corner_cases:
 | |
|     for value in corner_cases:
 | |
|       value_register, addr_register = rand_regs()
 | |
|       test = generate_case(xlen, instruction, value_register, value, addr_register, offset, base_delta)
 | |
|       validate = validate_memory(addr_register, value_register, value, base_delta, instr_len)
 | |
| 
 | |
|       outfile.write(test)
 | |
|       outfile.write(validate)
 | |
| 
 | |
|       base_delta += instr_len
 | |
|   
 | |
|   return base_delta
 | |
| 
 | |
| ##################################
 | |
| # main body
 | |
| ##################################
 | |
| 
 | |
| if __name__ == "__main__":
 | |
|   instructions = ["sd", "sw", "sh", "sb"]
 | |
|   author = "Jessica Torrey <jtorrey@hmc.edu> & Thomas Fleming <tfleming@hmc.edu>"
 | |
|   xlens = [32, 64]
 | |
|   numrand = 100
 | |
| 
 | |
|   # setup
 | |
|   seed(0) # make tests reproducible
 | |
| 
 | |
|   for xlen in xlens:
 | |
|     if (xlen == 32):
 | |
|       wordsize = 4
 | |
|     else:
 | |
|       wordsize = 8
 | |
| 
 | |
|     fname = f"../../imperas-riscv-tests/riscv-test-suite/rv{xlen}i/src/WALLY-VIRTUALMEMORY.S"
 | |
|     refname = f"../../imperas-riscv-tests/riscv-test-suite/rv{xlen}i/references/WALLY-VIRTUALMEMORY.reference_output"
 | |
|     f = open(fname, "w")
 | |
|     r = open(refname, "w")
 | |
| 
 | |
|     write_header(f)
 | |
| 
 | |
|     base_delta = 0
 | |
| 
 | |
|     for instruction in instructions:
 | |
|       if xlen == 32 and instruction == 'sd':
 | |
|         continue
 | |
|       instr_len = store_to_size[instruction]
 | |
|       base_delta = write_basic_tests(f, xlen, instr_len, 5, base_delta)
 | |
|       write_repeated_store_tests(f, xlen, instr_len, 32, base_delta)
 | |
|       write_random_store_tests(f, xlen, instr_len, 5, base_delta + wordsize)
 | |
| 
 | |
|     write_footer(f)
 | |
| 
 | |
|     write_signature(r)
 | |
|     f.close()
 | |
|     r.close()
 | |
| 
 | |
|     # Reset testcase_num and signature
 | |
|     testcase_num = 0
 | |
|     signature = [0xff for _ in range(signature_len)] |