I. What kind of CPU is emulated ? At the moment we have 4 internal working emulation modules. One integer only and 3 different kinds of fpu emulations with different speed levels. Depending on our distribution strategy we may offer all 4 kinds of emulations. So we have the choice of a slightly different 60ec or a real different 60lc. Let me explain what i mean with . o 60ec The original 60ec has address translation registers which we simply *ignore*. The original 60ec doesn`t support certain 64bit div/mul and cmp2/chk2 instructions which must be emulated through the 68060.library. These are implemented. It uses certain 0xff?? opcodes for special emulation functions where a normal 60ec would run into an exception. o 60 The FPU emulation is NOT exact and not complete. The main goal here was about speed and compatible to SAS and GCC FPU programs. Another goal was to implement all 6888x opcodes to avoid going through a 68060.library emulation for the missing opcodes. The *limitations* are.. o No 96Bit fpu datatype *calculations* which means less precision but we can use the PPC`s fpu. 96Bit fpu datatypes are transformed to/from internal FPU format when loaded or stored. SAS and GCC don`t create 96bit memory datatypes anyway so that`s mostly harmless. o No FPU exceptions. Not used to my knowledge. o No .p datatypes. Not generated by any C compiler. o No nan condition tests Not generated by any C compiler. Now to the different FPU emulation types. o Level1 Uses the emulhandle->FPU[] playfield and no direct mapped FPU registers. The format of the FPU[] contents is always the native CPU format which means the emulation writes a 64bit double for a PPC and not a 96bit extended format. This is the slowest version but doesn`t need as much space. o Level2 Uses a direct 68k FPn mapping onto PPC FPU registers. Needs more space but is also faster. o Level3 The same as Level2 but without any FPU condition code tests for each instruction as SAS/GCC don`t use this. And it uses some kind of technology where most used fpu instructions are replaced with easier to decode special emulation opcodes. Level3 is roughly 100% faster than Level1 ####################################################################################### II. Let`s look at the emulation interface The emulation uses the following global register layout for integer. register struct EmulHandle *MyEmulHandle __asm("r2"); register ULONG* REG_PC __asm("r13"); register UWORD REG_SR __asm("r14"); register void (**OPCODETABLE)(void) __asm("r15"); register ULONG REG_D0 __asm("r16"); register ULONG REG_D1 __asm("r17"); register ULONG REG_D2 __asm("r18"); register ULONG REG_D3 __asm("r19"); register ULONG REG_D4 __asm("r20"); register ULONG REG_D5 __asm("r21"); register ULONG REG_D6 __asm("r22"); register ULONG REG_D7 __asm("r23"); register ULONG REG_A0 __asm("r24"); register ULONG REG_A1 __asm("r25"); register ULONG REG_A2 __asm("r26"); register ULONG REG_A3 __asm("r27"); register ULONG REG_A4 __asm("r28"); register ULONG REG_A5 __asm("r29"); register ULONG REG_A6 __asm("r30"); register ULONG REG_A7 __asm("r31"); Register R2, the EmulHandle Ptr MUST never be killed by any application. This conforms to the System V4 API which declares r2 as "reserved for system". Changing this ptr would mean that following PPC emulation calls wouldn`t be possible and that the whole emulation would crash on interrupts. It`s like using a not valid A7 in the 68k enviroment. The meaning of REG_PC and REG_SR are obvious. OPCODETABLE is the ptr to the internal 16bit instruction function table but that register shouldn`t be touched. It`s private. REG_D0..A7 should also be obvious. Now..let`s take a look at the public entries of the *current* EmulHandle structure GPR2 *always* points at. struct EmulHandle { ULONG Dn[8]; /* 0x00 */ ULONG An[8]; /* 0x20 */ ULONG *PC; /* 0x40 Current PC */ ULONG SR; /* 0x44 Statusregister */ struct SuperHandle *SuperHandle; /* 0x48 Ptr to SuperHandle */ ULONG Type; /* 0x4c EmulHandle Type */ ULONG Flags; /* 0x50 Flags */ void (*EmulFunc)(void); /* 0x54 Here is the direct Emulation Jump..you have to setup the regframes*/ ULONG (*EmulCallOS)(struct EmulCaos*); /* 0x58 Here is the Emulation Jump for an 68k OSLib Function*/ ULONG (*EmulCall68k)(struct EmulCaos*); /* 0x5c Here is the Emulation Jump for an 68k Function*/ ULONG (*EmulCallQuick68k)(void); /* 0x60 Here is the Emulation Quick Jump for an 68k Function*/ ULONG (*EmulCallDirectOS)(LONG lvo); /* 0x64 Here is the Emulation Direct Jump for a 68k OSLib Function*/ ULONG (*EmulCallDirect68k)(APTR Function); /* 0x68 Here is the Emulation Direct Jump for a 68k Function*/ ULONG Pad1; /* 0x6c */ struct Float96 FPU[8]; /* 0x70 Not yet used...*/ ULONG FPCR; /* 0xd0 Not yet used...*/ ULONG FPSR; /* 0xd4 Not yet used...*/ ULONG FPIAR; /* 0xd8 Not yet used...*/ ULONG Pad3; /* 0xdc */ /******************************************************************************************** * Private * Don`t touch * 0xe0 */ }; This is the global key to the emulation. Think of the global registers defined above as cached representation of the real 68k context stored here when necessary. That way the emulation could also work on cpus with not enough available registers. It would directly use the entries in the structure here. Ok..let`s look at the single entries here o Dn[],An[],PC,SR should be obvious o SuperHandle points to a global structure like this struct SuperHandle { ULONG USP; /* Userstack */ ULONG SSP; /* Supervisor Stack */ ULONG VBR; /* Exception Base Register */ ULONG SFC; /* SFC Register ...not really used */ ULONG DFC; /* DFC Register ...not really used */ ULONG CACR; /* Cache Control Register ...not really used */ ULONG TC; ULONG ITT0; ULONG ITT1; ULONG DTT0; ULONG DTT1; ULONG URP; ULONG SRP; ULONG BUSCR; ULONG PCR; ULONG Type; /* SuperHandle Type..not used yet */ /******************************************************************************************** * Private * Don`t touch */ }; which handles all the relevant supervisor registers for a 68060ec/68060lc o Type internal identifier for the types of the emulation. NEVER EVER write something here..this is emulation land. o Flags defines in which state the current PPC context is at the moment. #define EMULFLAGSF_PPC 0x1 /* Set when the emulation runs in full native code */ #define EMULFLAGSF_QUICK 0x2 /* Set when the emulation runs quick native code.. * which is basicly still the emul reg layout */ #define EMULFLAGSF_INTERRUPT 0x4 /* Set when the emulation runs in interrupt mode The meaning of these are explained later when we come to emulation traps and emulhandle hooks o EmulFunc FunctionPtr to the emulation start function which was entered. (This is more a private functionptr you shouldn`t really care about) o EmulCallOS FunctionPtr for PPC code to run 68k library functions. o EmulCall68k FunctionPtr for PPC code to run 68k code directly. Works the same as above... o EmulCallQuick68k FunctionPtr for PPC code to run 68k code directly. With Quick mode you tell the emulation that you pass the registers directly. Be aware that YOU are responsible for loading/restoring the registers. o EmulCallDirectOS FunctionPtr for PPC code to run 68k library functions with the 68k registers given by the current EmulHandle. o EmulCallDirect68k FunctionPtr for PPC code to run 68k code with the 68k registers given by the current EmulHandle. o The FPU fields obvious Ok..and now to the other important issue...how does 68k code interact with PPC code ? ####################################################################################### II. How to use the Emulation in programs. 1. How does 68k code interact with PPC code ? There are new 68k instructions in the range of 0xff00 and 0xffff which are defined in Some little examples... o #define TRAP_LIB 0xff00 When the emulation hits this trap it assumes the following memory layout. struct EmulLibEntry { UWORD Trap; UWORD Pad; void (*Func)(void); }; o Pad must be zeroed..for future usage:-) o Func is your PPC function The emulation saves all *integer* registers in the current EmulHandle, sets the EMULFLAGSF_PPC flag in MyEmulHandle->Flags and then calls the PPC function in EmulLibEntry->Func. The PPC function could look like this. ULONG PPC_Func(void) { void *Arg1=(void*) REG_A0; ULONG Arg2=REG_D0; return(1); } REG_#? macros are basicly MyEmulHandle->#?[] accesses. The PPC function`s result is always in gpr3 and that is moved to REG_D0 by the emulation. The emulation then does an internal RTS as such LibEntry are assumed to be called as subroutines by JSR or BSR. o #define TRAP_LIBNR 0xff05 works like above..just doesn`t change REG_D0. Useful to replace no result functions. o #define TRAP_LIB_QUICK 0xff01 If you use this trap the emulation doesn`t save any registers..it assumes that you know about its register layout and continue using it. It`s bascily an internal emulation macro expansion method. Useful for small ppc functions which are only really called by 68k code. Not suggested if the PPC function calls subfunctions as the code gcc generates then begins to look ugly because of the lack of free registers. It depends a lot on the situation if using quickmode gives you a gain. In quickmode you have *full* control over the register set so you better be careful what you`re doing. Killing A7 and SR with senseless contents would lead to a crash. You MUST not call non quick code inside of quick code as it may kill your quick register layout (temporarily). In quickmode it sets EMULFLAGSF_QUICK flag in MyEmulHandle->Flags. If you use Quickmode in *C* you set a special define to tell the C compiler about the global register layout. #define EMUL_QUICKMODE #include * Very important * PPC code runs completely transparent in the same exec task context it was called. The PPC exec does a complete PPC context save/restore each task switch. So the emulation doesn't stop multitasking when it runs into PPC code. It also doesn't need to send messages to special PPC code tasks outside of its context like its needed in the old PowerUP dual cpu system. 2. The PPC side The key to the emulation from the PPC side is the EmulCaos structure which is a stripped version of the PowerUP structure...only without the unnecessary cachefields. struct EmulCaos { union { int Offset; APTR Function; } caos_Un; ULONG reg_d0; ULONG reg_d1; ULONG reg_d2; ULONG reg_d3; ULONG reg_d4; ULONG reg_d5; ULONG reg_d6; ULONG reg_d7; ULONG reg_a0; ULONG reg_a1; ULONG reg_a2; ULONG reg_a3; ULONG reg_a4; ULONG reg_a5; /* * here you have to put the LibBasePtr if you want * to call a Library. */ ULONG reg_a6; }; You simply fill out the structure above and then Result = (*MyEmulHandle->EmulCallOS)(&MyCaos); All amigaos library functions are available as easy inline macros in and there`s a script/makefile to create macros from 3rd party libraries in and Some examples... o PPC Hooks static void DispatcherFunc(void); struct EmulLibEntry GATEDispatcherFunc= { TRAP_LIB, 0, (void (*)(void)) DispatcherFunc }; void DispatcherFunc(void) { struct IClass *cl=(struct IClass*) REG_A0; Msg msg=(Msg) REG_A1; Object *obj=(Object*) REG_A2; } DispatcherHook.h_Entry = (void*) &GATEDispatcherFunc; o PPC Interrupts #include struct IntData { struct EmulLibEntry InterruptFunc; struct Interrupt Interrupt; struct ExecBase *SysBase; }; BOOL MyInterrupt(void) { struct IntDataData *MyIntData=(struct IntData*) REG_A1; struct ExecBase *SysBase=MyIntData->SysBase; u_int8_t *PPCRegs; } MyIntData.SysBase = SysBase; MyIntData.InterruptFunc.Trap = TRAP_LIB; MyIntData.InterruptFunc.Extension = 0; MyIntData.InterruptFunc.Func =(void (*)(void)) MyInterrupt; MyIntData.Interrupt.is_Node.ln_Type = NT_INTERRUPT; MyIntData.Interrupt.is_Node.ln_Pri = 0; MyIntData.Interrupt.is_Node.ln_Name = "My int"; MyIntData.Interrupt.is_Data = &MyIntData; MyIntData.Interrupt.is_Code = (void (*)(void)) &MyIntData.InterruptFunc; AddIntServer(INTB_VERTB, &MyIntData.Interrupt); o PPC SetFunctions #include struct TagItem MyTags[]= { SETFUNCTAG_MACHINE, MACHINE_PPC, SETFUNCTAG_TYPE, SETFUNCTYPE_NORMAL, TAG_END,0 }; OldNewLoadSeg=NewSetFunction((struct Library*) DOSBase, (ULONG (*)(void)) NEW_NewLoadSeg, LVO_NewLoadSeg, &MyTags); o PPC Tasks struct TaskInitExtension TaskInitExt; struct TagItem MyTags[4]; MyTags[0].ti_Tag = TASKTAG_CODETYPE; MyTags[0].ti_Data = CODETYPE_PPC; MyTags[1].ti_Tag = TASKTAG_PC; MyTags[1].ti_Data =(ULONG) &Dev_DeviceTask; MyTags[2].ti_Tag = TASKTAG_STACKSIZE; MyTags[2].ti_Data =(ULONG) 4096; MyTags[3].ti_Tag = TAG_END; TaskInitExt.Trap = TRAP_PPCTASK; TaskInitExt.Extension = 0; TaskInitExt.Tags = &MyTags[0]; AddTask(&MyDevice->Process.pr_Task, (void (*)(void)) &TaskInitExt, NULL); o PPC Process Here the situation looks a bit different...in the future we plan a rewrite of dos createproc to extend the tags for the new ppc features. You can`t use the TaskInitExtension above because the PC ptr isn`t directly passed to the AddTask(). Solutions are these... struct EmulFunc MyEmulFunc; MyEmulFunc.Trap = TRAP_FUNC; MyEmulFunc.Address =(ULONG) ProcessFunc; MyEmulFunc.StackSize = 8192; MyEmulFunc.Extension = 0; MyEmulFunc.Arg1 =(ULONG) SysBase; MyEmulFunc.Arg2 = 0; MyEmulFunc.Arg3 = 0; MyEmulFunc.Arg4 = 0; MyEmulFunc.Arg5 = 0; MyEmulFunc.Arg6 = 0; MyEmulFunc.Arg7 = 0; MyEmulFunc.Arg8 = 0; MyTags[0].ti_Tag = NP_Entry; MyTags[0].ti_Data =(ULONG) &MyEmulFunc.Trap; MyTags[1].ti_Tag = NP_Name; MyTags[1].ti_Data =(ULONG) "My Task"; MyTags[2].ti_Tag = TAG_END; MyProcess = CreateNewProc(&MyTags[0]); or struct EmulLibEntry GATE_ProcessFunc= { TRAP_LIB, 0, (void (*)(void)) &ProcessFunc }; MyTags[0].ti_Tag = NP_Entry; MyTags[0].ti_Data =(ULONG) &GATE_ProcessFunc; MyTags[1].ti_Tag = NP_Name; MyTags[1].ti_Data =(ULONG) "My Task"; MyTags[2].ti_Tag = TAG_END; MyProcess = CreateNewProc(&MyTags[0]); !!!!!!!!!!!!!!!!!!!!! Attention !!!!!!!!!!!!!!!!!!!!!!!!!! Don`t put these structures on the stack because of the asynchronous nature of CreateNewProc. !!!!!!!!!!!!!!!!!!!!! Attention !!!!!!!!!!!!!!!!!!!!!!!!!! ####################################################################################### III. Limitations The emulation system has certain optimisations because the speed is more important than exact emulation in certain cases. o The emulation doesn`t check for odd PCs for speed issues. (Maybe i'll add that later only for rte,rts,rtd,branch,jxx) o The interrupt system works completely different than in a 68k Amiga. The interrupt vectors aren`t called at all, so software which patches itself into the interrupt vectors of the 68k isn`t compatible anymore. But legal amigaos sw doesn`t do that anyway. ####################################################################################### IV. Internal Implementation Each instruction..about 32000(with FPU a lot more) are downcoded into C. Ok..that sounds like a lot work but i fooled a bit. There`s some createtable app which has type informations about each instruction. This app is able to generate big opcode jumptable and .c files like this inst_add_88.c /* OpCode Start 0xd000 InstGroup add*/ #include "lib.h" #include "inst_add_88.h" void FUNCPRE add_b__d0_d0(FUNCPROTO) { ADD(1,d0,d0) } /**************************************************/ void FUNCPRE add_b__d1_d0(FUNCPROTO) { ADD(1,d1,d0) } . . (and so on) The creative part is only needed in the .h files and these are just huge and partly reusable macros. inst_add_88.h #define ADD(Size,SOURCEEA,DESTEA) \ /* add.b EA,dn */ \ if ((SOURCEEA>=d0) && \ (SOURCEEA<=d7)) \ { \ /* add.? dn,dn */ \ BYTE SourceByte; \ BYTE DestByte; \ BYTE ResultByte; \ UBYTE ResultUByte; \ INCPC(2); \ DestByte =(BYTE) REG_DN(DESTEA); \ SourceByte =(BYTE) REG_DN(SOURCEEA); \ ResultByte = DestByte + SourceByte; \ ResultUByte = ResultByte; \ REG_DN(DESTEA) = (REG_DN(DESTEA) & 0xffffff00) | ResultUByte; \ CC_ADDBYTE \ } \ else if (SOURCEEA==di) \ { \ /* add.b #di,dn */ \ BYTE SourceByte; \ BYTE DestByte; \ BYTE ResultByte; \ UBYTE ResultUByte; \ DestByte =(BYTE) REG_DN(DESTEA); \ SourceByte = GETDIBYTE; \ INCPC(4); \ ResultByte = DestByte + SourceByte; \ ResultUByte = ResultByte; \ REG_DN(DESTEA) = (REG_DN(DESTEA) & 0xffffff00) | ResultUByte; \ CC_ADDBYTE \ } \ else \ { \ /* add.b EA,dn */ \ void *Accu; \ BYTE SourceByte; \ BYTE DestByte; \ BYTE ResultByte; \ UBYTE ResultUByte; \ INCPC(2); \ CALCEA(Accu, \ SOURCEEA, \ 1); \ DestByte =(BYTE) REG_DN(DESTEA); \ SourceByte = *((BYTE*) Accu); \ ResultByte = DestByte + SourceByte; \ ResultUByte = ResultByte; \ REG_DN(DESTEA) = (REG_DN(DESTEA) & 0xffffff00) | ResultUByte; \ CC_ADDBYTE \ } You see that this macro has other recursive global macros(CC_ADDBYTE,CALCEA,REG_DN(),REG_AN(),...) calls. As you can see a lot in the ADD Macro is a constant compare and completely optimizes away if it`s FALSE for the current instruction. Let`s take the example of ADD(1,aa0,d0) means add.b (a0),d0 would result into something like this /* add.b EA,dn */ \ void *Accu; \ BYTE SourceByte; \ BYTE DestByte; \ BYTE ResultByte; \ UBYTE ResultUByte; \ REG_PC =(ULONG*) ((ULONG) REG_PC + 2); \ if ((aa0>=aa0) && (aa0<=aa7)) \ { \ Accu=(void*)REG_AN(aa0); \ } \ DestByte =(BYTE) REG_DN(d0); \ SourceByte = *((BYTE*) Accu); \ ResultByte = DestByte + SourceByte; \ ResultUByte = ResultByte; \ REG_DN(d0) = (REG_DN(d0) & 0xffffff00) | ResultUByte; \ CHECKCC \ { \ REG_SR = REG_SR & SR_MASK; \ if (ResultByte == 0) \ { \ REG_SR |= SRF_Z; \ } \ else if (ResultByte < 0) \ { \ REG_SR |= SRF_N; \ } \ if ( (ISNEG(SourceByte) && ISNEG(DestByte) && !ISNEG(ResultByte)) ||\ !ISNEG(SourceByte) && !ISNEG(DestByte) && ISNEG(ResultByte) ) \ { \ REG_SR |= SRF_V; \ } \ if ( (ISNEG(SourceByte) && ISNEG(DestByte)) || \ (!ISNEG(ResultByte) && ISNEG(DestByte)) || \ (ISNEG(SourceByte) && !ISNEG(ResultByte)) ) \ { \ REG_SR |= SRF_C|SRF_X; \ } \ that into /* add.b EA,dn */ \ void *Accu; \ BYTE SourceByte; \ BYTE DestByte; \ BYTE ResultByte; \ UBYTE ResultUByte; \ REG_PC =(ULONG*) ((ULONG) REG_PC + 2); \ Accu=(void*)REG_A0; \ DestByte =(BYTE) REG_DN(d0); \ SourceByte = *((BYTE*) Accu); \ ResultByte = DestByte + SourceByte; \ ResultUByte = ResultByte; \ REG_D0 = (REG_D0 & 0xffffff00) | ResultUByte; \ CHECKCC \ { \ REG_SR = REG_SR & SR_MASK; \ if (ResultByte == 0) \ { \ REG_SR |= SRF_Z; \ } \ else if (ResultByte < 0) \ { \ REG_SR |= SRF_N; \ } \ if ( (ISNEG(SourceByte) && ISNEG(DestByte) && !ISNEG(ResultByte)) ||\ !ISNEG(SourceByte) && !ISNEG(DestByte) && ISNEG(ResultByte) ) \ { \ REG_SR |= SRF_V; \ } \ if ( (ISNEG(SourceByte) && ISNEG(DestByte)) || \ (!ISNEG(ResultByte) && ISNEG(DestByte)) || \ (ISNEG(SourceByte) && !ISNEG(ResultByte)) ) \ { \ REG_SR |= SRF_C|SRF_X; \ } \ Ok..you see that all the stuff which looks huge first completly folds away. But:-)..you may still think that this CC_ADDBYTE is horrible...it sort of is and that`s why important instruction macros look like this:-) /* add.b EA,dn */ \ void *Accu; \ UBYTE SourceByte; \ UBYTE DestByte; \ UBYTE ResultUByte; \ INCPC(2); \ CALCEA(Accu, \ SOURCEEA, \ 1); \ DestByte =(UBYTE) REG_DN(DESTEA); \ SourceByte = *((UBYTE*) Accu); \ ADDBYTE \ REG_DN(DESTEA) = (REG_DN(DESTEA) & 0xffffff00) | ResultUByte; \ where the macro ADDBYTE is a gcc asm macro which does the add+condition code thing a lot more efficient as the stuff above. But if you look at the way the whole thing is structured it`s possible to configurate it for other cpus as it`s still only C with some performance asm tweaks. It takes more than 3 hours to compile the whole src on a 66mhz 060.