/// <summary> /// Read the block of memory at the given address and size. /// </summary> /// <param name="address">address to read</param> /// <param name="ms">size of read</param> /// <returns>value read at that address</returns> public uint GetMemory(uint address, ARMPluginInterfaces.MemorySize ms) { System.Diagnostics.Debug.Assert(Valid && ((address & Mask) == Tag)); uint index = (address & ~Mask) >> 2; uint data = Data[index]; //fetch the value based on the size requested switch (ms) { case ARMPluginInterfaces.MemorySize.Byte: { uint byteNumber = address & 0x0003; data = (data >> (int)(byteNumber * 8)) & 0x00ff; } break; case ARMPluginInterfaces.MemorySize.HalfWord: { if ((address & 0x0002) != 0) { data >>= 16; } data &= 0xFFFF; } break; case ARMPluginInterfaces.MemorySize.Word: break; default: throw new Exception("Bad memory size specified"); } //switch return(data); } //GetMemory
}//allocateLine /// <summary> /// Read from the CacheSet. /// Check each block in the set and compare the line tag with the address. If the address tag matches the /// line tag, then we have a cache block hit. Otherwise we need to get the requested block into the /// cache and this will depend on the allocate policy. /// </summary> /// <param name="address">address to read</param> /// <param name="ms">size to read</param> /// <param name="allocatePolicy">allocation policy to use</param> /// <returns>value read</returns> public uint GetMemory(uint address, ARMPluginInterfaces.MemorySize ms, AllocatePolicyEnum allocatePolicy) { //check each line in the cache set foreach (CacheBlock cb in Blocks) { //only check valid lines and look for a tag match if (cb.Valid && ((address & cb.Mask) == cb.Tag)) { //cache read hit ReadHits++; return(cb.GetMemory(address, ms)); } } //cache read miss ReadMisses++; if (allocatePolicy != AllocatePolicyEnum.Write) { //read or both policy return(allocateLine(address).GetMemory(address, ms)); } else { //write policy, do not allocate a new cache line return(memBlock.GetMemory(address, ms)); } }//GetMemory
internal uint LoadMemorySignedByte(uint address, uint Rd, ARMPluginInterfaces.MemorySize memorySize) { System.Diagnostics.Debug.Assert(memorySize == ARMPluginInterfaces.MemorySize.Byte); uint value = GetMemory(address, memorySize); //Sign-extend the loaded byte to a word. mGPR[Rd] = (uint)(SByte)(value & 0xff); return((Rd == GeneralPurposeRegisters.PCRegisterIndex) ? (uint)5 : (uint)3); }
}//GetMemory /// <summary> /// Write memory to data cache /// </summary> /// <param name="address">address to read</param> /// <param name="ms">size to read</param> /// <param name="data">data to write</param> public void SetMemory(uint address, ARMPluginInterfaces.MemorySize ms, uint data) { if (!this.Enabled) { memBlock.SetMemory(address, ms, data); return; } //if Sets[computeSetNumber(address)].SetMemory(address, ms, data, _writeThru, _allocatePolicy); } //SetMemory
} //computeSetNumber /// <summary> /// Get memory from the cache. If the cache is not enabled, pass the request to main memory. /// Otherwise pass request to computed cache set. /// </summary> /// <param name="address">address to read</param> /// <param name="ms">size to read</param> /// <returns>value read</returns> public virtual uint GetMemory(uint address, ARMPluginInterfaces.MemorySize ms) { if (!this.Enabled) { return(memBlock.GetMemory(address, ms)); } //force allocate policy to be read for an L1cache return(Sets[computeSetNumber(address)].GetMemory(address, ms, ARMPluginInterfaces.Preferences.AllocatePolicyEnum.Read)); }//GetMemory
} //GetMemory /// <summary> /// Write to the cache Line. If the write-thru policy is active, write to both /// the cache line and out to main memory. /// </summary> /// <param name="address">address to write to</param> /// <param name="ms">size of write</param> /// <param name="data">value to write</param> /// <param name="writeThru">write through to main memory</param> public void SetMemory(uint address, ARMPluginInterfaces.MemorySize ms, uint data, bool writeThru) { System.Diagnostics.Debug.Assert(Valid && ((address & Mask) == Tag)); //convert address to array index uint index = (address & ~Mask) >> 2; switch (ms) { //case handles the writing of bytes case ARMPluginInterfaces.MemorySize.Byte: { uint byteNumber = address & 0x0003; uint mask = (uint)(0x00ff << (int)(byteNumber * 8)); uint newData = Data[index] & ~mask; uint thisData = data & 0x00ff; uint sData = thisData << (int)(byteNumber * 8); Data[index] = newData | sData; } break; //case handles the writing of halfwords case ARMPluginInterfaces.MemorySize.HalfWord: { uint d = Data[index]; if ((address & 0x0002) == 0) { Data[index] = (d & 0xFFFF0000) | (data & 0x0000FFFF); } else { Data[index] = (d & 0x0000FFFF) | (data << 16); } } break; //case handles the writing of words case ARMPluginInterfaces.MemorySize.Word: Data[index] = data; break; default: throw new Exception("Bad memory size specified"); } //switch if (writeThru) {//if write thru is active then write this to main memory as well memBlock.SetMemory(address, ms, data); } else {//else set the dirty bit Dirty = true; } }//SetMemory
internal uint transfer(uint op_code) { if ((op_code & undef_mask) == undef_code) { return(0); } ARMPluginInterfaces.MemorySize ms = ((op_code & byte_mask) == 0) ? ARMPluginInterfaces.MemorySize.Word : ARMPluginInterfaces.MemorySize.Byte; uint Rd = (op_code & rd_mask) >> 12; uint Rn = (op_code & rn_mask) >> 16; uint address = get_reg(Rn); int offset = transfer_offset((op_code & op2_mask), ((op_code & up_mask) != 0), ((op_code & imm_mask) != 0), false); //bit(25) = 1 -> reg if ((op_code & pre_mask) != 0) { //Pre-index address = (uint)((int)address + offset); } if (TrapUnalignedMemoryAccess) { if ((ms == ARMPluginInterfaces.MemorySize.Word && ((address & 0x03) != 0)) || (ms == ARMPluginInterfaces.MemorySize.HalfWord && ((address & 0x01) != 0))) { throw new UnalignedAccessException(address); } } uint cycles; if ((op_code & load_mask) == 0) { this.SetMemory(address, ms, get_reg(Rd)); cycles = 2; } else { mGPR[Rd] = this.GetMemory(address, ms); cycles = (Rd == GeneralPurposeRegisters.PCRegisterIndex) ? (uint)5 : (uint)3; } if ((op_code & pre_mask) == 0)//Post-index //Post index write-back { mGPR[Rn] = (uint)((int)address + offset); } else if ((op_code & write_back_mask) != 0) { //Pre index write-back mGPR[Rn] = address; } return(cycles); }
/* **************** DATA OP HANDLERS ********************** */ internal uint swap(uint op_code) { uint address = get_reg((byte)((op_code & rn_mask) >> 16)); ARMPluginInterfaces.MemorySize ms = ((op_code & byte_mask) != 0) ? ARMPluginInterfaces.MemorySize.Byte : ARMPluginInterfaces.MemorySize.Word; uint data = this.GetMemory(address, ms); this.SetMemory(address, ms, get_reg(op_code & rm_mask)); mGPR[((op_code & rd_mask) >> 12)] = data; return(4); }
private int CellWidth(ARMPluginInterfaces.MemorySize ms) { switch (ms) { case ARMPluginInterfaces.MemorySize.Byte: return(mCharSize.Width * 3); case ARMPluginInterfaces.MemorySize.HalfWord: return(mCharSize.Width * 5); case ARMPluginInterfaces.MemorySize.Word: return(mCharSize.Width * 10); default: return(0); } }
private static string MemoryToUnknownString(ARMPluginInterfaces.MemorySize ms) { switch (ms) { case ARMPluginInterfaces.MemorySize.Byte: return("??"); case ARMPluginInterfaces.MemorySize.HalfWord: return("????"); case ARMPluginInterfaces.MemorySize.Word: return("????????"); default: return(""); } }
} //Purge /// <summary> /// This method allows reading of the cache with no side effects. This is used ny the user interface /// so the cache contents can be displayed without changing the state of the cache. /// </summary> /// <param name="address">address to read</param> /// <param name="ms">size to read</param> /// <returns>valuee read</returns> public uint GetMemoryNoSideEffect(uint address, ARMPluginInterfaces.MemorySize ms) { //iterate over the cache blocks looking for the block that holds this address foreach (CacheBlock cl in Blocks) { //check if this block is valid and has this address if (cl.Valid && ((address & cl.Mask) == cl.Tag)) { //cache read hit return(cl.GetMemory(address, ms)); } //if } //foreach return(memBlock.GetMemory(address, ms)); }//GetMemoryNoSideEffect
} //memoryChangedHandler private static string MemoryToString(uint data, ARMPluginInterfaces.MemorySize ms) { switch (ms) { case ARMPluginInterfaces.MemorySize.Byte: return(data.ToString("X2")); case ARMPluginInterfaces.MemorySize.HalfWord: return(data.ToString("X4")); case ARMPluginInterfaces.MemorySize.Word: return(data.ToString("X8")); // case ARMPluginInterfaces.MemorySize.HalfWord: return Utils.reverseBinary(data, ARMPluginInterfaces.MemorySize.HalfWord).ToString("X4"); // case ARMPluginInterfaces.MemorySize.Word: return Utils.reverseBinary(data, ARMPluginInterfaces.MemorySize.Word).ToString("X8"); default: return(""); } }
public void memoryChangedHandler(uint address, ARMPluginInterfaces.MemorySize ms, uint oldValue, uint newValue) { if (mChangedMemory == null || address < mChangedMemoryStart) { return; } uint memAddress = address - mChangedMemoryStart; for (int ii = 0; ii < (int)ms; ii++, memAddress++) { if (memAddress < mChangedMemory.Length) { mChangedMemory[memAddress] = true; } //if } //for ii } //memoryChangedHandler
} //setmem8 /// <summary> /// Set a memory value at the specified address /// Checks for a valid address within the memory block limits. /// If the memory changed handler is set, call it with the write info. /// </summary> /// <param name="address">address to write</param> /// <param name="ms">size of memory to write</param> /// <param name="data">value to write</param> public void SetMemory(uint address, ARMPluginInterfaces.MemorySize ms, uint data) { if (ProtectTextArea) { if (!InDataRange(address, ms)) { throw new ARMPluginInterfaces.MemoryAccessException(address, "Out of range"); } } else { if (!InRange(address, ms)) { throw new ARMPluginInterfaces.MemoryAccessException(address, "Out of range"); } } //if changed handler set, call it if (_memoryChangedHandler != null) { _memoryChangedHandler(address, ms, GetMemory(address, ms), data); } //call the write function based on the data type switch (ms) { case ARMPluginInterfaces.MemorySize.Byte: setmem8(address, data); break; case ARMPluginInterfaces.MemorySize.HalfWord: setmem16(address, data); break; case ARMPluginInterfaces.MemorySize.Word: setmem32(address, data); break; default: throw new ARMPluginInterfaces.MemoryAccessException(address, "Bad memory size"); } //switch }//SetMemory
} //getmem8 /// <summary> /// Get a memory value at the specified address /// Checks for a valid address within the memory block limits. /// </summary> /// <param name="address">address to read</param> /// <param name="ms">size of memory to read</param> /// <returns>value read at address</returns> public uint GetMemory(uint address, ARMPluginInterfaces.MemorySize ms) { //if its out of range, abort if (!InRange(address, ms)) { throw new ARMPluginInterfaces.MemoryAccessException(address, "Out of range"); } //otherwise call the specific memget based on type switch (ms) { case ARMPluginInterfaces.MemorySize.Byte: return(getmem8(address)); case ARMPluginInterfaces.MemorySize.HalfWord: return(getmem16(address)); case ARMPluginInterfaces.MemorySize.Word: return(getmem32(address)); default: throw new ARMPluginInterfaces.MemoryAccessException(address, "Bad memory size"); } //switvh }//GetMemory
} //InRange /// <summary> /// Safely checks if the specified address and size are in the address range for data. /// </summary> /// <param name="address">address to check</param> /// <param name="ms">size of memory operation to check</param> /// <returns>true if in range</returns> public bool InDataRange(uint address, ARMPluginInterfaces.MemorySize ms) { if (address < _data_start) { return(false); } switch (ms) { case ARMPluginInterfaces.MemorySize.Byte: return(address < _memory_end); case ARMPluginInterfaces.MemorySize.HalfWord: return(address < (_memory_end - 1)); case ARMPluginInterfaces.MemorySize.Word: return(address < (_memory_end - 3)); default: throw new ARMPluginInterfaces.MemoryAccessException(address, "Bad memory size"); } //switch }
private bool get_changed(uint address, ARMPluginInterfaces.MemorySize ms) { if (mChangedMemory == null || address < mChangedMemoryStart) { return(false); } bool changed = false; uint memAddress = address - mChangedMemoryStart; for (int ii = 0; ii < (int)ms; ii++, memAddress++) { if (memAddress < mChangedMemory.Length) { if (mChangedMemory[memAddress]) { changed = true; } } //if } //for ii return(changed); }
} //WriteMisses /// <summary> /// Override the getmem here to use the true allocate policy. The L1Cache version forces /// this parameter to be read allocate. /// </summary> /// <param name="address">address to read</param> /// <param name="ms">size to read</param> /// <returns></returns> public override uint GetMemory(uint address, ARMPluginInterfaces.MemorySize ms) { uint result; if (!this.Enabled) { result = memBlock.GetMemory(address, ms); } else { result = Sets[computeSetNumber(address)].GetMemory(address, ms, _allocatePolicy); } if ((address & 0x03) != 0) // check for unaligned access { if (ms == ARMPluginInterfaces.MemorySize.Word) { // implement rotations of the memory word as specified in // the architectural specification of the LDR instruction switch (address & 0x03) { case 0x01: result = ((result >> 8) & 0x00FFFFFF) | (result << 24); break; case 0x02: result = ((result >> 16) & 0x0000FFFF) | (result << 16); break; case 0x03: result = ((result >> 24) & 0x000000FF) | (result << 8); break; } } // Note: for an unaligned halfword access, the result is unpredictable // so we do nothing. } return(result); }//GetMemory
/// <summary> /// Write to the CacheSet. /// Check each block in the set and compare the block tag with the address. If the address tag matches the /// block tag, then we have a cache write block hit. Otherwise we need to get the requested block into the /// cache and this will depend on the allocate policy. /// </summary> /// <param name="address">address to write to</param> /// <param name="ms">size to write</param> /// <param name="data">data to write</param> /// <param name="writeThru">true then write through to main memory</param> /// <param name="allocatePolicy">allocate policy to use</param> public void SetMemory(uint address, ARMPluginInterfaces.MemorySize ms, uint data, bool writeThru, AllocatePolicyEnum allocatePolicy) { //iterate over the cache blocks looking for the block that holds this address foreach (CacheBlock cb in Blocks) { //check if this block is valid and has this address if (cb.Valid && ((address & cb.Mask) == cb.Tag)) { //cache write hit, set data into cache memory WriteHits++; cb.SetMemory(address, ms, data, writeThru); //if the changed handler is set, call it if (_cacheChangedHandler != null) { _cacheChangedHandler(cb.BlockNumber, address & cb.Mask); } //done return; } //if } //foreach //cache write miss WriteMisses++; //allocate a cache line based on the allocation policy if (allocatePolicy != AllocatePolicyEnum.Read) { //write or both policy allocateLine(address).SetMemory(address, ms, data, writeThru); } else { //read policy, do not allocate a new cache line memBlock.SetMemory(address, ms, data); } } //SetMemory
/// <summary> /// Fetch memory from the main memory module and bypass the cache system. /// </summary> /// <param name="address"></param> /// <param name="ms"></param> /// <returns></returns> public abstract uint GetMemoryNoSideEffect(uint address, ARMPluginInterfaces.MemorySize ms);
/// <summary> /// Test if a given memory address and size are within the bounds of main memory. /// </summary> /// <param name="address"></param> /// <param name="ms"></param> /// <returns></returns> public abstract bool InRange(uint address, ARMPluginInterfaces.MemorySize ms);
/// <summary> /// Execute the next instruction pointed to by the PC. /// Can be in either ARM or Thumb mode. /// </summary> public void Execute() { uint pcBefore = mGPR.PC; // PC before op is executed try { ARMPluginInterfaces.MemorySize opcodeSize = mCPSR.tf ? ARMPluginInterfaces.MemorySize.HalfWord : ARMPluginInterfaces.MemorySize.Word; //this should never happen as the PC is checked after the opcode is executed, //but just being careful ... if (!this.InRange(pcBefore, opcodeSize)) { pcOutofRange(uint.MaxValue); // we don't know the old address?? return; } //track the number of instructions executed. ++InstructionCount; //get the opcode and increment the PC. Fetch through the cache system if available. uint opcode = this.mL1InstructionCache.GetMemory(pcBefore, opcodeSize); //increment the PC by the current opcode size (ARM or Thumb mode). mGPR.PC = pcBefore + (uint)opcodeSize; uint cycleCount; bool swiInstruction; try { cycleCount = ExecuteInstruction(opcode, out swiInstruction); } catch (UnalignedAccessException e) { ReportRuntimeError(pcBefore, "{0}", e.Message); HaltSimulation(); mGPR.PC = pcBefore; // undo any advance of the PC return; } catch (MemoryAccessException e) { ReportRuntimeError(pcBefore, "{0}", e.Message); HaltSimulation(); mGPR.PC = pcBefore; // undo any advance of the PC return; } // a cycle count of zero indicates an unknown instruction (including an swi instruction) if (cycleCount == 0) { //let plugins have first crack at the unknown instruction. //a return of 0 indicates it cannot handle it. cycleCount = UnknownOpCode(opcode); }//if //if the cycle count is still 0 and it's an swi instruction, it wasn't handled by a plugin //invoke an ARM swi exception if (cycleCount == 0 && swiInstruction) { this.RequestException(ARMExceptions.SoftwareInterrupt); }//if //otherwise its an unknown instruction that is not an swi instruction //so raise an ARM undefined instruction exception. else if (cycleCount == 0) { this.RequestException(ARMExceptions.UndefinedInstruction); }//else if //if we actually expended cycles, let the plugins know and update local cycle count if (cycleCount > 0) { CycleCount += cycleCount; //todo - check overflow HandleCycles((ushort)cycleCount); }//if //test the PC against valid memory. If it is not within the memory block, we have a problem if (!this.InRange(mGPR.PC, opcodeSize)) { pcOutofRange(pcBefore); }//if //check if any exceptions have been raised. //it is assumed that only 1 exception can be raised in a single instruction cycle. if (mRequestedException != 0) { if ((mRequestedException & (uint)ARMExceptions.SoftwareInterrupt) != 0) { SetExceptionState(ARMExceptions.SoftwareInterrupt); }//if //FIQ has highest priority else if (((mRequestedException & (uint)ARMExceptions.FIQ) != 0) && !mCPSR.FIQDisable) { SetExceptionState(ARMExceptions.FIQ); } else if (((mRequestedException & (uint)ARMExceptions.IRQ) != 0) && !mCPSR.IRQDisable) { SetExceptionState(ARMExceptions.IRQ); } else if ((mRequestedException & (uint)ARMExceptions.UndefinedInstruction) != 0) { SetExceptionState(ARMExceptions.UndefinedInstruction); } else if ((mRequestedException & (uint)ARMExceptions.Reset) != 0) { SetExceptionState(ARMExceptions.Reset); } mRequestedException = 0; }//if //test the PC against valid memory. If it is not within the memory block, we have a problem if (!this.InRange(mGPR.PC, opcodeSize)) { ARMExceptions possibleReason = interruptKind(mGPR.PC); if (possibleReason != ARMExceptions.None) { if (possibleReason == ARMExceptions.SoftwareInterrupt) { ReportRuntimeError(pcBefore, "Unimplemented SWI code: (0x{0:X6})\n" + "[Check File/Preferences/Plugins to see which SWI sets have been enabled]", opcode & 0xFFFFFF); } else { ReportRuntimeError(pcBefore, "Unhandled interrupt of kind {0}", interruptKind(mGPR.PC)); } HaltSimulation(); mGPR.PC = pcBefore; // leave PC stuck on the interrupting instruction } else { pcOutofRange(pcBefore); } } }//try catch (ARMPluginInterfaces.MemoryAccessException ex) { ReportRuntimeError(pcBefore, "Attempt to access memory out of valid range: 0x{0:X8}", ex.Address); HaltSimulation(); mGPR.PC = pcBefore; // leave PC stuck on the bad instruction }//catch }//Execute
/// <summary> /// Write data to main memory. /// This is abstract so that the Application level can handle cache and plugin logic. /// </summary> /// <param name="address"></param> /// <param name="ms"></param> /// <param name="data"></param> public abstract void SetMemory(uint address, ARMPluginInterfaces.MemorySize ms, uint data);
internal uint StoreMemory(uint address, uint Rd, ARMPluginInterfaces.MemorySize memorySize) { SetMemory(address, memorySize, get_reg(Rd)); return(2); }
private void drawNormalLineCell(Graphics formGraphics, uint address, Point startPoint, int cellNo, ARMPluginInterfaces.MemorySize ms, SolidBrush textBrush) { Brush drawBrush = get_changed(address, ms) ? new SolidBrush(_highlightColor) : textBrush; string dataChars; if (_JM.InRange(address, ARMPluginInterfaces.MemorySize.Byte)) { uint data = this.GetMemory(address, ms); dataChars = MemoryToString(data, ms); } else { dataChars = MemoryToUnknownString(ms); } int xpos = startPoint.X + (cellNo * CellWidth(ms)); for (int kk = 0; kk < dataChars.Length; kk++, xpos += mCharSize.Width) { formGraphics.DrawString(dataChars.Substring(kk, 1), CurrentFont, drawBrush, xpos, startPoint.Y); } //for kk if (drawBrush != textBrush) { drawBrush.Dispose(); } }
private uint GetMemory(uint address, ARMPluginInterfaces.MemorySize ms) { return(_JM.GetMemoryNoSideEffect(address, ms)); }
internal InstructionFunc GenerateLoadStoreInstruction(ComputeOffsetFunc ComputeOffset, ComputeOffsetAddressFunc ComputeOffsetAddress, ComputeAddressFunc ComputeFinalAddress, WritebackAddressFunc WritebackAddress, ARMPluginInterfaces.MemorySize memorySize, LoadStoreMemoryFunc LoadStoreMemory) { return(delegate(uint opcode) { uint Rd = (opcode & rd_mask) >> 12; uint Rn = (opcode & rn_mask) >> 16; uint base_address = get_reg(Rn); uint offset = ComputeOffset(opcode); uint offset_address = ComputeOffsetAddress(base_address, offset); uint address = ComputeFinalAddress(base_address, offset_address); if (TrapUnalignedMemoryAccess) { if ((address & (uint)(memorySize - 1)) != 0) { throw new UnalignedAccessException(address); } } //Writeback the address (if pre- or post-indexing is not being used, //the function will do nothing). WritebackAddress(Rn, offset_address); //Visit memory and perform whatever operation we came for. return LoadStoreMemory(address, Rd, memorySize); }); }
//The cycle counts in the below functions (i.e. 5 or 3 for loads, 2 for stores) were in the original code. //I have no idea where they came from originally - BB //TODO 10/16/2014 - The old code for LDRH contained some cryptic handling code for aligning the value //and toggling Thumb mode when loading into the PC. That code has been omitted here because I can't find //any explanation for it anywhere in the ARM documentation and it does not match the word load/store pattern... internal uint LoadMemory(uint address, uint Rd, ARMPluginInterfaces.MemorySize memorySize) { mGPR[Rd] = GetMemory(address, memorySize); return((Rd == GeneralPurposeRegisters.PCRegisterIndex) ? (uint)5 : (uint)3); }