From 0e11c502b05a9ccd67d34d1f0975852d8c3d4232 Mon Sep 17 00:00:00 2001 From: notaz Date: Sun, 5 Aug 2007 18:21:40 +0000 Subject: [PATCH] 0.0088 release git-svn-id: file:///home/notaz/opt/svn/PicoDrive@215 be3aeb3a-fb24-0410-a615-afba39da0efa --- cpu/Cyclone/Cyclone.h | 78 ++- cpu/Cyclone/Cyclone.txt | 776 +++++++++++++++++------------- cpu/Cyclone/Disa/Disa.c | 2 +- cpu/Cyclone/Ea.cpp | 17 +- cpu/Cyclone/Main.cpp | 799 +++++++++++++++++++++++-------- cpu/Cyclone/OpAny.cpp | 22 +- cpu/Cyclone/OpArith.cpp | 8 +- cpu/Cyclone/OpBranch.cpp | 117 +++-- cpu/Cyclone/OpLogic.cpp | 8 +- cpu/Cyclone/OpMove.cpp | 115 ++++- cpu/Cyclone/app.h | 16 +- cpu/Cyclone/config.h | 109 +++-- cpu/Cyclone/config_mamegp2x.h | 172 +++++++ cpu/Cyclone/config_pico.h | 172 +++++++ cpu/Cyclone/config_uae4all.h | 172 +++++++ cpu/Cyclone/proj/Makefile | 3 + cpu/Cyclone/proj/Makefile.win | 5 +- cpu/Cyclone/proj/cyclone.dsp | 8 +- cpu/Cyclone/tests/test_trace.bin | Bin 0 -> 558 bytes cpu/Cyclone/tests/test_trace.s | 140 ++++++ 20 files changed, 2084 insertions(+), 655 deletions(-) create mode 100644 cpu/Cyclone/config_mamegp2x.h create mode 100644 cpu/Cyclone/config_pico.h create mode 100644 cpu/Cyclone/config_uae4all.h create mode 100755 cpu/Cyclone/tests/test_trace.bin create mode 100644 cpu/Cyclone/tests/test_trace.s 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,36 +18,484 @@ ___________________________________________________________________________ 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. + + +How to Compile +-------------- + +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. + +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). + +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. + + +Adding to your project +---------------------- + +Compiling the .s or .asm (from previous step) for your target platform may require custom +build rules in your Makefile/project. + +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: + +AS = arm-linux-as + +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 + +Note that Cyclone does not use floating points, this is just to make the linker happy. + + +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 +------------------ + +If you have used Starscream, A68K or Turbo68K or similar emulators you'll be familiar with this! + +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 (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]. + +This would work, but remember some systems can execute code from RAM as well as ROM, and +that would fail. +So it's best to use byteswapped ROM and RAM if the 68000 can access it directly. +It's also faster for the memory handlers, because you can do this: + + return *(unsigned short *)(mem+a) + + +Declaring Memory handlers +------------------------- + +Before you can reset or execute 68000 opcodes you must first set up a set of memory handlers. +There are 7 functions you have to set up per CPU, like this: + + static unsigned int MyCheckPc(unsigned int pc) + static unsigned char MyRead8 (unsigned int a) + static unsigned short MyRead16 (unsigned int a) + static unsigned int MyRead32 (unsigned int a) + static void MyWrite8 (unsigned int a,unsigned char d) + static void MyWrite16(unsigned int a,unsigned short d) + static void MyWrite32(unsigned int a,unsigned int d) + +You can think of these functions representing the 68000's memory bus. +The Read and Write functions are called whenever the 68000 reads or writes memory. +For example you might set MyRead8 like this: + + unsigned char MyRead8(unsigned int a) + { + a&=0xffffff; // Clip address to 24-bits + + if (a=0xe00000) return RamData[(a^1)&0xffff]; + return 0xff; // Out of range memory access + } + +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 (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 initialize the memory to zero: + + memset(&MyCpu, 0,sizeof(MyCpu)); + memset(&MyCpu2,0,sizeof(MyCpu2)); + +Next point to your memory handlers: + + MyCpu.checkpc=MyCheckPc; + MyCpu.read8 =MyRead8; + MyCpu.read16 =MyRead16; + MyCpu.read32 =MyRead32; + MyCpu.write8 =MyWrite8; + MyCpu.write16=MyWrite16; + MyCpu.write32=MyWrite32; + +You also need to point the fetch handlers - for most systems out there you can just +point them at the read handlers: + MyCpu.fetch8 =MyRead8; + MyCpu.fetch16 =MyRead16; + MyCpu.fetch32 =MyRead32; + +( Why a different set of function pointers for fetch? + Well there are some systems, the main one being CPS2, which return different data + depending on whether the 'fetch' line on the 68000 bus is high or low. + 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 a few more functions, +one of them is: checkpc(). + + +The checkpc() function +---------------------- + +When Cyclone reads opcodes, it doesn't use a memory handler every time, this would be +far too slow, instead it uses a direct pointer to ARM memory. +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 68k PC, Cyclone just +subtracts 'membase'. + +When a long jump happens, Cyclone calls checkpc(). If the PC is in a different bank, +for example Ram instead of Rom, change 'membase', recalculate the new PC and return it: + +static int MyCheckPc(unsigned int pc) +{ + pc-=MyCpu.membase; // Get the real program counter + + if (pc=0xff0000) MyCpu.membase=(int)RamMem-0xff0000; // Jump to Ram + + return MyCpu.membase+pc; // New program counter +} + +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.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! +------------------------------- + +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; // Will be set by checkpc() + MyCpu.pc=MyCpu.checkpc(MyCpu.read32(4)); // Get Program Counter + +And that's ready to go. + + +Executing the 68000 +------------------- + +To execute the 68000, set the 'cycles' variable to the number of cycles you wish to execute, +and then call CycloneRun with a pointer to the Cyclone structure. + +e.g.: + // Execute 1000 cycles on the 68000: + MyCpu.cycles=1000; CycloneRun(&MyCpu); + +For each opcode, the number of cycles it took is subtracted and the function returns when +it reaches negative number. The result is stored back to MyCpu.cycles. + +e.g. + // Execute one instruction on the 68000: + MyCpu.cycles=0; CycloneRun(&MyCpu); + printf(" The opcode took %d cycles\n", -MyCpu.cycles); + +You should try to execute as many cycles as you can for maximum speed. +The number actually executed may be slightly more than requested, i.e. cycles may come +out with a small negative value: + +e.g. + int todo=12000000/60; // 12Mhz, for one 60hz frame + MyCpu.cycles=todo; CycloneRun(&MyCpu); + printf(" Actually executed %d cycles\n", todo-MyCpu.cycles); + +To calculate the number of cycles executed, use this formula: + Number of cycles requested - Cycle counter at the end + + +Interrupts +---------- + +Causing an interrupt is very simple, simply set the irq variable in the Cyclone structure +to the IRQ number. +To lower the IRQ line, set it to zero. + +e.g: + MyCpu.irq=6; // Interrupt level 6 + MyCpu.cycles=20000; CycloneRun(&MyCpu); + +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. + +If you need to force interrupt processing, you can use CycloneFlushIrq() function. +It is the same as doing + +MyCpu.cycles=0; CycloneRun(&MyCpu); + +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 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. + +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 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; + +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 +--------------------------- + +Since everything is based on the structures, emulating more than one cpu at the same time +is just a matter of declaring more than one structures and timeslicing. You can emulate +as many 68000s as you want. +Just set up the memory handlers for each cpu and run each cpu for a certain number of cycles. + +e.g. + // Execute 1000 cycles on 68000 #1: + MyCpu.cycles=1000; CycloneRun(&MyCpu); + + // Execute 1000 cycles on 68000 #2: + 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 + 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.0087 notaz +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" behaviour. - + Fixed flag behaviour of ROXR, ASL, LSR and NBCD in certain situations. + + 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 . @@ -171,317 +619,3 @@ v0.0060 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. - -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. - -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. - - -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) - - -How to Compile --------------- - -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 - -Cyclone.o is the GCC assembled version and Cyclone.obj is the Microsoft assembled version. - -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. - -Open up Cyclone.dsw in Visual Studio 6.0, compile and run the project. -Cyclone.obj and Cyclone.o will be created. - - -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. - - 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 - -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 ------------------- - -If you have used Starscream, A68K or Turbo68K or similar emulators you'll be familiar with this! - -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. - -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]. - -This would work, but remember some systems can execute code from RAM as well as ROM, and -that would fail. -So it's best to use byteswapped ROM and RAM if the 68000 can access it directly. -It's also faster for the memory handlers, because you can do this: - - return *(unsigned short *)(mem+a) - - -Declaring Memory handlers -------------------------- - -Before you can reset or execute 68000 opcodes you must first set up a set of memory handlers. -There are 7 functions you have to set up per CPU, like this: - - static unsigned int MyCheckPc(unsigned int pc) - static unsigned char MyRead8 (unsigned int a) - static unsigned short MyRead16 (unsigned int a) - static unsigned int MyRead32 (unsigned int a) - static void MyWrite8 (unsigned int a,unsigned char d) - static void MyWrite16(unsigned int a,unsigned short d) - static void MyWrite32(unsigned int a,unsigned int d) - -You can think of these functions representing the 68000's memory bus. -The Read and Write functions are called whenever the 68000 reads or writes memory. -For example you might set MyRead8 like this: - - unsigned char MyRead8(unsigned int a) - { - a&=0xffffff; // Clip address to 24-bits - - if (a=0xe00000) return RamData[(a^1)&0xffff]; - return 0xff; // Out of range memory access - } - -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: - - struct Cyclone MyCpu; - struct Cyclone MyCpu2; - -It's probably a good idea to initialise the memory to zero: - - memset(&MyCpu, 0,sizeof(MyCpu)); - memset(&MyCpu2,0,sizeof(MyCpu2)); - -Next point to your memory handlers: - - MyCpu.checkpc=MyCheckPc; - MyCpu.read8 =MyRead8; - MyCpu.read16 =MyRead16; - MyCpu.read32 =MyRead32; - MyCpu.write8 =MyWrite8; - MyCpu.write16=MyWrite16; - MyCpu.write32=MyWrite32; - -You also need to point the fetch handlers - for most systems out there you can just -point them at the read handlers: - MyCpu.fetch8 =MyRead8; - MyCpu.fetch16 =MyRead16; - MyCpu.fetch32 =MyRead32; - -( Why a different set of function pointers for fetch? - Well there are some systems, the main one being CPS2, which return different data - depending on whether the 'fetch' line on the 68000 bus is high or low. - 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(). - -The checkpc() function ----------------------- - -When Cyclone reads opcodes, it doesn't use a memory handler every time, this would be -far too slow, instead it uses a direct pointer to ARM memory. -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 -subtracts 'membase'. - -When a long jump happens, Cyclone calls checkpc(). If the PC is in a different bank, -for example Ram instead of Rom, change 'membase', recalculate the new PC and return it: - -static int MyCheckPc(unsigned int pc) -{ - pc-=MyCpu.membase; // Get the real program counter - - if (pc=0xff0000) MyCpu.membase=(int)RamMem-0xff0000; // Jump to Ram - - return MyCpu.membase+pc; // New program counter -} - -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). - - -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. - -Here is code which resets the 68000 (using your memory handlers): - - MyCpu.srh=0x27; // Set supervisor mode - MyCpu.a[7]=MyCpu.read32(0); // Get Stack Pointer - MyCpu.membase=0; - MyCpu.pc=MyCpu.checkpc(MyCpu.read32(4)); // Get Program Counter - -And that's ready to go. - - -Executing the 68000 -------------------- - -To execute the 68000, set the 'cycles' variable to the number of cycles you wish to execute, -and then call CycloneRun with a pointer to the Cyclone structure. - -e.g.: - // Execute 1000 cycles on the 68000: - MyCpu.cycles=1000; CycloneRun(&MyCpu); - -For each opcode, the number of cycles it took is subtracted and the function returns when -it reaches 0. - -e.g. - // Execute one instruction on the 68000: - MyCpu.cycles=0; CycloneRun(&MyCpu); - printf(" The opcode took %d cycles\n", -MyCpu.cycles); - -You should try to execute as many cycles as you can for maximum speed. -The number actually executed may be slightly more than requested, i.e. cycles may come -out with a small negative value: - -e.g. - int todo=12000000/60; // 12Mhz, for one 60hz frame - MyCpu.cycles=todo; CycloneRun(&MyCpu); - printf(" Actually executed %d cycles\n", todo-MyCpu.cycles); - -To calculate the number of cycles executed, use this formula: - Number of cycles requested - Cycle counter at the end - - -Interrupts ----------- - -Causing an interrupt is very simple, simply set the irq variable in the Cyclone structure -to the IRQ number. -To lower the IRQ line, set it to zero. - -e.g: - MyCpu.irq=6; // Interrupt level 6 - MyCpu.cycles=20000; CycloneRun(&MyCpu); - -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...) - - -Accessing Cycle Counter ------------------------ - -The cycle counter in the Cyclone structure is not, by default, updated before -calling a memory handler, only at the end of an execution. - -*update* -Now this is configurable in config.h, there is no 'debug' variable. - - -Accessing Program Counter and registers ---------------------------------------- - -You can read Cyclone's registers directly from the structure at any time (as far as I know). - -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. - - -Emulating more than one CPU ---------------------------- - -Since everything is based on the structures, emulating more than one cpu at the same time -is just a matter of declaring more than one structures and timeslicing. You can emulate -as many 68000s as you want. -Just set up the memory handlers for each cpu and run each cpu for a certain number of cycles. - -e.g. - // Execute 1000 cycles on 68000 #1: - MyCpu.cycles=1000; CycloneRun(&MyCpu); - - // Execute 1000 cycles on 68000 #2: - MyCpu2.cycles=1000; CycloneRun(&MyCpu2); - - -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 - -* Charles Macdonald, for researching just about every console ever -* MameDev+FBA, for keeping on going and going and going - - -------------- - -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"); - 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("\n"); + ot("\n"); + 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(" bic r8,r8,#0xff000000\n"); + ot(" orr r8,r8,r0,lsl #29 ;@ abuse r8\n"); - ot(";@ Clear stopped states:\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); - if (!(op&0x40)) - { - ot(";@ Jsr - Push old PC first\n"); - ot(" ldr r0,[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"); - 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(); + if (!(op&0x40)) + { + ot(" ldr r2,[r7,#0x3c]\n"); + ot(" sub r1,r4,r10 ;@ r1 = Old PC\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 - CheckPc(0); + 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); + } 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 0000000000000000000000000000000000000000..e9ca5fa9d3c0b901da110c53bda826347b9c8d4f GIT binary patch literal 558 zcmZQzU|?uqU}9rnV7Ujx6M%RX5Fdf^L1OoS@<3H!c`%=JHme5%0~}9+vZny)AfQ<> z3=HBHKr92qAwUeYi!}_0tDx$DdfCq**l=^8W>VrVmV3dj?v4uXu70k;p1}%0py8|u z7BaN7G|}@4^>Hc9El5pKaH>=Y33dS~AOM0rJpDZV+!fsXgPdIzj0mV!P;mEE@b%U; zG&VF<&^0jVRP;jA0SpWV2KoR08U6$5|Ns97Ic`v3P+(wWP_X}xrj#gdD4Ht2LLe+I n_+Rwj?ay+-5=J!}P" "<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