0.0088 release

git-svn-id: file:///home/notaz/opt/svn/PicoDrive@215 be3aeb3a-fb24-0410-a615-afba39da0efa
This commit is contained in:
notaz 2007-08-05 18:21:40 +00:00
parent ee5e024ce6
commit 0e11c502b0
20 changed files with 2084 additions and 655 deletions

View file

@ -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__

View file

@ -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<RomLength) return RomData[a^1]; // ^1 because the memory is byteswapped
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<RomLength) MyCpu.membase=(int)RomMem; // Jump to Rom
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<RomLength) return RomData[a^1]; // ^1 because the memory is byteswapped
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<RomLength) MyCpu.membase=(int)RomMem; // Jump to Rom
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 @

View file

@ -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]="";

View file

@ -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<<size, strr=a;
int low=0,lsl,i;
int low=0,lsl=0,i;
if ((ea&7)==7 && step<2) step=2; // move.b (a7)+ or -(a7) steps by 2 not 1
@ -340,8 +342,11 @@ int EaRead(int a,int v,int ea,int size,int mask,int top,int sign_extend)
ot("\n"); return 0;
}
if (ea>=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;
}

View file

@ -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();

View file

@ -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");

View file

@ -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);

View file

@ -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);

View file

@ -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;
}

View file

@ -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<<size);
earead_check_addrerr=0; // already checked
EaRead (6,0,ea,size,0x003f);
ot(" str r0,[r7,r10] ;@ Save value into Dn/An\n");
}
@ -391,7 +451,8 @@ int OpMovem(int op)
Cycles+=Ea_add_ns(g_movem_cycle_table,ea);
OpEnd(ea,0,1);
opend_op_changes_cycles = 1;
OpEnd(ea);
ltorg();
ot("\n");
@ -413,6 +474,7 @@ int OpMoveUsp(int op)
if (dir)
{
eawrite_check_addrerr=1;
ot(" ldr r1,[r7,#0x48] ;@ Get from USP\n\n");
EaCalc (0,0x000f,8,2,1);
EaWrite(0, 1,8,2,0x000f,1);
@ -568,11 +630,12 @@ int OpStopReset(int op)
ot("\n");
ot(" mov r0,#1\n");
ot(" str r0,[r7,#0x58] ;@ stopped\n");
ot(" ldr r0,[r7,#0x58]\n");
ot(" mov r5,#0 ;@ eat cycles\n");
ot(" orr r0,r0,#1 ;@ stopped\n");
ot(" str r0,[r7,#0x58]\n");
ot("\n");
ot(" mov r5,#0 ;@ eat cycles\n");
Cycles = 4;
ot("\n");
}

View file

@ -4,12 +4,17 @@
#include <stdlib.h>
#include <string.h>
#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);

View file

@ -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

View file

@ -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

172
cpu/Cyclone/config_pico.h Normal file
View file

@ -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

View file

@ -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

View file

@ -1,4 +1,7 @@
CFLAGS = -Wall
ifdef CONFIG_FILE
CFLAGS += -DCONFIG_FILE=\"$(CONFIG_FILE)\"
endif
all : cyclone.s

View file

@ -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"

View file

@ -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

BIN
cpu/Cyclone/tests/test_trace.bin Executable file

Binary file not shown.

View file

@ -0,0 +1,140 @@
| Processor: 68K
| Target Assembler: 680x0 Assembler by GNU project
| ___________________________________________________________________________
| Segment type: Pure code
| segment "ROM"
dword_0: .long 0 | DATA XREF: ROM:00007244r
| sub_764E+3Eo ...
| initial interrupt stack pointer
dword_4: .long _start | DATA XREF: ROM:00007248r
| ROM:000142C2w
| reset initial PC
dword_8: .long 0x4DE | DATA XREF: sub_20050+B54w
.long 0x490
.long 0x4AA | illegal instruction
.long 0x4C4
.long 0x4DE
.long 0x4DE
.long 0x4DE
.long _trace | trace
.long 0x4DE
.long 0x4DE
.long 0x4DE
.long 0x4DE
.long 0x4DE
.long 0x4DE
.long 0x4DE
.long 0x4DE
.long 0x4DE
.long 0x4DE
.long 0x4DE
.long 0x4DE
.long 0x4DE
.long 0x4DE
.long 0x4DE
.long 0x548 | Level 1 Interrupt Autovector
.long 0x548 | 2 = ext interrupt
.long 0x548
.long 0x592 | 4 = horizontal interrupt?
.long 0x548
.long 0x594 | 6 = verticai interrupt?
.long 0x552
dword_80: .long 0x45C | DATA XREF: ROM:00152F29o
| trap vector table? trap 0?
.long 0x1738
.long 0x171C
.long 0x1754
.long 0x1700
.long 0x556
.long 0x57A
.long 0x548
.long 0x548
.long 0x7CE | 9
.long 0x548
.long 0x548
.long 0x548
.long 0x548
.long 0x548
.long 0x548
.long 0x4DE
.long 0x4DE
.long 0x4DE
.long 0x4DE
.long 0x4DE
.long 0x4DE
.long 0x4DE
.long 0x4DE
.long 0x4DE
.long 0x4DE
.long 0x4DE
.long 0x4DE
.long 0x4DE
.long 0x4DE
.long 0x4DE
.long 0x4DE
aSegaGenesis: .ascii "SEGA GENESIS " | DATA XREF: ROM:00045C6Ao
aCSega1994_jul: .ascii "(C)SEGA 1994.JUL"
aDumpedByTsd: .ascii "Dumped By TSD "
aShiningForce2: .ascii "SHINING FORCE 2 "
aGmMk131500: .ascii "GM MK-1315 -00"
.word 0x8921 | checksum
aJ: .ascii "J " | IO_Support
.long 0 | Rom_Start_Adress
dword_1A4: .long 0x1FFFFF | DATA XREF: sub_28008+F66o
| Rom_End_Adress
.long 0xFF0000 | Ram_Start_Adress
.long 0xFFFFFF | Ram_End_Adress
aRaa: .ascii "RA° "<0>" "<0><1><0>" ?"<0xFF> | Modem_Infos
.ascii " "
.byte 0x20 |
.byte 0x20 |
.byte 0x20 |
.byte 0x20 |
.byte 0x20 |
.byte 0x20 |
.byte 0x20 |
.byte 0x20 |
.byte 0x20 |
.byte 0x20 |
.byte 0x20 |
.byte 0x20 |
aU: .ascii "U " | Countries
.byte 0x20 |
.byte 0x20 |
.byte 0x20 |
.byte 0x20 |
.byte 0x20 |
.byte 0x20 |
.byte 0x20 |
.byte 0x20 |
.byte 0x20 |
.byte 0x20 |
.byte 0x20 |
.byte 0x20 |
.byte 0x20 |
_trace:
nop
nop
rte
.globl _start
_start:
move.l #0xFFFFFFFF, %d0
move.l #0xFFFFFFFF, %d1
move.w #0xa711, %sr
move.l #0x1, %d2
move.l #0x8000, %d3
negx.l %d0
negx.l %d1
move.w #0x270f, %sr
negx.b %d2
negx.w %d3
_loop:
bra _loop
nop
nop
nop
nop