diff --git a/wally-pipelined/testgen/testgen-VIRTUALMEMORY.py b/wally-pipelined/testgen/testgen-VIRTUALMEMORY.py new file mode 100644 index 000000000..07bb6e374 --- /dev/null +++ b/wally-pipelined/testgen/testgen-VIRTUALMEMORY.py @@ -0,0 +1,285 @@ +#!/usr/bin/python3 +################################## +# testgen-VIRTUALMEMORY.py +# +# Jessica Torrey 01 March 2021 +# Thomas Fleming 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 & Thomas Fleming " + 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)] \ No newline at end of file diff --git a/wally-pipelined/testgen/virtual_memory_util.py b/wally-pipelined/testgen/virtual_memory_util.py new file mode 100644 index 000000000..83b34d5bb --- /dev/null +++ b/wally-pipelined/testgen/virtual_memory_util.py @@ -0,0 +1,125 @@ +#!/usr/bin/python3 +################################## +# virtual_memory_util.py +# +# Jessica Torrey 01 March 2021 +# Thomas Fleming 01 March 2021 +# +# Utility functions for simulating and testing virtual memory on RISC-V. +################################## + +################################## +# libraries +################################## +from datetime import datetime +from random import randint, seed, getrandbits +from textwrap import dedent + +################################## +# global structures +################################## +PTE_D = 1 << 7 +PTE_A = 1 << 6 +PTE_G = 1 << 5 +PTE_U = 1 << 4 +PTE_X = 1 << 3 +PTE_W = 1 << 2 +PTE_R = 1 << 1 +PTE_V = 1 << 0 + + +pgdir = [] + +testcase_num = 0 +signature_len = 2000 +signature = [0xff for _ in range(signature_len)] + +################################## +# classes +################################## +class Architecture: + def __init__(self, xlen): + if (xlen == 32): + self.PTESIZE = 4 + + self.VPN_BITS = 20 + self.VPN_SEGMENT_BITS = 10 + + self.PPN_BITS = 22 + + self.LEVELS = 2 + elif (xlen == 64): + self.PTESIZE = 8 + + self.VPN_BITS = 27 + self.VPN_SEGMENT_BITS = 9 + + self.PPN_BITS = 44 + + self.LEVELS = 3 + else: + raise ValueError('Only rv32 and rv64 are allowed.') + + self.PGSIZE = 2**12 + self.NPTENTRIES = self.PGSIZE // self.PTESIZE + self.PTE_BITS = 8 * self.PTESIZE + self.OFFSET_BITS = 12 + self.FLAG_BITS = 8 + +class PageTableEntry: + def __init__(self, ppn, flags, arch): + assert 0 <= ppn and ppn < 2**arch.PPN_BITS, "Invalid physical page number for PTE" + assert 0 <= flags and flags < 2**arch.FLAG_BITS, "Invalid flags for PTE" + self.ppn = ppn + self.flags = flags + self.arch = arch + + def entry(self): + return (self.ppn << (self.arch.PTE_BITS - self.arch.PPN_BITS)) | self.flags + + def __str__(self): + return "0x{0:0{1}x}".format(self.entry(), self.arch.PTESIZE*2) + + def __repr__(self): + return f"" + +class PageTable: + """ + Represents a single level of the page table, with + """ + def __init__(self, name, arch): + self.table = {} + self.name = name + self.arch = arch + + def add_entry(self, vpn_segment, ppn_segment, flags, linked_table = None): + if not (0 <= vpn_segment < 2**self.arch.VPN_SEGMENT_BITS): + raise ValueError("Invalid virtual page segment number") + self.table[vpn_segment] = (PageTableEntry(ppn_segment, flags, self.arch), linked_table) + + def add_mapping(self, va, pa, flags): + if not (0 <= va < 2**self.arch.VPN_BITS): + raise ValueError("Invalid virtual page number") + for level in range(self.arch.LEVELS - 1, -1, -1): + + + + + def assembly(self): + entries = list(sorted(self.table.items(), key=lambda item: item[0])) + current_index = 0 + asm = f".balign {self.arch.PGSIZE}\n{self.name}:\n" + for entry in entries: + vpn_index, (pte, _) = entry + if current_index < vpn_index: + asm += f" .fill {vpn_index - current_index}, {self.arch.PTESIZE}, 0\n" + asm += f" .4byte {str(pte)}\n" + current_index = vpn_index + 1 + if current_index < self.arch.NPTENTRIES: + asm += f" .fill {self.arch.NPTENTRIES - current_index}, {self.arch.PTESIZE}, 0\n" + return asm + +################################## +# functions +################################## +