forked from KolibriOS/kolibrios
b2ba8d7766
git-svn-id: svn://kolibrios.org@1814 a494cfbc-eb01-0410-851d-a64ba20cac60
408 lines
12 KiB
C
408 lines
12 KiB
C
/*=====================================================================
|
|
z80.c -> Main File related to the Z80 emulation code.
|
|
|
|
Please read documentation files to know how this works :)
|
|
|
|
Thanks go to Marat Fayzullin (read z80.h for more info), Raúl Gomez
|
|
(check his great R80 Spectrum emulator!), Philip Kendall (some code
|
|
of this emulator, such as the flags lookup tabled are from his fuse
|
|
Spectrum emulator) and more people I forget to name here ...
|
|
|
|
This program 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.
|
|
|
|
This program 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 this program; if not, write to the Free Software
|
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
Copyright (c) 2000 Santiago Romero Iglesias.
|
|
Email: sromero@escomposlinux.org
|
|
======================================================================*/
|
|
|
|
#include "z80.h"
|
|
#include "tables.h"
|
|
|
|
|
|
/* RAM variable, debug toggle variable, pressed key and
|
|
row variables for keyboard emulation */
|
|
extern byte *RAM;
|
|
extern int debug, main_tecla, scanl;
|
|
|
|
extern int fila[5][5];
|
|
|
|
//extern char *tapfile;
|
|
//extern FILE *tapfile;
|
|
extern char *tfont;
|
|
|
|
#include "macros.c"
|
|
|
|
|
|
/*====================================================================
|
|
void Z80Reset( Z80Regs *regs, int cycles )
|
|
|
|
This function simulates a z80 reset by setting the registers
|
|
to the values they are supposed to take on a real z80 reset.
|
|
You must pass it the Z80 register structure and the number
|
|
of cycles required to check for interrupts and do special
|
|
hardware checking/updating.
|
|
===================================================================*/
|
|
void Z80Reset( Z80Regs *regs, int int_cycles )
|
|
{
|
|
/* reset PC and the rest of main registers: */
|
|
regs->PC.W = regs->R.W = 0x0000;
|
|
|
|
regs->AF.W = regs->BC.W = regs->DE.W = regs->HL.W =
|
|
regs->AFs.W = regs->BCs.W = regs->DEs.W = regs->HLs.W =
|
|
regs->IX.W = regs->IY.W = 0x0000;
|
|
|
|
/* Make the stack point to $F000 */
|
|
regs->SP.W = 0xF000;
|
|
|
|
/* reset variables to their default values */
|
|
regs->I = 0x00;
|
|
regs->IFF1 = regs->IFF2 = regs->IM = regs->halted = 0x00;
|
|
regs->ICount = regs->IPeriod = int_cycles;
|
|
|
|
regs->IRequest = INT_NOINT;
|
|
regs->we_are_on_ddfd = regs->dobreak = regs->BorderColor = 0;
|
|
|
|
//#ifdef _DEBUG_
|
|
regs->DecodingErrors = 1;
|
|
//#endif
|
|
|
|
}
|
|
|
|
|
|
/*====================================================================
|
|
word Z80Run( Z80Regs *regs, int numopcodes )
|
|
|
|
This function does the whole Z80 simulation. It consists on a
|
|
for(;;) loop (as stated on Marat's Fayzullin HOWTO -How to
|
|
Write a Computer Emulator-) which fetchs the next opcode,
|
|
interprets it (using a switch statement) and then it's
|
|
executed in the right CASE: of that switch. I've put the different
|
|
case statements into C files included here with #include to
|
|
make this more readable (and programming easier! :).
|
|
|
|
This function will change regs->ICount register and will execute
|
|
an interrupt when it reaches 0 (or <0). You can then do anything
|
|
related to your machine emulation here, using the Z80Hardware()
|
|
function. This function must be filled by yourself: put there
|
|
the code related to the emulated machine hardware, such as
|
|
screen redrawing, sound playing and so on. This functions can
|
|
return an special value to make Z80Run stop the emulation (and
|
|
return to the caller): that's INT_QUIT. If there is time to
|
|
execute an interrupt, please return INT_IRQ or INT_NMI. Return
|
|
INT_NOINT if there is no time for an interrupt :) .
|
|
|
|
Z80Execute() will change PC and all the z80 registers acording
|
|
to the executed opcode, and those values will be returned when
|
|
a INT_QUIT is received.
|
|
|
|
Pass as numcycles the number of clock cycle you want to execute
|
|
z80 opcodes for or < 0 (negative) to execute "infinite" opcodes.
|
|
===================================================================*/
|
|
word Z80Run( Z80Regs *regs, int numcycles )
|
|
{
|
|
/* opcode and temp variables */
|
|
register byte opcode;
|
|
eword tmpreg, ops, mread, tmpreg2;
|
|
unsigned long tempdword;
|
|
register int loop;
|
|
unsigned short tempword;
|
|
|
|
/* emulate <numcycles> cycles */
|
|
loop = (regs->ICount - numcycles);
|
|
|
|
/* this is the emulation main loop */
|
|
while( regs->ICount > loop )
|
|
{
|
|
#ifdef DEBUG
|
|
/* test if we have reached the trap address */
|
|
if( regs->PC.W == regs->TrapAddress && regs->dobreak != 0 )
|
|
return(regs->PC.W);
|
|
#endif
|
|
|
|
if( regs->halted == 1 )
|
|
{ r_PC--; AddCycles(4); }
|
|
|
|
/* read the opcode from memory (pointed by PC) */
|
|
opcode = Z80ReadMem(regs->PC.W);
|
|
regs->PC.W++;
|
|
|
|
/* increment the R register and decode the instruction */
|
|
AddR(1);
|
|
switch(opcode)
|
|
{
|
|
#include "opcodes.c"
|
|
case PREFIX_CB:
|
|
AddR(1);
|
|
#include "op_cb.c"
|
|
break;
|
|
case PREFIX_ED:
|
|
AddR(1);
|
|
#include "op_ed.c"
|
|
break;
|
|
case PREFIX_DD:
|
|
case PREFIX_FD:
|
|
AddR(1);
|
|
if( opcode == PREFIX_DD )
|
|
{
|
|
#define REGISTER regs->IX
|
|
regs->we_are_on_ddfd = WE_ARE_ON_DD;
|
|
#include "op_dd_fd.c"
|
|
#undef REGISTER
|
|
}
|
|
else
|
|
{
|
|
#define REGISTER regs->IY
|
|
regs->we_are_on_ddfd = WE_ARE_ON_FD;
|
|
#include "op_dd_fd.c"
|
|
#undef REGISTER
|
|
}
|
|
regs->we_are_on_ddfd = 0;
|
|
break;
|
|
}
|
|
|
|
/* patch ROM loading routine */
|
|
// address contributed by Ignacio Burgueño :)
|
|
// if( r_PC == 0x0569 )
|
|
if( r_PC >= 0x0556 && r_PC <= 0x056c )
|
|
Z80Patch( regs );
|
|
|
|
/* check if it's time to do other hardware emulation */
|
|
if( regs->ICount <= 0 )
|
|
{
|
|
tmpreg.W = Z80Hardware(regs);
|
|
regs->ICount += regs->IPeriod;
|
|
loop = regs->ICount + loop;
|
|
|
|
/* check if we must exit the emulation or there is an INT */
|
|
if( tmpreg.W == INT_QUIT )
|
|
return( regs->PC.W );
|
|
if( tmpreg.W != INT_NOINT )
|
|
Z80Interrupt( regs, tmpreg.W );
|
|
}
|
|
}
|
|
|
|
return(regs->PC.W);
|
|
}
|
|
|
|
|
|
|
|
/*====================================================================
|
|
void Z80Interrupt( Z80Regs *regs, word ivec )
|
|
===================================================================*/
|
|
void Z80Interrupt( Z80Regs *regs, word ivec )
|
|
{
|
|
word intaddress;
|
|
|
|
/* unhalt the computer */
|
|
if( regs->halted == 1 )
|
|
regs->halted = 0;
|
|
|
|
if( regs->IFF1 )
|
|
{
|
|
PUSH(PC);
|
|
regs->IFF1 = 0;
|
|
switch(regs->IM)
|
|
{
|
|
case 0: r_PC = 0x0038; AddCycles(12); break;
|
|
case 1: r_PC = 0x0038; AddCycles(13); break;
|
|
case 2: intaddress = (((regs->I & 0xFF)<<8) | 0xFF);
|
|
regs->PC.B.l = Z80ReadMem(intaddress);
|
|
regs->PC.B.h = Z80ReadMem(intaddress+1);
|
|
AddCycles(19);
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/*====================================================================
|
|
word Z80Hardware(register Z80Regs *regs)
|
|
|
|
Do here your emulated machine hardware emulation. Read Z80Execute()
|
|
to know about how to quit emulation and generate interrupts.
|
|
===================================================================*/
|
|
word Z80Hardware( register Z80Regs *regs )
|
|
{
|
|
if(
|
|
debug != 1 // && scanl >= 224
|
|
)
|
|
{
|
|
;
|
|
}
|
|
return( INT_IRQ );
|
|
}
|
|
|
|
|
|
/*====================================================================
|
|
void Z80Patch( register Z80Regs *regs )
|
|
|
|
Write here your patches to some z80 opcodes that are quite related
|
|
to the emulated machines (i.e. maybe accessing to the I/O ports
|
|
and so on), such as ED_FE opcode:
|
|
|
|
case ED_FE: Z80Patch(regs);
|
|
break;
|
|
|
|
This allows "BIOS" patching (cassette loading, keyboard ...).
|
|
===================================================================*/
|
|
void Z80Patch( register Z80Regs *regs )
|
|
{
|
|
|
|
///!!! if( tapfile != NULL )
|
|
///!!! {
|
|
///!!! LoadTAP( regs, tapfile );
|
|
///!!! POP(PC);
|
|
///!!! }
|
|
|
|
/*
|
|
if( strlen(tapfile) != 0 )
|
|
{
|
|
if( LoadTapFile( regs, tapfile ) )
|
|
{ POP(PC); }
|
|
}
|
|
else
|
|
{
|
|
FileMenu( tfont, 3, tapfile );
|
|
if( strlen(tapfile) != 0 )
|
|
if( LoadTapFile( regs, tapfile ) )
|
|
{ POP(PC); }
|
|
}
|
|
*/
|
|
}
|
|
|
|
|
|
/*====================================================================
|
|
byte Z80Debug( register Z80Regs *regs )
|
|
|
|
This function is written for debugging purposes (it's supposed to
|
|
be a debugger by itself!). It will debug a single opcode, given
|
|
by the current PC address.
|
|
|
|
Return DEBUG_OK to state success and DEBUG_QUIT to quit emulation.
|
|
===================================================================*/
|
|
byte Z80Debug( register Z80Regs *regs )
|
|
{
|
|
return( DEBUG_QUIT );
|
|
}
|
|
|
|
|
|
|
|
/*====================================================================
|
|
byte Z80MemRead( register word address )
|
|
|
|
This function reads from the given memory address. It is not inlined,
|
|
and it's written for debugging purposes.
|
|
===================================================================*/
|
|
byte Z80MemRead( register word address, Z80Regs *regs )
|
|
{
|
|
return(Z80ReadMem(address));
|
|
}
|
|
|
|
|
|
/*====================================================================
|
|
void Z80MemWrite( register word address, register byte value )
|
|
|
|
This function writes on memory the given value. It is not inlined,
|
|
ands it's written for debugging purposes.
|
|
===================================================================*/
|
|
void Z80MemWrite( register word address, register byte value, Z80Regs *regs )
|
|
{
|
|
Z80WriteMem( address, value, regs );
|
|
}
|
|
|
|
|
|
/*====================================================================
|
|
byte Z80InPort( register word port )
|
|
|
|
This function reads from the given I/O port. It is not inlined,
|
|
and it's written for debugging purposes.
|
|
===================================================================*/
|
|
byte Z80InPort( register word port )
|
|
{
|
|
int porth;
|
|
int code = 0xFF;
|
|
|
|
porth = port >> 8;
|
|
|
|
if (!(porth & 0x01)) code &= fila[4][1];
|
|
if (!(porth & 0x02)) code &= fila[3][1];
|
|
if (!(porth & 0x04)) code &= fila[2][1];
|
|
if (!(porth & 0x08)) code &= fila[1][1];
|
|
if (!(porth & 0x10)) code &= fila[1][2];
|
|
if (!(porth & 0x20)) code &= fila[2][2];
|
|
if (!(porth & 0x40)) code &= fila[3][2];
|
|
if (!(porth & 0x80)) code &= fila[4][2];
|
|
|
|
/*
|
|
issue 2 emulation, thx to Raul Gomez!!!!!
|
|
I should implement this also:
|
|
if( !ear_on && mic_on )
|
|
code &= 0xbf;
|
|
where earon = bit 4 of the last OUT to the 0xFE port
|
|
and micon = bit 3 of the last OUT to the 0xFE port
|
|
*/
|
|
code &= 0xbf;
|
|
|
|
if( (port & 0xFF) == 0xFF )
|
|
{
|
|
if( (rand() % 10) > 7 ) return(0xff);
|
|
else return( rand()%0xFF );
|
|
}
|
|
|
|
return( code );
|
|
}
|
|
|
|
|
|
/*====================================================================
|
|
void Z80OutPort( register word port, register byte value )
|
|
|
|
This function outs a value to a given I/O port. It is not inlined,
|
|
and it's written for debugging purposes.
|
|
===================================================================*/
|
|
void Z80OutPort( register Z80Regs *regs,
|
|
register word port, register byte value )
|
|
{
|
|
/* change border colour */
|
|
if( ! (port & 0x01) )
|
|
regs->BorderColor = (value & 0x07);
|
|
}
|
|
|
|
|
|
|
|
/*====================================================================
|
|
static void Z80FlagTables ( void );
|
|
|
|
Creates a look-up table for future flag setting...
|
|
Taken from fuse's sources. Thanks to Philip Kendall.
|
|
===================================================================*/
|
|
void Z80FlagTables(void)
|
|
{
|
|
int i,j,k;
|
|
byte parity;
|
|
|
|
for(i=0;i<0x100;i++) {
|
|
sz53_table[i]= i & ( FLAG_3 | FLAG_5 | FLAG_S );
|
|
j=i; parity=0;
|
|
for(k=0;k<8;k++) { parity ^= j & 1; j >>=1; }
|
|
parity_table[i]= ( parity ? 0 : FLAG_P );
|
|
sz53p_table[i] = sz53_table[i] | parity_table[i];
|
|
}
|
|
|
|
sz53_table[0] |= FLAG_Z;
|
|
sz53p_table[0] |= FLAG_Z;
|
|
}
|
|
|