/* ------------------------------------------------------------------------- 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 #include #include #include #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 #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= 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; } }