initial import

git-svn-id: file:///home/notaz/opt/svn/PicoDrive@2 be3aeb3a-fb24-0410-a615-afba39da0efa
This commit is contained in:
notaz 2006-12-19 20:53:21 +00:00
parent 2cadbd5e56
commit cc68a136aa
341 changed files with 180839 additions and 0 deletions

58
cpu/Cyclone/Cyclone.h Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

Binary file not shown.

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

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

View 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.

View 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

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

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

File diff suppressed because it is too large Load diff

16
cpu/a68k/Makefile.win Normal file
View 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
View file

@ -0,0 +1 @@
// dave filler 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
View 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

File diff suppressed because it is too large Load diff

203
cpu/musashi/m68kconf.h Normal file
View 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

File diff suppressed because it is too large Load diff

2022
cpu/musashi/m68kcpu.h Normal file

File diff suppressed because it is too large Load diff

3604
cpu/musashi/m68kdasm.c Normal file

File diff suppressed because it is too large Load diff

1484
cpu/musashi/m68kmake.c Normal file

File diff suppressed because it is too large Load diff

12199
cpu/musashi/m68kopac.c Normal file

File diff suppressed because it is too large Load diff

13385
cpu/musashi/m68kopdm.c Normal file

File diff suppressed because it is too large Load diff

8878
cpu/musashi/m68kopnz.c Normal file

File diff suppressed because it is too large Load diff

2093
cpu/musashi/m68kops.c Normal file

File diff suppressed because it is too large Load diff

1986
cpu/musashi/m68kops.h Normal file

File diff suppressed because it is too large Load diff

315
cpu/musashi/readme.txt Normal file
View 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
View 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
View 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

File diff suppressed because it is too large Load diff

17053
cpu/mz80/mz80.c Normal file

File diff suppressed because it is too large Load diff

394
cpu/mz80/mz80.h Normal file
View 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
View 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