////////////////////////////////////////////////////////////////////// // // // cpuMisc.pas: Support systems for the CPU // // Pipeline management, mode switch code, the barrel shifter, // // and software interrupts, traps, etc... // // // // 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: // // None at present. // // // ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// unit cpuMisc; //////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// interface //////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// uses SysUtils, nexus, AddressSpace, cpuMemory; ////////////////////////////////////////////////////////////////////// // Pipeline management functions ///////////////////////////////////// ////////////////////////////////////////////////////////////////////// procedure FlushPipeARM; procedure FlushPipeThumb; ////////////////////////////////////////////////////////////////////// // Supporting hardware functions ///////////////////////////////////// ////////////////////////////////////////////////////////////////////// function BarrelShifter(number, shiftType, shift: uint32): uint32; function cpuReadCPSR: uint32; procedure cpuWriteCPSR(value: uint32); ////////////////////////////////////////////////////////////////////// // Exception processing ////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// procedure cpuEnterException(newMode, newVector: uint32); procedure TriggerIRQ(irqType: uint32); procedure SoftwareInterrupt; procedure UndefinedOpcode; ////////////////////////////////////////////////////////////////////// // Misc. ///////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// procedure DebugTrap; procedure UndefinedState(error: string); ////////////////////////////////////////////////////////////////////// implementation /////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// // leave PC at nextAddress + L procedure FlushPipeARM; begin regs[R15] := regs[R15] and not 3; regs[PIPELINE_0] := memReadWordUnc(regs[R15]); // at nextAddress Inc(regs[R15], 4); regs[PIPELINE_1] := memReadWordUnc(regs[R15]); // at nextAddress + L end; ////////////////////////////////////////////////////////////////////// // leave PC at nextAddress + L procedure FlushPipeThumb; begin regs[R15] := regs[R15] and not 1; regs[PIPELINE_0] := memReadHalfwordUnc(regs[R15]); // at nextAddress Inc(regs[R15], 2); regs[PIPELINE_1] := memReadHalfwordUnc(regs[R15]); // at nextAddress + L end; ////////////////////////////////////////////////////////////////////// // This function emulates the barrel shifter, taking a number, shift // type, and shift amount, and returning the shifted number. It also // sets the private barrelCarry to the carry-out of the barrel shifter function BarrelShifter(number, shiftType, shift: uint32): uint32; begin case shiftType of LSL: begin // A logical shift left (LSL) takes the contents of Rm and // moves each bit by the specified amount to a more significant position. The least // significant bits of the result are filled with zeros, and the high bits of Rm which do not // map into the result are discarded, except that the least significant discarded bit // becomes the shifter carry output which may be latched into the C bit of the CPSR when // the ALU operation is in the logical class (see above). // Note LSL #0 is a special case, where the shifter carry out is the old value of the CPSR C // flag. The contents of Rm are used directly as the second operand. if shift = 0 then begin Result := number; barrelCarry := carry; end else begin Result := number shl shift; barrelCarry := number and (1 shl (32-shift)) <> 0; end; end; LSR: begin // A logical shift right (LSR) is similar, but the contents of Rm are moved to less // significant positions in the result. // // The form of the shift field which might be expected to correspond to LSR #0 is used to // encode LSR #32, which has a zero result with bit 31 of Rm as the carry output. Logical // shift right zero is redundant as it is the same as logical shift left zero, so the assembler // will convert LSR #0 (and ASR #0 and ROR #0) into LSL #0, and allow LSR #32 to be // specified. if shift = 0 then begin Result := 0; barrelCarry := number shr 31 <> 0; end else begin barrelCarry := number and (1 shl (shift-1)) <> 0; Result := number shr shift; end; end; ASR: begin // An arithmetic shift right (ASR) is similar to logical shift right, except that the high bits // are filled with bit 31 of Rm instead of zeros. This preserves the sign in 2's complement // notation. // // The form of the shift field which might be expected to give ASR #0 is used to encode // ASR #32. Bit 31 of Rm is again used as the carry output, and each bit of operand 2 is // also equal to bit 31 of Rm. The result is therefore all ones or all zeros, according to the // value of bit 31 of Rm. if (shift = 0) or (shift > 31) then begin if number shr 31 = 0 then begin Result := 0; barrelCarry := false; end else begin Result := $FFFFFFFF; barrelCarry := true; end; end else begin barrelCarry := number and (1 shl (shift-1)) <> 0; Result := number shr shift; if number shr 31 <> 0 then Result := Result or ($FFFFFFFF shl (32-shift)); end; end; ROR: begin // Rotate right (ROR) operations reuse the bits which overshoot in a logical shift right // operation by reintroducing them at the high end of the result, in place of the zeros used // to fill the high end in logical right operations. // // The form of the shift field which might be expected to give ROR #0 is used to encode // a special function of the barrel shifter, rotate right extended (RRX). This is a rotate right // by one bit position of the 33 bit quantity formed by appending the CPSR C flag to the // most significant end of the contents of Rm. Result := number; if shift = 0 then begin // Not an ROR, but a RRX, which is a ROR of one on the 33 bit register // CRm, where carry is the 33rd bit and Rm is the 1..32 bits Result := Result shr 1; if carry then Result := Result or longword(1 shl 31); barrelCarry := number and $1 <> 0; carry := barrelCarry; end else begin Result := (Result shr shift) or (Result shl (32-shift)); barrelCarry := number and (1 shl (shift-1)) <> 0; end; end; else Result := 0; end; end; ////////////////////////////////////////////////////////////////////// procedure UndefinedState(error: string); var L: uint32; begin if logInvalidStates then begin if thumbMode then L := 2 else L := 4; error := Format('[%.8x] %s at %.8x', [cpuCurrentOpcode, error, regs[R15]-L]); LogWriteLn(error); end; end; ////////////////////////////////////////////////////////////////////// procedure TriggerIRQ(irqType: uint32); begin if registers[IRQ_MASTER] and 1 <> 0 then begin if Puint16(@(registers[IRQ_ENABLED]))^ and irqType <> 0 then begin Puint16(@(registers[IRQ_FLAGS]))^ := Puint16(@(registers[IRQ_FLAGS]))^ or irqType; irqPending := true; end; end; end; ////////////////////////////////////////////////////////////////////// procedure cpuSwitchMode(newMode: byte); const // topreg, numregs, spsr modes: array[0..15, 0..2] of integer = ( (0, 0, 0), (R14_fiq, 7, SPSR_fiq), (R14_irq, 2, SPSR_irq), (R14_svc, 2, SPSR_svc), (0, 0, -1), (0, 0, -1), (0, 0, -1), (R14_abt, 2, SPSR_abt), (0, 0, -1), (0, 0, -1), (0, 0, -1), (R14_und, 2, SPSR_und), (0, 0, -1), (0, 0, -1), (0, 0, -1), (0, 0, 0)); var i: integer; t: uint32; begin if cpuMode <> newMode then begin // Cycle out the old registers for i := 14 downto 14-modes[cpuMode and $F, 1]+1 do begin t := regs[i+modes[cpuMode and $F, 0]-14]; regs[i+modes[cpuMode and $F, 0]-14] := regs[i]; regs[i] := t; end; // Cycle in the new registers for i := 14 downto 14-modes[newMode and $F, 1]+1 do begin t := regs[i+modes[newMode and $F, 0]-14]; regs[i+modes[newMode and $F, 0]-14] := regs[i]; regs[i] := t; end; // Index the SPSR register and switch modes SPSR := modes[newMode and $F, 2]; cpuMode := newMode; end; end; ////////////////////////////////////////////////////////////////////// procedure cpuEnterException(newMode, newVector: uint32); var oldCPSR: uint32; begin // Save the CPSR and switch modes if thumbMode then Inc(regs[R15], 2); oldCPSR := cpuReadCPSR; cpuSwitchMode(newMode); // Preserve the CPSR and PC in the SPSR and LR if SPSR > 0 then regs[SPSR] := oldCPSR; regs[R14] := regs[R15]; // Start executing at the new vector regs[R15] := newVector; thumbMode := false; FlushPipeARM; end; ////////////////////////////////////////////////////////////////////// procedure SoftwareInterrupt; var swic: uint32; begin // The software interrupt (SWI) instruction is used to enter // supervisor mode in a controlled fashion (similar to INT or RST, // except with a number in the opcode and a single vector) if thumbMode then swic := cpuCurrentOpcode and $FF else swic := (cpuCurrentOpcode shr 16) and $FF; // Throw a note in the log if needed if logSoftwareInterrupts then begin if swic <= LAST_SWI then LogWriteLn(Format('BIOS call to %s from $%.8x', [swiNames[swic], regs[R15]])) else LogWriteLn(Format('BIOS call to unknown function $%.2x from $%.8x', [swic, regs[R15]])); LogWriteLn(Format(' ($%.8x, $%.8x, $%.8x, $%.8x)', [regs[R0], regs[R1], regs[R2], regs[R3]])); end; if swic = $FF then begin logWriteLn('VBA style dprint (SWI #0xFF) detected. Warning: This will crash on hardware'); Exit; end; // Switch to supervisor mode after adjusting the PC for prefetch Dec(regs[R15], 4); cpuEnterException(MODE_SUPERVISOR, SWI_VECTOR); end; ////////////////////////////////////////////////////////////////////// procedure UndefinedOpcode; begin // Switch to undefined opcode mode after offering the instruction to // coprocessors for a cycle and then adjusting the PC for prefetch Dec(quota, cycI); Dec(regs[R14], 4); cpuEnterException(MODE_UNDEFINED, UNDEFINED_INST_VECTOR); end; ////////////////////////////////////////////////////////////////////// function cpuReadCPSR: uint32; begin Result := cpuMode; if thumbMode then Result := Result or SR_T; if fiqDisabled then Result := Result or SR_F; if irqDisabled then Result := Result or SR_I; if overflow then Result := Result or SR_V; if carry then Result := Result or SR_C; if zero then Result := Result or SR_Z; if negative then Result := Result or SR_N; regs[CPSR] := result; end; ////////////////////////////////////////////////////////////////////// procedure cpuWriteCPSR(value: uint32); begin thumbMode := value and SR_T <> 0; fiqDisabled := value and SR_F <> 0; irqDisabled := value and SR_I <> 0; overflow := value and SR_V <> 0; carry := value and SR_C <> 0; zero := value and SR_Z <> 0; negative := value and SR_N <> 0; cpuSwitchMode(value and $1F); end; ////////////////////////////////////////////////////////////////////// procedure DebugTrap; var addr, i: uint32; st: array[0..256] of char; begin // r0 holds $C0DED00D // r1 holds function // r2..r3 hold parameters case regs[r1] of $0: begin // dprintf, string in r2, max 256 characters i := 0; addr := regs[r2]; repeat st[i] := char(vmReadByte(addr+i)); Inc(i); until (i = 256) or (st[i-1] in [#0, #10, #13]); st[i] := #0; logWrite(PChar(@st)); end; $3: begin hitBreakpoint := true; haveFlippedThumb := true; thumbMode := not thumbMode; end; $4: Beep; $42: begin regs[r0] := $BADF00D5; regs[r1] := $DEADF00D; end; end; end; ////////////////////////////////////////////////////////////////////// end. //////////////////////////////////////////////////////////////////////