From: notaz Date: Sun, 5 Aug 2007 18:21:40 +0000 (+0000) Subject: 0.0088 release X-Git-Tag: v1.85~701 X-Git-Url: https://notaz.gp2x.de/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0e11c502b05a9ccd67d34d1f0975852d8c3d4232;p=picodrive.git 0.0088 release git-svn-id: file:///home/notaz/opt/svn/PicoDrive@215 be3aeb3a-fb24-0410-a615-afba39da0efa --- diff --git a/cpu/Cyclone/Cyclone.h b/cpu/Cyclone/Cyclone.h index 6bce6710..6e7ce02f 100644 --- a/cpu/Cyclone/Cyclone.h +++ b/cpu/Cyclone/Cyclone.h @@ -1,12 +1,16 @@ // Cyclone 68000 Emulator - Header File -// Most code (c) Copyright 2004 Dave, All rights reserved. -// Some coding/bugfixing was done by notaz +// (c) Copyright 2004 Dave, All rights reserved. +// (c) 2005-2007 notaz // Cyclone 68000 is free for non-commercial use. // For commercial use, separate licencing terms must be obtained. + +#ifndef __CYCLONE_H__ +#define __CYCLONE_H__ + #ifdef __cplusplus extern "C" { #endif @@ -17,46 +21,74 @@ struct Cyclone { unsigned int d[8]; // [r7,#0x00] unsigned int a[8]; // [r7,#0x20] - unsigned int pc; // [r7,#0x40] Memory Base+PC + unsigned int pc; // [r7,#0x40] Memory Base (.membase) + 68k PC unsigned char srh; // [r7,#0x44] Status Register high (T_S__III) unsigned char unused; // [r7,#0x45] Unused unsigned char flags; // [r7,#0x46] Flags (ARM order: ____NZCV) [68k order is XNZVC] unsigned char irq; // [r7,#0x47] IRQ level unsigned int osp; // [r7,#0x48] Other Stack Pointer (USP/SSP) unsigned int xc; // [r7,#0x4c] Extend flag (bit29: ??X? _) - unsigned int prev_pc; // [r7,#0x50] set to start address of currently executed opcode + 2 (if enabled in config.h) - unsigned int unused1; // [r7,#0x54] Unused - int stopped; // [r7,#0x58] 1 == processor is in stopped state - int cycles; // [r7,#0x5c] + unsigned int prev_pc; // [r7,#0x50] Set to start address of currently executed opcode + 2 (if enabled in config.h) + unsigned int reserved;// [r7,#0x54] Reserved for possible future use + int state_flags; // [r7,#0x58] bit: 0: stopped state, 1: trace state, 2: activity bit, 3: addr error, 4: fatal halt + int cycles; // [r7,#0x5c] Number of cycles to execute - 1. Updates to cycles left after CycloneRun() int membase; // [r7,#0x60] Memory Base (ARM address minus 68000 address) - unsigned int (*checkpc)(unsigned int pc); // [r7,#0x64] - Called to recalc Memory Base+pc - unsigned char (*read8 )(unsigned int a); // [r7,#0x68] - unsigned short (*read16 )(unsigned int a); // [r7,#0x6c] - unsigned int (*read32 )(unsigned int a); // [r7,#0x70] + unsigned int (*checkpc)(unsigned int pc); // [r7,#0x64] called to recalc Memory Base+pc + unsigned int (*read8 )(unsigned int a); // [r7,#0x68] + unsigned int (*read16 )(unsigned int a); // [r7,#0x6c] + unsigned int (*read32 )(unsigned int a); // [r7,#0x70] void (*write8 )(unsigned int a,unsigned char d); // [r7,#0x74] void (*write16)(unsigned int a,unsigned short d); // [r7,#0x78] void (*write32)(unsigned int a,unsigned int d); // [r7,#0x7c] - unsigned char (*fetch8 )(unsigned int a); // [r7,#0x80] - unsigned short (*fetch16)(unsigned int a); // [r7,#0x84] - unsigned int (*fetch32)(unsigned int a); // [r7,#0x88] - void (*IrqCallback)(int int_level); // [r7,#0x8c] - optional irq callback function, see config.h - void (*ResetCallback)(void); // [r7,#0x90] - if enabled in config.h, calls this whenever RESET opcode is encountered. - int (*UnrecognizedCallback)(void); // [r7,#0x94] - if enabled in config.h, calls this whenever unrecognized opcode is encountered. + unsigned int (*fetch8 )(unsigned int a); // [r7,#0x80] + unsigned int (*fetch16)(unsigned int a); // [r7,#0x84] + unsigned int (*fetch32)(unsigned int a); // [r7,#0x88] + int (*IrqCallback)(int int_level); // [r7,#0x8c] optional irq callback function, see config.h + void (*ResetCallback)(void); // [r7,#0x90] if enabled in config.h, calls this whenever RESET opcode is encountered. + int (*UnrecognizedCallback)(void); // [r7,#0x94] if enabled in config.h, calls this whenever unrecognized opcode is encountered. + unsigned int internal[6]; // [r7,#0x98] reserved for internal use, do not change. }; -// used only if Cyclone was compiled with compressed jumptable, see config.h -void CycloneInit(); +// Initialize. Used only if Cyclone was compiled with compressed jumptable, see config.h +void CycloneInit(void); -// run cyclone. Cycles should be specified in context (pcy->cycles) +// Run cyclone. Cycles should be specified in context (pcy->cycles) void CycloneRun(struct Cyclone *pcy); -// utility functions to get and set SR -void CycloneSetSr(struct Cyclone *pcy, unsigned int sr); // auto-swaps a7<->osp if detects supervisor change -unsigned int CycloneGetSr(struct Cyclone *pcy); +// Utility functions to get and set SR +void CycloneSetSr(struct Cyclone *pcy, unsigned int sr); +unsigned int CycloneGetSr(const struct Cyclone *pcy); + +// Generates irq exception if needed (if pcy->irq > mask). +// Returns cycles used for exception if it was generated, 0 otherwise. +int CycloneFlushIrq(struct Cyclone *pcy); + +// Functions for saving and restoring state. +// CycloneUnpack() uses checkpc(), so it must be initialized. +// save_buffer must point to buffer of 128 (0x80) bytes of size. +void CyclonePack(const struct Cyclone *pcy, void *save_buffer); +void CycloneUnpack(struct Cyclone *pcy, const void *save_buffer); // genesis: if 1, switch to normal TAS handlers void CycloneSetRealTAS(int use_real); + +// These values are special return values for IrqCallback. + +// Causes an interrupt autovector (0x18 + interrupt level) to be taken. +// This happens in a real 68K if VPA or AVEC is asserted during an interrupt +// acknowledge cycle instead of DTACK (the most common situation). +#define CYCLONE_INT_ACK_AUTOVECTOR -1 + +// Causes the spurious interrupt vector (0x18) to be taken +// This happens in a real 68K if BERR is asserted during the interrupt +// acknowledge cycle (i.e. no devices responded to the acknowledge). +#define CYCLONE_INT_ACK_SPURIOUS -2 + + #ifdef __cplusplus } // End of extern "C" #endif + +#endif // __CYCLONE_H__ + diff --git a/cpu/Cyclone/Cyclone.txt b/cpu/Cyclone/Cyclone.txt index 8a68e16e..a452f159 100644 --- a/cpu/Cyclone/Cyclone.txt +++ b/cpu/Cyclone/Cyclone.txt @@ -18,224 +18,76 @@ ___________________________________________________________________________ Replace (atsymbol) with @ Additional coding and bugfixes done by notaz, 2005-2007 - Homepage: http://mif.vu.lt/~grig2790/Cyclone/ , http://notaz.gp2x.de + Homepage: http://notaz.gp2x.de e-mail: notasas(atsymbol)gmail.com ___________________________________________________________________________ -What is it? ------------ +About +----- Cyclone 68000 is an emulator for the 68000 microprocessor, written in ARM 32-bit assembly. It is aimed at chips such as ARM7 and ARM9 cores, StrongARM and XScale, to interpret 68000 -code as fast as possible. +code as fast as possible. It can emulate all 68000 instructions quite accurately, instruction +timing was synchronized with MAME's Musashi. Most 68k features are emulated (trace mode, +address errors), but prefetch is not emulated. -Flags are mapped onto ARM flags whenever possible, which speeds up the processing of opcode. - - -What's New ----------- -v0.0087 notaz - - Reduced amount of code in opcode handlers by ~23% by doing the following: - - Removed duplicate opcode handlers - - Optimized code to use less ARM instructions - - Merged some duplicate handler endings - + Cyclone now does better job avoiding pipeline interlocks. - + Replaced incorrect handler of DBT with proper one. - + Changed "MOVEA (An)+ An" behaviour. - + Fixed flag behaviour of ROXR, ASL, LSR and NBCD in certain situations. - Hopefully got them right now. - + Cyclone no longer sets most significant bits while pushing PC to stack. - Amiga Kickstart depends on this. - + Additional functionality added for MAME and other ports (see config.h). - -v0.0086 notaz - + Cyclone now can be customized to better suit your project, see config.h . - + Added an option to compress the jumptable at compile-time. Must call CycloneInit() - at runtime to decompress it if enabled (see config.h). - + Added missing CHK opcode handler (used by SeaQuest DSV). - + Added missing TAS opcode handler (Gargoyles,Bubba N Stix,...). As in real genesis, - memory write-back phase is ignored (but can be enabled in config.h if needed). - + Added missing NBCD and TRAPV opcode handlers. - + Added missing addressing mode for CMP/EOR. - + Added some minor optimizations. - - Removed 216 handlers for 2927 opcodes which were generated for invalid addressing modes. - + Fixed flags for ASL, NEG, NEGX, DIVU, ADDX, SUBX, ROXR. - + Bugs fixed in MOVEP, LINK, ADDQ, DIVS handlers. - * Undocumented flags for CHK, ABCD, SBCD and NBCD are now emulated the same way as in Musashi. - + Added Uninitialized Interrupt emulation. - + Altered timing for about half of opcodes to match Musashi's. - -v0.0082 Reesy - + Change cyclone to clear cycles before returning when halted - + Added Irq call back function. This allows emulators to be notified - when cyclone has taken an interrupt allowing them to set internal flags - which can help fix timing problems. - -v0.0081 notaz - + .asm version was broken and did not compile with armasm. Fixed. - + Finished implementing Stop opcode. Now it really stops the processor. - -v0.0080 notaz - + Added real cmpm opcode, it was using eor handler before this. - Fixes Dune and Sensible Soccer. - -v0.0078 notaz - note: these bugs were actually found Reesy, I reimplemented these by - using his changelog as a guide. - + Fixed a problem with divu which was using long divisor instead of word. - Fixes gear switching in Top Gear 2. - + Fixed btst opcode, The bit to test should shifted a max of 31 or 7 - depending on if a register or memory location is being tested. - + Fixed abcd,sbcd. They did bad decimal correction on invalid BCD numbers - Score counters in Streets of Rage level end work now. - + Changed flag handling of abcd,sbcd,addx,subx,asl,lsl,... - Some ops did not have flag handling at all. - Some ops must not change Z flag when result is zero, but they did. - Shift ops must not change X if shift count is zero, but they did. - There are probably still some flag problems left. - + Patially implemented Stop and Reset opcodes - Fixes Thunderforce IV - -v0.0075 notaz - + Added missing displacement addressing mode for movem (Fantastic Dizzy) - + Added OSP <-> A7 swapping code in opcodes, which change privilege mode - + Implemented privilege violation, line emulator and divide by zero exceptions - + Added negx opcode (Shining Force works!) - + Added overflow detection for divs/divu - -v0.0072 notaz - note: I could only get v0.0069 cyclone, so I had to implement these myself using Dave's - changelog as a guide. - + Fixed a problem with divs - remainder should be negative when divident is negative - + Added movep opcode (Sonic 3 works) - + Fixed a problem with DBcc incorrectly decrementing if the condition is true (Shadow of the Beast) - -v0.0069 - + Added SBCD and the flags for ABCD/SBCD. Score and time now works in games such as - Rolling Thunder 2, Ghouls 'N Ghosts - + Fixed a problem with addx and subx with 8-bit and 16-bit values. - Ghouls 'N' Ghosts now works! - -v0.0068 - + Added ABCD opcode (Streets of Rage works now!) - -v0.0067 - + Added dbCC (After Burner) - + Added asr EA (Sonic 1 Boss/Labyrinth Zone) - + Added andi/ori/eori ccr (Altered Beast) - + Added trap (After Burner) - + Added special case for move.b (a7)+ and -(a7), stepping by 2 - After Burner is playable! Eternal Champions shows more - + Fixed lsr.b/w zero flag (Ghostbusters) - Rolling Thunder 2 now works! - + Fixed N flag for .b and .w arithmetic. Golden Axe works! - -v0.0066 - + Fixed a stupid typo for exg (orr r10,r10, not orr r10,r8), which caused alignment - crashes on Strider - -v0.0065 - + Fixed a problem with immediate values - they weren't being shifted up correctly for some - opcodes. Spiderman works, After Burner shows a bit of graphics. - + Fixed a problem with EA:"110nnn" extension word. 32-bit offsets were being decoded as 8-bit - offsets by mistake. Castlevania Bloodlines seems fine now. - + Added exg opcode - + Fixed asr opcode (Sonic jumping left is fixed) - + Fixed a problem with the carry bit in rol.b (Marble Madness) - -v0.0064 - + Added rtr - + Fixed addq/subq.l (all An opcodes are 32-bit) (Road Rash) - + Fixed various little timings - -v0.0063 - + Added link/unlk opcodes - + Fixed various little timings - + Fixed a problem with dbCC opcode being emitted at set opcodes - + Improved long register access, the EA fetch now does ldr r0,[r7,r0,lsl #2] whenever - possible, saving 1 or 2 cycles on many opcodes, which should give a nice speed up. - + May have fixed N flag on ext opcode? - + Added dasm for link opcode. - -v0.0062 - * I was a bit too keen with the Arithmetic opcodes! Some of them should have been abcd, - exg and addx. Removed the incorrect opcodes, pending re-adding them as abcd, exg and addx. - + Changed unknown opcodes to act as nops. - Not very technical, but fun - a few more games show more graphics ;) -v0.0060 - + Fixed divu (EA intro) - + Added sf (set false) opcode - SOR2 - * Todo: pea/link/unlk opcodes - -v0.0059: Added remainder to divide opcodes. - - -The new stuff -------------- - -Before using Cyclone, be sure to customize config.h to better suit your project. All options -are documented inside that file. +How to Compile +-------------- -IrqCallback has been changed a bit, unlike in previous version, it should not return anything. -If you need to change IRQ level, you can safely do that in your handler. +Like Starscream and A68K, Cyclone uses a 'Core Creator' program which calculates and outputs +all possible 68000 Opcodes and a jump table into file called Cyclone.s or Cyclone.asm. +Only Cyclone.h and the mentioned .s or .asm file will be needed for your project, other files +are here to produce or test it. -Cyclone has changed quite a bit from the time when Dave stopped updating it, but the rest of -documentation still applies, so read it if you haven't done that yet. If you have, check the -"Accessing ..." parts. +First unzip "Cyclone.zip" into a "Cyclone" directory. The next thing to do is to edit config.h +file to tune Cyclone for your project. There are lots of options in config.h, but all of them +are documented and have defaults. You should set a define value to 1 to enable option, and +to 0 to disable. +After you are done with config.h, save it and compile Cyclone. If you are using Linux, Cygwin, +mingw or similar, you can simply cd to Cyclone/proj and type "make". If you are under Windows +and have Visual Studio installed, you can import cyclone.dsp in the proj/ directory and compile +it from there (this will produce cyclone.exe which you will have to run to get .s or .asm). +You can also use Microsoft command line compile tools by entering Cyclone/proj directory and +typing "nmake -f Makefile.win". Note that this step is done only to produce .s or .asm, and it +is done using native tools on your PC (not using cross-compiler or similar). -ARM Register Usage ------------------- +The .s file is meant to be compiled with GNU assembler, and .asm with ARMASM.EXE +(the Microsoft ARM assembler). Once you have the file, you can add it to your +Makefile/project/whatever. -See source code for up to date of register usage, however a summary is here: - r0-3: Temporary registers - r4 : Current PC + Memory Base (i.e. pointer to next opcode) - r5 : Cycles remaining - r6 : Pointer to Opcode Jump table - r7 : Pointer to Cpu Context - r8 : Current Opcode - r9 : Flags (NZCV) in highest four bits - (r10 : Temporary source value or Memory Base) - (r11 : Temporary register) - - -How to Compile --------------- +Adding to your project +---------------------- -Like Starscream and A68K, Cyclone uses a 'Core Creator' program which calculates and outputs -all possible 68000 Opcodes and a jump table into files called Cyclone.s and .asm -It then assembles these files into Cyclone.o and .obj +Compiling the .s or .asm (from previous step) for your target platform may require custom +build rules in your Makefile/project. -Cyclone.o is the GCC assembled version and Cyclone.obj is the Microsoft assembled version. +If you use some gcc-based toolchain, you will need to add Cyclone.o to an object list in +the Makefile. GNU make will use "as" to build Cyclone.o from Cyclone.s by default, so +you may need to define correct cross-assembler by setting AS variable like this: -First unzip "Cyclone.zip" into a "Cyclone" directory. -If you are compiling for Windows CE, find ARMASM.EXE (the Microsoft ARM assembler) and -put it in the directory as well or put it on your path. +AS = arm-linux-as -Open up Cyclone.dsw in Visual Studio 6.0, compile and run the project. -Cyclone.obj and Cyclone.o will be created. +This might be different in your case, basically it should be same prefix as for gcc. +You may also need to specify floating point type in your assembler flags for Cyclone.o +to link properly. This is done like this: +ASFLAGS = -mfloat-abi=soft -Compiling without Visual C++ ----------------------------- -If you aren't using Visual C++, it still shouldn't be too hard to compile, just get a C compiler, -compile all the CPPs and C file, link them into an EXE, and run the exe. +Note that Cyclone does not use floating points, this is just to make the linker happy. - e.g. gcc Main.cpp OpAny.cpp OpArith.cpp OpBranch.cpp OpLogic.cpp OpMove.cpp Disa.c - Main.exe - -Adding to your project ----------------------- - -To add Cyclone to you project, add Cyclone.o or obj, and include Cyclone.h -There is one structure: 'struct Cyclone', and one function: CycloneRun +If you are using Visual Studio, you may need to add "custom build step", which creates +Cyclone.obj from Cyclone.asm (asmasm.exe Cyclone.asm). Alternatively you can create +Cyclone.obj by using armasm once and then just add it to you project. Don't worry if this seem very minimal - its all you need to run as many 68000s as you want. It works with both C and C++. + Byteswapped Memory ------------------ @@ -243,7 +95,7 @@ If you have used Starscream, A68K or Turbo68K or similar emulators you'll be fam Any memory which the 68000 can access directly must be have every two bytes swapped around. This is to speed up 16-bit memory accesses, because the 68000 has Big-Endian memory -and ARM has Little-Endian memory. +and ARM has Little-Endian memory (in most cases). Now you may think you only technically have to byteswap ROM, not RAM, because 16-bit RAM reads go through a memory handler and you could just return (mem[a]<<8) | mem[a+1]. @@ -285,16 +137,17 @@ For example you might set MyRead8 like this: The other 5 read/write functions are similar. I'll describe the CheckPc function later on. + Declaring a CPU Context ----------------------- -To declare a CPU simple declare a struct Cyclone in your code. For example to declare -two 68000s: +To declare a CPU simple declare a struct Cyclone in your code (don't forget to include Cyclone.h). +For example to declare two 68000s: struct Cyclone MyCpu; struct Cyclone MyCpu2; -It's probably a good idea to initialise the memory to zero: +It's probably a good idea to initialize the memory to zero: memset(&MyCpu, 0,sizeof(MyCpu)); memset(&MyCpu2,0,sizeof(MyCpu2)); @@ -321,7 +174,9 @@ point them at the read handlers: If this is the case, you can set up different functions for fetch reads. Generally though you don't need to. ) -Now you are nearly ready to reset the 68000, except you need one more function: checkpc(). +Now you are nearly ready to reset the 68000, except a few more functions, +one of them is: checkpc(). + The checkpc() function ---------------------- @@ -332,7 +187,7 @@ For example if your Rom image was at 0x3000000 and the program counter was $206, Cyclone's program counter would be 0x3000206. The difference between an ARM address and a 68000 address is also stored in a variable called -'membase'. In the above example it's 0x3000000. To retrieve the real PC, Cyclone just +'membase'. In the above example it's 0x3000000. To retrieve the real 68k PC, Cyclone just subtracts 'membase'. When a long jump happens, Cyclone calls checkpc(). If the PC is in a different bank, @@ -351,20 +206,32 @@ static int MyCheckPc(unsigned int pc) Notice that the membase is always ARM address minus 68000 address. The above example doesn't consider mirrored ram, but for an example of what to do see -PicoDrive (in Memory.cpp). +PicoDrive (in Memory.c). + +The exact cases when checkpc() is called can be configured in config.h. + + +Initialization +-------------- + +Add a call to CycloneInit(). This is really only needed to be called once at startup +if you enabled COMPRESS_JUMPTABLE in config.h, but you can add this in any case, +it won't hurt. Almost there - Reset the 68000! ------------------------------- -Next we need to Reset the 68000 to get the initial Program Counter and Stack Pointer. This -is obtained from addresses 000000 and 000004. +Cyclone doesn't provide a reset function, so next we need to Reset the 68000 to get +the initial Program Counter and Stack Pointer. This is obtained from addresses +000000 and 000004. Here is code which resets the 68000 (using your memory handlers): + MyCpu.state_flags=0; // Go to default state (not stopped, halted, etc.) MyCpu.srh=0x27; // Set supervisor mode MyCpu.a[7]=MyCpu.read32(0); // Get Stack Pointer - MyCpu.membase=0; + MyCpu.membase=0; // Will be set by checkpc() MyCpu.pc=MyCpu.checkpc(MyCpu.read32(4)); // Get Program Counter And that's ready to go. @@ -381,7 +248,7 @@ e.g.: MyCpu.cycles=1000; CycloneRun(&MyCpu); For each opcode, the number of cycles it took is subtracted and the function returns when -it reaches 0. +it reaches negative number. The result is stored back to MyCpu.cycles. e.g. // Execute one instruction on the 68000: @@ -415,39 +282,48 @@ e.g: Note that the interrupt is not actually processed until the next call to CycloneRun, and the interrupt may not be taken until the 68000 interrupt mask is changed to allow it. -( The IRQ isn't checked on exiting from a memory handler: I don't think this will cause - me any trouble because I've never needed to trigger an interrupt from a memory handler, - but if someone needs to, let me know...) +If you need to force interrupt processing, you can use CycloneFlushIrq() function. +It is the same as doing +MyCpu.cycles=0; CycloneRun(&MyCpu); -Accessing Cycle Counter ------------------------ +but is better optimized and doesn't update .cycles (returns them instead). +This function can't be used from memory handlers and has no effect if interrupt is masked. -The cycle counter in the Cyclone structure is not, by default, updated before -calling a memory handler, only at the end of an execution. +The IRQ isn't checked on exiting from a memory handler. If you need to cause interrupt +check immediately, you should change cycle counter to 0 to cause a return from CycloneRun(), +and then call CycloneRun() again or just call CycloneFlushIrq(). Note that you need to +enable MEMHANDLERS_CHANGE_CYCLES in config.h for this to work. -*update* -Now this is configurable in config.h, there is no 'debug' variable. +If you need to do something during the interrupt acknowledge (the moment when interrupt +is taken), you can set USE_INT_ACK_CALLBACK in config.h and specify IrqCallback function. +This function should update the IRQ level (.irq variable in context) and return the +interrupt vector number. But for most cases it should return special constant +CYCLONE_INT_ACK_AUTOVECTOR so that Cyclone uses autovectors, which is what most real +systems were doing. Another less commonly used option is to return CYCLONE_INT_ACK_SPURIOUS +for spurious interrupt. Accessing Program Counter and registers --------------------------------------- -You can read Cyclone's registers directly from the structure at any time (as far as I know). +You can read most Cyclone's registers directly from the structure at any time. +However, the PC value, CCR and cycle counter are cached in ARM registers and can't +be accessed from memory handlers by default. They are written back and can be +accessed after execution. + +But if you need to access the mentioned registers during execution, you can set +MEMHANDLERS_NEED_* and MEMHANDLERS_CHANGE_* options in config.h The Program Counter, should you need to read or write it, is stored with membase added on. So use this formula to calculate the real 68000 program counter: pc = MyCpu.pc - MyCpu.membase; -The program counter is stored in r4 during execution, and isn't written back to the -structure until the end of execution, which means you can't read normally real it from -a memory handler. - -*update* -Now this is configurable in config.h, there is no 'debug' variable. You can even enable -access to SR if you need. However changing PC in memhandlers is still not safe, you should -better clear cycles, wait untill CycloneRun() returns and then do whatever you need. +For performance reasons Cyclone keeps the status register split into .srh +(status register "high" supervisor byte), .xc for the X flag, and .flags for remaining +CCR flags (in ARM order). To easily read/write the status register as normal 68k +16bit SR register, use CycloneGetSr() and CycloneSetSr() utility functions. Emulating more than one CPU @@ -466,22 +342,280 @@ e.g. MyCpu2.cycles=1000; CycloneRun(&MyCpu2); +Quick API reference +------------------- + +void CycloneInit(void); + Initializes Cyclone. Must be called if the jumptable is compressed, + doesn't matter otherwise. + +void CycloneRun(struct Cyclone *pcy); + Runs cyclone for pcy->cycles. Writes amount of cycles left back to + pcy->cycles (always negative). + +unsigned int CycloneGetSr(const struct Cyclone *pcy); + Reads status register in internal form from pcy, converts to standard 68k SR and returns it. + +void CycloneSetSr(struct Cyclone *pcy, unsigned int sr); + Takes standard 68k status register (sr), and updates Cyclone context with it. + +int CycloneFlushIrq(struct Cyclone *pcy); + If .irq is greater than IRQ mask in SR, or it is equal to 7 (NMI), processes interrupt + exception and returns number of cycles used. Otherwise, does nothing and returns 0. + +void CyclonePack(const struct Cyclone *pcy, void *save_buffer); + Writes Cyclone state to save_buffer. This allows to avoid all the trouble figuring what + actually needs to be saved from the Cyclone structure, as saving whole struct Cyclone + to a file will also save various pointers, which may become invalid after your program + is restarted, so simply reloading the structure will cause a crash. save_buffer size + should be 128 bytes (now it is really using less, but this allows future expansion). + +void CycloneUnpack(struct Cyclone *pcy, const void *save_buffer); + Reloads Cyclone state from save_buffer, which was previously saved by CyclonePack(). + This function uses checkpc() callback to rebase the PC, so .checkpc must be initialized + before calling it. + +Callbacks: + +.checkpc +unsigned int (*checkpc)(unsigned int pc); + This function is called when PC changes are performed in 68k code or because of exceptions. + It is passed ARM pointer and should return ARM pointer casted to int. It must also update + .membase if needed. See "The checkpc() function" section above. + +unsigned int (*read8 )(unsigned int a); +unsigned int (*read16 )(unsigned int a); +unsigned int (*read32 )(unsigned int a); + These are the read memory handler callbacks. They are called when 68k code reads from memory. + The parameter is a 68k address in data space, return value is a data value read. Data value + doesn't have to be masked to 8 or 16 bits for read8 or read16, Cyclone will do that itself + if needed. + +unsigned int (*fetch8 )(unsigned int a); +unsigned int (*fetch16)(unsigned int a); +unsigned int (*fetch32)(unsigned int a); + Same as above, but these are reads from program space (PC relative reads mostly). + +void (*write8 )(unsigned int a,unsigned char d); +void (*write16)(unsigned int a,unsigned short d); +void (*write32)(unsigned int a,unsigned int d); + These are called when 68k code writes to data space. d is the data value. + +int (*IrqCallback)(int int_level); + This function is called when Cyclone acknowledges an interrupt. The parameter is the IRQ + level being acknowledged, and return value is exception vector to use, or one of these special + values: CYCLONE_INT_ACK_AUTOVECTOR or CYCLONE_INT_ACK_SPURIOUS. Can be disabled in config.h. + See "Interrupts" section for more information. + +void (*ResetCallback)(void); + Cyclone will call this function if it encounters RESET 68k instruction. + Can be disabled in config.h. + +int (*UnrecognizedCallback)(void); + Cyclone will call this function if it encounters illegal instructions (including A-line and + F-line ones). Can be tuned / disabled in config.h. + + +Function codes +-------------- + +Cyclone doesn't pass function codes to it's memory handlers, but they can be calculated: +FC2: just use supervisor state bit from status register (eg. (MyCpu.srh & 0x20) >> 5) +FC1: if we are in fetch* function, then 1, else 0. +FC0: if we are in read* or write*, then 1, else 0. +CPU state (all FC bits set) is active in IrqCallback function. + + +References +---------- + +These documents were used while writing Cyclone and should be useful for those who want to +understand deeper how the 68000 works. + +MOTOROLA M68000 FAMILY Programmer's Reference Manual +common name: 68kPM.pdf + +M68000 8-/16-/32-Bit Microprocessors User's Manual +common name: MC68000UM.pdf + +68000 Undocumented Behavior Notes by Bart Trzynadlowski +http://www.trzy.org/files/68knotes.txt + +Instruction prefetch on the Motorola 68000 processor by Jorge Cwik +http://pasti.fxatari.com/68kdocs/68kPrefetch.html + + +ARM Register Usage +------------------ + +See source code for up to date of register usage, however a summary is here: + + r0-3: Temporary registers + r4 : Current PC + Memory Base (i.e. pointer to next opcode) + r5 : Cycles remaining + r6 : Pointer to Opcode Jump table + r7 : Pointer to Cpu Context + r8 : Current Opcode + r9 : Flags (NZCV) in highest four bits + (r10 : Temporary source value or Memory Base) + (r11 : Temporary register) + +Flags are mapped onto ARM flags whenever possible, which speeds up the processing of opcode. + + Thanks to... ------------ * All the previous code-generating assembler cpu core guys! Who are iirc... Neill Corlett, Neil Bradley, Mike Coates, Darren Olafson - and Bart Trzynadlowski + Karl Stenerud and Bart Trzynadlowski * Charles Macdonald, for researching just about every console ever * MameDev+FBA, for keeping on going and going and going -------------- +What's New +---------- +v0.0088 notaz + - Reduced amount of code in opcode handlers by ~23% by doing the following: + - Removed duplicate opcode handlers + - Optimized code to use less ARM instructions + - Merged some duplicate handler endings + + Cyclone now does better job avoiding pipeline interlocks. + + Replaced incorrect handler of DBT with proper one. + + Changed "MOVEA (An)+ An" behavior. + + Fixed flag behavior of ROXR, ASL, LSR and NBCD in certain situations. + Hopefully got them right now. + + Cyclone no longer sets most significant bits while pushing PC to stack. + Amiga Kickstart depends on this. + + Added optional trace mode emulation. + + Added optional address error emulation. + + Additional functionality added for MAME and other ports (see config.h). + + Added return value for IrqCallback to make it suitable for emulating devices which + pass the vector number during interrupt acknowledge cycle. For usual autovector + processing this function must return CYCLONE_INT_ACK_AUTOVECTOR, so those who are + upgrading must add "return CYCLONE_INT_ACK_AUTOVECTOR;" to their IrqCallback functions. + * Updated documentation. + +v0.0086 notaz + + Cyclone now can be customized to better suit your project, see config.h . + + Added an option to compress the jumptable at compile-time. Must call CycloneInit() + at runtime to decompress it if enabled (see config.h). + + Added missing CHK opcode handler (used by SeaQuest DSV). + + Added missing TAS opcode handler (Gargoyles,Bubba N Stix,...). As in real genesis, + memory write-back phase is ignored (but can be enabled in config.h if needed). + + Added missing NBCD and TRAPV opcode handlers. + + Added missing addressing mode for CMP/EOR. + + Added some minor optimizations. + - Removed 216 handlers for 2927 opcodes which were generated for invalid addressing modes. + + Fixed flags for ASL, NEG, NEGX, DIVU, ADDX, SUBX, ROXR. + + Bugs fixed in MOVEP, LINK, ADDQ, DIVS handlers. + * Undocumented flags for CHK, ABCD, SBCD and NBCD are now emulated the same way as in Musashi. + + Added Uninitialized Interrupt emulation. + + Altered timing for about half of opcodes to match Musashi's. + +v0.0082 Reesy + + Change cyclone to clear cycles before returning when halted + + Added Irq call back function. This allows emulators to be notified + when cyclone has taken an interrupt allowing them to set internal flags + which can help fix timing problems. + +v0.0081 notaz + + .asm version was broken and did not compile with armasm. Fixed. + + Finished implementing Stop opcode. Now it really stops the processor. + +v0.0080 notaz + + Added real cmpm opcode, it was using eor handler before this. + Fixes Dune and Sensible Soccer. + +v0.0078 notaz + note: these bugs were actually found Reesy, I reimplemented these by + using his changelog as a guide. + + Fixed a problem with divu which was using long divisor instead of word. + Fixes gear switching in Top Gear 2. + + Fixed btst opcode, The bit to test should shifted a max of 31 or 7 + depending on if a register or memory location is being tested. + + Fixed abcd,sbcd. They did bad decimal correction on invalid BCD numbers + Score counters in Streets of Rage level end work now. + + Changed flag handling of abcd,sbcd,addx,subx,asl,lsl,... + Some ops did not have flag handling at all. + Some ops must not change Z flag when result is zero, but they did. + Shift ops must not change X if shift count is zero, but they did. + There are probably still some flag problems left. + + Patially implemented Stop and Reset opcodes - Fixes Thunderforce IV + +v0.0075 notaz + + Added missing displacement addressing mode for movem (Fantastic Dizzy) + + Added OSP <-> A7 swapping code in opcodes, which change privilege mode + + Implemented privilege violation, line emulator and divide by zero exceptions + + Added negx opcode (Shining Force works!) + + Added overflow detection for divs/divu + +v0.0072 notaz + note: I could only get v0.0069 cyclone, so I had to implement these myself using Dave's + changelog as a guide. + + Fixed a problem with divs - remainder should be negative when divident is negative + + Added movep opcode (Sonic 3 works) + + Fixed a problem with DBcc incorrectly decrementing if the condition is true (Shadow of the Beast) + +v0.0069 + + Added SBCD and the flags for ABCD/SBCD. Score and time now works in games such as + Rolling Thunder 2, Ghouls 'N Ghosts + + Fixed a problem with addx and subx with 8-bit and 16-bit values. + Ghouls 'N' Ghosts now works! + +v0.0068 + + Added ABCD opcode (Streets of Rage works now!) + +v0.0067 + + Added dbCC (After Burner) + + Added asr EA (Sonic 1 Boss/Labyrinth Zone) + + Added andi/ori/eori ccr (Altered Beast) + + Added trap (After Burner) + + Added special case for move.b (a7)+ and -(a7), stepping by 2 + After Burner is playable! Eternal Champions shows more + + Fixed lsr.b/w zero flag (Ghostbusters) + Rolling Thunder 2 now works! + + Fixed N flag for .b and .w arithmetic. Golden Axe works! + +v0.0066 + + Fixed a stupid typo for exg (orr r10,r10, not orr r10,r8), which caused alignment + crashes on Strider + +v0.0065 + + Fixed a problem with immediate values - they weren't being shifted up correctly for some + opcodes. Spiderman works, After Burner shows a bit of graphics. + + Fixed a problem with EA:"110nnn" extension word. 32-bit offsets were being decoded as 8-bit + offsets by mistake. Castlevania Bloodlines seems fine now. + + Added exg opcode + + Fixed asr opcode (Sonic jumping left is fixed) + + Fixed a problem with the carry bit in rol.b (Marble Madness) + +v0.0064 + + Added rtr + + Fixed addq/subq.l (all An opcodes are 32-bit) (Road Rash) + + Fixed various little timings + +v0.0063 + + Added link/unlk opcodes + + Fixed various little timings + + Fixed a problem with dbCC opcode being emitted at set opcodes + + Improved long register access, the EA fetch now does ldr r0,[r7,r0,lsl #2] whenever + possible, saving 1 or 2 cycles on many opcodes, which should give a nice speed up. + + May have fixed N flag on ext opcode? + + Added dasm for link opcode. + +v0.0062 + * I was a bit too keen with the Arithmetic opcodes! Some of them should have been abcd, + exg and addx. Removed the incorrect opcodes, pending re-adding them as abcd, exg and addx. + + Changed unknown opcodes to act as nops. + Not very technical, but fun - a few more games show more graphics ;) + +v0.0060 + + Fixed divu (EA intro) + + Added sf (set false) opcode - SOR2 + * Todo: pea/link/unlk opcodes + +v0.0059: Added remainder to divide opcodes. -Dave - 17th April 2004 -notaz - 17th July 2006 -Homepage: http://www.finalburn.com/ -Dave's e-mail: dev(atsymbol)finalburn.com -Replace (atsymbol) with @ diff --git a/cpu/Cyclone/Disa/Disa.c b/cpu/Cyclone/Disa/Disa.c index 0743d2f4..26c99676 100644 --- a/cpu/Cyclone/Disa/Disa.c +++ b/cpu/Cyclone/Disa/Disa.c @@ -272,7 +272,7 @@ static int DisaMoveSr(int op) return 0; } -static int OpChk(op) +static int OpChk(int op) { int sea=0,dea=0; char seat[64]="",deat[64]=""; diff --git a/cpu/Cyclone/Ea.cpp b/cpu/Cyclone/Ea.cpp index 35f0048a..4be33e13 100644 --- a/cpu/Cyclone/Ea.cpp +++ b/cpu/Cyclone/Ea.cpp @@ -1,6 +1,8 @@ #include "app.h" +int earead_check_addrerr = 1, eawrite_check_addrerr = 0; + // some ops use non-standard cycle counts for EAs, so are listed here. // all constants borrowed from the MUSASHI core by Karl Stenerud. @@ -149,7 +151,7 @@ int EaCalc(int a,int mask,int ea,int size,int top,int sign_extend) if (ea<0x28) { int step=1<=0x3a && ea<=0x3b) MemHandler(2,size,a); // Fetch - else MemHandler(0,size,a); // Read + if (ea>=0x3a && ea<=0x3b) MemHandler(2,size,a,earead_check_addrerr); // Fetch + else MemHandler(0,size,a,earead_check_addrerr); // Read + + // defaults to 1, as most things begins with a read + earead_check_addrerr=1; if (sign_extend) { @@ -462,7 +467,11 @@ int EaWrite(int a,int v,int ea,int size,int mask,int top,int sign_extend_ea) if (shift) ot(" mov r1,r%d,asr #%d\n",v,shift); else if (v!=1) ot(" mov r1,r%d\n",v); - MemHandler(1,size,a); // Call write handler + MemHandler(1,size,a,eawrite_check_addrerr); // Call write handler + + // not check by default, because most cases are rmw and + // address was already checked before reading + eawrite_check_addrerr = 0; ot("\n"); return 0; } diff --git a/cpu/Cyclone/Main.cpp b/cpu/Cyclone/Main.cpp index 5fed8ee1..b9ec1fe5 100644 --- a/cpu/Cyclone/Main.cpp +++ b/cpu/Cyclone/Main.cpp @@ -3,7 +3,7 @@ static FILE *AsmFile=NULL; -static int CycloneVer=0x0087; // Version number of library +static int CycloneVer=0x0088; // Version number of library int *CyJump=NULL; // Jump table int ms=USE_MS_SYNTAX; // If non-zero, output in Microsoft ARMASM format char *Narm[4]={ "b", "h","",""}; // Normal ARM Extensions for operand sizes 0,1,2 @@ -82,56 +82,19 @@ static void ChangeTAS(int norm) } #endif -// trashes all temp regs -static void PrintException(int ints) +#if EMULATE_ADDRESS_ERRORS_JUMP || EMULATE_ADDRESS_ERRORS_IO +static void AddressErrorWrapper(char rw, char *dataprg, int iw) { - if(!ints) { - ot(" ;@ Cause an Exception - Vector address in r0\n"); - ot(" mov r11,r0\n"); - } - - ot(" ldr r0,[r7,#0x44] ;@ Get SR high\n"); - ot(" ldr r10,[r7,#0x60] ;@ Get Memory base\n"); - ot(" tst r0,#0x20\n"); - ot(";@ get our SP:\n"); - ot(" ldr r0,[r7,#0x3c] ;@ Get A7\n"); - ot(" ldreq r1,[r7,#0x48] ;@ ...or OSP as our stack pointer\n"); - ot(" streq r0,[r7,#0x48]\n"); - ot(" moveq r0,r1\n"); - - ot(" sub r1,r4,r10 ;@ r1 = Old PC\n"); - ot(";@ Push r1 onto stack\n"); - ot(" sub r0,r0,#4 ;@ Predecrement A7\n"); - ot(" str r0,[r7,#0x3c] ;@ Save A7\n"); - MemHandler(1,2); - OpPushSr(1); - - ot(" mov r0,r11\n"); - ot(";@ Read IRQ Vector:\n"); - MemHandler(0,2); - if(ints) { - ot(" tst r0,r0 ;@ uninitialized int vector?\n"); - ot(" moveq r0,#0x3c\n"); - ot(" moveq lr,pc\n"); - ot(" ldreq pc,[r7,#0x70] ;@ Call read32(r0) handler\n"); - } -#if USE_CHECKPC_CALLBACK - ot(" add r0,r0,r10 ;@ r0 = Memory Base + New PC\n"); - ot(" mov lr,pc\n"); - ot(" ldr pc,[r7,#0x64] ;@ Call checkpc()\n"); - ot(" mov r4,r0\n"); -#else - ot(" add r4,r0,r10 ;@ r4 = Memory Base + New PC\n"); -#endif + ot("ExceptionAddressError_%c_%s%s\n", rw, dataprg, ms?"":":"); + ot(" ldr r1,[r7,#0x44]\n"); + ot(" mov r10,#0x%02x\n", iw); + ot(" mov r11,r0\n"); + ot(" tst r1,#0x20\n"); + ot(" orrne r10,r10,#4\n"); + ot(" b ExceptionAddressError\n"); ot("\n"); - - if(!ints) { - ot(" ldr r0,[r7,#0x44] ;@ Get SR high\n"); - ot(" bic r0,r0,#0xd8 ;@ clear trace and unused flags\n"); - ot(" orr r0,r0,#0x20 ;@ set supervisor mode\n"); - ot(" strb r0,[r7,#0x44]\n"); - } } +#endif void FlushPC(void) { @@ -144,6 +107,14 @@ void FlushPC(void) static void PrintFramework() { + int state_flags_to_check = 1; // stopped +#if EMULATE_TRACE + state_flags_to_check |= 2; // tracing +#endif +#if EMULATE_HALT + state_flags_to_check |= 0x10; // halted +#endif + ot(";@ --------------------------- Framework --------------------------\n"); if (ms) ot("CycloneRun\n"); else ot("CycloneRun:\n"); @@ -161,6 +132,10 @@ static void PrintFramework() ot(" mov r9,r9,lsl #28 ;@ r9 = Flags 0xf0000000, cpsr format\n"); ot(" ;@ r10 = Source value / Memory Base\n"); ot("\n"); +#if (CYCLONE_FOR_GENESIS == 2) || EMULATE_TRACE + ot(" mov r2,#0\n"); + ot(" str r2,[r7,#0x98] ;@ clear custom CycloneEnd\n"); +#endif ot(";@ CheckInterrupt:\n"); ot(" movs r0,r1,lsr #24 ;@ Get IRQ level\n"); // same as ldrb r0,[r7,#0x47] ot(" beq NoInts0\n"); @@ -170,12 +145,22 @@ static void PrintFramework() ot(" bgt CycloneDoInterrupt\n"); ot("NoInts0%s\n", ms?"":":"); ot("\n"); - ot(";@ Check if our processor is in stopped state and jump to opcode handler if not\n"); - ot(" ldr r0,[r7,#0x58]\n"); + ot(";@ Check if our processor is in special state\n"); + ot(";@ and jump to opcode handler if not\n"); + ot(" ldr r0,[r7,#0x58] ;@ state_flags\n"); ot(" ldrh r8,[r4],#2 ;@ Fetch first opcode\n"); - ot(" tst r0,r0 ;@ stopped?\n"); - ot(" bne CycloneStopped\n"); - ot(" ldr pc,[r6,r8,asl #2] ;@ Jump to opcode handler\n"); + ot(" tst r0,#0x%02x ;@ special state?\n", state_flags_to_check); + ot(" ldreq pc,[r6,r8,asl #2] ;@ Jump to opcode handler\n"); + ot("\n"); + ot("CycloneSpecial%s\n", ms?"":":"); +#if EMULATE_TRACE + ot(" tst r0,#2 ;@ tracing?\n"); + ot(" bne CycloneDoTrace\n"); +#endif + ot(";@ stopped or halted\n"); + ot(" mov r5,#0\n"); + ot(" str r5,[r7,#0x5C] ;@ eat all cycles\n"); + ot(" ldmia sp!,{r4-r11,pc} ;@ we are stopped, do nothing!\n"); ot("\n"); ot("\n"); @@ -183,8 +168,8 @@ static void PrintFramework() ot("CycloneEnd%s\n", ms?"":":"); ot(" sub r4,r4,#2\n"); ot("CycloneEndNoBack%s\n", ms?"":":"); -#if (CYCLONE_FOR_GENESIS == 2) - ot(" ldr r1,[r7,#0x54]\n"); +#if (CYCLONE_FOR_GENESIS == 2) || EMULATE_TRACE + ot(" ldr r1,[r7,#0x98]\n"); ot(" mov r9,r9,lsr #28\n"); ot(" tst r1,r1\n"); ot(" bxne r1 ;@ jump to alternative CycloneEnd\n"); @@ -195,86 +180,76 @@ static void PrintFramework() ot(" str r5,[r7,#0x5c] ;@ Save Cycles\n"); ot(" strb r9,[r7,#0x46] ;@ Save Flags (NZCV)\n"); ot(" ldmia sp!,{r4-r11,pc}\n"); + ltorg(); ot("\n"); - ot("CycloneStopped%s\n", ms?"":":"); - ot(" mov r5,#0\n"); - ot(" str r5,[r7,#0x5C] ;@ eat all cycles\n"); - ot(" ldmia sp!,{r4-r11,pc} ;@ we are stopped, do nothing!\n"); ot("\n"); - ltorg(); - + ot("CycloneInit%s\n", ms?"":":"); #if COMPRESS_JUMPTABLE - ot(";@ uncompress jump table\n"); - if (ms) ot("CycloneInit\n"); - else ot("CycloneInit:\n"); - ot(" ldr r12,=CycloneJumpTab\n"); - ot(" add r0,r12,#0xe000*4 ;@ ctrl code pointer\n"); - ot(" ldr r1,[r0,#-4]\n"); - ot(" tst r1,r1\n"); - ot(" movne pc,lr ;@ already uncompressed\n"); - ot(" add r3,r12,#0xa000*4 ;@ handler table pointer, r12=dest\n"); - ot("unc_loop%s\n", ms?"":":"); - ot(" ldrh r1,[r0],#2\n"); - ot(" and r2,r1,#0xf\n"); - ot(" bic r1,r1,#0xf\n"); - ot(" ldr r1,[r3,r1,lsr #2] ;@ r1=handler\n"); - ot(" cmp r2,#0xf\n"); - ot(" addeq r2,r2,#1 ;@ 0xf is really 0x10\n"); - ot(" tst r2,r2\n"); - ot(" ldreqh r2,[r0],#2 ;@ counter is in next word\n"); - ot(" tst r2,r2\n"); - ot(" beq unc_finish ;@ done decompressing\n"); - ot(" tst r1,r1\n"); - ot(" addeq r12,r12,r2,lsl #2 ;@ 0 handler means we should skip those bytes\n"); - ot(" beq unc_loop\n"); - ot("unc_loop_in%s\n", ms?"":":"); - ot(" subs r2,r2,#1\n"); - ot(" str r1,[r12],#4\n"); - ot(" bgt unc_loop_in\n"); - ot(" b unc_loop\n"); - ot("unc_finish%s\n", ms?"":":"); - ot(" ldr r12,=CycloneJumpTab\n"); - ot(" ;@ set a-line and f-line handlers\n"); - ot(" add r0,r12,#0xa000*4\n"); - ot(" ldr r1,[r0,#4] ;@ a-line handler\n"); - ot(" ldr r3,[r0,#8] ;@ f-line handler\n"); - ot(" mov r2,#0x1000\n"); - ot("unc_fill3%s\n", ms?"":":"); - ot(" subs r2,r2,#1\n"); - ot(" str r1,[r0],#4\n"); - ot(" bgt unc_fill3\n"); - ot(" add r0,r12,#0xf000*4\n"); - ot(" mov r2,#0x1000\n"); - ot("unc_fill4%s\n", ms?"":":"); - ot(" subs r2,r2,#1\n"); - ot(" str r3,[r0],#4\n"); - ot(" bgt unc_fill4\n"); - ot(" bx lr\n"); - ltorg(); - ot("\n"); + ot(";@ decompress jump table\n"); + ot(" ldr r12,=CycloneJumpTab\n"); + ot(" add r0,r12,#0xe000*4 ;@ ctrl code pointer\n"); + ot(" ldr r1,[r0,#-4]\n"); + ot(" tst r1,r1\n"); + ot(" movne pc,lr ;@ already uncompressed\n"); + ot(" add r3,r12,#0xa000*4 ;@ handler table pointer, r12=dest\n"); + ot("unc_loop%s\n", ms?"":":"); + ot(" ldrh r1,[r0],#2\n"); + ot(" and r2,r1,#0xf\n"); + ot(" bic r1,r1,#0xf\n"); + ot(" ldr r1,[r3,r1,lsr #2] ;@ r1=handler\n"); + ot(" cmp r2,#0xf\n"); + ot(" addeq r2,r2,#1 ;@ 0xf is really 0x10\n"); + ot(" tst r2,r2\n"); + ot(" ldreqh r2,[r0],#2 ;@ counter is in next word\n"); + ot(" tst r2,r2\n"); + ot(" beq unc_finish ;@ done decompressing\n"); + ot(" tst r1,r1\n"); + ot(" addeq r12,r12,r2,lsl #2 ;@ 0 handler means we should skip those bytes\n"); + ot(" beq unc_loop\n"); + ot("unc_loop_in%s\n", ms?"":":"); + ot(" subs r2,r2,#1\n"); + ot(" str r1,[r12],#4\n"); + ot(" bgt unc_loop_in\n"); + ot(" b unc_loop\n"); + ot("unc_finish%s\n", ms?"":":"); + ot(" ldr r12,=CycloneJumpTab\n"); + ot(" ;@ set a-line and f-line handlers\n"); + ot(" add r0,r12,#0xa000*4\n"); + ot(" ldr r1,[r0,#4] ;@ a-line handler\n"); + ot(" ldr r3,[r0,#8] ;@ f-line handler\n"); + ot(" mov r2,#0x1000\n"); + ot("unc_fill3%s\n", ms?"":":"); + ot(" subs r2,r2,#1\n"); + ot(" str r1,[r0],#4\n"); + ot(" bgt unc_fill3\n"); + ot(" add r0,r12,#0xf000*4\n"); + ot(" mov r2,#0x1000\n"); + ot("unc_fill4%s\n", ms?"":":"); + ot(" subs r2,r2,#1\n"); + ot(" str r3,[r0],#4\n"); + ot(" bgt unc_fill4\n"); + ot(" bx lr\n"); + ltorg(); #else - ot(";@ do nothing\n"); - if (ms) ot("CycloneInit\n"); - else ot("CycloneInit:\n"); - ot(" bx lr\n"); - ot("\n"); + ot(";@ do nothing\n"); + ot(" bx lr\n"); #endif + ot("\n"); + + // -------------- // 68k: XNZVC, ARM: NZCV - if (ms) ot("CycloneSetSr\n"); - else ot("CycloneSetSr:\n"); + ot("CycloneSetSr%s\n", ms?"":":"); ot(" mov r2,r1,lsr #8\n"); - ot(" ldrb r3,[r0,#0x44] ;@ get SR high\n"); - ot(" eor r3,r3,r2\n"); - ot(" tst r3,#0x20\n"); - ot(" and r2,r2,#0xa7 ;@ only nonzero bits\n"); +// ot(" ldrb r3,[r0,#0x44] ;@ get SR high\n"); +// ot(" eor r3,r3,r2\n"); +// ot(" tst r3,#0x20\n"); +#if EMULATE_TRACE + ot(" and r2,r2,#0xa7 ;@ only defined bits\n"); +#else + ot(" and r2,r2,#0x27 ;@ only defined bits\n"); +#endif ot(" strb r2,[r0,#0x44] ;@ set SR high\n"); - ot(" bne setsr_noswap\n"); - ot(" ldr r2,[r0,#0x3C] ;@ Get A7\n"); - ot(" ldr r3,[r0,#0x48] ;@ Get OSP\n"); - ot(" str r3,[r0,#0x3C]\n"); - ot(" str r2,[r0,#0x48]\n"); - ot("setsr_noswap%s\n",ms?"":":"); ot(" mov r2,r1,lsl #25\n"); ot(" str r2,[r0,#0x4c] ;@ the X flag\n"); ot(" bic r2,r1,#0xf3\n"); @@ -286,8 +261,8 @@ static void PrintFramework() ot(" bx lr\n"); ot("\n"); - if (ms) ot("CycloneGetSr\n"); - else ot("CycloneGetSr:\n"); + // -------------- + ot("CycloneGetSr%s\n", ms?"":":"); ot(" ldrb r1,[r0,#0x46] ;@ flags\n"); ot(" bic r2,r1,#0xf3\n"); ot(" tst r1,#1\n"); @@ -302,8 +277,111 @@ static void PrintFramework() ot(" bx lr\n"); ot("\n"); - if (ms) ot("CycloneSetRealTAS\n"); - else ot("CycloneSetRealTAS:\n"); + // -------------- + ot("CyclonePack%s\n", ms?"":":"); + ot(" stmfd sp!,{r4,r5,lr}\n"); + ot(" mov r4,r0\n"); + ot(" mov r5,r1\n"); + ot(" mov r3,#16\n"); + ot(";@ 0x00-0x3f: DA registers\n"); + ot("c_pack_loop%s\n",ms?"":":"); + ot(" ldr r1,[r0],#4\n"); + ot(" subs r3,r3,#1\n"); + ot(" str r1,[r5],#4\n"); + ot(" bne c_pack_loop\n"); + ot(";@ 0x40: PC\n"); + ot(" ldr r0,[r4,#0x40] ;@ PC + Memory Base\n"); + ot(" ldr r1,[r4,#0x60] ;@ Memory base\n"); + ot(" sub r0,r0,r1\n"); + ot(" str r0,[r5],#4\n"); + ot(";@ 0x44: SR\n"); + ot(" mov r0,r4\n"); + ot(" bl CycloneGetSr\n"); + ot(" strh r0,[r5],#2\n"); + ot(";@ 0x46: IRQ level\n"); + ot(" ldrb r0,[r4,#0x47]\n"); + ot(" strb r0,[r5],#2\n"); + ot(";@ 0x48: other SP\n"); + ot(" ldr r0,[r4,#0x48]\n"); + ot(" str r0,[r5],#4\n"); + ot(";@ 0x4c: CPU state flags\n"); + ot(" ldr r0,[r4,#0x58]\n"); + ot(" str r0,[r5],#4\n"); + ot(" ldmfd sp!,{r4,r5,pc}\n"); + ot("\n"); + + // -------------- + ot("CycloneUnpack%s\n", ms?"":":"); + ot(" stmfd sp!,{r4,r5,lr}\n"); + ot(" mov r4,r0\n"); + ot(" mov r5,r1\n"); + ot(" mov r3,#16\n"); + ot(";@ 0x00-0x3f: DA registers\n"); + ot("c_unpack_loop%s\n",ms?"":":"); + ot(" ldr r1,[r5],#4\n"); + ot(" subs r3,r3,#1\n"); + ot(" str r1,[r0],#4\n"); + ot(" bne c_unpack_loop\n"); + ot(";@ 0x40: PC\n"); + ot(" ldr r0,[r5],#4 ;@ PC\n"); +#if USE_CHECKPC_CALLBACK + ot(" mov r1,#0\n"); + ot(" str r1,[r4,#0x60] ;@ Memory base\n"); + ot(" mov lr,pc\n"); + ot(" ldr pc,[r4,#0x64] ;@ Call checkpc()\n"); +#else + ot(" ldr r1,[r4,#0x60] ;@ Memory base\n"); + ot(" add r0,r0,r1 ;@ r0 = Memory Base + New PC\n"); +#endif + ot(" str r0,[r4,#0x40] ;@ PC + Memory Base\n"); + ot(";@ 0x44: SR\n"); + ot(" ldrh r1,[r5],#2\n"); + ot(" mov r0,r4\n"); + ot(" bl CycloneSetSr\n"); + ot(";@ 0x46: IRQ level\n"); + ot(" ldrb r0,[r5],#2\n"); + ot(" strb r0,[r4,#0x47]\n"); + ot(";@ 0x48: other SP\n"); + ot(" ldr r0,[r5],#4\n"); + ot(" str r0,[r4,#0x48]\n"); + ot(";@ 0x4c: CPU state flags\n"); + ot(" ldr r0,[r5],#4\n"); + ot(" str r0,[r4,#0x58]\n"); + ot(" ldmfd sp!,{r4,r5,pc}\n"); + ot("\n"); + + // -------------- + ot("CycloneFlushIrq%s\n", ms?"":":"); + ot(" ldr r1,[r0,#0x44] ;@ Get SR high T_S__III and irq level\n"); + ot(" mov r2,r1,lsr #24 ;@ Get IRQ level\n"); // same as ldrb r0,[r7,#0x47] + ot(" cmp r2,#6 ;@ irq>6 ?\n"); + ot(" andle r1,r1,#7 ;@ Get interrupt mask\n"); + ot(" cmple r2,r1 ;@ irq<=6: Is irq<=mask ?\n"); + ot(" movle r0,#0\n"); + ot(" bxle lr ;@ no ints\n"); + ot("\n"); + ot(" stmdb sp!,{r4,r5,r7-r11,lr}\n"); + ot(" mov r7,r0\n"); + ot(" mov r0,r2\n"); + ot(" ldrb r9,[r7,#0x46] ;@ r9 = Flags (NZCV)\n"); + ot(" mov r5,#0\n"); + ot(" ldr r4,[r7,#0x40] ;@ r4 = Current PC + Memory Base\n"); + ot(" mov r9,r9,lsl #28 ;@ r9 = Flags 0xf0000000, cpsr format\n"); + ot(" adr r2,CycloneFlushIrqEnd\n"); + ot(" str r2,[r7,#0x98] ;@ set custom CycloneEnd\n"); + ot(" b CycloneDoInterrupt\n"); + ot("\n"); + ot("CycloneFlushIrqEnd%s\n", ms?"":":"); + ot(" rsb r0,r5,#0\n"); + ot(" str r4,[r7,#0x40] ;@ Save Current PC + Memory Base\n"); + ot(" strb r9,[r7,#0x46] ;@ Save Flags (NZCV)\n"); + ot(" ldmia sp!,{r4,r5,r7-r11,lr}\n"); + ot(" bx lr\n"); + ot("\n"); + ot("\n"); + + // -------------- + ot("CycloneSetRealTAS%s\n", ms?"":":"); #if (CYCLONE_FOR_GENESIS == 2) ot(" ldr r12,=CycloneJumpTab\n"); ot(" tst r0,r0\n"); @@ -316,49 +394,122 @@ static void PrintFramework() ChangeTAS(0); ot(" bx lr\n"); ltorg(); - ot("\n"); #else ot(" bx lr\n"); - ot("\n"); #endif + ot("\n"); - ot(";@ DoInterrupt - r0=IRQ number\n"); + // -------------- + ot(";@ DoInterrupt - r0=IRQ level\n"); ot("CycloneDoInterruptGoBack%s\n", ms?"":":"); ot(" sub r4,r4,#2\n"); ot("CycloneDoInterrupt%s\n", ms?"":":"); - ot(";@ Get IRQ Vector address:\n"); - ot(" mov r0,r0,asl #2\n"); - ot(" add r11,r0,#0x60\n"); - PrintException(1); - - ot(" ldrb r0,[r7,#0x47] ;@ IRQ\n"); - ot(" mov r2,#0\n"); - ot(" orr r1,r0,#0x20 ;@ Supervisor mode + IRQ number\n"); - ot(" strb r1,[r7,#0x44] ;@ Put SR high\n"); - - ot(";@ Clear stopped states:\n"); + ot(" bic r8,r8,#0xff000000\n"); + ot(" orr r8,r8,r0,lsl #29 ;@ abuse r8\n"); + + // Steps are from "M68000 8-/16-/32-BIT MICROPROCESSORS USER'S MANUAL", p. 6-4 + // but their order is based on http://pasti.fxatari.com/68kdocs/68kPrefetch.html + // 1. Make a temporary copy of the status register and set the status register for exception processing. + ot(" ldr r2,[r7,#0x58] ;@ state flags\n"); + ot(" and r0,r0,#7\n"); + ot(" orr r3,r0,#0x20 ;@ Supervisor mode + IRQ level\n"); + ot(" bic r2,r2,#3 ;@ clear stopped and trace states\n"); +#if EMULATE_ADDRESS_ERRORS_JUMP || EMULATE_ADDRESS_ERRORS_IO + ot(" orr r2,r2,#4 ;@ set activity bit: 'not processing instruction'\n"); +#endif ot(" str r2,[r7,#0x58]\n"); + ot(" ldrb r10,[r7,#0x44] ;@ Get old SR high\n"); + ot(" strb r3,[r7,#0x44] ;@ Put new SR high\n"); + ot("\n"); + + // 3. Save the current processor context. + ot(" ldr r1,[r7,#0x60] ;@ Get Memory base\n"); + ot(" ldr r11,[r7,#0x3c] ;@ Get A7\n"); + ot(" tst r10,#0x20\n"); + ot(";@ get our SP:\n"); + ot(" ldreq r2,[r7,#0x48] ;@ ...or OSP as our stack pointer\n"); + ot(" streq r11,[r7,#0x48]\n"); + ot(" moveq r11,r2\n"); + ot(";@ Push old PC onto stack\n"); + ot(" sub r0,r11,#4 ;@ Predecremented A7\n"); + ot(" sub r1,r4,r1 ;@ r1 = Old PC\n"); + MemHandler(1,2); + ot(";@ Push old SR:\n"); + ot(" ldr r0,[r7,#0x4c] ;@ X bit\n"); + ot(" mov r1,r9,lsr #28 ;@ ____NZCV\n"); + ot(" eor r2,r1,r1,ror #1 ;@ Bit 0=C^V\n"); + ot(" tst r2,#1 ;@ 1 if C!=V\n"); + ot(" eorne r1,r1,#3 ;@ ____NZVC\n"); + ot(" and r0,r0,#0x20000000\n"); + ot(" orr r1,r1,r0,lsr #25 ;@ ___XNZVC\n"); + ot(" orr r1,r1,r10,lsl #8 ;@ Include old SR high\n"); + ot(" sub r0,r11,#6 ;@ Predecrement A7\n"); + ot(" str r0,[r7,#0x3c] ;@ Save A7\n"); + MemHandler(1,1,0,0); // already checked for address error by prev MemHandler ot("\n"); + + // 2. Obtain the exception vector. + ot(" mov r11,r8,lsr #29\n"); + ot(" mov r0,r11\n"); #if USE_INT_ACK_CALLBACK + ot(";@ call IrqCallback if it is defined\n"); #if INT_ACK_NEEDS_STUFF ot(" str r4,[r7,#0x40] ;@ Save PC\n"); ot(" mov r1,r9,lsr #28\n"); ot(" strb r1,[r7,#0x46] ;@ Save Flags (NZCV)\n"); ot(" str r5,[r7,#0x5c] ;@ Save Cycles\n"); #endif - ot(" ldr r11,[r7,#0x8c] ;@ IrqCallback\n"); - ot(" tst r11,r11\n"); - ot(" movne lr,pc\n"); - ot(" movne pc,r11 ;@ call IrqCallback if it is defined\n"); -#if INT_ACK_CHANGES_STUFF + ot(" ldr r3,[r7,#0x8c] ;@ IrqCallback\n"); + ot(" add lr,pc,#4*3\n"); + ot(" tst r3,r3\n"); + ot(" streqb r3,[r7,#0x47] ;@ just clear IRQ if there is no callback\n"); + ot(" mvneq r0,#0 ;@ and simulate -1 return\n"); + ot(" bxne r3\n"); +#if INT_ACK_CHANGES_CYCLES ot(" ldr r5,[r7,#0x5c] ;@ Load Cycles\n"); - ot(" ldrb r9,[r7,#0x46] ;@ r9 = Load Flags (NZCV)\n"); - ot(" mov r9,r9,lsl #28\n"); - ot(" ldr r4,[r7,#0x40] ;@ Load PC\n"); #endif + ot(";@ get IRQ vector address:\n"); + ot(" cmn r0,#1 ;@ returned -1?\n"); + ot(" addeq r0,r11,#0x18 ;@ use autovector then\n"); + ot(" cmn r0,#2 ;@ returned -2?\n"); // should be safe as above add should never result in -2 + ot(" moveq r0,#0x18 ;@ use spurious interrupt then\n"); #else // !USE_INT_ACK_CALLBACK ot(";@ Clear irq:\n"); + ot(" mov r2,#0\n"); ot(" strb r2,[r7,#0x47]\n"); + ot(" add r0,r0,#0x18 ;@ use autovector\n"); +#endif + ot(" mov r0,r0,lsl #2 ;@ get vector address\n"); + ot("\n"); + ot(" ldr r10,[r7,#0x60] ;@ Get Memory base\n"); + ot(";@ Read IRQ Vector:\n"); + MemHandler(0,2,0,0); + ot(" tst r0,r0 ;@ uninitialized int vector?\n"); + ot(" moveq r0,#0x3c\n"); + ot(" moveq lr,pc\n"); + ot(" ldreq pc,[r7,#0x70] ;@ Call read32(r0) handler\n"); +#if USE_CHECKPC_CALLBACK + ot(" add lr,pc,#4\n"); + ot(" add r0,r0,r10 ;@ r0 = Memory Base + New PC\n"); + ot(" ldr pc,[r7,#0x64] ;@ Call checkpc()\n"); + #if EMULATE_ADDRESS_ERRORS_JUMP + ot(" mov r4,r0\n"); + #else + ot(" bic r4,r0,#1\n"); + #endif +#else + ot(" add r4,r0,r10 ;@ r4 = Memory Base + New PC\n"); + #if EMULATE_ADDRESS_ERRORS_JUMP + ot(" bic r4,r4,#1\n"); + #endif +#endif + ot("\n"); + + // 4. Obtain a new context and resume instruction processing. + // note: the obtain part was already done in previous steps +#if EMULATE_ADDRESS_ERRORS_JUMP + ot(" tst r4,#1\n"); + ot(" bne ExceptionAddressError_r_prg_r4\n"); #endif ot(" ldrh r8,[r4],#2 ;@ Fetch next opcode\n"); ot(" subs r5,r5,#44 ;@ Subtract cycles\n"); @@ -366,17 +517,276 @@ static void PrintFramework() ot(" b CycloneEnd\n"); ot("\n"); + // -------------- + // trashes all temp regs ot("Exception%s\n", ms?"":":"); - ot(" stmdb sp!,{lr} ;@ Preserve ARM return address\n"); - PrintException(0); - ot(" ldmia sp!,{pc} ;@ Return\n"); + ot(" ;@ Cause an Exception - Vector number in r0\n"); + ot(" mov r11,lr ;@ Preserve ARM return address\n"); + ot(" bic r8,r8,#0xff000000\n"); + ot(" orr r8,r8,r0,lsl #24 ;@ abuse r8\n"); + + // 1. Make a temporary copy of the status register and set the status register for exception processing. + ot(" ldr r10,[r7,#0x44] ;@ Get old SR high\n"); + ot(" ldr r2,[r7,#0x58] ;@ state flags\n"); + ot(" and r3,r10,#0x27 ;@ clear trace and unused flags\n"); + ot(" orr r3,r3,#0x20 ;@ set supervisor mode\n"); + ot(" bic r2,r2,#3 ;@ clear stopped and trace states\n"); + ot(" str r2,[r7,#0x58]\n"); + ot(" strb r3,[r7,#0x44] ;@ Put new SR high\n"); + ot("\n"); + + // 3. Save the current processor context. + ot(" ldr r0,[r7,#0x3c] ;@ Get A7\n"); + ot(" tst r10,#0x20\n"); + ot(";@ get our SP:\n"); + ot(" ldreq r2,[r7,#0x48] ;@ ...or OSP as our stack pointer\n"); + ot(" streq r0,[r7,#0x48]\n"); + ot(" moveq r0,r2\n"); + ot(";@ Push old PC onto stack\n"); + ot(" ldr r1,[r7,#0x60] ;@ Get Memory base\n"); + ot(" sub r0,r0,#4 ;@ Predecremented A7\n"); + ot(" str r0,[r7,#0x3c] ;@ Save A7\n"); + ot(" sub r1,r4,r1 ;@ r1 = Old PC\n"); + MemHandler(1,2); + ot(";@ Push old SR:\n"); + ot(" ldr r0,[r7,#0x4c] ;@ X bit\n"); + ot(" mov r1,r9,lsr #28 ;@ ____NZCV\n"); + ot(" eor r2,r1,r1,ror #1 ;@ Bit 0=C^V\n"); + ot(" tst r2,#1 ;@ 1 if C!=V\n"); + ot(" eorne r1,r1,#3 ;@ ____NZVC\n"); + ot(" and r0,r0,#0x20000000\n"); + ot(" orr r1,r1,r0,lsr #25 ;@ ___XNZVC\n"); + ot(" ldr r0,[r7,#0x3c] ;@ A7\n"); + ot(" orr r1,r1,r10,lsl #8 ;@ Include SR high\n"); + ot(" sub r0,r0,#2 ;@ Predecrement A7\n"); + ot(" str r0,[r7,#0x3c] ;@ Save A7\n"); + MemHandler(1,1,0,0); + ot("\n"); + + // 2. Obtain the exception vector + ot(";@ Read Exception Vector:\n"); + ot(" mov r0,r8,lsr #24\n"); + ot(" mov r0,r0,lsl #2\n"); + MemHandler(0,2,0,0); + ot(" ldr r3,[r7,#0x60] ;@ Get Memory base\n"); +#if USE_CHECKPC_CALLBACK + ot(" add lr,pc,#4\n"); + ot(" add r0,r0,r3 ;@ r0 = Memory Base + New PC\n"); + ot(" ldr pc,[r7,#0x64] ;@ Call checkpc()\n"); + #if EMULATE_ADDRESS_ERRORS_JUMP + ot(" mov r4,r0\n"); + #else + ot(" bic r4,r0,#1\n"); + #endif +#else + ot(" add r4,r0,r3 ;@ r4 = Memory Base + New PC\n"); + #if EMULATE_ADDRESS_ERRORS_JUMP + ot(" bic r4,r4,#1\n"); + #endif +#endif + ot("\n"); + + // 4. Resume execution. +#if EMULATE_ADDRESS_ERRORS_JUMP + ot(" tst r4,#1\n"); + ot(" bne ExceptionAddressError_r_prg_r4\n"); +#endif + ot(" bx r11 ;@ Return\n"); + ot("\n"); + + // -------------- +#if EMULATE_ADDRESS_ERRORS_JUMP || EMULATE_ADDRESS_ERRORS_IO + // first some wrappers: I see no point inlining this code, + // as it will be executed in really rare cases. + AddressErrorWrapper('r', "data", 0x11); + AddressErrorWrapper('r', "prg", 0x12); + AddressErrorWrapper('w', "data", 0x01); + // there are no program writes + // cpu space is only for bus errors? + ot("ExceptionAddressError_r_prg_r4%s\n", ms?"":":"); + ot(" ldr r1,[r7,#0x44]\n"); + ot(" ldr r3,[r7,#0x60] ;@ Get Memory base\n"); + ot(" mov r10,#0x12\n"); + ot(" sub r11,r4,r3\n"); + ot(" tst r1,#0x20\n"); + ot(" orrne r10,r10,#4\n"); + ot("\n"); + + ot("ExceptionAddressError%s\n", ms?"":":"); + ot(";@ r10 - info word (without instruction/not bit), r11 - faulting address\n"); + + // 1. Make a temporary copy of the status register and set the status register for exception processing. + ot(" ldrb r0,[r7,#0x44] ;@ Get old SR high\n"); + ot(" ldr r2,[r7,#0x58] ;@ state flags\n"); + ot(" and r3,r0,#0x27 ;@ clear trace and unused flags\n"); + ot(" orr r3,r3,#0x20 ;@ set supervisor mode\n"); + ot(" strb r3,[r7,#0x44] ;@ Put new SR high\n"); + ot(" bic r2,r2,#3 ;@ clear stopped and trace states\n"); + ot(" tst r2,#4\n"); + ot(" orrne r10,r10,#8 ;@ complete info word\n"); + ot(" orr r2,r2,#4 ;@ set activity bit: 'not processing instruction'\n"); +#if EMULATE_HALT + ot(" tst r2,#8\n"); + ot(" orrne r2,r2,#0x10 ;@ HALT\n"); + ot(" orr r2,r2,#8 ;@ processing address error\n"); + ot(" str r2,[r7,#0x58]\n"); + ot(" movne r5,#0\n"); + ot(" bne CycloneEndNoBack ;@ bye bye\n"); +#else + ot(" str r2,[r7,#0x58]\n"); +#endif + ot(" and r9,r9,#0xf0000000\n"); + ot(" orr r9,r9,r0,lsl #4 ;@ some preparations for SR push\n"); + ot("\n"); + + // 3. Save the current processor context + additional information. + ot(" ldr r0,[r7,#0x3c] ;@ Get A7\n"); + ot(" tst r9,#0x200\n"); + ot(";@ get our SP:\n"); + ot(" ldreq r2,[r7,#0x48] ;@ ...or OSP as our stack pointer\n"); + ot(" streq r0,[r7,#0x48]\n"); + ot(" moveq r0,r2\n"); + // PC + ot(";@ Push old PC onto stack\n"); + ot(" ldr r1,[r7,#0x60] ;@ Get Memory base\n"); + ot(" sub r0,r0,#4 ;@ Predecremented A7\n"); + ot(" sub r1,r4,r1 ;@ r1 = Old PC\n"); + ot(" str r0,[r7,#0x3c] ;@ Save A7\n"); + MemHandler(1,2,0,EMULATE_HALT); + // SR + ot(";@ Push old SR:\n"); + ot(" ldr r0,[r7,#0x4c] ;@ X bit\n"); + ot(" mov r1,r9,ror #28 ;@ ____NZCV\n"); + ot(" eor r2,r1,r1,ror #1 ;@ Bit 0=C^V\n"); + ot(" tst r2,#1 ;@ 1 if C!=V\n"); + ot(" eorne r1,r1,#3 ;@ ____NZVC\n"); + ot(" and r0,r0,#0x20000000\n"); + ot(" orr r1,r1,r0,lsr #25 ;@ ___XNZVC\n"); + ot(" ldr r0,[r7,#0x3c] ;@ A7\n"); + ot(" and r9,r9,#0xf0000000\n"); + ot(" sub r0,r0,#2 ;@ Predecrement A7\n"); + ot(" str r0,[r7,#0x3c] ;@ Save A7\n"); + MemHandler(1,1,0,0); + // IR (instruction register) + ot(";@ Push IR:\n"); + ot(" ldr r0,[r7,#0x3c] ;@ A7\n"); + ot(" mov r1,r8\n"); + ot(" sub r0,r0,#2 ;@ Predecrement A7\n"); + ot(" str r0,[r7,#0x3c] ;@ Save A7\n"); + MemHandler(1,1,0,0); + // access address + ot(";@ Push address:\n"); + ot(" ldr r0,[r7,#0x3c] ;@ A7\n"); + ot(" mov r1,r11\n"); + ot(" sub r0,r0,#4 ;@ Predecrement A7\n"); + ot(" str r0,[r7,#0x3c] ;@ Save A7\n"); + MemHandler(1,2,0,0); + // information word + ot(";@ Push info word:\n"); + ot(" ldr r0,[r7,#0x3c] ;@ A7\n"); + ot(" mov r1,r10\n"); + ot(" sub r0,r0,#2 ;@ Predecrement A7\n"); + ot(" str r0,[r7,#0x3c] ;@ Save A7\n"); + MemHandler(1,1,0,0); + ot("\n"); + + // 2. Obtain the exception vector + ot(";@ Read Exception Vector:\n"); + ot(" mov r0,#0x0c\n"); + MemHandler(0,2,0,0); + ot(" ldr r3,[r7,#0x60] ;@ Get Memory base\n"); +#if USE_CHECKPC_CALLBACK + ot(" add lr,pc,#4\n"); + ot(" add r0,r0,r3 ;@ r0 = Memory Base + New PC\n"); + ot(" ldr pc,[r7,#0x64] ;@ Call checkpc()\n"); + ot(" mov r4,r0\n"); +#else + ot(" add r4,r0,r3 ;@ r4 = Memory Base + New PC\n"); +#endif + ot("\n"); + +#if EMULATE_ADDRESS_ERRORS_JUMP && EMULATE_HALT + ot(" tst r4,#1\n"); + ot(" bne ExceptionAddressError_r_prg_r4\n"); +#else + ot(" bic r4,r4,#1\n"); +#endif + + // 4. Resume execution. + ot(" ldrh r8,[r4],#2 ;@ Fetch next opcode\n"); + ot(" subs r5,r5,#50 ;@ Subtract cycles\n"); + ot(" ldrge pc,[r6,r8,asl #2] ;@ Jump to opcode handler\n"); + ot(" b CycloneEnd\n"); + ot("\n"); +#endif + + // -------------- +#if EMULATE_TRACE + // expects srh and irq level in r1, next opcode already fetched to r8 + ot("CycloneDoTraceWithChecks%s\n", ms?"":":"); + ot(" ldr r0,[r7,#0x58]\n"); + ot(" cmp r5,#0\n"); + ot(" orr r0,r0,#2 ;@ go to trace mode\n"); + ot(" str r0,[r7,#0x58]\n"); + ot(" blt CycloneEnd\n"); // should take care of situation where we come here when already tracing + ot(";@ CheckInterrupt:\n"); + ot(" movs r0,r1,lsr #24 ;@ Get IRQ level\n"); + ot(" beq CycloneDoTrace\n"); + ot(" cmp r0,#6 ;@ irq>6 ?\n"); + ot(" andle r1,r1,#7 ;@ Get interrupt mask\n"); + ot(" cmple r0,r1 ;@ irq<=6: Is irq<=mask ?\n"); + ot(" bgt CycloneDoInterruptGoBack\n"); + ot("\n"); + + // expects next opcode to be already fetched to r8 + ot("CycloneDoTrace%s\n", ms?"":":"); + ot(" str r5,[r7,#0x9c] ;@ save cycles\n"); + ot(" ldr r1,[r7,#0x98]\n"); + ot(" mov r5,#0\n"); + ot(" str r1,[r7,#0xa0]\n"); + ot(" adr r0,TraceEnd\n"); + ot(" str r0,[r7,#0x98] ;@ store TraceEnd as CycloneEnd hadler\n"); + ot(" ldr pc,[r6,r8,asl #2] ;@ Jump to opcode handler\n"); + ot("\n"); + + ot("TraceEnd%s\n", ms?"":":"); + ot(" ldr r2,[r7,#0x58]\n"); + ot(" ldr r0,[r7,#0x9c] ;@ restore cycles\n"); + ot(" ldr r1,[r7,#0xa0] ;@ old CycloneEnd handler\n"); + ot(" mov r9,r9,lsl #28\n"); + ot(" add r5,r0,r5\n"); + ot(" str r1,[r7,#0x98]\n"); + ot(";@ still tracing?\n"); // exception might have happend + ot(" tst r2,#2\n"); + ot(" beq TraceDisabled\n"); + ot(";@ trace exception\n"); +#if EMULATE_ADDRESS_ERRORS_JUMP || EMULATE_ADDRESS_ERRORS_IO + ot(" ldr r1,[r7,#0x58]\n"); + ot(" mov r0,#9\n"); + ot(" orr r1,r1,#4 ;@ set activity bit: 'not processing instruction'\n"); + ot(" str r1,[r7,#0x58]\n"); +#else + ot(" mov r0,#9\n"); +#endif + ot(" bl Exception\n"); + ot(" ldrh r8,[r4],#2 ;@ Fetch next opcode\n"); + ot(" subs r5,r5,#34 ;@ Subtract cycles\n"); + ot(" ldrge pc,[r6,r8,asl #2] ;@ Jump to opcode handler\n"); + ot(" b CycloneEnd\n"); ot("\n"); + ot("TraceDisabled%s\n", ms?"":":"); + ot(" ldrh r8,[r4],#2 ;@ Fetch next opcode\n"); + ot(" cmp r5,#0\n"); + ot(" ldrge pc,[r6,r8,asl #2] ;@ Jump to opcode handler\n"); + ot(" b CycloneEnd\n"); + ot("\n"); +#endif } // --------------------------------------------------------------------------- // Call Read(r0), Write(r0,r1) or Fetch(r0) // Trashes r0-r3,r12,lr -int MemHandler(int type,int size,int addrreg) +int MemHandler(int type,int size,int addrreg,int need_addrerr_check) { int func=0; func=0x68+type*0xc+(size<<2); // Find correct offset @@ -403,9 +813,24 @@ int MemHandler(int type,int size,int addrreg) ot(" bic r0,r%i,#0x%08x\n", addrreg, MEMHANDLERS_ADDR_MASK & 0x000000ff); addrreg=0; #endif + +#if EMULATE_ADDRESS_ERRORS_IO + if (size > 0 && need_addrerr_check) + { + ot(" add lr,pc,#4*%i\n", addrreg==0?2:3); // helps to prevent interlocks + if (addrreg != 0) ot(" mov r0,r%i\n", addrreg); + ot(" tst r0,#1 ;@ address error?\n"); + switch (type) { + case 0: ot(" bne ExceptionAddressError_r_data\n"); break; + case 1: ot(" bne ExceptionAddressError_w_data\n"); break; + case 2: ot(" bne ExceptionAddressError_r_prg\n"); break; + } + } + else +#endif if (addrreg != 0) { - ot(" add lr,pc,#4\n"); // helps to prevent interlocks + ot(" add lr,pc,#4\n"); ot(" mov r0,r%i\n", addrreg); } else @@ -442,7 +867,14 @@ static void PrintOpcodes() // Emit null opcode: ot("Op____%s ;@ Called if an opcode is not recognised\n", ms?"":":"); +#if EMULATE_ADDRESS_ERRORS_JUMP || EMULATE_ADDRESS_ERRORS_IO + ot(" ldr r1,[r7,#0x58]\n"); ot(" sub r4,r4,#2\n"); + ot(" orr r1,r1,#4 ;@ set activity bit: 'not processing instruction'\n"); + ot(" str r1,[r7,#0x58]\n"); +#else + ot(" sub r4,r4,#2\n"); +#endif #if USE_UNRECOGNIZED_CALLBACK ot(" str r4,[r7,#0x40] ;@ Save PC\n"); ot(" mov r1,r9,lsr #28\n"); @@ -457,10 +889,10 @@ static void PrintOpcodes() ot(" ldr r4,[r7,#0x40] ;@ Load PC\n"); ot(" mov r9,r9,lsl #28\n"); ot(" tst r0,r0\n"); - ot(" moveq r0,#0x10\n"); + ot(" moveq r0,#4\n"); ot(" bleq Exception\n"); #else - ot(" mov r0,#0x10\n"); + ot(" mov r0,#4\n"); ot(" bl Exception\n"); #endif ot("\n"); @@ -484,10 +916,10 @@ static void PrintOpcodes() ot(" ldr r4,[r7,#0x40] ;@ Load PC\n"); ot(" mov r9,r9,lsl #28\n"); ot(" tst r0,r0\n"); - ot(" moveq r0,#0x28\n"); + ot(" moveq r0,#0x0a\n"); ot(" bleq Exception\n"); #else - ot(" mov r0,#0x28\n"); + ot(" mov r0,#0x0a\n"); ot(" bl Exception\n"); #endif ot("\n"); @@ -510,10 +942,10 @@ static void PrintOpcodes() ot(" ldr r4,[r7,#0x40] ;@ Load PC\n"); ot(" mov r9,r9,lsl #28\n"); ot(" tst r0,r0\n"); - ot(" moveq r0,#0x2c\n"); + ot(" moveq r0,#0x0b\n"); ot(" bleq Exception\n"); #else - ot(" mov r0,#0x2c\n"); + ot(" mov r0,#0x0b\n"); ot(" bl Exception\n"); #endif ot("\n"); @@ -661,6 +1093,7 @@ static int CycloneMake() { int i; char *name="Cyclone.s"; + const char *globl=ms?"export":".global"; // Open the assembly file if (ms) name="Cyclone.asm"; @@ -680,32 +1113,24 @@ static int CycloneMake() for(i=0xa000; i<0xb000; i++) CyJump[i] = -2; // a-line emulation for(i=0xf000; i<0x10000; i++) CyJump[i] = -3; // f-line emulation - if (ms) - { - ot(" area |.text|, code\n"); - ot(" export CycloneInit\n"); - ot(" export CycloneRun\n"); - ot(" export CycloneSetSr\n"); - ot(" export CycloneGetSr\n"); - ot(" export CycloneSetRealTAS\n"); - ot(" export CycloneVer\n"); - ot("\n"); - ot("CycloneVer dcd 0x%.4x\n",CycloneVer); - } - else - { - ot(" .global CycloneInit\n"); - ot(" .global CycloneRun\n"); - ot(" .global CycloneSetSr\n"); - ot(" .global CycloneGetSr\n"); - ot(" .global CycloneVer\n"); + ot(ms?" area |.text|, code\n":" .text\n .align 4\n\n"); + ot(" %s CycloneInit\n",globl); + ot(" %s CycloneRun\n",globl); + ot(" %s CycloneSetSr\n",globl); + ot(" %s CycloneGetSr\n",globl); + ot(" %s CycloneFlushIrq\n",globl); + ot(" %s CyclonePack\n",globl); + ot(" %s CycloneUnpack\n",globl); + ot(" %s CycloneVer\n",globl); #if (CYCLONE_FOR_GENESIS == 2) - ot(" .global CycloneSetRealTAS\n"); - ot(" .global CycloneDoInterrupt\n"); - ot(" .global CycloneJumpTab\n"); + ot(" %s CycloneSetRealTAS\n",globl); + ot(" %s CycloneDoInterrupt\n",globl); + ot(" %s CycloneDoTrace\n",globl); + ot(" %s CycloneJumpTab\n",globl); #endif - ot("CycloneVer: .long 0x%.4x\n",CycloneVer); - } + ot("\n"); + ot(ms?"CycloneVer dcd 0x":"CycloneVer: .long 0x"); + ot("%.4x\n",CycloneVer); ot("\n"); PrintFramework(); diff --git a/cpu/Cyclone/OpAny.cpp b/cpu/Cyclone/OpAny.cpp index cbdf5bb2..0ada13f5 100644 --- a/cpu/Cyclone/OpAny.cpp +++ b/cpu/Cyclone/OpAny.cpp @@ -1,6 +1,8 @@ #include "app.h" +int opend_op_changes_cycles, opend_check_interrupt, opend_check_trace; + static unsigned char OpData[16]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; static unsigned short CPU_CALL OpRead16(unsigned int a) @@ -62,15 +64,17 @@ void OpStart(int op, int sea, int tea, int op_changes_cycles, int supervisor_che if (last_op_count!=arm_op_count) ot("\n"); pc_dirty = 1; + opend_op_changes_cycles = opend_check_interrupt = opend_check_trace = 0; } -void OpEnd(int sea, int tea, int op_changes_cycles, int check_interrupt) +void OpEnd(int sea, int tea) { int did_fetch=0; + opend_check_trace = opend_check_trace && EMULATE_TRACE; #if MEMHANDLERS_CHANGE_CYCLES if ((sea >= 0x10 && sea != 0x3c) || (tea >= 0x10 && tea != 0x3c)) { - if (op_changes_cycles) + if (opend_op_changes_cycles) { ot(" ldr r0,[r7,#0x5c] ;@ Load Cycles\n"); ot(" ldrh r8,[r4],#2 ;@ Fetch next opcode\n"); @@ -85,12 +89,22 @@ void OpEnd(int sea, int tea, int op_changes_cycles, int check_interrupt) #endif if (!did_fetch) ot(" ldrh r8,[r4],#2 ;@ Fetch next opcode\n"); + if (opend_check_trace) + ot(" ldr r1,[r7,#0x44]\n"); ot(" subs r5,r5,#%d ;@ Subtract cycles\n",Cycles); - if (check_interrupt) + if (opend_check_trace) + { + ot(";@ CheckTrace:\n"); + ot(" tst r1,#0x80\n"); + ot(" bne CycloneDoTraceWithChecks\n"); + ot(" cmp r5,#0\n"); + } + if (opend_check_interrupt) { ot(" blt CycloneEnd\n"); ot(";@ CheckInterrupt:\n"); - ot(" ldr r1,[r7,#0x44] ;@ Get SR high T_S__III and irq level\n"); + if (!opend_check_trace) + ot(" ldr r1,[r7,#0x44]\n"); ot(" movs r0,r1,lsr #24 ;@ Get IRQ level\n"); // same as ldrb r0,[r7,#0x47] ot(" ldreq pc,[r6,r8,asl #2] ;@ Jump to next opcode handler\n"); ot(" cmp r0,#6 ;@ irq>6 ?\n"); diff --git a/cpu/Cyclone/OpArith.cpp b/cpu/Cyclone/OpArith.cpp index 93d34a24..3aebd8fd 100644 --- a/cpu/Cyclone/OpArith.cpp +++ b/cpu/Cyclone/OpArith.cpp @@ -346,7 +346,7 @@ int OpMul(int op) if (type==0) // div { ot("divzero%.4x%s\n",op,ms?"":":"); - ot(" mov r0,#0x14 ;@ Divide by zero\n"); + ot(" mov r0,#5 ;@ Divide by zero\n"); ot(" bl Exception\n"); Cycles+=38; OpEnd(ea); @@ -553,16 +553,18 @@ int OpAritha(int op) // EA calculation order defines how situations like suba.w (A0)+, A0 get handled. // different emus act differently in this situation, I couldn't fugure which is right behaviour. - if (/*type == */1) + //if (type == 1) { EaCalcReadNoSE(-1,0,sea,size,0x003f); EaCalcReadNoSE(type!=1?10:-1,11,dea,2,0x0e00); } +#if 0 else { EaCalcReadNoSE(type!=1?10:-1,11,dea,2,0x0e00); EaCalcReadNoSE(-1,0,sea,size,0x003f); } +#endif if (size<2) ot(" mov r0,r0,asl #%d\n\n",size?16:24); if (size<2) asr=(char *)(size?",asr #16":",asr #24"); @@ -803,7 +805,7 @@ int OpChk(int op) OpEnd(ea); ot("chktrap%.4x%s ;@ CHK exception:\n",op,ms?"":":"); - ot(" mov r0,#0x18\n"); + ot(" mov r0,#6\n"); ot(" bl Exception\n"); Cycles+=40; OpEnd(ea); diff --git a/cpu/Cyclone/OpBranch.cpp b/cpu/Cyclone/OpBranch.cpp index 320b898b..d29ed031 100644 --- a/cpu/Cyclone/OpBranch.cpp +++ b/cpu/Cyclone/OpBranch.cpp @@ -1,19 +1,15 @@ #include "app.h" -static void CheckPc(int reg) +// in/out address in r0, trashes all temp regs +static void CheckPc(void) { #if USE_CHECKPC_CALLBACK - ot(";@ Check Memory Base+pc (r%i)\n",reg); - if (reg != 0) - ot(" mov r0,r%i\n", reg); + ot(";@ Check Memory Base+pc\n"); ot(" mov lr,pc\n"); ot(" ldr pc,[r7,#0x64] ;@ Call checkpc()\n"); - ot(" mov r4,r0\n"); -#else - ot(" bic r4,r%d,#1\n",reg); // we do not emulate address errors -#endif ot("\n"); +#endif } // Push 32-bit value in r1 - trashes r0-r3,r12,lr @@ -61,7 +57,12 @@ static void PopPc() MemHandler(0,2); ot(" add r0,r0,r10 ;@ Memory Base+PC\n"); ot("\n"); - CheckPc(0); + CheckPc(); +#if EMULATE_ADDRESS_ERRORS_JUMP + ot(" mov r4,r0\n"); +#else + ot(" bic r4,r0,#1\n"); +#endif } int OpTrap(int op) @@ -73,8 +74,7 @@ int OpTrap(int op) OpStart(op,0x10); ot(" and r0,r8,#0xf ;@ Get trap number\n"); - ot(" orr r0,r0,#0x20\n"); - ot(" mov r0,r0,asl #2\n"); + ot(" orr r0,r0,#0x20 ;@ 32+n\n"); ot(" bl Exception\n"); ot("\n"); @@ -177,14 +177,30 @@ int Op4E70(int op) PopSr(1); ot(" ldr r10,[r7,#0x60] ;@ Get Memory base\n"); PopPc(); - SuperChange(op); - OpEnd(0x10,0,0,1); + ot(" ldr r1,[r7,#0x44] ;@ reload SR high\n"); + SuperChange(op,1); +#if EMULATE_ADDRESS_ERRORS_JUMP || EMULATE_ADDRESS_ERRORS_IO || EMULATE_HALT + ot(" ldr r1,[r7,#0x58]\n"); + ot(" bic r1,r1,#0x0c ;@ clear 'not processing instruction' and 'doing addr error' bits\n"); + ot(" str r1,[r7,#0x58]\n"); +#endif +#if EMULATE_ADDRESS_ERRORS_JUMP + ot(" tst r4,#1 ;@ address error?\n"); + ot(" bne ExceptionAddressError_r_prg_r4\n"); +#endif + opend_check_interrupt = 1; + opend_check_trace = 1; + OpEnd(0x10,0); return 0; case 5: // rts OpStart(op,0x10); Cycles=16; ot(" ldr r10,[r7,#0x60] ;@ Get Memory base\n"); PopPc(); +#if EMULATE_ADDRESS_ERRORS_JUMP + ot(" tst r4,#1 ;@ address error?\n"); + ot(" bne ExceptionAddressError_r_prg_r4\n"); +#endif OpEnd(0x10); return 0; @@ -192,9 +208,10 @@ int Op4E70(int op) OpStart(op,0x10,0,1); Cycles=4; ot(" tst r9,#0x10000000\n"); ot(" subne r5,r5,#%i\n",34); - ot(" movne r0,#0x1c ;@ TRAPV exception\n"); + ot(" movne r0,#7 ;@ TRAPV exception\n"); ot(" blne Exception\n"); - OpEnd(0x10,0,1); + opend_op_changes_cycles = 1; + OpEnd(0x10,0); return 0; case 7: // rtr @@ -202,6 +219,10 @@ int Op4E70(int op) PopSr(0); ot(" ldr r10,[r7,#0x60] ;@ Get Memory base\n"); PopPc(); +#if EMULATE_ADDRESS_ERRORS_JUMP + ot(" tst r4,#1 ;@ address error?\n"); + ot(" bne ExceptionAddressError_r_prg_r4\n"); +#endif OpEnd(0x10); return 0; @@ -231,21 +252,32 @@ int OpJsr(int op) ot("\n"); EaCalc(11,0x003f,sea,0); + ot(";@ Jump - Get new PC from r11\n"); + ot(" add r0,r11,r10 ;@ Memory Base + New PC\n"); + ot("\n"); + CheckPc(); if (!(op&0x40)) { - ot(";@ Jsr - Push old PC first\n"); - ot(" ldr r0,[r7,#0x3c]\n"); + ot(" ldr r2,[r7,#0x3c]\n"); ot(" sub r1,r4,r10 ;@ r1 = Old PC\n"); - ot(";@ Push r1 onto stack\n"); - ot(" sub r0,r0,#4 ;@ Predecrement A7\n"); + } +#if EMULATE_ADDRESS_ERRORS_JUMP + // jsr prefetches next instruction before pushing old PC, + // according to http://pasti.fxatari.com/68kdocs/68kPrefetch.html + ot(" mov r4,r0\n"); + ot(" tst r4,#1 ;@ address error?\n"); + ot(" bne ExceptionAddressError_r_prg_r4\n"); +#else + ot(" bic r4,r0,#1\n"); +#endif + + if (!(op&0x40)) + { + ot(";@ Push old PC onto stack\n"); + ot(" sub r0,r2,#4 ;@ Predecrement A7\n"); ot(" str r0,[r7,#0x3c] ;@ Save A7\n"); MemHandler(1,2); } - ot(";@ Jump - Get new PC from r11\n"); - ot(" add r0,r11,r10 ;@ Memory Base + New PC\n"); - ot("\n"); - - CheckPc(0); Cycles=(op&0x40) ? 4 : 12; Cycles+=Ea_add_ns((op&0x40) ? g_jmp_cycle_table : g_jsr_cycle_table, sea); @@ -310,14 +342,21 @@ int OpDbra(int op) ot(";@ Check if Dn.w is -1\n"); ot(" cmn r0,#1\n"); -#if USE_CHECKPC_CALLBACK && USE_CHECKPC_DBRA +#if (USE_CHECKPC_CALLBACK && USE_CHECKPC_DBRA) || EMULATE_ADDRESS_ERRORS_JUMP ot(" beq DbraMin1\n"); ot("\n"); ot(";@ Get Branch offset:\n"); ot(" ldrsh r0,[r4]\n"); - ot(" add r0,r4,r0 ;@ r4 = New PC\n"); - CheckPc(0); + ot(" add r0,r4,r0 ;@ r0 = New PC\n"); + CheckPc(); +#if EMULATE_ADDRESS_ERRORS_JUMP + ot(" mov r4,r0\n"); + ot(" tst r4,#1 ;@ address error?\n"); + ot(" bne ExceptionAddressError_r_prg_r4\n"); +#else + ot(" bic r4,r0,#1\n"); +#endif #else ot("\n"); ot(";@ Get Branch offset:\n"); @@ -343,7 +382,7 @@ int OpDbra(int op) OpEnd(); } -#if USE_CHECKPC_CALLBACK && USE_CHECKPC_DBRA +#if (USE_CHECKPC_CALLBACK && USE_CHECKPC_DBRA) || EMULATE_ADDRESS_ERRORS_JUMP if (op==0x51c8) { ot(";@ Dn.w is -1:\n"); @@ -451,15 +490,15 @@ int OpBranch(int op) #if USE_CHECKPC_CALLBACK if (offset==-1) checkpc=1; #endif - if (checkpc) - { - CheckPc(0); - } - else - { - ot(" bic r4,r0,#1\n"); // we do not emulate address errors - ot("\n"); - } + if (checkpc) CheckPc(); +#if EMULATE_ADDRESS_ERRORS_JUMP + ot(" mov r4,r0\n"); + ot(" tst r4,#1 ;@ address error?\n"); + ot(" bne ExceptionAddressError_r_prg_r4\n"); +#else + ot(" bic r4,r0,#1\n"); +#endif + ot("\n"); OpEnd(size?0x10:0); diff --git a/cpu/Cyclone/OpLogic.cpp b/cpu/Cyclone/OpLogic.cpp index a081605e..4bd08695 100644 --- a/cpu/Cyclone/OpLogic.cpp +++ b/cpu/Cyclone/OpLogic.cpp @@ -207,6 +207,7 @@ int OpNeg(int op) ot("\n"); } + if (type==1) eawrite_check_addrerr=1; EaWrite(10, 1,ea,size,0x003f,0,0); OpEnd(ea); @@ -360,10 +361,12 @@ int OpSet(int op) ot("\n"); + eawrite_check_addrerr=1; EaCalc (0,0x003f, ea,size,0,0); EaWrite(0, 1, ea,size,0x003f,0,0); - OpEnd(ea,0,changed_cycles); + opend_op_changes_cycles=changed_cycles; + OpEnd(ea,0); return 0; } @@ -614,7 +617,8 @@ int OpAsr(int op) EaWrite(10, 0, ea,size,0x0007,1); - OpEnd(ea,0,count<0); + opend_op_changes_cycles = (count<0); + OpEnd(ea,0); return 0; } diff --git a/cpu/Cyclone/OpMove.cpp b/cpu/Cyclone/OpMove.cpp index 64c0f59f..b5dc0ce5 100644 --- a/cpu/Cyclone/OpMove.cpp +++ b/cpu/Cyclone/OpMove.cpp @@ -21,7 +21,7 @@ void OpFlagsToReg(int high) // Convert SR/CRR register in r0 to our flags // trashes r0,r1 -void OpRegToFlags(int high) +void OpRegToFlags(int high, int srh_reg) { ot(" eor r1,r0,r0,ror #1 ;@ Bit 0=C^V\n"); ot(" mov r2,r0,lsl #25\n"); @@ -32,9 +32,10 @@ void OpRegToFlags(int high) if (high) { - ot(" mov r0,r0,ror #8\n"); - ot(" and r0,r0,#0xa7 ;@ only take defined bits\n"); - ot(" strb r0,[r7,#0x44] ;@ Store SR high\n"); + int mask=EMULATE_TRACE?0xa7:0x27; + ot(" mov r%i,r0,ror #8\n",srh_reg); + ot(" and r%i,r%i,#0x%02x ;@ only take defined bits\n",srh_reg,srh_reg,mask); + ot(" strb r%i,[r7,#0x44] ;@ Store SR high\n",srh_reg); } ot("\n"); } @@ -44,8 +45,15 @@ void SuperEnd(void) ot(";@ ----------\n"); ot(";@ tried execute privileged instruction in user mode\n"); ot("WrongPrivilegeMode%s\n",ms?"":":"); +#if EMULATE_ADDRESS_ERRORS_JUMP || EMULATE_ADDRESS_ERRORS_IO + ot(" ldr r1,[r7,#0x58]\n"); ot(" sub r4,r4,#2 ;@ last opcode wasn't executed - go back\n"); - ot(" mov r0,#0x20 ;@ privilege violation\n"); + ot(" orr r1,r1,#4 ;@ set activity bit: 'not processing instruction'\n"); + ot(" str r1,[r7,#0x58]\n"); +#else + ot(" sub r4,r4,#2 ;@ last opcode wasn't executed - go back\n"); +#endif + ot(" mov r0,#8 ;@ privilege violation\n"); ot(" bl Exception\n"); Cycles=34; OpEnd(0); @@ -53,13 +61,15 @@ void SuperEnd(void) // does OSP and A7 swapping if needed // new or old SR (not the one already in [r7,#0x44]) should be passed in r11 -// trashes r0,r11 -void SuperChange(int op,int load_srh) +// uses srh from srh_reg (loads if < 0), trashes r0,r11 +void SuperChange(int op,int srh_reg) { ot(";@ A7 <-> OSP?\n"); - if (load_srh) + if (srh_reg < 0) { ot(" ldr r0,[r7,#0x44] ;@ Get other SR high\n"); - ot(" eor r0,r0,r11\n"); + srh_reg=0; + } + ot(" eor r0,r%i,r11\n",srh_reg); ot(" tst r0,#0x20\n"); ot(" beq no_sp_swap%.4x\n",op); ot(" ;@ swap OSP and A7:\n"); @@ -122,6 +132,7 @@ int OpMove(int op) if (movea) size=2; // movea always expands to 32-bits + eawrite_check_addrerr=1; #if SPLIT_MOVEL_PD if ((tea&0x38)==0x20 && size==2) { // -(An) EaCalc (10,0x0e00,tea,size,0,0); @@ -167,6 +178,7 @@ int OpLea(int op) OpStart(op,sea,tea); + eawrite_check_addrerr=1; EaCalc (1,0x003f,sea,0); // Lea EaCalc (0,0x0e00,tea,2); EaWrite(0, 1,tea,2,0x0e00); @@ -214,6 +226,7 @@ int OpMoveSr(int op) if (type==0 || type==1) { + eawrite_check_addrerr=1; OpFlagsToReg(type==0); EaCalc (0,0x003f,ea,size,0,0); EaWrite(0, 1,ea,size,0x003f,0,0); @@ -222,13 +235,17 @@ int OpMoveSr(int op) if (type==2 || type==3) { EaCalcReadNoSE(-1,0,ea,size,0x003f); - OpRegToFlags(type==3); + OpRegToFlags(type==3,1); if (type==3) { - SuperChange(op,0); + SuperChange(op,1); + opend_check_interrupt = 1; + opend_check_trace = 1; + OpEnd(ea); + return 0; } } - OpEnd(ea,0,0,type==3); + OpEnd(ea); return 0; } @@ -239,6 +256,7 @@ int OpArithSr(int op) { int type=0,ea=0; int use=0,size=0; + int sr_mask=EMULATE_TRACE?0xa7:0x27; type=(op>>9)&5; if (type==4) return 1; size=(op>>6)&1; // ccr or sr? @@ -249,19 +267,53 @@ int OpArithSr(int op) OpStart(op,ea,0,0,size!=0); Cycles=16; - EaCalc(10,0x003f,ea,size); - EaRead(10, 10,ea,size,0x003f); + EaCalcRead(-1,0,ea,size,0x003f); - OpFlagsToReg(size); - if (type==0) ot(" orr r0,r1,r10\n"); - if (type==1) ot(" and r0,r1,r10\n"); - if (type==5) ot(" eor r0,r1,r10\n"); - OpRegToFlags(size); - if (size && type!=0) { // we can't enter supervisor mode, nor unmask irqs just by using OR - SuperChange(op,0); + ot(" eor r1,r0,r0,ror #1 ;@ Bit 0=C^V\n"); + ot(" tst r1,#1 ;@ 1 if C!=V\n"); + ot(" eorne r0,r0,#3 ;@ ___XNZCV\n"); + ot(" ldr r2,[r7,#0x4c] ;@ Load old X bit\n"); + + // note: old srh is already in r11 (done by OpStart) + if (type==0) { + ot(" orr r9,r9,r0,lsl #28\n"); + ot(" orr r2,r2,r0,lsl #25 ;@ X bit\n"); + if (size!=0) { + ot(" orr r1,r11,r0,lsr #8\n"); + ot(" and r1,r1,#0x%02x ;@ mask-out unused bits\n",sr_mask); + } + } + if (type==1) { + ot(" and r9,r9,r0,lsl #28\n"); + ot(" and r2,r2,r0,lsl #25 ;@ X bit\n"); + if (size!=0) + ot(" and r1,r11,r0,lsr #8\n"); + } + if (type==5) { + ot(" eor r9,r9,r0,lsl #28\n"); + ot(" eor r2,r2,r0,lsl #25 ;@ X bit\n"); + if (size!=0) { + ot(" eor r1,r11,r0,lsr #8\n"); + ot(" and r1,r1,#0x%02x ;@ mask-out unused bits\n",sr_mask); + } } - OpEnd(ea,0,0,size!=0 && type!=0); + ot(" str r2,[r7,#0x4c] ;@ Save X bit\n"); + if (size!=0) + ot(" strb r1,[r7,#0x44]\n"); + ot("\n"); + + // we can't enter supervisor mode, nor unmask irqs just by using OR + if (size!=0 && type!=0) { + SuperChange(op,1); + ot("\n"); + opend_check_interrupt = 1; + } + // also can't set trace bit with AND + if (size!=0 && type!=1) + opend_check_trace = 1; + + OpEnd(ea); return 0; } @@ -338,6 +390,13 @@ int OpMovem(int op) ot(" tst r11,r11\n"); // sanity check ot(" beq NoRegs%.4x\n",op); +#if EMULATE_ADDRESS_ERRORS_IO + ot("\n"); + ot(" tst r6,#1 ;@ address error?\n"); + ot(" movne r0,r6\n"); + ot(" bne ExceptionAddressError_%c_data\n",dir?'r':'w'); +#endif + ot("\n"); ot("Movemloop%.4x%s\n",op, ms?"":":"); ot(" add r10,r10,#%d ;@ r10=Next Register\n",decr?-4:4); @@ -350,6 +409,7 @@ int OpMovem(int op) if (dir) { ot(" ;@ Copy memory to register:\n",1< #include -#include "config.h" +#ifndef CONFIG_FILE +#define CONFIG_FILE "config.h" +#endif +#include CONFIG_FILE // Disa.c #include "Disa/Disa.h" // Ea.cpp +extern int earead_check_addrerr; +extern int eawrite_check_addrerr; extern int g_jmp_cycle_table[]; extern int g_jsr_cycle_table[]; extern int g_lea_cycle_table[]; @@ -35,15 +40,16 @@ extern int pc_dirty; // something changed PC during processing extern int arm_op_count; // for stats void ot(const char *format, ...); void ltorg(); -int MemHandler(int type,int size,int addrreg=0); +int MemHandler(int type,int size,int addrreg=0,int need_addrerr_check=1); void FlushPC(void); // OpAny.cpp extern int g_op; +extern int opend_op_changes_cycles, opend_check_interrupt, opend_check_trace; int OpGetFlags(int subtract,int xbit,int sprecialz=0); void OpUse(int op,int use); void OpStart(int op,int sea=0,int tea=0,int op_changes_cycles=0,int supervisor_check=0); -void OpEnd(int sea=0,int tea=0,int op_changes_cycles=0,int check_interrupt=0); +void OpEnd(int sea=0,int tea=0); int OpBase(int op,int size,int sepa=0); void OpAny(int op); @@ -90,7 +96,7 @@ int OpTas(int op, int gen_special=0); int OpMove(int op); int OpLea(int op); void OpFlagsToReg(int high); -void OpRegToFlags(int high); +void OpRegToFlags(int high,int srh_reg=0); int OpMoveSr(int op); int OpArithSr(int op); int OpPea(int op); @@ -101,5 +107,5 @@ int OpExg(int op); int OpMovep(int op); int OpStopReset(int op); void SuperEnd(void); -void SuperChange(int op,int load_srh=1); +void SuperChange(int op,int srh_reg=-1); diff --git a/cpu/Cyclone/config.h b/cpu/Cyclone/config.h index 24b73a1c..6ec08f89 100644 --- a/cpu/Cyclone/config.h +++ b/cpu/Cyclone/config.h @@ -6,10 +6,10 @@ /* - * If this option is enabled, Microsoft ARMASM compatible output is generated. - * Otherwise GNU as syntax is used. + * If this option is enabled, Microsoft ARMASM compatible output is generated + * (output file - Cyclone.asm). Otherwise GNU as syntax is used (Cyclone.s). */ -#define USE_MS_SYNTAX 0 +#define USE_MS_SYNTAX 0 /* * Enable this option if you are going to use Cyclone to emulate Genesis / @@ -18,7 +18,7 @@ * the write-back phase. That will be emulated, if this option is enabled. * This option also alters timing slightly. */ -#define CYCLONE_FOR_GENESIS 0 +#define CYCLONE_FOR_GENESIS 0 /* * This option compresses Cyclone's jumptable. Because of this the executable @@ -27,7 +27,7 @@ * Warning: if you enable this, you MUST call CycloneInit() before calling * CycloneRun(), or else it will crash. */ -#define COMPRESS_JUMPTABLE 1 +#define COMPRESS_JUMPTABLE 1 /* * Address mask for memory hadlers. The bits set will be masked out of address @@ -35,51 +35,62 @@ * Using 0xff000000 means that only 24 least significant bits should be used. * Set to 0 if you want to mask unused address bits in the memory handlers yourself. */ -#define MEMHANDLERS_ADDR_MASK 0 +#define MEMHANDLERS_ADDR_MASK 0 /* * Cyclone keeps the 4 least significant bits of SR, PC+membase and it's cycle - * count in ARM registers instead of the context for performance reasons. If you for + * counter in ARM registers instead of the context for performance reasons. If you for * any reason need to access them in your memory handlers, enable the options below, * otherwise disable them to improve performance. + * * MEMHANDLERS_NEED_PC updates .pc context field with PC value effective at the time - * when memhandler was called (opcode address + unknown amount). + * when memhandler was called (opcode address + 2-10 bytes). * MEMHANDLERS_NEED_PREV_PC updates .prev_pc context field to currently executed * opcode address + 2. * Note that .pc and .prev_pc values are always real pointers to memory, so you must * subtract .membase to get M68k PC value. + * * Warning: updating PC in memhandlers is dangerous, as Cyclone may internally * increment the PC before fetching the next instruction and continue executing - * at wrong location. + * at wrong location. It's better to wait until Cyclone CycloneRun() finishes. + * + * Warning: if you enable MEMHANDLERS_CHANGE_CYCLES, you must also enable + * MEMHANDLERS_NEED_CYCLES, or else Cyclone will keep reloading the same cycle + * count and this will screw timing (if not cause a deadlock). */ -#define MEMHANDLERS_NEED_PC 1 -#define MEMHANDLERS_NEED_PREV_PC 0 -#define MEMHANDLERS_NEED_FLAGS 0 -#define MEMHANDLERS_NEED_CYCLES 1 -#define MEMHANDLERS_CHANGE_PC 0 -#define MEMHANDLERS_CHANGE_FLAGS 0 -#define MEMHANDLERS_CHANGE_CYCLES 1 +#define MEMHANDLERS_NEED_PC 0 +#define MEMHANDLERS_NEED_PREV_PC 0 +#define MEMHANDLERS_NEED_FLAGS 0 +#define MEMHANDLERS_NEED_CYCLES 0 +#define MEMHANDLERS_CHANGE_PC 0 +#define MEMHANDLERS_CHANGE_FLAGS 0 +#define MEMHANDLERS_CHANGE_CYCLES 0 /* - * If enabled, Cyclone will call IrqCallback routine from it's context whenever it - * acknowledges an IRQ. IRQ level is not cleared automatically, do this in your - * hadler if needed. PC, flags and cycles are valid in the context and can be read. - * If disabled, it simply clears the IRQ level and continues execution. + * If enabled, Cyclone will call .IrqCallback routine from it's context whenever it + * acknowledges an IRQ. IRQ level (.irq) is not cleared automatically, do this in your + * handler if needed. + * This function must either return vector number to use for interrupt exception, + * CYCLONE_INT_ACK_AUTOVECTOR to use autovector (this is the most common case), or + * CYCLONE_INT_ACK_SPURIOUS (least common case). + * If disabled, it simply uses appropriate autovector, clears the IRQ level and + * continues execution. */ -#define USE_INT_ACK_CALLBACK 1 +#define USE_INT_ACK_CALLBACK 0 /* - * Enable this if you need/change PC, flags or cycles in your IrqCallback function. + * Enable this if you need old PC, flags or cycles; + * or you change cycles in your IrqCallback function. */ -#define INT_ACK_NEEDS_STUFF 0 -#define INT_ACK_CHANGES_STUFF 0 +#define INT_ACK_NEEDS_STUFF 0 +#define INT_ACK_CHANGES_CYCLES 0 /* - * If enabled, ResetCallback is called from the context, whenever RESET opcode is + * If enabled, .ResetCallback is called from the context, whenever RESET opcode is * encountered. All context members are valid and can be changed. * If disabled, RESET opcode acts as an NOP. */ -#define USE_RESET_CALLBACK 0 +#define USE_RESET_CALLBACK 0 /* * If enabled, UnrecognizedCallback is called if an invalid opcode is @@ -91,34 +102,34 @@ * If disabled, "Illegal Instruction" exception is generated and execution is * continued. */ -#define USE_UNRECOGNIZED_CALLBACK 1 +#define USE_UNRECOGNIZED_CALLBACK 0 /* * This option will also call UnrecognizedCallback for a-line and f-line * (0xa*** and 0xf***) opcodes the same way as described above, only appropriate * exceptions will be generated. */ -#define USE_AFLINE_CALLBACK 1 +#define USE_AFLINE_CALLBACK 0 /* * This makes Cyclone to call checkpc from it's context whenever it changes the PC * by a large value. It takes and should return the PC value in PC+membase form. * The flags and cycle counter are not valid in this function. */ -#define USE_CHECKPC_CALLBACK 1 +#define USE_CHECKPC_CALLBACK 1 /* * This determines if checkpc() should be called after jumps when 8 and 16 bit * displacement values were used. */ -#define USE_CHECKPC_OFFSETBITS_16 1 -#define USE_CHECKPC_OFFSETBITS_8 0 +#define USE_CHECKPC_OFFSETBITS_16 1 +#define USE_CHECKPC_OFFSETBITS_8 0 /* * Call checkpc() after DBcc jumps (which use 16bit displacement). Cyclone prior to * 0.0087 never did that. */ -#define USE_CHECKPC_DBRA 0 +#define USE_CHECKPC_DBRA 0 /* * When this option is enabled Cyclone will do two word writes instead of one @@ -126,4 +137,36 @@ * Bart Trzynadlowski's doc (http://www.trzy.org/files/68knotes.txt). * Enable this if you are emulating a 16 bit system. */ -#define SPLIT_MOVEL_PD 1 +#define SPLIT_MOVEL_PD 1 + +/* + * Enable emulation of trace mode. Shouldn't cause any performance decrease, so it + * should be safe to keep this ON. + */ +#define EMULATE_TRACE 1 + +/* + * If enabled, address error exception will be generated if 68k code jumps to an + * odd address. Causes very small performance hit (2 ARM instructions for every + * emulated jump/return/exception in normal case). + * Note: checkpc() must not clear least significant bit of rebased address + * for this to work, as checks are performed after calling checkpc(). + */ +#define EMULATE_ADDRESS_ERRORS_JUMP 1 + +/* + * If enabled, address error exception will be generated if 68k code tries to + * access a word or longword at an odd address. The performance cost is also 2 ARM + * instructions per access (for address error checks). + */ +#define EMULATE_ADDRESS_ERRORS_IO 0 + +/* + * If an address error happens during another address error processing, + * the processor halts until it is reset (catastrophic system failure, as the manual + * states). This option enables halt emulation. + * Note that this might be not desired if it is known that emulated system should + * never reach this state. + */ +#define EMULATE_HALT 0 + diff --git a/cpu/Cyclone/config_mamegp2x.h b/cpu/Cyclone/config_mamegp2x.h new file mode 100644 index 00000000..307b9055 --- /dev/null +++ b/cpu/Cyclone/config_mamegp2x.h @@ -0,0 +1,172 @@ + + +/** + * Cyclone 68000 configuration file +**/ + + +/* + * If this option is enabled, Microsoft ARMASM compatible output is generated + * (output file - Cyclone.asm). Otherwise GNU as syntax is used (Cyclone.s). + */ +#define USE_MS_SYNTAX 0 + +/* + * Enable this option if you are going to use Cyclone to emulate Genesis / + * Mega Drive system. As VDP chip in these systems had control of the bus, + * several instructions were acting differently, for example TAS did'n have + * the write-back phase. That will be emulated, if this option is enabled. + * This option also alters timing slightly. + */ +#define CYCLONE_FOR_GENESIS 0 + +/* + * This option compresses Cyclone's jumptable. Because of this the executable + * will be smaller and load slightly faster and less relocations will be needed. + * This also fixes the crash problem with 0xfffe and 0xffff opcodes. + * Warning: if you enable this, you MUST call CycloneInit() before calling + * CycloneRun(), or else it will crash. + */ +#define COMPRESS_JUMPTABLE 1 + +/* + * Address mask for memory hadlers. The bits set will be masked out of address + * parameter, which is passed to r/w memory handlers. + * Using 0xff000000 means that only 24 least significant bits should be used. + * Set to 0 if you want to mask unused address bits in the memory handlers yourself. + */ +#define MEMHANDLERS_ADDR_MASK 0xff000000 + +/* + * Cyclone keeps the 4 least significant bits of SR, PC+membase and it's cycle + * counter in ARM registers instead of the context for performance reasons. If you for + * any reason need to access them in your memory handlers, enable the options below, + * otherwise disable them to improve performance. + * + * MEMHANDLERS_NEED_PC updates .pc context field with PC value effective at the time + * when memhandler was called (opcode address + 2-10 bytes). + * MEMHANDLERS_NEED_PREV_PC updates .prev_pc context field to currently executed + * opcode address + 2. + * Note that .pc and .prev_pc values are always real pointers to memory, so you must + * subtract .membase to get M68k PC value. + * + * Warning: updating PC in memhandlers is dangerous, as Cyclone may internally + * increment the PC before fetching the next instruction and continue executing + * at wrong location. It's better to wait until Cyclone CycloneRun() finishes. + * + * Warning: if you enable MEMHANDLERS_CHANGE_CYCLES, you must also enable + * MEMHANDLERS_NEED_CYCLES, or else Cyclone will keep reloading the same cycle + * count and this will screw timing (if not cause a deadlock). + */ +#define MEMHANDLERS_NEED_PC 1 +#define MEMHANDLERS_NEED_PREV_PC 1 +#define MEMHANDLERS_NEED_FLAGS 0 +#define MEMHANDLERS_NEED_CYCLES 1 +#define MEMHANDLERS_CHANGE_PC 0 +#define MEMHANDLERS_CHANGE_FLAGS 0 +#define MEMHANDLERS_CHANGE_CYCLES 1 + +/* + * If enabled, Cyclone will call .IrqCallback routine from it's context whenever it + * acknowledges an IRQ. IRQ level (.irq) is not cleared automatically, do this in your + * handler if needed. + * This function must either return vector number to use for interrupt exception, + * CYCLONE_INT_ACK_AUTOVECTOR to use autovector (this is the most common case), or + * CYCLONE_INT_ACK_SPURIOUS (least common case). + * If disabled, it simply uses appropriate autovector, clears the IRQ level and + * continues execution. + */ +#define USE_INT_ACK_CALLBACK 1 + +/* + * Enable this if you need old PC, flags or cycles; + * or you change cycles in your IrqCallback function. + */ +#define INT_ACK_NEEDS_STUFF 0 +#define INT_ACK_CHANGES_CYCLES 0 + +/* + * If enabled, .ResetCallback is called from the context, whenever RESET opcode is + * encountered. All context members are valid and can be changed. + * If disabled, RESET opcode acts as an NOP. + */ +#define USE_RESET_CALLBACK 0 + +/* + * If enabled, UnrecognizedCallback is called if an invalid opcode is + * encountered. All context members are valid and can be changed. The handler + * should return zero if you want Cyclone to gererate "Illegal Instruction" + * exception after this, or nonzero if not. In the later case you should change + * the PC by yourself, or else Cyclone will keep executing that opcode all over + * again. + * If disabled, "Illegal Instruction" exception is generated and execution is + * continued. + */ +#define USE_UNRECOGNIZED_CALLBACK 0 + +/* + * This option will also call UnrecognizedCallback for a-line and f-line + * (0xa*** and 0xf***) opcodes the same way as described above, only appropriate + * exceptions will be generated. + */ +#define USE_AFLINE_CALLBACK 0 + +/* + * This makes Cyclone to call checkpc from it's context whenever it changes the PC + * by a large value. It takes and should return the PC value in PC+membase form. + * The flags and cycle counter are not valid in this function. + */ +#define USE_CHECKPC_CALLBACK 1 + +/* + * This determines if checkpc() should be called after jumps when 8 and 16 bit + * displacement values were used. + */ +#define USE_CHECKPC_OFFSETBITS_16 1 +#define USE_CHECKPC_OFFSETBITS_8 0 + +/* + * Call checkpc() after DBcc jumps (which use 16bit displacement). Cyclone prior to + * 0.0087 never did that. + */ +#define USE_CHECKPC_DBRA 0 + +/* + * When this option is enabled Cyclone will do two word writes instead of one + * long write when handling MOVE.L with pre-decrementing destination, as described in + * Bart Trzynadlowski's doc (http://www.trzy.org/files/68knotes.txt). + * Enable this if you are emulating a 16 bit system. + */ +#define SPLIT_MOVEL_PD 1 + +/* + * Enable emulation of trace mode. Shouldn't cause any performance decrease, so it + * should be safe to keep this ON. + */ +#define EMULATE_TRACE 1 + +/* + * If enabled, address error exception will be generated if 68k code jumps to an + * odd address. Causes very small performance hit (2 ARM instructions for every + * emulated jump/return/exception in normal case). + * Note: checkpc() must not clear least significant bit of rebased address + * for this to work, as checks are performed after calling checkpc(). + */ +#define EMULATE_ADDRESS_ERRORS_JUMP 1 + +/* + * If enabled, address error exception will be generated if 68k code tries to + * access a word or longword at an odd address. The performance cost is also 2 ARM + * instructions per access (for address error checks). + */ +#define EMULATE_ADDRESS_ERRORS_IO 0 + +/* + * If an address error happens during another address error processing, + * the processor halts until it is reset (catastrophic system failure, as the manual + * states). This option enables halt emulation. + * Note that this might be not desired if it is known that emulated system should + * never reach this state. + */ +#define EMULATE_HALT 0 + diff --git a/cpu/Cyclone/config_pico.h b/cpu/Cyclone/config_pico.h new file mode 100644 index 00000000..6c002ae6 --- /dev/null +++ b/cpu/Cyclone/config_pico.h @@ -0,0 +1,172 @@ + + +/** + * Cyclone 68000 configuration file +**/ + + +/* + * If this option is enabled, Microsoft ARMASM compatible output is generated + * (output file - Cyclone.asm). Otherwise GNU as syntax is used (Cyclone.s). + */ +#define USE_MS_SYNTAX 0 + +/* + * Enable this option if you are going to use Cyclone to emulate Genesis / + * Mega Drive system. As VDP chip in these systems had control of the bus, + * several instructions were acting differently, for example TAS did'n have + * the write-back phase. That will be emulated, if this option is enabled. + * This option also alters timing slightly. + */ +#define CYCLONE_FOR_GENESIS 2 + +/* + * This option compresses Cyclone's jumptable. Because of this the executable + * will be smaller and load slightly faster and less relocations will be needed. + * This also fixes the crash problem with 0xfffe and 0xffff opcodes. + * Warning: if you enable this, you MUST call CycloneInit() before calling + * CycloneRun(), or else it will crash. + */ +#define COMPRESS_JUMPTABLE 1 + +/* + * Address mask for memory hadlers. The bits set will be masked out of address + * parameter, which is passed to r/w memory handlers. + * Using 0xff000000 means that only 24 least significant bits should be used. + * Set to 0 if you want to mask unused address bits in the memory handlers yourself. + */ +#define MEMHANDLERS_ADDR_MASK 0 + +/* + * Cyclone keeps the 4 least significant bits of SR, PC+membase and it's cycle + * counter in ARM registers instead of the context for performance reasons. If you for + * any reason need to access them in your memory handlers, enable the options below, + * otherwise disable them to improve performance. + * + * MEMHANDLERS_NEED_PC updates .pc context field with PC value effective at the time + * when memhandler was called (opcode address + 2-10 bytes). + * MEMHANDLERS_NEED_PREV_PC updates .prev_pc context field to currently executed + * opcode address + 2. + * Note that .pc and .prev_pc values are always real pointers to memory, so you must + * subtract .membase to get M68k PC value. + * + * Warning: updating PC in memhandlers is dangerous, as Cyclone may internally + * increment the PC before fetching the next instruction and continue executing + * at wrong location. It's better to wait until Cyclone CycloneRun() finishes. + * + * Warning: if you enable MEMHANDLERS_CHANGE_CYCLES, you must also enable + * MEMHANDLERS_NEED_CYCLES, or else Cyclone will keep reloading the same cycle + * count and this will screw timing (if not cause a deadlock). + */ +#define MEMHANDLERS_NEED_PC 0 +#define MEMHANDLERS_NEED_PREV_PC 0 +#define MEMHANDLERS_NEED_FLAGS 0 +#define MEMHANDLERS_NEED_CYCLES 1 +#define MEMHANDLERS_CHANGE_PC 0 +#define MEMHANDLERS_CHANGE_FLAGS 0 +#define MEMHANDLERS_CHANGE_CYCLES 0 + +/* + * If enabled, Cyclone will call .IrqCallback routine from it's context whenever it + * acknowledges an IRQ. IRQ level (.irq) is not cleared automatically, do this in your + * handler if needed. + * This function must either return vector number to use for interrupt exception, + * CYCLONE_INT_ACK_AUTOVECTOR to use autovector (this is the most common case), or + * CYCLONE_INT_ACK_SPURIOUS (least common case). + * If disabled, it simply uses appropriate autovector, clears the IRQ level and + * continues execution. + */ +#define USE_INT_ACK_CALLBACK 1 + +/* + * Enable this if you need old PC, flags or cycles; + * or you change cycles in your IrqCallback function. + */ +#define INT_ACK_NEEDS_STUFF 0 +#define INT_ACK_CHANGES_CYCLES 0 + +/* + * If enabled, .ResetCallback is called from the context, whenever RESET opcode is + * encountered. All context members are valid and can be changed. + * If disabled, RESET opcode acts as an NOP. + */ +#define USE_RESET_CALLBACK 1 + +/* + * If enabled, UnrecognizedCallback is called if an invalid opcode is + * encountered. All context members are valid and can be changed. The handler + * should return zero if you want Cyclone to gererate "Illegal Instruction" + * exception after this, or nonzero if not. In the later case you should change + * the PC by yourself, or else Cyclone will keep executing that opcode all over + * again. + * If disabled, "Illegal Instruction" exception is generated and execution is + * continued. + */ +#define USE_UNRECOGNIZED_CALLBACK 1 + +/* + * This option will also call UnrecognizedCallback for a-line and f-line + * (0xa*** and 0xf***) opcodes the same way as described above, only appropriate + * exceptions will be generated. + */ +#define USE_AFLINE_CALLBACK 1 + +/* + * This makes Cyclone to call checkpc from it's context whenever it changes the PC + * by a large value. It takes and should return the PC value in PC+membase form. + * The flags and cycle counter are not valid in this function. + */ +#define USE_CHECKPC_CALLBACK 1 + +/* + * This determines if checkpc() should be called after jumps when 8 and 16 bit + * displacement values were used. + */ +#define USE_CHECKPC_OFFSETBITS_16 1 +#define USE_CHECKPC_OFFSETBITS_8 0 + +/* + * Call checkpc() after DBcc jumps (which use 16bit displacement). Cyclone prior to + * 0.0087 never did that. + */ +#define USE_CHECKPC_DBRA 0 + +/* + * When this option is enabled Cyclone will do two word writes instead of one + * long write when handling MOVE.L with pre-decrementing destination, as described in + * Bart Trzynadlowski's doc (http://www.trzy.org/files/68knotes.txt). + * Enable this if you are emulating a 16 bit system. + */ +#define SPLIT_MOVEL_PD 1 + +/* + * Enable emulation of trace mode. Shouldn't cause any performance decrease, so it + * should be safe to keep this ON. + */ +#define EMULATE_TRACE 1 + +/* + * If enabled, address error exception will be generated if 68k code jumps to an + * odd address. Causes very small performance hit (2 ARM instructions for every + * emulated jump/return/exception in normal case). + * Note: checkpc() must not clear least significant bit of rebased address + * for this to work, as checks are performed after calling checkpc(). + */ +#define EMULATE_ADDRESS_ERRORS_JUMP 1 + +/* + * If enabled, address error exception will be generated if 68k code tries to + * access a word or longword at an odd address. The performance cost is also 2 ARM + * instructions per access (for address error checks). + */ +#define EMULATE_ADDRESS_ERRORS_IO 0 + +/* + * If an address error happens during another address error processing, + * the processor halts until it is reset (catastrophic system failure, as the manual + * states). This option enables halt emulation. + * Note that this might be not desired if it is known that emulated system should + * never reach this state. + */ +#define EMULATE_HALT 0 + diff --git a/cpu/Cyclone/config_uae4all.h b/cpu/Cyclone/config_uae4all.h new file mode 100644 index 00000000..d1f68c61 --- /dev/null +++ b/cpu/Cyclone/config_uae4all.h @@ -0,0 +1,172 @@ + + +/** + * Cyclone 68000 configuration file +**/ + + +/* + * If this option is enabled, Microsoft ARMASM compatible output is generated + * (output file - Cyclone.asm). Otherwise GNU as syntax is used (Cyclone.s). + */ +#define USE_MS_SYNTAX 0 + +/* + * Enable this option if you are going to use Cyclone to emulate Genesis / + * Mega Drive system. As VDP chip in these systems had control of the bus, + * several instructions were acting differently, for example TAS did'n have + * the write-back phase. That will be emulated, if this option is enabled. + * This option also alters timing slightly. + */ +#define CYCLONE_FOR_GENESIS 0 + +/* + * This option compresses Cyclone's jumptable. Because of this the executable + * will be smaller and load slightly faster and less relocations will be needed. + * This also fixes the crash problem with 0xfffe and 0xffff opcodes. + * Warning: if you enable this, you MUST call CycloneInit() before calling + * CycloneRun(), or else it will crash. + */ +#define COMPRESS_JUMPTABLE 1 + +/* + * Address mask for memory hadlers. The bits set will be masked out of address + * parameter, which is passed to r/w memory handlers. + * Using 0xff000000 means that only 24 least significant bits should be used. + * Set to 0 if you want to mask unused address bits in the memory handlers yourself. + */ +#define MEMHANDLERS_ADDR_MASK 0 + +/* + * Cyclone keeps the 4 least significant bits of SR, PC+membase and it's cycle + * counter in ARM registers instead of the context for performance reasons. If you for + * any reason need to access them in your memory handlers, enable the options below, + * otherwise disable them to improve performance. + * + * MEMHANDLERS_NEED_PC updates .pc context field with PC value effective at the time + * when memhandler was called (opcode address + 2-10 bytes). + * MEMHANDLERS_NEED_PREV_PC updates .prev_pc context field to currently executed + * opcode address + 2. + * Note that .pc and .prev_pc values are always real pointers to memory, so you must + * subtract .membase to get M68k PC value. + * + * Warning: updating PC in memhandlers is dangerous, as Cyclone may internally + * increment the PC before fetching the next instruction and continue executing + * at wrong location. It's better to wait until Cyclone CycloneRun() finishes. + * + * Warning: if you enable MEMHANDLERS_CHANGE_CYCLES, you must also enable + * MEMHANDLERS_NEED_CYCLES, or else Cyclone will keep reloading the same cycle + * count and this will screw timing (if not cause a deadlock). + */ +#define MEMHANDLERS_NEED_PC 1 +#define MEMHANDLERS_NEED_PREV_PC 0 +#define MEMHANDLERS_NEED_FLAGS 0 +#define MEMHANDLERS_NEED_CYCLES 1 +#define MEMHANDLERS_CHANGE_PC 0 +#define MEMHANDLERS_CHANGE_FLAGS 0 +#define MEMHANDLERS_CHANGE_CYCLES 1 + +/* + * If enabled, Cyclone will call .IrqCallback routine from it's context whenever it + * acknowledges an IRQ. IRQ level (.irq) is not cleared automatically, do this in your + * handler if needed. + * This function must either return vector number to use for interrupt exception, + * CYCLONE_INT_ACK_AUTOVECTOR to use autovector (this is the most common case), or + * CYCLONE_INT_ACK_SPURIOUS (least common case). + * If disabled, it simply uses appropriate autovector, clears the IRQ level and + * continues execution. + */ +#define USE_INT_ACK_CALLBACK 1 + +/* + * Enable this if you need old PC, flags or cycles; + * or you change cycles in your IrqCallback function. + */ +#define INT_ACK_NEEDS_STUFF 0 +#define INT_ACK_CHANGES_CYCLES 0 + +/* + * If enabled, .ResetCallback is called from the context, whenever RESET opcode is + * encountered. All context members are valid and can be changed. + * If disabled, RESET opcode acts as an NOP. + */ +#define USE_RESET_CALLBACK 0 + +/* + * If enabled, UnrecognizedCallback is called if an invalid opcode is + * encountered. All context members are valid and can be changed. The handler + * should return zero if you want Cyclone to gererate "Illegal Instruction" + * exception after this, or nonzero if not. In the later case you should change + * the PC by yourself, or else Cyclone will keep executing that opcode all over + * again. + * If disabled, "Illegal Instruction" exception is generated and execution is + * continued. + */ +#define USE_UNRECOGNIZED_CALLBACK 1 + +/* + * This option will also call UnrecognizedCallback for a-line and f-line + * (0xa*** and 0xf***) opcodes the same way as described above, only appropriate + * exceptions will be generated. + */ +#define USE_AFLINE_CALLBACK 1 + +/* + * This makes Cyclone to call checkpc from it's context whenever it changes the PC + * by a large value. It takes and should return the PC value in PC+membase form. + * The flags and cycle counter are not valid in this function. + */ +#define USE_CHECKPC_CALLBACK 1 + +/* + * This determines if checkpc() should be called after jumps when 8 and 16 bit + * displacement values were used. + */ +#define USE_CHECKPC_OFFSETBITS_16 1 +#define USE_CHECKPC_OFFSETBITS_8 0 + +/* + * Call checkpc() after DBcc jumps (which use 16bit displacement). Cyclone prior to + * 0.0087 never did that. + */ +#define USE_CHECKPC_DBRA 0 + +/* + * When this option is enabled Cyclone will do two word writes instead of one + * long write when handling MOVE.L with pre-decrementing destination, as described in + * Bart Trzynadlowski's doc (http://www.trzy.org/files/68knotes.txt). + * Enable this if you are emulating a 16 bit system. + */ +#define SPLIT_MOVEL_PD 1 + +/* + * Enable emulation of trace mode. Shouldn't cause any performance decrease, so it + * should be safe to keep this ON. + */ +#define EMULATE_TRACE 1 + +/* + * If enabled, address error exception will be generated if 68k code jumps to an + * odd address. Causes very small performance hit (2 ARM instructions for every + * emulated jump/return/exception in normal case). + * Note: checkpc() must not clear least significant bit of rebased address + * for this to work, as checks are performed after calling checkpc(). + */ +#define EMULATE_ADDRESS_ERRORS_JUMP 1 + +/* + * If enabled, address error exception will be generated if 68k code tries to + * access a word or longword at an odd address. The performance cost is also 2 ARM + * instructions per access (for address error checks). + */ +#define EMULATE_ADDRESS_ERRORS_IO 1 + +/* + * If an address error happens during another address error processing, + * the processor halts until it is reset (catastrophic system failure, as the manual + * states). This option enables halt emulation. + * Note that this might be not desired if it is known that emulated system should + * never reach this state. + */ +#define EMULATE_HALT 0 + diff --git a/cpu/Cyclone/proj/Makefile b/cpu/Cyclone/proj/Makefile index e9da743a..55e44865 100644 --- a/cpu/Cyclone/proj/Makefile +++ b/cpu/Cyclone/proj/Makefile @@ -1,4 +1,7 @@ CFLAGS = -Wall +ifdef CONFIG_FILE +CFLAGS += -DCONFIG_FILE=\"$(CONFIG_FILE)\" +endif all : cyclone.s diff --git a/cpu/Cyclone/proj/Makefile.win b/cpu/Cyclone/proj/Makefile.win index 20d489ba..758352b9 100644 --- a/cpu/Cyclone/proj/Makefile.win +++ b/cpu/Cyclone/proj/Makefile.win @@ -1,7 +1,7 @@ # Makefile for MS Visual C CPP=cl.exe -CPP_PROJ=/nologo /ML /W4 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" $(RC_FLAGS) /YX /FD /c +CPP_PROJ=/nologo /W4 /O2 /D "_CRT_SECURE_NO_WARNINGS" /c LINK32=link.exe LINK32_FLAGS=user32.lib /nologo /subsystem:console /machine:I386 /out:Cyclone.exe @@ -37,7 +37,7 @@ OpMove.obj : ..\OpMove.cpp ..\app.h $(CPP) $(CPP_PROJ) ..\OpMove.cpp Disa.obj : ..\disa\Disa.c ..\disa\Disa.h - $(CPP) /nologo /ML /W4 /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /c ..\disa\Disa.c + $(CPP) $(CPP_PROJ) ..\disa\Disa.c ..\app.h : ..\config.h @@ -56,5 +56,4 @@ CLEAN : -@erase "Cyclone.exe" -@erase "Cyclone.asm" -@erase "Cyclone.s" - -@erase "Cyclone.o" diff --git a/cpu/Cyclone/proj/cyclone.dsp b/cpu/Cyclone/proj/cyclone.dsp index 803ad477..1e667256 100644 --- a/cpu/Cyclone/proj/cyclone.dsp +++ b/cpu/Cyclone/proj/cyclone.dsp @@ -41,8 +41,8 @@ RSC=rc.exe # PROP Intermediate_Dir "Release" # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" -# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c -# ADD CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD BASE CPP /nologo /W4 /O2 /D "_CRT_SECURE_NO_WARNINGS" /c +# ADD CPP /nologo /W4 /O2 /D "_CRT_SECURE_NO_WARNINGS" /c # ADD BASE RSC /l 0x427 /d "NDEBUG" # ADD RSC /l 0x427 /d "NDEBUG" BSC32=bscmake.exe @@ -65,8 +65,8 @@ LINK32=link.exe # PROP Intermediate_Dir "Debug" # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" -# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c -# ADD CPP /nologo /W4 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD BASE CPP /nologo /W4 /O2 /D "_CRT_SECURE_NO_WARNINGS" /c +# ADD CPP /nologo /W4 /O2 /D "_CRT_SECURE_NO_WARNINGS" /c # ADD BASE RSC /l 0x427 /d "_DEBUG" # ADD RSC /l 0x427 /d "_DEBUG" BSC32=bscmake.exe diff --git a/cpu/Cyclone/tests/test_trace.bin b/cpu/Cyclone/tests/test_trace.bin new file mode 100755 index 00000000..e9ca5fa9 Binary files /dev/null and b/cpu/Cyclone/tests/test_trace.bin differ diff --git a/cpu/Cyclone/tests/test_trace.s b/cpu/Cyclone/tests/test_trace.s new file mode 100644 index 00000000..b6d8421d --- /dev/null +++ b/cpu/Cyclone/tests/test_trace.s @@ -0,0 +1,140 @@ +| Processor: 68K +| Target Assembler: 680x0 Assembler by GNU project + +| ___________________________________________________________________________ + +| Segment type: Pure code +| segment "ROM" +dword_0: .long 0 | DATA XREF: ROM:00007244r + | sub_764E+3Eo ... + | initial interrupt stack pointer +dword_4: .long _start | DATA XREF: ROM:00007248r + | ROM:000142C2w + | reset initial PC +dword_8: .long 0x4DE | DATA XREF: sub_20050+B54w + .long 0x490 + .long 0x4AA | illegal instruction + .long 0x4C4 + .long 0x4DE + .long 0x4DE + .long 0x4DE + .long _trace | trace + .long 0x4DE + .long 0x4DE + .long 0x4DE + .long 0x4DE + .long 0x4DE + .long 0x4DE + .long 0x4DE + .long 0x4DE + .long 0x4DE + .long 0x4DE + .long 0x4DE + .long 0x4DE + .long 0x4DE + .long 0x4DE + .long 0x4DE + .long 0x548 | Level 1 Interrupt Autovector + .long 0x548 | 2 = ext interrupt + .long 0x548 + .long 0x592 | 4 = horizontal interrupt? + .long 0x548 + .long 0x594 | 6 = verticai interrupt? + .long 0x552 +dword_80: .long 0x45C | DATA XREF: ROM:00152F29o + | trap vector table? trap 0? + .long 0x1738 + .long 0x171C + .long 0x1754 + .long 0x1700 + .long 0x556 + .long 0x57A + .long 0x548 + .long 0x548 + .long 0x7CE | 9 + .long 0x548 + .long 0x548 + .long 0x548 + .long 0x548 + .long 0x548 + .long 0x548 + .long 0x4DE + .long 0x4DE + .long 0x4DE + .long 0x4DE + .long 0x4DE + .long 0x4DE + .long 0x4DE + .long 0x4DE + .long 0x4DE + .long 0x4DE + .long 0x4DE + .long 0x4DE + .long 0x4DE + .long 0x4DE + .long 0x4DE + .long 0x4DE +aSegaGenesis: .ascii "SEGA GENESIS " | DATA XREF: ROM:00045C6Ao +aCSega1994_jul: .ascii "(C)SEGA 1994.JUL" +aDumpedByTsd: .ascii "Dumped By TSD " +aShiningForce2: .ascii "SHINING FORCE 2 " +aGmMk131500: .ascii "GM MK-1315 -00" + .word 0x8921 | checksum +aJ: .ascii "J " | IO_Support + .long 0 | Rom_Start_Adress +dword_1A4: .long 0x1FFFFF | DATA XREF: sub_28008+F66o + | Rom_End_Adress + .long 0xFF0000 | Ram_Start_Adress + .long 0xFFFFFF | Ram_End_Adress +aRaa: .ascii "RA° "<0>" "<0><1><0>" ?"<0xFF> | Modem_Infos + .ascii " " + .byte 0x20 | + .byte 0x20 | + .byte 0x20 | + .byte 0x20 | + .byte 0x20 | + .byte 0x20 | + .byte 0x20 | + .byte 0x20 | + .byte 0x20 | + .byte 0x20 | + .byte 0x20 | + .byte 0x20 | +aU: .ascii "U " | Countries + .byte 0x20 | + .byte 0x20 | + .byte 0x20 | + .byte 0x20 | + .byte 0x20 | + .byte 0x20 | + .byte 0x20 | + .byte 0x20 | + .byte 0x20 | + .byte 0x20 | + .byte 0x20 | + .byte 0x20 | + .byte 0x20 | +_trace: + nop + nop + rte + +.globl _start +_start: + move.l #0xFFFFFFFF, %d0 + move.l #0xFFFFFFFF, %d1 + move.w #0xa711, %sr + move.l #0x1, %d2 + move.l #0x8000, %d3 + negx.l %d0 + negx.l %d1 + move.w #0x270f, %sr + negx.b %d2 + negx.w %d3 +_loop: + bra _loop + + nop + nop + nop + nop