////////////////////////////////////////////////////////////////////// // // // ARM7tdmiCPU.pas: CPU interface // // Provides an interface to the CPU for whatever uses the core, // // including code to actually run the CPU, graphics, and sound // // modules, calling the appropriate decoder/executers as needed. // // // // The contents of this file are subject to the Bottled Light // // Public License Version 1.0 (the "License"); you may not use this // // file except in compliance with the License. You may obtain a // // copy of the License at http://www.bottledlight.com/BLPL/ // // // // Software distributed under the License is distributed on an // // "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or // // implied. See the License for the specific language governing // // rights and limitations under the License. // // // // The Original Code is the Mappy VM Core, released April 1st, 2003 // // The Initial Developer of the Original Code is Bottled Light, // // Inc. Portions created by Bottled Light, Inc. are Copyright // // (C) 2001 - 2003 Bottled Light, Inc. All Rights Reserved. // // // // Author(s): // // Michael Noland (joat), michael@bottledlight.com // // // // Changelog: // // 1.0: First public release (April 1st, 2003) // // // // Notes: // // Todo: Figure out a way to make vmStep really step a single // // instruction. Currently it will also run a single cycle of // // any non-atomic action like a write to $04000301, which can // // be muoy annoying. The easiest way would probably be to make // // vmStep run cycles until cpuStopped and cpuHalted are false, // // but that could lead to an infinite loop if some idiot halts // // the CPU without providing any wakeup conditions. // // // ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// unit ARM7tdmiCPU; //////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// interface //////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// uses SysUtils, Math, nexus, AddressSpace, cpuMisc, cpuMemory, cpuGraphics, cpuSound, cpuARMCore, cpuThumbCore, cpuPeripherals; ////////////////////////////////////////////////////////////////////// // Exported functions //////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// // vmReset returns the CPU to its initial state, as if it's reset pin // were pulsed. procedure vmReset; // vmExecute runs the CPU for a number of cycles and returns the // number of cycles actually executed, which could be less if a // breakpoint were reached, or more if a long DMA or opcode occured. function vmExecute(numCycles: integer): uint32; // vmStep() executes a single instruction procedure vmStep; // vmGetRegister returns the contents of a single CPU register function vmGetRegister(index: uint32): uint32; // vmGetRegisters returns the contents of all CPU registers procedure vmGetRegisters(var copy: TvmRegisterFile); // vmSetRegister sets the contents of a single CPU register procedure vmSetRegister(index, value: uint32); // vmSetRegisters() sets the contents of all CPU registers procedure vmSetRegisters(const copy: TvmRegisterFile); // vmStartProfile() returns a profiling token that can be used to get // the exact number of elapsed cycles between a call to // vmStartProfile and vmStopProfile. Its not good for anything else. function vmStartProfile: TvmProfileToken; // vmStopProfile() returns the number of MVM CPU cycles elapsed since // the token passed in was created. That token is no longer valid. function vmStopProfile(const token: TvmProfileToken): int64; // vmCurrentPC() returns the address of next instruction to execute function vmCurrentPC: uint32; // vmHitBP() returns true if the next instruction is a breakpoint. function vmHitBP: boolean; // vmRenderFrame() runs the CPU core for enough cycles to advance to // the next frame boundary (1232*228 cycles). procedure vmRenderFrame; // vmSaveState() is used to save the entire MVM state. If nil is // passed in, it returns the required amount of memory for the save // buffer. Otherwise, it stores a savestate image in the buffer. function vmSaveState(save: PvmSavestate): integer; // vmLoadSavestate() sets the entire MVM state from a savestate image procedure vmLoadState(save: PvmSavestate); ////////////////////////////////////////////////////////////////////// exports vmReset, vmExecute, vmStep, vmGetRegister, vmGetRegisters, vmSetRegister, vmSetRegisters, vmStartProfile, vmStopProfile, vmCurrentPC, vmHitBP, vmRenderFrame, vmSaveState, vmLoadState; ////////////////////////////////////////////////////////////////////// implementation /////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// // Not valid during emulation, only from external code function vmStartProfile: TvmProfileToken; begin Result := pointer(cpuGlobalTicks); end; ////////////////////////////////////////////////////////////////////// // Not valid during emulation, only from external code function vmStopProfile(const token: TvmProfileToken): int64; begin Result := uint32(cpuGlobalTicks - uint32(token)); end; ////////////////////////////////////////////////////////////////////// // vmCurrentPC() returns the address of next instruction to execute function vmCurrentPC: uint32; begin Result := regs[R15] - 4; if thumbMode then Inc(Result, 2); end; ////////////////////////////////////////////////////////////////////// // vmHitBP() returns true if the next instruction is a breakpoint. function vmHitBP: boolean; begin Result := hitBreakpoint; end; ////////////////////////////////////////////////////////////////////// // vmRenderFrame() runs the CPU core for enough cycles to advance to // the next frame boundary (1232*228 cycles). procedure vmRenderFrame; const CYCS_PER_FRAME = 1232*228; begin vmExecute(CYCS_PER_FRAME - cpuGlobalTicks mod CYCS_PER_FRAME); end; ////////////////////////////////////////////////////////////////////// // vmReset returns the CPU to its initial state, as if it's reset pin // were pulsed. procedure vmReset; var i: integer; begin // When reset, the ARM7tdmi copies the current PC to R14_svc and the // current CPSR to SPSR_svc. After this, it switches to supervisor // mode and sets the I and F bits. It then forces the PC to the reset // vector and resumes execution in ARM mode cpuEnterException(MODE_SUPERVISOR, 0); irqDisabled := true; fiqDisabled := true; // Initialize the stack pointers (fixme: should be ditched once bios only) regs[R13_svc] := $03007FE0; regs[R13_irq] := $03007FA0; regs[R13] := $03007F00; // Initialize the I/O registers (fixme: should be removed once bios only) for i := 0 to REGISTERS_MASK do registers[i] := 0; registers[DISPLAY_CR] := $80; Puint16(@(registers[BG2_A]))^ := $0100; Puint16(@(registers[BG2_D]))^ := $0100; Puint16(@(registers[BG3_A]))^ := $0100; Puint16(@(registers[BG3_D]))^ := $0100; Puint32(@(registers[BG2_X_LATCH]))^ := 0; Puint32(@(registers[BG2_Y_LATCH]))^ := 0; Puint32(@(registers[BG3_X_LATCH]))^ := 0; Puint32(@(registers[BG3_Y_LATCH]))^ := 0; registers[BG2_X_DIRTY] := 0; registers[BG2_Y_DIRTY] := 0; registers[BG3_X_DIRTY] := 0; registers[BG3_Y_DIRTY] := 0; downkeys := $3FF; Puint16(@(registers[KEYS]))^ := downkeys; // Initialize RAM FillChar(VRAM, VRAM_MASK+1, 0); FillChar(WRAM, WRAM_MASK+1, 0); FillChar(OAM, OAM_MASK+1, 0); FillChar(Palette, PALETTE_MASK+1, 0); FillChar(cartRAM, SRAM_MASK+1, $FF); cartRAMdirty := false; // Event setup enteringHBlank := true; HBlankEvent := 960; eventCycleDelta := HBlankEvent; eventCyclesLeft := HBlankEvent; cpuGlobalTicks := 0; // CPU power save (04000301) support cpuStopped := false; cpuHalted := false; end; ////////////////////////////////////////////////////////////////////// // vmExecute runs the CPU for a number of cycles and returns the // number of cycles actually executed, which could be less if a // breakpoint were reached, or more if a long DMA or opcode occured. function vmExecute(numCycles: integer): uint32; var quotaLeft: integer; cyclesForThisQuota: integer; begin hitBreakpoint := false; quotaLeft := numCycles; repeat // Activate an IRQ if queued if irqPending then begin if cpuHalted then begin if Puint16(@(registers[IRQ_FLAGS]))^ and IRQ_KEY <> 0 then begin cpuHalted := false; cpuStopped := false; end; end else cpuStopped := false; if not irqDisabled then begin irqPending := false; // logWriteLn('Entering IRQ from ' + IntToHex(vmCurrentPC, 8)); cpuEnterException(MODE_IRQ, IRQ_VECTOR); irqDisabled := true; end; end; // Run a slice of opcodes quota := Min(eventCyclesLeft, quotaLeft); cyclesForThisQuota := quota; soundQuotaAtLastFlush := cyclesForThisQuota; timerQuotaAtLastFlush := cyclesForThisQuota; if cpuStopped then quota := 0 else begin if thumbMode then EmulateThumb else EmulateArm; thumbMode := thumbMode xor haveFlippedThumb; haveFlippedThumb := false; end; flushTimers; // Update the counters by (cyclesForThisQuota - quota) cycles Dec(cyclesForThisQuota, quota); Dec(quotaLeft, cyclesForThisQuota); Dec(eventCyclesLeft, cyclesForThisQuota); Inc(soundCyclesUndone, cyclesForThisQuota); quota := 0; soundQuotaAtLastFlush := 0; timerQuotaAtLastFlush := 0; // Process any outstanding events if eventCyclesLeft <= 0 then begin // (eventCycleDelta - eventCyclesLeft) cycles since the last event Dec(eventCycleDelta, eventCyclesLeft); Dec(HBlankEvent, eventCycleDelta); // Take care of the raster clock events while HBlankEvent <= 0 do HBlank; eventCycleDelta := HBlankEvent; eventCyclesLeft := eventCycleDelta; end; until (quotaLeft <= 0) or (hitBreakpoint); // Flush the sound data to the system soundChanging; // Return the number of cycles executed and update the global count Result := numCycles - quotaLeft; Inc(cpuGlobalTicks, Result); end; ////////////////////////////////////////////////////////////////////// // vmGetRegister returns the contents of a single CPU register function vmGetRegister(index: uint32): uint32; begin if index = CPSR then Result := cpuReadCPSR else Result := regs[index]; end; ////////////////////////////////////////////////////////////////////// // vmGetRegisters returns the contents of all CPU registers procedure vmGetRegisters(var copy: TvmRegisterFile); begin regs[CPSR] := cpuReadCPSR; Move(regs, copy, SizeOf(TvmRegisterFile)); end; ////////////////////////////////////////////////////////////////////// // vmSetRegister sets the contents of a single CPU register procedure vmSetRegister(index, value: uint32); var oldMode: boolean; begin if index = R15 then begin regs[R15] := value; if thumbMode then FlushPipeThumb else FlushPipeARM; end else if index = CPSR then begin oldMode := thumbMode; cpuWriteCPSR(value); if thumbMode <> oldMode then begin if oldMode then Dec(regs[R15], 2) else Dec(regs[R15], 4); if thumbMode then FlushPipeThumb else FlushPipeARM; end; end else regs[index] := value; end; ////////////////////////////////////////////////////////////////////// // vmSetRegisters() sets the contents of all CPU registers procedure vmSetRegisters(const copy: TvmRegisterFile); begin Move(copy, regs, SizeOf(TvmRegisterFile)); end; ////////////////////////////////////////////////////////////////////// // vmStep() executes a single instruction // TODO: figure out a way to make this really do that, it will also // run a single cycle of something like a write to 0x04000301, which // can be muoy annoying. procedure vmStep; begin vmExecute(1); end; ////////////////////////////////////////////////////////////////////// // vmSaveState() is used to save the entire MVM state. If nil is // passed in, it returns the required amount of memory for the save // buffer. Otherwise, it stores a savestate image in the buffer. function vmSaveState(save: PvmSavestate): integer; begin Result := SizeOf(TvmSavestate1); if save = nil then Exit; save^.magic := $DEAF4242; save^.version := 1; save^.size := Result; // Memory space Move(systemROM, save^.systemROM, SYSTEM_ROM_MASK+1); Move(exWRAM, save^.exWRAM, EX_WRAM_MASK+1); Move(WRAM, save^.WRAM, WRAM_MASK+1); Move(registers, save^.registers, REGISTERS_MASK+1); Move(palette, save^.palette, PALETTE_MASK+1); Move(VRAM, save^.VRAM, VRAM_MASK+1); Move(OAM, save^.OAM, OAM_MASK+1); Move(cartRAM, save^.cartRAM, SRAM_MASK+1); save^.cartRAMdirty := cartRAMdirty; save^.lastAddress := lastAddress; save^.cartLoaded := cartLoaded; // CPU status save^.cpuGlobalTicks := cpuGlobalTicks; save^.cpuStopped := cpuStopped; save^.cpuHalted := cpuHalted; save^.irqPending := irqPending; save^.SPSR := SPSR; regs[CPSR] := cpuReadCPSR; Move(regs, save^.regs, SizeOf(regs)); // Events save^.HBlankEvent := HBlankEvent; save^.enteringHBlank := enteringHBlank; save^.eventCycleDelta := eventCycleDelta; save^.eventCyclesLeft := eventCyclesLeft; // Sound Move(soundA, save^.soundA, SizeOf(soundA)); Move(soundB, save^.soundB, SizeOf(soundB)); Move(sound1, save^.sound1, SizeOf(sound1)); Move(sound2, save^.sound2, SizeOf(sound2)); Move(sound3, save^.sound3, SizeOf(sound3)); Move(sound4, save^.sound4, SizeOf(sound4)); end; ////////////////////////////////////////////////////////////////////// // vmLoadSavestate() sets the entire MVM state from a savestate image procedure vmLoadState(save: PvmSavestate); begin if (save^.magic <> $DEAF4242) or (save^.version <> 1) or (save^.size <> SizeOf(TvmSavestate1)) then begin logWriteLn('Invalid Savestate (only version 1 files are currently supported'); Exit; end; // Memory space Move(save^.systemROM, systemROM, SYSTEM_ROM_MASK+1); Move(save^.exWRAM, exWRAM, EX_WRAM_MASK+1); Move(save^.WRAM, WRAM, WRAM_MASK+1); Move(save^.registers, registers, REGISTERS_MASK+1); Move(save^.palette, palette, PALETTE_MASK+1); Move(save^.VRAM, VRAM, VRAM_MASK+1); Move(save^.OAM, OAM, OAM_MASK+1); Move(save^.cartRAM, cartRAM, SRAM_MASK+1); cartRAMdirty := save^.cartRAMdirty; lastAddress := save^.lastAddress; // CPU status cpuGlobalTicks := save^.cpuGlobalTicks; cpuStopped := save^.cpuStopped; cpuHalted := save^.cpuHalted; irqPending := save^.irqPending; SPSR := save^.SPSR; Move(save^.regs, regs, SizeOf(regs)); cpuMode := regs[CPSR] and $1F; cpuWriteCPSR(regs[CPSR]); // Events HBlankEvent := save^.HBlankEvent; enteringHBlank := save^.enteringHBlank; eventCycleDelta := save^.eventCycleDelta; eventCyclesLeft := save^.eventCyclesLeft; // Sound Move(save^.soundA, soundA, SizeOf(soundA)); Move(save^.soundB, soundB, SizeOf(soundB)); Move(save^.sound1, sound1, SizeOf(sound1)); Move(save^.sound2, sound2, SizeOf(sound2)); Move(save^.sound3, sound3, SizeOf(sound3)); Move(save^.sound4, sound4, SizeOf(sound4)); // Etc... SetWaitStates; end; ////////////////////////////////////////////////////////////////////// end. //////////////////////////////////////////////////////////////////////