mirror of
https://github.com/RaySollium99/picodrive.git
synced 2025-09-06 23:58:04 -04:00
initial import
git-svn-id: file:///home/notaz/opt/svn/PicoDrive@2 be3aeb3a-fb24-0410-a615-afba39da0efa
This commit is contained in:
parent
2cadbd5e56
commit
cc68a136aa
341 changed files with 180839 additions and 0 deletions
58
cpu/Cyclone/Cyclone.h
Normal file
58
cpu/Cyclone/Cyclone.h
Normal file
|
@ -0,0 +1,58 @@
|
|||
|
||||
// Cyclone 68000 Emulator - Header File
|
||||
|
||||
// Most code (c) Copyright 2004 Dave, All rights reserved.
|
||||
// Some coding/bugfixing was done by notaz
|
||||
// Cyclone 68000 is free for non-commercial use.
|
||||
|
||||
// For commercial use, separate licencing terms must be obtained.
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern int CycloneVer; // Version number of library
|
||||
|
||||
struct Cyclone
|
||||
{
|
||||
unsigned int d[8]; // [r7,#0x00]
|
||||
unsigned int a[8]; // [r7,#0x20]
|
||||
unsigned int pc; // [r7,#0x40] Memory Base+PC
|
||||
unsigned char srh; // [r7,#0x44] Status Register high (T_S__III)
|
||||
unsigned char xc; // [r7,#0x45] Extend flag (____??X?)
|
||||
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 vector; // [r7,#0x4c] IRQ vector (temporary)
|
||||
unsigned int pad1[2];
|
||||
int stopped; // [r7,#0x58] 1 == processor is in stopped state
|
||||
int cycles; // [r7,#0x5c]
|
||||
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]
|
||||
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)(); // [r7,#0x90] - if enabled in config.h, calls this whenever RESET opcode is encountered.
|
||||
int (*UnrecognizedCallback)(); // [r7,#0x94] - if enabled in config.h, calls this whenever unrecognized opcode is encountered.
|
||||
};
|
||||
|
||||
// used only if Cyclone was compiled with compressed jumptable, see config.h
|
||||
void CycloneInit();
|
||||
|
||||
// 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);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // End of extern "C"
|
||||
#endif
|
473
cpu/Cyclone/Cyclone.txt
Normal file
473
cpu/Cyclone/Cyclone.txt
Normal file
|
@ -0,0 +1,473 @@
|
|||
|
||||
_____ __
|
||||
/ ___/__ __ ____ / /___ ___ ___ ___________________
|
||||
/ /__ / // // __// // _ \ / _ \/ -_) ___________________
|
||||
\___/ \_, / \__//_/ \___//_//_/\__/ ___________________
|
||||
/___/
|
||||
___________________ ____ ___ ___ ___ ___
|
||||
___________________ / __// _ \ / _ \ / _ \ / _ \
|
||||
___________________ / _ \/ _ // // // // // // /
|
||||
\___/\___/ \___/ \___/ \___/
|
||||
|
||||
___________________________________________________________________________
|
||||
|
||||
Cyclone 68000 (c) Copyright 2004 Dave. Free for non-commercial use
|
||||
|
||||
Homepage: http://www.finalburn.com/
|
||||
Dave's e-mail: dev(atsymbol)finalburn.com
|
||||
Replace (atsymbol) with @
|
||||
|
||||
Additional coding and bugfixes done by notaz, 2005, 2006
|
||||
Homepage: http://mif.vu.lt/~grig2790/Cyclone/
|
||||
e-mail: notasas(atsymbol)gmail.com
|
||||
___________________________________________________________________________
|
||||
|
||||
|
||||
What is it?
|
||||
-----------
|
||||
|
||||
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.
|
||||
|
||||
Flags are mapped onto ARM flags whenever possible, which speeds up the processing of opcode.
|
||||
|
||||
|
||||
What's New
|
||||
----------
|
||||
v0.0086 notaz
|
||||
+ Cyclone now can be customized to better suit your project, see config.h .
|
||||
+ Added an option to compress the jumptable at compile-time. Must call CycloneInit()
|
||||
at runtime to decompress it if enabled (see config.h).
|
||||
+ Added missing CHK opcode handler (used by SeaQuest DSV).
|
||||
+ Added missing TAS opcode handler (Gargoyles,Bubba N Stix,...). As in real genesis,
|
||||
memory write-back phase is ignored (but can be enabled in config.h if needed).
|
||||
+ Added missing NBCD and TRAPV opcode handlers.
|
||||
+ Added missing addressing mode for CMP/EOR.
|
||||
+ Added some minor optimizations.
|
||||
- Removed 216 handlers for 2927 opcodes which were generated for invalid addressing modes.
|
||||
+ Fixed flags for ASL, NEG, NEGX, DIVU, ADDX, SUBX, ROXR.
|
||||
+ Bugs fixed in MOVEP, LINK, ADDQ, DIVS handlers.
|
||||
* Undocumented flags for CHK, ABCD, SBCD and NBCD are now emulated the same way as in Musashi.
|
||||
+ Added Uninitialized Interrupt emulation.
|
||||
+ Altered timing for about half of opcodes to match Musashi's.
|
||||
|
||||
v0.0082 Reesy
|
||||
+ Change cyclone to clear cycles before returning when halted
|
||||
+ Added Irq call back function. This allows emulators to be notified
|
||||
when cyclone has taken an interrupt allowing them to set internal flags
|
||||
which can help fix timing problems.
|
||||
|
||||
v0.0081 notaz
|
||||
+ .asm version was broken and did not compile with armasm. Fixed.
|
||||
+ Finished implementing Stop opcode. Now it really stops the processor.
|
||||
|
||||
v0.0080 notaz
|
||||
+ Added real cmpm opcode, it was using eor handler before this.
|
||||
Fixes Dune and Sensible Soccer.
|
||||
|
||||
v0.0078 notaz
|
||||
note: these bugs were actually found Reesy, I reimplemented these by
|
||||
using his changelog as a guide.
|
||||
+ Fixed a problem with divu which was using long divisor instead of word.
|
||||
Fixes gear switching in Top Gear 2.
|
||||
+ Fixed btst opcode, The bit to test should shifted a max of 31 or 7
|
||||
depending on if a register or memory location is being tested.
|
||||
+ Fixed abcd,sbcd. They did bad decimal correction on invalid BCD numbers
|
||||
Score counters in Streets of Rage level end work now.
|
||||
+ Changed flag handling of abcd,sbcd,addx,subx,asl,lsl,...
|
||||
Some ops did not have flag handling at all.
|
||||
Some ops must not change Z flag when result is zero, but they did.
|
||||
Shift ops must not change X if shift count is zero, but they did.
|
||||
There are probably still some flag problems left.
|
||||
+ Patially implemented Stop and Reset opcodes - Fixes Thunderforce IV
|
||||
|
||||
v0.0075 notaz
|
||||
+ Added missing displacement addressing mode for movem (Fantastic Dizzy)
|
||||
+ Added OSP <-> A7 swapping code in opcodes, which change privilege mode
|
||||
+ Implemented privilege violation, line emulator and divide by zero exceptions
|
||||
+ Added negx opcode (Shining Force works!)
|
||||
+ Added overflow detection for divs/divu
|
||||
|
||||
v0.0072 notaz
|
||||
note: I could only get v0.0069 cyclone, so I had to implement these myself using Dave's
|
||||
changelog as a guide.
|
||||
+ Fixed a problem with divs - remainder should be negative when divident is negative
|
||||
+ Added movep opcode (Sonic 3 works)
|
||||
+ Fixed a problem with DBcc incorrectly decrementing if the condition is true (Shadow of the Beast)
|
||||
|
||||
v0.0069
|
||||
+ Added SBCD and the flags for ABCD/SBCD. Score and time now works in games such as
|
||||
Rolling Thunder 2, Ghouls 'N Ghosts
|
||||
+ Fixed a problem with addx and subx with 8-bit and 16-bit values.
|
||||
Ghouls 'N' Ghosts now works!
|
||||
|
||||
v0.0068
|
||||
+ Added ABCD opcode (Streets of Rage works now!)
|
||||
|
||||
v0.0067
|
||||
+ Added dbCC (After Burner)
|
||||
+ Added asr EA (Sonic 1 Boss/Labyrinth Zone)
|
||||
+ Added andi/ori/eori ccr (Altered Beast)
|
||||
+ Added trap (After Burner)
|
||||
+ Added special case for move.b (a7)+ and -(a7), stepping by 2
|
||||
After Burner is playable! Eternal Champions shows more
|
||||
+ Fixed lsr.b/w zero flag (Ghostbusters)
|
||||
Rolling Thunder 2 now works!
|
||||
+ Fixed N flag for .b and .w arithmetic. Golden Axe works!
|
||||
|
||||
v0.0066
|
||||
+ Fixed a stupid typo for exg (orr r10,r10, not orr r10,r8), which caused alignment
|
||||
crashes on Strider
|
||||
|
||||
v0.0065
|
||||
+ Fixed a problem with immediate values - they weren't being shifted up correctly for some
|
||||
opcodes. Spiderman works, After Burner shows a bit of graphics.
|
||||
+ Fixed a problem with EA:"110nnn" extension word. 32-bit offsets were being decoded as 8-bit
|
||||
offsets by mistake. Castlevania Bloodlines seems fine now.
|
||||
+ Added exg opcode
|
||||
+ Fixed asr opcode (Sonic jumping left is fixed)
|
||||
+ Fixed a problem with the carry bit in rol.b (Marble Madness)
|
||||
|
||||
v0.0064
|
||||
+ Added rtr
|
||||
+ Fixed addq/subq.l (all An opcodes are 32-bit) (Road Rash)
|
||||
+ Fixed various little timings
|
||||
|
||||
v0.0063
|
||||
+ Added link/unlk opcodes
|
||||
+ Fixed various little timings
|
||||
+ Fixed a problem with dbCC opcode being emitted at set opcodes
|
||||
+ Improved long register access, the EA fetch now does ldr r0,[r7,r0,lsl #2] whenever
|
||||
possible, saving 1 or 2 cycles on many opcodes, which should give a nice speed up.
|
||||
+ May have fixed N flag on ext opcode?
|
||||
+ Added dasm for link opcode.
|
||||
|
||||
v0.0062
|
||||
* I was a bit too keen with the Arithmetic opcodes! Some of them should have been abcd,
|
||||
exg and addx. Removed the incorrect opcodes, pending re-adding them as abcd, exg and addx.
|
||||
+ Changed unknown opcodes to act as nops.
|
||||
Not very technical, but fun - a few more games show more graphics ;)
|
||||
|
||||
v0.0060
|
||||
+ Fixed divu (EA intro)
|
||||
+ Added sf (set false) opcode - SOR2
|
||||
* Todo: pea/link/unlk opcodes
|
||||
|
||||
v0.0059: Added remainder to divide opcodes.
|
||||
|
||||
|
||||
The new stuff
|
||||
-------------
|
||||
|
||||
Before using Cyclone, be sure to customize config.h to better suit your project. All options
|
||||
are documented inside that file.
|
||||
|
||||
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 @
|
821
cpu/Cyclone/Disa/Disa.c
Normal file
821
cpu/Cyclone/Disa/Disa.c
Normal file
|
@ -0,0 +1,821 @@
|
|||
|
||||
// Dave's Disa 68000 Disassembler
|
||||
#ifndef __GNUC__
|
||||
#pragma warning(disable:4115)
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "Disa.h"
|
||||
|
||||
unsigned int DisaPc=0;
|
||||
char *DisaText=NULL; // Text buffer to write in
|
||||
static char Tasm[]="bwl?";
|
||||
static char Comment[64]="";
|
||||
unsigned short (CPU_CALL *DisaWord)(unsigned int a)=NULL;
|
||||
|
||||
static unsigned int DisaLong(unsigned int a)
|
||||
{
|
||||
unsigned int d=0;
|
||||
if (DisaWord==NULL) return d;
|
||||
|
||||
d= DisaWord(a)<<16;
|
||||
d|=DisaWord(a+2)&0xffff;
|
||||
return d;
|
||||
}
|
||||
|
||||
// Get text version of the effective address
|
||||
int DisaGetEa(char *t,int ea,int size)
|
||||
{
|
||||
ea&=0x3f; t[0]=0;
|
||||
if ((ea&0x38)==0x00) { sprintf(t,"d%d",ea ); return 0; } // 000rrr
|
||||
if ((ea&0x38)==0x08) { sprintf(t,"a%d",ea&7); return 0; } // 001rrr
|
||||
if ((ea&0x38)==0x10) { sprintf(t,"(a%d)",ea&7); return 0; } // 010rrr
|
||||
if ((ea&0x38)==0x18) { sprintf(t,"(a%d)+",ea&7); return 0; } // 011rrr
|
||||
if ((ea&0x38)==0x20) { sprintf(t,"-(a%d)",ea&7); return 0; } // 100rrr
|
||||
if ((ea&0x38)==0x28) { sprintf(t,"($%x,a%d)",DisaWord(DisaPc)&0xffff,ea&7); DisaPc+=2; return 0; } // 101rrr
|
||||
|
||||
if ((ea&0x38)==0x30)
|
||||
{
|
||||
// 110nnn - An + Disp + D/An
|
||||
int areg=0,ext=0,off=0,da=0,reg=0,wol=0,scale=0;
|
||||
ext=DisaWord(DisaPc)&0xffff;
|
||||
|
||||
areg=ea&7;
|
||||
off=ext&0xff; da =ext&0x8000?'a':'d';
|
||||
reg=(ext>>12)&7; wol=ext&0x0800?'l':'w';
|
||||
scale=1<<((ext>>9)&3);
|
||||
|
||||
if (scale<2) sprintf(t,"($%x,a%d,%c%d.%c)", off,areg,da,reg,wol);
|
||||
else sprintf(t,"($%x,a%d,%c%d.%c*%d)",off,areg,da,reg,wol,scale); // 68020
|
||||
|
||||
DisaPc+=2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ea==0x38) { sprintf(t,"$%x.w",DisaWord(DisaPc)&0xffff); DisaPc+=2; return 0; } // 111000 - Absolute short
|
||||
if (ea==0x39) { sprintf(t,"$%x.l",DisaLong(DisaPc)); DisaPc+=4; return 0; } // 111001 - Absolute long
|
||||
|
||||
if (ea==0x3a)
|
||||
{
|
||||
// 111010 - PC Relative
|
||||
int ext=DisaWord(DisaPc)&0xffff;
|
||||
sprintf(t,"($%x,pc)",ext);
|
||||
sprintf(Comment,"; =%x",DisaPc+(short)ext); // Comment where pc+ext is
|
||||
DisaPc+=2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ea==0x3b)
|
||||
{
|
||||
// 111011 - PC Relative + D/An
|
||||
int ext=0,off=0,da=0,reg=0,wol=0,scale=0;
|
||||
ext=DisaWord(DisaPc)&0xffff;
|
||||
|
||||
off=ext&0xff; da =ext&0x8000?'a':'d';
|
||||
reg=(ext>>12)&7; wol=ext&0x0800?'l':'w';
|
||||
scale=1<<((ext>>9)&3);
|
||||
|
||||
if (scale<2) sprintf(t,"($%x,pc,%c%d.%c)", off,da,reg,wol);
|
||||
else sprintf(t,"($%x,pc,%c%d.%c*%d)",off,da,reg,wol,scale); // 68020
|
||||
|
||||
sprintf(Comment,"; =%x",DisaPc+(char)off); // Comment where pc+ext is
|
||||
DisaPc+=2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ea==0x3c)
|
||||
{
|
||||
// 111100 - Immediate
|
||||
switch (size)
|
||||
{
|
||||
case 0: sprintf(t,"#$%x",DisaWord(DisaPc)&0x00ff); DisaPc+=2; return 0;
|
||||
case 1: sprintf(t,"#$%x",DisaWord(DisaPc)&0xffff); DisaPc+=2; return 0;
|
||||
case 2: sprintf(t,"#$%x",DisaLong(DisaPc) ); DisaPc+=4; return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Unknown effective address
|
||||
sprintf(t,"ea=(%d%d%d %d%d%d)",
|
||||
(ea>>5)&1,(ea>>4)&1,(ea>>3)&1,
|
||||
(ea>>2)&1,(ea>>1)&1, ea &1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void GetOffset(char *text)
|
||||
{
|
||||
int off=(short)DisaWord(DisaPc); DisaPc+=2;
|
||||
|
||||
if (off<0) sprintf(text,"-$%x",-off);
|
||||
else sprintf(text,"$%x", off);
|
||||
}
|
||||
|
||||
// ================ Opcodes 0x0000+ ================
|
||||
static int DisaArithImm(int op)
|
||||
{
|
||||
// Or/And/Sub/Add/Eor/Cmp Immediate 0000ttt0 xxDDDddd (tt=type, xx=size extension, DDDddd=Dest ea)
|
||||
int dea=0;
|
||||
char seat[64]="",deat[64]="";
|
||||
int type=0,size=0;
|
||||
char *arith[8]={"or","and","sub","add","?","eor","cmp","?"};
|
||||
|
||||
type=(op>>9)&7; if (type==4 || type>=7) return 1;
|
||||
size=(op>>6)&3; if (size>=3) return 1;
|
||||
dea=op&0x3f; if (dea==0x3c) return 1;
|
||||
|
||||
DisaGetEa(seat,0x3c,size);
|
||||
DisaGetEa(deat,dea, size);
|
||||
|
||||
sprintf(DisaText,"%si.%c %s, %s",arith[type],Tasm[size],seat,deat);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================ Opcodes 0x0108+ ================
|
||||
static int DisaMovep(int op)
|
||||
{
|
||||
// movep.x (Aa),Dn - 0000nnn1 dx001aaa nn
|
||||
int dn=0,dir=0,size=0,an=0;
|
||||
char offset[32]="";
|
||||
|
||||
dn =(op>>9)&7;
|
||||
dir =(op>>7)&1;
|
||||
size=(op>>6)&1; size++;
|
||||
an = op &7;
|
||||
|
||||
GetOffset(offset);
|
||||
if (dir) sprintf(DisaText,"movep.%c d%d, (%s,a%d)",Tasm[size],dn,offset,an);
|
||||
else sprintf(DisaText,"movep.%c (%s,a%d), d%d",Tasm[size],offset,an,dn);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================ Opcodes 0x007c+ ================
|
||||
static int DisaArithSr(int op)
|
||||
{
|
||||
// Ori/Andi/Eori $nnnn,sr 0000t0tx 0s111100
|
||||
char *opcode[6]={"ori","andi","","","","eori"};
|
||||
char seat[64]="";
|
||||
int type=0,size=0;
|
||||
|
||||
type=(op>>9)&5;
|
||||
size=(op>>6)&1;
|
||||
|
||||
DisaGetEa(seat,0x3c,size);
|
||||
sprintf(DisaText,"%s.%c %s, %s", opcode[type], Tasm[size], seat, size?"sr":"ccr");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================ Opcodes 0x0100+ ================
|
||||
static int DisaBtstReg(int op)
|
||||
{
|
||||
// Btst/Bchg/Bclr/Bset 0000nnn1 tteeeeee (nn=reg number, eeeeee=Dest ea)
|
||||
int type=0;
|
||||
int sea=0,dea=0;
|
||||
char seat[64]="",deat[64]="";
|
||||
char *opcode[4]={"btst","bchg","bclr","bset"};
|
||||
|
||||
sea =(op>>9)&7;
|
||||
type=(op>>6)&3;
|
||||
dea= op&0x3f;
|
||||
|
||||
if ((dea&0x38)==0x08) return 1; // movep
|
||||
DisaGetEa(seat,sea,0);
|
||||
DisaGetEa(deat,dea,0);
|
||||
|
||||
sprintf(DisaText,"%s %s, %s",opcode[type],seat,deat);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================ Opcodes 0x0800+ ================
|
||||
static int DisaBtstImm(int op)
|
||||
{
|
||||
// Btst/Bchg/Bclr/Bset 00001000 tteeeeee 00 nn (eeeeee=ea, nn=bit number)
|
||||
int type=0;
|
||||
char seat[64]="",deat[64]="";
|
||||
char *opcode[4]={"btst","bchg","bclr","bset"};
|
||||
|
||||
type=(op>>6)&3;
|
||||
DisaGetEa(seat, 0x3c,0);
|
||||
DisaGetEa(deat,op&0x3f,0);
|
||||
|
||||
sprintf(DisaText,"%s %s, %s",opcode[type],seat,deat);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================ Opcodes 0x1000+ ================
|
||||
static int DisaMove(int op)
|
||||
{
|
||||
// Move 00xxdddD DDssssss (xx=size extension, ssssss=Source EA, DDDddd=Dest ea)
|
||||
int sea=0,dea=0;
|
||||
char inst[64]="",seat[64]="",deat[64]="";
|
||||
char *movea="";
|
||||
int size=0;
|
||||
|
||||
if ((op&0x01c0)==0x0040) movea="a"; // See if it's a movea opcode
|
||||
|
||||
// Find size extension
|
||||
switch (op&0x3000)
|
||||
{
|
||||
case 0x1000: size=0; break;
|
||||
case 0x3000: size=1; break;
|
||||
case 0x2000: size=2; break;
|
||||
default: return 1;
|
||||
}
|
||||
|
||||
sea = op&0x003f;
|
||||
DisaGetEa(seat,sea,size);
|
||||
|
||||
dea =(op&0x01c0)>>3;
|
||||
dea|=(op&0x0e00)>>9;
|
||||
DisaGetEa(deat,dea,size);
|
||||
|
||||
sprintf(inst,"move%s.%c",movea,Tasm[size]);
|
||||
sprintf(DisaText,"%s %s, %s",inst,seat,deat);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================ Opcodes 0x4000+ ================
|
||||
static int DisaNeg(int op)
|
||||
{
|
||||
// 01000tt0 xxeeeeee (tt=negx/clr/neg/not, xx=size, eeeeee=EA)
|
||||
char eat[64]="";
|
||||
int type=0,size=0;
|
||||
char *opcode[4]={"negx","clr","neg","not"};
|
||||
|
||||
type=(op>>9)&3;
|
||||
size=(op>>6)&3; if (size>=3) return 1;
|
||||
DisaGetEa(eat,op&0x3f,size);
|
||||
|
||||
sprintf(DisaText,"%s.%c %s",opcode[type],Tasm[size],eat);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================ Opcodes 0x40c0+ ================
|
||||
static int DisaMoveSr(int op)
|
||||
{
|
||||
// 01000tt0 11eeeeee (tt=type, xx=size, eeeeee=EA)
|
||||
int type=0,ea=0;
|
||||
char eat[64]="";
|
||||
|
||||
type=(op>>9)&3;
|
||||
ea=op&0x3f;
|
||||
DisaGetEa(eat,ea,1);
|
||||
|
||||
switch (type)
|
||||
{
|
||||
default: sprintf(DisaText,"move sr, %s", eat); break;
|
||||
case 1: sprintf(DisaText,"move ccr, %s",eat); break;
|
||||
case 2: sprintf(DisaText,"move %s, ccr",eat); break;
|
||||
case 3: sprintf(DisaText,"move %s, sr", eat); break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================ Opcodes 0x41c0+ ================
|
||||
static int DisaLea(int op)
|
||||
{
|
||||
// Lea 0100nnn1 11eeeeee (eeeeee=ea)
|
||||
int sea=0,dea=0;
|
||||
char seat[64]="",deat[64]="";
|
||||
|
||||
sea=op&0x003f;
|
||||
DisaGetEa(seat,sea,0);
|
||||
|
||||
dea=(op>>9)&7; dea|=8;
|
||||
DisaGetEa(deat,dea,2);
|
||||
|
||||
sprintf(DisaText,"lea %s, %s",seat,deat);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int MakeRegList(char *list,int mask,int ea)
|
||||
{
|
||||
int reverse=0,i=0,low=0,len=0;
|
||||
|
||||
if ((ea&0x38)==0x20) reverse=1; // -(An), bitfield is reversed
|
||||
|
||||
mask&=0xffff; list[0]=0;
|
||||
|
||||
for (i=0;i<17;i++)
|
||||
{
|
||||
int bit=0;
|
||||
|
||||
// Mask off bit i:
|
||||
if (reverse) bit=0x8000>>i; else bit=1<<i;
|
||||
bit&=mask;
|
||||
|
||||
if (bit==0 || i==8)
|
||||
{
|
||||
// low to i-1 are a continuous section, add it:
|
||||
char add[16]="";
|
||||
int ad=low&8?'a':'d';
|
||||
if (low==i-1) sprintf(add,"%c%d/", ad,low&7);
|
||||
if (low< i-1) sprintf(add,"%c%d-%c%d/",ad,low&7, ad,(i-1)&7);
|
||||
strcat(list,add);
|
||||
|
||||
low=i; // Next section
|
||||
}
|
||||
|
||||
if (bit==0) low=i+1;
|
||||
}
|
||||
|
||||
// Knock off trailing '/'
|
||||
len=strlen(list);
|
||||
if (len>0) if (list[len-1]=='/') list[len-1]=0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================ Opcodes 0x4840+ ================
|
||||
static int DisaSwap(int op)
|
||||
{
|
||||
// Swap, 01001000 01000nnn swap Dn
|
||||
sprintf(DisaText,"swap d%d",op&7);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================ Opcodes 0x4850+ ================
|
||||
static int DisaPea(int op)
|
||||
{
|
||||
// Pea 01001000 01eeeeee (eeeeee=ea) pea
|
||||
int ea=0;
|
||||
char eat[64]="";
|
||||
|
||||
ea=op&0x003f; if (ea<0x10) return 1; // swap opcode
|
||||
DisaGetEa(eat,ea,2);
|
||||
|
||||
sprintf(DisaText,"pea %s",eat);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================ Opcodes 0x4880+ ================
|
||||
static int DisaExt(int op)
|
||||
{
|
||||
// Ext 01001000 1x000nnn (x=size, eeeeee=EA)
|
||||
char eat[64]="";
|
||||
int size=0;
|
||||
|
||||
size=(op>>6)&1; size++;
|
||||
DisaGetEa(eat,op&0x3f,size);
|
||||
|
||||
sprintf(DisaText,"ext.%c %s",Tasm[size],eat);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================ Opcodes 0x4890+ ================
|
||||
static int DisaMovem(int op)
|
||||
{
|
||||
// Movem 01001d00 1xeeeeee regmask d=direction, x=size, eeeeee=EA
|
||||
int dir=0,size=0;
|
||||
int ea=0,mask=0;
|
||||
char list[64]="",eat[64]="";
|
||||
|
||||
dir=(op>>10)&1;
|
||||
size=((op>>6)&1)+1;
|
||||
ea=op&0x3f; if (ea<0x10) return 1; // ext opcode
|
||||
|
||||
mask=DisaWord(DisaPc)&0xffff; DisaPc+=2;
|
||||
|
||||
MakeRegList(list,mask,ea); // Turn register mask into text
|
||||
DisaGetEa(eat,ea,size);
|
||||
|
||||
if (dir) sprintf(DisaText,"movem.%c %s, %s",Tasm[size],eat,list);
|
||||
else sprintf(DisaText,"movem.%c %s, %s",Tasm[size],list,eat);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================ Opcodes 0x4e40+ ================
|
||||
static int DisaTrap(int op)
|
||||
{
|
||||
sprintf(DisaText,"trap #%d",op&0xf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================ Opcodes 0x4e50+ ================
|
||||
static int DisaLink(int op)
|
||||
{
|
||||
// Link opcode, 01001110 01010nnn dd link An,#offset
|
||||
char eat[64]="";
|
||||
char offset[32]="";
|
||||
|
||||
DisaGetEa(eat,(op&7)|8,0);
|
||||
GetOffset(offset);
|
||||
|
||||
sprintf(DisaText,"link %s,#%s",eat,offset);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================ Opcodes 0x4e58+ ================
|
||||
static int DisaUnlk(int op)
|
||||
{
|
||||
// Link opcode, 01001110 01011nnn dd unlk An
|
||||
char eat[64]="";
|
||||
|
||||
DisaGetEa(eat,(op&7)|8,0);
|
||||
sprintf(DisaText,"unlk %s",eat);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================ Opcodes 0x4e60+ ================
|
||||
static int DisaMoveUsp(int op)
|
||||
{
|
||||
// Move USP opcode, 01001110 0110dnnn move An to/from USP (d=direction)
|
||||
int ea=0,dir=0;
|
||||
char eat[64]="";
|
||||
|
||||
dir=(op>>3)&1;
|
||||
ea=(op&7)|8;
|
||||
DisaGetEa(eat,ea,0);
|
||||
|
||||
if (dir) sprintf(DisaText,"move usp, %s",eat);
|
||||
else sprintf(DisaText,"move %s, usp",eat);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================ Opcodes 0x4e70+ ================
|
||||
static int Disa4E70(int op)
|
||||
{
|
||||
char *inst[8]={"reset","nop","stop","rte","rtd","rts","trapv","rtr"};
|
||||
int n=0;
|
||||
|
||||
n=op&7;
|
||||
|
||||
sprintf(DisaText,"%s",inst[n]);
|
||||
|
||||
//todo - 'stop' with 16 bit data
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================ Opcodes 0x4a00+ ================
|
||||
static int DisaTst(int op)
|
||||
{
|
||||
// Tst 01001010 xxeeeeee (eeeeee=ea)
|
||||
int ea=0;
|
||||
char eat[64]="";
|
||||
int size=0;
|
||||
|
||||
ea=op&0x003f;
|
||||
DisaGetEa(eat,ea,0);
|
||||
size=(op>>6)&3; if (size>=3) return 1;
|
||||
|
||||
sprintf(DisaText,"tst.%c %s",Tasm[size],eat);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================ Opcodes 0x4e80+ ================
|
||||
static int DisaJsr(int op)
|
||||
{
|
||||
// Jsr/Jmp 0100 1110 1mEE Eeee (eeeeee=ea m=1=jmp)
|
||||
int sea=0;
|
||||
char seat[64]="";
|
||||
|
||||
sea=op&0x003f;
|
||||
DisaGetEa(seat,sea,0);
|
||||
|
||||
sprintf(DisaText,"j%s %s", op&0x40?"mp":"sr", seat);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================ Opcodes 0x5000+ ================
|
||||
static int DisaAddq(int op)
|
||||
{
|
||||
// 0101nnnt xxeeeeee (nnn=#8,1-7 t=addq/subq xx=size, eeeeee=EA)
|
||||
int num=0,type=0,size=0,ea=0;
|
||||
char eat[64]="";
|
||||
|
||||
num =(op>>9)&7; if (num==0) num=8;
|
||||
type=(op>>8)&1;
|
||||
size=(op>>6)&3; if (size>=3) return 1;
|
||||
ea = op&0x3f;
|
||||
|
||||
DisaGetEa(eat,ea,size);
|
||||
|
||||
sprintf(DisaText,"%s.%c #%d, %s",type?"subq":"addq",Tasm[size],num,eat);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================ Opcodes 0x50c0+ ================
|
||||
static int DisaSet(int op)
|
||||
{
|
||||
// 0101cccc 11eeeeee (sxx ea)
|
||||
static char *cond[16]=
|
||||
{"t" ,"f", "hi","ls","cc","cs","ne","eq",
|
||||
"vc","vs","pl","mi","ge","lt","gt","le"};
|
||||
char *cc="";
|
||||
int ea=0;
|
||||
char eat[64]="";
|
||||
|
||||
cc=cond[(op>>8)&0xf]; // Get condition code
|
||||
ea=op&0x3f;
|
||||
if ((ea&0x38)==0x08) return 1; // dbra, not scc
|
||||
|
||||
DisaGetEa(eat,ea,0);
|
||||
sprintf(DisaText,"s%s %s",cc,eat);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================ Opcodes 0x50c8+ ================
|
||||
static int DisaDbra(int op)
|
||||
{
|
||||
// 0101cccc 11001nnn offset (dbra/dbxx Rn,offset)
|
||||
int dea=0; char deat[64]="";
|
||||
int pc=0,Offset=0;
|
||||
|
||||
static char *BraCode[16]=
|
||||
{"bt" ,"bra","bhi","bls","bcc","bcs","bne","beq",
|
||||
"bvc","bvs","bpl","bmi","bge","blt","bgt","ble"};
|
||||
char *Bra="";
|
||||
|
||||
dea=op&7;
|
||||
DisaGetEa(deat,dea,2);
|
||||
|
||||
// Get condition code
|
||||
Bra=BraCode[(op>>8)&0xf];
|
||||
|
||||
// Get offset
|
||||
pc=DisaPc;
|
||||
Offset=(short)DisaWord(DisaPc); DisaPc+=2;
|
||||
|
||||
sprintf(DisaText,"d%s %s, %x",Bra,deat,pc+Offset);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================ Opcodes 0x6000+ ================
|
||||
static int DisaBranch(int op)
|
||||
{
|
||||
// Branch 0110cccc nn (cccc=condition)
|
||||
int pc=0,Offset=0;
|
||||
|
||||
static char *BraCode[16]=
|
||||
{"bra","bsr","bhi","bls","bcc","bcs","bne","beq",
|
||||
"bvc","bvs","bpl","bmi","bge","blt","bgt","ble"};
|
||||
char *Bra="";
|
||||
|
||||
// Get condition code
|
||||
Bra=BraCode[(op>>8)&0x0f];
|
||||
|
||||
// Get offset
|
||||
pc=DisaPc;
|
||||
Offset=(char)(op&0xff);
|
||||
if (Offset== 0) { Offset=(short)DisaWord(DisaPc); DisaPc+=2; }
|
||||
else if (Offset==-1) { Offset= DisaLong(DisaPc); DisaPc+=4; }
|
||||
|
||||
sprintf(DisaText,"%s %x",Bra,pc+Offset);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================ Opcodes 0x7000+ ================
|
||||
static int DisaMoveq(int op)
|
||||
{
|
||||
// Moveq 0111rrr0 nn (rrr=Dest register, nn=data)
|
||||
|
||||
int dea=0; char deat[64]="";
|
||||
char *inst="moveq";
|
||||
int val=0;
|
||||
|
||||
dea=(op>>9)&7;
|
||||
DisaGetEa(deat,dea,2);
|
||||
|
||||
val=(char)(op&0xff);
|
||||
sprintf(DisaText,"%s #$%x, %s",inst,val,deat);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================ Opcodes 0x8000+ ================
|
||||
static int DisaArithReg(int op)
|
||||
{
|
||||
// 1t0tnnnd xxeeeeee (tt=type:or/sub/and/add xx=size, eeeeee=EA)
|
||||
int type=0,size=0,dir=0,rea=0,ea=0;
|
||||
char reat[64]="",eat[64]="";
|
||||
char *opcode[]={"or","sub","","","and","add"};
|
||||
|
||||
type=(op>>12)&5;
|
||||
rea =(op>> 9)&7;
|
||||
dir =(op>> 8)&1;
|
||||
size=(op>> 6)&3; if (size>=3) return 1;
|
||||
ea = op&0x3f;
|
||||
|
||||
if (dir && ea<0x10) return 1; // addx opcode
|
||||
|
||||
DisaGetEa(reat,rea,size);
|
||||
DisaGetEa( eat, ea,size);
|
||||
|
||||
if (dir) sprintf(DisaText,"%s.%c %s, %s",opcode[type],Tasm[size],reat,eat);
|
||||
else sprintf(DisaText,"%s.%c %s, %s",opcode[type],Tasm[size],eat,reat);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================ Opcodes 0x8100+ ================
|
||||
static int DisaAbcd(int op)
|
||||
{
|
||||
// 1t00ddd1 0000asss - sbcd/abcd Ds,Dd or -(As),-(Ad)
|
||||
int type=0;
|
||||
int dn=0,addr=0,sn=0;
|
||||
char *opcode[]={"sbcd","abcd"};
|
||||
|
||||
type=(op>>14)&1;
|
||||
dn =(op>> 9)&7;
|
||||
addr=(op>> 3)&1;
|
||||
sn = op &7;
|
||||
|
||||
if (addr) sprintf(DisaText,"%s -(a%d), -(a%d)",opcode[type],sn,dn);
|
||||
else sprintf(DisaText,"%s d%d, d%d", opcode[type],sn,dn);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================ Opcodes 0x80c0+ ================
|
||||
static int DisaMul(int op)
|
||||
{
|
||||
// Div/Mul: 1m00nnns 11eeeeee (m=Mul, nnn=Register Dn, s=signed, eeeeee=EA)
|
||||
int type=0,rea=0,sign=0,ea=0,size=1;
|
||||
char reat[64]="",eat[64]="";
|
||||
char *opcode[2]={"div","mul"};
|
||||
|
||||
type=(op>>14)&1; // div/mul
|
||||
rea =(op>> 9)&7;
|
||||
sign=(op>> 8)&1;
|
||||
ea = op&0x3f;
|
||||
|
||||
DisaGetEa(reat,rea,size);
|
||||
DisaGetEa( eat, ea,size);
|
||||
|
||||
sprintf(DisaText,"%s%c.%c %s, %s",opcode[type],sign?'s':'u',Tasm[size],eat,reat);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================ Opcodes 0x90c0+ ================
|
||||
static int DisaAritha(int op)
|
||||
{
|
||||
// Suba/Cmpa/Adda 1tt1nnnx 11eeeeee (tt=type, x=size, eeeeee=Source EA)
|
||||
int type=0,size=0,sea=0,dea=0;
|
||||
char seat[64]="",deat[64]="";
|
||||
char *aritha[4]={"suba","cmpa","adda",""};
|
||||
|
||||
type=(op>>13)&3; if (type>=3) return 1;
|
||||
size=(op>>8)&1; size++;
|
||||
dea =(op>>9)&7; dea|=8; // Dest=An
|
||||
sea = op&0x003f; // Source
|
||||
|
||||
DisaGetEa(seat,sea,size);
|
||||
DisaGetEa(deat,dea,size);
|
||||
|
||||
sprintf(DisaText,"%s.%c %s, %s",aritha[type],Tasm[size],seat,deat);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================ Opcodes 0xb000+ ================
|
||||
static int DisaCmpEor(int op)
|
||||
{
|
||||
// Cmp/Eor 1011rrrt xxeeeeee (rrr=Dn, t=cmp/eor, xx=size extension, eeeeee=ea)
|
||||
char reat[64]="",eat[64]="";
|
||||
int type=0,size=0;
|
||||
|
||||
type=(op>>8)&1;
|
||||
size=(op>>6)&3; if (size>=3) return 1;
|
||||
DisaGetEa(reat,(op>>9)&7,size);
|
||||
DisaGetEa(eat, op&0x3f, size);
|
||||
|
||||
if (type) sprintf(DisaText,"eor.%c %s, %s",Tasm[size],reat,eat);
|
||||
else sprintf(DisaText,"cmp.%c %s, %s",Tasm[size],eat,reat);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================ Opcodes 0xc140+ ================
|
||||
// 1100ttt1 01000sss exg ds,dt
|
||||
// 1100ttt1 01001sss exg as,at
|
||||
// 1100ttt1 10001sss exg as,dt
|
||||
static int DisaExg(int op)
|
||||
{
|
||||
int tr=0,type=0,sr=0;
|
||||
|
||||
tr =(op>>9)&7;
|
||||
type= op&0xf8;
|
||||
sr = op&7;
|
||||
|
||||
if (type==0x40) sprintf(DisaText,"exg d%d, d%d",sr,tr);
|
||||
else if (type==0x48) sprintf(DisaText,"exg a%d, a%d",sr,tr);
|
||||
else if (type==0x88) sprintf(DisaText,"exg a%d, d%d",sr,tr);
|
||||
else return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================ Opcodes 0xd100+ ================
|
||||
static int DisaAddx(int op)
|
||||
{
|
||||
// 1t01ddd1 xx000sss addx
|
||||
int type=0,size=0,dea=0,sea=0;
|
||||
char deat[64]="",seat[64]="";
|
||||
char *opcode[6]={"","subx","","","","addx"};
|
||||
|
||||
type=(op>>12)&5;
|
||||
dea =(op>> 9)&7;
|
||||
size=(op>> 6)&3; if (size>=3) return 1;
|
||||
sea = op&0x3f;
|
||||
|
||||
DisaGetEa(deat,dea,size);
|
||||
DisaGetEa(seat,sea,size);
|
||||
|
||||
sprintf(DisaText,"%s.%c %s, %s",opcode[type],Tasm[size],seat,deat);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================ Opcodes 0xe000+ ================
|
||||
static char *AsrName[4]={"as","ls","rox","ro"};
|
||||
static int DisaAsr(int op)
|
||||
{
|
||||
// Asr/l/Ror/l etc - 1110cccd xxuttnnn
|
||||
// (ccc=count, d=direction xx=size extension, u=use reg for count, tt=type, nnn=register Dn)
|
||||
int count=0,dir=0,size=0,usereg=0,type=0,num=0;
|
||||
|
||||
count =(op>>9)&7;
|
||||
dir =(op>>8)&1;
|
||||
size =(op>>6)&3; if (size>=3) return 1; // todo Asr EA
|
||||
usereg=(op>>5)&1;
|
||||
type =(op>>3)&3;
|
||||
num = op &7; // Register number
|
||||
|
||||
if (usereg==0) count=((count-1)&7)+1; // because ccc=000 means 8
|
||||
|
||||
sprintf(DisaText,"%s%c.%c %c%d, d%d",
|
||||
AsrName[type], dir?'l':'r', Tasm[size],
|
||||
usereg?'d':'#', count, num);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int DisaAsrEa(int op)
|
||||
{
|
||||
// Asr/l/Ror/l etc EA - 11100ttd 11eeeeee
|
||||
int type=0,dir=0,size=1;
|
||||
char eat[64]="";
|
||||
|
||||
type=(op>>9)&3;
|
||||
dir =(op>>8)&1;
|
||||
DisaGetEa(eat,op&0x3f,size);
|
||||
|
||||
sprintf(DisaText,"%s%c.w %s", AsrName[type], dir?'l':'r', eat);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
|
||||
static int TryOp(int op)
|
||||
{
|
||||
if ((op&0xf100)==0x0000) DisaArithImm(op); // Ori/And/Sub/Add/Eor/Cmp Immediate
|
||||
if ((op&0xf5bf)==0x003c) DisaArithSr(op); // Ori/Andi/Eori $nnnn,sr
|
||||
if ((op&0xf100)==0x0100) DisaBtstReg(op);
|
||||
if ((op&0xf138)==0x0108) DisaMovep(op);
|
||||
if ((op&0xff00)==0x0800) DisaBtstImm(op); // Btst/Bchg/Bclr/Bset
|
||||
if ((op&0xc000)==0x0000) DisaMove(op);
|
||||
if ((op&0xf900)==0x4000) DisaNeg(op); // Negx/Clr/Neg/Not
|
||||
if ((op&0xf1c0)==0x41c0) DisaLea(op);
|
||||
if ((op&0xf9c0)==0x40c0) DisaMoveSr(op);
|
||||
if ((op&0xfff8)==0x4840) DisaSwap(op);
|
||||
if ((op&0xffc0)==0x4840) DisaPea(op);
|
||||
if ((op&0xffb8)==0x4880) DisaExt(op);
|
||||
if ((op&0xfb80)==0x4880) DisaMovem(op);
|
||||
if ((op&0xff00)==0x4a00) DisaTst(op);
|
||||
if ((op&0xfff0)==0x4e40) DisaTrap(op);
|
||||
if ((op&0xfff8)==0x4e50) DisaLink(op);
|
||||
if ((op&0xfff8)==0x4e58) DisaUnlk(op);
|
||||
if ((op&0xfff0)==0x4e60) DisaMoveUsp(op);
|
||||
if ((op&0xfff8)==0x4e70) Disa4E70(op);
|
||||
if ((op&0xff80)==0x4e80) DisaJsr(op);
|
||||
if ((op&0xf000)==0x5000) DisaAddq(op);
|
||||
if ((op&0xf0c0)==0x50c0) DisaSet(op);
|
||||
if ((op&0xf0f8)==0x50c8) DisaDbra(op);
|
||||
if ((op&0xf000)==0x6000) DisaBranch(op);
|
||||
if ((op&0xa000)==0x8000) DisaArithReg(op); // Or/Sub/And/Add
|
||||
if ((op&0xb1f0)==0x8100) DisaAbcd(op);
|
||||
if ((op&0xb130)==0x9100) DisaAddx(op);
|
||||
if ((op&0xb0c0)==0x80c0) DisaMul(op);
|
||||
if ((op&0xf100)==0x7000) DisaMoveq(op);
|
||||
if ((op&0x90c0)==0x90c0) DisaAritha(op);
|
||||
if ((op&0xf000)==0xb000) DisaCmpEor(op);
|
||||
if ((op&0xf130)==0xc100) DisaExg(op);
|
||||
if ((op&0xf000)==0xe000) DisaAsr(op);
|
||||
if ((op&0xf8c0)==0xe0c0) DisaAsrEa(op);
|
||||
|
||||
// Unknown opcoode
|
||||
return 0;
|
||||
}
|
||||
|
||||
int DisaGet()
|
||||
{
|
||||
int op=0;
|
||||
if (DisaWord==NULL) return 1;
|
||||
|
||||
Comment[0]=0;
|
||||
DisaText[0]=0; // Assume opcode unknown
|
||||
|
||||
op=DisaWord(DisaPc)&0xffff; DisaPc+=2;
|
||||
TryOp(op);
|
||||
strcat(DisaText,Comment);
|
||||
|
||||
// Unknown opcoode
|
||||
return 0;
|
||||
}
|
24
cpu/Cyclone/Disa/Disa.h
Normal file
24
cpu/Cyclone/Disa/Disa.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
|
||||
// Dave's Disa 68000 Disassembler
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if defined(ARM) || defined(GP32) || !defined (__WINS__)
|
||||
#define CPU_CALL
|
||||
#else
|
||||
#define CPU_CALL __fastcall
|
||||
#endif
|
||||
|
||||
extern unsigned int DisaPc;
|
||||
extern char *DisaText; // Text buffer to write in
|
||||
|
||||
extern unsigned short (CPU_CALL *DisaWord)(unsigned int a);
|
||||
int DisaGetEa(char *t,int ea,int size);
|
||||
|
||||
int DisaGet();
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // End of extern "C"
|
||||
#endif
|
414
cpu/Cyclone/Ea.cpp
Normal file
414
cpu/Cyclone/Ea.cpp
Normal file
|
@ -0,0 +1,414 @@
|
|||
|
||||
#include "app.h"
|
||||
|
||||
// some ops use non-standard cycle counts for EAs, so are listed here.
|
||||
// all constants borrowed from the MUSASHI core by Karl Stenerud.
|
||||
|
||||
/* Extra cycles for JMP instruction (000, 010) */
|
||||
int g_jmp_cycle_table[8] =
|
||||
{
|
||||
4, /* EA_MODE_AI */
|
||||
6, /* EA_MODE_DI */
|
||||
10, /* EA_MODE_IX */
|
||||
6, /* EA_MODE_AW */
|
||||
8, /* EA_MODE_AL */
|
||||
6, /* EA_MODE_PCDI */
|
||||
10, /* EA_MODE_PCIX */
|
||||
0, /* EA_MODE_I */
|
||||
};
|
||||
|
||||
/* Extra cycles for JSR instruction (000, 010) */
|
||||
int g_jsr_cycle_table[8] =
|
||||
{
|
||||
4, /* EA_MODE_AI */
|
||||
6, /* EA_MODE_DI */
|
||||
10, /* EA_MODE_IX */
|
||||
6, /* EA_MODE_AW */
|
||||
8, /* EA_MODE_AL */
|
||||
6, /* EA_MODE_PCDI */
|
||||
10, /* EA_MODE_PCIX */
|
||||
0, /* EA_MODE_I */
|
||||
};
|
||||
|
||||
/* Extra cycles for LEA instruction (000, 010) */
|
||||
int g_lea_cycle_table[8] =
|
||||
{
|
||||
4, /* EA_MODE_AI */
|
||||
8, /* EA_MODE_DI */
|
||||
12, /* EA_MODE_IX */
|
||||
8, /* EA_MODE_AW */
|
||||
12, /* EA_MODE_AL */
|
||||
8, /* EA_MODE_PCDI */
|
||||
12, /* EA_MODE_PCIX */
|
||||
0, /* EA_MODE_I */
|
||||
};
|
||||
|
||||
/* Extra cycles for PEA instruction (000, 010) */
|
||||
int g_pea_cycle_table[8] =
|
||||
{
|
||||
6, /* EA_MODE_AI */
|
||||
10, /* EA_MODE_DI */
|
||||
14, /* EA_MODE_IX */
|
||||
10, /* EA_MODE_AW */
|
||||
14, /* EA_MODE_AL */
|
||||
10, /* EA_MODE_PCDI */
|
||||
14, /* EA_MODE_PCIX */
|
||||
0, /* EA_MODE_I */
|
||||
};
|
||||
|
||||
/* Extra cycles for MOVEM instruction (000, 010) */
|
||||
int g_movem_cycle_table[8] =
|
||||
{
|
||||
0, /* EA_MODE_AI */
|
||||
4, /* EA_MODE_DI */
|
||||
6, /* EA_MODE_IX */
|
||||
4, /* EA_MODE_AW */
|
||||
8, /* EA_MODE_AL */
|
||||
0, /* EA_MODE_PCDI */
|
||||
0, /* EA_MODE_PCIX */
|
||||
0, /* EA_MODE_I */
|
||||
};
|
||||
|
||||
// add nonstandard EA
|
||||
int Ea_add_ns(int *tab, int ea)
|
||||
{
|
||||
if(ea<0x10) return 0;
|
||||
if((ea&0x38)==0x10) return tab[0]; // (An) (ai)
|
||||
if(ea<0x28) return 0;
|
||||
if(ea<0x30) return tab[1]; // ($nn,An) (di)
|
||||
if(ea<0x38) return tab[2]; // ($nn,An,Rn) (ix)
|
||||
if(ea==0x38) return tab[3]; // (aw)
|
||||
if(ea==0x39) return tab[4]; // (al)
|
||||
if(ea==0x3a) return tab[5]; // ($nn,PC) (pcdi)
|
||||
if(ea==0x3b) return tab[6]; // ($nn,pc,Rn) (pcix)
|
||||
if(ea==0x3c) return tab[7]; // #$nnnn (i)
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Gets the offset of a register for an ea, and puts it in 'r'
|
||||
// Shifted left by 'shift'
|
||||
// Doesn't trash anything
|
||||
static int EaCalcReg(int r,int ea,int mask,int forceor,int shift,int noshift=0)
|
||||
{
|
||||
int i=0,low=0,needor=0;
|
||||
int lsl=0;
|
||||
|
||||
for (i=mask|0x8000; (i&1)==0; i>>=1) low++; // Find out how high up the EA mask is
|
||||
mask&=0xf<<low; // This is the max we can do
|
||||
|
||||
if (ea>=8) needor=1; // Need to OR to access A0-7
|
||||
|
||||
if ((mask>>low)&8) if (ea&8) needor=0; // Ah - no we don't actually need to or, since the bit is high in r8
|
||||
|
||||
if (forceor) needor=1; // Special case for 0x30-0x38 EAs ;)
|
||||
|
||||
ot(" and r%d,r8,#0x%.4x\n",r,mask);
|
||||
if (needor) ot(" orr r%d,r%d,#0x%x ;@ A0-7\n",r,r,8<<low);
|
||||
|
||||
// Find out amount to shift left:
|
||||
lsl=shift-low;
|
||||
|
||||
if (lsl&&!noshift)
|
||||
{
|
||||
ot(" mov r%d,r%d,",r,r);
|
||||
if (lsl>0) ot("lsl #%d\n", lsl);
|
||||
else ot("lsr #%d\n",-lsl);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EaCalc - ARM Register 'a' = Effective Address
|
||||
// Trashes r0,r2 and r3
|
||||
// size values 0, 1, 2 ~ byte, word, long
|
||||
int EaCalc(int a,int mask,int ea,int size,int top)
|
||||
{
|
||||
char text[32]="";
|
||||
int func=0;
|
||||
|
||||
DisaPc=2; DisaGetEa(text,ea,size); // Get text version of the effective address
|
||||
func=0x68+(size<<2); // Get correct read handler
|
||||
|
||||
if (ea<0x10)
|
||||
{
|
||||
int noshift=0;
|
||||
if (size>=2||(size==0&&top)) noshift=1; // Saves one opcode
|
||||
|
||||
ot(";@ EaCalc : Get register index into r%d:\n",a);
|
||||
|
||||
EaCalcReg(a,ea,mask,0,2,noshift);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ot(";@ EaCalc : Get '%s' into r%d:\n",text,a);
|
||||
// (An), (An)+, -(An)
|
||||
if (ea<0x28)
|
||||
{
|
||||
int step=1<<size, strr=a;
|
||||
int low=0,lsl,i;
|
||||
|
||||
if ((ea&7)==7 && step<2) step=2; // move.b (a7)+ or -(a7) steps by 2 not 1
|
||||
|
||||
EaCalcReg(2,ea,mask,0,0,1);
|
||||
if(mask)
|
||||
for (i=mask|0x8000; (i&1)==0; i>>=1) low++; // Find out how high up the EA mask is
|
||||
lsl=2-low; // Having a lsl #x here saves one opcode
|
||||
if (lsl>=0) ot(" ldr r%d,[r7,r2,lsl #%i]\n",a,lsl);
|
||||
else if (lsl<0) ot(" ldr r%d,[r7,r2,lsr #%i]\n",a,-lsl);
|
||||
|
||||
if ((ea&0x38)==0x18) // (An)+
|
||||
{
|
||||
ot(" add r3,r%d,#%d ;@ Post-increment An\n",a,step);
|
||||
strr=3;
|
||||
}
|
||||
|
||||
if ((ea&0x38)==0x20) // -(An)
|
||||
ot(" sub r%d,r%d,#%d ;@ Pre-decrement An\n",a,a,step);
|
||||
|
||||
if ((ea&0x38)==0x18||(ea&0x38)==0x20)
|
||||
{
|
||||
if (lsl>=0) ot(" str r%d,[r7,r2,lsl #%i]\n",strr,lsl);
|
||||
else if (lsl<0) ot(" str r%d,[r7,r2,lsr #%i]\n",strr,-lsl);
|
||||
}
|
||||
|
||||
if ((ea&0x38)==0x20) Cycles+=size<2 ? 6:10; // -(An) Extra cycles
|
||||
else Cycles+=size<2 ? 4:8; // (An),(An)+ Extra cycles
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ea<0x30) // ($nn,An) (di)
|
||||
{
|
||||
EaCalcReg(2,8,mask,0,0);
|
||||
ot(" ldrsh r0,[r4],#2 ;@ Fetch offset\n");
|
||||
ot(" ldr r2,[r7,r2,lsl #2]\n");
|
||||
ot(" add r%d,r0,r2 ;@ Add on offset\n",a);
|
||||
Cycles+=size<2 ? 8:12; // Extra cycles
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ea<0x38) // ($nn,An,Rn) (ix)
|
||||
{
|
||||
ot(";@ Get extension word into r3:\n");
|
||||
ot(" ldrh r3,[r4],#2 ;@ ($Disp,PC,Rn)\n");
|
||||
ot(" mov r2,r3,lsr #10\n");
|
||||
ot(" tst r3,#0x0800 ;@ Is Rn Word or Long\n");
|
||||
ot(" and r2,r2,#0x3c ;@ r2=Index of Rn\n");
|
||||
ot(" ldreqsh r2,[r7,r2] ;@ r2=Rn.w\n");
|
||||
ot(" ldrne r2,[r7,r2] ;@ r2=Rn.l\n");
|
||||
ot(" mov r0,r3,asl #24 ;@ r0=Get 8-bit signed Disp\n");
|
||||
ot(" add r3,r2,r0,asr #24 ;@ r3=Disp+Rn\n");
|
||||
|
||||
EaCalcReg(2,8,mask,1,0);
|
||||
ot(" ldr r2,[r7,r2,lsl #2]\n");
|
||||
ot(" add r%d,r2,r3 ;@ r%d=Disp+An+Rn\n",a,a);
|
||||
Cycles+=size<2 ? 10:14; // Extra cycles
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ea==0x38) // (aw)
|
||||
{
|
||||
ot(" ldrsh r%d,[r4],#2 ;@ Fetch Absolute Short address\n",a);
|
||||
Cycles+=size<2 ? 8:12; // Extra cycles
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ea==0x39) // (al)
|
||||
{
|
||||
ot(" ldrh r2,[r4],#2 ;@ Fetch Absolute Long address\n");
|
||||
ot(" ldrh r0,[r4],#2\n");
|
||||
ot(" orr r%d,r0,r2,lsl #16\n",a);
|
||||
Cycles+=size<2 ? 12:16; // Extra cycles
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ea==0x3a) // ($nn,PC) (pcdi)
|
||||
{
|
||||
ot(" ldr r0,[r7,#0x60] ;@ Get Memory base\n");
|
||||
ot(" sub r0,r4,r0 ;@ Real PC\n");
|
||||
ot(" ldrsh r2,[r4],#2 ;@ Fetch extension\n");
|
||||
ot(" mov r0,r0,lsl #8\n");
|
||||
ot(" add r%d,r2,r0,asr #8 ;@ ($nn,PC)\n",a);
|
||||
Cycles+=size<2 ? 8:12; // Extra cycles
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ea==0x3b) // ($nn,pc,Rn) (pcix)
|
||||
{
|
||||
ot(" ldr r0,[r7,#0x60] ;@ Get Memory base\n");
|
||||
ot(" ldrh r3,[r4] ;@ Get extension word\n");
|
||||
ot(" sub r0,r4,r0 ;@ r0=PC\n");
|
||||
ot(" add r4,r4,#2\n");
|
||||
ot(" mov r0,r0,asl #8 ;@ use only 24bits of PC\n");
|
||||
ot(" mov r2,r3,lsr #10\n");
|
||||
ot(" tst r3,#0x0800 ;@ Is Rn Word or Long\n");
|
||||
ot(" and r2,r2,#0x3c ;@ r2=Index of Rn\n");
|
||||
ot(" ldreqsh r2,[r7,r2] ;@ r2=Rn.w\n");
|
||||
ot(" ldrne r2,[r7,r2] ;@ r2=Rn.l\n");
|
||||
ot(" mov r3,r3,asl #24 ;@ r3=Get 8-bit signed Disp\n");
|
||||
ot(" add r2,r2,r3,asr #24 ;@ r2=Disp+Rn\n");
|
||||
ot(" add r%d,r2,r0,asr #8 ;@ r%d=Disp+PC+Rn\n",a,a);
|
||||
Cycles+=size<2 ? 10:14; // Extra cycles
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ea==0x3c) // #$nnnn (i)
|
||||
{
|
||||
if (size<2)
|
||||
{
|
||||
ot(" ldr%s r%d,[r4],#2 ;@ Fetch immediate value\n",Sarm[size&3],a);
|
||||
Cycles+=4; // Extra cycles
|
||||
return 0;
|
||||
}
|
||||
|
||||
ot(" ldrh r2,[r4],#2 ;@ Fetch immediate value\n");
|
||||
ot(" ldrh r0,[r4],#2\n");
|
||||
ot(" orr r%d,r0,r2,lsl #16\n",a);
|
||||
Cycles+=8; // Extra cycles
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Read effective address in (ARM Register 'a') to ARM register 'v'
|
||||
// 'a' and 'v' can be anything but 0 is generally best (for both)
|
||||
// If (ea<0x10) nothing is trashed, else r0-r3 is trashed
|
||||
// If 'top' is given, the ARM register v shifted to the top, e.g. 0xc000 -> 0xc0000000
|
||||
// Otherwise the ARM register v is sign extended, e.g. 0xc000 -> 0xffffc000
|
||||
|
||||
int EaRead(int a,int v,int ea,int size,int mask,int top)
|
||||
{
|
||||
char text[32]="";
|
||||
int shift=0;
|
||||
|
||||
shift=32-(8<<size);
|
||||
|
||||
DisaPc=2; DisaGetEa(text,ea,size); // Get text version of the effective address
|
||||
|
||||
if (ea<0x10)
|
||||
{
|
||||
int lsl=0,low=0,i;
|
||||
if (size>=2||(size==0&&top)) {
|
||||
if(mask)
|
||||
for (i=mask|0x8000; (i&1)==0; i>>=1) low++; // Find out how high up the EA mask is
|
||||
lsl=2-low; // Having a lsl #2 here saves one opcode
|
||||
}
|
||||
|
||||
ot(";@ EaRead : Read register[r%d] into r%d:\n",a,v);
|
||||
|
||||
if (lsl>0) ot(" ldr%s r%d,[r7,r%d,lsl #%i]\n",Narm[size&3],v,a,lsl);
|
||||
else if (lsl<0) ot(" ldr%s r%d,[r7,r%d,lsr #%i]\n",Narm[size&3],v,a,-lsl);
|
||||
else ot(" ldr%s r%d,[r7,r%d]\n",Sarm[size&3],v,a);
|
||||
|
||||
if (top && shift) ot(" mov r%d,r%d,asl #%d\n",v,v,shift);
|
||||
|
||||
ot("\n"); return 0;
|
||||
}
|
||||
|
||||
ot(";@ EaRead : Read '%s' (address in r%d) into r%d:\n",text,a,v);
|
||||
|
||||
if (ea==0x3c)
|
||||
{
|
||||
int asl=0;
|
||||
|
||||
if (top) asl=shift;
|
||||
|
||||
if (v!=a || asl) ot(" mov r%d,r%d,asl #%d\n",v,a,asl);
|
||||
ot("\n"); return 0;
|
||||
}
|
||||
|
||||
if (a!=0) ot(" mov r0,r%d\n",a);
|
||||
|
||||
if (ea>=0x3a && ea<=0x3b) MemHandler(2,size); // Fetch
|
||||
else MemHandler(0,size); // Read
|
||||
|
||||
if (v!=0 || shift) {
|
||||
if (shift) ot(" mov r%d,r0,asl #%d\n",v,shift);
|
||||
else ot(" mov r%d,r0\n",v);
|
||||
}
|
||||
if (top==0 && shift) ot(" mov r%d,r%d,asr #%d\n",v,v,shift);
|
||||
|
||||
ot("\n"); return 0;
|
||||
}
|
||||
|
||||
// Return 1 if we can read this ea
|
||||
int EaCanRead(int ea,int size)
|
||||
{
|
||||
if (size<0)
|
||||
{
|
||||
// LEA:
|
||||
// These don't make sense?:
|
||||
if (ea< 0x10) return 0; // Register
|
||||
if (ea==0x3c) return 0; // Immediate
|
||||
if (ea>=0x18 && ea<0x28) return 0; // Pre/Post inc/dec An
|
||||
}
|
||||
|
||||
if (ea<=0x3c) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Write effective address (ARM Register 'a') with ARM register 'v'
|
||||
// Trashes r0-r3,r12,lr; 'a' can be 0 or 2+, 'v' can be 1 or higher
|
||||
// If a==0 and v==1 it's faster though.
|
||||
int EaWrite(int a,int v,int ea,int size,int mask,int top)
|
||||
{
|
||||
char text[32]="";
|
||||
int shift=0;
|
||||
|
||||
if(a == 1) { printf("Error! EaWrite a==1 !\n"); return 1; }
|
||||
|
||||
if (top) shift=32-(8<<size);
|
||||
|
||||
DisaPc=2; DisaGetEa(text,ea,size); // Get text version of the effective address
|
||||
|
||||
if (ea<0x10)
|
||||
{
|
||||
int lsl=0,low=0,i;
|
||||
if (size>=2||(size==0&&top)) {
|
||||
if(mask)
|
||||
for (i=mask|0x8000; (i&1)==0; i>>=1) low++; // Find out how high up the EA mask is
|
||||
lsl=2-low; // Having a lsl #x here saves one opcode
|
||||
}
|
||||
|
||||
ot(";@ EaWrite: r%d into register[r%d]:\n",v,a);
|
||||
if (shift) ot(" mov r%d,r%d,asr #%d\n",v,v,shift);
|
||||
|
||||
if (lsl>0) ot(" str%s r%d,[r7,r%d,lsl #%i]\n",Narm[size&3],v,a,lsl);
|
||||
else if (lsl<0) ot(" str%s r%d,[r7,r%d,lsr #%i]\n",Narm[size&3],v,a,-lsl);
|
||||
else ot(" str%s r%d,[r7,r%d]\n",Narm[size&3],v,a);
|
||||
|
||||
ot("\n"); return 0;
|
||||
}
|
||||
|
||||
ot(";@ EaWrite: Write r%d into '%s' (address in r%d):\n",v,text,a);
|
||||
|
||||
if (ea==0x3c) { ot("Error! Write EA=0x%x\n\n",ea); return 1; }
|
||||
|
||||
if (a!=0 && v!=0) ot(" mov r0,r%d\n",a);
|
||||
if (v!=1 || shift) ot(" mov r1,r%d,asr #%d\n",v,shift);
|
||||
if (a!=0 && v==0) ot(" mov r0,r%d\n",a);
|
||||
|
||||
MemHandler(1,size); // Call write handler
|
||||
|
||||
ot("\n"); return 0;
|
||||
}
|
||||
|
||||
// Return 1 if we can write this ea
|
||||
int EaCanWrite(int ea)
|
||||
{
|
||||
if (ea<=0x39) return 1; // 3b?
|
||||
return 0;
|
||||
}
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Return 1 if EA is An reg
|
||||
int EaAn(int ea)
|
||||
{
|
||||
if((ea&0x38)==8) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
648
cpu/Cyclone/Main.cpp
Normal file
648
cpu/Cyclone/Main.cpp
Normal file
|
@ -0,0 +1,648 @@
|
|||
|
||||
#include "app.h"
|
||||
|
||||
static FILE *AsmFile=NULL;
|
||||
|
||||
static int CycloneVer=0x0086; // 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
|
||||
char *Sarm[4]={"sb","sh","",""}; // Sign-extend ARM Extensions for operand sizes 0,1,2
|
||||
int Cycles; // Current cycles for opcode
|
||||
|
||||
|
||||
void ot(const char *format, ...)
|
||||
{
|
||||
va_list valist=NULL;
|
||||
int i, len;
|
||||
|
||||
// notaz: stop me from leaving newlines in the middle of format string
|
||||
// and generating bad code
|
||||
for(i=0, len=strlen(format); i < len && format[i] != '\n'; i++);
|
||||
if(i < len-1 && format[len-1] != '\n') printf("\nWARNING: possible improper newline placement:\n%s\n", format);
|
||||
|
||||
va_start(valist,format);
|
||||
if (AsmFile) vfprintf(AsmFile,format,valist);
|
||||
va_end(valist);
|
||||
}
|
||||
|
||||
void ltorg()
|
||||
{
|
||||
if (ms) ot(" LTORG\n");
|
||||
else ot(" .ltorg\n");
|
||||
}
|
||||
|
||||
// trashes all temp regs
|
||||
static void PrintException(int ints)
|
||||
{
|
||||
if(!ints) {
|
||||
ot(" ;@ Cause an Exception - Vector address in r0\n");
|
||||
ot(" mov r11,r0\n");
|
||||
}
|
||||
|
||||
ot(";@ swap OSP <-> A7?\n");
|
||||
ot(" ldr r0,[r7,#0x44] ;@ Get SR high\n");
|
||||
ot(" tst r0,#0x20\n");
|
||||
ot(" bne no_sp_swap%i\n",ints);
|
||||
ot(";@ swap OSP and A7:\n");
|
||||
ot(" ldr r0,[r7,#0x3C] ;@ Get A7\n");
|
||||
ot(" ldr r1,[r7,#0x48] ;@ Get OSP\n");
|
||||
ot(" str r0,[r7,#0x48]\n");
|
||||
ot(" str r1,[r7,#0x3C]\n");
|
||||
ot("no_sp_swap%i%s\n",ints,ms?"":":");
|
||||
|
||||
ot(" ldr r10,[r7,#0x60] ;@ Get Memory base\n");
|
||||
ot(" mov r1,r4,lsl #8\n");
|
||||
ot(" sub r1,r1,r10,lsl #8 ;@ r1 = Old PC\n");
|
||||
ot(" mov r1,r1,asr #8 ;@ push sign extended\n");
|
||||
OpPush32();
|
||||
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");
|
||||
#endif
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
// Trashes r0,r1
|
||||
void CheckInterrupt(int op)
|
||||
{
|
||||
ot(";@ CheckInterrupt:\n");
|
||||
ot(" ldr r0,[r7,#0x44]\n"); // same as ldrb r0,[r7,#0x47]
|
||||
ot(" movs r0,r0,lsr #24 ;@ Get IRQ level (loading word is faster)\n");
|
||||
ot(" beq NoInts%x\n",op);
|
||||
ot(" cmp r0,#6 ;@ irq>6 ?\n");
|
||||
ot(" ldrleb r1,[r7,#0x44] ;@ Get SR high: T_S__III\n");
|
||||
ot(" andle r1,r1,#7 ;@ Get interrupt mask\n");
|
||||
ot(" cmple r0,r1 ;@ irq<=6: Is irq<=mask ?\n");
|
||||
ot(" blgt DoInterrupt\n");
|
||||
ot("NoInts%x%s\n", op,ms?"":":");
|
||||
ot("\n");
|
||||
}
|
||||
|
||||
static void PrintFramework()
|
||||
{
|
||||
ot(";@ --------------------------- Framework --------------------------\n");
|
||||
if (ms) ot("CycloneRun\n");
|
||||
else ot("CycloneRun:\n");
|
||||
|
||||
ot(" stmdb sp!,{r4-r11,lr}\n");
|
||||
|
||||
ot(" mov r7,r0 ;@ r7 = Pointer to Cpu Context\n");
|
||||
ot(" ;@ r0-3 = Temporary registers\n");
|
||||
ot(" ldrb r9,[r7,#0x46] ;@ r9 = Flags (NZCV)\n");
|
||||
ot(" ldr r6,=JumpTab ;@ r6 = Opcode Jump table\n");
|
||||
ot(" ldr r5,[r7,#0x5c] ;@ r5 = Cycles\n");
|
||||
ot(" ldr r4,[r7,#0x40] ;@ r4 = Current PC + Memory Base\n");
|
||||
ot(" ;@ r8 = Current Opcode\n");
|
||||
ot(" ldr r0,[r7,#0x44]\n");
|
||||
ot(" mov r9,r9,lsl #28 ;@ r9 = Flags 0xf0000000, cpsr format\n");
|
||||
ot(" ;@ r10 = Source value / Memory Base\n");
|
||||
ot("\n");
|
||||
ot(";@ CheckInterrupt:\n");
|
||||
ot(" movs r0,r0,lsr #24 ;@ Get IRQ level\n"); // same as ldrb r0,[r7,#0x47]
|
||||
ot(" beq NoInts0\n");
|
||||
ot(" cmp r0,#6 ;@ irq>6 ?\n");
|
||||
ot(" ldrleb r1,[r7,#0x44] ;@ Get SR high: T_S__III\n");
|
||||
ot(" andle r1,r1,#7 ;@ Get interrupt mask\n");
|
||||
ot(" cmple r0,r1 ;@ irq<=6: Is irq<=mask ?\n");
|
||||
ot(" blgt DoInterrupt\n");
|
||||
ot(";@ Check if interrupt used up all the cycles:\n");
|
||||
ot(" subs r5,r5,#0\n");
|
||||
ot(" blt CycloneEndNoBack\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(" 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("\n");
|
||||
ot("\n");
|
||||
|
||||
ot(";@ We come back here after execution\n");
|
||||
ot("CycloneEnd%s\n", ms?"":":");
|
||||
ot(" sub r4,r4,#2\n");
|
||||
ot("CycloneEndNoBack%s\n", ms?"":":");
|
||||
ot(" mov r9,r9,lsr #28\n");
|
||||
ot(" str r4,[r7,#0x40] ;@ Save Current PC + Memory Base\n");
|
||||
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();
|
||||
|
||||
#if COMPRESS_JUMPTABLE
|
||||
ot(";@ uncompress jump table\n");
|
||||
if (ms) ot("CycloneInit\n");
|
||||
else ot("CycloneInit:\n");
|
||||
ot(" ldr r12,=JumpTab\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,=JumpTab\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");
|
||||
#else
|
||||
ot(";@ do nothing\n");
|
||||
if (ms) ot("CycloneInit\n");
|
||||
else ot("CycloneInit:\n");
|
||||
ot(" bx lr\n");
|
||||
ot("\n");
|
||||
#endif
|
||||
if (ms) ot("CycloneSetSr\n");
|
||||
else ot("CycloneSetSr:\n");
|
||||
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(" 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,lsr #3\n");
|
||||
ot(" strb r2,[r0,#0x45] ;@ the X flag\n");
|
||||
ot(" bic r2,r1,#0xf3\n");
|
||||
ot(" tst r1,#1\n");
|
||||
ot(" orrne r2,r2,#2\n");
|
||||
ot(" tst r1,#2\n");
|
||||
ot(" orrne r2,r2,#1\n");
|
||||
ot(" strb r2,[r0,#0x46] ;@ flags\n");
|
||||
ot(" bx lr\n");
|
||||
ot("\n");
|
||||
|
||||
if (ms) ot("CycloneGetSr\n");
|
||||
else ot("CycloneGetSr:\n");
|
||||
ot(" ldrb r1,[r0,#0x46] ;@ flags\n");
|
||||
ot(" bic r2,r1,#0xf3\n");
|
||||
ot(" tst r1,#1\n");
|
||||
ot(" orrne r2,r2,#2\n");
|
||||
ot(" tst r1,#2\n");
|
||||
ot(" orrne r2,r2,#1\n");
|
||||
ot(" ldrb r1,[r0,#0x45] ;@ the X flag\n");
|
||||
ot(" tst r1,#2\n");
|
||||
ot(" orrne r2,r2,#0x10\n");
|
||||
ot(" ldrb r1,[r0,#0x44] ;@ the SR high\n");
|
||||
ot(" orr r0,r2,r1,lsl #8\n");
|
||||
ot(" bx lr\n");
|
||||
ot("\n");
|
||||
|
||||
ot(";@ DoInterrupt - r0=IRQ number\n");
|
||||
ot("DoInterrupt%s\n", ms?"":":");
|
||||
ot(" stmdb sp!,{lr} ;@ Push ARM return address\n");
|
||||
|
||||
ot(";@ Get IRQ Vector address:\n");
|
||||
ot(" mov r0,r0,asl #2\n");
|
||||
ot(" add r11,r0,#0x60\n");
|
||||
PrintException(1);
|
||||
|
||||
ot(" ldrb r0,[r7,#0x47] ;@ IRQ\n");
|
||||
ot(" mov r2,#0\n");
|
||||
ot(" orr r1,r0,#0x20 ;@ Supervisor mode + IRQ number\n");
|
||||
ot(" strb r1,[r7,#0x44] ;@ Put SR high\n");
|
||||
|
||||
ot(";@ Clear stopped states:\n");
|
||||
ot(" str r2,[r7,#0x58]\n");
|
||||
ot(" sub r5,r5,#%d ;@ Subtract cycles\n",44);
|
||||
ot("\n");
|
||||
#if USE_INT_ACK_CALLBACK
|
||||
#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 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
|
||||
#else // not USE_INT_ACK_CALLBACK
|
||||
ot(";@ Clear irq:\n");
|
||||
ot(" strb r1,[r7,#0x47]\n");
|
||||
#endif
|
||||
ot(" ldmia sp!,{pc} ;@ Return\n");
|
||||
ot("\n");
|
||||
|
||||
ot("Exception%s\n", ms?"":":");
|
||||
ot("\n");
|
||||
ot(" stmdb sp!,{lr} ;@ Preserve ARM return address\n");
|
||||
PrintException(0);
|
||||
ot(" ldmia sp!,{pc} ;@ Return\n");
|
||||
ot("\n");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Call Read(r0), Write(r0,r1) or Fetch(r0)
|
||||
// Trashes r0-r3,r12,lr
|
||||
int MemHandler(int type,int size)
|
||||
{
|
||||
int func=0;
|
||||
func=0x68+type*0xc+(size<<2); // Find correct offset
|
||||
|
||||
#if MEMHANDLERS_NEED_PC
|
||||
ot(" str r4,[r7,#0x40] ;@ Save PC\n");
|
||||
#endif
|
||||
#if MEMHANDLERS_NEED_FLAGS
|
||||
ot(" mov r3,r9,lsr #28\n");
|
||||
ot(" strb r3,[r7,#0x46] ;@ Save Flags (NZCV)\n");
|
||||
#endif
|
||||
#if MEMHANDLERS_NEED_CYCLES
|
||||
ot(" str r5,[r7,#0x5c] ;@ Save Cycles\n");
|
||||
#endif
|
||||
|
||||
ot(" mov lr,pc\n");
|
||||
ot(" ldr pc,[r7,#0x%x] ;@ Call ",func);
|
||||
|
||||
// Document what we are calling:
|
||||
if (type==0) ot("read");
|
||||
if (type==1) ot("write");
|
||||
if (type==2) ot("fetch");
|
||||
|
||||
if (type==1) ot("%d(r0,r1)",8<<size);
|
||||
else ot("%d(r0)", 8<<size);
|
||||
ot(" handler\n");
|
||||
|
||||
#if MEMHANDLERS_CHANGE_CYCLES
|
||||
ot(" ldr r5,[r7,#0x5c] ;@ Load Cycles\n");
|
||||
#endif
|
||||
#if MEMHANDLERS_CHANGE_FLAGS
|
||||
ot(" ldrb r9,[r7,#0x46] ;@ r9 = Load Flags (NZCV)\n");
|
||||
ot(" mov r9,r9,lsl #28\n");
|
||||
#endif
|
||||
#if MEMHANDLERS_CHANGE_PC
|
||||
ot(" ldr r4,[r7,#0x40] ;@ Load PC\n");
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void PrintOpcodes()
|
||||
{
|
||||
int op=0;
|
||||
|
||||
printf("Creating Opcodes: [");
|
||||
|
||||
ot(";@ ---------------------------- Opcodes ---------------------------\n");
|
||||
|
||||
// Emit null opcode:
|
||||
ot("Op____%s ;@ Called if an opcode is not recognised\n", ms?"":":");
|
||||
ot(" sub r4,r4,#2\n");
|
||||
#if USE_UNRECOGNIZED_CALLBACK
|
||||
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");
|
||||
ot(" ldr r11,[r7,#0x94] ;@ UnrecognizedCallback\n");
|
||||
ot(" tst r11,r11\n");
|
||||
ot(" movne lr,pc\n");
|
||||
ot(" movne pc,r11 ;@ call UnrecognizedCallback if it is defined\n");
|
||||
ot(" ldrb r9,[r7,#0x46] ;@ r9 = Load Flags (NZCV)\n");
|
||||
ot(" ldr r5,[r7,#0x5c] ;@ Load Cycles\n");
|
||||
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(" bleq Exception\n");
|
||||
#else
|
||||
ot(" mov r0,#0x10\n");
|
||||
ot(" bl Exception\n");
|
||||
#endif
|
||||
Cycles=34;
|
||||
OpEnd();
|
||||
|
||||
// Unrecognised a-line and f-line opcodes throw an exception:
|
||||
ot("Op__al%s ;@ Unrecognised a-line opcode\n", ms?"":":");
|
||||
ot(" sub r4,r4,#2\n");
|
||||
#if USE_AFLINE_CALLBACK
|
||||
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");
|
||||
ot(" ldr r11,[r7,#0x94] ;@ UnrecognizedCallback\n");
|
||||
ot(" tst r11,r11\n");
|
||||
ot(" movne lr,pc\n");
|
||||
ot(" movne pc,r11 ;@ call UnrecognizedCallback if it is defined\n");
|
||||
ot(" ldrb r9,[r7,#0x46] ;@ r9 = Load Flags (NZCV)\n");
|
||||
ot(" ldr r5,[r7,#0x5c] ;@ Load Cycles\n");
|
||||
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(" bleq Exception\n");
|
||||
#else
|
||||
ot(" mov r0,#0x28\n");
|
||||
ot(" bl Exception\n");
|
||||
#endif
|
||||
Cycles=4;
|
||||
OpEnd();
|
||||
|
||||
ot("Op__fl%s ;@ Unrecognised f-line opcode\n", ms?"":":");
|
||||
ot(" sub r4,r4,#2\n");
|
||||
#if USE_AFLINE_CALLBACK
|
||||
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");
|
||||
ot(" ldr r11,[r7,#0x94] ;@ UnrecognizedCallback\n");
|
||||
ot(" tst r11,r11\n");
|
||||
ot(" movne lr,pc\n");
|
||||
ot(" movne pc,r11 ;@ call UnrecognizedCallback if it is defined\n");
|
||||
ot(" ldrb r9,[r7,#0x46] ;@ r9 = Load Flags (NZCV)\n");
|
||||
ot(" ldr r5,[r7,#0x5c] ;@ Load Cycles\n");
|
||||
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(" bleq Exception\n");
|
||||
#else
|
||||
ot(" mov r0,#0x2c\n");
|
||||
ot(" bl Exception\n");
|
||||
#endif
|
||||
Cycles=4;
|
||||
OpEnd();
|
||||
|
||||
|
||||
for (op=0;op<0x10000;op++)
|
||||
{
|
||||
if ((op&0xfff)==0) { printf("%x",op>>12); fflush(stdout); } // Update progress
|
||||
|
||||
OpAny(op);
|
||||
}
|
||||
|
||||
ot("\n");
|
||||
|
||||
printf("]\n");
|
||||
}
|
||||
|
||||
// helper
|
||||
static void ott(const char *str, int par, const char *nl, int nlp, int counter, int size)
|
||||
{
|
||||
switch(size) {
|
||||
case 0: if((counter&7)==0) ot(ms?" dcb ":" .byte "); break;
|
||||
case 1: if((counter&7)==0) ot(ms?" dcw ":" .hword "); break;
|
||||
case 2: if((counter&7)==0) ot(ms?" dcd ":" .long "); break;
|
||||
}
|
||||
ot(str, par);
|
||||
if((counter&7)==7) ot(nl,nlp); else ot(",");
|
||||
}
|
||||
|
||||
static void PrintJumpTable()
|
||||
{
|
||||
int i=0,op=0,len=0;
|
||||
|
||||
ot(";@ -------------------------- Jump Table --------------------------\n");
|
||||
|
||||
#if COMPRESS_JUMPTABLE
|
||||
int handlers=0,reps=0,*indexes,ip,u,out;
|
||||
// use some weird compression on the jump table
|
||||
indexes=(int *)malloc(0x10000*4);
|
||||
if(!indexes) { printf("ERROR: out of memory\n"); exit(1); }
|
||||
len=0x10000;
|
||||
|
||||
// space for decompressed table
|
||||
ot(ms?" area |.data|, data\n":" .data\n .align 4\n\n");
|
||||
|
||||
ot("JumpTab%s\n", ms?"":":");
|
||||
if(ms) {
|
||||
for(i = 0; i < 0xa000/8; i++)
|
||||
ot(" dcd 0,0,0,0,0,0,0,0\n");
|
||||
} else
|
||||
ot(" .rept 0x%x\n .long 0,0,0,0,0,0,0,0\n .endr\n", 0xa000/8);
|
||||
|
||||
// hanlers live in "a-line" part of the table
|
||||
// first output nop,a-line,f-line handlers
|
||||
ot(ms?" dcd Op____,Op__al,Op__fl,":" .long Op____,Op__al,Op__fl,");
|
||||
handlers=3;
|
||||
|
||||
for(i=0;i<len;i++)
|
||||
{
|
||||
op=CyJump[i];
|
||||
|
||||
for(u=i-1; u>=0; u--) if(op == CyJump[u]) break; // already done with this op?
|
||||
if(u==-1 && op >= 0) {
|
||||
ott("Op%.4x",op," ;@ %.4x\n",i,handlers,2);
|
||||
indexes[op] = handlers;
|
||||
handlers++;
|
||||
}
|
||||
}
|
||||
if(handlers&7) {
|
||||
fseek(AsmFile, -1, SEEK_CUR); // remove last comma
|
||||
for(i = 8-(handlers&7); i > 0; i--)
|
||||
ot(",000000");
|
||||
ot("\n");
|
||||
}
|
||||
if(ms) {
|
||||
for(i = (0x4000-handlers)/8; i > 0; i--)
|
||||
ot(" dcd 0,0,0,0,0,0,0,0\n");
|
||||
} else {
|
||||
ot(ms?"":" .rept 0x%x\n .long 0,0,0,0,0,0,0,0\n .endr\n", (0x4000-handlers)/8);
|
||||
}
|
||||
printf("total distinct hanlers: %i\n",handlers);
|
||||
// output data
|
||||
for(i=0,ip=0; i < 0xf000; i++, ip++) {
|
||||
op=CyJump[i];
|
||||
if(op == -2) {
|
||||
// it must skip a-line area, because we keep our data there
|
||||
ott("0x%.4x", handlers<<4, "\n",0,ip++,1);
|
||||
ott("0x%.4x", 0x1000, "\n",0,ip,1);
|
||||
i+=0xfff;
|
||||
continue;
|
||||
}
|
||||
for(reps=1; i < 0xf000; i++, reps++) if(op != CyJump[i+1]) break;
|
||||
if(op>=0) out=indexes[op]<<4; else out=0; // unrecognised
|
||||
if(reps <= 0xe || reps==0x10) {
|
||||
if(reps!=0x10) out|=reps; else out|=0xf; // 0xf means 0x10 (0xf appeared to be unused anyway)
|
||||
ott("0x%.4x", out, "\n",0,ip,1);
|
||||
} else {
|
||||
ott("0x%.4x", out, "\n",0,ip++,1);
|
||||
ott("0x%.4x", reps,"\n",0,ip,1);
|
||||
}
|
||||
}
|
||||
if(ip&1) ott("0x%.4x", 0, "\n",0,ip++,1);
|
||||
if(ip&7) fseek(AsmFile, -1, SEEK_CUR); // remove last comma
|
||||
ot("\n");
|
||||
if(ip&7) {
|
||||
for(i = 8-(ip&7); i > 0; i--)
|
||||
ot(",0x0000");
|
||||
ot("\n");
|
||||
}
|
||||
if(ms) {
|
||||
for(i = (0x2000-ip/2)/8+1; i > 0; i--)
|
||||
ot(" dcd 0,0,0,0,0,0,0,0\n");
|
||||
} else {
|
||||
ot(" .rept 0x%x\n .long 0,0,0,0,0,0,0,0\n .endr\n", (0x2000-ip/2)/8+1);
|
||||
}
|
||||
ot("\n");
|
||||
free(indexes);
|
||||
#else
|
||||
ot("JumpTab%s\n", ms?"":":");
|
||||
len=0xfffe; // Hmmm, armasm 2.50.8684 messes up with a 0x10000 long jump table
|
||||
// notaz: same thing with GNU as 2.9-psion-98r2 (reloc overflow)
|
||||
// this is due to COFF objects using only 2 bytes for reloc count
|
||||
|
||||
for (i=0;i<len;i++)
|
||||
{
|
||||
op=CyJump[i];
|
||||
|
||||
if(op>=0) ott("Op%.4x",op," ;@ %.4x\n",i-7,i,2);
|
||||
else if(op==-2) ott("Op__al",0, " ;@ %.4x\n",i-7,i,2);
|
||||
else if(op==-3) ott("Op__fl",0, " ;@ %.4x\n",i-7,i,2);
|
||||
else ott("Op____",0, " ;@ %.4x\n",i-7,i,2);
|
||||
}
|
||||
if(i&7) fseek(AsmFile, -1, SEEK_CUR); // remove last comma
|
||||
|
||||
ot("\n");
|
||||
ot(";@ notaz: we don't want to crash if we run into those 2 missing opcodes\n");
|
||||
ot(";@ so we leave this pattern to patch it later\n");
|
||||
ot("%s 0x78563412\n", ms?" dcd":" .long");
|
||||
ot("%s 0x56341290\n", ms?" dcd":" .long");
|
||||
#endif
|
||||
}
|
||||
|
||||
static int CycloneMake()
|
||||
{
|
||||
int i;
|
||||
char *name="Cyclone.s";
|
||||
|
||||
// Open the assembly file
|
||||
if (ms) name="Cyclone.asm";
|
||||
AsmFile=fopen(name,"wt"); if (AsmFile==NULL) return 1;
|
||||
|
||||
printf("Making %s...\n",name);
|
||||
|
||||
ot("\n;@ Dave's Cyclone 68000 Emulator v%x.%.3x - Assembler Output\n\n",CycloneVer>>12,CycloneVer&0xfff);
|
||||
|
||||
ot(";@ (c) Copyright 2003 Dave, All rights reserved.\n");
|
||||
ot(";@ some code (c) Copyright 2005-2006 notaz, All rights reserved.\n");
|
||||
ot(";@ Cyclone 68000 is free for non-commercial use.\n\n");
|
||||
ot(";@ For commercial use, separate licencing terms must be obtained.\n\n");
|
||||
|
||||
CyJump=(int *)malloc(0x40000); if (CyJump==NULL) return 1;
|
||||
memset(CyJump,0xff,0x40000); // Init to -1
|
||||
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 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("CycloneVer: .long 0x%.4x\n",CycloneVer);
|
||||
}
|
||||
ot("\n");
|
||||
|
||||
PrintFramework();
|
||||
PrintOpcodes();
|
||||
PrintJumpTable();
|
||||
|
||||
if (ms) ot(" END\n");
|
||||
|
||||
fclose(AsmFile); AsmFile=NULL;
|
||||
|
||||
#if 0
|
||||
printf("Assembling...\n");
|
||||
// Assemble the file
|
||||
if (ms) system("armasm Cyclone.asm");
|
||||
else system("as -o Cyclone.o Cyclone.s");
|
||||
printf("Done!\n\n");
|
||||
#endif
|
||||
|
||||
free(CyJump);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
printf("\n Dave's Cyclone 68000 Emulator v%x.%.3x - Core Creator\n\n",CycloneVer>>12,CycloneVer&0xfff);
|
||||
|
||||
// Make GAS or ARMASM version
|
||||
CycloneMake();
|
||||
return 0;
|
||||
}
|
||||
|
119
cpu/Cyclone/OpAny.cpp
Normal file
119
cpu/Cyclone/OpAny.cpp
Normal file
|
@ -0,0 +1,119 @@
|
|||
|
||||
#include "app.h"
|
||||
|
||||
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)
|
||||
{
|
||||
return (unsigned short)( (OpData[a&15]<<8) | OpData[(a+1)&15] );
|
||||
}
|
||||
|
||||
// For opcode 'op' use handler 'use'
|
||||
void OpUse(int op,int use)
|
||||
{
|
||||
char text[64]="";
|
||||
CyJump[op]=use;
|
||||
|
||||
if (op!=use) return;
|
||||
|
||||
// Disassemble opcode
|
||||
DisaPc=0;
|
||||
DisaText=text;
|
||||
DisaWord=OpRead16;
|
||||
|
||||
DisaGet();
|
||||
ot(";@ ---------- [%.4x] %s uses Op%.4x ----------\n",op,text,use);
|
||||
}
|
||||
|
||||
void OpStart(int op)
|
||||
{
|
||||
Cycles=0;
|
||||
OpUse(op,op); // This opcode obviously uses this handler
|
||||
ot("Op%.4x%s\n", op, ms?"":":");
|
||||
}
|
||||
|
||||
void OpEnd()
|
||||
{
|
||||
ot(" ldrh r8,[r4],#2 ;@ Fetch next opcode\n");
|
||||
ot(" subs r5,r5,#%d ;@ Subtract cycles\n",Cycles);
|
||||
ot(" ldrge pc,[r6,r8,asl #2] ;@ Jump to opcode handler\n");
|
||||
ot(" b CycloneEnd\n");
|
||||
ot("\n");
|
||||
}
|
||||
|
||||
int OpBase(int op,int sepa)
|
||||
{
|
||||
int ea=op&0x3f; // Get Effective Address
|
||||
if (ea<0x10) return sepa?(op&~0x7):(op&~0xf); // Use 1 handler for d0-d7 and a0-a7
|
||||
if (ea>=0x18 && ea<0x28 && (ea&7)==7) return op; // Specific handler for (a7)+ and -(a7)
|
||||
if (ea<0x38) return op&~7; // Use 1 handler for (a0)-(a7), etc...
|
||||
return op;
|
||||
}
|
||||
|
||||
// Get flags, trashes r2
|
||||
int OpGetFlags(int subtract,int xbit,int specialz)
|
||||
{
|
||||
if (specialz) ot(" orr r2,r9,#0xb0000000 ;@ for old Z\n");
|
||||
|
||||
ot(" mrs r9,cpsr ;@ r9=flags\n");
|
||||
|
||||
if (specialz) ot(" andeq r9,r9,r2 ;@ fix Z\n");
|
||||
|
||||
if (subtract) ot(" eor r9,r9,#0x20000000 ;@ Invert carry\n");
|
||||
|
||||
if (xbit)
|
||||
{
|
||||
ot(" mov r2,r9,lsr #28\n");
|
||||
ot(" strb r2,[r7,#0x45] ;@ Save X bit\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
void OpAny(int op)
|
||||
{
|
||||
memset(OpData,0x33,sizeof(OpData));
|
||||
OpData[0]=(unsigned char)(op>>8);
|
||||
OpData[1]=(unsigned char)op;
|
||||
|
||||
if ((op&0xf100)==0x0000) OpArith(op);
|
||||
if ((op&0xc000)==0x0000) OpMove(op);
|
||||
if ((op&0xf5bf)==0x003c) OpArithSr(op); // Ori/Andi/Eori $nnnn,sr
|
||||
if ((op&0xf100)==0x0100) OpBtstReg(op);
|
||||
if ((op&0xf138)==0x0108) OpMovep(op);
|
||||
if ((op&0xff00)==0x0800) OpBtstImm(op);
|
||||
if ((op&0xf900)==0x4000) OpNeg(op);
|
||||
if ((op&0xf140)==0x4100) OpChk(op);
|
||||
if ((op&0xf1c0)==0x41c0) OpLea(op);
|
||||
if ((op&0xf9c0)==0x40c0) OpMoveSr(op);
|
||||
if ((op&0xffc0)==0x4800) OpNbcd(op);
|
||||
if ((op&0xfff8)==0x4840) OpSwap(op);
|
||||
if ((op&0xffc0)==0x4840) OpPea(op);
|
||||
if ((op&0xffb8)==0x4880) OpExt(op);
|
||||
if ((op&0xfb80)==0x4880) OpMovem(op);
|
||||
if ((op&0xff00)==0x4a00) OpTst(op);
|
||||
if ((op&0xffc0)==0x4ac0) OpTas(op);
|
||||
if ((op&0xfff0)==0x4e40) OpTrap(op);
|
||||
if ((op&0xfff8)==0x4e50) OpLink(op);
|
||||
if ((op&0xfff8)==0x4e58) OpUnlk(op);
|
||||
if ((op&0xfff0)==0x4e60) OpMoveUsp(op);
|
||||
if ((op&0xfff8)==0x4e70) Op4E70(op); // Reset/Rts etc
|
||||
if ((op&0xfffd)==0x4e70) OpStopReset(op);
|
||||
if ((op&0xff80)==0x4e80) OpJsr(op);
|
||||
if ((op&0xf000)==0x5000) OpAddq(op);
|
||||
if ((op&0xf0c0)==0x50c0) OpSet(op);
|
||||
if ((op&0xf0f8)==0x50c8) OpDbra(op);
|
||||
if ((op&0xf000)==0x6000) OpBranch(op);
|
||||
if ((op&0xf100)==0x7000) OpMoveq(op);
|
||||
if ((op&0xa000)==0x8000) OpArithReg(op); // Or/Sub/And/Add
|
||||
if ((op&0xb1f0)==0x8100) OpAbcd(op);
|
||||
if ((op&0xb0c0)==0x80c0) OpMul(op);
|
||||
if ((op&0x90c0)==0x90c0) OpAritha(op);
|
||||
if ((op&0xb130)==0x9100) OpAddx(op);
|
||||
if ((op&0xf000)==0xb000) OpCmpEor(op);
|
||||
if ((op&0xf138)==0xb108) OpCmpm(op);
|
||||
if ((op&0xf130)==0xc100) OpExg(op);
|
||||
if ((op&0xf000)==0xe000) OpAsr(op); // Asr/l/Ror/l etc
|
||||
if ((op&0xf8c0)==0xe0c0) OpAsrEa(op);
|
||||
}
|
758
cpu/Cyclone/OpArith.cpp
Normal file
758
cpu/Cyclone/OpArith.cpp
Normal file
|
@ -0,0 +1,758 @@
|
|||
|
||||
#include "app.h"
|
||||
|
||||
// --------------------- Opcodes 0x0000+ ---------------------
|
||||
// Emit an Ori/And/Sub/Add/Eor/Cmp Immediate opcode, 0000ttt0 ssaaaaaa
|
||||
int OpArith(int op)
|
||||
{
|
||||
int type=0,size=0;
|
||||
int sea=0,tea=0;
|
||||
int use=0;
|
||||
|
||||
// Get source and target EA
|
||||
type=(op>>9)&7; if (type==4 || type>=7) return 1;
|
||||
size=(op>>6)&3; if (size>=3) return 1;
|
||||
sea= 0x003c;
|
||||
tea=op&0x003f;
|
||||
|
||||
// See if we can do this opcode:
|
||||
if (EaCanRead(tea,size)==0) return 1;
|
||||
if (EaCanWrite(tea)==0 || EaAn(tea)) return 1;
|
||||
|
||||
use=OpBase(op);
|
||||
if (op!=use) { OpUse(op,use); return 0; } // Use existing handler
|
||||
|
||||
OpStart(op); Cycles=4;
|
||||
|
||||
EaCalc(10,0x0000, sea,size,1);
|
||||
EaRead(10, 10, sea,size,0,1);
|
||||
|
||||
EaCalc(11,0x003f, tea,size,1);
|
||||
EaRead(11, 0, tea,size,0x003f,1);
|
||||
|
||||
ot(";@ Do arithmetic:\n");
|
||||
|
||||
if (type==0) ot(" orr r1,r0,r10\n");
|
||||
if (type==1) ot(" and r1,r0,r10\n");
|
||||
if (type==2) ot(" subs r1,r0,r10 ;@ Defines NZCV\n");
|
||||
if (type==3) ot(" adds r1,r0,r10 ;@ Defines NZCV\n");
|
||||
if (type==5) ot(" eor r1,r0,r10\n");
|
||||
if (type==6) ot(" cmp r0,r10 ;@ Defines NZCV\n");
|
||||
|
||||
if (type<2 || type==5) ot(" adds r1,r1,#0 ;@ Defines NZ, clears CV\n"); // 0,1,5
|
||||
|
||||
if (type< 2) OpGetFlags(0,0); // Ori/And
|
||||
if (type==2) OpGetFlags(1,1); // Sub: Subtract/X-bit
|
||||
if (type==3) OpGetFlags(0,1); // Add: X-bit
|
||||
if (type==5) OpGetFlags(0,0); // Eor
|
||||
if (type==6) OpGetFlags(1,0); // Cmp: Subtract
|
||||
ot("\n");
|
||||
|
||||
if (type!=6)
|
||||
{
|
||||
EaWrite(11, 1, tea,size,0x003f,1);
|
||||
}
|
||||
|
||||
// Correct cycles:
|
||||
if (type==6)
|
||||
{
|
||||
if (size>=2 && tea<0x10) Cycles+=2;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (size>=2) Cycles+=4;
|
||||
if (tea>=8) Cycles+=4;
|
||||
if (type==1 && size>=2 && tea<8) Cycles-=2;
|
||||
}
|
||||
|
||||
OpEnd();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --------------------- Opcodes 0x5000+ ---------------------
|
||||
int OpAddq(int op)
|
||||
{
|
||||
// 0101nnnt xxeeeeee (nnn=#8,1-7 t=addq/subq xx=size, eeeeee=EA)
|
||||
int num=0,type=0,size=0,ea=0;
|
||||
int use=0;
|
||||
char count[16]="";
|
||||
int shift=0;
|
||||
|
||||
num =(op>>9)&7; if (num==0) num=8;
|
||||
type=(op>>8)&1;
|
||||
size=(op>>6)&3; if (size>=3) return 1;
|
||||
ea = op&0x3f;
|
||||
|
||||
// See if we can do this opcode:
|
||||
if (EaCanRead (ea,size)==0) return 1;
|
||||
if (EaCanWrite(ea) ==0) return 1;
|
||||
if (size == 0 && EaAn(ea) ) return 1;
|
||||
|
||||
use=OpBase(op,1);
|
||||
|
||||
if (num!=8) use|=0x0e00; // If num is not 8, use same handler
|
||||
if (op!=use) { OpUse(op,use); return 0; } // Use existing handler
|
||||
|
||||
OpStart(op);
|
||||
Cycles=ea<8?4:8;
|
||||
if(type==0&&size==1) Cycles=ea<0x10?4:8;
|
||||
if(size>=2) Cycles=ea<0x10?8:12;
|
||||
|
||||
if (size>0 && (ea&0x38)==0x08) size=2; // addq.w #n,An is also 32-bit
|
||||
|
||||
EaCalc(10,0x003f, ea,size,1);
|
||||
EaRead(10, 0, ea,size,0x003f,1);
|
||||
|
||||
shift=32-(8<<size);
|
||||
|
||||
if (num!=8)
|
||||
{
|
||||
int lsr=9-shift;
|
||||
|
||||
if (lsr>=0) ot(" mov r2,r8,lsr #%d ;@ Get quick value\n", lsr);
|
||||
else ot(" mov r2,r8,lsl #%d ;@ Get quick value\n",-lsr);
|
||||
|
||||
ot(" and r2,r2,#0x%.4x\n",7<<shift);
|
||||
ot("\n");
|
||||
strcpy(count,"r2");
|
||||
}
|
||||
|
||||
if (num==8) sprintf(count,"#0x%.4x",8<<shift);
|
||||
|
||||
if (type==0) ot(" adds r1,r0,%s\n",count);
|
||||
if (type==1) ot(" subs r1,r0,%s\n",count);
|
||||
|
||||
if ((ea&0x38)!=0x08) OpGetFlags(type,1);
|
||||
ot("\n");
|
||||
|
||||
EaWrite(10, 1, ea,size,0x003f,1);
|
||||
|
||||
OpEnd();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --------------------- Opcodes 0x8000+ ---------------------
|
||||
// 1t0tnnnd xxeeeeee (tt=type:or/sub/and/add xx=size, eeeeee=EA)
|
||||
int OpArithReg(int op)
|
||||
{
|
||||
int use=0;
|
||||
int type=0,size=0,dir=0,rea=0,ea=0;
|
||||
|
||||
type=(op>>12)&5;
|
||||
rea =(op>> 9)&7;
|
||||
dir =(op>> 8)&1; // er,re
|
||||
size=(op>> 6)&3; if (size>=3) return 1;
|
||||
ea = op&0x3f;
|
||||
|
||||
if (dir && ea<0x10) return 1; // addx/subx opcode
|
||||
|
||||
// See if we can do this opcode:
|
||||
if (dir==0 && EaCanRead (ea,size)==0) return 1;
|
||||
if (dir && EaCanWrite(ea)==0) return 1;
|
||||
if ((size==0||!(type&1))&&EaAn(ea)) return 1;
|
||||
|
||||
use=OpBase(op);
|
||||
use&=~0x0e00; // Use same opcode for Dn
|
||||
if (op!=use) { OpUse(op,use); return 0; } // Use existing handler
|
||||
|
||||
OpStart(op); Cycles=4;
|
||||
|
||||
ot(";@ Get r10=EA r11=EA value\n");
|
||||
EaCalc(10,0x003f, ea,size,1);
|
||||
EaRead(10, 11, ea,size,0x003f,1);
|
||||
ot(";@ Get r0=Register r1=Register value\n");
|
||||
EaCalc( 0,0x0e00,rea,size,1);
|
||||
EaRead( 0, 1,rea,size,0x0e00,1);
|
||||
|
||||
ot(";@ Do arithmetic:\n");
|
||||
if (type==0) ot(" orr ");
|
||||
if (type==1) ot(" subs ");
|
||||
if (type==4) ot(" and ");
|
||||
if (type==5) ot(" adds ");
|
||||
if (dir) ot("r1,r11,r1\n");
|
||||
else ot("r1,r1,r11\n");
|
||||
|
||||
if ((type&1)==0) ot(" adds r1,r1,#0 ;@ Defines NZ, clears CV\n");
|
||||
|
||||
OpGetFlags(type==1,type&1); // 1==subtract
|
||||
ot("\n");
|
||||
|
||||
ot(";@ Save result:\n");
|
||||
if (dir) EaWrite(10, 1, ea,size,0x003f,1);
|
||||
else EaWrite( 0, 1,rea,size,0x0e00,1);
|
||||
|
||||
if(rea==ea) {
|
||||
if(ea<8) Cycles=(size>=2)?8:4; else Cycles+=(size>=2)?26:14;
|
||||
} else if(dir) {
|
||||
Cycles+=4;
|
||||
if(size>=2) Cycles+=4;
|
||||
} else {
|
||||
if(size>=2) {
|
||||
Cycles+=2;
|
||||
if(ea<0x10||ea==0x3c) Cycles+=2;
|
||||
}
|
||||
}
|
||||
|
||||
OpEnd();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --------------------- Opcodes 0x80c0+ ---------------------
|
||||
int OpMul(int op)
|
||||
{
|
||||
// Div/Mul: 1m00nnns 11eeeeee (m=Mul, nnn=Register Dn, s=signed, eeeeee=EA)
|
||||
int type=0,rea=0,sign=0,ea=0;
|
||||
int use=0;
|
||||
|
||||
type=(op>>14)&1; // div/mul
|
||||
rea =(op>> 9)&7;
|
||||
sign=(op>> 8)&1;
|
||||
ea = op&0x3f;
|
||||
|
||||
// See if we can do this opcode:
|
||||
if (EaCanRead(ea,1)==0||EaAn(ea)) return 1;
|
||||
|
||||
use=OpBase(op);
|
||||
use&=~0x0e00; // Use same for all registers
|
||||
if (op!=use) { OpUse(op,use); return 0; } // Use existing handler
|
||||
|
||||
OpStart(op);
|
||||
if(type) Cycles=54;
|
||||
else Cycles=sign?158:140;
|
||||
|
||||
EaCalc(10,0x003f, ea, 1);
|
||||
EaRead(10, 10, ea, 1,0x003f);
|
||||
|
||||
EaCalc (0,0x0e00,rea, 2,1);
|
||||
EaRead (0, 2,rea, 2,0x0e00,1);
|
||||
|
||||
if (type==0) // div
|
||||
{
|
||||
// the manual says C is always cleared, but neither Musashi nor FAME do that
|
||||
//ot(" bic r9,r9,#0x20000000 ;@ always clear C\n");
|
||||
ot(" tst r10,r10\n");
|
||||
ot(" beq divzero%.4x ;@ division by zero\n",op);
|
||||
ot("\n");
|
||||
|
||||
if (sign)
|
||||
{
|
||||
ot(" mov r11,#0 ;@ r11 = 1 or 2 if the result is negative\n");
|
||||
ot(" orrmi r11,r11,#1\n");
|
||||
ot(" rsbmi r10,r10,#0 ;@ Make r10 positive\n");
|
||||
ot("\n");
|
||||
ot(" tst r2,r2\n");
|
||||
ot(" orrmi r11,r11,#2\n");
|
||||
ot(" rsbmi r2,r2,#0 ;@ Make r2 positive\n");
|
||||
ot("\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
ot(" mov r10,r10,lsl #16 ;@ use only 16 bits of divisor\n");
|
||||
ot(" mov r10,r10,lsr #16\n");
|
||||
}
|
||||
|
||||
ot(";@ Divide r2 by r10\n");
|
||||
ot(" mov r3,#0\n");
|
||||
ot(" mov r1,r10\n");
|
||||
ot("\n");
|
||||
ot(";@ Shift up divisor till it's just less than numerator\n");
|
||||
ot("Shift%.4x%s\n",op,ms?"":":");
|
||||
ot(" cmp r1,r2,lsr #1\n");
|
||||
ot(" movls r1,r1,lsl #1\n");
|
||||
ot(" bcc Shift%.4x\n",op);
|
||||
ot("\n");
|
||||
|
||||
ot("Divide%.4x%s\n",op,ms?"":":");
|
||||
ot(" cmp r2,r1\n");
|
||||
ot(" adc r3,r3,r3 ;@ Double r3 and add 1 if carry set\n");
|
||||
ot(" subcs r2,r2,r1\n");
|
||||
ot(" teq r1,r10\n");
|
||||
ot(" movne r1,r1,lsr #1\n");
|
||||
ot(" bne Divide%.4x\n",op);
|
||||
ot("\n");
|
||||
ot(";@r3==quotient,r2==remainder\n");
|
||||
|
||||
if (sign)
|
||||
{
|
||||
// sign correction
|
||||
ot(" and r1,r11,#1\n");
|
||||
ot(" teq r1,r11,lsr #1\n");
|
||||
ot(" rsbne r3,r3,#0 ;@ negate if quotient is negative\n");
|
||||
ot(" tst r11,#2\n");
|
||||
ot(" rsbne r2,r2,#0 ;@ negate the remainder if divident was negative\n");
|
||||
ot("\n");
|
||||
|
||||
// signed overflow check
|
||||
ot(" mov r1,r3,asl #16\n");
|
||||
ot(" cmp r3,r1,asr #16 ;@ signed overflow?\n");
|
||||
ot(" orrne r9,r9,#0x10000000 ;@ set overflow flag\n");
|
||||
ot(" bne endofop%.4x ;@ overflow!\n",op);
|
||||
}
|
||||
else
|
||||
{
|
||||
// overflow check
|
||||
ot(" movs r1,r3,lsr #16 ;@ check for overflow condition\n");
|
||||
ot(" orrne r9,r9,#0x10000000 ;@ set overflow flag\n");
|
||||
ot(" bne endofop%.4x ;@ overflow!\n",op);
|
||||
}
|
||||
|
||||
ot(" mov r1,r3,lsl #16 ;@ Clip to 16-bits\n");
|
||||
ot(" adds r1,r1,#0 ;@ Defines NZ, clears CV\n");
|
||||
OpGetFlags(0,0);
|
||||
|
||||
ot(" mov r1,r1,lsr #16\n");
|
||||
ot(" orr r1,r1,r2,lsl #16 ;@ Insert remainder\n");
|
||||
}
|
||||
|
||||
if (type==1)
|
||||
{
|
||||
char *shift="asr";
|
||||
|
||||
ot(";@ Get 16-bit signs right:\n");
|
||||
if (sign==0) { ot(" mov r10,r10,lsl #16\n"); shift="lsr"; }
|
||||
ot(" mov r2,r2,lsl #16\n");
|
||||
|
||||
if (sign==0) ot(" mov r10,r10,lsr #16\n");
|
||||
ot(" mov r2,r2,%s #16\n",shift);
|
||||
ot("\n");
|
||||
|
||||
ot(" mul r1,r2,r10\n");
|
||||
ot(" adds r1,r1,#0 ;@ Defines NZ, clears CV\n");
|
||||
OpGetFlags(0,0);
|
||||
}
|
||||
ot("\n");
|
||||
|
||||
EaWrite(0, 1,rea, 2,0x0e00,1);
|
||||
|
||||
ot("endofop%.4x%s\n",op,ms?"":":");
|
||||
OpEnd();
|
||||
|
||||
ot("divzero%.4x%s\n",op,ms?"":":");
|
||||
ot(" mov r0,#0x14 ;@ Divide by zero\n");
|
||||
ot(" bl Exception\n");
|
||||
Cycles+=38;
|
||||
OpEnd();
|
||||
ot("\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get X Bit into carry - trashes r2
|
||||
int GetXBit(int subtract)
|
||||
{
|
||||
ot(";@ Get X bit:\n");
|
||||
ot(" ldrb r2,[r7,#0x45]\n");
|
||||
if (subtract) ot(" mvn r2,r2,lsl #28 ;@ Invert it\n");
|
||||
else ot(" mov r2,r2,lsl #28\n");
|
||||
ot(" msr cpsr_flg,r2 ;@ Get into Carry\n");
|
||||
ot("\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --------------------- Opcodes 0x8100+ ---------------------
|
||||
// 1t00ddd1 0000asss - sbcd/abcd Ds,Dd or -(As),-(Ad)
|
||||
int OpAbcd(int op)
|
||||
{
|
||||
int use=0;
|
||||
int type=0,sea=0,addr=0,dea=0;
|
||||
|
||||
type=(op>>14)&1; // sbcd/abcd
|
||||
dea =(op>> 9)&7;
|
||||
addr=(op>> 3)&1;
|
||||
sea = op &7;
|
||||
|
||||
if (addr) { sea|=0x20; dea|=0x20; }
|
||||
|
||||
use=op&~0x0e07; // Use same opcode for all registers..
|
||||
if (sea==0x27||dea==0x27) use=op; // ..except -(a7)
|
||||
if (op!=use) { OpUse(op,use); return 0; } // Use existing handler
|
||||
|
||||
OpStart(op); Cycles=6;
|
||||
|
||||
EaCalc( 0,0x0007, sea,0,1);
|
||||
EaRead( 0, 10, sea,0,0x0007,1);
|
||||
EaCalc(11,0x0e00, dea,0,1);
|
||||
EaRead(11, 1, dea,0,0x0e00,1);
|
||||
|
||||
ot(" bic r9,r9,#0xb1000000 ;@ clear all flags except old Z\n");
|
||||
|
||||
if (type)
|
||||
{
|
||||
ot(" ldrb r0,[r7,#0x45] ;@ Get X bit\n");
|
||||
ot(" mov r3,#0x00f00000\n");
|
||||
ot(" and r2,r3,r1,lsr #4\n");
|
||||
ot(" tst r0,#2\n");
|
||||
ot(" and r0,r3,r10,lsr #4\n");
|
||||
ot(" add r0,r0,r2\n");
|
||||
ot(" addne r0,r0,#0x00100000\n");
|
||||
// ot(" tst r0,#0x00800000\n");
|
||||
// ot(" orreq r9,r9,#0x01000000 ;@ Undefined V behavior\n");
|
||||
ot(" cmp r0,#0x00900000\n");
|
||||
ot(" addhi r0,r0,#0x00600000 ;@ Decimal adjust units\n");
|
||||
|
||||
ot(" mov r2,r1,lsr #28\n");
|
||||
ot(" add r0,r0,r2,lsl #24\n");
|
||||
ot(" mov r2,r10,lsr #28\n");
|
||||
ot(" add r0,r0,r2,lsl #24\n");
|
||||
ot(" cmp r0,#0x09900000\n");
|
||||
ot(" orrhi r9,r9,#0x20000000 ;@ C\n");
|
||||
ot(" subhi r0,r0,#0x0a000000\n");
|
||||
// ot(" and r3,r9,r0,lsr #3 ;@ Undefined V behavior part II\n");
|
||||
// ot(" orr r9,r9,r3,lsl #4 ;@ V\n");
|
||||
ot(" movs r0,r0,lsl #4\n");
|
||||
ot(" orrmi r9,r9,#0x90000000 ;@ Undefined N+V behavior\n"); // this is what Musashi really does
|
||||
ot(" bicne r9,r9,#0x40000000 ;@ Z flag\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
ot(" ldrb r0,[r7,#0x45] ;@ Get X bit\n");
|
||||
ot(" mov r3,#0x00f00000\n");
|
||||
ot(" and r2,r3,r10,lsr #4\n");
|
||||
ot(" tst r0,#2\n");
|
||||
ot(" and r0,r3,r1,lsr #4\n");
|
||||
ot(" sub r0,r0,r2\n");
|
||||
ot(" subne r0,r0,#0x00100000\n");
|
||||
// ot(" tst r0,#0x00800000\n");
|
||||
// ot(" orreq r9,r9,#0x01000000 ;@ Undefined V behavior\n");
|
||||
ot(" cmp r0,#0x00900000\n");
|
||||
ot(" subhi r0,r0,#0x00600000 ;@ Decimal adjust units\n");
|
||||
|
||||
ot(" mov r2,r1,lsr #28\n");
|
||||
ot(" add r0,r0,r2,lsl #24\n");
|
||||
ot(" mov r2,r10,lsr #28\n");
|
||||
ot(" sub r0,r0,r2,lsl #24\n");
|
||||
ot(" cmp r0,#0x09900000\n");
|
||||
ot(" orrhi r9,r9,#0xa0000000 ;@ N and C\n");
|
||||
ot(" addhi r0,r0,#0x0a000000\n");
|
||||
// ot(" and r3,r9,r0,lsr #3 ;@ Undefined V behavior part II\n");
|
||||
// ot(" orr r9,r9,r3,lsl #4 ;@ V\n");
|
||||
ot(" movs r0,r0,lsl #4\n");
|
||||
// ot(" orrmi r9,r9,#0x80000000 ;@ Undefined N behavior\n");
|
||||
ot(" bicne r9,r9,#0x40000000 ;@ Z flag\n");
|
||||
}
|
||||
|
||||
ot(" mov r2,r9,lsr #28\n");
|
||||
ot(" strb r2,[r7,#0x45] ;@ Save X bit\n");
|
||||
|
||||
EaWrite(11, 0, dea,0,0x0e00,1);
|
||||
OpEnd();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 01008000 00eeeeee - nbcd <ea>
|
||||
int OpNbcd(int op)
|
||||
{
|
||||
int use=0;
|
||||
int ea=0;
|
||||
|
||||
ea=op&0x3f;
|
||||
|
||||
if(EaCanWrite(ea)==0||EaAn(ea)) return 1;
|
||||
|
||||
use=OpBase(op);
|
||||
if(op!=use) { OpUse(op,use); return 0; } // Use existing handler
|
||||
|
||||
OpStart(op); Cycles=6;
|
||||
if(ea >= 8) Cycles+=2;
|
||||
|
||||
EaCalc(10,0x3f, ea,0,1);
|
||||
EaRead(10, 0, ea,0,0x3f,1);
|
||||
|
||||
// this is rewrite of Musashi's code
|
||||
ot(" ldrb r2,[r7,#0x45]\n");
|
||||
ot(" tst r2,#2\n");
|
||||
ot(" mov r2,r0\n");
|
||||
ot(" addne r2,r0,#0x01000000 ;@ add X\n");
|
||||
ot(" rsbs r1,r2,#0x9a000000 ;@ do arithmetic\n");
|
||||
|
||||
ot(" bic r9,r9,#0xb0000000 ;@ clear all flags, except Z\n");
|
||||
ot(" orrmi r9,r9,#0x80000000 ;@ N\n");
|
||||
ot(" cmp r1,#0x9a000000\n");
|
||||
ot(" beq finish%.4x\n",op);
|
||||
ot("\n");
|
||||
|
||||
ot(" mvn r3,r9,lsr #3 ;@ Undefined V behavior\n",op);
|
||||
ot(" and r2,r1,#0x0f000000\n");
|
||||
ot(" cmp r2,#0x0a000000\n");
|
||||
ot(" andeq r1,r1,#0xf0000000\n");
|
||||
ot(" addeq r1,r1,#0x10000000\n");
|
||||
ot(" and r3,r3,r1,lsr #3 ;@ Undefined V behavior part II\n",op);
|
||||
ot(" tst r1,r1\n");
|
||||
ot(" orr r9,r9,r3 ;@ save V\n",op);
|
||||
ot(" bicne r9,r9,#0x40000000 ;@ Z\n");
|
||||
ot(" orr r9,r9,#0x20000000 ;@ C\n");
|
||||
ot("\n");
|
||||
|
||||
EaWrite(10, 1, ea,0,0x3f,1);
|
||||
|
||||
ot("finish%.4x%s\n",op,ms?"":":");
|
||||
ot(" mov r2,r9,lsr #28\n");
|
||||
ot(" strb r2, [r7,#0x45]\n");
|
||||
|
||||
OpEnd();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --------------------- Opcodes 0x90c0+ ---------------------
|
||||
// Suba/Cmpa/Adda 1tt1nnnx 11eeeeee (tt=type, x=size, eeeeee=Source EA)
|
||||
int OpAritha(int op)
|
||||
{
|
||||
int use=0;
|
||||
int type=0,size=0,sea=0,dea=0;
|
||||
|
||||
// Suba/Cmpa/Adda/(invalid):
|
||||
type=(op>>13)&3; if (type>=3) return 1;
|
||||
|
||||
size=(op>>8)&1; size++;
|
||||
dea=(op>>9)&7; dea|=8; // Dest=An
|
||||
sea=op&0x003f; // Source
|
||||
|
||||
// See if we can do this opcode:
|
||||
if (EaCanRead(sea,size)==0) return 1;
|
||||
|
||||
use=OpBase(op);
|
||||
use&=~0x0e00; // Use same opcode for An
|
||||
if (op!=use) { OpUse(op,use); return 0; } // Use existing handler
|
||||
|
||||
OpStart(op); Cycles=(size==2)?6:8;
|
||||
if(size==2&&(sea<0x10||sea==0x3c)) Cycles+=2;
|
||||
if(type==1) Cycles=6;
|
||||
|
||||
|
||||
EaCalc ( 0,0x003f, sea,size);
|
||||
EaRead ( 0, 10, sea,size,0x003f);
|
||||
|
||||
EaCalc ( 0,0x0e00, dea,2,1);
|
||||
EaRead ( 0, 1, dea,2,0x0e00);
|
||||
|
||||
if (type==0) ot(" sub r1,r1,r10\n");
|
||||
if (type==1) ot(" cmp r1,r10 ;@ Defines NZCV\n");
|
||||
if (type==1) OpGetFlags(1,0); // Get Cmp flags
|
||||
if (type==2) ot(" add r1,r1,r10\n");
|
||||
ot("\n");
|
||||
|
||||
if (type!=1) EaWrite( 0, 1, dea,2,0x0e00,1);
|
||||
|
||||
OpEnd();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --------------------- Opcodes 0x9100+ ---------------------
|
||||
// Emit a Subx/Addx opcode, 1t01ddd1 zz00rsss addx.z Ds,Dd
|
||||
int OpAddx(int op)
|
||||
{
|
||||
int use=0;
|
||||
int type=0,size=0,dea=0,sea=0,mem=0;
|
||||
|
||||
type=(op>>12)&5;
|
||||
dea =(op>> 9)&7;
|
||||
size=(op>> 6)&3; if (size>=3) return 1;
|
||||
sea = op&7;
|
||||
mem =(op>> 3)&1;
|
||||
|
||||
// See if we can do this opcode:
|
||||
if (EaCanRead(sea,size)==0) return 1;
|
||||
if (EaCanWrite(dea)==0) return 1;
|
||||
|
||||
if(mem) { sea+=0x20; dea+=0x20; }
|
||||
|
||||
use=op&~0x0e07; // Use same opcode for Dn
|
||||
if (size==0&&(sea==0x27||dea==0x27)) use=op; // ___x.b -(a7)
|
||||
if (op!=use) { OpUse(op,use); return 0; } // Use existing handler
|
||||
|
||||
OpStart(op); Cycles=4;
|
||||
if(size>=2) Cycles+=4;
|
||||
if(sea>=0x10) Cycles+=2;
|
||||
|
||||
ot(";@ Get r10=EA r11=EA value\n");
|
||||
EaCalc( 0,0x0007,sea,size,1);
|
||||
EaRead( 0, 11,sea,size,0x0007,1);
|
||||
ot(";@ Get r0=Register r1=Register value\n");
|
||||
EaCalc( 0,0x0e00,dea,size,1);
|
||||
EaRead( 0, 1,dea,size,0x0e00,1);
|
||||
|
||||
ot(";@ Do arithmetic:\n");
|
||||
GetXBit(type==1);
|
||||
|
||||
if (type==5 && size<2)
|
||||
{
|
||||
ot(";@ Make sure the carry bit will tip the balance:\n");
|
||||
ot(" mvn r2,#0\n");
|
||||
ot(" orr r11,r11,r2,lsr #%i\n",(size==0)?8:16);
|
||||
ot("\n");
|
||||
}
|
||||
|
||||
if (type==1) ot(" sbcs r1,r1,r11\n");
|
||||
if (type==5) ot(" adcs r1,r1,r11\n");
|
||||
ot(" orr r3,r9,#0xb0000000 ;@ for old Z\n");
|
||||
OpGetFlags(type==1,1,0); // subtract
|
||||
if (size<2) {
|
||||
ot(" movs r2,r1,lsr #%i\n", size?16:24);
|
||||
ot(" orreq r9,r9,#0x40000000 ;@ add potentially missed Z\n");
|
||||
}
|
||||
ot(" andeq r9,r9,r3 ;@ fix Z\n");
|
||||
ot("\n");
|
||||
|
||||
ot(";@ Save result:\n");
|
||||
EaWrite( 0, 1, dea,size,0x0e00,1);
|
||||
|
||||
OpEnd();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --------------------- Opcodes 0xb000+ ---------------------
|
||||
// Emit a Cmp/Eor opcode, 1011rrrt xxeeeeee (rrr=Dn, t=cmp/eor, xx=size extension, eeeeee=ea)
|
||||
int OpCmpEor(int op)
|
||||
{
|
||||
int rea=0,eor=0;
|
||||
int size=0,ea=0,use=0;
|
||||
|
||||
// Get EA and register EA
|
||||
rea=(op>>9)&7;
|
||||
eor=(op>>8)&1;
|
||||
size=(op>>6)&3; if (size>=3) return 1;
|
||||
ea=op&0x3f;
|
||||
|
||||
if (eor && (ea>>3) == 1) return 1; // not a valid mode for eor
|
||||
|
||||
// See if we can do this opcode:
|
||||
if (EaCanRead(ea,size)==0) return 1;
|
||||
if (eor && EaCanWrite(ea)==0) return 1;
|
||||
if (EaAn(ea)&&(eor||size==0)) return 1;
|
||||
|
||||
use=OpBase(op);
|
||||
use&=~0x0e00; // Use 1 handler for register d0-7
|
||||
if (op!=use) { OpUse(op,use); return 0; } // Use existing handler
|
||||
|
||||
OpStart(op); Cycles=4;
|
||||
if(eor) {
|
||||
if(ea>8) Cycles+=4;
|
||||
if(size>=2) Cycles+=4;
|
||||
} else {
|
||||
if(size>=2) Cycles+=2;
|
||||
}
|
||||
|
||||
ot(";@ Get EA into r10 and value into r0:\n");
|
||||
EaCalc (10,0x003f, ea,size,1);
|
||||
EaRead (10, 0, ea,size,0x003f,1);
|
||||
|
||||
ot(";@ Get register operand into r1:\n");
|
||||
EaCalc (1, 0x0e00, rea,size,1);
|
||||
EaRead (1, 1, rea,size,0x0e00,1);
|
||||
|
||||
ot(";@ Do arithmetic:\n");
|
||||
if (eor==0) ot(" cmp r1,r0\n");
|
||||
if (eor)
|
||||
{
|
||||
ot(" eor r1,r0,r1\n");
|
||||
ot(" adds r1,r1,#0 ;@ Defines NZ, clears CV\n");
|
||||
}
|
||||
|
||||
OpGetFlags(eor==0,0); // Cmp like subtract
|
||||
ot("\n");
|
||||
|
||||
if (eor) EaWrite(10, 1,ea,size,0x003f,1);
|
||||
|
||||
OpEnd();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Emit a Cmpm opcode, 1011ddd1 xx001sss (rrr=Adst, xx=size extension, sss=Asrc)
|
||||
int OpCmpm(int op)
|
||||
{
|
||||
int size=0,sea=0,dea=0,use=0;
|
||||
|
||||
// get size, get EAs
|
||||
size=(op>>6)&3; if (size>=3) return 1;
|
||||
sea=(op&7)|0x18;
|
||||
dea=(op>>9)&0x3f;
|
||||
|
||||
use=op&~0x0e07; // Use 1 handler for all registers..
|
||||
if (size==0&&(sea==0x1f||dea==0x1f)) use=op; // ..except (a7)+
|
||||
if (op!=use) { OpUse(op,use); return 0; } // Use existing handler
|
||||
|
||||
OpStart(op); Cycles=4;
|
||||
|
||||
ot(";@ Get src operand into r10:\n");
|
||||
EaCalc (0,0x000f, sea,size,1);
|
||||
EaRead (0, 10, sea,size,0x000f,1);
|
||||
|
||||
ot(";@ Get dst operand into r0:\n");
|
||||
EaCalc (0,0x1e00, dea,size,1);
|
||||
EaRead (0, 0, dea,size,0x1e00,1);
|
||||
|
||||
ot(" cmp r0,r10\n");
|
||||
OpGetFlags(1,0); // Cmp like subtract
|
||||
|
||||
OpEnd();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// Emit a Chk opcode, 0100ddd1 x0eeeeee (rrr=Dn, x=size extension, eeeeee=ea)
|
||||
int OpChk(int op)
|
||||
{
|
||||
int rea=0;
|
||||
int size=0,ea=0,use=0;
|
||||
|
||||
// Get EA and register EA
|
||||
rea=(op>>9)&7;
|
||||
if((op>>7)&1)
|
||||
size=1; // word operation
|
||||
else size=2; // long
|
||||
ea=op&0x3f;
|
||||
|
||||
if (EaAn(ea)) return 1; // not a valid mode
|
||||
if (size!=1) return 1; // 000 variant only supports word
|
||||
|
||||
// See if we can do this opcode:
|
||||
if (EaCanRead(ea,size)==0) return 1;
|
||||
|
||||
use=OpBase(op);
|
||||
use&=~0x0e00; // Use 1 handler for register d0-7
|
||||
if (op!=use) { OpUse(op,use); return 0; } // Use existing handler
|
||||
|
||||
OpStart(op); Cycles=10;
|
||||
|
||||
ot(";@ Get EA into r10 and value into r0:\n");
|
||||
EaCalc (10,0x003f, ea,size,1);
|
||||
EaRead (10, 0, ea,size,0x003f,1);
|
||||
|
||||
ot(";@ Get register operand into r1:\n");
|
||||
EaCalc (1, 0x0e00, rea,size,1);
|
||||
EaRead (1, 1, rea,size,0x0e00,1);
|
||||
|
||||
ot(";@ get flags, including undocumented ones\n");
|
||||
ot(" and r3,r9,#0x80000000\n");
|
||||
ot(" adds r1,r1,#0 ;@ Defines NZ, clears CV\n");
|
||||
OpGetFlags(0,0);
|
||||
|
||||
ot(";@ is reg negative?\n");
|
||||
ot(" bmi chktrap%.4x\n",op);
|
||||
|
||||
ot(";@ Do arithmetic:\n");
|
||||
ot(" cmp r1,r0\n");
|
||||
ot(" bicgt r9,r9,#0x80000000 ;@ N\n");
|
||||
ot(" bgt chktrap%.4x\n",op);
|
||||
|
||||
ot(";@ old N remains\n");
|
||||
ot(" bic r9,r9,#0x80000000 ;@ N\n");
|
||||
ot(" orr r9,r9,r3\n");
|
||||
OpEnd();
|
||||
|
||||
ot("chktrap%.4x%s ;@ CHK exception:\n",op,ms?"":":");
|
||||
ot(" mov r0,#0x18\n");
|
||||
ot(" bl Exception\n");
|
||||
Cycles+=40;
|
||||
OpEnd();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
434
cpu/Cyclone/OpBranch.cpp
Normal file
434
cpu/Cyclone/OpBranch.cpp
Normal file
|
@ -0,0 +1,434 @@
|
|||
|
||||
#include "app.h"
|
||||
|
||||
#if USE_CHECKPC_CALLBACK
|
||||
static void CheckPc()
|
||||
{
|
||||
ot(";@ Check Memory Base+pc (r4)\n");
|
||||
ot(" add lr,pc,#4\n");
|
||||
ot(" mov r0,r4\n");
|
||||
ot(" ldr pc,[r7,#0x64] ;@ Call checkpc()\n");
|
||||
ot(" mov r4,r0\n");
|
||||
ot("\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
// Push 32-bit value in r1 - trashes r0-r3,r12,lr
|
||||
void OpPush32()
|
||||
{
|
||||
ot(";@ Push r1 onto stack\n");
|
||||
ot(" ldr r0,[r7,#0x3c]\n");
|
||||
ot(" sub r0,r0,#4 ;@ Predecrement A7\n");
|
||||
ot(" str r0,[r7,#0x3c] ;@ Save A7\n");
|
||||
MemHandler(1,2);
|
||||
ot("\n");
|
||||
}
|
||||
|
||||
// Push SR - trashes r0-r3,r12,lr
|
||||
void OpPushSr(int high)
|
||||
{
|
||||
ot(";@ Push SR:\n");
|
||||
OpFlagsToReg(high);
|
||||
ot(" ldr r0,[r7,#0x3c]\n");
|
||||
ot(" sub r0,r0,#2 ;@ Predecrement A7\n");
|
||||
ot(" str r0,[r7,#0x3c] ;@ Save A7\n");
|
||||
MemHandler(1,1);
|
||||
ot("\n");
|
||||
}
|
||||
|
||||
// Pop SR - trashes r0-r3
|
||||
static void PopSr(int high)
|
||||
{
|
||||
ot(";@ Pop SR:\n");
|
||||
ot(" ldr r0,[r7,#0x3c]\n");
|
||||
ot(" add r1,r0,#2 ;@ Postincrement A7\n");
|
||||
ot(" str r1,[r7,#0x3c] ;@ Save A7\n");
|
||||
MemHandler(0,1);
|
||||
ot("\n");
|
||||
OpRegToFlags(high);
|
||||
}
|
||||
|
||||
// Pop PC - assumes r10=Memory Base - trashes r0-r3
|
||||
static void PopPc()
|
||||
{
|
||||
ot(";@ Pop PC:\n");
|
||||
ot(" ldr r0,[r7,#0x3c]\n");
|
||||
ot(" add r1,r0,#4 ;@ Postincrement A7\n");
|
||||
ot(" str r1,[r7,#0x3c] ;@ Save A7\n");
|
||||
MemHandler(0,2);
|
||||
ot(" add r4,r0,r10 ;@ r4=Memory Base+PC\n");
|
||||
ot("\n");
|
||||
CheckPc();
|
||||
}
|
||||
|
||||
int OpTrap(int op)
|
||||
{
|
||||
int use=0;
|
||||
|
||||
use=op&~0xf;
|
||||
if (op!=use) { OpUse(op,use); return 0; } // Use existing handler
|
||||
|
||||
OpStart(op);
|
||||
ot(" and r0,r8,#0xf ;@ Get trap number\n");
|
||||
ot(" orr r0,r0,#0x20\n");
|
||||
ot(" mov r0,r0,asl #2\n");
|
||||
ot(" bl Exception\n");
|
||||
ot("\n");
|
||||
|
||||
Cycles=38; OpEnd();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --------------------- Opcodes 0x4e50+ ---------------------
|
||||
int OpLink(int op)
|
||||
{
|
||||
int use=0,reg;
|
||||
|
||||
use=op&~7;
|
||||
reg=op&7;
|
||||
if (reg==7) use=op;
|
||||
if (op!=use) { OpUse(op,use); return 0; } // Use existing handler
|
||||
|
||||
OpStart(op);
|
||||
|
||||
if(reg!=7) {
|
||||
ot(";@ Get An\n");
|
||||
EaCalc(10, 7, 8, 2, 1);
|
||||
EaRead(10, 1, 8, 2, 7, 1);
|
||||
}
|
||||
|
||||
ot(" ldr r0,[r7,#0x3c] ;@ Get A7\n");
|
||||
ot(" sub r0,r0,#4 ;@ A7-=4\n");
|
||||
ot(" mov r11,r0\n");
|
||||
if(reg==7) ot(" mov r1,r0\n");
|
||||
ot("\n");
|
||||
|
||||
ot(";@ Write An to Stack\n");
|
||||
MemHandler(1,2);
|
||||
|
||||
ot(";@ Save to An\n");
|
||||
if(reg!=7)
|
||||
EaWrite(10,11, 8, 2, 7, 1);
|
||||
|
||||
ot(";@ Get offset:\n");
|
||||
EaCalc(0,0,0x3c,1);
|
||||
EaRead(0,0,0x3c,1,0);
|
||||
|
||||
ot(" add r11,r11,r0 ;@ Add offset to A7\n");
|
||||
ot(" str r11,[r7,#0x3c]\n");
|
||||
ot("\n");
|
||||
|
||||
Cycles=16;
|
||||
OpEnd();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --------------------- Opcodes 0x4e58+ ---------------------
|
||||
int OpUnlk(int op)
|
||||
{
|
||||
int use=0;
|
||||
|
||||
use=op&~7;
|
||||
if (op!=use) { OpUse(op,use); return 0; } // Use existing handler
|
||||
|
||||
OpStart(op);
|
||||
|
||||
ot(";@ Get An\n");
|
||||
EaCalc(10, 7, 8, 2, 1);
|
||||
EaRead(10, 0, 8, 2, 7, 1);
|
||||
|
||||
ot(" add r11,r0,#4 ;@ A7+=4\n");
|
||||
ot("\n");
|
||||
ot(";@ Pop An from stack:\n");
|
||||
MemHandler(0,2);
|
||||
ot("\n");
|
||||
ot(" str r11,[r7,#0x3c] ;@ Save A7\n");
|
||||
ot("\n");
|
||||
ot(";@ An = value from stack:\n");
|
||||
EaWrite(10, 0, 8, 2, 7, 1);
|
||||
|
||||
Cycles=12;
|
||||
OpEnd();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --------------------- Opcodes 0x4e70+ ---------------------
|
||||
int Op4E70(int op)
|
||||
{
|
||||
int type=0;
|
||||
|
||||
type=op&7; // 01001110 01110ttt, reset/nop/stop/rte/rtd/rts/trapv/rtr
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case 1: // nop
|
||||
OpStart(op);
|
||||
Cycles=4;
|
||||
OpEnd();
|
||||
return 0;
|
||||
|
||||
case 3: // rte
|
||||
OpStart(op); Cycles=20;
|
||||
SuperCheck(op);
|
||||
PopSr(1);
|
||||
ot(" ldr r10,[r7,#0x60] ;@ Get Memory base\n");
|
||||
PopPc();
|
||||
SuperChange(op);
|
||||
CheckInterrupt(op);
|
||||
OpEnd();
|
||||
SuperEnd(op);
|
||||
return 0;
|
||||
|
||||
case 5: // rts
|
||||
OpStart(op); Cycles=16;
|
||||
ot(" ldr r10,[r7,#0x60] ;@ Get Memory base\n");
|
||||
PopPc();
|
||||
OpEnd();
|
||||
return 0;
|
||||
|
||||
case 6: // trapv
|
||||
OpStart(op); Cycles=4;
|
||||
ot(" tst r9,#0x10000000\n");
|
||||
ot(" subne r5,r5,#%i\n",30);
|
||||
ot(" movne r0,#0x1c ;@ TRAPV exception\n");
|
||||
ot(" blne Exception\n");
|
||||
OpEnd();
|
||||
return 0;
|
||||
|
||||
case 7: // rtr
|
||||
OpStart(op); Cycles=20;
|
||||
PopSr(0);
|
||||
ot(" ldr r10,[r7,#0x60] ;@ Get Memory base\n");
|
||||
PopPc();
|
||||
OpEnd();
|
||||
return 0;
|
||||
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------- Opcodes 0x4e80+ ---------------------
|
||||
// Emit a Jsr/Jmp opcode, 01001110 1meeeeee
|
||||
int OpJsr(int op)
|
||||
{
|
||||
int use=0;
|
||||
int sea=0;
|
||||
|
||||
sea=op&0x003f;
|
||||
|
||||
// See if we can do this opcode:
|
||||
if (EaCanRead(sea,-1)==0) return 1;
|
||||
|
||||
use=OpBase(op);
|
||||
if (op!=use) { OpUse(op,use); return 0; } // Use existing handler
|
||||
|
||||
OpStart(op);
|
||||
|
||||
ot(" ldr r10,[r7,#0x60] ;@ Get Memory base\n");
|
||||
ot("\n");
|
||||
EaCalc(0,0x003f,sea,0);
|
||||
|
||||
ot(";@ Jump - Get new PC from r0\n");
|
||||
if (op&0x40)
|
||||
{
|
||||
// Jmp - Get new PC from r0
|
||||
ot(" add r4,r0,r10 ;@ r4 = Memory Base + New PC\n");
|
||||
ot("\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
ot(";@ Jsr - Push old PC first\n");
|
||||
ot(" sub r1,r4,r10 ;@ r1 = Old PC\n");
|
||||
ot(" add r4,r0,r10 ;@ r4 = Memory Base + New PC\n");
|
||||
ot(" mov r1,r1,lsl #8\n");
|
||||
ot(" ldr r0,[r7,#0x3c]\n");
|
||||
ot(" mov r1,r1,asr #8\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("\n");
|
||||
}
|
||||
|
||||
#if USE_CHECKPC_CALLBACK
|
||||
CheckPc();
|
||||
#endif
|
||||
|
||||
Cycles=(op&0x40) ? 4 : 12;
|
||||
Cycles+=Ea_add_ns((op&0x40) ? g_jmp_cycle_table : g_jsr_cycle_table, sea);
|
||||
|
||||
OpEnd();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --------------------- Opcodes 0x50c8+ ---------------------
|
||||
|
||||
// ARM version of 68000 condition codes:
|
||||
static char *Cond[16]=
|
||||
{
|
||||
"", "", "hi","ls","cc","cs","ne","eq",
|
||||
"vc","vs","pl","mi","ge","lt","gt","le"
|
||||
};
|
||||
|
||||
// Emit a Dbra opcode, 0101cccc 11001nnn vv
|
||||
int OpDbra(int op)
|
||||
{
|
||||
int use=0;
|
||||
int cc=0;
|
||||
|
||||
use=op&~7; // Use same handler
|
||||
cc=(op>>8)&15;
|
||||
|
||||
if (op!=use) { OpUse(op,use); return 0; } // Use existing handler
|
||||
OpStart(op);
|
||||
|
||||
if (cc>=2)
|
||||
{
|
||||
ot(";@ Is the condition true?\n");
|
||||
if ((cc&~1)==2) ot(" eor r9,r9,#0x20000000 ;@ Invert carry for hi/ls\n");
|
||||
ot(" msr cpsr_flg,r9 ;@ ARM flags = 68000 flags\n");
|
||||
if ((cc&~1)==2) ot(" eor r9,r9,#0x20000000\n");
|
||||
ot(";@ If so, don't dbra\n");
|
||||
ot(" b%s DbraTrue%.4x\n",Cond[cc],op);
|
||||
ot("\n");
|
||||
}
|
||||
|
||||
ot(";@ Decrement Dn.w\n");
|
||||
ot(" and r1,r8,#0x0007\n");
|
||||
ot(" mov r1,r1,lsl #2\n");
|
||||
ot(" ldrsh r0,[r7,r1]\n");
|
||||
ot(" sub r0,r0,#1\n");
|
||||
ot(" strh r0,[r7,r1]\n");
|
||||
ot("\n");
|
||||
|
||||
ot(";@ Check if Dn.w is -1\n");
|
||||
ot(" cmps r0,#-1\n");
|
||||
ot(" beq DbraMin1%.4x\n",op);
|
||||
ot("\n");
|
||||
|
||||
ot(";@ Get Branch offset:\n");
|
||||
ot(" ldrsh r0,[r4]\n");
|
||||
ot(" add r4,r4,r0 ;@ r4 = New PC\n");
|
||||
ot("\n");
|
||||
Cycles=12-2;
|
||||
OpEnd();
|
||||
|
||||
ot(";@ Dn.w is -1:\n");
|
||||
ot("DbraMin1%.4x%s\n", op, ms?"":":");
|
||||
ot(" add r4,r4,#2 ;@ Skip branch offset\n");
|
||||
ot("\n");
|
||||
Cycles=12+2;
|
||||
OpEnd();
|
||||
|
||||
ot(";@ condition true:\n");
|
||||
ot("DbraTrue%.4x%s\n", op, ms?"":":");
|
||||
ot(" add r4,r4,#2 ;@ Skip branch offset\n");
|
||||
ot("\n");
|
||||
Cycles=12;
|
||||
OpEnd();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --------------------- Opcodes 0x6000+ ---------------------
|
||||
// Emit a Branch opcode 0110cccc nn (cccc=condition)
|
||||
int OpBranch(int op)
|
||||
{
|
||||
int size=0,use=0;
|
||||
int offset=0;
|
||||
int cc=0;
|
||||
|
||||
offset=(char)(op&0xff);
|
||||
cc=(op>>8)&15;
|
||||
|
||||
// Special offsets:
|
||||
if (offset==0) size=1;
|
||||
if (offset==-1) size=2;
|
||||
|
||||
if (size) use=op; // 16-bit or 32-bit
|
||||
else use=(op&0xff00)+1; // Use same opcode for all 8-bit branches
|
||||
|
||||
if (op!=use) { OpUse(op,use); return 0; } // Use existing handler
|
||||
OpStart(op);
|
||||
|
||||
ot(";@ Get Branch offset:\n");
|
||||
if (size)
|
||||
{
|
||||
EaCalc(0,0,0x3c,size);
|
||||
EaRead(0,0,0x3c,size,0);
|
||||
}
|
||||
|
||||
// above code messes cycles
|
||||
Cycles=10; // Assume branch taken
|
||||
|
||||
if (size==0) ot(" mov r0,r8,asl #24 ;@ Shift 8-bit signed offset up...\n\n");
|
||||
|
||||
if (cc==1) ot(" ldr r10,[r7,#0x60] ;@ Get Memory base\n");
|
||||
|
||||
if (cc>=2)
|
||||
{
|
||||
ot(";@ Is the condition true?\n");
|
||||
if ((cc&~1)==2) ot(" eor r9,r9,#0x20000000 ;@ Invert carry for hi/ls\n");
|
||||
ot(" msr cpsr_flg,r9 ;@ ARM flags = 68000 flags\n");
|
||||
if ((cc&~1)==2) ot(" eor r9,r9,#0x20000000\n");
|
||||
|
||||
if (size==0) ot(" mov r0,r0,asr #24 ;@ ...shift down\n\n");
|
||||
|
||||
ot(" b%s DontBranch%.4x\n",Cond[cc^1],op);
|
||||
|
||||
ot("\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (size==0) ot(" mov r0,r0,asr #24 ;@ ...shift down\n\n");
|
||||
}
|
||||
|
||||
ot(";@ Branch taken - Add on r0 to PC\n");
|
||||
|
||||
if (cc==1)
|
||||
{
|
||||
ot(";@ Bsr - remember old PC\n");
|
||||
ot(" sub r1,r4,r10 ;@ r1 = Old PC\n");
|
||||
ot(" mov r1,r1, lsl #8\n");
|
||||
ot(" mov r1,r1, asr #8\n");
|
||||
ot("\n");
|
||||
if (size) ot(" sub r4,r4,#%d ;@ (Branch is relative to Opcode+2)\n",1<<size);
|
||||
ot(" ldr r2,[r7,#0x3c]\n");
|
||||
ot(" add r4,r4,r0 ;@ r4 = New PC\n");
|
||||
ot(";@ Push r1 onto stack\n");
|
||||
ot(" sub r0,r2,#4 ;@ Predecrement A7\n");
|
||||
ot(" str r0,[r7,#0x3c] ;@ Save A7\n");
|
||||
MemHandler(1,2);
|
||||
ot("\n");
|
||||
Cycles=18; // always 18
|
||||
}
|
||||
else
|
||||
{
|
||||
if (size) ot(" sub r4,r4,#%d ;@ (Branch is relative to Opcode+2)\n",1<<size);
|
||||
ot(" add r4,r4,r0 ;@ r4 = New PC\n");
|
||||
ot("\n");
|
||||
}
|
||||
|
||||
#if USE_CHECKPC_CALLBACK
|
||||
if (offset==0 || offset==-1)
|
||||
{
|
||||
ot(";@ Branch is quite far, so may be a good idea to check Memory Base+pc\n");
|
||||
CheckPc();
|
||||
}
|
||||
#endif
|
||||
|
||||
OpEnd();
|
||||
|
||||
if (cc>=2)
|
||||
{
|
||||
ot("DontBranch%.4x%s\n", op, ms?"":":");
|
||||
Cycles+=(size==1)? 2 : -2; // Branch not taken
|
||||
OpEnd();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
672
cpu/Cyclone/OpLogic.cpp
Normal file
672
cpu/Cyclone/OpLogic.cpp
Normal file
|
@ -0,0 +1,672 @@
|
|||
#include "app.h"
|
||||
|
||||
// --------------------- Opcodes 0x0100+ ---------------------
|
||||
// Emit a Btst (Register) opcode 0000nnn1 ttaaaaaa
|
||||
int OpBtstReg(int op)
|
||||
{
|
||||
int use=0;
|
||||
int type=0,sea=0,tea=0;
|
||||
int size=0;
|
||||
|
||||
type=(op>>6)&3; // Btst/Bchg/Bclr/Bset
|
||||
// Get source and target EA
|
||||
sea=(op>>9)&7;
|
||||
tea=op&0x003f;
|
||||
if (tea<0x10) size=2; // For registers, 32-bits
|
||||
|
||||
if ((tea&0x38)==0x08) return 1; // movep
|
||||
|
||||
// See if we can do this opcode:
|
||||
if (EaCanRead(tea,0)==0) return 1;
|
||||
if (type>0)
|
||||
{
|
||||
if (EaCanWrite(tea)==0) return 1;
|
||||
}
|
||||
|
||||
use=OpBase(op);
|
||||
use&=~0x0e00; // Use same handler for all registers
|
||||
if (op!=use) { OpUse(op,use); return 0; } // Use existing handler
|
||||
|
||||
OpStart(op);
|
||||
|
||||
if(type==1||type==3) {
|
||||
Cycles=8;
|
||||
} else {
|
||||
Cycles=type?8:4;
|
||||
if(size>=2) Cycles+=2;
|
||||
}
|
||||
|
||||
EaCalc (0,0x0e00,sea,0);
|
||||
EaRead (0, 0,sea,0,0x0e00);
|
||||
if (tea>=0x10)
|
||||
ot(" and r10,r0,#7 ;@ mem - do mod 8\n");
|
||||
else ot(" and r10,r0,#31 ;@ reg - do mod 32\n");
|
||||
ot("\n");
|
||||
|
||||
EaCalc(11,0x003f,tea,size);
|
||||
EaRead(11, 0,tea,size,0x003f);
|
||||
ot(" mov r1,#1\n");
|
||||
ot(" tst r0,r1,lsl r10 ;@ Do arithmetic\n");
|
||||
ot(" bicne r9,r9,#0x40000000\n");
|
||||
ot(" orreq r9,r9,#0x40000000 ;@ Get Z flag\n");
|
||||
ot("\n");
|
||||
|
||||
if (type>0)
|
||||
{
|
||||
if (type==1) ot(" eor r1,r0,r1,lsl r10 ;@ Toggle bit\n");
|
||||
if (type==2) ot(" bic r1,r0,r1,lsl r10 ;@ Clear bit\n");
|
||||
if (type==3) ot(" orr r1,r0,r1,lsl r10 ;@ Set bit\n");
|
||||
ot("\n");
|
||||
EaWrite(11, 1,tea,size,0x003f);
|
||||
}
|
||||
OpEnd();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --------------------- Opcodes 0x0800+ ---------------------
|
||||
// Emit a Btst/Bchg/Bclr/Bset (Immediate) opcode 00001000 ttaaaaaa nn
|
||||
int OpBtstImm(int op)
|
||||
{
|
||||
int type=0,sea=0,tea=0;
|
||||
int use=0;
|
||||
int size=0;
|
||||
|
||||
type=(op>>6)&3;
|
||||
// Get source and target EA
|
||||
sea= 0x003c;
|
||||
tea=op&0x003f;
|
||||
if (tea<0x10) size=2; // For registers, 32-bits
|
||||
|
||||
// See if we can do this opcode:
|
||||
if (EaCanRead(tea,0)==0||EaAn(tea)||tea==0x3c) return 1;
|
||||
if (type>0)
|
||||
{
|
||||
if (EaCanWrite(tea)==0) return 1;
|
||||
}
|
||||
|
||||
use=OpBase(op);
|
||||
if (op!=use) { OpUse(op,use); return 0; } // Use existing handler
|
||||
|
||||
OpStart(op);
|
||||
|
||||
ot(" mov r10,#1\n");
|
||||
ot("\n");
|
||||
EaCalc ( 0,0x0000,sea,0);
|
||||
EaRead ( 0, 0,sea,0,0);
|
||||
ot(" bic r9,r9,#0x40000000 ;@ Blank Z flag\n");
|
||||
if (tea>=0x10)
|
||||
ot(" and r0,r0,#7 ;@ mem - do mod 8\n");
|
||||
else ot(" and r0,r0,#0x1F ;@ reg - do mod 32\n");
|
||||
ot(" mov r10,r10,lsl r0 ;@ Make bit mask\n");
|
||||
ot("\n");
|
||||
|
||||
if(type==1||type==3) {
|
||||
Cycles=12;
|
||||
} else {
|
||||
Cycles=type?12:8;
|
||||
if(size>=2) Cycles+=2;
|
||||
}
|
||||
|
||||
EaCalc (11,0x003f,tea,size);
|
||||
EaRead (11, 0,tea,size,0x003f);
|
||||
ot(" tst r0,r10 ;@ Do arithmetic\n");
|
||||
ot(" orreq r9,r9,#0x40000000 ;@ Get Z flag\n");
|
||||
ot("\n");
|
||||
|
||||
if (type>0)
|
||||
{
|
||||
if (type==1) ot(" eor r1,r0,r10 ;@ Toggle bit\n");
|
||||
if (type==2) ot(" bic r1,r0,r10 ;@ Clear bit\n");
|
||||
if (type==3) ot(" orr r1,r0,r10 ;@ Set bit\n");
|
||||
ot("\n");
|
||||
EaWrite(11, 1,tea,size,0x003f);
|
||||
}
|
||||
|
||||
OpEnd();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --------------------- Opcodes 0x4000+ ---------------------
|
||||
int OpNeg(int op)
|
||||
{
|
||||
// 01000tt0 xxeeeeee (tt=negx/clr/neg/not, xx=size, eeeeee=EA)
|
||||
int type=0,size=0,ea=0,use=0;
|
||||
|
||||
type=(op>>9)&3;
|
||||
ea =op&0x003f;
|
||||
size=(op>>6)&3; if (size>=3) return 1;
|
||||
|
||||
// See if we can do this opcode:
|
||||
if (EaCanRead (ea,size)==0||EaAn(ea)) return 1;
|
||||
if (EaCanWrite(ea )==0) return 1;
|
||||
|
||||
use=OpBase(op);
|
||||
if (op!=use) { OpUse(op,use); return 0; } // Use existing handler
|
||||
|
||||
OpStart(op); Cycles=size<2?4:6;
|
||||
if(ea >= 0x10) {
|
||||
Cycles*=2;
|
||||
#ifdef CYCLONE_FOR_GENESIS
|
||||
// This is same as in Starscream core, CLR uses only 6 cycles for memory EAs.
|
||||
// May be this is similar case as with TAS opcode, but this time the dummy
|
||||
// read is ignored somehow? Without this hack Fatal Rewind hangs even in Gens.
|
||||
if(type==1&&size<2) Cycles-=2;
|
||||
#endif
|
||||
}
|
||||
|
||||
EaCalc (10,0x003f,ea,size);
|
||||
|
||||
if (type!=1) EaRead (10,0,ea,size,0x003f); // Don't need to read for 'clr'
|
||||
if (type==1) ot("\n");
|
||||
|
||||
if (type==0)
|
||||
{
|
||||
ot(";@ Negx:\n");
|
||||
GetXBit(1);
|
||||
if(size!=2) ot(" mov r0,r0,lsl #%i\n",size?16:24);
|
||||
ot(" rscs r1,r0,#0 ;@ do arithmetic\n");
|
||||
ot(" orr r3,r9,#0xb0000000 ;@ for old Z\n");
|
||||
OpGetFlags(1,1,0);
|
||||
if(size!=2) {
|
||||
ot(" movs r1,r1,asr #%i\n",size?16:24);
|
||||
ot(" orreq r9,r9,#0x40000000 ;@ possily missed Z\n");
|
||||
}
|
||||
ot(" andeq r9,r9,r3 ;@ fix Z\n");
|
||||
ot("\n");
|
||||
}
|
||||
|
||||
if (type==1)
|
||||
{
|
||||
ot(";@ Clear:\n");
|
||||
ot(" mov r1,#0\n");
|
||||
ot(" mov r9,#0x40000000 ;@ NZCV=0100\n");
|
||||
ot("\n");
|
||||
}
|
||||
|
||||
if (type==2)
|
||||
{
|
||||
ot(";@ Neg:\n");
|
||||
if(size!=2) ot(" mov r0,r0,lsl #%i\n",size?16:24);
|
||||
ot(" rsbs r1,r0,#0\n");
|
||||
OpGetFlags(1,1);
|
||||
if(size!=2) ot(" mov r1,r1,asr #%i\n",size?16:24);
|
||||
ot("\n");
|
||||
}
|
||||
|
||||
if (type==3)
|
||||
{
|
||||
ot(";@ Not:\n");
|
||||
ot(" mvn r1,r0\n");
|
||||
ot(" adds r1,r1,#0 ;@ Defines NZ, clears CV\n");
|
||||
OpGetFlags(0,0);
|
||||
ot("\n");
|
||||
}
|
||||
|
||||
EaWrite(10, 1,ea,size,0x003f);
|
||||
|
||||
OpEnd();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --------------------- Opcodes 0x4840+ ---------------------
|
||||
// Swap, 01001000 01000nnn swap Dn
|
||||
int OpSwap(int op)
|
||||
{
|
||||
int ea=0,use=0;
|
||||
|
||||
ea=op&7;
|
||||
use=op&~0x0007; // Use same opcode for all An
|
||||
|
||||
if (op!=use) { OpUse(op,use); return 0; } // Use existing handler
|
||||
|
||||
OpStart(op); Cycles=4;
|
||||
|
||||
EaCalc (10,0x0007,ea,2,1);
|
||||
EaRead (10, 0,ea,2,0x0007,1);
|
||||
|
||||
ot(" mov r1,r0,ror #16\n");
|
||||
ot(" adds r1,r1,#0 ;@ Defines NZ, clears CV\n");
|
||||
OpGetFlags(0,0);
|
||||
|
||||
EaWrite(10, 1,8,2,0x0007,1);
|
||||
|
||||
OpEnd();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --------------------- Opcodes 0x4a00+ ---------------------
|
||||
// Emit a Tst opcode, 01001010 xxeeeeee
|
||||
int OpTst(int op)
|
||||
{
|
||||
int sea=0;
|
||||
int size=0,use=0;
|
||||
|
||||
sea=op&0x003f;
|
||||
size=(op>>6)&3; if (size>=3) return 1;
|
||||
|
||||
// See if we can do this opcode:
|
||||
if (EaCanWrite(sea)==0||EaAn(sea)) return 1;
|
||||
|
||||
use=OpBase(op);
|
||||
if (op!=use) { OpUse(op,use); return 0; } // Use existing handler
|
||||
|
||||
OpStart(op); Cycles=4;
|
||||
|
||||
EaCalc ( 0,0x003f,sea,size,1);
|
||||
EaRead ( 0, 0,sea,size,0x003f,1);
|
||||
|
||||
ot(" adds r0,r0,#0 ;@ Defines NZ, clears CV\n");
|
||||
ot(" mrs r9,cpsr ;@ r9=flags\n");
|
||||
ot("\n");
|
||||
|
||||
OpEnd();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --------------------- Opcodes 0x4880+ ---------------------
|
||||
// Emit an Ext opcode, 01001000 1x000nnn
|
||||
int OpExt(int op)
|
||||
{
|
||||
int ea=0;
|
||||
int size=0,use=0;
|
||||
int shift=0;
|
||||
|
||||
ea=op&0x0007;
|
||||
size=(op>>6)&1;
|
||||
shift=32-(8<<size);
|
||||
|
||||
use=OpBase(op);
|
||||
if (op!=use) { OpUse(op,use); return 0; } // Use existing handler
|
||||
|
||||
OpStart(op); Cycles=4;
|
||||
|
||||
EaCalc (10,0x0007,ea,size+1);
|
||||
EaRead (10, 0,ea,size+1,0x0007);
|
||||
|
||||
ot(" mov r0,r0,asl #%d\n",shift);
|
||||
ot(" adds r0,r0,#0 ;@ Defines NZ, clears CV\n");
|
||||
ot(" mrs r9,cpsr ;@ r9=flags\n");
|
||||
ot(" mov r1,r0,asr #%d\n",shift);
|
||||
ot("\n");
|
||||
|
||||
EaWrite(10, 1,ea,size+1,0x0007);
|
||||
|
||||
OpEnd();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --------------------- Opcodes 0x50c0+ ---------------------
|
||||
// Emit a Set cc opcode, 0101cccc 11eeeeee
|
||||
int OpSet(int op)
|
||||
{
|
||||
int cc=0,ea=0;
|
||||
int size=0,use=0;
|
||||
char *cond[16]=
|
||||
{
|
||||
"al","", "hi","ls","cc","cs","ne","eq",
|
||||
"vc","vs","pl","mi","ge","lt","gt","le"
|
||||
};
|
||||
|
||||
cc=(op>>8)&15;
|
||||
ea=op&0x003f;
|
||||
|
||||
if ((ea&0x38)==0x08) return 1; // dbra, not scc
|
||||
|
||||
// See if we can do this opcode:
|
||||
if (EaCanWrite(ea)==0) return 1;
|
||||
|
||||
use=OpBase(op);
|
||||
if (op!=use) { OpUse(op,use); return 0; } // Use existing handler
|
||||
|
||||
OpStart(op); Cycles=8;
|
||||
if (ea<8) Cycles=4;
|
||||
|
||||
ot(" mov r1,#0\n");
|
||||
|
||||
if (cc!=1)
|
||||
{
|
||||
ot(";@ Is the condition true?\n");
|
||||
if ((cc&~1)==2) ot(" eor r9,r9,#0x20000000 ;@ Invert carry for hi/ls\n");
|
||||
ot(" msr cpsr_flg,r9 ;@ ARM flags = 68000 flags\n");
|
||||
if ((cc&~1)==2) ot(" eor r9,r9,#0x20000000 ;@ Invert carry for hi/ls\n");
|
||||
ot(" mvn%s r1,r1\n",cond[cc]);
|
||||
}
|
||||
|
||||
if (cc!=1 && ea<8) ot(" sub%s r5,r5,#2 ;@ Extra cycles\n",cond[cc]);
|
||||
ot("\n");
|
||||
|
||||
EaCalc (0,0x003f, ea,size);
|
||||
EaWrite(0, 1, ea,size,0x003f);
|
||||
|
||||
OpEnd();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Emit a Asr/Lsr/Roxr/Ror opcode
|
||||
static int EmitAsr(int op,int type,int dir,int count,int size,int usereg)
|
||||
{
|
||||
char pct[8]=""; // count
|
||||
int shift=32-(8<<size);
|
||||
|
||||
if (count>=1) sprintf(pct,"#%d",count); // Fixed count
|
||||
|
||||
if (usereg)
|
||||
{
|
||||
ot(";@ Use Dn for count:\n");
|
||||
ot(" and r2,r8,#7<<9\n");
|
||||
ot(" ldr r2,[r7,r2,lsr #7]\n");
|
||||
ot(" and r2,r2,#63\n");
|
||||
ot("\n");
|
||||
strcpy(pct,"r2");
|
||||
}
|
||||
else if (count<0)
|
||||
{
|
||||
ot(" mov r2,r8,lsr #9 ;@ Get 'n'\n");
|
||||
ot(" and r2,r2,#7\n\n"); strcpy(pct,"r2");
|
||||
}
|
||||
|
||||
// Take 2*n cycles:
|
||||
if (count<0) ot(" sub r5,r5,r2,asl #1 ;@ Take 2*n cycles\n\n");
|
||||
else Cycles+=count<<1;
|
||||
|
||||
if (type<2)
|
||||
{
|
||||
// Asr/Lsr
|
||||
if (dir==0 && size<2)
|
||||
{
|
||||
ot(";@ For shift right, use loworder bits for the operation:\n");
|
||||
ot(" mov r0,r0,%s #%d\n",type?"lsr":"asr",32-(8<<size));
|
||||
ot("\n");
|
||||
}
|
||||
|
||||
if (type==0 && dir) ot(" mov r3,r0 ;@ save old value for V flag calculation\n");
|
||||
|
||||
ot(";@ Shift register:\n");
|
||||
if (type==0) ot(" movs r0,r0,%s %s\n",dir?"asl":"asr",pct);
|
||||
if (type==1) ot(" movs r0,r0,%s %s\n",dir?"lsl":"lsr",pct);
|
||||
|
||||
if (dir==0 && size<2)
|
||||
{
|
||||
ot(";@ restore after right shift:\n");
|
||||
ot(" mov r0,r0,lsl #%d\n",32-(8<<size));
|
||||
ot("\n");
|
||||
}
|
||||
|
||||
OpGetFlags(0,0);
|
||||
if (usereg) { // store X only if count is not 0
|
||||
ot(" cmp %s,#0 ;@ shifting by 0?\n",pct);
|
||||
ot(" biceq r9,r9,#0x20000000 ;@ if so, clear carry\n");
|
||||
ot(" movne r1,r9,lsr #28\n");
|
||||
ot(" strneb r1,[r7,#0x45] ;@ else Save X bit\n");
|
||||
} else {
|
||||
// count will never be 0 if we use immediate
|
||||
ot(" mov r1,r9,lsr #28\n");
|
||||
ot(" strb r1,[r7,#0x45] ;@ Save X bit\n");
|
||||
}
|
||||
|
||||
if (type==0 && dir) {
|
||||
ot(";@ calculate V flag (set if sign bit changes at anytime):\n");
|
||||
ot(" mov r1,#0x80000000\n");
|
||||
ot(" ands r3,r3,r1,asr %s\n", pct);
|
||||
ot(" cmpne r3,r1,asr %s\n", pct);
|
||||
ot(" biceq r9,r9,#0x10000000\n");
|
||||
ot(" orrne r9,r9,#0x10000000\n");
|
||||
}
|
||||
|
||||
ot("\n");
|
||||
}
|
||||
|
||||
// --------------------------------------
|
||||
if (type==2)
|
||||
{
|
||||
int wide=8<<size;
|
||||
|
||||
// Roxr
|
||||
if(count == 1) {
|
||||
if(dir==0) {
|
||||
if(size!=2) {
|
||||
ot(" orr r0,r0,r0,lsr #%i\n", size?16:24);
|
||||
ot(" bic r0,r0,#0x%x\n", 1<<(32-wide));
|
||||
}
|
||||
GetXBit(0);
|
||||
ot(" movs r0,r0,rrx\n");
|
||||
OpGetFlags(0,1);
|
||||
} else {
|
||||
ot(" ldrb r3,[r7,#0x45]\n");
|
||||
ot(" movs r0,r0,lsl #1\n");
|
||||
OpGetFlags(0,1);
|
||||
ot(" tst r3,#2\n");
|
||||
ot(" orrne r0,r0,#0x%x\n", 1<<(32-wide));
|
||||
ot(" bicne r9,r9,#0x40000000 ;@ clear Z in case it got there\n");
|
||||
}
|
||||
ot(" bic r9,r9,#0x10000000 ;@ make suve V is clear\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (usereg)
|
||||
{
|
||||
ot(";@ Reduce r2 until <0:\n");
|
||||
ot("Reduce_%.4x%s\n",op,ms?"":":");
|
||||
ot(" subs r2,r2,#%d\n",wide+1);
|
||||
ot(" bpl Reduce_%.4x\n",op);
|
||||
ot(" adds r2,r2,#%d ;@ Now r2=0-%d\n",wide+1,wide);
|
||||
ot(" beq norotx%.4x\n",op);
|
||||
ot("\n");
|
||||
}
|
||||
|
||||
if (usereg||count < 0)
|
||||
{
|
||||
if (dir) ot(" rsb r2,r2,#%d ;@ Reverse direction\n",wide+1);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dir) ot(" mov r2,#%d ;@ Reversed\n",wide+1-count);
|
||||
else ot(" mov r2,#%d\n",count);
|
||||
}
|
||||
|
||||
if (shift) ot(" mov r0,r0,lsr #%d ;@ Shift down\n",shift);
|
||||
|
||||
ot(";@ Rotate bits:\n");
|
||||
ot(" mov r3,r0,lsr r2 ;@ Get right part\n");
|
||||
ot(" rsbs r2,r2,#%d ;@ should also clear ARM V\n",wide+1);
|
||||
ot(" movs r0,r0,lsl r2 ;@ Get left part\n");
|
||||
ot(" orr r0,r3,r0 ;@ r0=Rotated value\n");
|
||||
|
||||
ot(";@ Insert X bit into r2-1:\n");
|
||||
ot(" ldrb r3,[r7,#0x45]\n");
|
||||
ot(" sub r2,r2,#1\n");
|
||||
ot(" and r3,r3,#2\n");
|
||||
ot(" mov r3,r3,lsr #1\n");
|
||||
ot(" orr r0,r0,r3,lsl r2\n");
|
||||
ot("\n");
|
||||
|
||||
if (shift) ot(" movs r0,r0,lsl #%d ;@ Shift up and get correct NC flags\n",shift);
|
||||
OpGetFlags(0,!usereg);
|
||||
if (!shift) {
|
||||
ot(" tst r0,r0\n");
|
||||
ot(" bicne r9,r9,#0x40000000 ;@ make sure we didn't mess Z\n");
|
||||
}
|
||||
if (usereg) { // store X only if count is not 0
|
||||
ot(" mov r2,r9,lsr #28\n");
|
||||
ot(" strb r2,[r7,#0x45] ;@ if not 0, Save X bit\n");
|
||||
ot(" b nozerox%.4x\n",op);
|
||||
ot("norotx%.4x%s\n",op,ms?"":":");
|
||||
ot(" ldrb r2,[r7,#0x45]\n");
|
||||
ot(" adds r0,r0,#0 ;@ Defines NZ, clears CV\n");
|
||||
OpGetFlags(0,0);
|
||||
ot(" and r2,r2,#2\n");
|
||||
ot(" orr r9,r9,r2,lsl #28 ;@ C = old_X\n");
|
||||
ot("nozerox%.4x%s\n",op,ms?"":":");
|
||||
}
|
||||
|
||||
ot("\n");
|
||||
}
|
||||
|
||||
// --------------------------------------
|
||||
if (type==3)
|
||||
{
|
||||
// Ror
|
||||
if (size<2)
|
||||
{
|
||||
ot(";@ Mirror value in whole 32 bits:\n");
|
||||
if (size<=0) ot(" orr r0,r0,r0,lsr #8\n");
|
||||
if (size<=1) ot(" orr r0,r0,r0,lsr #16\n");
|
||||
ot("\n");
|
||||
}
|
||||
|
||||
ot(";@ Rotate register:\n");
|
||||
if (count<0)
|
||||
{
|
||||
if (dir) ot(" rsbs %s,%s,#32\n",pct,pct);
|
||||
ot(" movs r0,r0,ror %s\n",pct);
|
||||
}
|
||||
else
|
||||
{
|
||||
int ror=count;
|
||||
if (dir) ror=32-ror;
|
||||
if (ror&31) ot(" movs r0,r0,ror #%d\n",ror);
|
||||
}
|
||||
|
||||
OpGetFlags(0,0);
|
||||
if (!dir) ot(" bic r9,r9,#0x10000000 ;@ make suve V is clear\n");
|
||||
if (dir)
|
||||
{
|
||||
ot(";@ Get carry bit from bit 0:\n");
|
||||
if (usereg)
|
||||
{
|
||||
ot(" cmp %s,#32 ;@ rotating by 0?\n",pct);
|
||||
ot(" tstne r0,#1 ;@ no, check bit 0\n");
|
||||
}
|
||||
else
|
||||
ot(" tst r0,#1\n");
|
||||
ot(" orrne r9,r9,#0x20000000\n");
|
||||
ot(" biceq r9,r9,#0x20000000\n");
|
||||
}
|
||||
else if (usereg)
|
||||
{
|
||||
// if we rotate something by 0, ARM doesn't clear C
|
||||
// so we need to detect that
|
||||
ot(" cmp %s,#0\n",pct);
|
||||
ot(" biceq r9,r9,#0x20000000\n");
|
||||
}
|
||||
ot("\n");
|
||||
|
||||
}
|
||||
// --------------------------------------
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Emit a Asr/Lsr/Roxr/Ror opcode - 1110cccd xxuttnnn
|
||||
// (ccc=count, d=direction(r,l) xx=size extension, u=use reg for count, tt=type, nnn=register Dn)
|
||||
int OpAsr(int op)
|
||||
{
|
||||
int ea=0,use=0;
|
||||
int count=0,dir=0;
|
||||
int size=0,usereg=0,type=0;
|
||||
|
||||
ea=0;
|
||||
count =(op>>9)&7;
|
||||
dir =(op>>8)&1;
|
||||
size =(op>>6)&3;
|
||||
if (size>=3) return 1; // use OpAsrEa()
|
||||
usereg=(op>>5)&1;
|
||||
type =(op>>3)&3;
|
||||
|
||||
if (usereg==0) count=((count-1)&7)+1; // because ccc=000 means 8
|
||||
|
||||
// Use the same opcode for target registers:
|
||||
use=op&~0x0007;
|
||||
|
||||
// As long as count is not 8, use the same opcode for all shift counts::
|
||||
if (usereg==0 && count!=8 && !(count==1&&type==2)) { use|=0x0e00; count=-1; }
|
||||
if (usereg) { use&=~0x0e00; count=-1; } // Use same opcode for all Dn
|
||||
|
||||
if (op!=use) { OpUse(op,use); return 0; } // Use existing handler
|
||||
|
||||
OpStart(op); Cycles=size<2?6:8;
|
||||
|
||||
EaCalc(10,0x0007, ea,size,1);
|
||||
EaRead(10, 0, ea,size,0x0007,1);
|
||||
|
||||
EmitAsr(op,type,dir,count, size,usereg);
|
||||
|
||||
EaWrite(10, 0, ea,size,0x0007,1);
|
||||
|
||||
OpEnd();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Asr/Lsr/Roxr/Ror etc EA - 11100ttd 11eeeeee
|
||||
int OpAsrEa(int op)
|
||||
{
|
||||
int use=0,type=0,dir=0,ea=0,size=1;
|
||||
|
||||
type=(op>>9)&3;
|
||||
dir =(op>>8)&1;
|
||||
ea = op&0x3f;
|
||||
|
||||
if (ea<0x10) return 1;
|
||||
// See if we can do this opcode:
|
||||
if (EaCanRead(ea,0)==0) return 1;
|
||||
if (EaCanWrite(ea)==0) return 1;
|
||||
|
||||
use=OpBase(op);
|
||||
if (op!=use) { OpUse(op,use); return 0; } // Use existing handler
|
||||
|
||||
OpStart(op); Cycles=6; // EmitAsr() will add 2
|
||||
|
||||
EaCalc (10,0x003f,ea,size,1);
|
||||
EaRead (10, 0,ea,size,0x003f,1);
|
||||
|
||||
EmitAsr(op,type,dir,1,size,0);
|
||||
|
||||
EaWrite(10, 0,ea,size,0x003f,1);
|
||||
|
||||
OpEnd();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int OpTas(int op)
|
||||
{
|
||||
int ea=0;
|
||||
int use=0;
|
||||
|
||||
ea=op&0x003f;
|
||||
|
||||
// See if we can do this opcode:
|
||||
if (EaCanWrite(ea)==0 || EaAn(ea)) return 1;
|
||||
|
||||
use=OpBase(op);
|
||||
if (op!=use) { OpUse(op,use); return 0; } // Use existing handler
|
||||
|
||||
OpStart(op); Cycles=4;
|
||||
if(ea>=8) Cycles+=10;
|
||||
|
||||
EaCalc (10,0x003f,ea,0,1);
|
||||
EaRead (10, 1,ea,0,0x003f,1);
|
||||
|
||||
ot(" adds r1,r1,#0 ;@ Defines NZ, clears CV\n");
|
||||
OpGetFlags(0,0);
|
||||
ot("\n");
|
||||
|
||||
#if CYCLONE_FOR_GENESIS
|
||||
// the original Sega hardware ignores write-back phase (to memory only)
|
||||
if (ea < 0x10) {
|
||||
#endif
|
||||
ot(" orr r1,r1,#0x80000000 ;@ set bit7\n");
|
||||
|
||||
EaWrite(10, 1,ea,0,0x003f,1);
|
||||
#if CYCLONE_FOR_GENESIS
|
||||
}
|
||||
#endif
|
||||
|
||||
OpEnd();
|
||||
return 0;
|
||||
}
|
||||
|
616
cpu/Cyclone/OpMove.cpp
Normal file
616
cpu/Cyclone/OpMove.cpp
Normal file
|
@ -0,0 +1,616 @@
|
|||
|
||||
#include "app.h"
|
||||
|
||||
|
||||
// Pack our flags into r1, in SR/CCR register format
|
||||
// trashes r0,r2
|
||||
void OpFlagsToReg(int high)
|
||||
{
|
||||
ot(" ldrb r0,[r7,#0x45] ;@ 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("\n");
|
||||
if (high) ot(" ldrb r2,[r7,#0x44] ;@ Include SR high\n");
|
||||
ot(" and r0,r0,#0x02\n");
|
||||
ot(" orr r1,r1,r0,lsl #3 ;@ ___XNZVC\n");
|
||||
if (high) ot(" orr r1,r1,r2,lsl #8\n");
|
||||
ot("\n");
|
||||
}
|
||||
|
||||
// Convert SR/CRR register in r0 to our flags
|
||||
// trashes r0,r1
|
||||
void OpRegToFlags(int high)
|
||||
{
|
||||
ot(" eor r1,r0,r0,ror #1 ;@ Bit 0=C^V\n");
|
||||
ot(" mov r2,r0,lsr #3 ;@ r2=___XN\n");
|
||||
ot(" tst r1,#1 ;@ 1 if C!=V\n");
|
||||
ot(" eorne r0,r0,#3 ;@ ___XNZCV\n");
|
||||
ot(" strb r2,[r7,#0x45] ;@ Store X bit\n");
|
||||
ot(" mov r9,r0,lsl #28 ;@ r9=NZCV...\n");
|
||||
|
||||
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");
|
||||
}
|
||||
ot("\n");
|
||||
}
|
||||
|
||||
// checks for supervisor bit, if not set, jumps to SuperEnd()
|
||||
// also sets r11 to SR high value, SuperChange() uses this
|
||||
void SuperCheck(int op)
|
||||
{
|
||||
ot(" ldr r11,[r7,#0x44] ;@ Get SR high\n");
|
||||
ot(" tst r11,#0x20 ;@ Check we are in supervisor mode\n");
|
||||
ot(" beq WrongMode%.4x ;@ No\n",op);
|
||||
ot("\n");
|
||||
}
|
||||
|
||||
void SuperEnd(int op)
|
||||
{
|
||||
ot("WrongMode%.4x%s\n",op,ms?"":":");
|
||||
ot(" sub r4,r4,#2 ;@ this opcode wasn't executed - go back\n");
|
||||
ot(" mov r0,#0x20 ;@ privilege violation\n");
|
||||
ot(" bl Exception\n");
|
||||
Cycles=34;
|
||||
OpEnd();
|
||||
}
|
||||
|
||||
// does OSP and A7 swapping if needed
|
||||
// new or old SR (not the one already in [r7,#0x44]) should be passed in r11
|
||||
// trashes r1,r11
|
||||
void SuperChange(int op)
|
||||
{
|
||||
ot(";@ A7 <-> OSP?\n");
|
||||
ot(" ldr r1,[r7,#0x44] ;@ Get other SR high\n");
|
||||
ot(" and r11,r11,#0x20\n");
|
||||
ot(" and r1,r1,#0x20\n");
|
||||
ot(" teq r11,r1 ;@ r11 xor r1\n");
|
||||
ot(" beq no_sp_swap%.4x\n",op);
|
||||
ot(" ;@ swap OSP and A7:\n");
|
||||
ot(" ldr r11,[r7,#0x3C] ;@ Get A7\n");
|
||||
ot(" ldr r1, [r7,#0x48] ;@ Get OSP\n");
|
||||
ot(" str r11,[r7,#0x48]\n");
|
||||
ot(" str r1, [r7,#0x3C]\n");
|
||||
ot("no_sp_swap%.4x%s\n", op, ms?"":":");
|
||||
}
|
||||
|
||||
|
||||
|
||||
// --------------------- Opcodes 0x1000+ ---------------------
|
||||
// Emit a Move opcode, 00xxdddd ddssssss
|
||||
int OpMove(int op)
|
||||
{
|
||||
int sea=0,tea=0;
|
||||
int size=0,use=0;
|
||||
int movea=0;
|
||||
|
||||
// Get source and target EA
|
||||
sea = op&0x003f;
|
||||
tea =(op&0x01c0)>>3;
|
||||
tea|=(op&0x0e00)>>9;
|
||||
|
||||
if (tea>=8 && tea<0x10) movea=1;
|
||||
|
||||
// Find size extension
|
||||
switch (op&0x3000)
|
||||
{
|
||||
default: return 1;
|
||||
case 0x1000: size=0; break;
|
||||
case 0x3000: size=1; break;
|
||||
case 0x2000: size=2; break;
|
||||
}
|
||||
|
||||
if (size<1 && (movea || EaAn(sea))) return 1; // move.b An,* and movea.b * are invalid
|
||||
|
||||
// See if we can do this opcode:
|
||||
if (EaCanRead (sea,size)==0) return 1;
|
||||
if (EaCanWrite(tea )==0) return 1;
|
||||
|
||||
use=OpBase(op);
|
||||
if (tea<0x38) use&=~0x0e00; // Use same handler for register ?0-7
|
||||
|
||||
if (tea>=0x18 && tea<0x28 && (tea&7)==7) use|=0x0e00; // Specific handler for (a7)+ and -(a7)
|
||||
|
||||
if (op!=use) { OpUse(op,use); return 0; } // Use existing handler
|
||||
|
||||
OpStart(op); Cycles=4;
|
||||
|
||||
EaCalc(0,0x003f,sea,size);
|
||||
EaRead(0, 1,sea,size,0x003f);
|
||||
|
||||
if (movea==0) {
|
||||
ot(" adds r1,r1,#0 ;@ Defines NZ, clears CV\n");
|
||||
ot(" mrs r9,cpsr ;@ r9=NZCV flags\n");
|
||||
ot("\n");
|
||||
}
|
||||
|
||||
if (movea) size=2; // movea always expands to 32-bits
|
||||
|
||||
EaCalc (0,0x0e00,tea,size);
|
||||
#if SPLIT_MOVEL_PD
|
||||
if ((tea&0x38)==0x20 && size==2) { // -(An)
|
||||
ot(" mov r10,r0\n");
|
||||
ot(" mov r11,r1\n");
|
||||
ot(" add r0,r0,#2\n");
|
||||
EaWrite(0, 1,tea,1,0x0e00);
|
||||
EaWrite(10, 11,tea,1,0x0e00,1);
|
||||
} else {
|
||||
EaWrite(0, 1,tea,size,0x0e00);
|
||||
}
|
||||
#else
|
||||
EaWrite(0, 1,tea,size,0x0e00);
|
||||
#endif
|
||||
|
||||
#if CYCLONE_FOR_GENESIS && !MEMHANDLERS_CHANGE_CYCLES
|
||||
// this is a bit hacky
|
||||
if ((tea==0x39||(tea&0x38)==0x10)&&size>=1)
|
||||
ot(" ldr r5,[r7,#0x5c] ;@ Load Cycles\n");
|
||||
#endif
|
||||
|
||||
if((tea&0x38)==0x20) Cycles-=2; // less cycles when dest is -(An)
|
||||
|
||||
OpEnd();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --------------------- Opcodes 0x41c0+ ---------------------
|
||||
// Emit an Lea opcode, 0100nnn1 11aaaaaa
|
||||
int OpLea(int op)
|
||||
{
|
||||
int use=0;
|
||||
int sea=0,tea=0;
|
||||
|
||||
sea= op&0x003f;
|
||||
tea=(op&0x0e00)>>9; tea|=8;
|
||||
|
||||
if (EaCanRead(sea,-1)==0) return 1; // See if we can do this opcode
|
||||
|
||||
use=OpBase(op);
|
||||
use&=~0x0e00; // Also use 1 handler for target ?0-7
|
||||
if (op!=use) { OpUse(op,use); return 0; } // Use existing handler
|
||||
|
||||
OpStart(op);
|
||||
|
||||
EaCalc (1,0x003f,sea,0); // Lea
|
||||
EaCalc (0,0x0e00,tea,2,1);
|
||||
EaWrite(0, 1,tea,2,0x0e00,1);
|
||||
|
||||
Cycles=Ea_add_ns(g_lea_cycle_table,sea);
|
||||
|
||||
OpEnd();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --------------------- Opcodes 0x40c0+ ---------------------
|
||||
// Move SR opcode, 01000tt0 11aaaaaa move SR
|
||||
int OpMoveSr(int op)
|
||||
{
|
||||
int type=0,ea=0;
|
||||
int use=0,size=1;
|
||||
|
||||
type=(op>>9)&3; // from SR, from CCR, to CCR, to SR
|
||||
ea=op&0x3f;
|
||||
|
||||
if(EaAn(ea)) return 1; // can't use An regs
|
||||
|
||||
switch(type)
|
||||
{
|
||||
case 0:
|
||||
if (EaCanWrite(ea)==0) return 1; // See if we can do this opcode:
|
||||
break;
|
||||
|
||||
case 1:
|
||||
return 1; // no such op in 68000
|
||||
|
||||
case 2: case 3:
|
||||
if (EaCanRead(ea,size)==0) return 1; // See if we can do this opcode:
|
||||
break;
|
||||
}
|
||||
|
||||
use=OpBase(op);
|
||||
if (op!=use) { OpUse(op,use); return 0; } // Use existing handler
|
||||
|
||||
OpStart(op);
|
||||
Cycles=12;
|
||||
if (type==0) Cycles=(ea>=8)?8:6;
|
||||
|
||||
if (type==3) SuperCheck(op); // 68000 model allows reading whole SR in user mode (but newer models don't)
|
||||
|
||||
if (type==0 || type==1)
|
||||
{
|
||||
OpFlagsToReg(type==0);
|
||||
EaCalc (0,0x003f,ea,size);
|
||||
EaWrite(0, 1,ea,size,0x003f);
|
||||
}
|
||||
|
||||
if (type==2 || type==3)
|
||||
{
|
||||
EaCalc(0,0x003f,ea,size);
|
||||
EaRead(0, 0,ea,size,0x003f);
|
||||
OpRegToFlags(type==3);
|
||||
if (type==3) {
|
||||
SuperChange(op);
|
||||
CheckInterrupt(op);
|
||||
}
|
||||
}
|
||||
|
||||
OpEnd();
|
||||
|
||||
if (type==3) SuperEnd(op);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// Ori/Andi/Eori $nnnn,sr 0000t0t0 01111100
|
||||
int OpArithSr(int op)
|
||||
{
|
||||
int type=0,ea=0;
|
||||
int use=0,size=0;
|
||||
|
||||
type=(op>>9)&5; if (type==4) return 1;
|
||||
size=(op>>6)&1; // ccr or sr?
|
||||
ea=0x3c;
|
||||
|
||||
use=OpBase(op);
|
||||
if (op!=use) { OpUse(op,use); return 0; } // Use existing handler
|
||||
|
||||
OpStart(op); Cycles=16;
|
||||
|
||||
if (size) SuperCheck(op);
|
||||
|
||||
EaCalc(0,0x003f,ea,size);
|
||||
EaRead(0, 10,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) {
|
||||
SuperChange(op);
|
||||
CheckInterrupt(op);
|
||||
}
|
||||
|
||||
OpEnd();
|
||||
if (size) SuperEnd(op);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --------------------- Opcodes 0x4850+ ---------------------
|
||||
// Emit an Pea opcode, 01001000 01aaaaaa
|
||||
int OpPea(int op)
|
||||
{
|
||||
int use=0;
|
||||
int ea=0;
|
||||
|
||||
ea=op&0x003f; if (ea<0x10) return 1; // Swap opcode
|
||||
if (EaCanRead(ea,-1)==0) return 1; // See if we can do this opcode:
|
||||
|
||||
use=OpBase(op);
|
||||
if (op!=use) { OpUse(op,use); return 0; } // Use existing handler
|
||||
|
||||
OpStart(op);
|
||||
|
||||
ot(" ldr r10,[r7,#0x3c]\n");
|
||||
EaCalc (1,0x003f, ea,0);
|
||||
ot("\n");
|
||||
ot(" sub r0,r10,#4 ;@ Predecrement A7\n");
|
||||
ot(" str r0,[r7,#0x3c] ;@ Save A7\n");
|
||||
ot("\n");
|
||||
MemHandler(1,2); // Write 32-bit
|
||||
ot("\n");
|
||||
|
||||
Cycles=6+Ea_add_ns(g_pea_cycle_table,ea);
|
||||
|
||||
OpEnd();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --------------------- Opcodes 0x4880+ ---------------------
|
||||
// Emit a Movem opcode, 01001d00 1xeeeeee regmask
|
||||
int OpMovem(int op)
|
||||
{
|
||||
int size=0,ea=0,cea=0,dir=0;
|
||||
int use=0,decr=0,change=0;
|
||||
|
||||
size=((op>>6)&1)+1; // word, long
|
||||
ea=op&0x003f;
|
||||
dir=(op>>10)&1; // Direction (1==ea2reg)
|
||||
|
||||
if (dir) {
|
||||
if (ea<0x10 || ea>0x3b || (ea&0x38)==0x20) return 1; // Invalid EA
|
||||
} else {
|
||||
if (ea<0x10 || ea>0x39 || (ea&0x38)==0x18) return 1;
|
||||
}
|
||||
|
||||
if ((ea&0x38)==0x18 || (ea&0x38)==0x20) change=1;
|
||||
if ((ea&0x38)==0x20) decr=1; // -(An), bitfield is decr
|
||||
|
||||
cea=ea; if (change) cea=0x10;
|
||||
|
||||
use=OpBase(op);
|
||||
if (op!=use) { OpUse(op,use); return 0; } // Use existing handler
|
||||
|
||||
OpStart(op);
|
||||
|
||||
ot(" stmdb sp!,{r9} ;@ Push r9\n"); // can't just use r12 or lr here, because memhandlers touch them
|
||||
ot(" ldrh r11,[r4],#2 ;@ r11=register mask\n");
|
||||
|
||||
ot("\n");
|
||||
ot(";@ Get the address into r9:\n");
|
||||
EaCalc(9,0x003f,cea,size);
|
||||
|
||||
ot(";@ r10=Register Index*4:\n");
|
||||
if (decr) ot(" mov r10,#0x3c ;@ order reversed for -(An)\n");
|
||||
else ot(" mov r10,#0\n");
|
||||
|
||||
ot("\n");
|
||||
ot("MoreReg%.4x%s\n",op, ms?"":":");
|
||||
|
||||
ot(" tst r11,#1\n");
|
||||
ot(" beq SkipReg%.4x\n",op);
|
||||
ot("\n");
|
||||
|
||||
if (decr) ot(" sub r9,r9,#%d ;@ Pre-decrement address\n",1<<size);
|
||||
|
||||
if (dir)
|
||||
{
|
||||
ot(" ;@ Copy memory to register:\n",1<<size);
|
||||
EaRead (9,0,ea,size,0x003f);
|
||||
ot(" str r0,[r7,r10] ;@ Save value into Dn/An\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
ot(" ;@ Copy register to memory:\n",1<<size);
|
||||
ot(" ldr r1,[r7,r10] ;@ Load value from Dn/An\n");
|
||||
EaWrite(9,1,ea,size,0x003f);
|
||||
}
|
||||
|
||||
if (decr==0) ot(" add r9,r9,#%d ;@ Post-increment address\n",1<<size);
|
||||
|
||||
ot(" sub r5,r5,#%d ;@ Take some cycles\n",2<<size);
|
||||
ot("\n");
|
||||
ot("SkipReg%.4x%s\n",op, ms?"":":");
|
||||
ot(" movs r11,r11,lsr #1;@ Shift mask:\n");
|
||||
ot(" add r10,r10,#%d ;@ r10=Next Register\n",decr?-4:4);
|
||||
ot(" bne MoreReg%.4x\n",op);
|
||||
ot("\n");
|
||||
|
||||
if (change)
|
||||
{
|
||||
ot(";@ Write back address:\n");
|
||||
EaCalc (0,0x0007,8|(ea&7),2);
|
||||
EaWrite(0, 9,8|(ea&7),2,0x0007);
|
||||
}
|
||||
|
||||
ot(" ldmia sp!,{r9} ;@ Pop r9\n");
|
||||
ot("\n");
|
||||
|
||||
if(dir) { // er
|
||||
if (ea==0x3a) Cycles=16; // ($nn,PC)
|
||||
else if (ea==0x3b) Cycles=18; // ($nn,pc,Rn)
|
||||
else Cycles=12;
|
||||
} else {
|
||||
Cycles=8;
|
||||
}
|
||||
|
||||
Cycles+=Ea_add_ns(g_movem_cycle_table,ea);
|
||||
|
||||
OpEnd();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --------------------- Opcodes 0x4e60+ ---------------------
|
||||
// Emit a Move USP opcode, 01001110 0110dnnn move An to/from USP
|
||||
int OpMoveUsp(int op)
|
||||
{
|
||||
int use=0,dir=0;
|
||||
|
||||
dir=(op>>3)&1; // Direction
|
||||
use=op&~0x0007; // Use same opcode for all An
|
||||
|
||||
if (op!=use) { OpUse(op,use); return 0; } // Use existing handler
|
||||
|
||||
OpStart(op); Cycles=4;
|
||||
|
||||
SuperCheck(op);
|
||||
|
||||
if (dir)
|
||||
{
|
||||
ot(" ldr r1,[r7,#0x48] ;@ Get from USP\n\n");
|
||||
EaCalc (0,0x0007,8,2,1);
|
||||
EaWrite(0, 1,8,2,0x0007,1);
|
||||
}
|
||||
else
|
||||
{
|
||||
EaCalc (0,0x0007,8,2,1);
|
||||
EaRead (0, 0,8,2,0x0007,1);
|
||||
ot(" str r0,[r7,#0x48] ;@ Put in USP\n\n");
|
||||
}
|
||||
|
||||
OpEnd();
|
||||
|
||||
SuperEnd(op);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --------------------- Opcodes 0x7000+ ---------------------
|
||||
// Emit a Move Quick opcode, 0111nnn0 dddddddd moveq #dd,Dn
|
||||
int OpMoveq(int op)
|
||||
{
|
||||
int use=0;
|
||||
|
||||
use=op&0xf100; // Use same opcode for all values
|
||||
if (op!=use) { OpUse(op,use); return 0; } // Use existing handler
|
||||
|
||||
OpStart(op); Cycles=4;
|
||||
|
||||
ot(" movs r0,r8,asl #24\n");
|
||||
ot(" and r1,r8,#0x0e00\n");
|
||||
ot(" mov r0,r0,asr #24 ;@ Sign extended Quick value\n");
|
||||
ot(" mrs r9,cpsr ;@ r9=NZ flags\n");
|
||||
ot(" str r0,[r7,r1,lsr #7] ;@ Store into Dn\n");
|
||||
ot("\n");
|
||||
|
||||
OpEnd();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --------------------- Opcodes 0xc140+ ---------------------
|
||||
// Emit a Exchange opcode:
|
||||
// 1100ttt1 01000sss exg ds,dt
|
||||
// 1100ttt1 01001sss exg as,at
|
||||
// 1100ttt1 10001sss exg as,dt
|
||||
int OpExg(int op)
|
||||
{
|
||||
int use=0,type=0;
|
||||
|
||||
type=op&0xf8;
|
||||
|
||||
if (type!=0x40 && type!=0x48 && type!=0x88) return 1; // Not an exg opcode
|
||||
|
||||
use=op&0xf1f8; // Use same opcode for all values
|
||||
if (op!=use) { OpUse(op,use); return 0; } // Use existing handler
|
||||
|
||||
OpStart(op); Cycles=6;
|
||||
|
||||
ot(" and r10,r8,#0x0e00 ;@ Find T register\n");
|
||||
ot(" and r11,r8,#0x000f ;@ Find S register\n");
|
||||
if (type==0x48) ot(" orr r10,r10,#0x1000 ;@ T is an address register\n");
|
||||
ot("\n");
|
||||
ot(" ldr r0,[r7,r10,lsr #7] ;@ Get T\n");
|
||||
ot(" ldr r1,[r7,r11,lsl #2] ;@ Get S\n");
|
||||
ot("\n");
|
||||
ot(" str r0,[r7,r11,lsl #2] ;@ T->S\n");
|
||||
ot(" str r1,[r7,r10,lsr #7] ;@ S->T\n");
|
||||
ot("\n");
|
||||
|
||||
OpEnd();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ------------------------- movep -------------------------------
|
||||
// 0000ddd1 0z001sss
|
||||
// 0000sss1 1z001ddd (to mem)
|
||||
int OpMovep(int op)
|
||||
{
|
||||
int ea=0;
|
||||
int size=1,use=0,dir;
|
||||
|
||||
use=op&0xf1f8;
|
||||
if (op!=use) { OpUse(op,use); return 0; } // Use existing handler (for all dests, srcs)
|
||||
|
||||
// Get EA
|
||||
ea = (op&0x0007)|0x28;
|
||||
dir = (op>>7)&1;
|
||||
|
||||
// Find size extension
|
||||
if(op&0x0040) size=2;
|
||||
|
||||
OpStart(op);
|
||||
|
||||
if(dir) { // reg to mem
|
||||
EaCalc(11,0x0e00,0,size); // reg number -> r11
|
||||
EaRead(11,11,0,size,0x0e00); // regval -> r11
|
||||
EaCalc(10,0x0007,ea,size);
|
||||
if(size==2) { // if operand is long
|
||||
ot(" mov r1,r11,lsr #24 ;@ first byte\n");
|
||||
EaWrite(10,1,ea,0,0x0007); // store first byte
|
||||
ot(" add r10,r10,#2\n");
|
||||
ot(" mov r1,r11,lsr #16 ;@ second byte\n");
|
||||
EaWrite(10,1,ea,0,0x0007); // store second byte
|
||||
ot(" add r10,r10,#2\n");
|
||||
}
|
||||
ot(" mov r1,r11,lsr #8 ;@ first or third byte\n");
|
||||
EaWrite(10,1,ea,0,0x0007);
|
||||
ot(" add r10,r10,#2\n");
|
||||
ot(" and r1,r11,#0xff\n");
|
||||
EaWrite(10,1,ea,0,0x0007);
|
||||
} else { // mem to reg
|
||||
EaCalc(10,0x0007,ea,size,1);
|
||||
EaRead(10,11,ea,0,0x0007,1); // read first byte
|
||||
ot(" add r10,r10,#2\n");
|
||||
EaRead(10,1,ea,0,0x0007,1); // read second byte
|
||||
if(size==2) { // if operand is long
|
||||
ot(" orr r11,r11,r1,lsr #8 ;@ second byte\n");
|
||||
ot(" add r10,r10,#2\n");
|
||||
EaRead(10,1,ea,0,0x0007,1);
|
||||
ot(" orr r11,r11,r1,lsr #16 ;@ third byte\n");
|
||||
ot(" add r10,r10,#2\n");
|
||||
EaRead(10,1,ea,0,0x0007,1);
|
||||
ot(" orr r0,r11,r1,lsr #24 ;@ fourth byte\n");
|
||||
} else {
|
||||
ot(" orr r0,r11,r1,lsr #8 ;@ second byte\n");
|
||||
}
|
||||
// store the result
|
||||
EaCalc(11,0x0e00,0,size,1); // reg number -> r11
|
||||
EaWrite(11,0,0,size,0x0e00,1);
|
||||
}
|
||||
|
||||
Cycles=(size==2)?24:16;
|
||||
OpEnd();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Emit a Stop/Reset opcodes, 01001110 011100t0 imm
|
||||
int OpStopReset(int op)
|
||||
{
|
||||
int type=(op>>1)&1; // reset/stop
|
||||
|
||||
OpStart(op);
|
||||
|
||||
SuperCheck(op);
|
||||
|
||||
if(type) {
|
||||
// copy immediate to SR, stop the CPU and eat all remaining cycles.
|
||||
ot(" ldrh r0,[r4],#2 ;@ Fetch the immediate\n");
|
||||
SuperChange(op);
|
||||
OpRegToFlags(1);
|
||||
|
||||
ot("\n");
|
||||
|
||||
ot(" mov r0,#1\n");
|
||||
ot(" str r0,[r7,#0x58] ;@ stopped\n");
|
||||
ot("\n");
|
||||
|
||||
ot(" mov r5,#0 ;@ eat cycles\n");
|
||||
Cycles = 4;
|
||||
ot("\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
Cycles = 132;
|
||||
#if USE_RESET_CALLBACK
|
||||
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");
|
||||
ot(" ldr r11,[r7,#0x90] ;@ ResetCallback\n");
|
||||
ot(" tst r11,r11\n");
|
||||
ot(" movne lr,pc\n");
|
||||
ot(" movne pc,r11 ;@ call ResetCallback if it is defined\n");
|
||||
ot(" ldrb r9,[r7,#0x46] ;@ r9 = Load Flags (NZCV)\n");
|
||||
ot(" ldr r5,[r7,#0x5c] ;@ Load Cycles\n");
|
||||
ot(" ldr r4,[r7,#0x40] ;@ Load PC\n");
|
||||
ot(" mov r9,r9,lsl #28\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
OpEnd();
|
||||
SuperEnd(op);
|
||||
|
||||
return 0;
|
||||
}
|
100
cpu/Cyclone/app.h
Normal file
100
cpu/Cyclone/app.h
Normal file
|
@ -0,0 +1,100 @@
|
|||
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
// Disa.c
|
||||
#include "Disa/Disa.h"
|
||||
|
||||
// Ea.cpp
|
||||
extern int g_jmp_cycle_table[];
|
||||
extern int g_jsr_cycle_table[];
|
||||
extern int g_lea_cycle_table[];
|
||||
extern int g_pea_cycle_table[];
|
||||
extern int g_movem_cycle_table[];
|
||||
int Ea_add_ns(int *tab, int ea); // add nonstandard EA cycles
|
||||
int EaCalc(int a,int mask,int ea,int size,int top=0);
|
||||
int EaRead(int a,int v,int ea,int size,int mask,int top=0);
|
||||
int EaCanRead(int ea,int size);
|
||||
int EaWrite(int a,int v,int ea,int size,int mask,int top=0);
|
||||
int EaCanWrite(int ea);
|
||||
int EaAn(int ea);
|
||||
|
||||
// Main.cpp
|
||||
extern int *CyJump; // Jump table
|
||||
extern int ms; // If non-zero, output in Microsoft ARMASM format
|
||||
extern char *Narm[4]; // Normal ARM Extensions for operand sizes 0,1,2
|
||||
extern char *Sarm[4]; // Sign-extend ARM Extensions for operand sizes 0,1,2
|
||||
extern int Cycles; // Current cycles for opcode
|
||||
void ot(const char *format, ...);
|
||||
void ltorg();
|
||||
void CheckInterrupt(int op);
|
||||
int MemHandler(int type,int size);
|
||||
|
||||
// OpAny.cpp
|
||||
int OpGetFlags(int subtract,int xbit,int sprecialz=0);
|
||||
void OpUse(int op,int use);
|
||||
void OpStart(int op);
|
||||
void OpEnd();
|
||||
int OpBase(int op,int sepa=0);
|
||||
void OpAny(int op);
|
||||
|
||||
//----------------------
|
||||
// OpArith.cpp
|
||||
int OpArith(int op);
|
||||
int OpLea(int op);
|
||||
int OpAddq(int op);
|
||||
int OpArithReg(int op);
|
||||
int OpMul(int op);
|
||||
int OpAbcd(int op);
|
||||
int OpNbcd(int op);
|
||||
int OpAritha(int op);
|
||||
int OpAddx(int op);
|
||||
int OpCmpEor(int op);
|
||||
int OpCmpm(int op);
|
||||
int OpChk(int op);
|
||||
int GetXBit(int subtract);
|
||||
|
||||
// OpBranch.cpp
|
||||
void OpPush32();
|
||||
void OpPushSr(int high);
|
||||
int OpTrap(int op);
|
||||
int OpLink(int op);
|
||||
int OpUnlk(int op);
|
||||
int Op4E70(int op);
|
||||
int OpJsr(int op);
|
||||
int OpBranch(int op);
|
||||
int OpDbra(int op);
|
||||
|
||||
// OpLogic.cpp
|
||||
int OpBtstReg(int op);
|
||||
int OpBtstImm(int op);
|
||||
int OpNeg(int op);
|
||||
int OpSwap(int op);
|
||||
int OpTst(int op);
|
||||
int OpExt(int op);
|
||||
int OpSet(int op);
|
||||
int OpAsr(int op);
|
||||
int OpAsrEa(int op);
|
||||
int OpTas(int op);
|
||||
|
||||
// OpMove.cpp
|
||||
int OpMove(int op);
|
||||
int OpLea(int op);
|
||||
void OpFlagsToReg(int high);
|
||||
void OpRegToFlags(int high);
|
||||
int OpMoveSr(int op);
|
||||
int OpArithSr(int op);
|
||||
int OpPea(int op);
|
||||
int OpMovem(int op);
|
||||
int OpMoveq(int op);
|
||||
int OpMoveUsp(int op);
|
||||
int OpExg(int op);
|
||||
int OpMovep(int op); // notaz
|
||||
int OpStopReset(int op);
|
||||
void SuperCheck(int op);
|
||||
void SuperEnd(int op);
|
||||
void SuperChange(int op);
|
101
cpu/Cyclone/config.h
Normal file
101
cpu/Cyclone/config.h
Normal file
|
@ -0,0 +1,101 @@
|
|||
|
||||
|
||||
/**
|
||||
* Cyclone 68000 configuration file
|
||||
**/
|
||||
|
||||
|
||||
/*
|
||||
* If this option is enabled, Microsoft ARMASM compatible output is generated.
|
||||
* Otherwise GNU as syntax is used.
|
||||
*/
|
||||
#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 enebled.
|
||||
* This option also alters timing slightly.
|
||||
*/
|
||||
#define CYCLONE_FOR_GENESIS 1
|
||||
|
||||
/*
|
||||
* 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
|
||||
|
||||
/*
|
||||
* 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
|
||||
* any reason need to access them in your memory handlers, enable the options below,
|
||||
* otherwise disable them to improve performance.
|
||||
* Warning: the PC value will not point to start of instruction (it will be middle or
|
||||
* end), also updating PC is dangerous, as Cyclone may internally increment the PC
|
||||
* before fetching the next instruction and continue executing at wrong location.
|
||||
*/
|
||||
#define MEMHANDLERS_NEED_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 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.
|
||||
*/
|
||||
#define USE_INT_ACK_CALLBACK 1
|
||||
|
||||
/*
|
||||
* Enable this if you need/change PC, flags or cycles in your IrqCallback function.
|
||||
*/
|
||||
#define INT_ACK_NEEDS_STUFF 0
|
||||
#define INT_ACK_CHANGES_STUFF 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 shuold 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
|
||||
|
||||
/*
|
||||
* 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
|
BIN
cpu/Cyclone/epoc/crash_cyclone.bin
Normal file
BIN
cpu/Cyclone/epoc/crash_cyclone.bin
Normal file
Binary file not shown.
150
cpu/Cyclone/epoc/patchtable_symb.c
Normal file
150
cpu/Cyclone/epoc/patchtable_symb.c
Normal file
|
@ -0,0 +1,150 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
typedef struct {
|
||||
unsigned long _dontcare1[4];
|
||||
char signature[4]; // 'EPOC'
|
||||
unsigned long iCpu; // 0x1000 = X86, 0x2000 = ARM, 0x4000 = M*Core
|
||||
unsigned long iCheckSumCode; // sum of all 32 bit words in .text
|
||||
unsigned long _dontcare3[5];
|
||||
unsigned long iCodeSize; // size of code, import address table, constant data and export dir |+30
|
||||
unsigned long _dontcare4[12];
|
||||
unsigned long iCodeOffset; // file offset to code section |+64
|
||||
unsigned long _dontcare5[2];
|
||||
unsigned long iCodeRelocOffset; // relocations for code and const |+70
|
||||
unsigned long iDataRelocOffset; // relocations for data
|
||||
unsigned long iPriority; // priority of this process (EPriorityHigh=450)
|
||||
} E32ImageHeader;
|
||||
|
||||
|
||||
typedef struct {
|
||||
unsigned long iSize; // size of this relocation section
|
||||
unsigned long iNumberOfRelocs; // number of relocations in this section
|
||||
} E32RelocSection;
|
||||
|
||||
|
||||
typedef struct {
|
||||
unsigned long base;
|
||||
unsigned long size;
|
||||
} reloc_page_header;
|
||||
|
||||
|
||||
// E32Image relocation section consists of a number of pages
|
||||
// every page has 8 byte header and a number or 16bit relocation entries
|
||||
// entry format:
|
||||
// 0x3000 | <12bit_reloc_offset>
|
||||
//
|
||||
// if we have page_header.base == 0x1000 and a reloc entry 0x3110,
|
||||
// it means that 32bit value at offset 0x1110 of .text section
|
||||
// is relocatable
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
FILE *f = 0;
|
||||
unsigned char pattern[8] = { 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56 };
|
||||
unsigned char *buff, *p;
|
||||
unsigned long patt_offset; // pattern offset in .text section
|
||||
unsigned long size = 0, i, symbols, insert_pos, *handler;
|
||||
unsigned short reloc_entry;
|
||||
E32ImageHeader *header;
|
||||
E32RelocSection *reloc_section;
|
||||
reloc_page_header *reloc_page;
|
||||
|
||||
if(argc != 3) {
|
||||
printf("usage: %s <e32_exe> <nsymbols>\n\n", argv[0]);
|
||||
printf("note: this was written to fix a problem caused by as v.2.9-psion-98r2 and shouldn't be used for anything else.\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
f = fopen(argv[1], "rb+");
|
||||
if(!f) {
|
||||
printf("%s: couldn't open %s\n", argv[0], argv[1]);
|
||||
return 2;
|
||||
}
|
||||
|
||||
symbols = atoi(argv[2]);
|
||||
|
||||
// read the file
|
||||
fseek(f,0,SEEK_END); size=ftell(f); fseek(f,0,SEEK_SET);
|
||||
buff = (unsigned char *) malloc(size);
|
||||
fread(buff,1,size,f);
|
||||
|
||||
header = (E32ImageHeader *) buff;
|
||||
|
||||
if(strncmp(header->signature, "EPOC", 4) || header->iCpu != 0x2000) {
|
||||
printf("%s: not a E32 executable image for ARM target.\n", argv[0]);
|
||||
fclose(f);
|
||||
free(buff);
|
||||
return 2;
|
||||
}
|
||||
|
||||
// find the pattern
|
||||
for(i = 0; i < size-8; i++)
|
||||
if(memcmp(buff+i, pattern, 8) == 0) break;
|
||||
if(i == size-8 || i < 4) {
|
||||
printf("%s: failed to find the pattern.\n", argv[0]);
|
||||
fclose(f);
|
||||
free(buff);
|
||||
return 3;
|
||||
}
|
||||
patt_offset = i - header->iCodeOffset;
|
||||
|
||||
// find suitable reloc section
|
||||
reloc_section = (E32RelocSection *) (buff + header->iCodeRelocOffset);
|
||||
for(i = 0, p = buff+header->iCodeRelocOffset+8; i < reloc_section->iSize; ) {
|
||||
reloc_page = (reloc_page_header *) p;
|
||||
if(patt_offset - reloc_page->base >= 0 && patt_offset - reloc_page->base < 0x1000 - symbols*4) break;
|
||||
i += reloc_page->size;
|
||||
p += reloc_page->size;
|
||||
}
|
||||
|
||||
if(i >= reloc_section->iSize) {
|
||||
printf("%s: suitable reloc section not found.\n", argv[0]);
|
||||
fclose(f);
|
||||
free(buff);
|
||||
return 4;
|
||||
}
|
||||
|
||||
// now find the insert pos and update everything
|
||||
insert_pos = p + reloc_page->size - buff;
|
||||
reloc_page->size += symbols*2;
|
||||
reloc_section->iSize += symbols*2;
|
||||
reloc_section->iNumberOfRelocs += symbols;
|
||||
header->iDataRelocOffset += symbols*2; // data reloc section is now also pushed a little
|
||||
header->iPriority = 450; // let's boost our priority :)
|
||||
|
||||
// replace the placeholders themselves
|
||||
handler = (unsigned long *) (buff + patt_offset + header->iCodeOffset - 4);
|
||||
for(i = 1; i <= symbols; i++)
|
||||
*(handler+i) = *handler;
|
||||
|
||||
// recalculate checksum
|
||||
header->iCheckSumCode = 0;
|
||||
for(i = 0, p = buff+header->iCodeOffset; i < header->iCodeSize; i+=4, p+=4)
|
||||
header->iCheckSumCode += *(unsigned long *) p;
|
||||
|
||||
// check for possible padding
|
||||
if(!*(buff+insert_pos-1)) insert_pos -= 2;
|
||||
|
||||
// write all this joy
|
||||
fseek(f,0,SEEK_SET);
|
||||
fwrite(buff, 1, insert_pos, f);
|
||||
|
||||
// write new reloc entries
|
||||
for(i = 0; i < symbols; i++) {
|
||||
handler++;
|
||||
reloc_entry = ((unsigned char *) handler - buff - reloc_page->base - header->iCodeOffset) | 0x3000;
|
||||
fwrite(&reloc_entry, 1, 2, f);
|
||||
}
|
||||
|
||||
// write the remaining data
|
||||
fwrite(buff+insert_pos, 1, size-insert_pos, f);
|
||||
|
||||
// done at last!
|
||||
fclose(f);
|
||||
free(buff);
|
||||
|
||||
return 0;
|
||||
}
|
133
cpu/Cyclone/epoc/patchtable_symb2.c
Normal file
133
cpu/Cyclone/epoc/patchtable_symb2.c
Normal file
|
@ -0,0 +1,133 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#define symbols 2
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
FILE *f = 0;
|
||||
unsigned char pattern[8] = { 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56 };
|
||||
unsigned char *buff, *p;
|
||||
unsigned long patt_offset; // pattern offset in .text section
|
||||
unsigned long size = 0, i, insert_pos, *handler;//, symbols;
|
||||
unsigned short reloc_entry;
|
||||
IMAGE_BASE_RELOCATION *reloc_page;
|
||||
IMAGE_DOS_HEADER *dos_header;
|
||||
IMAGE_FILE_HEADER *file_header;
|
||||
IMAGE_SECTION_HEADER *sect_header, *relocsect_header = 0, *codesect_header = 0;
|
||||
|
||||
if(argc != 2) {
|
||||
printf("usage: %s <pe_exe_or_app_before_petran>\n\n", argv[0]);
|
||||
printf("note: this was written to fix a problem related to Cyclone and as v.2.9-psion-98r2 and shouldn't be used for anything else. See Readme.\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
f = fopen(argv[1], "rb+");
|
||||
if(!f) {
|
||||
printf("%s: couldn't open %s\n", argv[0], argv[1]);
|
||||
return 2;
|
||||
}
|
||||
|
||||
//symbols = atoi(argv[2]);
|
||||
|
||||
// read the file
|
||||
fseek(f,0,SEEK_END); size=ftell(f); fseek(f,0,SEEK_SET);
|
||||
buff = (unsigned char *) malloc(size);
|
||||
fread(buff,1,size,f);
|
||||
|
||||
dos_header = (IMAGE_DOS_HEADER *) buff;
|
||||
file_header= (IMAGE_FILE_HEADER *) (buff+dos_header->e_lfanew+4);
|
||||
sect_header= (IMAGE_SECTION_HEADER *) (buff+dos_header->e_lfanew+4+sizeof(IMAGE_FILE_HEADER)+sizeof(IMAGE_OPTIONAL_HEADER32));
|
||||
|
||||
if(size < 0x500 || dos_header->e_magic != IMAGE_DOS_SIGNATURE ||
|
||||
*(DWORD *)(buff+dos_header->e_lfanew) != IMAGE_NT_SIGNATURE || file_header->Machine != 0x0A00) {
|
||||
printf("%s: not a PE executable image for ARM target.\n", argv[0]);
|
||||
fclose(f);
|
||||
free(buff);
|
||||
return 2;
|
||||
}
|
||||
|
||||
// scan all sections for data and reloc sections
|
||||
for(i = 0; i < file_header->NumberOfSections; i++, sect_header++) {
|
||||
if(strncmp(sect_header->Name, ".text", 5) == 0) codesect_header = sect_header;
|
||||
else if(strncmp(sect_header->Name, ".reloc", 6) == 0) relocsect_header = sect_header;
|
||||
}
|
||||
|
||||
if(!codesect_header || !relocsect_header) {
|
||||
printf("%s: failed to find reloc and/or data section.\n", argv[0]);
|
||||
fclose(f);
|
||||
free(buff);
|
||||
return 3;
|
||||
}
|
||||
|
||||
if(relocsect_header != sect_header-1) {
|
||||
printf("%s: bug: reloc section is not last, this is unexpected and not supported.\n", argv[0]);
|
||||
fclose(f);
|
||||
free(buff);
|
||||
return 4;
|
||||
}
|
||||
|
||||
// find the pattern
|
||||
for(i = codesect_header->PointerToRawData; i < size-8; i+=2)
|
||||
if(memcmp(buff+i, pattern, 8) == 0) break;
|
||||
if(i == size-8 || i < 4) {
|
||||
printf("%s: failed to find the pattern.\n", argv[0]);
|
||||
fclose(f);
|
||||
free(buff);
|
||||
return 5;
|
||||
}
|
||||
|
||||
// calculate pattern offset in RVA (relative virtual address)
|
||||
patt_offset = i - codesect_header->PointerToRawData + codesect_header->VirtualAddress;
|
||||
|
||||
// replace the placeholders themselves
|
||||
handler = (unsigned long *) (buff + i - 4);
|
||||
for(i = 1; i <= symbols; i++)
|
||||
*(handler+i) = *handler;
|
||||
|
||||
// find suitable reloc section
|
||||
for(i = 0, p = buff+relocsect_header->PointerToRawData; i < relocsect_header->SizeOfRawData; ) {
|
||||
reloc_page = (IMAGE_BASE_RELOCATION *) p;
|
||||
if(patt_offset - reloc_page->VirtualAddress >= 0 && patt_offset - reloc_page->VirtualAddress < 0x1000 - symbols*2) break;
|
||||
i += reloc_page->SizeOfBlock;
|
||||
p += reloc_page->SizeOfBlock;
|
||||
}
|
||||
|
||||
if(i >= relocsect_header->SizeOfRawData) {
|
||||
printf("%s: suitable reloc section not found.\n", argv[0]);
|
||||
fclose(f);
|
||||
free(buff);
|
||||
return 6;
|
||||
}
|
||||
|
||||
// now find the insert pos and update everything
|
||||
insert_pos = p + reloc_page->SizeOfBlock - buff;
|
||||
reloc_page->SizeOfBlock += symbols*2;
|
||||
relocsect_header->SizeOfRawData += symbols*2;
|
||||
|
||||
// check for possible padding
|
||||
if(!*(buff+insert_pos-1)) insert_pos -= 2;
|
||||
|
||||
// write all this joy
|
||||
fseek(f,0,SEEK_SET);
|
||||
fwrite(buff, 1, insert_pos, f);
|
||||
|
||||
// write new reloc entries
|
||||
for(i = 0; i < symbols; i++) {
|
||||
handler++;
|
||||
reloc_entry = (unsigned short)(((unsigned char *) handler - buff) - reloc_page->VirtualAddress - codesect_header->PointerToRawData + codesect_header->VirtualAddress) | 0x3000; // quite nasty
|
||||
fwrite(&reloc_entry, 1, 2, f);
|
||||
}
|
||||
|
||||
// write the remaining data
|
||||
fwrite(buff+insert_pos, 1, size-insert_pos, f);
|
||||
|
||||
// done at last!
|
||||
fclose(f);
|
||||
free(buff);
|
||||
|
||||
return 0;
|
||||
}
|
42
cpu/Cyclone/epoc/readme.txt
Normal file
42
cpu/Cyclone/epoc/readme.txt
Normal file
|
@ -0,0 +1,42 @@
|
|||
*update*
|
||||
Use the "compress jumtable" Cyclone config.h option to fix this issue
|
||||
(no patcher will be needed then).
|
||||
|
||||
|
||||
There is a problem with Cyclone on symbian platform:
|
||||
GNU as generates COFF object files, which allow max of 0xFFFF (65535) relocation
|
||||
entries. Cyclone uses a jumptable of 0x10000 (65536, 1 for every opcode-word)
|
||||
antries. When the executable is loaded, all jumptable entries must be relocated
|
||||
to point to right code location. Because of this limitation, Cyclone's jumptable is
|
||||
incomplete (misses 2 entries), and if M68k opcodes 0xFFFE or 0xFFFF are ever
|
||||
encoundered when emulating, your emulator will crash.
|
||||
|
||||
I have written a little patcher to fix that. It writes those two missing entries and
|
||||
marks them as relocatable. Placeholders must not be deleted just after the jumttable
|
||||
in the Cyclone source code.
|
||||
|
||||
This version works with intermediate PE executable, which is used both for APPs and EXEs,
|
||||
and is produced by gcc toolkit just before running petran. So it's best to insert
|
||||
this in your makefile, in the rule which builds your APP/EXE, just after last 'ld'
|
||||
statement, for example:
|
||||
|
||||
$(EPOCTRGUREL)\PICODRIVEN.APP : $(EPOCBLDUREL)\PICODRIVEN.in $(EPOCSTATLINKUREL)\EDLL.LIB $(LIBSUREL)
|
||||
...
|
||||
ld -s -e _E32Dll -u _E32Dll --dll \
|
||||
"$(EPOCBLDUREL)\PICODRIVEN.exp" \
|
||||
-Map "$(EPOCTRGUREL)\PICODRIVEN.APP.map" -o "$(EPOCBLDUREL)\PICODRIVEN.APP" \
|
||||
"$(EPOCSTATLINKUREL)\EDLL.LIB" --whole-archive "$(EPOCBLDUREL)\PICODRIVEN.in" \
|
||||
--no-whole-archive $(LIBSUREL) $(USERLDFLAGS)
|
||||
-$(ERASE) "$(EPOCBLDUREL)\PICODRIVEN.exp"
|
||||
|
||||
patchtable_symb2 "$(EPOCBLDUREL)\PICODRIVEN.APP"
|
||||
|
||||
petran "$(EPOCBLDUREL)\PICODRIVEN.APP" "$@" \
|
||||
-nocall -uid1 0x10000079 -uid2 0x100039ce -uid3 0x1000c193
|
||||
-$(ERASE) "$(EPOCBLDUREL)\PICODRIVEN.APP"
|
||||
perl -S ecopyfile.pl "$@" "PICODRIVEN.APP"
|
||||
|
||||
|
||||
This is also compatible with ECompXL.
|
||||
|
||||
To test if this thing worked, you can load crash_cyclone.bin in your emulator.
|
39
cpu/Cyclone/proj/Makefile.linux
Normal file
39
cpu/Cyclone/proj/Makefile.linux
Normal file
|
@ -0,0 +1,39 @@
|
|||
CFLAGS = -Wall
|
||||
|
||||
ALL : cyclone.s
|
||||
|
||||
cyclone.s : Cyclone.exe
|
||||
./Cyclone.exe
|
||||
|
||||
Cyclone.exe : Main.o Ea.o OpAny.o OpArith.o OpBranch.o OpLogic.o Disa.o OpMove.o
|
||||
$(CC) $^ -o $@ -lstdc++
|
||||
|
||||
Main.o : ../Main.cpp ../app.h
|
||||
$(CC) $(CFLAGS) ../Main.cpp -c -o $@
|
||||
|
||||
Ea.o : ../Ea.cpp ../app.h
|
||||
$(CC) $(CFLAGS) ../Ea.cpp -c -o $@
|
||||
|
||||
OpAny.o : ../OpAny.cpp ../app.h
|
||||
$(CC) $(CFLAGS) ../OpAny.cpp -c -o $@
|
||||
|
||||
OpArith.o : ../OpArith.cpp ../app.h
|
||||
$(CC) $(CFLAGS) ../OpArith.cpp -c -o $@
|
||||
|
||||
OpBranch.o : ../OpBranch.cpp ../app.h
|
||||
$(CC) $(CFLAGS) ../OpBranch.cpp -c -o $@
|
||||
|
||||
OpLogic.o : ../OpLogic.cpp ../app.h
|
||||
$(CC) $(CFLAGS) ../OpLogic.cpp -c -o $@
|
||||
|
||||
OpMove.o : ../OpMove.cpp ../app.h
|
||||
$(CC) $(CFLAGS) ../OpMove.cpp -c -o $@
|
||||
|
||||
Disa.o : ../Disa/Disa.c ../Disa/Disa.h
|
||||
$(CC) $(CFLAGS) ../Disa/Disa.c -c -o $@
|
||||
|
||||
../app.h : ../config.h
|
||||
|
||||
clean :
|
||||
$(RM) *.o Cyclone.exe Cyclone.s
|
||||
|
60
cpu/Cyclone/proj/Makefile.win
Normal file
60
cpu/Cyclone/proj/Makefile.win
Normal file
|
@ -0,0 +1,60 @@
|
|||
# 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
|
||||
|
||||
LINK32=link.exe
|
||||
LINK32_FLAGS=user32.lib /nologo /subsystem:console /machine:I386 /out:Cyclone.exe
|
||||
|
||||
|
||||
ALL : cyclone.s
|
||||
|
||||
cyclone.s : Cyclone.exe
|
||||
Cyclone.exe
|
||||
|
||||
Cyclone.exe : Main.obj Ea.obj OpAny.obj OpArith.obj OpBranch.obj OpLogic.obj Disa.obj OpMove.obj
|
||||
$(LINK32) Main.obj Ea.obj OpAny.obj OpArith.obj OpBranch.obj OpLogic.obj Disa.obj OpMove.obj $(LINK32_FLAGS)
|
||||
|
||||
Main.obj : ..\Main.cpp ..\app.h
|
||||
$(CPP) $(CPP_PROJ) ..\Main.cpp
|
||||
|
||||
Ea.obj : ..\Ea.cpp ..\app.h
|
||||
$(CPP) $(CPP_PROJ) ..\Ea.cpp
|
||||
|
||||
OpAny.obj : ..\OpAny.cpp ..\app.h
|
||||
$(CPP) $(CPP_PROJ) ..\OpAny.cpp
|
||||
|
||||
OpArith.obj : ..\OpArith.cpp ..\app.h
|
||||
$(CPP) $(CPP_PROJ) ..\OpArith.cpp
|
||||
|
||||
OpBranch.obj : ..\OpBranch.cpp ..\app.h
|
||||
$(CPP) $(CPP_PROJ) ..\OpBranch.cpp
|
||||
|
||||
OpLogic.obj : ..\OpLogic.cpp ..\app.h
|
||||
$(CPP) $(CPP_PROJ) ..\OpLogic.cpp
|
||||
|
||||
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
|
||||
|
||||
..\app.h : ..\config.h
|
||||
|
||||
|
||||
CLEAN :
|
||||
-@erase "Disa.obj"
|
||||
-@erase "Ea.obj"
|
||||
-@erase "Main.obj"
|
||||
-@erase "OpAny.obj"
|
||||
-@erase "OpArith.obj"
|
||||
-@erase "OpBranch.obj"
|
||||
-@erase "OpLogic.obj"
|
||||
-@erase "OpMove.obj"
|
||||
-@erase "vc60.idb"
|
||||
-@erase "vc60.pch"
|
||||
-@erase "Cyclone.exe"
|
||||
-@erase "Cyclone.asm"
|
||||
-@erase "Cyclone.s"
|
||||
-@erase "Cyclone.o"
|
||||
|
146
cpu/Cyclone/proj/cyclone.dsp
Normal file
146
cpu/Cyclone/proj/cyclone.dsp
Normal file
|
@ -0,0 +1,146 @@
|
|||
# Microsoft Developer Studio Project File - Name="cyclone" - Package Owner=<4>
|
||||
# Microsoft Developer Studio Generated Build File, Format Version 6.00
|
||||
# ** DO NOT EDIT **
|
||||
|
||||
# TARGTYPE "Win32 (x86) Console Application" 0x0103
|
||||
|
||||
CFG=cyclone - Win32 Debug
|
||||
!MESSAGE This is not a valid makefile. To build this project using NMAKE,
|
||||
!MESSAGE use the Export Makefile command and run
|
||||
!MESSAGE
|
||||
!MESSAGE NMAKE /f "cyclone.mak".
|
||||
!MESSAGE
|
||||
!MESSAGE You can specify a configuration when running NMAKE
|
||||
!MESSAGE by defining the macro CFG on the command line. For example:
|
||||
!MESSAGE
|
||||
!MESSAGE NMAKE /f "cyclone.mak" CFG="cyclone - Win32 Debug"
|
||||
!MESSAGE
|
||||
!MESSAGE Possible choices for configuration are:
|
||||
!MESSAGE
|
||||
!MESSAGE "cyclone - Win32 Release" (based on "Win32 (x86) Console Application")
|
||||
!MESSAGE "cyclone - Win32 Debug" (based on "Win32 (x86) Console Application")
|
||||
!MESSAGE
|
||||
|
||||
# Begin Project
|
||||
# PROP AllowPerConfigDependencies 0
|
||||
# PROP Scc_ProjName ""
|
||||
# PROP Scc_LocalPath ""
|
||||
CPP=cl.exe
|
||||
RSC=rc.exe
|
||||
|
||||
!IF "$(CFG)" == "cyclone - Win32 Release"
|
||||
|
||||
# PROP BASE Use_MFC 0
|
||||
# PROP BASE Use_Debug_Libraries 0
|
||||
# PROP BASE Output_Dir "Release"
|
||||
# PROP BASE Intermediate_Dir "Release"
|
||||
# PROP BASE Target_Dir ""
|
||||
# PROP Use_MFC 0
|
||||
# PROP Use_Debug_Libraries 0
|
||||
# PROP Output_Dir "Release"
|
||||
# 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 RSC /l 0x427 /d "NDEBUG"
|
||||
# ADD RSC /l 0x427 /d "NDEBUG"
|
||||
BSC32=bscmake.exe
|
||||
# ADD BASE BSC32 /nologo
|
||||
# ADD BSC32 /nologo
|
||||
LINK32=link.exe
|
||||
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386
|
||||
# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 /out:"cyclone.exe"
|
||||
|
||||
!ELSEIF "$(CFG)" == "cyclone - Win32 Debug"
|
||||
|
||||
# PROP BASE Use_MFC 0
|
||||
# PROP BASE Use_Debug_Libraries 1
|
||||
# PROP BASE Output_Dir "Debug"
|
||||
# PROP BASE Intermediate_Dir "Debug"
|
||||
# PROP BASE Target_Dir ""
|
||||
# PROP Use_MFC 0
|
||||
# PROP Use_Debug_Libraries 1
|
||||
# PROP Output_Dir "Debug"
|
||||
# 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 RSC /l 0x427 /d "_DEBUG"
|
||||
# ADD RSC /l 0x427 /d "_DEBUG"
|
||||
BSC32=bscmake.exe
|
||||
# ADD BASE BSC32 /nologo
|
||||
# ADD BSC32 /nologo
|
||||
LINK32=link.exe
|
||||
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
|
||||
# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /out:"cyclone.exe" /pdbtype:sept
|
||||
|
||||
!ENDIF
|
||||
|
||||
# Begin Target
|
||||
|
||||
# Name "cyclone - Win32 Release"
|
||||
# Name "cyclone - Win32 Debug"
|
||||
# Begin Group "Source Files"
|
||||
|
||||
# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=..\disa\Disa.c
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=..\Ea.cpp
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=..\Main.cpp
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=..\OpAny.cpp
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=..\OpArith.cpp
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=..\OpBranch.cpp
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=..\OpLogic.cpp
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=..\OpMove.cpp
|
||||
# End Source File
|
||||
# End Group
|
||||
# Begin Group "Header Files"
|
||||
|
||||
# PROP Default_Filter "h;hpp;hxx;hm;inl"
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=..\app.h
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=..\config.h
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=..\Cyclone.h
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=..\disa\Disa.h
|
||||
# End Source File
|
||||
# End Group
|
||||
# Begin Group "Resource Files"
|
||||
|
||||
# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
|
||||
# End Group
|
||||
# End Target
|
||||
# End Project
|
144
cpu/DrZ80/DrZ80.txt
Normal file
144
cpu/DrZ80/DrZ80.txt
Normal file
|
@ -0,0 +1,144 @@
|
|||
|
||||
___________________________________________________________________________
|
||||
|
||||
DrZ80 (c) Copyright 2004 Reesy. Free for non-commercial use
|
||||
|
||||
Reesy's e-mail: drsms_reesy(atsymbol)yahoo.co.uk
|
||||
Replace (atsymbol) with @
|
||||
|
||||
___________________________________________________________________________
|
||||
|
||||
|
||||
What is it?
|
||||
-----------
|
||||
|
||||
DrZ80 is an emulator for the Z80 microprocessor, written in ARM 32-bit assembly.
|
||||
It is aimed at chips such as ARM7 and ARM9 cores, StrongARM and XScale, to interpret Z80
|
||||
code as fast as possible.
|
||||
|
||||
Flags are mapped onto ARM flags whenever possible, which speeds up the processing of an opcode.
|
||||
|
||||
ARM Register Usage
|
||||
------------------
|
||||
|
||||
See source code for up to date of register usage, however a summary is here:
|
||||
|
||||
r0-3: Temporary registers
|
||||
r3 : Pointer to Opcode Jump table
|
||||
r4 : T-States remaining
|
||||
r5 : Pointer to Cpu Context
|
||||
r6 : Current PC + Memory Base (i.e. pointer to next opcode)
|
||||
r7 : Z80 A Register in top 8 bits (i.e. 0xAA000000)
|
||||
r8 : Z80 F Register (Flags) (NZCV) in lowest four bits
|
||||
r9 : Z80 BC Register pair in top 16 bits (i.e. 0xBBCC0000)
|
||||
r10 : Z80 DE Register pair in top 16 bits (i.e. 0xDDEE0000)
|
||||
r11 : Z80 HL Register pair in top 16 bits (i.e. 0xHHLL0000)
|
||||
r12 : Z80 Stack + Memory Base (i.e. pointer to current stack in host system memory)
|
||||
|
||||
( note: r3,r12 are always preserved when calling external memory functions )
|
||||
|
||||
How to Compile
|
||||
--------------
|
||||
|
||||
The core is targeted for the GNU compiler, so to compile just add the "DrZ80.o" object
|
||||
to your makefile and that should be it.
|
||||
|
||||
If you want to compile it seperately, use: as -o DrZ80.o DrZ80.s
|
||||
|
||||
( note: If you want to use DrZ80 with a different compiler you will need to run
|
||||
some sort of parser through the source to make the syntax of the source
|
||||
compatible with your target compiler )
|
||||
|
||||
|
||||
Adding to your project
|
||||
----------------------
|
||||
|
||||
To add DrZ80 to your project, add DrZ80.o, and include DrZ80.h
|
||||
There is one structure: 'struct DrZ80', and three functions: DrZ80Run,DrZ80RaiseInt
|
||||
and DrZ80_irq_callback.
|
||||
|
||||
Don't worry if this seem very minimal - its all you need to run as many Z80s as you want.
|
||||
It works with both C and C++.
|
||||
|
||||
( Note: DrZ80_irq_callback is just a pointer to an irq call back function that needs
|
||||
to be written by you )
|
||||
|
||||
Declaring a Memory handlers
|
||||
---------------------------
|
||||
|
||||
Before you can reset or execute Z80 opcodes you must first set up a set of memory handlers.
|
||||
There are 8 functions you have to set up per CPU, like this:
|
||||
|
||||
unsigned int z80_rebaseSP(unsigned short new_sp);
|
||||
unsigned int z80_rebasePC(unsigned short new_pc);
|
||||
unsigned char z80_read8(unsigned short a);
|
||||
unsigned short z80_read16(unsigned short a);
|
||||
void z80_write8(unsigned char d,unsigned short a);
|
||||
void z80_write16(unsigned short d,unsigned short a);
|
||||
unsigned char z80_in(unsigned char p);
|
||||
void z80_out(unsigned char p,unsigned char d);
|
||||
|
||||
You can think of these functions representing the Z80's memory bus.
|
||||
The Read and Write functions are called whenever the Z80 reads or writes memory.
|
||||
The In and Out functions are called whenever the Z80 uses the I/O ports.
|
||||
The z80_rebasePC and z80_rebaseSP functions are to do with creating direct memory
|
||||
pointers in the host machines memory, I will explain more about this later.
|
||||
|
||||
Declaring a CPU Context
|
||||
-----------------------
|
||||
|
||||
To declare a CPU simple declare a struct Cyclone in your code. For example to declare
|
||||
two Z80s:
|
||||
|
||||
struct DrZ80 MyCpu;
|
||||
struct DrZ80 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.z80_rebasePC=z80_rebasePC;
|
||||
MyCpu.z80_rebaseSP=z80_rebaseSP;
|
||||
MyCpu.z80_read8 =z80_read8;
|
||||
MyCpu.z80_read16 =z80_read16;
|
||||
MyCpu.z80_write8 =z80_write8;
|
||||
MyCpu.z80_write16 =z80_write16;
|
||||
MyCpu.z80_in =z80_in;
|
||||
MyCpu.z80_out =z80_out;
|
||||
|
||||
Now you are nearly ready to reset the Z80, except you need one more function: checkpc().
|
||||
|
||||
The z80_rebasePC() function
|
||||
---------------------------
|
||||
|
||||
When DrZ80 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 Z80 address is also stored in a variable called
|
||||
'pc_membase'. In the above example it's 0x3000000. To retrieve the real PC, DrZ80 just
|
||||
subtracts 'pc_membase'.
|
||||
|
||||
Everytime the Z80 PC is modified, i.e. by a jump,branch intructions or by an interupt, DrZ80
|
||||
calls the z80_rebasePC function. If the PC is in a different bank, for example Ram instead
|
||||
of Rom, change 'pc_membase', recalculate the new PC and return it.
|
||||
|
||||
The z80_rebaseSP() function
|
||||
---------------------------
|
||||
|
||||
When DrZ80 pushs/pops to the Z80 stack pointer, it doesn't use a memory handler every time. In
|
||||
order to gain more speed a direct pointer to ARM memory is used. For example if your Ram was at
|
||||
0x3000000 and the z80 stack pointer counter was 0xD000, DrZ80's stack pointer would be 0x300D000.
|
||||
|
||||
The difference between an ARM address and a Z80 address is also stored in a variable called
|
||||
'sp_membase'. In the above example it's 0x3000000. To retrieve the real SP, DrZ80 just
|
||||
subtracts 'sp_membase'.
|
||||
|
||||
Everytime the Z80 SP is modified ( i.e. set with a new value LD SP,NN etc ) DrZ80
|
||||
calls the z80_rebaseSP function. If the SP is in a different bank, for example Rom instead
|
||||
of Ram, change 'sp_membase', recalculate the new SP and return it.
|
||||
|
77
cpu/DrZ80/drz80.h
Normal file
77
cpu/DrZ80/drz80.h
Normal file
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* DrZ80 Version 1.0
|
||||
* Z80 Emulator by Reesy
|
||||
* Copyright 2005 Reesy
|
||||
*
|
||||
* This file is part of DrZ80.
|
||||
*
|
||||
* DrZ80 is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* DrZ80 is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with DrZ80; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef DRZ80_H
|
||||
#define DRZ80_H
|
||||
|
||||
extern int DrZ80Ver; /* Version number of library */
|
||||
|
||||
struct DrZ80
|
||||
{
|
||||
unsigned int Z80PC; /*0x00 - PC Program Counter (Memory Base + PC) */
|
||||
unsigned int Z80A; /*0x04 - A Register: 0xAA------ */
|
||||
unsigned int Z80F; /*0x08 - F Register: 0xFF------ */
|
||||
unsigned int Z80BC; /*0x0C - BC Registers: 0xBBCC---- */
|
||||
unsigned int Z80DE; /*0x10 - DE Registers: 0xDDEE---- */
|
||||
unsigned int Z80HL; /*0x14 - HL Registers: 0xHHLL---- */
|
||||
unsigned int Z80SP; /*0x18 - SP Stack Pointer (Memory Base + PC) */
|
||||
unsigned int Z80PC_BASE; /*0x1C - PC Program Counter (Memory Base) */
|
||||
unsigned int Z80SP_BASE; /*0x20 - SP Stack Pointer (Memory Base) */
|
||||
unsigned int Z80IX; /*0x24 - IX Index Register */
|
||||
unsigned int Z80IY; /*0x28 - IY Index Register */
|
||||
unsigned int Z80I; /*0x2C - I Interrupt Register */
|
||||
unsigned int Z80A2; /*0x30 - A' Register: 0xAA------ */
|
||||
unsigned int Z80F2; /*0x34 - F' Register: 0xFF------ */
|
||||
unsigned int Z80BC2; /*0x38 - B'C' Registers: 0xBBCC---- */
|
||||
unsigned int Z80DE2; /*0x3C - D'E' Registers: 0xDDEE---- */
|
||||
unsigned int Z80HL2; /*0x40 - H'L' Registers: 0xHHLL---- */
|
||||
int cycles; /*0x44 - Cycles pending to be executed yet */
|
||||
int previouspc; /*0x48 - Previous PC */
|
||||
unsigned char Z80_IRQ; /*0x4C - Set IRQ Number (must be halfword aligned) */
|
||||
unsigned char Z80IF; /*0x4D - Interrupt Flags: bit1=_IFF1, bit2=_IFF2, bit3=_HALT */
|
||||
unsigned char Z80IM; /*0x4E - Set IRQ Mode */
|
||||
unsigned char spare; /*0x4F - N/A */
|
||||
unsigned int z80irqvector; /*0x50 - Set IRQ Vector i.e. 0xFF=RST */
|
||||
void (*z80_irq_callback )(void);
|
||||
void (*z80_write8 )(unsigned char d,unsigned short a);
|
||||
void (*z80_write16 )(unsigned short d,unsigned short a);
|
||||
unsigned char (*z80_in)(unsigned short p);
|
||||
void (*z80_out )(unsigned short p,unsigned char d);
|
||||
unsigned char (*z80_read8)(unsigned short a);
|
||||
unsigned short (*z80_read16)(unsigned short a);
|
||||
unsigned int (*z80_rebaseSP)(unsigned short new_sp);
|
||||
unsigned int (*z80_rebasePC)(unsigned short new_pc);
|
||||
unsigned int bla;
|
||||
};
|
||||
|
||||
extern int DrZ80Run(struct DrZ80 *pcy,unsigned int cyc);
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* End of extern "C" */
|
||||
#endif
|
8076
cpu/DrZ80/drz80.s
Normal file
8076
cpu/DrZ80/drz80.s
Normal file
File diff suppressed because it is too large
Load diff
16
cpu/a68k/Makefile.win
Normal file
16
cpu/a68k/Makefile.win
Normal file
|
@ -0,0 +1,16 @@
|
|||
all : a68k.obj
|
||||
|
||||
a68k.obj :
|
||||
cl /DWIN32 /DFASTCALL make68kd.c
|
||||
make68kd a68k.asm a68k_jmp.asm 00
|
||||
nasm -f win32 a68k.asm
|
||||
|
||||
|
||||
clean : tidy
|
||||
del a68k.obj
|
||||
|
||||
tidy :
|
||||
del a68k.asm
|
||||
del a68k_jmp.asm
|
||||
del make68kd.exe
|
||||
del make68kd.obj
|
1
cpu/a68k/cpuintrf.h
Normal file
1
cpu/a68k/cpuintrf.h
Normal file
|
@ -0,0 +1 @@
|
|||
// dave filler file
|
8192
cpu/a68k/make68kd.c
Normal file
8192
cpu/a68k/make68kd.c
Normal file
File diff suppressed because it is too large
Load diff
371
cpu/musashi/m68k.h
Normal file
371
cpu/musashi/m68k.h
Normal file
|
@ -0,0 +1,371 @@
|
|||
#ifndef M68K__HEADER
|
||||
#define M68K__HEADER
|
||||
|
||||
/* ======================================================================== */
|
||||
/* ========================= LICENSING & COPYRIGHT ======================== */
|
||||
/* ======================================================================== */
|
||||
/*
|
||||
* MUSASHI
|
||||
* Version 3.3
|
||||
*
|
||||
* A portable Motorola M680x0 processor emulation engine.
|
||||
* Copyright 1998-2001 Karl Stenerud. All rights reserved.
|
||||
*
|
||||
* This code may be freely used for non-commercial purposes as long as this
|
||||
* copyright notice remains unaltered in the source code and any binary files
|
||||
* containing this code in compiled form.
|
||||
*
|
||||
* All other lisencing terms must be negotiated with the author
|
||||
* (Karl Stenerud).
|
||||
*
|
||||
* The latest version of this code can be obtained at:
|
||||
* http://kstenerud.cjb.net
|
||||
*/
|
||||
|
||||
/* ======================================================================== */
|
||||
/* ============================= CONFIGURATION ============================ */
|
||||
/* ======================================================================== */
|
||||
|
||||
/* Import the configuration for this build */
|
||||
#include "m68kconf.h"
|
||||
|
||||
|
||||
/* ======================================================================== */
|
||||
/* ============================ GENERAL DEFINES =========================== */
|
||||
|
||||
/* ======================================================================== */
|
||||
|
||||
/* There are 7 levels of interrupt to the 68K.
|
||||
* A transition from < 7 to 7 will cause a non-maskable interrupt (NMI).
|
||||
*/
|
||||
#define M68K_IRQ_NONE 0
|
||||
#define M68K_IRQ_1 1
|
||||
#define M68K_IRQ_2 2
|
||||
#define M68K_IRQ_3 3
|
||||
#define M68K_IRQ_4 4
|
||||
#define M68K_IRQ_5 5
|
||||
#define M68K_IRQ_6 6
|
||||
#define M68K_IRQ_7 7
|
||||
|
||||
|
||||
/* Special interrupt acknowledge values.
|
||||
* Use these as special returns from the interrupt acknowledge callback
|
||||
* (specified later in this header).
|
||||
*/
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
#define M68K_INT_ACK_AUTOVECTOR 0xffffffff
|
||||
|
||||
/* 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 M68K_INT_ACK_SPURIOUS 0xfffffffe
|
||||
|
||||
|
||||
/* CPU types for use in m68k_set_cpu_type() */
|
||||
enum
|
||||
{
|
||||
M68K_CPU_TYPE_INVALID,
|
||||
M68K_CPU_TYPE_68000,
|
||||
M68K_CPU_TYPE_68008,
|
||||
M68K_CPU_TYPE_68010,
|
||||
M68K_CPU_TYPE_68EC020,
|
||||
M68K_CPU_TYPE_68020,
|
||||
M68K_CPU_TYPE_68030, /* Supported by disassembler ONLY */
|
||||
M68K_CPU_TYPE_68040 /* Supported by disassembler ONLY */
|
||||
};
|
||||
|
||||
/* Registers used by m68k_get_reg() and m68k_set_reg() */
|
||||
typedef enum
|
||||
{
|
||||
/* Real registers */
|
||||
M68K_REG_D0, /* Data registers */
|
||||
M68K_REG_D1,
|
||||
M68K_REG_D2,
|
||||
M68K_REG_D3,
|
||||
M68K_REG_D4,
|
||||
M68K_REG_D5,
|
||||
M68K_REG_D6,
|
||||
M68K_REG_D7,
|
||||
M68K_REG_A0, /* Address registers */
|
||||
M68K_REG_A1,
|
||||
M68K_REG_A2,
|
||||
M68K_REG_A3,
|
||||
M68K_REG_A4,
|
||||
M68K_REG_A5,
|
||||
M68K_REG_A6,
|
||||
M68K_REG_A7,
|
||||
M68K_REG_PC, /* Program Counter */
|
||||
M68K_REG_SR, /* Status Register */
|
||||
M68K_REG_SP, /* The current Stack Pointer (located in A7) */
|
||||
M68K_REG_USP, /* User Stack Pointer */
|
||||
M68K_REG_ISP, /* Interrupt Stack Pointer */
|
||||
M68K_REG_MSP, /* Master Stack Pointer */
|
||||
M68K_REG_SFC, /* Source Function Code */
|
||||
M68K_REG_DFC, /* Destination Function Code */
|
||||
M68K_REG_VBR, /* Vector Base Register */
|
||||
M68K_REG_CACR, /* Cache Control Register */
|
||||
M68K_REG_CAAR, /* Cache Address Register */
|
||||
|
||||
/* Assumed registers */
|
||||
/* These are cheat registers which emulate the 1-longword prefetch
|
||||
* present in the 68000 and 68010.
|
||||
*/
|
||||
M68K_REG_PREF_ADDR, /* Last prefetch address */
|
||||
M68K_REG_PREF_DATA, /* Last prefetch data */
|
||||
|
||||
/* Convenience registers */
|
||||
M68K_REG_PPC, /* Previous value in the program counter */
|
||||
M68K_REG_IR, /* Instruction register */
|
||||
M68K_REG_CPU_TYPE /* Type of CPU being run */
|
||||
} m68k_register_t;
|
||||
|
||||
/* ======================================================================== */
|
||||
/* ====================== FUNCTIONS CALLED BY THE CPU ===================== */
|
||||
/* ======================================================================== */
|
||||
|
||||
/* You will have to implement these functions */
|
||||
|
||||
/* read/write functions called by the CPU to access memory.
|
||||
* while values used are 32 bits, only the appropriate number
|
||||
* of bits are relevant (i.e. in write_memory_8, only the lower 8 bits
|
||||
* of value should be written to memory).
|
||||
*
|
||||
* NOTE: I have separated the immediate and PC-relative memory fetches
|
||||
* from the other memory fetches because some systems require
|
||||
* differentiation between PROGRAM and DATA fetches (usually
|
||||
* for security setups such as encryption).
|
||||
* This separation can either be achieved by setting
|
||||
* M68K_SEPARATE_READS in m68kconf.h and defining
|
||||
* the read functions, or by setting M68K_EMULATE_FC and
|
||||
* making a function code callback function.
|
||||
* Using the callback offers better emulation coverage
|
||||
* because you can also monitor whether the CPU is in SYSTEM or
|
||||
* USER mode, but it is also slower.
|
||||
*/
|
||||
|
||||
/* Read from anywhere */
|
||||
unsigned int m68k_read_memory_8(unsigned int address);
|
||||
unsigned int m68k_read_memory_16(unsigned int address);
|
||||
unsigned int m68k_read_memory_32(unsigned int address);
|
||||
|
||||
/* Read data immediately following the PC */
|
||||
unsigned int m68k_read_immediate_16(unsigned int address);
|
||||
unsigned int m68k_read_immediate_32(unsigned int address);
|
||||
|
||||
/* Read data relative to the PC */
|
||||
unsigned int m68k_read_pcrelative_8(unsigned int address);
|
||||
unsigned int m68k_read_pcrelative_16(unsigned int address);
|
||||
unsigned int m68k_read_pcrelative_32(unsigned int address);
|
||||
|
||||
/* Memory access for the disassembler */
|
||||
unsigned int m68k_read_disassembler_8 (unsigned int address);
|
||||
unsigned int m68k_read_disassembler_16 (unsigned int address);
|
||||
unsigned int m68k_read_disassembler_32 (unsigned int address);
|
||||
|
||||
/* Write to anywhere */
|
||||
void m68k_write_memory_8(unsigned int address, unsigned int value);
|
||||
void m68k_write_memory_16(unsigned int address, unsigned int value);
|
||||
void m68k_write_memory_32(unsigned int address, unsigned int value);
|
||||
|
||||
/* Special call to simulate undocumented 68k behavior when move.l with a
|
||||
* predecrement destination mode is executed.
|
||||
* To simulate real 68k behavior, first write the high word to
|
||||
* [address+2], and then write the low word to [address].
|
||||
*
|
||||
* Enable this functionality with M68K_SIMULATE_PD_WRITES in m68kconf.h.
|
||||
*/
|
||||
void m68k_write_memory_32_pd(unsigned int address, unsigned int value);
|
||||
|
||||
|
||||
|
||||
/* ======================================================================== */
|
||||
/* ============================== CALLBACKS =============================== */
|
||||
/* ======================================================================== */
|
||||
|
||||
/* These functions allow you to set callbacks to the host when specific events
|
||||
* occur. Note that you must enable the corresponding value in m68kconf.h
|
||||
* in order for these to do anything useful.
|
||||
* Note: I have defined default callbacks which are used if you have enabled
|
||||
* the corresponding #define in m68kconf.h but either haven't assigned a
|
||||
* callback or have assigned a callback of NULL.
|
||||
*/
|
||||
|
||||
/* Set the callback for an interrupt acknowledge.
|
||||
* You must enable M68K_EMULATE_INT_ACK in m68kconf.h.
|
||||
* The CPU will call the callback with the interrupt level being acknowledged.
|
||||
* The host program must return either a vector from 0x02-0xff, or one of the
|
||||
* special interrupt acknowledge values specified earlier in this header.
|
||||
* If this is not implemented, the CPU will always assume an autovectored
|
||||
* interrupt, and will automatically clear the interrupt request when it
|
||||
* services the interrupt.
|
||||
* Default behavior: return M68K_INT_ACK_AUTOVECTOR.
|
||||
*/
|
||||
void m68k_set_int_ack_callback(int (*callback)(int int_level));
|
||||
|
||||
|
||||
/* Set the callback for a breakpoint acknowledge (68010+).
|
||||
* You must enable M68K_EMULATE_BKPT_ACK in m68kconf.h.
|
||||
* The CPU will call the callback with whatever was in the data field of the
|
||||
* BKPT instruction for 68020+, or 0 for 68010.
|
||||
* Default behavior: do nothing.
|
||||
*/
|
||||
void m68k_set_bkpt_ack_callback(void (*callback)(unsigned int data));
|
||||
|
||||
|
||||
/* Set the callback for the RESET instruction.
|
||||
* You must enable M68K_EMULATE_RESET in m68kconf.h.
|
||||
* The CPU calls this callback every time it encounters a RESET instruction.
|
||||
* Default behavior: do nothing.
|
||||
*/
|
||||
void m68k_set_reset_instr_callback(void (*callback)(void));
|
||||
|
||||
|
||||
/* Set the callback for the CMPI.L #v, Dn instruction.
|
||||
* You must enable M68K_CMPILD_HAS_CALLBACK in m68kconf.h.
|
||||
* The CPU calls this callback every time it encounters a CMPI.L #v, Dn instruction.
|
||||
* Default behavior: do nothing.
|
||||
*/
|
||||
void m68k_set_cmpild_instr_callback(void (*callback)(unsigned int val, int reg));
|
||||
|
||||
|
||||
/* Set the callback for the RTE instruction.
|
||||
* You must enable M68K_RTE_HAS_CALLBACK in m68kconf.h.
|
||||
* The CPU calls this callback every time it encounters a RTE instruction.
|
||||
* Default behavior: do nothing.
|
||||
*/
|
||||
void m68k_set_rte_instr_callback(void (*callback)(void));
|
||||
|
||||
|
||||
/* Set the callback for informing of a large PC change.
|
||||
* You must enable M68K_MONITOR_PC in m68kconf.h.
|
||||
* The CPU calls this callback with the new PC value every time the PC changes
|
||||
* by a large value (currently set for changes by longwords).
|
||||
* Default behavior: do nothing.
|
||||
*/
|
||||
void m68k_set_pc_changed_callback(void (*callback)(unsigned int new_pc));
|
||||
|
||||
|
||||
/* Set the callback for CPU function code changes.
|
||||
* You must enable M68K_EMULATE_FC in m68kconf.h.
|
||||
* The CPU calls this callback with the function code before every memory
|
||||
* access to set the CPU's function code according to what kind of memory
|
||||
* access it is (supervisor/user, program/data and such).
|
||||
* Default behavior: do nothing.
|
||||
*/
|
||||
void m68k_set_fc_callback(void (*callback)(unsigned int new_fc));
|
||||
|
||||
|
||||
/* Set a callback for the instruction cycle of the CPU.
|
||||
* You must enable M68K_INSTRUCTION_HOOK in m68kconf.h.
|
||||
* The CPU calls this callback just before fetching the opcode in the
|
||||
* instruction cycle.
|
||||
* Default behavior: do nothing.
|
||||
*/
|
||||
void m68k_set_instr_hook_callback(void (*callback)(void));
|
||||
|
||||
|
||||
|
||||
/* ======================================================================== */
|
||||
/* ====================== FUNCTIONS TO ACCESS THE CPU ===================== */
|
||||
/* ======================================================================== */
|
||||
|
||||
/* Use this function to set the CPU type you want to emulate.
|
||||
* Currently supported types are: M68K_CPU_TYPE_68000, M68K_CPU_TYPE_68008,
|
||||
* M68K_CPU_TYPE_68010, M68K_CPU_TYPE_EC020, and M68K_CPU_TYPE_68020.
|
||||
*/
|
||||
void m68k_set_cpu_type(unsigned int cpu_type);
|
||||
|
||||
/* Do whatever initialisations the core requires. Should be called
|
||||
* at least once at init time.
|
||||
*/
|
||||
void m68k_init(void);
|
||||
|
||||
/* Pulse the RESET pin on the CPU.
|
||||
* You *MUST* reset the CPU at least once to initialize the emulation
|
||||
* Note: If you didn't call m68k_set_cpu_type() before resetting
|
||||
* the CPU for the first time, the CPU will be set to
|
||||
* M68K_CPU_TYPE_68000.
|
||||
*/
|
||||
void m68k_pulse_reset(void);
|
||||
|
||||
/* execute num_cycles worth of instructions. returns number of cycles used */
|
||||
int m68k_execute(int num_cycles);
|
||||
|
||||
/* These functions let you read/write/modify the number of cycles left to run
|
||||
* while m68k_execute() is running.
|
||||
* These are useful if the 68k accesses a memory-mapped port on another device
|
||||
* that requires immediate processing by another CPU.
|
||||
*/
|
||||
int m68k_cycles_run(void); /* Number of cycles run so far */
|
||||
int m68k_cycles_remaining(void); /* Number of cycles left */
|
||||
void m68k_modify_timeslice(int cycles); /* Modify cycles left */
|
||||
void m68k_end_timeslice(void); /* End timeslice now */
|
||||
|
||||
/* Set the IPL0-IPL2 pins on the CPU (IRQ).
|
||||
* A transition from < 7 to 7 will cause a non-maskable interrupt (NMI).
|
||||
* Setting IRQ to 0 will clear an interrupt request.
|
||||
*/
|
||||
void m68k_set_irq(unsigned int int_level);
|
||||
|
||||
|
||||
/* Halt the CPU as if you pulsed the HALT pin. */
|
||||
void m68k_pulse_halt(void);
|
||||
|
||||
|
||||
/* Context switching to allow multiple CPUs */
|
||||
|
||||
/* Get the size of the cpu context in bytes */
|
||||
unsigned int m68k_context_size(void);
|
||||
|
||||
/* Get a cpu context */
|
||||
unsigned int m68k_get_context(void* dst);
|
||||
|
||||
/* set the current cpu context */
|
||||
void m68k_set_context(void* dst);
|
||||
|
||||
/* Register the CPU state information */
|
||||
void m68k_state_register(const char *type, int index);
|
||||
|
||||
|
||||
/* Peek at the internals of a CPU context. This can either be a context
|
||||
* retrieved using m68k_get_context() or the currently running context.
|
||||
* If context is NULL, the currently running CPU context will be used.
|
||||
*/
|
||||
unsigned int m68k_get_reg(void* context, m68k_register_t reg);
|
||||
|
||||
/* Poke values into the internals of the currently running CPU context */
|
||||
void m68k_set_reg(m68k_register_t reg, unsigned int value);
|
||||
|
||||
/* Check if an instruction is valid for the specified CPU type */
|
||||
unsigned int m68k_is_valid_instruction(unsigned int instruction, unsigned int cpu_type);
|
||||
|
||||
/* Disassemble 1 instruction using the epecified CPU type at pc. Stores
|
||||
* disassembly in str_buff and returns the size of the instruction in bytes.
|
||||
*/
|
||||
unsigned int m68k_disassemble(char* str_buff, unsigned int pc, unsigned int cpu_type);
|
||||
|
||||
/* Same as above but accepts raw opcode data directly rather than fetching
|
||||
* via the read/write interfaces.
|
||||
*/
|
||||
unsigned int m68k_disassemble_raw(char* str_buff, unsigned int pc, unsigned char* opdata, unsigned char* argdata, int length, unsigned int cpu_type);
|
||||
|
||||
|
||||
/* ======================================================================== */
|
||||
/* ============================== MAME STUFF ============================== */
|
||||
/* ======================================================================== */
|
||||
|
||||
#if M68K_COMPILE_FOR_MAME == OPT_ON
|
||||
#include "m68kmame.h"
|
||||
#endif /* M68K_COMPILE_FOR_MAME */
|
||||
|
||||
|
||||
/* ======================================================================== */
|
||||
/* ============================== END OF FILE ============================= */
|
||||
/* ======================================================================== */
|
||||
|
||||
#endif /* M68K__HEADER */
|
10636
cpu/musashi/m68k_in.c
Normal file
10636
cpu/musashi/m68k_in.c
Normal file
File diff suppressed because it is too large
Load diff
203
cpu/musashi/m68kconf.h
Normal file
203
cpu/musashi/m68kconf.h
Normal file
|
@ -0,0 +1,203 @@
|
|||
/* ======================================================================== */
|
||||
/* ========================= LICENSING & COPYRIGHT ======================== */
|
||||
/* ======================================================================== */
|
||||
/*
|
||||
* MUSASHI
|
||||
* Version 3.3
|
||||
*
|
||||
* A portable Motorola M680x0 processor emulation engine.
|
||||
* Copyright 1998-2001 Karl Stenerud. All rights reserved.
|
||||
*
|
||||
* This code may be freely used for non-commercial purposes as long as this
|
||||
* copyright notice remains unaltered in the source code and any binary files
|
||||
* containing this code in compiled form.
|
||||
*
|
||||
* All other lisencing terms must be negotiated with the author
|
||||
* (Karl Stenerud).
|
||||
*
|
||||
* The latest version of this code can be obtained at:
|
||||
* http://kstenerud.cjb.net
|
||||
*/
|
||||
|
||||
|
||||
// notaz: kill some stupid VC warnings
|
||||
#ifndef __GNUC__
|
||||
#pragma warning (disable:4100) // unreferenced formal parameter
|
||||
#pragma warning (disable:4127) // conditional expression is constant
|
||||
#pragma warning (disable:4245) // type conversion
|
||||
#pragma warning (disable:4514) // unreferenced inline function has been removed
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef M68KCONF__HEADER
|
||||
#define M68KCONF__HEADER
|
||||
|
||||
|
||||
/* Configuration switches.
|
||||
* Use OPT_SPECIFY_HANDLER for configuration options that allow callbacks.
|
||||
* OPT_SPECIFY_HANDLER causes the core to link directly to the function
|
||||
* or macro you specify, rather than using callback functions whose pointer
|
||||
* must be passed in using m68k_set_xxx_callback().
|
||||
*/
|
||||
#define OPT_OFF 0
|
||||
#define OPT_ON 1
|
||||
#define OPT_SPECIFY_HANDLER 2
|
||||
|
||||
|
||||
/* ======================================================================== */
|
||||
/* ============================== MAME STUFF ============================== */
|
||||
/* ======================================================================== */
|
||||
|
||||
/* If you're compiling this for MAME, only change M68K_COMPILE_FOR_MAME
|
||||
* to OPT_ON and use m68kmame.h to configure the 68k core.
|
||||
*/
|
||||
#ifndef M68K_COMPILE_FOR_MAME
|
||||
#define M68K_COMPILE_FOR_MAME OPT_OFF
|
||||
#endif /* M68K_COMPILE_FOR_MAME */
|
||||
|
||||
|
||||
#if M68K_COMPILE_FOR_MAME == OPT_OFF
|
||||
|
||||
|
||||
/* ======================================================================== */
|
||||
/* ============================= CONFIGURATION ============================ */
|
||||
/* ======================================================================== */
|
||||
|
||||
/* Turn ON if you want to use the following M68K variants */
|
||||
#define M68K_EMULATE_008 OPT_OFF
|
||||
#define M68K_EMULATE_010 OPT_OFF
|
||||
#define M68K_EMULATE_EC020 OPT_OFF
|
||||
#define M68K_EMULATE_020 OPT_OFF
|
||||
#define M68K_EMULATE_040 OPT_OFF
|
||||
|
||||
|
||||
/* If ON, the CPU will call m68k_read_immediate_xx() for immediate addressing
|
||||
* and m68k_read_pcrelative_xx() for PC-relative addressing.
|
||||
* If off, all read requests from the CPU will be redirected to m68k_read_xx()
|
||||
*/
|
||||
#define M68K_SEPARATE_READS OPT_ON
|
||||
|
||||
/* If ON, the CPU will call m68k_write_32_pd() when it executes move.l with a
|
||||
* predecrement destination EA mode instead of m68k_write_32().
|
||||
* To simulate real 68k behavior, m68k_write_32_pd() must first write the high
|
||||
* word to [address+2], and then write the low word to [address].
|
||||
*/
|
||||
#define M68K_SIMULATE_PD_WRITES OPT_OFF
|
||||
|
||||
/* If ON, CPU will call the interrupt acknowledge callback when it services an
|
||||
* interrupt.
|
||||
* If off, all interrupts will be autovectored and all interrupt requests will
|
||||
* auto-clear when the interrupt is serviced.
|
||||
*/
|
||||
#define M68K_EMULATE_INT_ACK OPT_ON
|
||||
#define M68K_INT_ACK_CALLBACK(A) your_int_ack_handler_function(A)
|
||||
|
||||
|
||||
/* If ON, CPU will call the breakpoint acknowledge callback when it encounters
|
||||
* a breakpoint instruction and it is running a 68010+.
|
||||
*/
|
||||
#define M68K_EMULATE_BKPT_ACK OPT_OFF
|
||||
#define M68K_BKPT_ACK_CALLBACK() your_bkpt_ack_handler_function()
|
||||
|
||||
|
||||
/* If ON, the CPU will monitor the trace flags and take trace exceptions
|
||||
*/
|
||||
#define M68K_EMULATE_TRACE OPT_OFF
|
||||
|
||||
|
||||
/* If ON, CPU will call the output reset callback when it encounters a reset
|
||||
* instruction.
|
||||
*/
|
||||
#define M68K_EMULATE_RESET OPT_OFF
|
||||
#define M68K_RESET_CALLBACK() your_reset_handler_function()
|
||||
|
||||
|
||||
/* If ON, CPU will call the callback when it encounters a cmpi.l #v, dn
|
||||
* instruction.
|
||||
*/
|
||||
#define M68K_CMPILD_HAS_CALLBACK OPT_OFF
|
||||
#define M68K_CMPILD_CALLBACK(v,r) your_cmpild_handler_function(v,r)
|
||||
|
||||
|
||||
/* If ON, CPU will call the callback when it encounters a rte
|
||||
* instruction.
|
||||
*/
|
||||
#define M68K_RTE_HAS_CALLBACK OPT_OFF
|
||||
#define M68K_RTE_CALLBACK() your_rte_handler_function()
|
||||
|
||||
|
||||
/* If ON, CPU will call the set fc callback on every memory access to
|
||||
* differentiate between user/supervisor, program/data access like a real
|
||||
* 68000 would. This should be enabled and the callback should be set if you
|
||||
* want to properly emulate the m68010 or higher. (moves uses function codes
|
||||
* to read/write data from different address spaces)
|
||||
*/
|
||||
#define M68K_EMULATE_FC OPT_OFF
|
||||
#define M68K_SET_FC_CALLBACK(A) your_set_fc_handler_function(A)
|
||||
|
||||
|
||||
/* If ON, CPU will call the pc changed callback when it changes the PC by a
|
||||
* large value. This allows host programs to be nicer when it comes to
|
||||
* fetching immediate data and instructions on a banked memory system.
|
||||
*/
|
||||
#define M68K_MONITOR_PC OPT_OFF
|
||||
#define M68K_SET_PC_CALLBACK(A) your_pc_changed_handler_function(A)
|
||||
|
||||
|
||||
/* If ON, CPU will call the instruction hook callback before every
|
||||
* instruction.
|
||||
*/
|
||||
#define M68K_INSTRUCTION_HOOK OPT_OFF
|
||||
#define M68K_INSTRUCTION_CALLBACK() your_instruction_hook_function()
|
||||
|
||||
|
||||
/* If ON, the CPU will emulate the 4-byte prefetch queue of a real 68000 */
|
||||
#define M68K_EMULATE_PREFETCH OPT_OFF
|
||||
|
||||
|
||||
/* If ON, the CPU will generate address error exceptions if it tries to
|
||||
* access a word or longword at an odd address.
|
||||
* NOTE: This is only emulated properly for 68000 mode.
|
||||
*/
|
||||
#define M68K_EMULATE_ADDRESS_ERROR OPT_OFF
|
||||
|
||||
|
||||
/* Turn ON to enable logging of illegal instruction calls.
|
||||
* M68K_LOG_FILEHANDLE must be #defined to a stdio file stream.
|
||||
* Turn on M68K_LOG_1010_1111 to log all 1010 and 1111 calls.
|
||||
*/
|
||||
#define M68K_LOG_ENABLE OPT_OFF
|
||||
#define M68K_LOG_1010_1111 OPT_OFF
|
||||
#define M68K_LOG_FILEHANDLE some_file_handle
|
||||
|
||||
|
||||
/* ----------------------------- COMPATIBILITY ---------------------------- */
|
||||
|
||||
/* The following options set optimizations that violate the current ANSI
|
||||
* standard, but will be compliant under the forthcoming C9X standard.
|
||||
*/
|
||||
|
||||
|
||||
/* If ON, the enulation core will use 64-bit integers to speed up some
|
||||
* operations.
|
||||
*/
|
||||
#define M68K_USE_64_BIT OPT_OFF
|
||||
|
||||
|
||||
/* Set to your compiler's static inline keyword to enable it, or
|
||||
* set it to blank to disable it.
|
||||
* If you define INLINE in the makefile, it will override this value.
|
||||
* NOTE: not enabling inline functions will SEVERELY slow down emulation.
|
||||
*/
|
||||
#ifndef INLINE
|
||||
#define INLINE static __inline
|
||||
#endif /* INLINE */
|
||||
|
||||
#endif /* M68K_COMPILE_FOR_MAME */
|
||||
|
||||
|
||||
/* ======================================================================== */
|
||||
/* ============================== END OF FILE ============================= */
|
||||
/* ======================================================================== */
|
||||
|
||||
#endif /* M68KCONF__HEADER */
|
1015
cpu/musashi/m68kcpu.c
Normal file
1015
cpu/musashi/m68kcpu.c
Normal file
File diff suppressed because it is too large
Load diff
2022
cpu/musashi/m68kcpu.h
Normal file
2022
cpu/musashi/m68kcpu.h
Normal file
File diff suppressed because it is too large
Load diff
3604
cpu/musashi/m68kdasm.c
Normal file
3604
cpu/musashi/m68kdasm.c
Normal file
File diff suppressed because it is too large
Load diff
1484
cpu/musashi/m68kmake.c
Normal file
1484
cpu/musashi/m68kmake.c
Normal file
File diff suppressed because it is too large
Load diff
12199
cpu/musashi/m68kopac.c
Normal file
12199
cpu/musashi/m68kopac.c
Normal file
File diff suppressed because it is too large
Load diff
13385
cpu/musashi/m68kopdm.c
Normal file
13385
cpu/musashi/m68kopdm.c
Normal file
File diff suppressed because it is too large
Load diff
8878
cpu/musashi/m68kopnz.c
Normal file
8878
cpu/musashi/m68kopnz.c
Normal file
File diff suppressed because it is too large
Load diff
2093
cpu/musashi/m68kops.c
Normal file
2093
cpu/musashi/m68kops.c
Normal file
File diff suppressed because it is too large
Load diff
1986
cpu/musashi/m68kops.h
Normal file
1986
cpu/musashi/m68kops.h
Normal file
File diff suppressed because it is too large
Load diff
315
cpu/musashi/readme.txt
Normal file
315
cpu/musashi/readme.txt
Normal file
|
@ -0,0 +1,315 @@
|
|||
MUSASHI
|
||||
=======
|
||||
|
||||
Version 3.3
|
||||
|
||||
A portable Motorola M680x0 processor emulation engine.
|
||||
Copyright 1998-2001 Karl Stenerud. All rights reserved.
|
||||
|
||||
|
||||
|
||||
INTRODUCTION:
|
||||
------------
|
||||
|
||||
Musashi is a Motorola 68000, 68010, 68EC020, and 68020 emulator written in C.
|
||||
This emulator was written with two goals in mind: portability and speed.
|
||||
|
||||
The emulator is written to ANSI C specifications with the exception that I use
|
||||
inline functions. This is not compliant to the ANSI spec, but will be
|
||||
compliant to the ANSI C9X spec.
|
||||
|
||||
It has been successfully running in the MAME project (www.mame.net) for over 2
|
||||
years and so has had time to mature.
|
||||
|
||||
|
||||
|
||||
LICENSE AND COPYRIGHT:
|
||||
---------------------
|
||||
|
||||
The Musashi M680x0 emulator is copyright 1998-2001 Karl Stenerud.
|
||||
|
||||
The source code included in this archive is provided AS-IS, free for any
|
||||
non-commercial purpose.
|
||||
|
||||
If you build a program using this core, please give credit to the author.
|
||||
|
||||
If you wish to use this core in a commercial environment, please contact
|
||||
the author to discuss commercial licensing.
|
||||
|
||||
|
||||
|
||||
AVAILABILITY:
|
||||
------------
|
||||
The latest version of this code can be obtained at:
|
||||
http://kstenerud.cjb.net
|
||||
|
||||
|
||||
|
||||
CONTACTING THE AUTHOR:
|
||||
---------------------
|
||||
I can be reached at kstenerud@mame.net
|
||||
|
||||
|
||||
|
||||
BASIC CONFIGURATION:
|
||||
-------------------
|
||||
The basic configuration will give you a standard 68000 that has sufficient
|
||||
functionality to work in a primitive environment.
|
||||
|
||||
This setup assumes that you only have 1 device interrupting it, that the
|
||||
device will always request an autovectored interrupt, and it will always clear
|
||||
the interrupt before the interrupt service routine finishes (but could
|
||||
possibly re-assert the interrupt).
|
||||
You will have only one address space, no tracing, and no instruction prefetch.
|
||||
|
||||
To implement the basic configuration:
|
||||
|
||||
- Open m68kconf.h and verify that the settings for INLINE and DECL_SPEC will
|
||||
work with your compiler. (They are set for gcc)
|
||||
|
||||
- In your host program, implement the following functions:
|
||||
unsigned int m68k_read_memory_8(unsigned int address);
|
||||
unsigned int m68k_read_memory_16(unsigned int address);
|
||||
unsigned int m68k_read_memory_32(unsigned int address);
|
||||
void m68k_write_memory_8(unsigned int address, unsigned int value);
|
||||
void m68k_write_memory_16(unsigned int address, unsigned int value);
|
||||
void m68k_write_memory_32(unsigned int address, unsigned int value);
|
||||
|
||||
- In your host program, be sure to call m68k_pulse_reset() once before calling
|
||||
any of the other functions as this initializes the core.
|
||||
|
||||
- Use m68k_execute() to execute instructions and m68k_set_irq() to cause an
|
||||
interrupt.
|
||||
|
||||
|
||||
|
||||
ADDING PROPER INTERRUPT HANDLING:
|
||||
--------------------------------
|
||||
The interrupt handling in the basic configuration doesn't emulate the
|
||||
interrupt acknowledge phase of the CPU and automatically clears an interrupt
|
||||
request during interrupt processing.
|
||||
While this works for most systems, you may need more accurate interrupt
|
||||
handling.
|
||||
|
||||
To add proper interrupt handling:
|
||||
|
||||
- In m68kconf.h, set M68K_EMULATE_INT_ACK to OPT_SPECIFY_HANDLER
|
||||
|
||||
- In m68kconf.h, set M68K_INT_ACK_CALLBACK(A) to your interrupt acknowledge
|
||||
routine
|
||||
|
||||
- Your interrupt acknowledge routine must return an interrupt vector,
|
||||
M68K_INT_ACK_AUTOVECTOR, or M68K_INT_ACK_SPURIOUS. most m68k
|
||||
implementations just use autovectored interrupts.
|
||||
|
||||
- When the interrupting device is satisfied, you must call m68k_set_irq(0) to
|
||||
remove the interrupt request.
|
||||
|
||||
|
||||
|
||||
MULTIPLE INTERRUPTS:
|
||||
-------------------
|
||||
The above system will work if you have only one device interrupting the CPU,
|
||||
but if you have more than one device, you must do a bit more.
|
||||
|
||||
To add multiple interrupts:
|
||||
|
||||
- You must make an interrupt arbitration device that will take the highest
|
||||
priority interrupt and encode it onto the IRQ pins on the CPU.
|
||||
|
||||
- The interrupt arbitration device should use m68k_set_irq() to set the
|
||||
highest pending interrupt, or 0 for no interrupts pending.
|
||||
|
||||
|
||||
|
||||
SEPARATE IMMEDIATE AND PC-RELATIVE READS:
|
||||
----------------------------------------
|
||||
You can write faster memory access functions if you know whether you are
|
||||
fetching from ROM or RAM. Immediate reads are always from the program space
|
||||
(Always in ROM unless it is running self-modifying code).
|
||||
This will also separate the pc-relative reads, since some systems treat
|
||||
PROGRAM mode reads and DATA mode reads differently (for program encryption,
|
||||
for instance). See the section below (ADDRESS SPACE) for an explanation of
|
||||
PROGRAM and DATA mode.
|
||||
|
||||
To enable separate reads:
|
||||
|
||||
- In m68kconf.h, turn on M68K_SEPARATE_READS.
|
||||
|
||||
- In your host program, implement the following functions:
|
||||
unsigned int m68k_read_immediate_16(unsigned int address);
|
||||
unsigned int m68k_read_immediate_32(unsigned int address);
|
||||
|
||||
unsigned int m68k_read_pcrelative_8(unsigned int address);
|
||||
unsigned int m68k_read_pcrelative_16(unsigned int address);
|
||||
unsigned int m68k_read_pcrelative_32(unsigned int address);
|
||||
|
||||
- If you need to know the current PC (for banking and such), set
|
||||
M68K_MONITOR_PC to OPT_SPECIFY_HANDLER, and set M68K_SET_PC_CALLBACK(A) to
|
||||
your routine.
|
||||
|
||||
|
||||
|
||||
ADDRESS SPACES:
|
||||
--------------
|
||||
Most systems will only implement one address space, placing ROM at the lower
|
||||
addresses and RAM at the higher. However, there is the possibility that a
|
||||
system will implement ROM and RAM in the same address range, but in different
|
||||
address spaces, or will have different mamory types that require different
|
||||
handling for the program and the data.
|
||||
|
||||
The 68k accomodates this by allowing different program spaces, the most
|
||||
important to us being PROGRAM and DATA space. Here is a breakdown of
|
||||
how information is fetched:
|
||||
|
||||
- All immediate reads are fetched from PROGRAM space.
|
||||
|
||||
- All PC-relative reads are fetched from PROGRAM space.
|
||||
|
||||
- The initial stack pointer and program counter are fetched from PROGRAM space.
|
||||
|
||||
- All other reads (except for those from the moves instruction for 68020)
|
||||
are fetched from DATA space.
|
||||
|
||||
The m68k deals with this by encoding the requested address space on the
|
||||
function code pins:
|
||||
|
||||
FC
|
||||
Address Space 210
|
||||
------------------ ---
|
||||
USER DATA 001
|
||||
USER PROGRAM 010
|
||||
SUPERVISOR DATA 101
|
||||
SUPERVISOR PROGRAM 110
|
||||
CPU SPACE 111 <-- not emulated in this core since we emulate
|
||||
interrupt acknowledge in another way.
|
||||
|
||||
Problems arise here if you need to emulate this distinction (if, for example,
|
||||
your ROM and RAM are at the same address range, with RAM and ROM enable
|
||||
wired to the function code pins).
|
||||
|
||||
There are 2 ways to deal with this situation using Musashi:
|
||||
|
||||
1. If you only need the distinction between PROGRAM and DATA (the most common),
|
||||
you can just separate the reads (see the preceeding section). This is the
|
||||
faster solution.
|
||||
|
||||
2. You can emulate the function code pins entirely.
|
||||
|
||||
To emulate the function code pins:
|
||||
|
||||
- In m68kconf.h, set M68K_EMULATE_FC to OPT_SPECIFY_HANDLER and set
|
||||
M68K_SET_FC_CALLBACK(A) to your function code handler function.
|
||||
|
||||
- Your function code handler should select the proper address space for
|
||||
subsequent calls to m68k_read_xx (and m68k_write_xx for 68010+).
|
||||
|
||||
Note: immediate reads are always done from program space, so technically you
|
||||
don't need to implement the separate immediate reads, although you could
|
||||
gain more speed improvements leaving them in and doing some clever
|
||||
programming.
|
||||
|
||||
|
||||
|
||||
USING DIFFERENT CPU TYPES:
|
||||
-------------------------
|
||||
The default is to enable only the 68000 cpu type. To change this, change the
|
||||
settings for M68K_EMULATE_010 etc in m68kconf.h.
|
||||
|
||||
To set the CPU type you want to use:
|
||||
|
||||
- Make sure it is enabled in m68kconf.h. Current switches are:
|
||||
M68K_EMULATE_010
|
||||
M68K_EMULATE_EC020
|
||||
M68K_EMULATE_020
|
||||
|
||||
- In your host program, call m68k_set_cpu_type() and then call
|
||||
m68k_pulse_reset(). Valid CPU types are:
|
||||
M68K_CPU_TYPE_68000,
|
||||
M68K_CPU_TYPE_68010,
|
||||
M68K_CPU_TYPE_68EC020,
|
||||
M68K_CPU_TYPE_68020
|
||||
|
||||
|
||||
|
||||
CLOCK FREQUENCY:
|
||||
---------------
|
||||
In order to emulate the correct clock frequency, you will have to calculate
|
||||
how long it takes the emulation to execute a certain number of "cycles" and
|
||||
vary your calls to m68k_execute() accordingly.
|
||||
As well, it is a good idea to take away the CPU's timeslice when it writes to
|
||||
a memory-mapped port in order to give the device it wrote to a chance to
|
||||
react.
|
||||
|
||||
You can use the functions m68k_cycles_run(), m68k_cycles_remaining(),
|
||||
m68k_modify_timeslice(), and m68k_end_timeslice() to do this.
|
||||
Try to use large cycle values in your calls to m68k_execute() since it will
|
||||
increase throughput. You can always take away the timeslice later.
|
||||
|
||||
|
||||
|
||||
MORE CORRECT EMULATION:
|
||||
----------------------
|
||||
You may need to enable these in order to properly emulate some of the more
|
||||
obscure functions of the m68k:
|
||||
|
||||
- M68K_EMULATE_BKPT_ACK causes the CPU to call a breakpoint handler on a BKPT
|
||||
instruction
|
||||
|
||||
- M68K_EMULATE_TRACE causes the CPU to generate trace exceptions when the
|
||||
trace bits are set
|
||||
|
||||
- M68K_EMULATE_RESET causes the CPU to call a reset handler on a RESET
|
||||
instruction.
|
||||
|
||||
- M68K_EMULATE_PREFETCH emulates the 4-word instruction prefetch that is part
|
||||
of the 68000/68010 (needed for Amiga emulation).
|
||||
|
||||
- call m68k_pulse_halt() to emulate the HALT pin.
|
||||
|
||||
|
||||
|
||||
CONVENIENCE FUNCTIONS:
|
||||
---------------------
|
||||
These are in here for programmer convenience:
|
||||
|
||||
- M68K_INSTRUCTION_HOOK lets you call a handler before each instruction.
|
||||
|
||||
- M68K_LOG_ENABLE and M68K_LOG_1010_1111 lets you log illegal and A/F-line
|
||||
instructions.
|
||||
|
||||
|
||||
|
||||
MULTIPLE CPU EMULATION:
|
||||
----------------------
|
||||
The default is to use only one CPU. To use more than one CPU in this core,
|
||||
there are some things to keep in mind:
|
||||
|
||||
- To have different cpus call different functions, use OPT_ON instead of
|
||||
OPT_SPECIFY_HANDLER, and use the m68k_set_xxx_callback() functions to set
|
||||
your callback handlers on a per-cpu basis.
|
||||
|
||||
- Be sure to call set_cpu_type() for each CPU you use.
|
||||
|
||||
- Use m68k_set_context() and m68k_get_context() to switch to another CPU.
|
||||
|
||||
|
||||
|
||||
LOAD AND SAVE CPU CONTEXTS FROM DISK:
|
||||
------------------------------------
|
||||
You can use them68k_load_context() and m68k_save_context() functions to load
|
||||
and save the CPU state to disk.
|
||||
|
||||
|
||||
|
||||
GET/SET INFORMATION FROM THE CPU:
|
||||
--------------------------------
|
||||
You can use m68k_get_reg() and m68k_set_reg() to gain access to the internals
|
||||
of the CPU.
|
||||
|
||||
|
||||
|
||||
EXAMPLE:
|
||||
-------
|
||||
|
||||
I have included a file example.zip that contains a full example.
|
13
cpu/mz80/Makefile
Normal file
13
cpu/mz80/Makefile
Normal file
|
@ -0,0 +1,13 @@
|
|||
CFLAGS = -Wno-conversion -Wno-sign-compare # -Wno-pointer-sign
|
||||
|
||||
all : mz80.asm
|
||||
|
||||
mz80.asm : makez80
|
||||
./makez80 -s -l -x86 $@
|
||||
|
||||
makez80 : makez80.o
|
||||
|
||||
|
||||
clean :
|
||||
$(RM) makez80 makez80.o mz80.asm
|
||||
|
18
cpu/mz80/Makefile.win
Normal file
18
cpu/mz80/Makefile.win
Normal file
|
@ -0,0 +1,18 @@
|
|||
all : mz80.obj
|
||||
|
||||
mz80.obj : mz80.asm
|
||||
nasm -f win32 mz80.asm -o $@
|
||||
|
||||
mz80.asm : makez80.exe
|
||||
makez80.exe -s -x86 $@
|
||||
|
||||
makez80.exe : makez80.c
|
||||
cl /DWIN32 /W3 makez80.c
|
||||
|
||||
|
||||
clean : tidy
|
||||
del mz80.obj
|
||||
|
||||
tidy :
|
||||
del mz80.asm makez80.exe makez80.obj
|
||||
|
9512
cpu/mz80/makez80.c
Normal file
9512
cpu/mz80/makez80.c
Normal file
File diff suppressed because it is too large
Load diff
17053
cpu/mz80/mz80.c
Normal file
17053
cpu/mz80/mz80.c
Normal file
File diff suppressed because it is too large
Load diff
394
cpu/mz80/mz80.h
Normal file
394
cpu/mz80/mz80.h
Normal file
|
@ -0,0 +1,394 @@
|
|||
/* Multi-Z80 32 Bit emulator */
|
||||
|
||||
/* Copyright 1996, Neil Bradley, All rights reserved
|
||||
*
|
||||
* License agreement:
|
||||
*
|
||||
* The mZ80 emulator may be distributed in unmodified form to any medium.
|
||||
*
|
||||
* mZ80 May not be sold, or sold as a part of a commercial package without
|
||||
* the express written permission of Neil Bradley (neil@synthcom.com). This
|
||||
* includes shareware.
|
||||
*
|
||||
* Modified versions of mZ80 may not be publicly redistributed without author
|
||||
* approval (neil@synthcom.com). This includes distributing via a publicly
|
||||
* accessible LAN. You may make your own source modifications and distribute
|
||||
* mZ80 in object only form.
|
||||
*
|
||||
* mZ80 Licensing for commercial applications is available. Please email
|
||||
* neil@synthcom.com for details.
|
||||
*
|
||||
* Synthcom Systems, Inc, and Neil Bradley will not be held responsible for
|
||||
* any damage done by the use of mZ80. It is purely "as-is".
|
||||
*
|
||||
* If you use mZ80 in a freeware application, credit in the following text:
|
||||
*
|
||||
* "Multi-Z80 CPU emulator by Neil Bradley (neil@synthcom.com)"
|
||||
*
|
||||
* must accompany the freeware application within the application itself or
|
||||
* in the documentation.
|
||||
*
|
||||
* Legal stuff aside:
|
||||
*
|
||||
* If you find problems with mZ80, please email the author so they can get
|
||||
* resolved. If you find a bug and fix it, please also email the author so
|
||||
* that those bug fixes can be propogated to the installed base of mZ80
|
||||
* users. If you find performance improvements or problems with mZ80, please
|
||||
* email the author with your changes/suggestions and they will be rolled in
|
||||
* with subsequent releases of mZ80.
|
||||
*
|
||||
* The whole idea of this emulator is to have the fastest available 32 bit
|
||||
* Multi-z80 emulator for the PC, giving maximum performance.
|
||||
*/
|
||||
|
||||
/* General z80 based defines */
|
||||
|
||||
#ifndef _MZ80_H_
|
||||
#define _MZ80_H_
|
||||
|
||||
#ifndef UINT32
|
||||
#define UINT32 unsigned long int
|
||||
#endif
|
||||
|
||||
#ifndef UINT16
|
||||
#define UINT16 unsigned short int
|
||||
#endif
|
||||
|
||||
#ifndef UINT8
|
||||
#define UINT8 unsigned char
|
||||
#endif
|
||||
|
||||
#ifndef INT32
|
||||
#define INT32 signed long int
|
||||
#endif
|
||||
|
||||
#ifndef INT16
|
||||
#define INT16 signed short int
|
||||
#endif
|
||||
|
||||
#ifndef INT8
|
||||
#define INT8 signed char
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef _MEMORYREADWRITEBYTE_
|
||||
#define _MEMORYREADWRITEBYTE_
|
||||
|
||||
struct MemoryWriteByte
|
||||
{
|
||||
UINT32 lowAddr;
|
||||
UINT32 highAddr;
|
||||
void (*memoryCall)(UINT32, UINT8, struct MemoryWriteByte *);
|
||||
void *pUserArea;
|
||||
};
|
||||
|
||||
struct MemoryReadByte
|
||||
{
|
||||
UINT32 lowAddr;
|
||||
UINT32 highAddr;
|
||||
UINT8 (*memoryCall)(UINT32, struct MemoryReadByte *);
|
||||
void *pUserArea;
|
||||
};
|
||||
|
||||
#endif // _MEMORYREADWRITEBYTE_
|
||||
|
||||
struct z80PortWrite
|
||||
{
|
||||
UINT16 lowIoAddr;
|
||||
UINT16 highIoAddr;
|
||||
void (*IOCall)(UINT16, UINT8, struct z80PortWrite *);
|
||||
void *pUserArea;
|
||||
};
|
||||
|
||||
struct z80PortRead
|
||||
{
|
||||
UINT16 lowIoAddr;
|
||||
UINT16 highIoAddr;
|
||||
UINT16 (*IOCall)(UINT16, struct z80PortRead *);
|
||||
void *pUserArea;
|
||||
};
|
||||
|
||||
struct z80TrapRec
|
||||
{
|
||||
UINT16 trapAddr;
|
||||
UINT8 skipCnt;
|
||||
UINT8 origIns;
|
||||
};
|
||||
|
||||
typedef union
|
||||
{
|
||||
UINT32 af;
|
||||
|
||||
struct
|
||||
{
|
||||
#ifdef WORDS_BIGENDIAN
|
||||
UINT16 wFiller;
|
||||
UINT8 a;
|
||||
UINT8 f;
|
||||
#else
|
||||
UINT8 f;
|
||||
UINT8 a;
|
||||
UINT16 wFiller;
|
||||
#endif
|
||||
} half;
|
||||
} reg_af;
|
||||
|
||||
#define z80AF z80af.af
|
||||
#define z80A z80af.half.a
|
||||
#define z80F z80af.half.f
|
||||
|
||||
typedef union
|
||||
{
|
||||
UINT32 bc;
|
||||
|
||||
struct
|
||||
{
|
||||
#ifdef WORDS_BIGENDIAN
|
||||
UINT16 wFiller;
|
||||
UINT8 b;
|
||||
UINT8 c;
|
||||
#else
|
||||
UINT8 c;
|
||||
UINT8 b;
|
||||
UINT16 wFiller;
|
||||
#endif
|
||||
} half;
|
||||
} reg_bc;
|
||||
|
||||
#define z80BC z80bc.bc
|
||||
#define z80B z80bc.half.b
|
||||
#define z80C z80bc.half.c
|
||||
|
||||
typedef union
|
||||
{
|
||||
UINT32 de;
|
||||
|
||||
struct
|
||||
{
|
||||
#ifdef WORDS_BIGENDIAN
|
||||
UINT16 wFiller;
|
||||
UINT8 d;
|
||||
UINT8 e;
|
||||
#else
|
||||
UINT8 e;
|
||||
UINT8 d;
|
||||
UINT16 wFiller;
|
||||
#endif
|
||||
} half;
|
||||
} reg_de;
|
||||
|
||||
#define z80DE z80de.de
|
||||
#define z80D z80de.half.d
|
||||
#define z80E z80de.half.e
|
||||
|
||||
typedef union
|
||||
{
|
||||
UINT32 hl;
|
||||
|
||||
struct
|
||||
{
|
||||
#ifdef WORDS_BIGENDIAN
|
||||
UINT16 wFiller;
|
||||
UINT8 h;
|
||||
UINT8 l;
|
||||
#else
|
||||
UINT8 l;
|
||||
UINT8 h;
|
||||
UINT16 wFiller;
|
||||
#endif
|
||||
} half;
|
||||
} reg_hl;
|
||||
|
||||
#define z80HL z80hl.hl
|
||||
#define z80H z80hl.half.h
|
||||
#define z80L z80hl.half.l
|
||||
|
||||
#define z80SP z80sp.sp
|
||||
|
||||
typedef union
|
||||
{
|
||||
UINT32 ix;
|
||||
|
||||
struct
|
||||
{
|
||||
#ifdef WORDS_BIGENDIAN
|
||||
UINT16 wFiller;
|
||||
UINT8 xh;
|
||||
UINT8 xl;
|
||||
#else
|
||||
UINT8 xl;
|
||||
UINT8 xh;
|
||||
UINT16 wFiller;
|
||||
#endif
|
||||
} half;
|
||||
} reg_ix;
|
||||
|
||||
#define z80IX z80ix.ix
|
||||
#define z80XH z80ix.half.xh
|
||||
#define z80XL z80ix.half.xl
|
||||
|
||||
typedef union
|
||||
{
|
||||
UINT32 iy;
|
||||
|
||||
struct
|
||||
{
|
||||
#ifdef WORDS_BIGENDIAN
|
||||
UINT16 wFiller;
|
||||
UINT8 yh;
|
||||
UINT8 yl;
|
||||
#else
|
||||
UINT8 yl;
|
||||
UINT8 yh;
|
||||
UINT16 wFiller;
|
||||
#endif
|
||||
} half;
|
||||
} reg_iy;
|
||||
|
||||
#define z80IY z80iy.iy
|
||||
#define z80YH z80iy.half.yh
|
||||
#define z80YL z80iy.half.yl
|
||||
|
||||
struct mz80context
|
||||
{
|
||||
UINT8 *z80Base;
|
||||
struct MemoryReadByte *z80MemRead;
|
||||
struct MemoryWriteByte *z80MemWrite;
|
||||
struct z80PortRead *z80IoRead;
|
||||
struct z80PortWrite *z80IoWrite;
|
||||
UINT32 z80clockticks;
|
||||
UINT32 z80iff;
|
||||
UINT32 z80interruptMode;
|
||||
UINT32 z80halted;
|
||||
|
||||
reg_af z80af;
|
||||
reg_bc z80bc;
|
||||
reg_de z80de;
|
||||
reg_hl z80hl;
|
||||
UINT32 z80afprime;
|
||||
UINT32 z80bcprime;
|
||||
UINT32 z80deprime;
|
||||
UINT32 z80hlprime;
|
||||
reg_ix z80ix;
|
||||
reg_iy z80iy;
|
||||
UINT32 z80sp;
|
||||
UINT32 z80pc;
|
||||
UINT32 z80nmiAddr;
|
||||
UINT32 z80intAddr;
|
||||
UINT32 z80rCounter;
|
||||
UINT8 z80i;
|
||||
UINT8 z80r;
|
||||
UINT8 z80intPending;
|
||||
};
|
||||
|
||||
// These are the enumerations used for register access. DO NOT ALTER THEIR
|
||||
// ORDER! It must match the same order as in the mz80.c/mz80.asm files!
|
||||
|
||||
enum
|
||||
{
|
||||
#ifndef CPUREG_PC
|
||||
CPUREG_PC = 0,
|
||||
#endif
|
||||
CPUREG_Z80_AF = 1,
|
||||
CPUREG_Z80_BC,
|
||||
CPUREG_Z80_DE,
|
||||
CPUREG_Z80_HL,
|
||||
CPUREG_Z80_AFPRIME,
|
||||
CPUREG_Z80_BCPRIME,
|
||||
CPUREG_Z80_DEPRIME,
|
||||
CPUREG_Z80_HLPRIME,
|
||||
CPUREG_Z80_IX,
|
||||
CPUREG_Z80_IY,
|
||||
CPUREG_Z80_SP,
|
||||
CPUREG_Z80_I,
|
||||
CPUREG_Z80_R,
|
||||
CPUREG_Z80_A,
|
||||
CPUREG_Z80_B,
|
||||
CPUREG_Z80_C,
|
||||
CPUREG_Z80_D,
|
||||
CPUREG_Z80_E,
|
||||
CPUREG_Z80_H,
|
||||
CPUREG_Z80_L,
|
||||
CPUREG_Z80_F,
|
||||
CPUREG_Z80_CARRY,
|
||||
CPUREG_Z80_NEGATIVE,
|
||||
CPUREG_Z80_PARITY,
|
||||
CPUREG_Z80_OVERFLOW,
|
||||
CPUREG_Z80_HALFCARRY,
|
||||
CPUREG_Z80_ZERO,
|
||||
CPUREG_Z80_SIGN,
|
||||
CPUREG_Z80_IFF1,
|
||||
CPUREG_Z80_IFF2,
|
||||
|
||||
// Leave this here!
|
||||
|
||||
CPUREG_Z80_MAX_INDEX
|
||||
};
|
||||
|
||||
extern UINT32 mz80exec(UINT32);
|
||||
extern UINT32 mz80GetContextSize(void);
|
||||
extern UINT32 mz80GetElapsedTicks(UINT32);
|
||||
extern void mz80ReleaseTimeslice(void);
|
||||
extern void mz80GetContext(void *);
|
||||
extern void mz80SetContext(void *);
|
||||
extern void mz80reset(void);
|
||||
extern void mz80ClearPendingInterrupt(void);
|
||||
extern UINT32 mz80int(UINT32);
|
||||
extern UINT32 mz80nmi(void);
|
||||
extern void mz80init(void);
|
||||
extern void mz80shutdown(void);
|
||||
extern UINT32 z80intAddr;
|
||||
extern UINT32 z80nmiAddr;
|
||||
|
||||
// Debugger useful routines
|
||||
|
||||
extern UINT8 mz80SetRegisterValue(void *, UINT32, UINT32);
|
||||
extern UINT32 mz80GetRegisterValue(void *, UINT32);
|
||||
extern UINT32 mz80GetRegisterTextValue(void *, UINT32, UINT8 *);
|
||||
extern UINT8 *mz80GetRegisterName(UINT32);
|
||||
|
||||
// Memory/IO read/write commands
|
||||
|
||||
#ifndef VALUE_BYTE
|
||||
#define VALUE_BYTE 0
|
||||
#endif
|
||||
|
||||
#ifndef VALUE_WORD
|
||||
#define VALUE_WORD 1
|
||||
#endif
|
||||
|
||||
#ifndef VALUE_DWORD
|
||||
#define VALUE_DWORD 2
|
||||
#endif
|
||||
|
||||
#ifndef VALUE_IO
|
||||
#define VALUE_IO 3
|
||||
#endif
|
||||
|
||||
extern void mz80WriteValue(UINT8 bWhat, UINT32 dwAddr, UINT32 dwData);
|
||||
extern UINT32 mz80ReadValue(UINT8 bWhat, UINT32 dwAddr);
|
||||
|
||||
// Flag definitions
|
||||
|
||||
#define Z80_FLAG_CARRY 0x01
|
||||
#define Z80_FLAG_NEGATIVE 0x02
|
||||
#define Z80_FLAG_OVERFLOW_PARITY 0x04
|
||||
#define Z80_FLAG_UNDEFINED1 0x08
|
||||
#define Z80_FLAG_HALF_CARRY 0x10
|
||||
#define Z80_FLAG_UNDEFINED2 0x20
|
||||
#define Z80_FLAG_ZERO 0x40
|
||||
#define Z80_FLAG_SIGN 0x80
|
||||
|
||||
#define IFF1 0x01
|
||||
#define IFF2 0x02
|
||||
|
||||
typedef struct mz80context CONTEXTMZ80;
|
||||
|
||||
#ifdef __cplusplus
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif // _MZ80_H_
|
809
cpu/mz80/mz80.txt
Normal file
809
cpu/mz80/mz80.txt
Normal file
|
@ -0,0 +1,809 @@
|
|||
Multi-Z80 32 Bit emulator
|
||||
Copyright 1996, 1997, 1998, 1999, 2000 - Neil Bradley, All rights reserved
|
||||
|
||||
MZ80 License agreement
|
||||
-----------------------
|
||||
|
||||
(MZ80 Refers to both the assembly code emitted by makez80.c and makez80.c
|
||||
itself)
|
||||
|
||||
MZ80 May be distributed in unmodified form to any medium.
|
||||
|
||||
MZ80 May not be sold, or sold as a part of a commercial package without
|
||||
the express written permission of Neil Bradley (neil@synthcom.com). This
|
||||
includes shareware.
|
||||
|
||||
Modified versions of MZ80 may not be publicly redistributed without author
|
||||
approval (neil@synthcom.com). This includes distributing via a publicly
|
||||
accessible LAN. You may make your own source modifications and distribute
|
||||
MZ80 in source or object form, but if you make modifications to MZ80
|
||||
then it should be noted in the top as a comment in makez80.c.
|
||||
|
||||
MZ80 Licensing for commercial applications is available. Please email
|
||||
neil@synthcom.com for details.
|
||||
|
||||
Synthcom Systems, Inc, and Neil Bradley will not be held responsible for
|
||||
any damage done by the use of MZ80. It is purely "as-is".
|
||||
|
||||
If you use MZ80 in a freeware application, credit in the following text:
|
||||
|
||||
"Multi-Z80 CPU emulator by Neil Bradley (neil@synthcom.com)"
|
||||
|
||||
must accompany the freeware application within the application itself or
|
||||
in the documentation.
|
||||
|
||||
Legal stuff aside:
|
||||
|
||||
If you find problems with MZ80, please email the author so they can get
|
||||
resolved. If you find a bug and fix it, please also email the author so
|
||||
that those bug fixes can be propogated to the installed base of MZ80
|
||||
users. If you find performance improvements or problems with MZ80, please
|
||||
email the author with your changes/suggestions and they will be rolled in
|
||||
with subsequent releases of MZ80.
|
||||
|
||||
The whole idea of this emulator is to have the fastest available 32 bit
|
||||
Multi-Z80 emulator for the x86, giving maximum performance.
|
||||
|
||||
MZ80 Contact information
|
||||
-------------------------
|
||||
|
||||
Author : Neil Bradley (neil@synthcom.com)
|
||||
Distribution: ftp://ftp.synthcom.com/pub/emulators/cpu/makez80.zip (latest)
|
||||
|
||||
You can join the cpuemu mailing list on Synthcom for discussion of Neil
|
||||
Bradley's Z80 (and other) CPU emulators. Send a message to
|
||||
"cpuemu-request@synthcom.com" with "subscribe" in the message body. The
|
||||
traffic is fairly low, and is used as a general discussion and announcement
|
||||
for aforementioned emulators.
|
||||
|
||||
|
||||
MZ80 Documentation
|
||||
-------------------
|
||||
|
||||
MZ80 Is a full featured Z80 emulator coded in 32 bit assembly. It runs well
|
||||
over a hundred games, in addition to it supporting many undocumented Z80
|
||||
instructions required to run some of the Midway MCR games, Galaga, and
|
||||
countless other wonderful Z80 based arcade games.
|
||||
|
||||
MZ80 Contains a makez80.c program that must be compiled. It is the program
|
||||
that emits the assembly code that NASM will compile. This minimizes the
|
||||
possibility of bugs creeping in to MZ80 for the different addressing modes
|
||||
for each instruction. It requires NASM 0.97 or greater.
|
||||
|
||||
The goal of MZ80 is to have a high performance Z80 emulator that is capable
|
||||
of running multiple emulations concurrently at full speed, even on lower-end
|
||||
machines (486/33). MZ80 Harnesses the striking similarities of both the Z80
|
||||
and the x86 instruction sets to take advantage of flag handling which greatly
|
||||
reduces the time required to emulate a processor, so no extra time is spent
|
||||
computing things that are already available in the native x86 processor,
|
||||
allowing it to perform leaps and bounds over comparable C based Z80 emulators
|
||||
on the same platform.
|
||||
|
||||
MZ80 Is designed exclusively for use with NASM, the Netwide Assembler. This
|
||||
gives the ultimate in flexibility, as NASM can emit object files that work
|
||||
with Watcom, Microsoft Visual C++ (4.0-current), DJGPP, Borland C++, and
|
||||
gcc under FreeBSD or Linux. MZ80 Has been tested with each one of these
|
||||
compilers and is known to work properly on each.
|
||||
|
||||
|
||||
What's in the package
|
||||
---------------------
|
||||
|
||||
MZ80.TXT - This text file
|
||||
|
||||
MAKEZ80.C - Multi Z80 32 Bit emulator emitter program
|
||||
|
||||
MZ80.H - C Header file for MZ80 functions
|
||||
|
||||
|
||||
What's new in this release
|
||||
--------------------------
|
||||
|
||||
Revision 3.4:
|
||||
|
||||
* Fixed the overflow flag not getting cleared in the SetOverflow()
|
||||
routine. It caused strange problems with a handful of Genesis games
|
||||
* Removed invalid instruction in the C version so that more
|
||||
instructions will execute
|
||||
|
||||
Revision 3.3:
|
||||
|
||||
* Undocumented opcodes added to the C emitter
|
||||
* Bug fix to the C emission that properly handles shared RAM regions
|
||||
(I.E. with handlers that are NULL)
|
||||
* Now using 32 bit registers to do register/memory access. Slight
|
||||
speed increase (assembly version only)
|
||||
|
||||
Revision 3.2:
|
||||
|
||||
* R Register emulation now accurate with a real Z80
|
||||
* mz80int() Called when interrupts are disabled causes the
|
||||
z80intPending flag to be set, and an interrupt will be caused after
|
||||
the execution of EI and the next instruction. See "IMPORTANT NOTE
|
||||
ABOUT INTERRUPTS" below
|
||||
* The instruction after EI executes fully before interrupt status is
|
||||
checked. (as does a real Z80)
|
||||
|
||||
|
||||
Revision 3.1:
|
||||
|
||||
* Fixed bug in memory dereference when handler was set to NULL (keeps
|
||||
system from crashing or faulting)
|
||||
* Removed the only stricmp() from the entire file and replaced it
|
||||
with strcmp() so that stdlibs without it will compile
|
||||
* Changed cyclesRemaining > 0 to cyclesRemaining >= 0 to be compatible
|
||||
with the ASM core
|
||||
* Removed additional sub [dwCyclesRemaining], 5 at the beginning of
|
||||
mz80exec() (ASM Core only). Increases timing accuracy.
|
||||
* NMIs And INTs add additional time to dwElapsedTicks as it should
|
||||
* mz80ReleaseTimeslice() Sets remaining clocks to 0 instead of 1
|
||||
|
||||
|
||||
Revision 3.0:
|
||||
|
||||
* All instructions validated against a real Z80. Used an ISA card
|
||||
with a Z80 on it to validate flag handling, instruction handling,
|
||||
timing, and other goodies. The only thing not implemented/emulated
|
||||
is flag bit 3 & 5 emulation. Believed to be 100% bug free!
|
||||
* 80% Speed improvement over version 2.7 of mz80
|
||||
* z80stb.c Removed. Use -c to emit a C version of mz80! API compatible!
|
||||
Note that this is mostly, but not fully, debugged, so consider the
|
||||
C version a beta! It's at least healthier than z80stb.c was. The C
|
||||
version does not include the undocumented Z80 instructions.
|
||||
* mz80nmi() No longer trashes registers it uses when using -cs
|
||||
* IN/OUT Instructions work properly when using -16
|
||||
* IN A, (xxh) uses A as high 8 bits of I/O fetch address when using -16
|
||||
* IM 0/IM 1 Description in documentation fixed
|
||||
* Sizes of all context registers increased to 32 bits - for speed!
|
||||
* IFF1/IFF2 Now properly emulated
|
||||
* JR Instruction offset can fetch from $ffff and properly wrap
|
||||
* LDIR/LDDR Instruction now won't go to completion - instead it will
|
||||
run until BC=0 or the # of cycles to execute have expired. These
|
||||
instructions used to run to completion - even beyond the # of cycles
|
||||
left to execute
|
||||
* INI/IND/INIR/INDR countdown bug fixed - it was decrementing B twice
|
||||
for each IN! Whoops!
|
||||
* If you specify NULL as a handler address to a memory region, mz80 will
|
||||
use vpData as a pointer to where that block of data resides. Quite
|
||||
useful for multiprocessor emulations that share the same memory.
|
||||
* EDI Now keeps track of cycle counting for faster execution
|
||||
* Modified memory region scanning code to use 32 bit registers instead
|
||||
of their 16 bit counterparts
|
||||
* Get/SetContext() uses rep movsd/movsb. Insignificant overall, but
|
||||
why waste the time?
|
||||
* Debugging routines added. See the "DEBUGGING" section below for more
|
||||
information. NOTE: The debugging routines are not yet available in
|
||||
the C emission.
|
||||
* Timing done slightly differently now. Mz80 now executes one
|
||||
instruction past the timing given on input. For example, mz80exec(0)
|
||||
will cause a single instruction to be executed (thusly -ss was
|
||||
removed).
|
||||
|
||||
Revision 2.7:
|
||||
|
||||
* Fixed OTIR/OTDR/INIR/INDR instructions so their 16 bit counterparts
|
||||
work properly
|
||||
* Emulation core 30-70% faster overall than 2.6 due to optimization to
|
||||
the timing routines
|
||||
* Replaced word reads/writes with a special word write routine rather
|
||||
than the standard calling to read/write byte functions
|
||||
* z80stb.c (the C equivalent of mz80) compiles properly now
|
||||
* Fixed OS/2 text/segment issue
|
||||
* Fixed bug in set/getCPU context that ensures that ES=DS and avoids
|
||||
crashes. Caused crashes under OS/2 and other OS's
|
||||
|
||||
Revision 2.6:
|
||||
|
||||
* Emulator core 5-30% faster overall. Some 16 and 8 bit instructions
|
||||
sped up when using their 32 bit equivalents.
|
||||
* Fix to -l so that proper labels without leading and trailing
|
||||
underscores so Linux/FreeBSD compiles will work properly
|
||||
* Single step now executes the # of instructions passed in to z80exec()
|
||||
instead of just 1 as it had in prior releases. This is only active
|
||||
when the -ss option is used.
|
||||
* The -nt option was added. This will cause the timing information to
|
||||
not be added in, speeding up execution. Warning: Only do this if your
|
||||
emulated target does not require instruction timing!
|
||||
* Updated documentation errors
|
||||
* C Version of mz80 (mz80.c) that is API compliant is distributed with
|
||||
the archive (With kind permission of Edward Massey).
|
||||
|
||||
Revision 2.5:
|
||||
|
||||
* Fixed an unconditional flag being cleared in the ddcbxx instructions.
|
||||
It caused Donkey Kong's barrels to not roll.
|
||||
|
||||
Revision 2.4:
|
||||
|
||||
* Fixed improper HALT handling (didn't advance the PTR when it should)
|
||||
* Fixed SRL (IX+$xx) instruction so that carry wasn't trashed
|
||||
* Fixed single stepping problems with it giving too much time to
|
||||
any given instruction
|
||||
* Fixed half carry flag handling with 16 bit SBC and ADD instructions
|
||||
* Fixed DAA emulation so that parity flags weren't getting trashed
|
||||
|
||||
Revision 2.3:
|
||||
|
||||
* Fixed many stack handling bugs
|
||||
* Timing problems fixed. The prior version was causing massive
|
||||
overruns on maximum timeslices with some insutructions.
|
||||
|
||||
Revision 2.2:
|
||||
|
||||
* Fixed a bug in CPI/CPD/CPIR/CPDR that mishandled flags
|
||||
* All known bugs are out of mz80 now
|
||||
* Added the -cs option to route all stack operations through the
|
||||
handlers (required for games like Galaga)
|
||||
|
||||
Revision 2.1:
|
||||
|
||||
* Fixed a bug in CPI/CPD/CPIR/CPDR that caused intermittent lockups.
|
||||
Also fixed a bug that caused erratic behavior in several video games.
|
||||
* Added INI/IND/INIR/INDR instruction group
|
||||
* Added OUTI/OUTD/OTIR/OTDR instruction group
|
||||
|
||||
Revision 1.0:
|
||||
|
||||
* First release! The whole thing is new!
|
||||
|
||||
|
||||
ASSEMBLING FOR USE WITH WATCOM C/C++
|
||||
------------------------------------
|
||||
|
||||
Watcom, by default, uses register calling conventions, as does MZ80. To
|
||||
create a proper emulator for Watcom:
|
||||
|
||||
makez80 MZ80.asm -x86
|
||||
|
||||
From here:
|
||||
|
||||
nasm -f win32 MZ80.asm
|
||||
|
||||
Link the MZ80.obj with your Watcom linker.
|
||||
|
||||
|
||||
ASSEMBLING FOR USE WITH MICROSOFT VISUAL C++ AND BORLAND C++
|
||||
--------------------------------------------------------------------
|
||||
|
||||
Visual C++ and Borland C++ use stack calling conventions by default. To
|
||||
create a proper emulator for these compilers:
|
||||
|
||||
makez80 MZ80.asm -s -x86
|
||||
|
||||
For Visual C++ or Borland C++:
|
||||
|
||||
nasm -f win32 MZ80.asm
|
||||
|
||||
Link with your standard Visual C++ or Borland C++.
|
||||
|
||||
|
||||
ASSEMBLING FOR USE WITH DJGPP, GCC/FREEBSD, OR GCC/LINUX
|
||||
--------------------------------------------------------------------
|
||||
|
||||
DJGPP Uses stack calling conventions:
|
||||
|
||||
makez80 MZ80.asm -s -x86
|
||||
|
||||
To assemble:
|
||||
|
||||
nasm -f coff MZ80.asm
|
||||
|
||||
Link with your standard DJGPP linker. The same holds true for GCC under
|
||||
FreeBSD or Linux. If you're using GCC, use the -l option to generate "plain"
|
||||
labels so that gcc's linker will properly link things.
|
||||
|
||||
|
||||
MAKEZ80 COMMAND LINE OPTIONS
|
||||
----------------------------
|
||||
|
||||
-s - Use stack calling conventions (DJGPP, MSVC, Borland, etc...)
|
||||
|
||||
-cs - Force all stack operations to go through the Read/Write memory handlers.
|
||||
This slows things down, but is useful when needed.
|
||||
|
||||
-16 - Treat all I/O input and output as 16 bit (BC)
|
||||
|
||||
-l - Create 'plain' labels - ones without leading and trailing underscores
|
||||
|
||||
-nt - Do not generate timing code - this speeds the emulator up, but the
|
||||
downside is that no timing info is available.
|
||||
|
||||
-c - Emit a C mz80 emulator (API Compatible with the assembly version -
|
||||
handy for porters!)
|
||||
|
||||
-x86 - Emit an assembly (x86) mz80 emulator
|
||||
|
||||
-os2 - Generate OS/2 compatible segmentation
|
||||
|
||||
|
||||
IMPORTANT NOTE ABOUT INTERRUPTS
|
||||
-------------------------------
|
||||
|
||||
A minor change was made between the 3.1 and 3.2 versions of makez80 in the
|
||||
way that interrupts were handled.
|
||||
|
||||
On a real Z80, the !INT line is a level triggered interrupt, meaning that if
|
||||
the interrupt line is held low, the Z80 will continue to take interrupts
|
||||
immediately after the instruction after the EI instruction is executed until
|
||||
the interrupt line is high again.
|
||||
|
||||
In 3.1, if an interrupt came in and interrupts were disabled, the interrupt
|
||||
would never be "latched" for later execution. The Z80 does not have any
|
||||
internal latching capabilities, however external hardware often does hold
|
||||
the interrupt line low until the interrupt is executed, in effect, a latch.
|
||||
|
||||
I've only found one video game so far that requires the "raising/lowering"
|
||||
of the interrupt line (Ataxx). In the games that I've tried, it has improved
|
||||
performance, in some cases drastically, and in others not at all. This can
|
||||
be accounted for by interrupts being taken now, where they were being dropped
|
||||
in prior mz80 releases.
|
||||
|
||||
mz80 Emulates the most commonly used scenario. Now when mz80int() is executed
|
||||
and a nonzero value is returned (indicating interrupts were disabled), it
|
||||
will set z80intPending, and the interrupt will be taken after execution of
|
||||
one instruction beyond the EI instruction.
|
||||
|
||||
So now, if mz80int() returns a nonzero value, that means an interrupt is
|
||||
latched. If clearing this latch is desired or the old behavior of 3.1 is
|
||||
desired, make a call to the mz80ClearPendingInterrupt() call. It's a 2
|
||||
instruction call that has extremely small overhead and will not affect
|
||||
performance in any measurable way.
|
||||
|
||||
In any case, MZ80 will now execute one instruction after EI regardless of
|
||||
how much time is available to avoid the possibility of an interrupt request
|
||||
coming in directly after the EI instruction.
|
||||
|
||||
|
||||
STEPS TO EMULATION
|
||||
------------------
|
||||
|
||||
NOTE: -16 Is a command line option that will treat all I/O as 16 bit. That
|
||||
is, in an instruction like "IN AL, (C)", the addressed passed to the I/O
|
||||
handler will be BC instead of just C. Bear this in mind when considering your
|
||||
emulated platform.
|
||||
|
||||
There are a few steps you want to go through to get proper emulation, and a
|
||||
few guidelines must be followed.
|
||||
|
||||
1) Create a MZ80CONTEXT
|
||||
|
||||
2) Create your virtual 64K memory space using whatever means of obtaining
|
||||
memory you need to do.
|
||||
|
||||
3) Set mz80Base in your context to be the base of your 64K memory space
|
||||
|
||||
4) Load up your image to be emulated within that 64K address space.
|
||||
|
||||
5) Set z80IoRead and z80IoWrite to their appropriate structure arrays. Here's
|
||||
an example:
|
||||
|
||||
struct z80PortRead ReadPorts[] =
|
||||
{
|
||||
{0x10, 0x1f, SoundChip1Read},
|
||||
{0x20, 0x2f, SoundChip2Read}
|
||||
{(UINT32) -1, (UINT32) -1, NULL}
|
||||
};
|
||||
|
||||
When an IN instruction occurs, mz80 will probe this table looking for a
|
||||
handler to the address of the "IN" instruction. If it is found in the list,
|
||||
it's up to the handler to return the proper value. Otherwise, a value of
|
||||
0ffh is returned internally if no handler for that I/O address is found. In
|
||||
the case above, SoundChip1Read is called when the I/O address is between 0x10-
|
||||
0x1f. A similar structure is used for I/O writes as well (OUT):
|
||||
|
||||
struct z80PortWrite WritePorts[] =
|
||||
{
|
||||
{0x20, 0x2f, SoundChip2Write},
|
||||
{0x30, 0x36, VideoCtrlWrite},
|
||||
{(UINT32) -1, (UINT32) -1, NULL}
|
||||
}
|
||||
|
||||
Of course, this does the opposite that the z80PortRead struct, and instead
|
||||
looks for a handler to hand some data to. If it doesn't find an appropriate
|
||||
handler, nothing happens.
|
||||
|
||||
6) Set mz80MemoryRead & mz80MemoryWrite to their appropriate structure
|
||||
arrays. Here is an example:
|
||||
|
||||
struct MemoryWriteByte GameWrite[] =
|
||||
{
|
||||
{0x3000, 0x3fff, VideoWrite},
|
||||
{0x4000, 0x4fff, SpriteWrite},
|
||||
{(UINT32) -1, (UINT32) -1, NULL}
|
||||
};
|
||||
|
||||
The above example says that any time a write occurs in the 0x3000-0x3fff
|
||||
range, call the VideoWrite routine. The same holds true for the SpriteWrite
|
||||
region as well.
|
||||
|
||||
NOTE: When your write handler is called, it is passed the address of the
|
||||
write and the data that is to be written to it. If your handler doesn't
|
||||
write the data to the virtual image, the mz80 internal code will not.
|
||||
|
||||
NOTE: These routines will *NOT* be called when execution asks for these
|
||||
addresses. It will only call them when a particular instruction uses the
|
||||
memory at these locations.
|
||||
|
||||
If you wish for a region to be RAM, just leave it out of your memory region
|
||||
exception list. The WriteMemoryByte routine will treat it as read/write
|
||||
RAM and will write to mz80Base + addr directly.
|
||||
|
||||
If you wish to protect ROM regions (not often necessary), create a range that
|
||||
encompasses the ROM image, and have it call a routine that does nothing. This
|
||||
will prevent data from being written back onto the ROM image.
|
||||
|
||||
Leave your last entry in the table as shown above, with a null handler and
|
||||
0xffffffff-0xffffffff as your read address. Even though the Z80 only
|
||||
addresses 64K of space, the read/write handlers are defined as 32 bit so
|
||||
the compiler won't pass junk in the upper 16 bits of the address lines. Not
|
||||
only that, it allows orthoganality for future CPU emulators that may use
|
||||
these upper bits.
|
||||
|
||||
You can do a mz80GetContext() if you'd like to read the current context of
|
||||
the registers. Note that by the time your handler gets called, the program
|
||||
counter will be pointing to the *NEXT* instruction.
|
||||
|
||||
struct MemoryReadByte GameRead[] =
|
||||
{
|
||||
{0x2000, 0x200f, ReadHandler},
|
||||
{(UINT32) -1, (UINT32) -1, NULL}
|
||||
};
|
||||
|
||||
Same story here. If you have a special handler for an attempted read at a
|
||||
particular address, place its range in this table and create a handler
|
||||
routine for it.
|
||||
|
||||
If you don't define a handler for a particular region, then the ReadMemoryByte
|
||||
in mz80.ASM will actually read the value out of mz80Base + the offset
|
||||
required to complete the instruction.
|
||||
|
||||
7) Set the intAddr and nmiAddr to the addresses where you want mz80 to start
|
||||
executing when an interrupt or NMI happens. Take a look at the section
|
||||
entitled "INTERRUPTS" below for more information on this.
|
||||
|
||||
8) Call mz80SetContext() on your Z80 context
|
||||
|
||||
9) Call mz80Reset(). This will prime the program counter and cause a virtual
|
||||
CPU-wide reset.
|
||||
|
||||
10) Once you have those defined, you're ready to begin emulation. There's some
|
||||
sort of main loop that you'll want. Maybe something like:
|
||||
|
||||
while (hit == 0)
|
||||
{
|
||||
if (lastSec != (UINT32) time(0))
|
||||
{
|
||||
diff = (mz80clockticks - prior) / 3000000;
|
||||
printf("%ld Clockticks, %ld frames, %ld Times original speed\n", MZ80clockticks - prior, frames, diff);
|
||||
frames = 0;
|
||||
prior = mz80clockticks;
|
||||
lastSec = time(0);
|
||||
if (kbhit())
|
||||
{
|
||||
getch();
|
||||
hit = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 9000 Cycles per NMI (~3 milliseconds @ 3MHZ) */
|
||||
|
||||
dwResult = mz80exec(9000);
|
||||
mz80clockticks += mz80GetElapsedTicks(TRUE);
|
||||
mz80nmi();
|
||||
|
||||
/* If the result is not 0x80000000, it's an address where
|
||||
an invalid instruction was hit. */
|
||||
|
||||
if (0x80000000 != dwResult)
|
||||
{
|
||||
mz80GetContext(&sCpu1);
|
||||
printf("Invalid instruction at %.2x\n", sCpu1.MZ80pc);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
Call mz80exec() With the # of virtual CPU cycles you'd like mz80 to
|
||||
execute. Be sure to use the mz80GetElapsedTicks() call *AFTER* execution to
|
||||
see how many virtual CPU cycles it actually executed. For example, if you tell
|
||||
mz80 to execute 500 virtual CPU cycles, it will execute slightly more. Anything
|
||||
from 500 to 524 (24 cycles being the longest any 1 instruction takes in the
|
||||
Z80).
|
||||
|
||||
Use the mz80GetElapsedTicks() call for more accurate cycle counting. Of course,
|
||||
this is only if you have *NOT* included the -nt option.
|
||||
|
||||
If you pass FALSE to the mz80GetElapsedTicks() function, the internal CPU
|
||||
elapsed tick clock will not be reset. The elapsed tick counter is something
|
||||
that continues to increase every emulated instruction, and like an odometer,
|
||||
will keep counting unless you pass TRUE to mz80GetElapsedTicks(), of which
|
||||
case it will return you the current value of the elapsed ticks and set it to
|
||||
0 when complete.
|
||||
|
||||
NOTE: The bigger value you pass to mz80exec, the greater benefit you get out
|
||||
of the virtual registers persisting within the emulator, and it will run
|
||||
faster. Pass in a value that is large enough to take advantage of it, but
|
||||
not so often that you can't handle nmi or int's properly.
|
||||
|
||||
If you wish to create a virtual NMI, call mz80nmi(), and it will be taken
|
||||
the next time you call mz80exec, or alternately if you have a handler call
|
||||
mz80nmi/mz80int(), the interrupt will be taken upon return. Note that
|
||||
mz80nmi() doesn't actually execute any code - it only primes the emulator to
|
||||
begin executing NMI/INT code.
|
||||
|
||||
NOTE: mz80int() is defined with a UINT32 as a formal parameter. Depending
|
||||
upon what interrupt mode you're executing in (described later), it may or may
|
||||
not take a value.
|
||||
|
||||
NMI's can interrupt interrupts, but not the other way around - just like a
|
||||
real Z80. If your program is already in an interrupt, another one will not be
|
||||
taken. The same holds true for an NMI - Just like a real Z80!
|
||||
|
||||
|
||||
MUTLI-PROCESSOR NOTES
|
||||
---------------------
|
||||
|
||||
Doing multi processor support is a bit trickier, but is still fairly straight-
|
||||
forward.
|
||||
|
||||
For each processor to be emulated, go through steps 1-7 above - giving each
|
||||
CPU its own memory space, register storage, and read/write handlers.
|
||||
|
||||
|
||||
EXECUTION OF MULTI-CPUS:
|
||||
-------------------------
|
||||
|
||||
When you're ready to execute a given CPU, do the following:
|
||||
|
||||
mz80SetContext(contextPointer);
|
||||
|
||||
This will load up all information saved before into the emulator and ready it
|
||||
for execution. Then execute step 7 above to do your virtual NMI's, interrupts,
|
||||
etc... All CPU state information is saved within a context.
|
||||
|
||||
When the execution cycle is complete, do the following to save the updated
|
||||
context away for later:
|
||||
|
||||
mz80GetContext(contextPointer);
|
||||
|
||||
Give each virtual processor a slice of time to execute. Don't make the values
|
||||
too small or it will spend its time swapping contexts. While this in itself
|
||||
isn't particularly CPU expensive, the more time you spend executing the better.
|
||||
mz80 Keeps all of the Z80 register in native x86 register (including most
|
||||
of the flags, HL, BC, and A). If no context swap is needed, then you get the
|
||||
added advantage of the register storage. For example, let's say you were
|
||||
running two Z80s - one at 2.0MHZ and one at 3.0MHZ. An example like this
|
||||
might be desirable:
|
||||
|
||||
mz80SetContext(cpu1Context); // Set CPU #1's information
|
||||
mz80exec(2000); // 2000 Instructions for 2.0MHZ CPU
|
||||
mz80GetContext(cpu1Context); // Get CPU #1's state info
|
||||
|
||||
mz80SetContext(cpu2Context); // Set CPU #2's state information
|
||||
mz80exec(3000); // 3000 Instructions for 3.0MHZ CPU
|
||||
mz80GetContext(cpu2Context); // Get CPU #2's state information
|
||||
|
||||
This isn't entirely realistic, but if you keep the instruction or timing
|
||||
ratios between the emulated CPUs even, then timing is a bit more accurate.
|
||||
|
||||
NOTE: If you need to make a particular CPU give up its own time cycle because
|
||||
of a memory read/write, simply trap a particular address (say, a write to a
|
||||
slave processor) and call mz80ReleaseTimeslice(). It will not execute any
|
||||
further instructions, and will give up its timeslice. Put this in your
|
||||
read/write memory trap.
|
||||
|
||||
NOTE: You are responsible for "holding back" the processor emulator from
|
||||
running too fast.
|
||||
|
||||
|
||||
INTERRUPTS
|
||||
----------
|
||||
|
||||
The Z80 has three interrupt modes: IM 0 - IM 2. Each act differently. Here's
|
||||
a description of each:
|
||||
|
||||
IM 0
|
||||
|
||||
This mode will cause the Z80 to be able to pull a "single byte instruction"
|
||||
off the bus when an interrupt occurs. Since we're not doing bus cycle
|
||||
emulation, it acts identically to mode 1 (described below). The formal
|
||||
parameter to mz80int() is ignored. There is really no point in actually
|
||||
emulating the instruction execution since any instruction that would be
|
||||
executed would be a branch instruction!
|
||||
|
||||
IM 1
|
||||
|
||||
This mode is the "default" mode that the Z80 (and mz80 for that matter) comes
|
||||
up in. When you call mz80reset(), the interrupt address is set to 38h and
|
||||
the NMI address is set to 66h. So when you're in IM 1 and mz80int() is
|
||||
called, the formal parameter is ignored and the z80intAddr/z80nmiAddr values
|
||||
are appropriately loaded into the program counter.
|
||||
|
||||
IM 2
|
||||
|
||||
This mode causes the Z80 to read the upper 8 bits from the current value
|
||||
of the "I" register, and the lower 8 bits from the value passed into mz80int().
|
||||
So, if I contained 35h, and you did an mz80int(0x64), then an interrupt at
|
||||
address 3564h would be taken. Simple!
|
||||
|
||||
|
||||
OTHER GOODIES
|
||||
-------------
|
||||
|
||||
MZ80 Has a nice feature for allowing the same handler to handle different
|
||||
data regions on a single handler. Here's an example:
|
||||
|
||||
struct PokeyDataStruct Pokey1;
|
||||
struct PokeyDataStruct Pokey2;
|
||||
|
||||
struct MemoryWriteByte GameWrite[] =
|
||||
{
|
||||
{0x1000, 0x100f, PokeyHandler, Pokey1},
|
||||
{0x1010, 0x101f, PokeyHandler, Pokey2},
|
||||
{(UINT32) -1, (UINT32) -1, NULL}
|
||||
};
|
||||
|
||||
void PokeyHandler(UINT32 dwAddr, UINT8 bData, struct sMemoryWriteByte *psMem)
|
||||
{
|
||||
struct PokeyDataStruct *psPokey = psMem->pUserArea;
|
||||
|
||||
// Do stuff with psPokey here....
|
||||
}
|
||||
|
||||
This passes in the pointer to the sMemoryWriteByte structure that caused
|
||||
the handler to be called. The pUserArea is a user defined address that can
|
||||
be anything. It is not necessary to fill it in with anything or even
|
||||
initialize it if the handler doesn't actually use it.
|
||||
|
||||
This allows a single handler to handle multiple data references. This is
|
||||
particularly useful when handling sound chip emulation, where there might
|
||||
be more than one of a given device. Sure beats having multiple unique
|
||||
handlers that are identical with the exception of the data area where it
|
||||
writes! This allows a good deal of flexibility.
|
||||
|
||||
The same construct holds for MemoryReadByte, z80PortRead, and z80PortWrite,
|
||||
so all can take advantage of this feature.
|
||||
|
||||
|
||||
SHARED MEMORY FEATURES
|
||||
----------------------
|
||||
|
||||
MZ80 Also has another useful feature for dealing with shared memory regions:
|
||||
|
||||
UINT8 bSharedRAM[0x100];
|
||||
|
||||
struct MemoryWriteByte Processor1[] =
|
||||
{
|
||||
{0x1000, 0x10ff, NULL, bSharedRAM},
|
||||
{(UINT32) -1, (UINT32) -1, NULL}
|
||||
};
|
||||
|
||||
struct MemoryWriteByte Processor2[] =
|
||||
{
|
||||
{0x1000, 0x10ff, NULL, bSharedRAM},
|
||||
{(UINT32) -1, (UINT32) -1, NULL}
|
||||
};
|
||||
|
||||
If the handler address is NULL, mz80 will look at the pUserArea field as a
|
||||
pointer to RAM to read from/write to. This comes in extremely handy when you
|
||||
have an emulation that requires two or more processors writing to the same
|
||||
memory block. And it's lots faster than creating a handler that writes to
|
||||
a common area as well.
|
||||
|
||||
|
||||
DEBUGGING
|
||||
---------
|
||||
|
||||
Several new functions have been added to mz80 that assist the emulator
|
||||
author by providing a standard set of functions for register access:
|
||||
|
||||
UINT8 mz80SetRegisterValue(void *pContext, UINT32 dwRegister, UINT32 dwValue)
|
||||
|
||||
This allows setting of any register within the Z80. The register field can be
|
||||
one of the following values (defined in mz80.h):
|
||||
|
||||
CPUREG_PC
|
||||
CPUREG_Z80_AF
|
||||
CPUREG_Z80_BC
|
||||
CPUREG_Z80_DE
|
||||
CPUREG_Z80_HL
|
||||
CPUREG_Z80_AFPRIME
|
||||
CPUREG_Z80_BCPRIME
|
||||
CPUREG_Z80_DEPRIME
|
||||
CPUREG_Z80_HLPRIME
|
||||
CPUREG_Z80_IX
|
||||
CPUREG_Z80_IY
|
||||
CPUREG_Z80_SP
|
||||
CPUREG_Z80_I
|
||||
CPUREG_Z80_R
|
||||
CPUREG_Z80_A
|
||||
CPUREG_Z80_B
|
||||
CPUREG_Z80_C
|
||||
CPUREG_Z80_D
|
||||
CPUREG_Z80_E
|
||||
CPUREG_Z80_H
|
||||
CPUREG_Z80_L
|
||||
CPUREG_Z80_F
|
||||
CPUREG_Z80_CARRY
|
||||
CPUREG_Z80_NEGATIVE
|
||||
CPUREG_Z80_PARITY
|
||||
CPUREG_Z80_OVERFLOW
|
||||
CPUREG_Z80_HALFCARRY
|
||||
CPUREG_Z80_ZERO
|
||||
CPUREG_Z80_SIGN
|
||||
CPUREG_Z80_IFF1
|
||||
CPUREG_Z80_IFF2
|
||||
|
||||
Each individual register's value can be set, including the flags at the end.
|
||||
The only valid values for the flags are 1 and 0. Setting these will
|
||||
automatically adjust the "F" register.
|
||||
|
||||
If pContext is NULL, then the registers in the currently active context are
|
||||
changed. If pContext points to a non-NULL area, that area is assumed to be
|
||||
a CONTEXTMZ80 structure where the new register value will be written.
|
||||
|
||||
If mz80SetRegisterValue() returns a nonzero value, either the register value
|
||||
or register is out of range or invalid.
|
||||
|
||||
|
||||
UINT32 mz80GetRegisterValue(void *pContext, UINT32 dwRegister)
|
||||
|
||||
This returns the value of the register given on input (listed above as
|
||||
CPUREG_Z80_xxxxx). Flag values will be 1 or 0.
|
||||
|
||||
If pContext is NULL, then the registers in the currently active context are
|
||||
read. If pContext points to a non-NULL area, that area is assumed to be
|
||||
a CONTEXTMZ80 structure from which register values are pulled.
|
||||
|
||||
|
||||
UINT32 mz80GetRegisterTextValue(void *pContext, UINT32 dwRegister,
|
||||
UINT8 *pbTextArea)
|
||||
|
||||
This returns the textual representation of the value of a given register.
|
||||
It is a text printable string that can be used in sprintf() statements and
|
||||
the like. This function is useful because different representations for
|
||||
registers (like flags) can be a group of 8 flag bytes instead of a single
|
||||
value.
|
||||
|
||||
On entry, pContext being set to NULL indicates that mz80 should get the
|
||||
register value from the currently active context. Otherwise, it is assumed
|
||||
to be pointing to a CONTEXTMZ80 structure, which contains the value of the
|
||||
registers to be read.
|
||||
|
||||
pbTextArea points to a buffer where the value text can be written. This points
|
||||
to a user supplied buffer.
|
||||
|
||||
On exit, if any nonzero value is encountered, either the register # is out
|
||||
of range or pbTextArea is NULL.
|
||||
|
||||
|
||||
UINT8 *mz80GetRegisterName(UINT32 dwRegister)
|
||||
|
||||
This returns a pointer to the textual name of the register passed in. NULL
|
||||
Is returned if the register index (CPUREG_Z80_xxxx table described above) is
|
||||
out of range. DO NOT MODIFY THE TEXT! It is static data.
|
||||
|
||||
|
||||
FINAL NOTES
|
||||
-----------
|
||||
|
||||
I have debugged MZ80.ASM to the best of my abilities. There might still be
|
||||
a few bugs floating around in it, but I'm not aware of any. I've validated
|
||||
all instructions (That I could) against a custom built Z80 on an ISA card
|
||||
(that fits in a PC) so I'm quite confident that it works just like a real
|
||||
Z80.
|
||||
|
||||
If you see any problems, please point them out to me, as I am eager to make
|
||||
mz80 the best emulator that I can.
|
||||
|
||||
If you have questions, comments, etc... about mz80, please don't hesitate
|
||||
to send me an email. And if you use mz80 in your emulator, I'd love to take
|
||||
a look at your work. If you have special needs, or need implementation
|
||||
specific hints, feel free to email me, Neil Bradley (neil@synthcom.com). I
|
||||
will do my best to help you.
|
||||
|
||||
Enjoy!
|
||||
|
||||
Neil Bradley
|
||||
neil@synthcom.com
|
||||
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue