diff --git a/.coveragerc b/.coveragerc index f017b5d..de00cab 100644 --- a/.coveragerc +++ b/.coveragerc @@ -23,10 +23,17 @@ exclude_lines = if self.debug.enabled('rf'): if self.debug.enabled('mem'): if self.debug.enabled('memcheck'): + if not sparse_storage: self.memory.debug.enabled_flags s.debug_flags() + self.bounds_check print + # Don't complain about invalid memory accesses. + if addr > self.size: + if addr == 0: + + # Don't complain about assertions in syscalls -- special code for debuggers inst.t5 == 4 inst.t5 == 5 diff --git a/revelation/machine.py b/revelation/machine.py index 1fd8f5e..88b294a 100644 --- a/revelation/machine.py +++ b/revelation/machine.py @@ -2,12 +2,10 @@ # machine.py #======================================================================= -from pydgin.debug import Debug, pad, pad_hex from pydgin.machine import Machine -from pydgin.storage import Memory -from pydgin.utils import r_uint, specialize from revelation.isa import reg_map +from revelation.storage import MemoryMappedRegisterFile try: from rpython.rlib.rarithmetic import intmask @@ -17,89 +15,16 @@ RESET_ADDR = 0 -#----------------------------------------------------------------------- -# Memory -#----------------------------------------------------------------------- -class RevelationMemory(object): - """Since the Epiphany has memory-mapped register files, we need to - intercept any read / write which should actually go to the registers. - - FIXME: Correctly map address 0xf0408 to the program counter in State. - """ - - def __init__(self, data=None, size=2**32, byte_storage=True): - self.memory = Memory(data=data, size=size, byte_storage=byte_storage) - self.rf = None # Set by Sim.init_state(). Also sets self.fetch_pc() - self.debug = None # Set after ELF file loaded. - - def set_debug(self, debug): - self.debug = debug - if self.debug.enabled('mem'): - self.memory.debug.enabled_flags.append('mem') - if self.debug.enabled('memcheck'): - self.memory.debug.enabled_flags.append('memcheck') - - def iread(self, address, nbytes=4): - if 0xf0718 >= address >= 0xf0000: - _, reg_num = _register_map[r_uint(address)] - return intmask(self.rf[reg_num]) - else: - return self.memory.iread(address, nbytes) - - def read(self, address, nbytes=4): - if 0xf0718 >= address >= 0xf0000: - _, reg_num = _register_map[r_uint(address)] - return intmask(self.rf[reg_num]) - else: - return self.memory.read(address, nbytes) - - def write(self, address, nbytes, value): - if 0xf0718 >= address >= 0xf0000: - _, reg_num = _register_map[r_uint(address)] - self.rf[r_uint(reg_num)] = r_uint(value) - else: - self.memory.write(address, nbytes, value) - - -#----------------------------------------------------------------------- -# RegisterFile -#----------------------------------------------------------------------- -class RegisterFile( object ): - def __init__(self, constant_zero=True, num_regs=32, nbits=32): - self.num_regs = num_regs - self.regs = [r_uint(0)] * self.num_regs - self.debug = Debug() - self.nbits = nbits - self.debug_nchars = nbits / 4 - # Ignore constant_zero, but keep it here to maintain - # compatibility with Pydgin. - - def __getitem__(self, idx): - if self.debug.enabled('rf') and idx < 64: - print (':: RD.RF[%s] = %s' % - (pad('%d' % idx, 2), - pad_hex(self.regs[idx], len=self.debug_nchars))), - return intmask(self.regs[idx]) - - @specialize.argtype(2) - def __setitem__(self, idx, value): - self.regs[idx] = r_uint(value) - if self.debug.enabled('rf') and idx < 64: - print (':: WR.RF[%s] = %s' % - (pad('%d' % idx, 2), - pad_hex(self.regs[idx], len=self.debug_nchars))), - - #----------------------------------------------------------------------- # State #----------------------------------------------------------------------- class State(Machine): - _virtualizable_ = ['pc', 'num_insts'] + _virtualizable_ = ['num_insts'] def __init__(self, memory, debug, reset_addr=RESET_ADDR): Machine.__init__(self, memory, - RegisterFile(constant_zero=False, num_regs=107), + MemoryMappedRegisterFile(memory), debug, reset_addr=RESET_ADDR) # Epiphany III exceptions. @@ -122,7 +47,7 @@ def get_lateched_interrupt(self): ilat_highest_bit= -1 for index in range(10): if ((self.rf[reg_map['ILAT']] & (1 << index)) and - not (self.rf[reg_map['IMASK']] & (1 << index))): + not (self.rf[reg_map['IMASK']] & (1 << index))): ilat_highest_bit = index break return ilat_highest_bit @@ -134,7 +59,7 @@ def _set_nth_bit_of_register(self, register, n, value): if value: self.rf[reg_map[register]] |= (1 << n) else: - self.rf[reg_map[register]] &= ~(1 << n) + self.rf[reg_map[register]] &= ~(1 << n) @property def ACTIVE(self): @@ -285,67 +210,3 @@ def debug_flags(self): print ('AN=%s AZ=%s AC=%s AV=%s AVS=%s BN=%s BZ=%s BIS=%s BUS=%s BV=%s BVS=%s ' % (self.AN, self.AZ, self.AC, self.AV, self.AVS, self.BN, self.BZ, self.BIS, self.BUS, self.BV, self.BVS)), - - -def get_address_of_register_by_name(register_name): - register_number = reg_map[register_name] - for address in _register_map: - if _register_map[address][1] == register_number: - return address - - -_register_map = { - # Memory location -> (size, register number) - 0xf0400 : (4, reg_map['CONFIG']), # Core configuration - 0xf0404 : (4, reg_map['STATUS']), # Core status - 0xf0408 : (4, reg_map['pc']), # Program counter - 0xf040c : (4, reg_map['DEBUGSTATUS']), # Debug status - 0xf0414 : (4, reg_map['LC']), # Hardware counter loop - 0xf0418 : (4, reg_map['LS']), # Hardware counter start address - 0xf041c : (4, reg_map['LE']), # Hardware counter end address - 0xf0420 : (4, reg_map['IRET']), # Interrupt PC return address - 0xf0424 : (4, reg_map['IMASK']), # Interrupt mask - 0xf0428 : (4, reg_map['ILAT']), # Interrupt latch - 0xf042c : (4, reg_map['ILATST']), # Alias for setting interrupts - 0xf0430 : (4, reg_map['ILATCL']), # Alias for clearing interrupts - 0xf0434 : (4, reg_map['IPEND']), # Interrupt currently in progress - 0xf0440 : (4, reg_map['FSTATUS']), # Alias for writing to all STATUS bits - 0xf0448 : (1, reg_map['DEBUGCMD']), # Debug command register (2 bits) - 0xf070c : (1, reg_map['RESETCORE']), # Per core software reset (1 bit) - # Event timer registers - 0xf0438 : (4, reg_map['CTIMER0']), # Core timer 0 - 0xf043c : (4, reg_map['CTIMER1']), # Core timer 1 - # Process control registers - 0xf0604 : (2, reg_map['MEMSTATUS']), # Memory protection status - # Epiphany IV: 14 bits, III: 1 bit ([2]) - 0xf0608 : (2, reg_map['MEMPROTECT']), # Memory protection registration - # Epiphany IV: 16 bits, III: 8 bits. - # DMA registers - 0xf0500 : (4, reg_map['DMA0CONFIG']), # DMA channel 0 configuration - 0xf0504 : (4, reg_map['DMA0STRIDE']), # DMA channel 0 stride - 0xf0508 : (4, reg_map['DMA0COUNT']), # DMA channel 0 count - 0xf050c : (4, reg_map['DMA0SRCADDR']), # DMA channel 0 source address - 0xf0510 : (4, reg_map['DMA0DSTADDR']), # DMA channel 0 destination address - 0xf0514 : (4, reg_map['DMA0AUTO0']), # DMA channel 0 slave lower data - 0xf0518 : (4, reg_map['DMA0AUTO1']), # DMA channel 0 slave upper data - 0xf051c : (4, reg_map['DMA0STATUS']), # DMA channel 0 status - 0xf0520 : (4, reg_map['DMA1CONFIG']), # DMA channel 1 configuration - 0xf0524 : (4, reg_map['DMA1STRIDE']), # DMA channel 1 stride - 0xf0528 : (4, reg_map['DMA1COUNT']), # DMA channel 1 count - 0xf052c : (4, reg_map['DMA1SRCADDR']), # DMA channel 1 source address - 0xf0530 : (4, reg_map['DMA1DSTADDR']), # DMA channel 1 destination address - 0xf0534 : (4, reg_map['DMA1AUTO0']), # DMA channel 1 slave lower data - 0xf0538 : (4, reg_map['DMA1AUTO1']), # DMA channel 1 slave upper data - 0xf053c : (4, reg_map['DMA1STATUS']), # DMA channel 1 status - # Mesh node control registers - 0xf0700 : (2, reg_map['MESHCONFIG']), # Mesh node configuration - 0xf0704 : (3, reg_map['COREID']), # Processor core ID (12 bits) - 0xf0708 : (4, reg_map['MULTICAST']), # Multicast configuration - 0xf0710 : (3, reg_map['CMESHROUTE']), # cMesh routing configuration (12 bits) - 0xf0714 : (3, reg_map['XMESHROUTE']), # xMesh routing configuration (12 bits) - 0xf0718 : (3, reg_map['RMESHROUTE']), # rMesh routing configuration (12 bits) -} - -# Add general purpose registers to register_map. -for index, address in enumerate(xrange(0xf0000, 0xf0100, 0x4)): - _register_map[address] = (4, reg_map['r%d' % ((address - 0xf0000) / 0x4)]) diff --git a/revelation/sim.py b/revelation/sim.py index ec31827..2e96cce 100644 --- a/revelation/sim.py +++ b/revelation/sim.py @@ -2,14 +2,15 @@ from pydgin.misc import load_program from pydgin.sim import Sim, init_sim -from revelation.isa import decode, reg_map -from revelation.machine import RevelationMemory, State, RESET_ADDR from revelation.instruction import Instruction +from revelation.isa import decode, reg_map +from revelation.machine import State, RESET_ADDR +from revelation.storage import Memory MEMORY_SIZE = 2**32 # Global on-chip address space. def new_memory(): - return RevelationMemory(size=MEMORY_SIZE, byte_storage=True) + return Memory(size=MEMORY_SIZE) class Revelation(Sim): @@ -55,8 +56,8 @@ def post_execute(self): self.hardware_loop = False return # Service interrupts. See: http://blog.alexrp.com/revelation-notes/ - if (self.state.rf[reg_map['ILAT']] > 0 and not (self.state.GID or - self.state.rf[reg_map['DEBUGSTATUS']] == 1)): + if (self.state.rf[reg_map['ILAT']] > 0 and + not (self.state.GID or self.state.rf[reg_map['DEBUGSTATUS']] == 1)): self._service_interrupts() def _service_interrupts(self): @@ -91,12 +92,7 @@ def init_state(self, exe_file, filename, run_argv, Debug.global_enabled = True memory = new_memory() _, _ = load_program(exe_file, memory) - memory.set_debug(self.debug) self.state = State(memory, self.debug, reset_addr=RESET_ADDR) - # Give the RAM model a reference to the register files. Since the - # Epiphany has memory-mapped register files, we need to intercept - # any read / write which should actually go to the registers. - memory.rf = self.state.rf self.state.ACTIVE = True self.state.SUPERUSER = True diff --git a/revelation/storage.py b/revelation/storage.py new file mode 100644 index 0000000..dd66487 --- /dev/null +++ b/revelation/storage.py @@ -0,0 +1,268 @@ +from collections import OrderedDict +from pydgin.debug import Debug, pad, pad_hex +from pydgin.jit import elidable, unroll_safe, hint +from pydgin.utils import specialize + + +#----------------------------------------------------------------------- +# Memory +#----------------------------------------------------------------------- +def Memory(data=None, size=2**10): + try: + from rpython.rlib.objectmodel import we_are_translated + sparse_storage = not we_are_translated() + except ImportError: + sparse_storage = True + if not sparse_storage: + return _ByteMemory(data, size) + else: + print "NOTE: Using sparse storage" + return _SparseMemory(_ByteMemory) + + +#----------------------------------------------------------------------- +# _ByteMemory +#----------------------------------------------------------------------- +class _ByteMemory(object): + def __init__(self, data=None, size=2**10): + self.data = data if data else [' '] * size + self.size = len(self.data) + self.debug = Debug() + + def bounds_check(self, addr): + # Check if the accessed data is larger than the memory size. + if addr > self.size: + print 'WARNING: accessing larger address than memory size. ' + \ + 'addr=%s size=%s' % (pad_hex(addr), pad_hex(self.size)) + if addr == 0: + print 'WARNING: writing null pointer!' + raise Exception() + + @unroll_safe + def read(self, start_addr, num_bytes): + if self.debug.enabled('memcheck'): + self.bounds_check(start_addr) + value = 0 + if (self.debug.enabled('mem') and + (start_addr < 0xf0000 or start_addr > 0xf0718)): + print ':: RD.MEM[%s] = ' % pad_hex(start_addr), + for i in range(num_bytes - 1, -1, -1): + value = value << 8 + value = value | ord(self.data[start_addr + i]) + if self.debug.enabled('mem'): + print '%s' % pad_hex(value), + return value + + # this is instruction read, which is otherwise identical to read. The + # only difference is the elidable annotation, which we assume the + # instructions are not modified (no side effects, assumes the addresses + # correspond to the same instructions) + @elidable + def iread(self, start_addr, num_bytes): + value = 0 + for i in range(num_bytes - 1, -1, -1): + value = value << 8 + value = value | ord(self.data[start_addr + i]) + return value + + @unroll_safe + def write(self, start_addr, num_bytes, value): + if self.debug.enabled('memcheck'): + self.bounds_check(start_addr) + if (self.debug.enabled('mem') and + (start_addr < 0xf0000 or start_addr > 0xf0718)): + print ':: WR.MEM[%s] = %s' % (pad_hex(start_addr), pad_hex(value)), + for i in range(num_bytes): + self.data[start_addr + i] = chr(value & 0xff) + value = value >> 8 + + +#----------------------------------------------------------------------- +# _SparseMemory +#----------------------------------------------------------------------- +class _SparseMemory(object): + _immutable_fields_ = ['BlockMemory', 'block_size', 'addr_mask', 'block_mask'] + + def __init__(self, BlockMemory, block_size=2**10): + self.BlockMemory = BlockMemory + self.block_size = block_size + self.addr_mask = block_size - 1 + self.block_mask = 0xffffffff ^ self.addr_mask + self.debug = Debug() + print 'sparse memory size %x addr mask %x block mask %x' \ + % (self.block_size, self.addr_mask, self.block_mask) + self.block_dict = {} + self.debug = Debug() + + def add_block(self, block_addr): + self.block_dict[block_addr] = self.BlockMemory(size=self.block_size) + + @elidable + def get_block_mem(self, block_addr): + if block_addr not in self.block_dict: + self.add_block(block_addr) + block_mem = self.block_dict[block_addr] + return block_mem + + @elidable + def iread(self, start_addr, num_bytes): + start_addr = hint(start_addr, promote=True) + num_bytes = hint(num_bytes, promote=True) + end_addr = start_addr + num_bytes - 1 + block_addr = self.block_mask & start_addr + block_mem = self.get_block_mem(block_addr) + # For mixed-width ISAs, the start_addr is not necessarily + # word-aligned, and can cross block memory boundaries. If there is + # such a case, we have two instruction reads and then form the word + # for it + block_end_addr = self.block_mask & end_addr + if block_addr == block_end_addr: + return block_mem.iread(start_addr & self.addr_mask, num_bytes) + else: + num_bytes1 = min(self.block_size - (start_addr & self.addr_mask), + num_bytes) + num_bytes2 = num_bytes - num_bytes1 + block_mem1 = block_mem + block_mem2 = self.get_block_mem(block_end_addr) + value1 = block_mem1.iread(start_addr & self.addr_mask, num_bytes1) + value2 = block_mem2.iread(0, num_bytes2) + value = value1 | (value2 << (num_bytes1 * 8)) + return value + + def read(self, start_addr, num_bytes): + if (self.debug.enabled('mem') and + (start_addr < 0xf0000 or start_addr > 0xf0718)): + print ':: RD.MEM[%s] = ' % pad_hex(start_addr), + block_addr = self.block_mask & start_addr + block_addr = hint(block_addr, promote=True) + block_mem = self.get_block_mem(block_addr) + value = block_mem.read(start_addr & self.addr_mask, num_bytes) + if (self.debug.enabled('mem') and + (start_addr < 0xf0000 or start_addr > 0xf0718)): + print '%s' % pad_hex(value), + return value + + def write(self, start_addr, num_bytes, value): + if (self.debug.enabled('mem') and + (start_addr < 0xf0000 or start_addr > 0xf0718)): + print ':: WR.MEM[%s] = %s' % (pad_hex(start_addr), pad_hex(value)), + block_addr = self.block_mask & start_addr + block_addr = hint(block_addr, promote=True) + block_mem = self.get_block_mem(block_addr) + block_mem.write(start_addr & self.addr_mask, num_bytes, value) + + +#----------------------------------------------------------------------- +# MemoryMappedRegisterFile +#----------------------------------------------------------------------- +class MemoryMappedRegisterFile(object): + """Simulate the memory-mapped registers of a single Epiphany core. + FIXME: Use correct bit-width for registers whose width is not a + multiple of 8. Note that memory objects can only read and write + aligned words. + """ + def __init__(self, memory): + self.debug = Debug() + self.memory = memory + self.num_regs = len(_register_map) + self.debug_nchars = 8 + # All registers hold zero when the simulator starts. + for index in _register_map: + self.__setitem__(index, 0) + return + + def __getitem__(self, index): + address, nbytes, _ = _register_map[index] + value = self.memory.read(address, nbytes) + if self.debug.enabled('rf') and index < 64: + print (':: RD.RF[%s] = %s' % (pad('%d' % index, 2), + pad_hex(value, len=self.debug_nchars))), + return value + + @specialize.argtype(2) + def __setitem__(self, index, value): + address, nbytes, _ = _register_map[index] + self.memory.write(address, nbytes, value) + if self.debug.enabled('rf') and index < 64: + print (':: WR.RF[%s] = %s' % ((pad('%d' % index, 2), + pad_hex(value, len=self.debug_nchars)))), + + def print_regs(self, per_row=6): + """Prints all registers (register dump). + per_row specifies the number of registers to display per row. + """ + for col in xrange(0, self.num_regs, per_row): + line = '' + for row in xrange(col, min(self.num_regs, col + per_row)): + _, _, name = _register_map[row] + value = self.__getitem__(row) + line += '%s:%s ' % (pad('%s' % name, 2), pad_hex(value)) + print line + + +def get_address_of_register_by_name(register_name): + for reg_index in _register_map: + if _register_map[reg_index][2] == register_name: + return _register_map[reg_index][0] + + +_special_purpose_registers = [ + (0xf0400, 4, 'CONFIG'), # Core configuration + (0xf0404, 4, 'STATUS'), # Core status + (0xf0408, 4, 'pc'), # Program counter + (0xf040c, 4, 'DEBUGSTATUS'), # Debug status + (0xf0414, 4, 'LC'), # Hardware counter loop + (0xf0418, 4, 'LS'), # Hardware counter start address + (0xf041c, 4, 'LE'), # Hardware counter end address + (0xf0420, 4, 'IRET'), # Interrupt PC return address + (0xf0424, 4, 'IMASK'), # Interrupt mask + (0xf0428, 4, 'ILAT'), # Interrupt latch + (0xf042c, 4, 'ILATST'), # Alias for setting interrupts + (0xf0430, 4, 'ILATCL'), # Alias for clearing interrupts + (0xf0434, 4, 'IPEND'), # Interrupt currently in progress + (0xf0440, 4, 'FSTATUS'), # Alias for writing to all STATUS bits + (0xf0448, 1, 'DEBUGCMD'), # Debug command register (2 bits) + (0xf070c, 1, 'RESETCORE'), # Per core software reset (1 bit) + # Event timer registers + (0xf0438, 4, 'CTIMER0'), # Core timer 0 + (0xf043c, 4, 'CTIMER1'), # Core timer 1 + # Process control registers + (0xf0604, 2, 'MEMSTATUS'), # Memory protection status + # Epiphany IV: 14 bits, III: 1 bit ([2]) + (0xf0608, 2, 'MEMPROTECT'), # Memory protection registration + # Epiphany IV: 16 bits, III: 8 bits. + # DMA registers + (0xf0500, 4, 'DMA0CONFIG'), # DMA channel 0 configuration + (0xf0504, 4, 'DMA0STRIDE'), # DMA channel 0 stride + (0xf0508, 4, 'DMA0COUNT'), # DMA channel 0 count + (0xf050c, 4, 'DMA0SRCADDR'), # DMA channel 0 source address + (0xf0510, 4, 'DMA0DSTADDR'), # DMA channel 0 destination address + (0xf0514, 4, 'DMA0AUTO0'), # DMA channel 0 slave lower data + (0xf0518, 4, 'DMA0AUTO1'), # DMA channel 0 slave upper data + (0xf051c, 4, 'DMA0STATUS'), # DMA channel 0 status + (0xf0520, 4, 'DMA1CONFIG'), # DMA channel 1 configuration + (0xf0524, 4, 'DMA1STRIDE'), # DMA channel 1 stride + (0xf0528, 4, 'DMA1COUNT'), # DMA channel 1 count + (0xf052c, 4, 'DMA1SRCADDR'), # DMA channel 1 source address + (0xf0530, 4, 'DMA1DSTADDR'), # DMA channel 1 destination address + (0xf0534, 4, 'DMA1AUTO0'), # DMA channel 1 slave lower data + (0xf0538, 4, 'DMA1AUTO1'), # DMA channel 1 slave upper data + (0xf053c, 4, 'DMA1STATUS'), # DMA channel 1 status + # Mesh node control registers + (0xf0700, 2, 'MESHCONFIG'), # Mesh node configuration + (0xf0704, 3, 'COREID'), # Processor core ID (12 bits) + (0xf0708, 4, 'MULTICAST'), # Multicast configuration + (0xf0710, 3, 'CMESHROUTE'), # cMesh routing configuration (12 bits) + (0xf0714, 3, 'XMESHROUTE'), # xMesh routing configuration (12 bits) + (0xf0718, 3, 'RMESHROUTE'), # rMesh routing configuration (12 bits) +] + + +# Register number -> (memory address, num bytes, name) +_register_map = OrderedDict() +# Add general purpose registers to register_map. +for index, address in enumerate(xrange(0xf0000, 0xf0100, 0x4)): + _register_map[index] = (address, 4, 'r%d' % ((address - 0xf0000) / 0x4)) +# Add special purpose registers to _register_map. +for index in xrange(len(_special_purpose_registers)): + _register_map[index + 64] = _special_purpose_registers[index] diff --git a/revelation/test/opcode_factory.py b/revelation/test/opcode_factory.py index a207d92..31ae635 100644 --- a/revelation/test/opcode_factory.py +++ b/revelation/test/opcode_factory.py @@ -1,4 +1,4 @@ -from revelation.machine import get_address_of_register_by_name +from revelation.storage import get_address_of_register_by_name def make_zero_operand_factory(opcode): def factory():