/* ------------------------------------------------------------------------- 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= 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>= 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 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 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