saturnng/src/cpu.c
2024-03-20 16:25:41 +01:00

3185 lines
71 KiB
C

/* -------------------------------------------------------------------------
saturn - A poor-man's emulator of some HP calculators
Copyright (C) 1998-2000 Ivan Cibrario Bertolotti
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 the documentation of this program; if not, write to
the Free Software Foundation, Inc.,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
For more information, please contact the author, preferably by email,
at the following address:
Ivan Cibrario Bertolotti
IRITI - National Research Council
c/o IEN "Galileo Ferraris"
Strada delle Cacce, 91
10135 - Torino (ITALY)
email: cibrario@iriti.cnr.it
------------------------------------------------------------------------- */
/* +-+ */
/* .+
.identifier : $Id: cpu.c,v 4.1 2000/12/11 09:54:19 cibrario Rel $
.context : SATURN, Saturn CPU / HP48 emulator
.title : $RCSfile: cpu.c,v $
.kind : C source
.author : Ivan Cibrario B.
.site : CSTV-CNR
.creation : 2-Feb-1998
.keywords : *
.description :
This file executes the Saturn CPU opcodes. References:
SASM.DOC by HP (HORN disk 4)
Guide to the Saturn Processor Rev. 0.00f by Matthew Mastracci
entries.srt by Mika Heiskanen (mheiskan@vipunen.hut.fi)
x48 source code by Eddie C. Dost (ecd@dressler.de)
.include : config.h, machdep.h, cpu.h
.notes :
$Log: cpu.c,v $
Revision 4.1 2000/12/11 09:54:19 cibrario
Public release.
Revision 3.14 2000/11/13 10:30:04 cibrario
Implemented fast load/save; improved keyboard interface emulation at
high emulated CPU speed:
- Added a delay loop in ExecIN(), when CPU_SLOW_IN is defined. the loop
is implemented executing the same instruction multiple times and is
needed because the HP firmware uses an active loop instead of a
timer to determine the keyboard automatic repeat rate.
- Changed initial value of cpu_status.inner_loop_max after a CPU reset,
to be as documented (that is, maximum speed).
- During CPU initialization, both shutdn and halt flags are now
resetted.
Revision 3.13 2000/11/09 11:23:12 cibrario
Revised to add file selection box GUI element, CPU halt/run
requests and emulator's extended functions:
- Implemented CpuHaltRequest(), CpuRunRequest(), CpuHaltAllowed()
Revision 3.10 2000/10/24 16:14:28 cibrario
Added/Replaced GPL header
Revision 3.5 2000/10/02 09:42:09 cibrario
Linux support:
- gcc does not like array subscripts with type 'char', and it is right.
Revision 3.1 2000/09/20 13:39:18 cibrario
Minor updates and fixes to avoid gcc compiler warnings on Solaris
when -ansi -pedantic -Wall options are selected.
* Revision 1.2 2000/09/07 14:31:34 cibrario
* Bug fix: cpu_status.return_sp and .reset_req were not reset; this gave
* troubles when attempting to override a corrupt status with CpuReset().
*
* Revision 1.1 1998/02/17 15:25:16 cibrario
* Initial revision
*
.- */
#ifndef lint
static char rcs_id[] = "$Id: cpu.c,v 4.1 2000/12/11 09:54:19 cibrario Rel $";
#endif
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
#include <string.h>
#include "config.h"
#include "machdep.h"
#include "cpu.h"
#include "modules.h"
#include "keyb.h"
#include "disk_io.h" /* 3.1: ReadStructFromFile/WriteStructToFile */
#include "args.h"
#include "debug.h"
#define CHF_MODULE_ID CPU_CHF_MODULE_ID
#include <Chf.h>
#define GetNibble FetchNibble
/*---------------------------------------------------------------------------
Global variables
---------------------------------------------------------------------------*/
struct CpuStatus cpu_status;
/*---------------------------------------------------------------------------
Private variables
---------------------------------------------------------------------------*/
/* Field selector indexes, lo/hi nibble.
NOTE: The P and WP elements of the array must be dynamically adjusted
since they depend on the current value of the P CPU register
*/
static const int fs_idx_lo[N_FS] =
/* P, WP, XS, X, S, M, B, W
??, ??, ??, ??, ??, ??, ??, A
*/
{ 0, 0, 2, 0, 15, 3, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
};
static const int fs_idx_hi[N_FS] =
/* P, WP, XS, X, S, M, B, W
??, ??, ??, ??, ??, ??, ??, A
*/
{ 0, 0, 2, 2, 15, 14, 1, 15,
0, 0, 0, 0, 0, 0, 0, 4
};
/* Register Pair pointers */
static Nibble *const reg_pair_0[] =
/* AB, BC, CA, DC */
{ cpu_status.A, cpu_status.B, cpu_status.C, cpu_status.D };
static Nibble *const reg_pair_1[] =
/* AB, BC, CA, DC */
{ cpu_status.B, cpu_status.C, cpu_status.A, cpu_status.C };
/* Nibble bit masks */
static const Nibble nibble_bit_mask[] =
{ 0x1, 0x2, 0x4, 0x8 };
/* ProgramStatusRegister bit masks */
static const ProgramStatusRegister st_bit_mask[] =
{ 0x0001, 0x0002, 0x0004, 0x0008,
0x0010, 0x0020, 0x0040, 0x0080,
0x0100, 0x0200, 0x0400, 0x0800,
0x1000, 0x2000, 0x4000, 0x8000
};
/* Decimal sum/carry tables, range 0..31 */
static const int dec_sum[] =
{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
0, 1
};
static const int dec_carry[] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1
};
/* Decimal sub/borrow tables, range -10..15 */
static const int dec_sub_t[] =
{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
0, 1, 2, 3, 4, 5
};
static const int dec_borrow_t[] =
{
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0
};
static const int *const dec_sub = dec_sub_t + 10;
static const int *const dec_borrow = dec_borrow_t + 10;
/* Decimal one's complement table */
static const int dec_one_c[] =
{
9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
0, 0, 0, 0, 0
};
/*---------------------------------------------------------------------------
Private functions: return stack handling
---------------------------------------------------------------------------*/
/* PushRSTK */
static void PushRSTK(const Address r)
{
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "PushRSTK");
cpu_status.return_stack[cpu_status.return_sp] = r;
cpu_status.return_sp = (cpu_status.return_sp+1) & RETURN_SP_MASK;
}
/* PopRSTK */
static Address PopRSTK(void)
{
Address r;
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "PopRSTK");
cpu_status.return_sp = (cpu_status.return_sp-1) & RETURN_SP_MASK;
r = cpu_status.return_stack[cpu_status.return_sp];
cpu_status.return_stack[cpu_status.return_sp] = (Address)0;
return r;
}
/*---------------------------------------------------------------------------
Private functions: interrupt handling
---------------------------------------------------------------------------*/
/* RTI */
static void ExecRTI(void)
{
debug1(DEBUG_C_TRACE|DEBUG_C_INT, CPU_I_CALLED, "ExecRTI");
if(cpu_status.int_pending != INT_REQUEST_NONE)
{
debug1(DEBUG_C_INT, CPU_I_RTI_LOOP,
(cpu_status.int_pending == INT_REQUEST_NMI ? "NMI" : "IRQ"));
/* Service immediately any pending interrupt request */
cpu_status.int_service = 1;
cpu_status.int_pending = INT_REQUEST_NONE;
cpu_status.PC = INT_HANDLER_PC;
}
else
{
/* Reenable interrupts and return */
debug0(DEBUG_C_INT, CPU_I_RTI_END);
cpu_status.int_service = 0;
cpu_status.PC = PopRSTK();
}
}
/* RSI */
static void ExecRSI(void)
{
debug1(DEBUG_C_TRACE|DEBUG_C_INT, CPU_I_CALLED, "ExecRSI");
/* Discard last nibble of RSI opcode */
cpu_status.PC++;
KeybRSI();
}
/* INTON */
static void ExecINTON(void)
{
debug1(DEBUG_C_TRACE|DEBUG_C_INT, CPU_I_CALLED, "ExecINTON");
/* Enable maskable interrupts */
cpu_status.int_enable = 1;
}
/* INTOFF */
static void ExecINTOFF(void)
{
debug1(DEBUG_C_TRACE|DEBUG_C_INT, CPU_I_CALLED, "ExecINTOFF");
cpu_status.int_enable = 0;
}
/*---------------------------------------------------------------------------
Private functions: bus input/output
---------------------------------------------------------------------------*/
/* BUSCB */
static void ExecBUSCB(void)
{
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "ExecBUSCB");
ChfCondition CPU_F_INTERR, CHF_WARNING, "BUSCB" ChfEnd;
ChfSignal();
}
/* BUSCC */
static void ExecBUSCC(void)
{
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "ExecBUSCC");
ChfCondition CPU_F_INTERR, CHF_WARNING, "BUSCC" ChfEnd;
ChfSignal();
}
/* BUSCD */
static void ExecBUSCD(void)
{
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "ExecBUSCD");
ChfCondition CPU_F_INTERR, CHF_WARNING, "BUSCD" ChfEnd;
ChfSignal();
}
/* SREQ */
static void ExecSREQ(void)
{
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "ExecSREQ");
ChfCondition CPU_F_INTERR, CHF_WARNING, "SREQ" ChfEnd;
ChfSignal();
}
/* OUTC */
static void ExecOUTC(void)
{
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "ExecOUTC");
cpu_status.OUT =
((OutputRegister)cpu_status.C[0]) |
((OutputRegister)cpu_status.C[1] << 4) |
((OutputRegister)cpu_status.C[2] << 8);
}
/* OUTCS */
static void ExecOUTCS(void)
{
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "ExecOUTCS");
cpu_status.OUT =
((OutputRegister)cpu_status.C[0]) | (cpu_status.OUT & 0xFF0);
}
/* IN */
static void ExecIN(Nibble *r)
{
/* In */
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "ExecIN");
#ifdef CPU_SLOW_IN
/* We must slow the A=IN and C=IN instruction down a bit, depending
on the emulated CPU speed. This is necessary because the HP firmware
uses an active loop instead of a timer to determine the keyboard
automatic repeat rate.
Since implementing a precise, tiny (~ 1 microsecond), passive delay
in unix is almost impossible, we chose to execute the same
instruction (A=IN or C=IN) multiple times by artificially resetting
the PC as appropriate.
The number of repetions depends linearly, with gain CPU_SLOW_IN,
from the current value of cpu_status.inner_loop:
cpu_status.inner_loop==INNER_LOOP_MAX corresponds to the nominal
CPU speed of 4MHz and to a repetition rate of 1 (instructions are
executed once as usual).
*/
{
static int count_down = 0;
/* Decrement counter; set PC back and return immediately if counter
was not zero (counter not expired yet).
*/
if(count_down-- != 0)
{
cpu_status.PC -= 3;
return;
}
/* Counter expired; reset counter and execute the instruction */
count_down = ((cpu_status.inner_loop + (INNER_LOOP_MAX/2))
/ INNER_LOOP_MAX) * CPU_SLOW_IN;
}
#endif
cpu_status.IN = KeybIN(cpu_status.OUT);
r[0] = (Nibble)(cpu_status.IN & NIBBLE_MASK);
r[1] = (Nibble)((cpu_status.IN) >> 4 & NIBBLE_MASK);
r[2] = (Nibble)((cpu_status.IN) >> 8 & NIBBLE_MASK);
r[3] = (Nibble)((cpu_status.IN) >> 12 & NIBBLE_MASK);
}
/*---------------------------------------------------------------------------
Private functions: CPU control
---------------------------------------------------------------------------*/
static void ExecSHUTDN(void)
{
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "SHUTDN");
#ifdef CPU_SPIN_SHUTDN
/* If the CPU_SPIN_SHUTDN symbol is defined, the CPU module implements
SHUTDN as a spin loop; the program counter is reset to the starting
nibble of the SHUTDN opcode.
*/
cpu_status.PC -= 3;
#endif
/* Set shutdown flag */
cpu_status.shutdn = 1;
#ifndef CPU_SPIN_SHUTDN
/* If the CPU_SPIN_SHUTDN symbol is not defined, the CPU module implements
SHUTDN signalling the condition CPU_I_SHUTDN
*/
ChfCondition CPU_I_SHUTDN, CHF_INFO ChfEnd;
ChfSignal();
#endif
}
/*---------------------------------------------------------------------------
Private functions: data type conversions
---------------------------------------------------------------------------*/
/* Copies the A field of a DataRegister into an Address; this is not a
loop to achieve greater execution speed.
*/
static Address R2Addr(const Nibble *r)
{
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "R2Addr");
return(
((Address)r[0] ) |
((Address)r[1] << 4) |
((Address)r[2] << 8) |
((Address)r[3] << 12) |
((Address)r[4] << 16)
);
}
/* Returns the nibs 0-3 of a DataRegister into an Address; this is not a
loop to achieve greater execution speed.
*/
static Address R2AddrS(const Nibble *r)
{
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "R2AddrS");
return(
((Address)r[0] ) |
((Address)r[1] << 4) |
((Address)r[2] << 8) |
((Address)r[3] << 12)
);
}
/* Copies an Address into the A field of a register; this is not a loop
to achieve grater execution speed
*/
static void Addr2R(Nibble *d, Address a)
{
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "Addr2R");
d[0] = (Nibble)(a & NIBBLE_MASK); a >>= 4;
d[1] = (Nibble)(a & NIBBLE_MASK); a >>= 4;
d[2] = (Nibble)(a & NIBBLE_MASK); a >>= 4;
d[3] = (Nibble)(a & NIBBLE_MASK); a >>= 4;
d[4] = (Nibble)(a & NIBBLE_MASK);
}
/* Copies an Address into nibs 0-3 of a register; this is not a loop
to achieve grater execution speed
*/
static void Addr2RS(Nibble *d, Address a)
{
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "Addr2RS");
d[0] = (Nibble)(a & NIBBLE_MASK); a >>= 4;
d[1] = (Nibble)(a & NIBBLE_MASK); a >>= 4;
d[2] = (Nibble)(a & NIBBLE_MASK); a >>= 4;
d[3] = (Nibble)(a & NIBBLE_MASK);
}
/* Copy the 12 low-order bits of ST into C */
static void St2C(void)
{
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "St2C");
cpu_status.C[0] = (Nibble)(cpu_status.ST & NIBBLE_MASK);
cpu_status.C[1] = (Nibble)((cpu_status.ST >> 4) & NIBBLE_MASK);
cpu_status.C[2] = (Nibble)((cpu_status.ST >> 8) & NIBBLE_MASK);
}
/* Copy the 12 low-order bits of C into ST */
static void C2St(void)
{
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "C2St");
cpu_status.ST =
(ProgramStatusRegister)cpu_status.C[0] |
((ProgramStatusRegister)cpu_status.C[1] << 4) |
((ProgramStatusRegister)cpu_status.C[2] << 8) |
(cpu_status.ST & CLRST_MASK);
}
/* Exchange the 12 low-order bits of C with the 12 low-order bits of ST */
static void CStExch(void)
{
ProgramStatusRegister tst = cpu_status.ST;
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "CStExch");
cpu_status.ST =
(ProgramStatusRegister)cpu_status.C[0] |
((ProgramStatusRegister)cpu_status.C[1] << 4) |
((ProgramStatusRegister)cpu_status.C[2] << 8) |
(cpu_status.ST & CLRST_MASK);
cpu_status.C[0] = (Nibble)(tst & NIBBLE_MASK);
cpu_status.C[1] = (Nibble)((tst >> 4) & NIBBLE_MASK);
cpu_status.C[2] = (Nibble)((tst >> 8) & NIBBLE_MASK);
}
/*---------------------------------------------------------------------------
Private functions: data memory read/write
---------------------------------------------------------------------------*/
/* Read a field of a DataRegister from memory */
void ReadDAT(Nibble *d, Address s, int fs)
{
register int lo = cpu_status.fs_idx_lo[fs];
register int hi = cpu_status.fs_idx_hi[fs];
register int n;
for(n=lo; n<=hi; n++)
d[n] = ReadNibble(s++);
}
/* Read a field of a DataRegister from memory, with immediate fs */
void ReadDATImm(Nibble *d, Address s, int imm_fs)
{
register int n;
for(n=0; n<=imm_fs; n++)
d[n] = ReadNibble(s++);
}
/* Write a field of a DataRegister into memory */
void WriteDAT(Address d, const Nibble *r, int fs)
{
register int lo = cpu_status.fs_idx_lo[fs];
register int hi = cpu_status.fs_idx_hi[fs];
register int n;
for(n=lo; n<=hi; n++)
WriteNibble(d++, r[n]);
}
/* Write a field of a DataRegister into memory, with immediate fs */
void WriteDATImm(Address d, const Nibble *r, int imm_fs)
{
register int n;
for(n=0; n<=imm_fs; n++)
WriteNibble(d++, r[n]);
}
/*---------------------------------------------------------------------------
Private functions: instruction fetch/immediate register load
---------------------------------------------------------------------------*/
/* Read two nibbles in two-complement form, starting from pc */
static Address Get2Nibbles2C(Address pc)
{
Address v = (Address)GetNibble(pc) | ((Address)GetNibble(pc+1) << 4);
return (v & 0x80) ? v - 0x100 : v;
}
/* Read three nibbles in two-complement form, starting from pc */
static Address Get3Nibbles2C(Address pc)
{
Address v = (Address)GetNibble(pc) | ((Address)GetNibble(pc+1) << 4) |
((Address)GetNibble(pc+2) << 8);
return (v & 0x800) ? v - 0x1000 : v;
}
/* Read four nibbles in two-complement form, starting from pc */
static Address Get4Nibbles2C(Address pc)
{
Address v = (Address)GetNibble(pc) | ((Address)GetNibble(pc+1) << 4) |
((Address)GetNibble(pc+2) << 8) | ((Address)GetNibble(pc+3) << 12);
return (v & 0x8000) ? v - 0x10000 : v;
}
/* Read four nibbles in absolute form, starting from pc */
static Address Get5NibblesAbs(Address pc)
{
Address v = (Address)GetNibble(pc) | ((Address)GetNibble(pc+1) << 4) |
((Address)GetNibble(pc+2) << 8) | ((Address)GetNibble(pc+3) << 12) |
((Address)GetNibble(pc+4) << 16);
return v;
}
/* Fetch the lower 'n' nibbles of the D register pointed by 'd' from the
current instruction body
*/
void FetchD(Address *d, register int n)
{
register Address mask = ADDRESS_MASK;
register Address v = 0x00000;
register int shift = 0;
register int i;
for(i=0; i<n; i++)
{
v |= ((Address)GetNibble(cpu_status.PC++) << shift);
mask <<= 4;
shift += 4;
}
*d = (*d & mask) | v;
}
/* Fetch 'n'+1 nibbles of the DataRegister r from the current instruction body,
starting from the nibble pointed by the P register.
*/
void FetchR(Nibble *r, register int n)
{
register int p = (int)cpu_status.P;
register int i;
for(i=0; i<=n; i++)
{
r[p++] = GetNibble(cpu_status.PC++);
if(p >= NIBBLE_PER_REGISTER) p=0;
}
}
/*---------------------------------------------------------------------------
Private functions: P register setting
---------------------------------------------------------------------------*/
void SetP(Nibble n)
{
cpu_status.P = n;
cpu_status.fs_idx_lo[FS_P] = n;
cpu_status.fs_idx_hi[FS_P] = n;
cpu_status.fs_idx_hi[FS_WP] = n;
}
/*---------------------------------------------------------------------------
Private functions: DataRegister tests
---------------------------------------------------------------------------*/
/* ?r=s */
static void TestRREq(int rp, int fs)
{
register const Nibble *const r = reg_pair_0[rp];
register const Nibble *const s = reg_pair_1[rp];
register int lo = cpu_status.fs_idx_lo[fs];
register int hi = cpu_status.fs_idx_hi[fs];
register int n;
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "TestRREq");
for(n=lo; n<=hi; n++)
if(r[n] != s[n]) { cpu_status.carry = 0; return; };
cpu_status.carry = 1;
}
/* ?r=0 */
static void TestRZ(int rp, int fs)
{
register const Nibble *const r = reg_pair_0[rp];
register int lo = cpu_status.fs_idx_lo[fs];
register int hi = cpu_status.fs_idx_hi[fs];
register int n;
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "TestRZ");
for(n=lo; n<=hi; n++)
if(r[n] != (Nibble)0) { cpu_status.carry = 0; return; };
cpu_status.carry = 1;
}
/* ?r#s */
static void TestRRNe(int rp, int fs)
{
register const Nibble *const r = reg_pair_0[rp];
register const Nibble *const s = reg_pair_1[rp];
register int lo = cpu_status.fs_idx_lo[fs];
register int hi = cpu_status.fs_idx_hi[fs];
register int n;
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "TestRRNe");
for(n=lo; n<=hi; n++)
if(r[n] != s[n]) { cpu_status.carry = 1; return; };
cpu_status.carry = 0;
}
/* ?r#0 */
static void TestRNZ(int rp, int fs)
{
register const Nibble *const r = reg_pair_0[rp];
register int lo = cpu_status.fs_idx_lo[fs];
register int hi = cpu_status.fs_idx_hi[fs];
register int n;
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "TestRNZ");
for(n=lo; n<=hi; n++)
if(r[n] != (Nibble)0) { cpu_status.carry = 1; return; };
cpu_status.carry = 0;
}
/* ?r>s */
static void TestRRGt(int rp, int fs)
{
register const Nibble *const r = reg_pair_0[rp];
register const Nibble *const s = reg_pair_1[rp];
register int lo = cpu_status.fs_idx_lo[fs];
register int hi = cpu_status.fs_idx_hi[fs];
register int n;
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "TestRRGt");
for(n=hi; n>=lo; n--)
{
if(r[n] > s[n]) { cpu_status.carry = 1; return; };
if(r[n] < s[n]) { cpu_status.carry = 0; return; };
}
cpu_status.carry = 0;
}
/* ?r>=s */
static void TestRRGe(int rp, int fs)
{
register const Nibble *const r = reg_pair_0[rp];
register const Nibble *const s = reg_pair_1[rp];
register int lo = cpu_status.fs_idx_lo[fs];
register int hi = cpu_status.fs_idx_hi[fs];
register int n;
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "TestRRGe");
for(n=hi; n>=lo; n--)
{
if(r[n] > s[n]) { cpu_status.carry = 1; return; };
if(r[n] < s[n]) { cpu_status.carry = 0; return; };
}
cpu_status.carry = 1;
}
/* ?r<s */
static void TestRRLt(int rp, int fs)
{
register const Nibble *const r = reg_pair_0[rp];
register const Nibble *const s = reg_pair_1[rp];
register int lo = cpu_status.fs_idx_lo[fs];
register int hi = cpu_status.fs_idx_hi[fs];
register int n;
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "TestRRLt");
for(n=hi; n>=lo; n--)
{
if(r[n] < s[n]) { cpu_status.carry = 1; return; };
if(r[n] > s[n]) { cpu_status.carry = 0; return; };
}
cpu_status.carry = 0;
}
/* ?r<=s */
static void TestRRLe(int rp, int fs)
{
register const Nibble *const r = reg_pair_0[rp];
register const Nibble *const s = reg_pair_1[rp];
register int lo = cpu_status.fs_idx_lo[fs];
register int hi = cpu_status.fs_idx_hi[fs];
register int n;
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "TestRRLe");
for(n=hi; n>=lo; n--)
{
if(r[n] < s[n]) { cpu_status.carry = 1; return; };
if(r[n] > s[n]) { cpu_status.carry = 0; return; };
}
cpu_status.carry = 1;
}
/*---------------------------------------------------------------------------
Private functions: DataRegister operations
---------------------------------------------------------------------------*/
/* r=r+r */
static void AddRR(register Nibble *d,
register const Nibble *a, register const Nibble *b, int fs)
{
register int carry;
register int lo = cpu_status.fs_idx_lo[fs];
register int hi = cpu_status.fs_idx_hi[fs];
register int n;
register int s;
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "AddRR");
carry = 0;
if(cpu_status.hexmode)
{
for(n=lo; n<=hi; n++)
{
s = a[n] + b[n] + carry;
d[n] = (Nibble)(s & NIBBLE_MASK);
carry = ((s & ~NIBBLE_MASK) != 0);
}
}
else
{
for(n=lo; n<=hi; n++)
{
s = a[n] + b[n] + carry;
d[n] = dec_sum[s];
carry = dec_carry[s];
}
}
cpu_status.carry = carry;
}
/* r=r+1 */
static void IncrR(register Nibble *d, int fs)
{
register int carry;
register int lo = cpu_status.fs_idx_lo[fs];
register int hi = cpu_status.fs_idx_hi[fs];
register int n;
register int s;
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "IncrR");
carry = 1;
if(cpu_status.hexmode)
{
for(n=lo; n<=hi; n++)
{
s = d[n] + carry;
d[n] = (Nibble)(s & NIBBLE_MASK);
carry = ((s & ~NIBBLE_MASK) != 0);
}
}
else
{
for(n=lo; n<=hi; n++)
{
s = d[n] + carry;
d[n] = dec_sum[s];
carry = dec_carry[s];
}
}
cpu_status.carry = carry;
}
/* r=r-r */
static void SubRR(register Nibble *d,
register Nibble *a, register Nibble *b, int fs)
{
register int carry;
register int lo = cpu_status.fs_idx_lo[fs];
register int hi = cpu_status.fs_idx_hi[fs];
register int n;
register int s;
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "SubRR");
carry = 0;
if(cpu_status.hexmode)
{
for(n=lo; n<=hi; n++)
{
s = a[n] - b[n] - carry;
d[n] = (Nibble)(s & NIBBLE_MASK);
carry = ((s & ~NIBBLE_MASK) != 0);
}
}
else
{
for(n=lo; n<=hi; n++)
{
s = a[n] - b[n] - carry;
d[n] = dec_sub[s];
carry = dec_borrow[s];
}
}
cpu_status.carry = carry;
}
/* r=r-1 */
static void DecrR(register Nibble *d, int fs)
{
register int carry;
register int lo = cpu_status.fs_idx_lo[fs];
register int hi = cpu_status.fs_idx_hi[fs];
register int n;
register int s;
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "DecrR");
carry = 1;
if(cpu_status.hexmode)
{
for(n=lo; n<=hi; n++)
{
s = d[n] - carry;
d[n] = (Nibble)(s & NIBBLE_MASK);
carry = ((s & ~NIBBLE_MASK) != 0);
}
}
else
{
for(n=lo; n<=hi; n++)
{
s = d[n] - carry;
d[n] = dec_sub[s];
carry = dec_borrow[s];
}
}
cpu_status.carry = carry;
}
/* r=0 */
static void ClearR(register Nibble *d, int fs)
{
register int lo = cpu_status.fs_idx_lo[fs];
register int hi = cpu_status.fs_idx_hi[fs];
register int n;
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "ClearR");
for(n=lo; n<=hi; n++)
{
d[n] = (Nibble)0;
}
}
/* r=r */
static void CopyRR(register Nibble *d, register Nibble *s, int fs)
{
register int lo = cpu_status.fs_idx_lo[fs];
register int hi = cpu_status.fs_idx_hi[fs];
register int n;
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "CopyRR");
for(n=lo; n<=hi; n++)
{
d[n] = s[n];
}
}
/* rrEX */
static void ExchRR(register Nibble *d, register Nibble *s, int fs)
{
register int lo = cpu_status.fs_idx_lo[fs];
register int hi = cpu_status.fs_idx_hi[fs];
register Nibble t;
register int n;
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "ExchRR");
for(n=lo; n<=hi; n++)
{
t = d[n]; d[n] = s[n]; s[n] = t;
}
}
/* rSL */
static void ShiftLeftR(register Nibble *d, int fs)
{
register int lo = cpu_status.fs_idx_lo[fs];
register int hi = cpu_status.fs_idx_hi[fs];
register int n;
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "ShiftLeftR");
for(n=hi; n>lo; n--)
d[n] = d[n-1];
d[lo] = (Nibble)0;
}
/* rSR */
static void ShiftRightR(register Nibble *d, int fs)
{
register int lo = cpu_status.fs_idx_lo[fs];
register int hi = cpu_status.fs_idx_hi[fs];
register int n;
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "ShiftRightR");
if(d[lo] != (Nibble)0) cpu_status.HST |= HST_SB_MASK;
for(n=lo; n<hi; n++)
d[n] = d[n+1];
d[hi] = (Nibble)0;
}
/* rSRB */
static void ShiftRightBitR(register Nibble *d, int fs)
{
register int lo = cpu_status.fs_idx_lo[fs];
register int hi = cpu_status.fs_idx_hi[fs];
register int n;
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "ShiftRightBitR");
if((d[lo] & nibble_bit_mask[0]) != (Nibble)0) cpu_status.HST |= HST_SB_MASK;
for(n=lo; n<hi; n++)
{
d[n] >>= 1;
d[n] |= ((d[n+1] & nibble_bit_mask[0]) ? nibble_bit_mask[3] : 0);
}
d[hi] >>= 1;
}
/* rSLC */
static void ShiftLeftCircR(register Nibble *d, int fs)
{
register int lo = cpu_status.fs_idx_lo[fs];
register int hi = cpu_status.fs_idx_hi[fs];
register Nibble s;
register int n;
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "ShiftLeftCircR");
s = d[hi];
for(n=hi; n>lo; n--)
d[n] = d[n-1];
d[lo] = s;
}
/* rSRC */
static void ShiftRightCircR(register Nibble *d, int fs)
{
register int lo = cpu_status.fs_idx_lo[fs];
register int hi = cpu_status.fs_idx_hi[fs];
register Nibble s;
register int n;
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "ShiftRightCircR");
if((s=d[lo]) != (Nibble)0) cpu_status.HST |= HST_SB_MASK;
for(n=lo; n<hi; n++)
d[n] = d[n+1];
d[hi] = s;
}
/* r=-r */
static void TwoComplR(register Nibble *d, int fs)
{
register int carry;
register int lo = cpu_status.fs_idx_lo[fs];
register int hi = cpu_status.fs_idx_hi[fs];
register int n;
register int s;
register int nz;
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "TwoComplR");
carry = 0;
nz = 0;
if(cpu_status.hexmode)
{
for(n=lo; n<=hi; n++)
{
s = -d[n] - carry;
d[n] = (Nibble)(s & NIBBLE_MASK);
carry = ((s & ~NIBBLE_MASK) != 0);
nz = nz || (d[n] != (Nibble)0);
}
}
else
{
for(n=lo; n<=hi; n++)
{
s = -d[n] - carry;
d[n] = dec_sub[s];
carry = dec_borrow[s];
nz = nz || (d[n] != (Nibble)0);
}
}
cpu_status.carry = nz;
}
/* r=-r-1 */
static void OneComplR(register Nibble *d, int fs)
{
register int lo = cpu_status.fs_idx_lo[fs];
register int hi = cpu_status.fs_idx_hi[fs];
register int n;
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "OneComplR");
if(cpu_status.hexmode)
{
for(n=lo; n<=hi; n++)
d[n] = (0xF - d[n]) & NIBBLE_MASK;
}
else
{
for(n=lo; n<=hi; n++)
d[n] = dec_one_c[(int)d[n]];
}
cpu_status.carry = 0;
}
/* r=r&r */
static void AndRR(register Nibble *d,
register const Nibble *a, register const Nibble *b, int fs)
{
register int lo = cpu_status.fs_idx_lo[fs];
register int hi = cpu_status.fs_idx_hi[fs];
register int n;
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "AndRR");
for(n=lo; n<=hi; n++)
d[n] = a[n] & b[n];
}
/* r=r!r */
static void OrRR(register Nibble *d,
register const Nibble *a, register const Nibble *b, int fs)
{
register int lo = cpu_status.fs_idx_lo[fs];
register int hi = cpu_status.fs_idx_hi[fs];
register int n;
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "OrRR");
for(n=lo; n<=hi; n++)
d[n] = a[n] | b[n];
}
/* Add immediate value 'v'+1 to the DataRegister 'r', Field Selector 'fs',
always HEX mode
*/
static void AddRImm(Nibble *r, int fs, Nibble v)
{
register int carry;
register int lo = cpu_status.fs_idx_lo[fs];
register int hi = cpu_status.fs_idx_hi[fs];
register int n;
register int s;
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "AddRImm");
carry = (int)v + 1;
for(n=lo; n<=hi; n++)
{
s = r[n] + carry;
r[n] = (Nibble)(s & NIBBLE_MASK);
carry = ((s & ~NIBBLE_MASK) != 0);
}
cpu_status.carry = carry;
}
/* Subtract immediate value 'v'+1 from the DataRegister 'r',
Field Selector 'fs', always HEX mode
*/
static void SubRImm(register Nibble *r, int fs, Nibble v)
{
register int carry;
register int lo = cpu_status.fs_idx_lo[fs];
register int hi = cpu_status.fs_idx_hi[fs];
register int n;
register int s;
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "DecrR");
carry = (int)v + 1;
for(n=lo; n<=hi; n++)
{
s = r[n] - carry;
r[n] = (Nibble)(s & NIBBLE_MASK);
carry = ((s & ~NIBBLE_MASK) != 0);
}
cpu_status.carry = carry;
}
/*---------------------------------------------------------------------------
Private functions: DataRegister bit operations
---------------------------------------------------------------------------*/
void ExecBIT0(Nibble *r, Nibble n)
{
r[n/4] &= ~nibble_bit_mask[n%4];
}
void ExecBIT1(Nibble *r, Nibble n)
{
r[n/4] |= nibble_bit_mask[n%4];
}
void TestBIT0(Nibble *r, Nibble n)
{
cpu_status.carry = ((r[n/4] & nibble_bit_mask[n%4]) == 0);
}
void TestBIT1(Nibble *r, Nibble n)
{
cpu_status.carry = ((r[n/4] & nibble_bit_mask[n%4]) != 0);
}
/*---------------------------------------------------------------------------
Private functions: jumps/subroutine calls
---------------------------------------------------------------------------*/
/* GOYES/RTNYES */
static void ExecGOYES_RTNYES(void)
{
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "ExecGOYES_RTNYES");
if(cpu_status.carry)
{
/* Taken */
Address offset = Get2Nibbles2C(cpu_status.PC);
if(offset == 0)
/* RTNYES */
cpu_status.PC = PopRSTK();
else
cpu_status.PC += offset;
}
else
/* Not taken */
cpu_status.PC += 2;
}
/*---------------------------------------------------------------------------
Private functions: instruction stream decoding
---------------------------------------------------------------------------*/
/* ?..., GOYES/RTNYES, Test with Field Selector, opcode 9ftyy, length 5 */
static void ExecTest_9(void)
{
Nibble f = GetNibble(cpu_status.PC++);
Nibble t = GetNibble(cpu_status.PC++);
int fs = GetFS(f);
int tc = GetOC_2(f, t);
int rp = GetRP(t);
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "ExecTest_9");
/* Decode test code */
switch(tc)
{
case 0:
TestRREq(rp, fs); break;
case 1:
TestRRNe(rp, fs); break;
case 2:
TestRZ(rp, fs); break;
case 3:
TestRNZ(rp, fs); break;
case 4:
TestRRGt(rp, fs); break;
case 5:
TestRRLt(rp, fs); break;
break;
case 6:
TestRRGe(rp, fs); break;
case 7:
TestRRLe(rp, fs); break;
default:
ChfCondition CPU_F_INTERR, CHF_FATAL, "Bad_Test_Code" ChfEnd;
ChfSignal();
break;
}
/* Execute GOYES/RTNYES */
ExecGOYES_RTNYES();
}
/* ?..., GOYES/RTNYES, Test on A Fields, opcode 8Atyy, length 5 */
static void ExecTest_8A(void)
{
Nibble t = GetNibble(cpu_status.PC++);
int tc = GetOC_1(t);
int rp = GetRP(t);
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "ExecTest_8A");
/* Decode test code */
switch(tc)
{
case 0:
TestRREq(rp, FS_A); break;
case 1:
TestRRNe(rp, FS_A); break;
case 2:
TestRZ(rp, FS_A); break;
case 3:
TestRNZ(rp, FS_A); break;
default:
ChfCondition CPU_F_INTERR, CHF_FATAL, "Bad_Test_Code" ChfEnd;
ChfSignal();
break;
}
/* Execute GOYES/RTNYES */
ExecGOYES_RTNYES();
}
/* ?..., GOYES/RTNYES, Test on A Fields, opcode 8Btyy, length 5 */
static void ExecTest_8B(void)
{
Nibble t = GetNibble(cpu_status.PC++);
int tc = GetOC_1(t);
int rp = GetRP(t);
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "ExecTest_8B");
/* Decode test code */
switch(tc)
{
case 0:
TestRRGt(rp, FS_A); break;
case 1:
TestRRLt(rp, FS_A); break;
case 2:
TestRRGe(rp, FS_A); break;
case 3:
TestRRLe(rp, FS_A); break;
default:
ChfCondition CPU_F_INTERR, CHF_FATAL, "Bad_Test_Code" ChfEnd;
ChfSignal();
break;
}
/* Execute GOYES/RTNYES */
ExecGOYES_RTNYES();
}
/* ..., Register Operation with Field Selector, opcode Afo, length 3 */
static void ExecRegOp_A(void)
{
Nibble f = GetNibble(cpu_status.PC++);
Nibble o = GetNibble(cpu_status.PC++);
int fs = GetFS(f);
int oc = GetOC_2(f, o);
int rp = GetRP(o);
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "ExecRegOp_A");
/* Decode operation code */
switch(oc)
{
case 0:
AddRR(reg_pair_0[rp], reg_pair_0[rp], reg_pair_1[rp], fs); break;
case 1:
AddRR(reg_pair_0[rp], reg_pair_0[rp], reg_pair_0[rp], fs); break;
case 2:
AddRR(reg_pair_1[rp], reg_pair_1[rp], reg_pair_0[rp], fs); break;
case 3:
DecrR(reg_pair_0[rp], fs); break;
case 4:
ClearR(reg_pair_0[rp], fs); break;
case 5:
CopyRR(reg_pair_0[rp], reg_pair_1[rp], fs); break;
case 6:
CopyRR(reg_pair_1[rp], reg_pair_0[rp], fs); break;
case 7:
ExchRR(reg_pair_0[rp], reg_pair_1[rp], fs); break;
default:
ChfCondition CPU_F_INTERR, CHF_FATAL, "Bad_Operation_Code" ChfEnd;
ChfSignal();
break;
}
}
/* ..., Register Operation with Field Selector, opcode Bfo, length 3 */
static void ExecRegOp_B(void)
{
Nibble f = GetNibble(cpu_status.PC++);
Nibble o = GetNibble(cpu_status.PC++);
int fs = GetFS(f);
int oc = GetOC_2(f, o);
int rp = GetRP(o);
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "ExecRegOp_B");
/* Decode operation code */
switch(oc)
{
case 0:
SubRR(reg_pair_0[rp], reg_pair_0[rp], reg_pair_1[rp], fs); break;
case 1:
IncrR(reg_pair_0[rp], fs); break;
case 2:
SubRR(reg_pair_1[rp], reg_pair_1[rp], reg_pair_0[rp], fs); break;
case 3:
SubRR(reg_pair_0[rp], reg_pair_1[rp], reg_pair_0[rp], fs); break;
case 4:
ShiftLeftR(reg_pair_0[rp], fs); break;
case 5:
ShiftRightR(reg_pair_0[rp], fs); break;
case 6:
TwoComplR(reg_pair_0[rp], fs); break;
case 7:
OneComplR(reg_pair_0[rp], fs); break;
default:
ChfCondition CPU_F_INTERR, CHF_FATAL, "Bad_Operation_Code" ChfEnd;
ChfSignal();
break;
}
}
/* ..., Register Operation on A Fields, opcode Co, length 2 */
static void ExecRegOp_C(void)
{
Nibble o = GetNibble(cpu_status.PC++);
int oc = GetOC_1(o);
int rp = GetRP(o);
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "ExecRegOp_C");
/* Decode operation code */
switch(oc)
{
case 0:
AddRR(reg_pair_0[rp], reg_pair_0[rp], reg_pair_1[rp], FS_A); break;
case 1:
AddRR(reg_pair_0[rp], reg_pair_0[rp], reg_pair_0[rp], FS_A); break;
case 2:
AddRR(reg_pair_1[rp], reg_pair_1[rp], reg_pair_0[rp], FS_A); break;
case 3:
DecrR(reg_pair_0[rp], FS_A); break;
default:
ChfCondition CPU_F_INTERR, CHF_FATAL, "Bad_Operation_Code" ChfEnd;
ChfSignal();
break;
}
}
/* ..., Register Operation on A Fields, opcode Do, length 2 */
static void ExecRegOp_D(void)
{
Nibble o = GetNibble(cpu_status.PC++);
int oc = GetOC_1(o);
int rp = GetRP(o);
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "ExecRegOp_D");
/* Decode operation code */
switch(oc)
{
case 0:
ClearR(reg_pair_0[rp], FS_A); break;
case 1:
CopyRR(reg_pair_0[rp], reg_pair_1[rp], FS_A); break;
case 2:
CopyRR(reg_pair_1[rp], reg_pair_0[rp], FS_A); break;
case 3:
ExchRR(reg_pair_0[rp], reg_pair_1[rp], FS_A); break;
default:
ChfCondition CPU_F_INTERR, CHF_FATAL, "Bad_Operation_Code" ChfEnd;
ChfSignal();
break;
}
}
/* ..., Register Operation on A Fields, opcode Eo, length 2 */
static void ExecRegOp_E(void)
{
Nibble o = GetNibble(cpu_status.PC++);
int oc = GetOC_1(o);
int rp = GetRP(o);
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "ExecRegOp_E");
/* Decode operation code */
switch(oc)
{
case 0:
SubRR(reg_pair_0[rp], reg_pair_0[rp], reg_pair_1[rp], FS_A); break;
case 1:
IncrR(reg_pair_0[rp], FS_A); break;
case 2:
SubRR(reg_pair_1[rp], reg_pair_1[rp], reg_pair_0[rp], FS_A); break;
case 3:
SubRR(reg_pair_0[rp], reg_pair_1[rp], reg_pair_0[rp], FS_A); break;
default:
ChfCondition CPU_F_INTERR, CHF_FATAL, "Bad_Operation_Code" ChfEnd;
ChfSignal();
break;
}
}
/* ..., Register Operation on A Fields, opcode Fo, length 2 */
static void ExecRegOp_F(void)
{
Nibble o = GetNibble(cpu_status.PC++);
int oc = GetOC_1(o);
int rp = GetRP(o);
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "ExecRegOp_F");
/* Decode operation code */
switch(oc)
{
case 0:
ShiftLeftR(reg_pair_0[rp], FS_A); break;
case 1:
ShiftRightR(reg_pair_0[rp], FS_A); break;
case 2:
TwoComplR(reg_pair_0[rp], FS_A); break;
case 3:
OneComplR(reg_pair_0[rp], FS_A); break;
default:
ChfCondition CPU_F_INTERR, CHF_FATAL, "Bad_Operation_Code" ChfEnd;
ChfSignal();
break;
}
}
/* .&., .!., AND/OR Operations, opcode 0Efo, length 4 */
static void ExecAND_OR(void)
{
Nibble f = GetNibble(cpu_status.PC++);
Nibble o = GetNibble(cpu_status.PC++);
int oc = GetOC_1(o);
int rp = GetRP(o);
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "ExecAND_OR");
/* Decode operation code */
switch(oc)
{
case 0:
AndRR(reg_pair_0[rp], reg_pair_0[rp], reg_pair_1[rp], f); break;
case 1:
AndRR(reg_pair_1[rp], reg_pair_1[rp], reg_pair_0[rp], f); break;
case 2:
OrRR(reg_pair_0[rp], reg_pair_0[rp], reg_pair_1[rp], f); break;
case 3:
OrRR(reg_pair_1[rp], reg_pair_1[rp], reg_pair_0[rp], f); break;
default:
ChfCondition CPU_F_INTERR, CHF_FATAL, "Bad_Operation_Code" ChfEnd;
ChfSignal();
break;
}
}
/* Instruction Group_0 */
static void ExecGroup_0(void)
{
Nibble n = GetNibble(cpu_status.PC++);
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "ExecGroup_0");
switch(n)
{
case 0: /* RTNSXM */
cpu_status.HST |= HST_XM_MASK;
cpu_status.PC = PopRSTK();
break;
case 1: /* RTN */
cpu_status.PC = PopRSTK();
break;
case 2: /* RTNSC */
cpu_status.carry = 1;
cpu_status.PC = PopRSTK();
break;
case 3: /* RTNCC */
cpu_status.carry = 0;
cpu_status.PC = PopRSTK();
break;
case 4: /* SETHEX */
cpu_status.hexmode = 1;
break;
case 5: /* SETDEC */
cpu_status.hexmode = 0;
break;
case 6: /* RSTK=C */
PushRSTK(R2Addr(cpu_status.C));
break;
case 7: /* C=RSTK */
Addr2R(cpu_status.C, PopRSTK());
break;
case 8: /* CLRST */
cpu_status.ST &= CLRST_MASK;
break;
case 9: /* C=ST */
St2C();
break;
case 0xA: /* ST=C */
C2St();
break;
case 0xB: /* CSTEX */
CStExch();
break;
case 0xC: /* P=P+1 */
{
if(cpu_status.P == NIBBLE_MASK)
{
SetP(0);
cpu_status.carry = 1;
}
else
{
SetP(cpu_status.P+1);
cpu_status.carry = 0;
}
break;
}
case 0xD: /* P=P-1 */
{
if(cpu_status.P == (Nibble)0)
{
SetP(NIBBLE_MASK);
cpu_status.carry = 1;
}
else
{
SetP(cpu_status.P-1);
cpu_status.carry = 0;
}
break;
}
case 0xE: /* AND_OR */
ExecAND_OR();
break;
case 0xF: /* RTI */
ExecRTI();
break;
default: /* Unknown opcode */
ChfCondition CPU_E_BAD_OPCODE, CHF_ERROR, cpu_status.PC, n ChfEnd;
ChfSignal();
break;
}
}
/* Instruction Group_1 */
static void ExecGroup_1(void)
{
Nibble n = GetNibble(cpu_status.PC++);
Nibble f;
int rn, ac;
int oc, is;
Address ta;
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "ExecGroup_1");
switch(n)
{
case 0: /* Rn=A/C */
n = GetNibble(cpu_status.PC++);
rn = GetRn(n);
ac = GetAC(n);
CopyRR(cpu_status.R[rn], (ac ? cpu_status.C : cpu_status.A), FS_W);
break;
case 1: /* A/C=Rn */
n = GetNibble(cpu_status.PC++);
rn = GetRn(n);
ac = GetAC(n);
CopyRR((ac ? cpu_status.C : cpu_status.A), cpu_status.R[rn], FS_W);
break;
case 2:
/* ARnEX, CRnEX */
n = GetNibble(cpu_status.PC++);
rn = GetRn(n);
ac = GetAC(n);
ExchRR((ac ? cpu_status.C : cpu_status.A), cpu_status.R[rn], FS_W);
break;
case 3:
/* Copy/Exchange A/C and D0/D1 */
switch(GetNibble(cpu_status.PC++))
{
case 0: /* D0=A */
cpu_status.D0 = R2Addr(cpu_status.A);
break;
case 1: /* D1=A */
cpu_status.D1 = R2Addr(cpu_status.A);
break;
case 2: /* AD0EX */
ta = cpu_status.D0;
cpu_status.D0 = R2Addr(cpu_status.A);
Addr2R(cpu_status.A, ta);
break;
case 3: /* AD1EX */
ta = cpu_status.D1;
cpu_status.D1 = R2Addr(cpu_status.A);
Addr2R(cpu_status.A, ta);
break;
case 4: /* D0=C */
cpu_status.D0 = R2Addr(cpu_status.C);
break;
case 5: /* D1=C */
cpu_status.D1 = R2Addr(cpu_status.C);
break;
case 6: /* CD0EX */
ta = cpu_status.D0;
cpu_status.D0 = R2Addr(cpu_status.C);
Addr2R(cpu_status.C, ta);
break;
case 7: /* CD1EX */
ta = cpu_status.D1;
cpu_status.D1 = R2Addr(cpu_status.C);
Addr2R(cpu_status.C, ta);
break;
case 8: /* D0=AS */
cpu_status.D0 = R2AddrS(cpu_status.A) | (cpu_status.D0 & D_S_MASK);
break;
case 9: /* D1=AS */
cpu_status.D1 = R2AddrS(cpu_status.A) | (cpu_status.D1 & D_S_MASK);
break;
case 0xA: /* AD0XS */
ta = cpu_status.D0;
cpu_status.D0 = R2AddrS(cpu_status.A) | (cpu_status.D0 & D_S_MASK);
Addr2RS(cpu_status.A, ta);
break;
case 0xB: /* AD1XS */
ta = cpu_status.D1;
cpu_status.D1 = R2AddrS(cpu_status.A) | (cpu_status.D1 & D_S_MASK);
Addr2RS(cpu_status.A, ta);
break;
case 0xC: /* D0=CS */
cpu_status.D0 = R2AddrS(cpu_status.C) | (cpu_status.D0 & D_S_MASK);
break;
case 0xD: /* D1=CS */
cpu_status.D1 = R2AddrS(cpu_status.C) | (cpu_status.D1 & D_S_MASK);
break;
case 0xE: /* CD0XS */
ta = cpu_status.D0;
cpu_status.D0 = R2AddrS(cpu_status.C) | (cpu_status.D0 & D_S_MASK);
Addr2RS(cpu_status.C, ta);
break;
case 0xF: /* CD1XS */
ta = cpu_status.D1;
cpu_status.D1 = R2AddrS(cpu_status.C) | (cpu_status.D1 & D_S_MASK);
Addr2RS(cpu_status.C, ta);
break;
}
break;
case 4:
/* Load/Store A/C to @D0/@D1, Field selector A or B */
switch(GetNibble(cpu_status.PC++))
{
case 0: /* DAT0=A A */
WriteDAT(cpu_status.D0, cpu_status.A, FS_A);
break;
case 1: /* DAT1=A A */
WriteDAT(cpu_status.D1, cpu_status.A, FS_A);
break;
case 2: /* A=DAT0 A */
ReadDAT(cpu_status.A, cpu_status.D0, FS_A);
break;
case 3: /* A=DAT1 A */
ReadDAT(cpu_status.A, cpu_status.D1, FS_A);
break;
case 4: /* DAT0=C A */
WriteDAT(cpu_status.D0, cpu_status.C, FS_A);
break;
case 5: /* DAT1=C A */
WriteDAT(cpu_status.D1, cpu_status.C, FS_A);
break;
case 6: /* C=DAT0 A */
ReadDAT(cpu_status.C, cpu_status.D0, FS_A);
break;
case 7: /* C=DAT1 A */
ReadDAT(cpu_status.C, cpu_status.D1, FS_A);
break;
case 8: /* DAT0=A B */
WriteDAT(cpu_status.D0, cpu_status.A, FS_B);
break;
case 9: /* DAT1=A B */
WriteDAT(cpu_status.D1, cpu_status.A, FS_B);
break;
case 0xA: /* A=DAT0 B */
ReadDAT(cpu_status.A, cpu_status.D0, FS_B);
break;
case 0xB: /* A=DAT1 B */
ReadDAT(cpu_status.A, cpu_status.D1, FS_B);
break;
case 0xC: /* DAT0=C B */
WriteDAT(cpu_status.D0, cpu_status.C, FS_B);
break;
case 0xD: /* DAT1=C B */
WriteDAT(cpu_status.D1, cpu_status.C, FS_B);
break;
case 0xE: /* C=DAT0 B */
ReadDAT(cpu_status.C, cpu_status.D0, FS_B);
break;
case 0xF: /* C=DAT1 B */
ReadDAT(cpu_status.C, cpu_status.D1, FS_B);
break;
}
break;
case 5:
/* Load/Store A/C to @D0/@D1, Other Field Selectors */
n = GetNibble(cpu_status.PC++);
f = GetNibble(cpu_status.PC++);
oc = GetOC_3b(n);
is = GetImmFS(n);
switch(oc)
{
case 0: /* DAT0=A */
if(is)
WriteDATImm(cpu_status.D0, cpu_status.A, f);
else
WriteDAT(cpu_status.D0, cpu_status.A, f);
break;
case 1: /* DAT1=A */
if(is)
WriteDATImm(cpu_status.D1, cpu_status.A, f);
else
WriteDAT(cpu_status.D1, cpu_status.A, f);
break;
case 2: /* A=DAT0 */
if(is)
ReadDATImm(cpu_status.A, cpu_status.D0, f);
else
ReadDAT(cpu_status.A, cpu_status.D0, f);
break;
case 3: /* A=DAT1 */
if(is)
ReadDATImm(cpu_status.A, cpu_status.D1, f);
else
ReadDAT(cpu_status.A, cpu_status.D1, f);
break;
case 4: /* DAT0=C */
if(is)
WriteDATImm(cpu_status.D0, cpu_status.C, f);
else
WriteDAT(cpu_status.D0, cpu_status.C, f);
break;
case 5: /* DAT1=C */
if(is)
WriteDATImm(cpu_status.D1, cpu_status.C, f);
else
WriteDAT(cpu_status.D1, cpu_status.C, f);
break;
case 6: /* C=DAT0 */
if(is)
ReadDATImm(cpu_status.C, cpu_status.D0, f);
else
ReadDAT(cpu_status.C, cpu_status.D0, f);
break;
case 7: /* C=DAT1 */
if(is)
ReadDATImm(cpu_status.C, cpu_status.D1, f);
else
ReadDAT(cpu_status.C, cpu_status.D1, f);
break;
default:
ChfCondition CPU_F_INTERR, CHF_FATAL, "Bad_Operation_Code" ChfEnd;
ChfSignal();
break;
}
break;
case 6:
/* D0=D0+n+1 */
n = GetNibble(cpu_status.PC++);
ta = (cpu_status.D0 + n + 1) & ADDRESS_MASK;
cpu_status.carry = (ta < cpu_status.D0);
cpu_status.D0 = ta;
break;
case 7:
/* D1=D1+n+1 */
n = GetNibble(cpu_status.PC++);
ta = (cpu_status.D1 + n + 1) & ADDRESS_MASK;
cpu_status.carry = (ta < cpu_status.D1);
cpu_status.D1 = ta;
break;
case 8:
/* D0=D0-(n+1) */
n = GetNibble(cpu_status.PC++);
ta = (cpu_status.D0 - n - 1) & ADDRESS_MASK;
cpu_status.carry = (ta > cpu_status.D0);
cpu_status.D0 = ta;
break;
case 9:
/* D0=(2) nn */
FetchD(&cpu_status.D0, 2);
break;
case 0xA:
/* D0=(4) nn */
FetchD(&cpu_status.D0, 4);
break;
case 0xB:
/* D0=(5) nn */
FetchD(&cpu_status.D0, 5);
break;
case 0xC:
/* D1=D1-(n+1) */
n = GetNibble(cpu_status.PC++);
ta = (cpu_status.D1 - n - 1) & ADDRESS_MASK;
cpu_status.carry = (ta > cpu_status.D1);
cpu_status.D1 = ta;
break;
case 0xD:
/* D1=(2) nn */
FetchD(&cpu_status.D1, 2);
break;
case 0xE:
/* D1=(4) nn */
FetchD(&cpu_status.D1, 4);
break;
case 0xF:
/* D1=(5) nn */
FetchD(&cpu_status.D1, 5);
break;
default:
/* Unknown opcode */
ChfCondition CPU_E_BAD_OPCODE, CHF_ERROR, cpu_status.PC, n ChfEnd;
ChfSignal();
break;
}
}
/* Instruction Group_808 */
static void ExecGroup_808(void)
{
Nibble n = GetNibble(cpu_status.PC++);
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "ExecGroup_808");
switch(n)
{
case 0: /* INTON */
ExecINTON();
break;
case 1: /* RSI */
ExecRSI();
break;
case 2: /* LA(m) n..n */
FetchR(cpu_status.A, GetNibble(cpu_status.PC++));
break;
case 3: /* BUSCB */
ExecBUSCB();
break;
case 4: /* ABIT=0 d */
ExecBIT0(cpu_status.A, GetNibble(cpu_status.PC++));
break;
case 5: /* ABIT=1 d */
ExecBIT1(cpu_status.A, GetNibble(cpu_status.PC++));
break;
case 6: /* ?ABIT=0 d */
TestBIT0(cpu_status.A, GetNibble(cpu_status.PC++));
ExecGOYES_RTNYES();
break;
case 7: /* ?ABIT=1 d */
TestBIT1(cpu_status.A, GetNibble(cpu_status.PC++));
ExecGOYES_RTNYES();
break;
case 8: /* CBIT=0 d */
ExecBIT0(cpu_status.C, GetNibble(cpu_status.PC++));
break;
case 9: /* CBIT=1 d */
ExecBIT1(cpu_status.C, GetNibble(cpu_status.PC++));
break;
case 0xA: /* ?CBIT=0 d */
TestBIT0(cpu_status.C, GetNibble(cpu_status.PC++));
ExecGOYES_RTNYES();
break;
case 0xB: /* ?CBIT=1 d */
TestBIT1(cpu_status.C, GetNibble(cpu_status.PC++));
ExecGOYES_RTNYES();
break;
case 0xC: /* PC=(A) */
cpu_status.PC = Get5NibblesAbs(R2Addr(cpu_status.A));
break;
case 0xD:
/* BUSCD */
ExecBUSCD();
break;
case 0xE:
/* PC=(C) */
cpu_status.PC = Get5NibblesAbs(R2Addr(cpu_status.C));
break;
case 0xF:
/* INTOFF */
ExecINTOFF();
break;
default:
/* Unknown opcode */
ChfCondition CPU_E_BAD_OPCODE, CHF_ERROR, cpu_status.PC, n ChfEnd;
ChfSignal();
break;
}
}
/* Instruction Group_80 */
static void ExecGroup_80(void)
{
Nibble n = GetNibble(cpu_status.PC++);
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "ExecGroup_80");
switch(n)
{
case 0: /* OUT=CS */
ExecOUTCS();
break;
case 1: /* OUT=C */
ExecOUTC();
break;
case 2: /* A=IN */
ExecIN(cpu_status.A);
break;
case 3: /* C=IN */
ExecIN(cpu_status.C);
break;
case 4: /* UNCNFG */
ModUnconfig(R2Addr(cpu_status.C));
break;
case 5: /* CONFIG */
ModConfig(R2Addr(cpu_status.C));
break;
case 6: /* C=ID */
Addr2R(cpu_status.C, ModGetID());
break;
case 7: /* SHUTDN */
ExecSHUTDN();
break;
case 8: /* Group 808 */
ExecGroup_808();
break;
case 9: /* C+P+1 */
AddRImm(cpu_status.C, FS_A, cpu_status.P);
break;
case 0xA: /* RESET */
ModReset();
break;
case 0xB: /* BUSCC */
ExecBUSCC();
break;
case 0xE: /* SREQ? */
ExecSREQ();
break;
case 0xC: /* C=P n */
cpu_status.C[(int)GetNibble(cpu_status.PC++)] = cpu_status.P;
break;
case 0xD: /* P=C n */
SetP(cpu_status.C[(int)GetNibble(cpu_status.PC++)]);
break;
case 0xF: /* CPEX */
{
Nibble t;
n = GetNibble(cpu_status.PC++);
t = cpu_status.P;
SetP(cpu_status.C[(int)n]);
cpu_status.C[(int)n] = t;
break;
}
default:
ChfCondition CPU_E_BAD_OPCODE, CHF_ERROR, cpu_status.PC, n ChfEnd;
ChfSignal();
break;
}
}
/* Special functions Group_81 */
static void ExecSpecialGroup_81(int rp)
{
Nibble n, f, m;
int rn, ac;
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "ExecSpecialGroup_81");
switch(rp)
{
case 0: /* r=r+-CON fs, d */
f = GetNibble(cpu_status.PC++);
n = GetNibble(cpu_status.PC++);
m = GetNibble(cpu_status.PC++);
rp = GetRP(n);
if(GetAS(n)) /* Subtract */
SubRImm(reg_pair_0[rp], f, m);
else /* Add */
AddRImm(reg_pair_0[rp], f, m);
break;
case 1: /* rSRB.f fs */
f = GetNibble(cpu_status.PC++);
n = GetNibble(cpu_status.PC++);
rp = GetRP(n);
ShiftRightBitR(reg_pair_0[rp], f);
break;
case 2: /* Rn=r.F fs, r=R0.F fs, rRnEX.F fs */
f = GetNibble(cpu_status.PC++);
n = GetNibble(cpu_status.PC++);
m = GetNibble(cpu_status.PC++);
rn = GetRn(m);
ac = GetAC(m);
switch(n)
{
case 0: /* Rn=r.F fs */
CopyRR(cpu_status.R[rn], (ac ? cpu_status.C : cpu_status.A), f);
break;
case 1: /* r=R0.F fs */
CopyRR((ac ? cpu_status.C : cpu_status.A), cpu_status.R[rn], f);
break;
case 2: /* rRnEX.F fs */
ExchRR((ac ? cpu_status.C : cpu_status.A), cpu_status.R[rn], f);
break;
default:
ChfCondition CPU_E_BAD_OPCODE, CHF_ERROR, cpu_status.PC, n ChfEnd;
ChfSignal();
break;
}
break;
case 3: /* Group 81B */
switch(n = GetNibble(cpu_status.PC++))
{
case 2: /* PC=A */
cpu_status.PC = R2Addr(cpu_status.A);
break;
case 3: /* PC=C */
cpu_status.PC = R2Addr(cpu_status.C);
break;
case 4: /* A=PC */
Addr2R(cpu_status.A, cpu_status.PC);
break;
case 5: /* C=PC */
Addr2R(cpu_status.C, cpu_status.PC);
break;
case 6: /* APCEX */
{
Address t;
t = R2Addr(cpu_status.A);
Addr2R(cpu_status.A, cpu_status.PC);
cpu_status.PC = t;
break;
}
case 7: /* CPCEX */
{
Address t;
t = R2Addr(cpu_status.C);
Addr2R(cpu_status.C, cpu_status.PC);
cpu_status.PC = t;
break;
}
default:
ChfCondition CPU_E_BAD_OPCODE, CHF_ERROR, cpu_status.PC, n ChfEnd;
ChfSignal();
break;
}
break;
default:
ChfCondition CPU_F_INTERR, CHF_FATAL, "Bad_Register_Pair" ChfEnd;
ChfSignal();
break;
}
}
/* Instruction Group_8 */
static void ExecGroup_8(void)
{
Nibble n = GetNibble(cpu_status.PC++);
Address addr;
int oc, rp;
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "ExecGroup_8");
switch(n)
{
case 0:
ExecGroup_80();
break;
case 1: /* rSLC, rSRC, rSRB, Special Group_81 */
n = GetNibble(cpu_status.PC++);
oc = GetOC_1(n);
rp = GetRP(n);
switch(oc)
{
case 0: /* rSLC */
ShiftLeftCircR(reg_pair_0[rp], FS_W);
break;
case 1: /* rSRC */
ShiftRightCircR(reg_pair_0[rp], FS_W);
break;
case 2: /* Special Group_81 */
ExecSpecialGroup_81(rp);
break;
case 3: /* rSRB */
ShiftRightBitR(reg_pair_0[rp], FS_W);
break;
default:
ChfCondition CPU_F_INTERR, CHF_FATAL, "Bad_Operation_Code" ChfEnd;
ChfSignal();
break;
}
break;
case 2: /* CLRHSn */
cpu_status.HST &= ~GetNibble(cpu_status.PC++);
break;
case 3: /* ?HS=0 */
n = GetNibble(cpu_status.PC++);
cpu_status.carry = ((cpu_status.HST & n) == 0);
ExecGOYES_RTNYES();
break;
case 4: /* ST=0 n */
cpu_status.ST &= ~st_bit_mask[(int)GetNibble(cpu_status.PC++)];
break;
case 5: /* ST=1 n */
cpu_status.ST |= st_bit_mask[(int)GetNibble(cpu_status.PC++)];
break;
case 6: /* ?ST=0 n */
cpu_status.carry =
((cpu_status.ST & st_bit_mask[(int)GetNibble(cpu_status.PC++)]) == 0);
ExecGOYES_RTNYES();
break;
case 7: /* ?ST=1 n */
cpu_status.carry =
((cpu_status.ST & st_bit_mask[(int)GetNibble(cpu_status.PC++)]) != 0);
ExecGOYES_RTNYES();
break;
case 8: /* ?P#n */
cpu_status.carry = (cpu_status.P != GetNibble(cpu_status.PC++));
ExecGOYES_RTNYES();
break;
case 9: /* ?P=n */
cpu_status.carry = (cpu_status.P == GetNibble(cpu_status.PC++));
ExecGOYES_RTNYES();
break;
case 0xA: /* Test */
ExecTest_8A();
break;
case 0xB: /* Test */
ExecTest_8B();
break;
case 0xC: /* GOLONG */
addr = Get4Nibbles2C(cpu_status.PC);
cpu_status.PC += addr;
break;
case 0xD:
/* GOVLNG */
cpu_status.PC = Get5NibblesAbs(cpu_status.PC);
break;
case 0xE:
/* GOSUBL */
addr = Get4Nibbles2C(cpu_status.PC);
cpu_status.PC += 4;
PushRSTK(cpu_status.PC);
cpu_status.PC += addr;
break;
case 0xF:
/* GOSBVL */
PushRSTK(cpu_status.PC + 5);
cpu_status.PC = Get5NibblesAbs(cpu_status.PC);
break;
default:
ChfCondition CPU_E_BAD_OPCODE, CHF_ERROR, cpu_status.PC, n ChfEnd;
ChfSignal();
break;
}
}
/*---------------------------------------------------------------------------
Private functions: dump
---------------------------------------------------------------------------*/
const char *DumpR(Nibble *r)
{
static char b[NIBBLE_PER_REGISTER+1];
static const char hex_char[NIBBLE_PER_REGISTER] = "0123456789ABCDEF";
int n;
for(n=0; n<NIBBLE_PER_REGISTER; n++)
b[n] = hex_char[(int)r[NIBBLE_PER_REGISTER-1-n]];
b[NIBBLE_PER_REGISTER] = '\0';
return b;
}
/*---------------------------------------------------------------------------
Public functions
---------------------------------------------------------------------------*/
/* .+
.title : CpuReset
.kind : C function
.creation : 3-Feb-1998
.description :
This function resets the CPU, performing the following operations:
- Copies the field selector index arrays to the cpu_status structure
- Set P=0
- Clears registers A, B, C, D, Rn
- Clears registers D0, D1
- Sets PC to zero
- Clears registers IN, OUT, ST, HST
- Sets hex mode for arithmetic operations
- Clears carry, int_enable, int_service, int_pending, and shutdn
- The inner_loop limit is set to INNER_LOOP_MED
.call :
CpuReset();
.input :
void
.output :
void
.status_codes :
CPU_I_CALLED
CPU_E_BAD_OPCODE
CPU_F_INTERR
.notes :
1.1, 3-Feb-1998, creation
1.2, 7-Sep-2000, bug fix
- cpu_status.return_sp and .reset_req were not reset; this gave troubles
when attempting to override a corrupt status with CpuReset().
3.13, 2-Nov-2000, update
- cpu_status.halt and cpu_status.inner_loop_max need reset
3.14, 10-Nov-2000, bug fix
- cpu_status.inner_loop_max must be reset to 0, because the default
emulator speed is maximum speed.
.- */
void CpuReset(void)
{
int n;
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "CpuReset");
/* Copy field selector index arrays to the cpu_status structure */
(void)memcpy(cpu_status.fs_idx_lo, fs_idx_lo, sizeof(fs_idx_lo));
(void)memcpy(cpu_status.fs_idx_hi, fs_idx_hi, sizeof(fs_idx_hi));
/* Set P=0 and adjust fs index arrays */
SetP((Nibble)0);
/* Clear A, B, C, D */
for(n=0; n<N_WORKING_REGISTER; n++) ClearR(cpu_status.work[n], FS_W);
/* Clear Rn */
for(n=0; n<N_SCRATCH_REGISTER; n++) ClearR(cpu_status.R[n], FS_W);
/* Clear D0, D1 */
cpu_status.D0 = cpu_status.D1 = (Address)0;
/* Clear PC */
cpu_status.PC = (Address)0;
/* Clear IN, OUT, ST, HST */
cpu_status.IN = (InputRegister)0;
cpu_status.OUT = (OutputRegister)0;
cpu_status.ST = (ProgramStatusRegister)0;
cpu_status.HST = (Nibble)0;
/* Fill the return stack with (Address)0 */
cpu_status.return_sp = 0;
for(n=0; n<RETURN_STACK_SIZE; n++)
cpu_status.return_stack[n] = (Address)0;
/* Set hexmode */
cpu_status.hexmode = 1;
/* Clear carry */
cpu_status.carry = 0;
/* Disable maskable interrupts */
cpu_status.int_enable = 0;
/* No interrupts are pending (for now) */
cpu_status.int_service = 0;
cpu_status.int_pending = 0;
/* The CPU is running */
cpu_status.shutdn = cpu_status.halt = 0;
/* Set inner_loop and inner_loop_max to default values */
cpu_status.inner_loop = INNER_LOOP_MED;
cpu_status.inner_loop_max = 0;
/* Reset reset_req if necessary */
#ifdef CPU_SPIN_LOOP
cpu_status.reset_req = 0;
#endif
}
/* .+
.title : CpuInit
.kind : C function
.creation : 11-Feb-1998
.description :
This function initializes the Saturn CPU, reading its status from disk.
If something goes wrong with the disk I/O, the function resets the CPU.
.call :
CpuInit();
.input :
void
.output :
void
.status_codes :
CPU_I_CALLED
CPU_I_REVISION
CPU_W_RESETTING
.notes :
1.1, 11-Feb-1998, creation
3.14, 10-Nov-2000, update
- clear both shutdn and halt cpu flags here; this helps when the CPU
state was saved and reloaded when the CPU was halted.
.- */
void CpuInit(void)
{
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "CpuInit");
debug1(DEBUG_C_REVISION, CPU_I_REVISION, CPU_RCS_INFO);
if(ReadStructFromFile(args.cpu_file_name, sizeof(cpu_status), &cpu_status))
{
ChfCondition CPU_W_RESETTING, CHF_WARNING ChfEnd;
ChfSignal();
CpuReset();
}
/* The CPU is running */
cpu_status.shutdn = cpu_status.halt = 0;
}
/* .+
.title : CpuSave
.kind : C function
.creation : 11-Feb-1998
.description :
This function saves the current Saturn CPU status to disk.
.call :
CpuSave();
.input :
void
.output :
void
.status_codes :
CPU_I_CALLED
CPU_E_SAVE
.notes :
1.1, 11-Feb-1998, creation
.- */
void CpuSave(void)
{
debug1(DEBUG_C_TRACE, CPU_I_CALLED, "CpuSave");
if(WriteStructToFile(&cpu_status, sizeof(cpu_status), args.cpu_file_name))
{
ChfCondition CPU_E_SAVE, CHF_ERROR ChfEnd;
ChfSignal();
}
}
/* .+
.title : CpuIntRequest
.kind : C function
.creation : 11-Feb-1998
.description :
This function posts an interrupt request for the Saturn CPU.
The NMI interrupt requests are always honored; the IRQ requests are
honored immediately only if the CPU interrupts are enabled, otherwise
they will be honored as soon as the CPU reenables interrupts.
NOTE: The interrupt request can be INT_REQUEST_NONE; in this case, this
function does not post any interrupt request.
.call :
CpuIntRequest(ireq);
.input :
enum IntRequest ireq, interrupt request type, or
INT_REQUEST_NONE
.output :
void
.status_codes :
CPU_I_CALLED
CPU_I_INT
CPU_I_INT_PENDING
.notes :
1.1, 11-Feb-1998, creation
.- */
void CpuIntRequest(enum IntRequest ireq)
{
debug1(DEBUG_C_TRACE|DEBUG_C_INT, CPU_I_CALLED, "CpuIntRequest");
if((ireq == INT_REQUEST_IRQ && cpu_status.int_enable) ||
ireq == INT_REQUEST_NMI)
{
/* Wake the CPU if it's sleeping */
CpuWake();
/* Check if immediate vectoring is ok */
if(cpu_status.int_service == 0)
{
/* Vector immediately */
cpu_status.int_service = 1;
cpu_status.int_pending = INT_REQUEST_NONE;
PushRSTK(cpu_status.PC);
cpu_status.PC = INT_HANDLER_PC;
debug1(DEBUG_C_INT, CPU_I_INT,
(ireq == INT_REQUEST_NMI ? "NMI" : "IRQ"));
}
else
{
/* int_service is set; save the request for later processing */
cpu_status.int_pending = ireq;
debug1(DEBUG_C_INT, CPU_I_INT_PENDING,
(ireq == INT_REQUEST_NMI ? "NMI" : "IRQ"));
}
}
}
/* .+
.title : CpuWake
.kind : C function
.creation : 11-Feb-1998
.description :
This function awakes the CPU if it has executed a SHUTDN instruction
and no halt requests are pending (see CpuHaltRequest() for more
information).
If the CPU is running, this function has no effect.
.call :
CpuWake();
.input :
void
.output :
void
.status_codes :
CPU_I_CALLED
CPU_I_WAKE
.notes :
1.1, 11-Feb-1998, creation
3.13, 2-Nov-2000, update:
- the CPU must be awoken only if no halt request is pending
.- */
void CpuWake(void)
{
debug1(DEBUG_C_TRACE|DEBUG_C_INT, CPU_I_CALLED, "CpuWake");
if(cpu_status.shutdn)
{
if(cpu_status.halt == 0)
{
debug0(DEBUG_C_INT, CPU_I_WAKE);
/* Clear SHUTDN flag */
cpu_status.shutdn = 0;
#ifdef CPU_SPIN_SHUTDN
/* Adjust PC if SHUTDN is implemented using a spin loop */
cpu_status.PC += 3;
#endif
/* Clear PC if necessary */
/* if(cpu_status.OUT == (OutputRegister)0)
cpu_status.PC = (Address)0;
*/
}
}
}
/* .+
.title : CpuHaltRequest
.kind : C function
.creation : 2-Nov-2000
.description :
This function makes an halt request to the CPU emulator.
The halt condition is similar to the shutdn condition, and
actually forces a shutdn, but it cannot be broken by CpuWake();
only CpuRunRequest() can do this. When the CPU is halted:
- instruction execution is suspended
- IRQ and NMI service is delayed until the halt condition is broken
- timers are not updated (they will be resynchronized later)
- GUI events are handled
Multiple calls to CpuHaltRequest()/CpuRunRequest() can be nested;
the CPU remains halted as long as there are more than zero pending
halt requests.
CpuHaltRequest() relies on the presence of a suitable handler
of the CPU_I_SHUTDN condition; this is currently true only
if CpuHaltRequest() is invoked when the emulator loop is active.
Notice that setting the CPU_SPIN_SHUTDN build-time option
in config.h disables all halt requests; both CpuHaltRequest()
and CpuRunRequest() return -1 in this case.
The function returns the updated number of pending halt requests,
or -1 if halt/run requests are disabled; in the latter case,
the CPU_E_NO_HALT condition is generated and signalled, too.
The function may never return to the caller if the CPU_SPIN_SHUTDN
is handled locally by the handler, or if an unwind occurs.
.call :
ph = CpuHaltRequest();
.input :
void
.output :
int ph, updated number of pending halt requests, or
-1 if halt/run requests are disabled
.status_codes :
CPU_I_CALLED
CPU_I_HALT
CPU_E_NO_HALT
.notes :
3.13, 2-Nov-2000, creation
*/
int CpuHaltRequest(void)
{
debug1(DEBUG_C_TRACE|DEBUG_C_INT, CPU_I_CALLED, "CpuHaltRequest");
#ifdef CPU_SPIN_SHUTDN
ChfCondition CPU_E_NO_HALT, CHF_ERROR ChfEnd;
ChfSignal();
return -1;
#else
if(cpu_status.halt++ == 0)
{
debug0(DEBUG_C_INT, CPU_I_HALT);
/* CPU must actually be halted: call ExecSHUTDN() to simulate
the execution of a regular SHUTDN instruction.
CpuWake() will check .halt before clearing this condition.
*/
ExecSHUTDN();
}
return cpu_status.halt;
#endif
}
/* .+
.title : CpuRunRequest
.kind : C function
.creation : 2-Nov-2000
.description :
This function undoes exactly one CpuHaltRequest(); it has no effect
if the CPU is not halted. See CpuHaltRequest() for more information.
The function returns the updated number of pending halt requests,
or -1 if halt/run requests are disabled; in the latter case,
the CPU_W_NO_HALT condition is generated, but not signalled, too.
.call :
ph = CpuRunRequest();
.input :
void
.output :
int ph, updated number of pending halt requests, or
-1 if halt requests are disabled
.status_codes :
CPU_I_CALLED
CPU_I_RUN
CPU_E_NO_HALT
.notes :
3.13, 2-Nov-2000, creation
*/
int CpuRunRequest(void)
{
debug1(DEBUG_C_TRACE|DEBUG_C_INT, CPU_I_CALLED, "CpuRunRequest");
#ifdef CPU_SPIN_SHUTDN
ChfCondition CPU_E_NO_HALT, CHF_ERROR ChfEnd;
ChfSignal();
return -1;
#else
if(cpu_status.halt > 0)
if(--cpu_status.halt == 0)
{
debug0(DEBUG_C_INT, CPU_I_RUN);
/* CPU must actually be awoken: call CpuWake() */
CpuWake();
}
return cpu_status.halt;
#endif
}
/* .+
.title : CpuHaltAllowed
.kind : C function
.creation : 7-Nov-2000
.description :
This function return a non-zero value if CpuHaltRequest()
is allowed, zero otherwise.
.call :
s = CpuHaltRequest();
.input :
void
.output :
int s, non-zero if CpuHaltRequest() is allowed, 0 otherwise
.status_codes :
CPU_I_CALLED
.notes :
3.13, 7-Nov-2000, creation
*/
int CpuHaltAllowed(void)
{
debug1(DEBUG_C_TRACE|DEBUG_C_INT, CPU_I_CALLED, "CpuHaltAllowed");
#ifdef CPU_SPIN_SHUTDN
return 0;
#else
return 1;
#endif
}
/* .+
.title : DumpCpuStatus
.kind : C function
.creation : 3-Feb-1998
.description :
This function dumps the current CPU status into the string buffer 'ob'.
.call :
DumpCpuStatus(ob);
.input :
void
.output :
char ob[DUMP_CPU_STATUS_OB_SIZE];
.status_codes :
*
.notes :
1.1, 3-Feb-1998, creation
.- */
void DumpCpuStatus(char ob[DUMP_CPU_STATUS_OB_SIZE])
{
static const char *work_n[N_WORKING_REGISTER] = { "A", "B", "C", "D" };
char dob[DISASSEMBLE_OB_SIZE];
int n;
/* Dump PC and current instruction */
(void)Disassemble(cpu_status.PC, dob);
sprintf(ob, "%s\n\n", dob);
ob += strlen(ob);
/* Dump A, B, C, D */
for(n=0; n<N_WORKING_REGISTER; n++)
{
sprintf(ob, "%s:\t%s\n", work_n[n], DumpR(cpu_status.work[n]));
ob += strlen(ob);
}
sprintf(ob, "\n");
ob += strlen(ob);
/* Dump Rn */
for(n=0; n<N_SCRATCH_REGISTER; n++)
{
sprintf(ob, "R%d:\t%s\n", n, DumpR(cpu_status.R[n]));
ob += strlen(ob);
}
sprintf(ob, "\n");
ob += strlen(ob);
sprintf(ob, "D0:\t%05X\t\tD1:\t%05X\n", cpu_status.D0, cpu_status.D1);
ob += strlen(ob);
sprintf(ob, "P:\t%01X\t\tIN:\t%04X\t\tOUT:\t%03X\n",
cpu_status.P, cpu_status.IN, cpu_status.OUT);
ob += strlen(ob);
sprintf(ob, "HST:\t%01X\t\tST:\t%04X\n",
cpu_status.HST, cpu_status.ST);
ob += strlen(ob);
sprintf(ob,
"hexmode: %d, carry: %d, int_enable/pending/service: %d/%d/%d, shutdn:%d\n",
cpu_status.hexmode, cpu_status.carry,
cpu_status.int_enable, cpu_status.int_pending, cpu_status.int_service,
cpu_status.shutdn);
ob += strlen(ob);
}
/* .+
.title : OneStep
.kind : C function
.creation : 3-Feb-1998
.description :
This function executes a Saturn instruction starting from the current
program counter, updating accordingly the global cpu_status data structure.
The function signals all exceptional situations through Chf conditions.
.call :
OneStep()
.input :
void
.output :
void
.status_codes :
CPU_I_EXECUTING
CPU_E_BAD_OPCODE
CPU_F_INTERR
.notes :
1.1, 3-Feb-1998, creation
.- */
void OneStep(void)
{
Nibble n;
Address offset;
debug1(DEBUG_C_TRACE, CPU_I_EXECUTING, cpu_status.PC);
/* Get first instruction nibble */
n = GetNibble(cpu_status.PC++);
switch(n)
{
case 0: /* Group_0 */
ExecGroup_0();
break;
case 1: /* Group_1 */
ExecGroup_1();
break;
case 2: /* P=n */
SetP(GetNibble(cpu_status.PC++));
break;
case 3: /* LC(m) n...n */
FetchR(cpu_status.C, GetNibble(cpu_status.PC++));
break;
case 4: /* RTNC/GOC */
if(cpu_status.carry)
{
offset = Get2Nibbles2C(cpu_status.PC);
if(offset == 0)
cpu_status.PC = PopRSTK();
else
cpu_status.PC += offset;
}
else
cpu_status.PC += 2;
break;
case 5: /* RTNNC/GONC */
if(!cpu_status.carry)
{
offset = Get2Nibbles2C(cpu_status.PC);
if(offset == 0)
cpu_status.PC = PopRSTK();
else
cpu_status.PC += offset;
}
else
cpu_status.PC += 2;
break;
case 6: /* GOTO */
cpu_status.PC += Get3Nibbles2C(cpu_status.PC);
break;
case 7: /* GOSUB */
offset = Get3Nibbles2C(cpu_status.PC);
cpu_status.PC += 3;
PushRSTK(cpu_status.PC);
cpu_status.PC += offset;
break;
case 8: /* Group_8 */
ExecGroup_8();
break;
case 9: /* Test */
ExecTest_9();
break;
case 0xA: /* Register Operation, group A */
ExecRegOp_A();
break;
case 0xB: /* Register Operation, group B */
ExecRegOp_B();
break;
case 0xC: /* Register Operation, group C */
ExecRegOp_C();
break;
case 0xD: /* Register Operation, group D */
ExecRegOp_D();
break;
case 0xE: /* Register Operation, group E */
ExecRegOp_E();
break;
case 0xF: /* Register Operation, group F */
ExecRegOp_F();
break;
default:
ChfCondition CPU_E_BAD_OPCODE, CHF_ERROR, cpu_status.PC, n ChfEnd;
ChfSignal();
break;
}
}